{"id":203,"date":"2009-08-04T21:20:13","date_gmt":"2009-08-05T02:20:13","guid":{"rendered":"http:\/\/osric.com\/chris\/accidental-developer\/?p=203"},"modified":"2009-08-05T09:22:06","modified_gmt":"2009-08-05T14:22:06","slug":"twitter-status-ids-and-direct-message-ids","status":"publish","type":"post","link":"https:\/\/osric.com\/chris\/accidental-developer\/2009\/08\/twitter-status-ids-and-direct-message-ids\/","title":{"rendered":"Twitter Status IDs and Direct Message IDs"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/osric.com\/chris\/accidental-developer\/wp-content\/uploads\/2009\/08\/twitter-bird.png\" alt=\"twitter-bird\" title=\"twitter-bird\" width=\"80\" height=\"55\" class=\"alignleft size-full wp-image-206\" \/>I recently created a <a href=\"http:\/\/twitter.com\/osric8ball\">Magic Eight Ball twitter-bot<\/a> as a demo. Written in <a href=\"http:\/\/www.python.org\/\">Python<\/a> using the <a href=\"http:\/\/code.google.com\/p\/python-twitter\/\">python-twitter<\/a> API wrapper, it runs every 2 minutes and polls <a href=\"http:\/\/twitter.com\/\">twitter<\/a> for new replies (status updates containing <em>@osric8ball<\/em>) and direct messages (DMs) to <em>osric8ball<\/em>. If there are any, it replies with a random 8-Ball response.<\/p>\n<p>Every status update and DM has an associated numeric ID. Initially, I stored the highest ID in a log file and used that when I polled twitter (i.e. &#8220;retrieve all replies and DMs with ID > highest ID&#8221;). However, I discovered that status updates and DMs apparently are stored in separate tables on twitter&#8217;s backend, as they have a separate set of IDs. Since the highest status ID was an order of magnitude larger than the highest DM ID, my bot completely ignored all DMs. This was not entirely obvious at first, as the IDs looked very similar, other than an extra digit: 2950029179 and 273876291.<\/p>\n<p>My fix for this was to store both the highest status update ID and the highest DM ID is separate log files.<\/p>\n<p>Another interesting twist: you have to be a follower of a user in order to send that user a DM.<!--more--><br \/>\nAt first I thought I had solved this by telling the bot to follow anyone who sent it a message. However, if you submit an API request to follow a user whom you are already following, it produces an error. So I added a check to see if the bot was already following the user, and, if not, follow the user.<\/p>\n<p>In case anyone is interested, the entire code is below:<br \/>\n<code>#!\/usr\/bin\/python2.4<\/p>\n<p>import twitter<br \/>\nimport random<\/p>\n<p>replyfile = 'twitterreply.log'<br \/>\ndmfile = 'twitterdm.log'<br \/>\nlast_id = 0<\/p>\n<p>api = twitter.Api(username='osric8ball', password='[actualpassword]')<\/p>\n<p>friends = api.GetFriends()<\/p>\n<p>responses = ['Signs Point To Yes','Yes','You May Rely On It','Ask Again Later','Concentrate And Ask Again','Outlook Good','My Sources Say No','Better Not Tell You Now','Without A Doubt','Reply Hazy, Try Again','It Is Decidedly So','Cannot Predict Now','My Reply Is No','As I See It Yes','It Is Certain','Yes Definitely','Don\\'t Count On It','Most Likely','Very Doubtful','Outlook Not So Good']<\/p>\n<p>def areWeFriends(screen_name):<br \/>\n        isFriend = 0<br \/>\n        for friend in friends:<br \/>\n                if friend.screen_name == screen_name:<br \/>\n                        isFriend = 1<br \/>\n        return isFriend<\/p>\n<p>def makeFriend(screen_name):<br \/>\n        if areWeFriends(screen_name) == 0:<br \/>\n                api.CreateFriendship(user=screen_name)<\/p>\n<p>def sendReply(screen_name):<br \/>\n        makeFriend(screen_name)<br \/>\n        response = responses[random.randint(0,19)]<br \/>\n        api.PostUpdates('@' + screen_name + ': ' + response)<br \/>\n        print '@' + screen_name + ': ' + response<\/p>\n<p>def sendDirectMessage(screen_name):<br \/>\n        makeFriend(screen_name)<br \/>\n        response = responses[random.randint(0,19)]<br \/>\n        api.PostDirectMessage(screen_name,response)<br \/>\n        print 'd ' + screen_name + ': ' + response<\/p>\n<p>#Read logs<br \/>\nlog = open(replyfile, 'r')<br \/>\nlast_reply_id = log.read()<br \/>\nlog.close()<\/p>\n<p>log = open(dmfile, 'r')<br \/>\nlast_dm_id = log.read()<br \/>\nlog.close()<\/p>\n<p>if last_reply_id &gt; 0 and last_dm_id &gt; 0:<\/p>\n<p>        #Get replies and direct messages<br \/>\n        replies = api.GetReplies(since_id=last_reply_id)<br \/>\n        directMessages = api.GetDirectMessages(since_id=last_dm_id)<\/p>\n<p>        #Update logs<br \/>\n        if directMessages != []:<br \/>\n                last_dm_id = directMessages[0].id<\/p>\n<p>        if replies != []:<br \/>\n                last_reply_id = replies[0].id<\/p>\n<p>        if last_reply_id &gt; 0:<br \/>\n                log = open(replyfile,'w')<br \/>\n                log.write( str(last_reply_id) )<br \/>\n                log.close()<\/p>\n<p>        if last_dm_id &gt; 0:<br \/>\n                log = open(dmfile,'w')<br \/>\n                log.write( str(last_dm_id) )<br \/>\n                log.close()<\/p>\n<p>        # Loop through replies<br \/>\n        for reply in replies:<br \/>\n                sendReply(reply.user.screen_name)<\/p>\n<p>        # Loop through direct messages<br \/>\n        for directMessage in directMessages:<br \/>\n                sendDirectMessage(directMessage.sender_screen_name)<\/p>\n<p>else:<br \/>\n        print 'Unable to read ' + replyfile + ' or ' + dmfile<br \/>\n<\/code><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I built a twitter-bot in Python using the python-twitter API wrapper (code included in post). Twitter status update IDs and direct message (DM) IDs use different sequences, which is important if you are trying to retrieve messages posted after a specified ID.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86,40],"tags":[358,354,85,87],"class_list":["post-203","post","type-post","status-publish","format-standard","hentry","category-python","category-twitter","tag-python","tag-twitter","tag-twitter-api","tag-twitter-bot"],"_links":{"self":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/203","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/comments?post=203"}],"version-history":[{"count":6,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/203\/revisions"}],"predecessor-version":[{"id":211,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/203\/revisions\/211"}],"wp:attachment":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/media?parent=203"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/categories?post=203"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/tags?post=203"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}