Welcome, guest | Sign In | My Account | Store | Cart

TwitterCmd is an interactive console for twitter built on top of python-twitter library. It provides short, easy to remember commands for most twitter actions and provides a one-way interaction with the Python interpreter, exposing its objects to further inspection by the python-twitter API.

Python, 396 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#!/usr/bin/env python

""" TwitterCmd - Interactive, console prompt to twitter
written on top of the python-twitter API.

A lot of credit to DeWitt Clinton and his team of
developers for the excellent 'python-twitter' API on
top of which, this program is written on.

Author: Anand B Pillai ('pythonhacker')
License: BSD License

"""

from twitter import Api
from cmd import Cmd
import sys, os
import re
import urllib2
import optparse
import time
import glob
import cPickle
import subprocess

__version__ = 0.3
__author__ = 'Anand B Pillai'
__lastmodified__ = "Thu Oct  8 14:39:24 IST 2009"
__modifications__ = """This version gets session save feature
and ability to enter Python prompt from TwitterCmd and back
while keeping state same across both. Basically you get the
simplicity of TwitterCmd plus the power of python-twitter
in one go!

Oct 8 - Bug in api attribute fixed, loading it fresh always.
        Reduced stuff printed in inspecting timeline & user.
"""

LOADER="""
from twittercmd import *
from twitter import Api
import cPickle, urllib2
obj=cPickle.load(open('%s', 'rb'))
if obj.username:
    obj.api=Api(obj.username)
else:
    obj.api=Api()
g=globals()
g.update(obj.__dict__)
"""

INTRO="""Welcome to TwitterCmd, a command line interface to twitter,
written in Python. TwitterCmd provides a simple, interactive
interface to Twitter with short, easy to remember commands.

For a full set of commands, type "help" on the prompt.
For help for a specific command try "help <cmd>".

Please send comments/bug reports to <abpillai at gmail dot com>                   
or tweet them to the ID 'pythonhacker'.
"""

HELP="""
The following commands are available.

User commands

1. l <user> <passwd> - Set login credentials.
2. u <user>          - Get details of a user.
3. f                 - Get friend's details.
4. fo                - Get followers' details.

Timeline commands

1. pt                - Get public timeline.
2. ut [<user>]       - Get current user timeline
                       (given no arguments), or
                       timeline of given user.
3. ft                - Get all friends' timeline.

Message commands

1. m <msg>           - Post a status message.
2. im <user> <msg>   - Post a direct message to user.
3. r                 - Get replies to messages.
4. dm                - Get all direct messages.

You can also use the 'inspect' or 'i' command to
inspect live objects. The following object types are
supported as argument.

1. s/status          - Inspect current msg status.
2. f/p/friends/peers - Inspect current friends status.
3. t/tl/tline        - Inspect current timeline.
4. u/user            - Inspect current user.

Version 0.2 also adds the power of entering Python
prompt while keeping state intact. To enter Python
prompt use any of 'shell','py' or 'python' commands.

Examples:

# Post a message
Twitter> l user pass
Twitter> m Hey, I love twitter! 

# Get direct messages
Twitter> dm
["'No problem. :-)' => dloss"]

# Get names of all friends
Twitter> f
['dloss', 'gvanrossum', 'MallikaLA', 'BarackObama', 'ShashiTharoor']

# Use Python prompt from TwitterCmd

Twitter> py
Entering Python shell...
>>>
# Inspect objects in shell and use python-twitter
# directly by using the 'api' object.
>>> api
<twitter.Api object at 0x7fb69e6509d0>
# All state of TwitterCmd is available as globals
# in the interpreter...
>>> tline
[<twitter.Status object at 0xe643ad0>, <twitter.Status object at 0xe643d50>]
>>> status
<twitter.Status object at 0x7fb69e643850>
>>> status.text
'Completed session saving and entering Python shell and back from TwitterCmd\
, code will be up in a while, Hooray!'
# Use api just as you would before...!
>>> [s.text for s in api.GetPublicTimeline()]
['tweeting from gravity',...]
# Exit Python prompt
>>> ^D
Exiting Python shell...
Twitter>

To exit a session, type q/quit/exit or Ctrl-D.
"""

class TwitterCmdException(Exception):
    pass

