Пример #1
0
	def get(self):

		if REQUIRE_INVITATION:
			code = self.request.get("code", default_value="")
			if not removeInvitation( code ):
				self.redirect( "/oauth/sign?error=Invalid%20invitation%20code" )
				# self.response.write("Invalid invitation code, authorization aborted.")
				return

		tc = TwitterConsumer(CONSUMER_KEY, CONSUMER_SECRET, CALLBACK_URL) 
		tc.request_token()

		entity = OAuthRequest(token=tc.token)
		entity.token_secret = tc.token_secret
		entity.put()
		#self.response.write("addr: https://api.twitter.com/oauth/authenticate?oauth_token=%s" % tc.token)
		self.redirect( "https://api.twitter.com/oauth/authorize?oauth_token=%s&force=1" % tc.token )
Пример #2
0
	def __init__(self, account):
		"""
			need account name to initialize this class
			fed with all keys necessary, I will get ready to sign requests
		"""
		self.account = account
		a_token, a_secret, self.uid = getUserInfo(account)
		self.tc = TwitterConsumer(CONSUMER_KEY, CONSUMER_SECRET)
		self.tc.for_user(a_token, a_secret)
Пример #3
0
	def get(self):
		token = self.request.get('oauth_token')
		verifier = self.request.get('oauth_verifier')
		
		# find token in database...
		query = db.GqlQuery( 
			"SELECT * "
				"FROM OAuthRequest "
				"WHERE token = :1 ",
			token)
		entity = query.get()
		if entity is None:
			self.response.write("error: cannot find")
			return

		# prepare for obtaining access_token
		tc = TwitterConsumer(CONSUMER_KEY, CONSUMER_SECRET, CALLBACK_URL)
		tc.token = entity.token
		tc.token_secret = entity.token_secret
		tc.verifier = verifier
		
		# obtain access token
		tc.access_token()
		
		if not tc.has_user():
			self.response.write("failed when verifying.")
			return

		entity.user_id = tc.user_id
		entity.access_token = tc.a_token
		entity.access_secret = tc.a_secret
		
		# dump all codes
		query = db.GqlQuery(
			"SELECT code "
				"FROM OAuthRequest ")
		code_list = []
		for e in query:
			if e.code is not None:
				code_list.append( int(e.code) )

		# select one unique code as PIN
		code = random.randint(0, 999999)
		while code in code_list:
			code = random.randint(0, 999999)

		entity.code = "%06d" % code
		entity.put()
		#self.response.write("use code: %s to bind your gtalk." % entity.code )
		self.redirect( "/oauth/finish?code=%s" % entity.code )
