?

Log in

No account? Create an account
Using Python and XMLRPC to post to LJ - LiveJournal Client Discussions [entries|archive|friends|userinfo]
LiveJournal Client Discussions

[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Using Python and XMLRPC to post to LJ [May. 1st, 2006|04:05 am]
LiveJournal Client Discussions

lj_clients

[fallen_x_ashes]
[mood |curiouscurious]

I'm VERY new at this. (Both to using XMLRPC and Python, infact I wouldn't say I really know what I'm doing, merely coding from imitation and some basic knowledge of the general syntax of most programming languages.) Basically I started doing this when it seemed like the Blogger API had been removed from Livejournal. I couldn't make a post using it. So, I set about altering my application to use the LiveJournal XMLRPC. It's a very quick and dirty hack, I'm passing passwords in the clear essentially. I'm VERY close, it works for LJ.XMLRPC.login, but when I try LJ.XMLRPC.postevent I get the following error message:

XML-RPC Fault: ServerCan't use string ("1") as a HASH ref while "strict refs" in use at /home/lj/cgi-bin/ljprotocol.pl line 2527, [anglebracket]GEN2[anglebracket] line 41.

I added the [anglebracket] thing there. The line number following Gen2 changes with each try, so I'm not sure if it really matters. Here's the Code for the function responsible for creating the post:


def newPostLiveJournal(logger, audioFilePath, cgiAudioFilePath, AuthMethod, LineEndings,
xmlrpcServer, blogID, userName, password, publish, useNewMediaObject, nmoDestPath):
"""Adds a new post to a LiveJournal"""

logger.write('In newPostLiveJournal\n')
logger.flush()
postID = {}
postID['itemid'] = -1
LJEvent = {}
LJEvent['username'] = userName
LJEvent['auth_method'] = AuthMethod
LJEvent['password'] = password
LJEvent['event'] = '

This post was created with PhoneBlogger. ' \
+ 'Click to listen to the ' \
+ 'recorded message.

'
LJEvent['lineendings'] = LineEndings
LJEvent['subject'] = 'Post from PhoneBlogger'
LJEvent['year'] = time.localtime()[0]
LJEvent['mon'] = time.localtime()[1]
LJEvent['day'] = time.localtime()[2]
LJEvent['hour'] = time.localtime()[3]
LJEvent['min'] = time.localtime()[4]
logger.write('Set LJEvent\n')
logger.flush()
try:
postID = xmlrpcServer.LJ.XMLRPC.postevent(LJEvent and xmlrpclib.True or xmlrpclib.False)
except xmlrpclib.Fault, fault:
logger.write('XML-RPC Fault: ' + fault.faultCode + fault.faultString + '\n')
logger.flush()
return postID['itemid']


Does anyone have any ideas as to what could be wrong, or how I can go about debugging this?
linkReply

Comments:
[User Picture]From: curtis
2006-05-01 08:23 pm (UTC)
I'm no python programmer (I use a few different languages, myself...) but shouldn't the main line be this? [Note the placement of parentheses compared to what you pasted in...]

postID = xmlrpcServer.LJ.XMLRPC.postevent(LJEvent) and xmlrpclib.True or xmlrpclib.False
(Reply) (Thread)
[User Picture]From: vulture23
2006-05-01 08:35 pm (UTC)
I'm presuming that the LJEvent['event'] line is a result of misplaced formatting, and that you're actually assigning a valid string there. (The way that it's shown, it doesn't look like a valid string, but that's likely due to bad line-wrapping.)

I'm not sure what your intent is in the postevent() call, using "LJEvent and xmlrpclib.True or xmlrpclib.False" -- that's really not a very good way to simulate a conditional expression, even if it works most of the time, and I'd strongly advise rewriting that with an explicit if statement. That will both make the intent of your code clearer to someone reading it, *and* will reduce the possibility that some unexpected side-effect is happening there.

I don't have access to an interpreter at the moment to check, but are you sure that "X and Y" (if true) evaluates to X? The error message you're getting would be appropriate if that statement evaluates to Y -- you'd be posting an event consisting only of xmlrpclib.True, which should evaluate to 1, which gets converted to a string in the process of being sent over xml-rpc.
(Reply) (Thread)
[User Picture]From: fallen_x_ashes
2006-05-02 04:16 am (UTC)

And I have no idea what I'm doing there either.

That conditional section is actually something I mimiced from a function specifically meant to work with Movable Type. I have never seen such a structure for conditional statements before, as I'm used to operators ala C. In fact I think that you may have caught the reason for a bug in the program: Wordpress drafts are always posted even if the publish variable is set to 0. The originol line of coding is here:

postID = xmlrpcServer.metaWeblog.newPost(blogID, userName, password, content, publish and xmlrpclib.True or xmlrpclib.False)


The author of this software was himself new to python when he wrote it, so it's possible that he didn't realize what was going on when he wrote it. It does appear that you're right, this seems to evaluate to Y rather than X.

I actually don't even really follow the logic. How would this look like as an explicit if statement? It'd be really helpful if you could show me that.

Anyway, since it really didn't seem like that conditional statement was meant to do something other then work with the publish variable, and since it was absent from the function that posts to Blogger, I removed it from the code completely. And it works! Almost. The Post goes through but there's a runtime error that occurs shortly after it.

Now it seems I'm having a problem returning the itemid. I think that I may be taking for granted the way the struct gets passed back to me. Is the standard xmlrpc library in python good enough to work with it? I really don't have much of a clue as to why the error could be occuring other then perhaps itemid turns out to be undefined. As soon as I try to touch it anywhere in the code, such as printing out its value, the script terminates.

Here's the code as it looks like so far, as I've made a few changes, (I apologize in advance for any line-wrapping problems, they will hopefully be minimal):

def newPostLiveJournal(logger, audioFilePath, cgiAudioFilePath, AuthMethod, LineEndings, xmlrpcServer, blogID, userName, password, publish, useNewMediaObject, nmoDestPath):
"""Adds a new post to a LiveJournal"""

logger.write('In newPostLiveJournal\n')
logger.flush()
LJResponse = {}
LJEvent = {}
LJEvent['username'] = userName
LJEvent['auth_method'] = AuthMethod
LJEvent['password'] = password
LJEvent['event'] = '

This post was created with PhoneBlogger. ' + 'Click to listen to the ' + 'recorded message.

'
LJEvent['lineendings'] = LineEndings
LJEvent['subject'] = 'Post from PhoneBlogger'
LJEvent['year'] = time.localtime()[0]
LJEvent['mon'] = time.localtime()[1]
LJEvent['day'] = time.localtime()[2]
LJEvent['hour'] = time.localtime()[3]
LJEvent['min'] = time.localtime()[4]
logger.write('Set LJEvent\n')
logger.flush()
try:
LJResponse = xmlrpcServer.LJ.XMLRPC.postevent(LJEvent)
logger.write('Set LJResponse\n')
except xmlrpclib.Fault, fault:
logger.write('XML-RPC Fault: ' + fault.faultCode + fault.faultString + '\n')
logger.flush()
itemid = LJResponse['itemid']
logger.write(itemid)
logger.write('\n')
logger.write('Now Returning\n')
logger.flush()
return itemid
(Reply) (Parent) (Thread)
[User Picture]From: fallen_x_ashes
2006-05-02 04:22 am (UTC)

Re: And I have no idea what I'm doing there either.

Argh! The indentation has been stripped! Rest assured that this stuff is being indented as it should, with the first two lines after the try: and except: lines indented.

The problem occurs on logger.write(itemid), the script fails right there.
(Reply) (Parent) (Thread)
[User Picture]From: vulture23
2006-05-02 04:56 am (UTC)

Re: And I have no idea what I'm doing there either.

The move into html (as it gets posted to LJ) will likely eat any leading spaces. This is a common issue when posting Python code. :)

How that would look as an explicit if statement kinda depends on what the intention is. "X and Y or Z" is a well-known Python hack that's intended to emulate C's ternary operator (x ? y : z , if I'm remembering my C syntax correctly). However, it has some gotchas that C doesn't have, and it's really not obvious how it works until the trick has been pointed out to you -- and nonobvious code is dangerous code, because you *will* make mistakes with it sooner or later.

Here, if LJEvent is not an empty dict, then "LJEvent and xmlrpclib.True" evaluates to true, and takes on the value of the second operand. If LJEvent *is* empty, then the 'and' is false and evaluates to LJEvent, at which point "LJEvent or xmlrpclib.False" is evaluated. If we're here, LJEvent is false, so the 'or' evaluates to the second operand. Thus, in any case, "X and Y or Z" will evaluate to either Y or Z, but never X. Your code has this expression as the argument for postevent(), meaning that you will always submit either True or False as an argument, but never LJEvent. I can't guess what was intended by the original author of this code, but... in general this construct is better rendered as:

if X:
....return Y
else:
....return Z

(or using assignment or function call or whatever instead of return.)

Not sure where you're having problems with the itemid -- my LJ xmlrpclib code looks pretty similar at that point, and I haven't had any problems. When it terminates, are you getting a stack trace? If so, copy & paste the whole thing here; there's a lot of useful info in those stack traces.
(Reply) (Parent) (Thread)
From: eyeh8u
2006-05-02 07:58 am (UTC)
IIRC,

In Python and returns the last part of the statement if true. or returns the first part that is true (shortcircuit operator). and must walk the full statement checking each side to see that the statement is true.

So x and y will return y if x and y are both true.

So, given publish and xmlrpclib.True, xmlrpclib.True will be returned. That will then shortcircuit return ragardless of xmlprclib.False as or returns when the first clause that is true is found.

Dive Into Python has a good section on this in the peculiar nature of and and or. Worth a read.
(Reply) (Parent) (Thread)
[User Picture]From: xb95
2006-05-01 09:43 pm (UTC)
Why don't you fix your code's formatting and then I can take a look at it.
(Reply) (Thread)
[User Picture]From: hythloday
2006-05-02 12:23 pm (UTC)
[conditional] and [truth] or [falsehood] is one of my pet peeves in any language. Just return [conditional] - in a statically typed language, it'll be coerced automatically - in a dynamic one, it'll be evaluated correctly.

And use <pre> to wrap python code.
(Reply) (Thread)
[User Picture]From: evilhat
2006-05-03 10:19 pm (UTC)
Just for reference, my own open source, Python-based client, Charm, successfully implements a range of protocols -- LiveJournal's traditional and XML-RPC interfaces, the Atom API (Blogger, Movable Type, etc.), and the Metaweb API.

You may want to look at that for code examples.


(Reply) (Thread)