class TwitterCmd(Cmd):
    """ Python command interpreter to Twitter API. Allows a simple
    interactive interface to Twitter to those who prefer twittering
    from their *nix console, while providing the flexibility to
    switch back and forth from Python interactive prompt keeping
    state intact """

    commands = {'m' : 'PostUpdate', 'im': 'PostDirectMessage',
                'ut': 'GetUserTimeline', 'pt': 'GetPublicTimeline',
                'ft': 'GetFriendsTimeline', 'fo': 'GetFollowers',
                'f': 'GetFriends', 'r': 'GetReplies',
                'dm': 'GetDirectMessages', 'u':'GetUser',
                'l': 'SetCredentials'}
    
    prompt = 'Twitter> '
    intro = INTRO
    
    onecmd = lambda self, line: (line==None) and True
    emptyline = lambda self: None
    default = lambda self, line: None

    
    def __init__(self, user='', passwd=''):
        self.api = Api()
        if user and passwd:
            self.api.SetCredentials(user, passwd)
        # Current username
        self.username = user
        # Current command
        self.cmd = ''
        # Current message status
        self.status = None
        # Current timeline
        self.tline = None
        # Current user object
        self.user = None
        # Current friends/followers
        self.peers = None
        # System stuff
        # Python executable
        self.python = sys.executable
        Cmd.__init__(self)
        # Load previous session
        self.load_session()

    def __getstate__(self):
        odict = self.__dict__.copy()
        del odict['stdin']
        del odict['stdout']
        del odict['api']
        return odict

    def __setstate__(self, dict):
        # Don't update api object...
        try:
            del dict['api']
        except KeyError:
            pass
        self.__dict__.update(dict)
        
    def precmd(self, line):

        line = line.strip()
        if len(line)==0:
            return line
        
        if line.lower() in ('q','quit','exit','eof'):
            print
            self.save_session()
            return None

        if line.lower().startswith('help'):
            self.print_help(line)
            return line

        if line.lower() in ('shell','py','python'):
            # Start Python interpreter with current state
            self.run_wild()
            return line
            
        l = line.split(' ')
        cmd, rest = l[0], l[1:]
        # Inspect objects ?
        if cmd.lower() in ('i','inspect'):
            self.inspect(' '.join(l[1:]).lower())
            return line
        elif cmd not in self.commands:
            print "Command '%s' not understood" % cmd
            return line
        
        self.cmd = cmd.strip()
        try:
            self.action(*rest)
        except IndexError, e:
            print 'Command "%s" requires arguments!' % self.cmd
        except urllib2.HTTPError, e:
            print 'Twitter says:',e
        # Any other exception
        except Exception, e:
            print 'Twitter API says:',e
            
        return line

    def inspect(self, obj_type):
        """ Inspect our objects """

        if obj_type in ('status', 's', 'st'):
            if type(self.status) is list:
                if self.status: print [str(i) for i in self.status]
            else:
                print str(self.status)
        elif obj_type in ('tline','t','tl'):
            if self.tline: print [i.text for i in self.tline]
        elif obj_type in ('user','u'):
            print self.user
        elif obj_type in ('peers','friends','p','f'):
            if self.peers: print [(i.name, i.screen_name) for i in self.peers]       
        else:
            print 'Unknown object type',obj_type
            
    def action(self, *args):
        """ Perform a twitter action """
        
        f = getattr(self.api, self.commands.get(self.cmd, None))
        if self.cmd == 'l':
            if len(args)>=2:
                f(args[0], args[1])
            else:
                f(args[0], '')
            print 'Set login credentials'
        elif self.cmd == 'm':
            # Force IndexError
            x = args[0]
            self.status = f(' '.join(args))
            print repr(self.status)
        elif self.cmd == 'im':
            # Force IndexError
            x = args[0]            
            self.status = f(args[0], ' '.join(args[1:]))
            print repr(self.status)
        elif self.cmd == 'ut':
            self.tline = [f(args[0]) if len(args) else f()][0]
            print [s.text for s in self.tline]
        elif self.cmd == 'pt':
            self.tline = f()
            print [s.text for s in self.tline]
        elif self.cmd == 'ft':
            self.tline = f()
            print [s.text for s in self.tline]
        elif self.cmd == 'r':
            self.status = f()
            print [s.text for s in self.status]            
        elif self.cmd == 'dm':
            self.status = f()
            print [' => '.join(("'" + s.text + "'", s._sender_screen_name)) \
                   for s in self.status]            
        elif self.cmd in ('f','fo'):
            self.peers = f()
            print [s.screen_name for s in self.peers]
        elif self.cmd=='u':
            self.user = f(args[0])
            print self.user.name

    def load_session(self):
        """ Load most recent session from disk, if present """

        fnames = glob.glob(os.path.join(os.path.expanduser('~'),'.twitter_session_*'))
        if len(fnames):
            print 'Loading saved session...'
            try:
                obj = cPickle.load(open(fnames[0], 'rb'))
                self.__setstate__(obj.__dict__)
            except cPickle.UnpicklingError, e:
                print 'Error loading saved session'
        
    def save_session(self):
        """ Save current session to disk """

        fname = os.path.join(os.path.expanduser('~'), '.twitter_session_%d' % int(time.time()))
        try:
            # Remove older sessions
            olds = glob.glob(os.path.join(os.path.expanduser('~'),'.twitter_session_*'))
            cPickle.dump(self, open(fname, 'wb'))
            # Remove older sessions
            for f in olds:
                try:
                    os.remove(f)
                except OSError, e:
                    pass
        except cPickle.PicklingError, e:
            print 'Error saving current session to disk:',e

        return fname

    def run_wild(self):

        session = self.save_session()
        # Create a module that will load the saved session
        loader = LOADER % session
        module = '.twitter_loader.py'
        open(module,'w').write(loader)
        print 'Entering Python shell...'
        subprocess.call([self.python,"-i", module])
        print 'Exiting Python shell...'
        
    def print_help(self, line):
        if line.lower()=='help':
            print HELP
        else:
            l = line.split(' ')
            cmd, rest = l[0], ' '.join(l[1:])
            if cmd.lower() != 'help':
                return
            if rest=='m':
                print 'm <msg>: Post status message <msg>'
            elif rest=='im':
                print 'm <user> <msg>: Post direct message <msg> to user <user>'
            elif rest=='ut':
                print 'ut [<user>]: Get current user timeline without arguments\n\
                  and given user timeline with arguments'
            elif rest=='pt':
                print 'pt: Get twitter public timeline'
            elif rest=='ft':
                print "ft: Get all friends' timelines"
            elif rest=='fo':
                print 'fo: Get all follower details'
            elif rest=='f':
                print "f: Get all friends' details"
            elif rest=='dm':
                print 'dm: Get all direct messages'
            elif rest=='r':
                print 'r: Get all replies to direct messages'
            elif rest=='u':
                print 'u <user>: Get a given user details'
            elif rest=='l':
                print 'l <user> <passwd>: Set current user credentials'
            elif rest in ('i','inspect'):
                print 'i <obj_type>: Inspect object of given type'
            else:
                print 'No such command "%s"' % rest
                
        