Пример #4
0
class TwitterAPIWrapper(object):
	"""
		twitter api wrapper, provide context for commands
		that requires twitter authorization
	"""

	def __init__(self, account):
		"""
			need account name to initialize this class
			fed with all keys necessary, I will get ready to sign requests
		"""
		self.account = account
		a_token, a_secret, self.uid = getUserInfo(account)
		self.tc = TwitterConsumer(CONSUMER_KEY, CONSUMER_SECRET)
		self.tc.for_user(a_token, a_secret)

	def post(self, url, r):
		"""
			shortcut for post requests
			return (True, response) on success, 
			and return (False, response) on failure
			explanation of errors will be written on 'r'
		"""
		tr = TwitterRequest("POST", url )
		response = self.tc.get_response( tr )
		return (self._judgeStatus( response, r ), response)

	def get(self, url, r):
		"""
			shortcut for get requests
			return (True, response) on success, 
			and return (False, response) on failure
			explanation of errors will be written on 'r'
		"""
		tr = TwitterRequest("GET", url )
		response = self.tc.get_response( tr )
		return (self._judgeStatus( response, r ), response)

	def _parseList(self, raw, builder):
		"""
			parse items in list into classes
			raw: raw json data
			builder: function that reads data and products a corresponding class
			* list will be reversed for reading conveniences
		"""
		jdata = json.loads( raw )
		result = map(builder, jdata)
		result.reverse()
		return result

	def _judgeStatus(self, response, r):
		"""
			read, judge status
			on success:
			  return True
			on failure:
			  return False, and write explantion to "r"
		"""
		if (response.status!= 200):
			r.l( ("status", response.status) )
			if response.status == 404:
				return
			err_info = json.loads( response.read() )
			try:
				r.l( '!%s' % err_info["errors"][0]["message"] )
			except:
				try:
					r.l( "!%s" % err_info["errors"] )
				except:
					r.l( '!something went wrong...' )
					r.l( unicode( err_info ) )

			return False
		else:
			return True
		

	def twiCmdUpdate(self, params, r):
		"""
			# ttweet
			% send a new tweet
			% format: .update <text>
			% simply type in messages leading with anything but '.'
			% will send tweets as well, for messages leading with '.'
			% duplicating '.' will make it
			% e.g. "..net is good!" products ".net is good!"
		"""
		if params.isspace() or len(params) == 0:
			r.l("!blank text")
			return
		params = twig_command.cmdLengthCheck(None, params, r) 
		url = mkURL("https://api.twitter.com/1.1/statuses/update.json", status=params)
		succ, response = self.post(url, r)
		if not succ:
			return
		r.l( "sent" )

	def twiCmdHomeTimeline(self, params, r):
		"""
			# ttweet
			% show my home timeline
			% format: .ho [where]
			% use command '.where' for how to use '[where]'
		"""
		extra = tools.parseWhere( params )
		if extra is None:
			r.l("!bad page argument")
			return
		url = mkURL( "https://api.twitter.com/1.1/statuses/home_timeline.json", **extra )
		succ, response = self.get(url, r)	
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.Tweet )
		r.l( tl )

	def twiCmdMention(self, params, r):
		"""
			# ttweet
			% show my mentions timeline
			% * if you are seeking help for how to reply others,
			%   please refer to '.help reply'
			% format: .mention [where]
			% use command '.where' for how to use '[where]'
		"""
		extra = tools.parseWhere( params )
		if extra is None:
			# there is another form of reply, jump to it.
			self.twiCmdReply( params, r )
			return

		url = mkURL( "https://api.twitter.com/1.1/statuses/mentions_timeline.json", **extra )
		succ, response = self.get(url, r)	
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.Tweet )
		r.l( tl )

	def twiCmdReply(self, params, r):
		"""
			# ttweet
			% reply only to the author of a given tweet
			% * if you want to reply all users mentioned in a tweet,
			%   please refer to '.help replyall'
			% format: .reply <id> <text>
			% e.g.: .reply 123456 I'm fine thank you, and you?
		"""
		pat = re.compile(r'^(\d+)(?:\s+)(.*)$')
		m = pat.match( params )
		if m is None:
			r.l( "!bad argument")
			return
		tid, text = m.group(1), m.group(2) 

		# at first we should find this tweet ...
		url = "https://api.twitter.com/1.1/statuses/show/%s.json" % tid
		succ, response = self.get( url, r )
		if not succ:
			r.l( "!failed when fetching origin tweet" )
			return
		user = json.loads( response.read() )["user"]["screen_name"]
		text = "@%s %s" % (user, text)
		text = twig_command.cmdLengthCheck( None, text, r)

		# now send text!
		url = mkURL( "https://api.twitter.com/1.1/statuses/update.json", status=text, in_reply_to_status_id=tid)
		succ, response = self.post( url, r )
		if not succ:
			r.l("!failed when sending reply")
			return
		
		r.l( "replied to %s" % user )
		 
	def twiCmdReplyAll(self, params, r):
		"""
			# ttweet
			% reply to all users mentions in a given tweet
			% * if you want to reply only to the author of a tweet,
			%   please refer to '.help reply'
			% format: .replyall <id> <text>
			% e.g.: .replyall 123456 I'm fine thank you, and you?
		"""
		pat = re.compile(r'^(\d+)(?:\s+)(.*)$')
		m = pat.match( params )
		if m is None:
			r.l( "!bad argument")
			return
		tid, text = m.group(1), m.group(2) 

		# at first we should find this tweet ...
		url = "https://api.twitter.com/1.1/statuses/show/%s.json" % tid
		succ, response = self.get( url, r )
		if not succ:
			r.l( "!failed when fetching origin tweet" )
			return
		data = json.loads( response.read() ) 
		# metion list build: mention list should contain author
		# and all users mentioned except you
		author = data["user"]["screen_name"]
		# make use of "entities" to find users mentioned
		user_list = data["entities"]["user_mentions"]
		# let's filter out myself and transform it into a list
		# that contains screen name only
		user_list = filter ( lambda user: user["id_str"] != self.uid, user_list)
		user_list = map (lambda user: "******"+user["screen_name"], user_list )
		user_list.append( "@" + author )
		user_list = sorted( list( set( user_list ) ) )

		text = "%s %s" % (' '.join(user_list), text)
		text = twig_command.cmdLengthCheck( None, text, r)
		# ok I'm ready to send
		url = mkURL( "https://api.twitter.com/1.1/statuses/update.json", status=text, in_reply_to_status_id=tid)
		succ, response = self.post( url, r )
		if not succ:
			r.l("!failed when sending reply")
			return
		
		r.l( "replied to %s" % ' '.join( map(lambda t: t[1:], user_list) ) )
	
	def twiCmdDirectMessage(self, params, r):
		"""
			# tdm
			% view received direct messages (inbox)
			% format: .dm [where]
			% use command '.where' for how to use '[where]'

		"""
		extra = tools.parseWhere( params )
		if extra is None:
			r.l("!bad page argument")
			return
		url = mkURL ( "https://api.twitter.com/1.1/direct_messages.json", **extra )
		succ, response = self.get( url, r )	
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.DirectMessage )
		r.l( tl )

	def twiCmdDirectMessageSent(self, params, r):
		"""
			# tdm
			% view sent direct messages (outbox)
			% format: .dout [where]
			% use command '.where' for how to use '[where]'

		"""
		extra = tools.parseWhere( params )
		if extra is None:
			r.l("!bad page argument")
			return
		url = mkURL ( "https://api.twitter.com/1.1/direct_messages/sent.json", **extra )
		succ, response = self.get( url, r )	
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.DirectMessage )
		r.l( tl )

	def twiCmdDirectMessageNew(self, params, r):
		"""
			# tdm
			% send direct message
			% * if you want help of checking received direct message
			%   please refer to ".dm"
			% format: .d <user> <message>
		"""
		pat = re.compile(r"([A-Za-z0-9_]+)\s+(.*)")
		m = pat.match( params )
		if m is None:
			# user might want to check inbox by this command
			self.twiCmdDirectMessage( params, r )
			return
		usr, text = m.groups()
		url = mkURL( "https://api.twitter.com/1.1/direct_messages/new.json", screen_name=usr, text=text)
		succ, response = self.post( url, r )
		if not succ:
			return
		r.l( "direct message sent" )

	def twiCmdDirectMessageDestroy(self, params, r):
		"""
			# tdm
			% delete a direct message
			% format: .d- <direct message id>
		"""
		if not params.isdigit():
			r.l( "!bad argument" )
			return

		url = mkURL( "https://api.twitter.com/1.1/direct_messages/destroy.json", id=params )
		succ, response = self.post( url, r)
		if not succ:
			return
		r.l( "direct message removed" )
		
	def twiCmdFollow(self, params, r):
		"""
			# tfriend
			% follow user
			% format: .fo <user>
		"""
		# XXX: potential injection
		url = mkURL( "https://api.twitter.com/1.1/friendships/create.json", screen_name = params )
		succ, response = self.post( url, r )
		if not succ:
			return
		name = json.loads( response.read() )["screen_name"]
		r.l("followed %s" % name)

	def twiCmdUnFollow(self, params, r):
		"""
			# tfriend
			% un-follow user
			% format: .unfo <user>
		"""
		# XXX: potential injection
		url = mkURL( "https://api.twitter.com/1.1/friendships/destroy.json", screen_name = params )
		succ, response = self.post( url, r )
		if not succ:
			return
		name = json.loads( response.read() )["screen_name"]
		r.l("un-followed %s" % name)

	def twiCmdBlock(self, params, r):
		"""
			# tfriend
			% block user
			% format: .block <user>
		"""
		# XXX: potential injection
		url = mkURL( "https://api.twitter.com/1.1/blocks/create.json", screen_name = params )
		succ, response = self.post( url, r )
		if not succ:
			return
		name = json.loads( response.read() )["screen_name"]
		r.l("blocked %s" % name)

	def twiCmdUnBlock(self, params, r):
		"""
			# tfriend
			% un-block user
			% format: .unblock <user>
		"""
		# XXX: potential injection
		url = mkURL( "https://api.twitter.com/1.1/blocks/destroy.json", screen_name = params )
		succ, response = self.post( url, r )
		if not succ:
			return
		name = json.loads( response.read() )["screen_name"]
		r.l("un-blocked %s" % name)

	def twiCmdCheckRelation(self, params, r):
		"""
			# tfriend
			% check relationship between you and another twitter user
			% format: .checkrelation <user>
		"""
		# XXX: potential injection
		url = mkURL( "https://api.twitter.com/1.1/friendships/lookup.json", screen_name = params )
		succ, response = self.get( url, r )
		if not succ:
			return
		ppl_list = json.loads( response.read() )
		if len(ppl_list) == 0:
			r.l( "!cannot find this user" )
			return
		
		ppl = ppl_list[0]
		if ppl["id_str"] == self.uid:
			r.l( "it's you!" )
			return

		if "none" in ppl["connections"]:
			r.l( "%s has no connection with you" % ppl["screen_name"] )
			return

		if len( ppl["connections"] ) == 2:
			r.l( "%s and you are following each other" % ppl["screen_name"])
			return

		if "following" in ppl["connections"]:
			r.l( "you are following %s" % ppl["screen_name"])
			return

		if "followed_by" in ppl["connections"]:
			r.l( "you are followed by %s" % ppl["screen_name"])
			return

		r.l( "relationship check failed" )
	
	def twiCmdStatus(self, params, r):
		"""
			# tfriend
			% get information about a user
			% format: .status [user]
			% when user is not given, the status of you will be shown
			% MIGHT return the most recent status, if any
		"""
		url = "https://api.twitter.com/1.1/users/show.json" 
		if len(params) > 0:
			url = mkURL( url, screen_name = params )
		else:
			url = mkURL( url, user_id = self.uid )
		succ, response = self.get( url, r )
		if not succ:
			return
		data = json.loads( response.read() )
		
		status = twitter_object.Status( data )
		r.l( status )

		if len(params) > 0:
			self.twiCmdCheckRelation(params, r)

	def twiCmdRetweet(self, params, r):
		"""
			# ttweet
			% official retweet, cannot add your comment
			% if you want help of 'retweet with comment'
			% please refer to .rt
			% format: .r <tweet id>
			% * you can add your comment after the tweet_id
			%   but by doing this, this command will act like ".rt"
			% e.g. : 
			%   1)  ".r 123456" retweets tweet that has id 123456
			%   2)  ".r 123456 comment" is identical to ".rt 123456 comment"
		"""
		if not params.isdigit():
			# jump to 'rt', which allows comments
			self.twiCmdRetweetWithComment(params, r)
			return
		url = "https://api.twitter.com/1.1/statuses/retweet/%s.json" % params
		succ, response = self.post( url, r)
		if not succ:
			return
		r.l( "official retweeted" )

	def twiCmdRetweetWithComment(self, params, r):
		"""
			# ttweet
			% retweet with your comment, differ from the offical retweet
			% format: .rt <tweet id> [your comment]
		"""
		pat = re.compile("(\d+)(?:(?:\s+)(.*))?$")
		m = pat.match( params )
		if m is None:
			r.l("!bad argument")
			return
		tid, text = m.group(1), m.group(2)

		# collection origin author name...
		url = "https://api.twitter.com/1.1/statuses/show/%s.json" % tid
		succ, response = self.get( url, r )
		if not succ:
			r.l( "!failed when fetching origin tweet" )
			return
		data = json.loads( response.read() ) 
		tweetObj = twitter_object.Tweet(data)
		author = tweetObj.owner
		
		if text is None:
			text = "RT @%s: %s" % (tweetObj.owner, tweetObj.text)
		else:
			text = "%s RT @%s: %s" % (text, tweetObj.owner, tweetObj.text)
		
		text = twig_command.cmdLengthCheck(None, text, r) 
		url = mkURL("https://api.twitter.com/1.1/statuses/update.json", status=text, in_reply_to_status_id=tid)
		succ, response = self.post(url, r)
		if not succ:
			return
		r.l( "retweeted" )

	def twiCmdDestroy(self, params, r):
		"""
			# ttweet
			% remove a specific or the most recent tweet you've sent
			% format: .del [id]
			% if id is not given, I will delete the most recent one.
		"""
		if len( params ) > 0 and not params.isdigit():
			r.l("!bad argument")
			return
		if len( params ) == 0:
			# now I have to find the last tweet you've sent
			url = mkURL( "https://api.twitter.com/1.1/users/show.json", user_id = self.uid )
			succ, response = self.get( url, r )
			if not succ:
				r.l( "!failed when fetching origin tweets" )
				return
			data = json.loads( response.read() )
			try:
				tid = data["status"]["id_str"]
			except:
				r.l( "!you don't have any tweet" )
				return
		else:
			tid = params

		url = mkURL( "https://api.twitter.com/1.1/statuses/destroy/%s.json" % tid )
		succ, response = self.post( url, r)
		if not succ:
			return
		r.l("removed")

	def twiCmdFavorite(self, params, r):
		"""
			# tfav
			% view tweets favorited by you or a specific user
			% format: .fav [user] [where]
			% both 'user' and 'where' are optional
			% if 'user' is not given, I will grab the favorate list of you.
			% use command '.where' for how to use '[where]'
		"""
		para_list = params.split()
		query = {}
		if len( para_list ) > 2:
			r.l("!bad argument")
			return
		if len( para_list ) == 0:
			# view favorite tweets of yourself
			query["user_id"] = self.uid
		elif len( para_list ) == 2:
			w = tools.parseWhere( para_list[1] )
			if w is None:
				r.l("!bad argument")
				return
			query.update(w)
			query["screen_name"] = para_list[0]
		else:
			# only 1 element in the list
			# which must be either [user] or [where]
			w = tools.parseWhere( para_list[0] )
			if w is None:
				# this is actually a user name
				query["screen_name"] = para_list[0]
			else:
				# this is a 'where' 
				query.update(w)
				query["user_id"] = self.uid
		url = mkURL( "https://api.twitter.com/1.1/favorites/list.json", **query )
		succ, response = self.get( url, r)
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.Tweet )
		r.l( tl )

	def twiCmdFavoriteCreate(self, params, r):
		"""
			# tfav
			% mark tweets as favorate
			% format: .+fav <tweet id>
		"""
		if not params.isdigit():
			r.l("!bad argument")
			return
		url = mkURL( "https://api.twitter.com/1.1/favorites/create.json", id=params )
		succ, response = self.post( url, r )
		if not succ:
			return
		r.l ( "favorited" ) 

	def twiCmdFavoriteDestroy(self, params, r):
		"""
			# tfav
			% remove tweets from favorite list
			% format: .-fav <tweet id>
		"""
		if not params.isdigit():
			r.l("!bad argument")
			return
		url = mkURL( "https://api.twitter.com/1.1/favorites/destroy.json", id=params )
		succ, response = self.post( url, r )
		if not succ:
			return
		r.l ( "un-favorited" ) 

	def twiCmdMyTweets(self, params, r):
		"""
			# ttweet
			% get my tweets
			% format: .me [where]
			% use command '.where' for how to use '[where]'
		"""
		extra = tools.parseWhere( params )
		if extra is None:
			r.l( "!bad argument" )
			return
		extra["user_id"] = self.uid
		url = mkURL( "https://api.twitter.com/1.1/statuses/user_timeline.json", **extra )
		succ, response = self.get( url, r )
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.Tweet )
		r.l( tl )

	def twiCmdUserTimeline(self, params, r):
		"""
			# ttweet
			% view timeline of a given user
			% format: .tl [user] [where] 
			% both 'user' and 'where' are optional
			% 'user' is you by default
			% use command '.where' for how to use '[where]'
		"""
		para_list = params.split()
		query = {}
		if len( para_list ) > 2:
			r.l( "!bad argument" )
		if len( para_list ) == 0:
			# identical to '.me'
			self.twiCmdMyTweets( params, r )
			return
		if len( para_list ) == 1:
			if para_list[0][0] in '<>':
				# identical to '.me'
				self.twiCmdMyTweets( params, r)
				return
			else:
				# is a user
				query["screen_name"] = para_list[0]
		else:
			# has both user and 'where'
			extra = tools.parseWhere( para_list[1] )
			if extra is None:
				r.l ( "!bad argument" )
				return
			query.update( extra )
			query["screen_name"] = para_list[0]
		# query is ready
		url = mkURL("https://api.twitter.com/1.1/statuses/user_timeline.json", **query)
		succ, response = self.get( url, r )
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.Tweet )
		r.l( tl )

	def twiCmdConversation(self, params, r):
		"""
			# ttweet
			% view conversation, backtrack tweets
			% format: .msg <tweet id>
			% up to 5 tweets
			* modify BACKTRACK_LIMIT and this comment 
			  if you want a stronger/weaker backtracking ability
		"""
		BACKTRACK_LIMIT = 5
		
		if not params.isdigit():
			r.l("!bad argument")
			return

		bt_count = 0
		tid = params
		tweets = []
		while bt_count < BACKTRACK_LIMIT and tid is not None:
			bt_count += 1
			url = "https://api.twitter.com/1.1/statuses/show/%s.json" % tid
			succ, response = self.get( url, r )
			if not succ:
				break
			tweet = json.loads( response.read() )
			tweets.append( tweet )
			tid = tweet["in_reply_to_status_id_str"]
		tweets = map( twitter_object.Tweet, tweets )
		tweets.reverse()
		r.l( tweets )
		if tid is None:
			r.l( "origin tweet reached" )
		else:
			r.l( "backtrack limit reached" )
	
	def twiCmdSearch(self, params, r):
		"""
			# ttweet
			% search tweets
			% format: .s "<query>" [where]
			% query can contain any characters including whitespace
			% use command '.where' for how to use '[where]'
			% e.g:  .s "bob and alice" <123456
		"""
		pat = re.compile( r'"(.*)"(\s+([<|>]\d+))?' )
		m = pat.match( params )
		if m is None:
			r.l( "!bad argument" )
			return
		q, _, where = m.groups()
		if where is None:
			where = ''
		query = tools.parseWhere( where )
		query["q"] = q
		url = mkURL( "https://api.twitter.com/1.1/search/tweets.json", **query)
		succ, response = self.get( url, r )
		if not succ:
			return
		data = json.loads( response.read() )["statuses"]
		tl = map( twitter_object.Tweet, data )
		tl.reverse()
		r.l( tl )

	def twiCmdList(self, params, r):
		"""
			# tlist
			% return all lists you subscribes to
			% format: .lsm
		"""
		url = "https://api.twitter.com/1.1/lists/list.json"
		succ, response = self.get( url, r )
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.ListInfo )
		r.l( tl )

	def twiCmdListSubscription(self, params, r):
		"""
			# tlist
			% show lists you are subscribed to
			% format: .lssub [cursor]
			% cursor will be given in the result of this command
			% use number in 'next' to lead you to the next page
			% and number in 'prev' to the previous page
		"""
		cursor = {}
		if len( params ) > 0:
			try:
				cursor["cursor"] = int ( params )
			except:
				r.l( "!bad argument" )
				return

		url = mkURL( "https://api.twitter.com/1.1/lists/subscriptions.json", **cursor )
		succ, response = self.get( url, r )
		if not succ:
			return
		data = json.loads( response.read() )
		lists = data["lists"]
		r.l( map( twitter_object.ListInfo, lists ) )
		r.l( ("next", data["next_cursor_str"]) )
		r.l( ("prev", data["previous_cursor_str"]) )

	def twiCmdListMembership(self, params, r):
		"""
			# tlist
			% show lists you have been added to
			% format: .lsmbr [cursor]
			% cursor will be given in the result of this command
			% use number in 'next' to lead you to the next page
			% and number in 'prev' to the previous page
		"""
		cursor = {}
		if len( params ) > 0:
			try:
				cursor["cursor"] = int ( params )
			except:
				r.l( "!bad argument" )
				return

		url = mkURL( "https://api.twitter.com/1.1/lists/memberships.json", **cursor )
		succ, response = self.get( url, r )
		if not succ:
			return
		data = json.loads( response.read() )
		lists = data["lists"]
		r.l( map( twitter_object.ListInfo, lists ) )
		r.l( ("next", data["next_cursor_str"]) )
		r.l( ("prev", data["previous_cursor_str"]) )

	def twiCmdListStatus(self, params, r):
		"""
			# tlist
			% show tweets in a given list
			% format: .ls <list id> [where]
			% use command '.where' for how to use '[where]'
		"""
		params = params.split()
		if len( params ) == 0:
			r.l("!bad argument")
			return
		if not params[0].isdigit():
			r.l("!bad argument")
			return
		extra = {}
		if len( params ) == 2:
			extra = tools.parseWhere( params[1] )
			if extra is None:
				r.l("!bad page argument")
				return
		assert extra is not None
		extra["list_id"] = params[0]
		url = mkURL( "https://api.twitter.com/1.1/lists/statuses.json", **extra )
		succ, response = self.get(url, r)	
		if not succ:
			return
		raw_data = response.read()
		tl = self._parseList( raw_data, twitter_object.Tweet )
		r.l( tl )

	def twiCmdSwitchUser(self, params, r):
		"""
			# account
			% switch user by screen_name
			% format: .su [screen_name]
			% * note this command only works 
			%   when you have bound an active user 
			% leaving screen_name blank leads to command ".user"
		"""
		if len( params ) == 0:
			twig_command.cmdUser(self.account, params, r)
			return
		pat = re.compile(r'^([A-Za-z0-9_]+)$')
		m = pat.match( params )
		if m is None:
			r.l("!bad argument")
			return

		url = mkURL( "https://api.twitter.com/1.1/users/show.json", screen_name=params )
		succ, response = self.get( url, r )
		if not succ:
			return
		data = json.load( response )
		twig_command.cmdSwitchID(self.account, data["id_str"], r)