if __name__ == "__main__":
    p = optparse.OptionParser()
    p.add_option('-u','--user',dest='user',default='',help='Optional Twitter username')
    p.add_option('-p','--passwd',dest='passw',default='',help='Optional Twitter password')

    options, args = p.parse_args()
    user, passwd = options.user, options.passw
    TwitterCmd(user, passwd).cmdloop()

TwitterCmd provides easy to use, shortcuts for most twitter actions. Here is a sample session.

Login with username/password

Twitter> l pythonhacker <passwd>
Set login credentials

Post an update

Twitter> m Posting TwitterCmd to Aspn Python cookbook website now... <twitter.Status object at 0x1293490>

User timeline

Twitter> ut
['Posting TwitterCmd to Aspn Python cookbook website now...', 'OK I am done, enuf hacking for 2 days, hope TwitterCmd is useful and worth the effort, drop me a tweet if found useful!,...]

Public timeline

Twitter> pt
['@tiws80 hehehehe... gw juga lupa... siapa yg sok tau ya??? huahahahahaha....', 'got out of class two hours early, good thing sense this night sucks,...]

User timeline of another user

Twitter> ut dloss
['Principal Programming Paradigms Poster http://bit.ly/krgct "More is not better (or worse) than less, just different"',...]

Direct messages

Twitter> dm
["'No problem. :-)' => dloss", "'Well done! BTW: You seem to be using Scapy v1. Check out Scapy v2 and its new Sphinx-based docs: http://is.gd/3PkI0' => dloss"]

Replies

Twitter> r
['@ershad_k http://bit.ly/1HoxQT by @pythonhacker', "testing @pythonhacker's command line client",...]

Send a direct message to another user

Twitter> im pythonhacker2 Hey dude, how about soccer tonight ?
<twitter.DirectMessage object at 0x11045b0>

Get name of a user

Twitter> u crackpot
Shirley Twitter> u pythonhacker
Anand B Pillai

Inspect commands

Inspect the most recent status message

Twitter> i s
{"created_at": "Wed Oct 07 04:24:08 +0000 2009", "id": 434985222, "recipient_id": 79802660, "recipient_screen_name": "pythonhacker2", "sender_id": 6158512, "sender_screen_name": "pythonhacker", "text": "Hey dude, how about soccer tonight ?"}

Inspect the most recent user

Twitter> i u
{"description": "Developer, Programmer, Hacker, Python Enthusiast...", "favourites_count": 1, "followers_count": 47, "friends_count": 5, "id": 6158512, "location": "Bangalore, India", "name": "Anand B Pillai", "profile_background_color": "0099B9", "profile_background_tile": false, "profile_image_url": "http://a1.twimg.com/profile_images/288309206/python_normal.png", "profile_link_color": "0099B9", "profile_sidebar_fill_color": "http://s.twimg.com/a/1254855407/images/themes/theme4/bg.gif", "profile_text_color": "3C3940", "protected": false, "screen_name": "pythonhacker", "status": {"created_at": "Wed Oct 07 04:22:17 +0000 2009", "favorited": false, "id": 4674679432, "source": "<a href=\"http://apiwiki.twitter.com/\" rel=\"nofollow\">API</a>", "text": "Posting TwitterCmd to Aspn Python cookbook website now...", "truncated": false}, "statuses_count": 40, "time_zone": "Chennai", "url": "http://harvestmanontheweb.com", "utc_offset": 19800}

Inspect the most recent timeline

Twitter> i t
['{"created_at": "Mon Oct 05 21:47:07 +0000 2009", "favorited": false, "id": 4638567339, "source": "<a href=\\"http://www.mrrsoftware.com/MRRSoftware/Syrinx.html\\" rel=\\"nofollow\\">Syrinx</a>", "text": "Principal Programming Paradigms Poster http://bit.ly/krgct \"More is not better (or worse) than less, just different\"",...]

This also allows to jump to Python prompt carrying its state across, thereby allowing the more avid Python developer to use it as a tool for twittering plus a channel for further inspection using python-twitter.

Twitter> py
Entering Python shell...

All state is available in the same name...

>>> status  
<twitter.DirectMessage object at 0x10fa910>
>>> status.text  
'Hey dude, how about soccer tonight ?'
>>> tline
[<twitter.Status object at 0x10facd0>, <twitter.Status object at 0x11034d0>, <twitter.Status object at 0x1103850>, <twitter.Status object at 0x1103bd0>, <twitter.Status object at 0x1103f50>, <twitter.Status object at 0x11052f0>, <twitter.Status object at 0x1105670>, <twitter.Status object at 0x1105a10>, <twitter.Status object at 0x1105dd0>, <twitter.Status object at 0x11081b0>, <twitter.Status object at 0x1108530>, <twitter.Status object at 0x11088b0>, <twitter.Status object at 0x1108c30>, <twitter.Status object at 0x1108fb0>, <twitter.Status object at 0x1109350>, <twitter.Status object at 0x11096d0>, <twitter.Status object at 0x1109a50>, <twitter.Status object at 0x1109dd0>, <twitter.Status object at 0x110b170>, <twitter.Status object at 0x110b510>]



>>> [i.text for i in tline]
['Principal Programming Paradigms Poster http://bit.ly/krgct "More is not better (or worse) than less, just different"', 'RT @justinvincent: Entire Cities Recreated Using Thousands of Flickr Photos http://bit.ly/4lFOaF', 'The Google Technology Stack http://bit.ly/18fN10 (via @justinvincent) - Maybe I should read more science papers instead of buying books...',...]
>>> user
<twitter.User object at 0x110bd10>
>>> user.name
'Anand B Pillai'
>>> user.name, user.screen_name
('Anand B Pillai', 'pythonhacker')

Exit the Python shell

>>> ^D
Exiting Python shell...
Twitter> 
<h4>Exit TwitterCmd</h4>

Twitter> ^D
anand-b-pillais-computer-2:~/programs/python/twitter>

State transfer is done using Pickled files and a custom loader script which is saved to the current directory. Session files are saved and loaded automatically when exiting and entering the prompt.

1 comment

Anand (author) 14 years, 6 months ago  # | flag

Find the most uptodate version of this script at http://harvestmanontheweb.com/python/twitter/ always.