Commits

Anonymous committed b9d97f2

bored

Comments (0)

Files changed (70)

+application: idevicedog
+version: 1
+runtime: python
+api_version: 1
+
+
+inbound_services:
+- xmpp_message
+
+handlers:
+- url: /_ah/xmpp/message/chat/
+  script: xmpp_handler.py
+
+- url: /show
+  script: main.py
+
+- url: .*
+  script: main.py
+#!/usr/bin/python
+
+consumer_key= "2398039588"
+consumer_secret ="565f0cc06b7a13c9c81385d7515762b6"
+token = ["1099ce13380b22f93e7ec35a3bc2e341","c66ebfe1f95364c06f45f9e7c1267e78"]
+watchName=['rainywh269']

main/config.pyc

Binary file added.
+cron:
+- description: daily job
+  url: /dataa
+  schedule: every 1 minutes
+  timezone: Asia/Shanghai
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# TwiTalker - Tweet it easy with your GTalk
+# Copyright 2009-2010 Kavin Gray
+# See LICENSE for details.
+# 
+#
+
+from google.appengine.ext import db
+
+class record(db.Model):
+    name = db.StringProperty()
+    msgid = db.IntegerProperty()
+

main/db_model.pyc

Binary file added.
+ ../../../sdk/google_appengine/appcfg.py update .

main/getTimelines.py

+#!/usr/bin/python
+#coding=gbk
+
+'''
+Created on 2011-2-25
+
+@author: sina(weibo.com)
+'''
+
+import unittest
+from weibopy.auth import OAuthHandler, BasicAuthHandler
+from weibopy.api import API
+from config import consumer_key,consumer_secret
+
+class Test(unittest.TestCase):
+    
+
+
+    def __init__(self):
+            """ constructor """
+    
+    def getAtt(self, key):
+        try:
+            return self.obj.__getattribute__(key)
+        except Exception, e:
+            print e
+            return ''
+        
+    def getAttValue(self, obj, key):
+        try:
+            return obj.__getattribute__(key)
+        except Exception, e:
+            print e
+            return ''
+    def setToken(self, token, tokenSecret):
+        self.auth = OAuthHandler(consumer_key, consumer_secret)
+        self.auth.setToken(token, tokenSecret)
+        self.api = API(self.auth)
+        
+    def basicAuth(self, source, username, password):
+        self.auth = BasicAuthHandler(username, password)
+        self.api = API(self.auth,source=source)
+        
+    def auth(self):
+        self.auth = OAuthHandler(consumer_key, consumer_secret)
+        auth_url = self.auth.get_authorization_url()
+        print 'Please authorize: ' + auth_url
+        verifier = raw_input('PIN: ').strip()
+        self.auth.get_access_token(verifier)
+        self.api = API(self.auth)
+        
+    def friends_timeline(self):
+        timeline = self.api.friends_timeline(count=50, page=1)
+        for line in timeline:
+            self.obj = line
+            mid = self.getAtt("id")
+            text = self.getAtt("text")
+            print "friends_timeline---"+ str(mid) +":"+ text
+
+    def comments_timeline(self):
+        timeline = self.api.comments_timeline(count=2, page=1)
+        for line in timeline:
+            self.obj = line
+            mid = self.getAtt("id")
+            text = self.getAtt("text")
+            print "comments_timeline---"+ str(mid) +":"+ text
+            
+    def user_timeline(self,name,msgId,first=False):
+        if not first:
+            timeline = self.api.user_timeline(screen_name=name,since_id=msgId)
+        else:
+            timeline = self.api.user_timeline(screen_name=name,count=1)
+        return timeline
+
+    def public_timeline(self):
+        timeline = self.api.public_timeline(count=5000, page=1)
+        for line in timeline:
+            self.obj = line
+            mid = self.getAtt("id")
+            text = self.getAtt("text")
+            print "public_timeline---"+ str(mid) +":"+ text
+    def userInfo(self):
+        info=self.api.get_user(screen_name="rainywh269")
+        print dir(info),info.statuses_count
+
+
+if __name__ == "__main__":
+    test = Test()
+    #AccessToken�� key��Secret
+    #test.auth()
+    test.setToken("1099ce13380b22f93e7ec35a3bc2e341", "c66ebfe1f95364c06f45f9e7c1267e78")
+    #test.userInfo()
+    test.user_timeline()

main/getTimelines.pyc

Binary file added.
+from google.appengine.api import xmpp
+from google.appengine.ext.webapp.util import run_wsgi_app
+from google.appengine.ext import webapp
+from google.appengine.ext import db
+from google.appengine.api import mail
+from weibopy.auth import OAuthHandler, BasicAuthHandler
+from weibopy.api import API
+from db_model import record 
+from getTimelines import Test
+from config import watchName, token
+import logging
+
+
+def addUser(user):
+    a = record()
+    a.name = user
+    a.msgid = 0
+    db.put(a)
+
+def updateMsgId(a,msgId):
+    a.msgid = msgId
+    db.put(a)
+
+def getMsg(user):
+    test = Test()
+    test.setToken(token[0],token[1])
+    if user.msgid <> 0:
+        result = test.user_timeline(user.name,user.msgid)
+    else:
+        result = test.user_timeline(user.name,user.msgid,True)
+    if len(result)>0:
+        for i in result:
+            try:
+                status_code = xmpp.send_message("guohaochuan@gmail.com", user.name+":retweet:"+unicode(i.__getattribute__('text'))+"::"+unicode(i.__getattribute__('retweeted_status').__getattribute__('text')))
+                logging.info("retweet")
+            except:
+                status_code = xmpp.send_message("guohaochuan@gmail.com", user.name+":"+unicode(i.__getattribute__('text')))
+                logging.info("original")
+
+            if status_code == xmpp.NO_ERROR :
+                updateMsgId(user,int(result[0].__getattribute__('id')))
+
+def checkMsg():
+    for name in watchName:
+        command="SELECT * FROM record where name = '"+name+"'"
+        q = db.GqlQuery(command)
+        num = q.count()
+        if num<>0:
+            if num == 1:
+                getMsg(q[0])
+        elif num==0:
+            addUser(name)
+            checkMsg()
+
+
+class realMain(webapp.RequestHandler):
+    def get(self):
+        checkMsg()
+
+application = webapp.WSGIApplication([('/dataa', realMain),],debug=True)
+
+def main():
+    run_wsgi_app(application)
+
+if __name__ == "__main__":
+    main()  
+../sdk/google_appengine/dev_appserver.py . -a 0.0.0.0

main/weibopy/__init__.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+"""
+weibo API library
+"""
+__version__ = '1.5'
+__author__ = 'Joshua Roesslein'
+__license__ = 'MIT'
+
+from weibopy.models import Status, User, DirectMessage, Friendship, SavedSearch, SearchResult, ModelFactory, IDSModel
+from weibopy.error import WeibopError
+from weibopy.api import API
+from weibopy.cache import Cache, MemoryCache, FileCache
+from weibopy.auth import BasicAuthHandler, OAuthHandler
+from weibopy.streaming import Stream, StreamListener
+from weibopy.cursor import Cursor
+
+# Global, unauthenticated instance of API
+api = API()
+
+def debug(enable=True, level=1):
+
+    import httplib
+    httplib.HTTPConnection.debuglevel = level
+

main/weibopy/__init__.pyc

Binary file added.

main/weibopy/api.py

+
+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+import os
+import mimetypes
+
+from weibopy.binder import bind_api
+from weibopy.error import WeibopError
+from weibopy.parsers import ModelParser
+
+
+class API(object):
+    """Mblog API"""
+
+    def __init__(self, auth_handler=None,
+            host='api.t.sina.com.cn', search_host='api.t.sina.com.cn',
+             cache=None, secure=False, api_root='', search_root='',
+            retry_count=0, retry_delay=0, retry_errors=None,source=None,
+            parser=None, log = None):
+        self.auth = auth_handler
+        self.host = host
+        if source == None:
+            if auth_handler != None:
+                self.source = self.auth._consumer.key
+        else:
+            self.source = source
+        self.search_host = search_host
+        self.api_root = api_root
+        self.search_root = search_root
+        self.cache = cache
+        self.secure = secure
+        self.retry_count = retry_count
+        self.retry_delay = retry_delay
+        self.retry_errors = retry_errors
+        self.parser = parser or ModelParser()
+        self.log = log
+
+    """ statuses/public_timeline """
+    public_timeline = bind_api(
+        path = '/statuses/public_timeline.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = []
+    )
+
+    """ statuses/home_timeline """
+    home_timeline = bind_api(
+        path = '/statuses/home_timeline.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+
+    """ statuses/friends_timeline """
+    friends_timeline = bind_api(
+        path = '/statuses/friends_timeline.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+    """ statuses/comment """
+    comment = bind_api(
+        path = '/statuses/comment.json',
+        method = 'POST',
+        payload_type = 'comments',
+        allowed_param = ['id', 'cid', 'comment'],
+        require_auth = True
+    )
+    
+    """ statuses/comment_destroy """
+    comment_destroy  = bind_api(
+        path = '/statuses/comment_destroy/{id}.json',
+        method = 'DELETE',
+        payload_type = 'comments',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+    
+    """ statuses/comments_timeline """
+    comments = bind_api(
+        path = '/statuses/comments.json',
+        payload_type = 'comments', payload_list = True,
+        allowed_param = ['id', 'count', 'page'],
+        require_auth = True
+    )
+    
+    """ statuses/comments_timeline """
+    comments_timeline = bind_api(
+        path = '/statuses/comments_timeline.json',
+        payload_type = 'comments', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+    
+    """ statuses/comments_by_me """
+    comments_by_me = bind_api(
+        path = '/statuses/comments_by_me.json',
+        payload_type = 'comments', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+    
+    """ statuses/user_timeline """
+    user_timeline = bind_api(
+        path = '/statuses/user_timeline.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['id', 'user_id', 'screen_name', 'since_id',
+                          'max_id', 'count', 'page']
+    )
+
+    """ statuses/mentions """
+    mentions = bind_api(
+        path = '/statuses/mentions.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+
+    """ statuses/counts """
+    counts = bind_api(
+        path = '/statuses/counts.json',
+        payload_type = 'counts', payload_list = True,
+        allowed_param = ['ids'],
+        require_auth = True
+    )
+    
+    """ statuses/unread """
+    unread = bind_api(
+        path = '/statuses/unread.json',
+        payload_type = 'counts'
+    )
+    
+    """ statuses/retweeted_by_me """
+    retweeted_by_me = bind_api(
+        path = '/statuses/retweeted_by_me.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+
+    """ statuses/retweeted_to_me """
+    retweeted_to_me = bind_api(
+        path = '/statuses/retweeted_to_me.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+
+    """ statuses/retweets_of_me """
+    retweets_of_me = bind_api(
+        path = '/statuses/retweets_of_me.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+
+    """ statuses/show """
+    get_status = bind_api(
+        path = '/statuses/show.json',
+        payload_type = 'status',
+        allowed_param = ['id']
+    )
+
+    """ statuses/update """
+    update_status = bind_api(
+        path = '/statuses/update.json',
+        method = 'POST',
+        payload_type = 'status',
+        allowed_param = ['status', 'lat', 'long', 'source'],
+        require_auth = True
+    )
+    """ statuses/upload """
+    def upload(self, filename, status, lat=None, long=None, source=None):
+        if source is None:
+            source=self.source
+        headers, post_data = API._pack_image(filename, 1024, source=source, status=status, lat=lat, long=long, contentname="pic")
+        args = [status]
+        allowed_param = ['status']
+        
+        if lat is not None:
+            args.append(lat)
+            allowed_param.append('lat')
+        
+        if long is not None:
+            args.append(long)
+            allowed_param.append('long')
+        
+        if source is not None:
+            args.append(source)
+            allowed_param.append('source')
+        kargs={
+               'post_data': post_data,
+               'headers': headers,
+               }    
+        return bind_api(
+            path = '/statuses/upload.json',            
+            method = 'POST',
+            payload_type = 'status',
+            require_auth = True,
+            allowed_param = allowed_param            
+#        )(self, *args, post_data=post_data, headers=headers)
+         )(self, *args, **kargs)
+        
+    """ statuses/reply """
+    reply = bind_api(
+        path = '/statuses/reply.json',
+        method = 'POST',
+        payload_type = 'status',
+        allowed_param = ['id', 'cid','comment'],
+        require_auth = True
+    )
+    
+    """ statuses/repost """
+    repost = bind_api(
+        path = '/statuses/repost.json',
+        method = 'POST',
+        payload_type = 'status',
+        allowed_param = ['id', 'status'],
+        require_auth = True
+    )
+    
+    """ statuses/destroy """
+    destroy_status = bind_api(
+        path = '/statuses/destroy/{id}.json',
+        method = 'DELETE',
+        payload_type = 'status',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ statuses/retweet """
+    retweet = bind_api(
+        path = '/statuses/retweet/{id}.json',
+        method = 'POST',
+        payload_type = 'status',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ statuses/retweets """
+    retweets = bind_api(
+        path = '/statuses/retweets/{id}.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['id', 'count'],
+        require_auth = True
+    )
+
+    """ users/show """
+    get_user = bind_api(
+        path = '/users/show.json',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name']
+    )
+    
+    """ Get the authenticated user """
+    def me(self):
+        return self.get_user(screen_name=self.auth.get_username())
+
+    """ users/search """
+    search_users = bind_api(
+        path = '/users/search.json',
+        payload_type = 'user', payload_list = True,
+        require_auth = True,
+        allowed_param = ['q', 'per_page', 'page']
+    )
+
+    """ statuses/friends """
+    friends = bind_api(
+        path = '/statuses/friends.json',
+        payload_type = 'user', payload_list = True,
+        allowed_param = ['id', 'user_id', 'screen_name', 'page', 'cursor']
+    )
+
+    """ statuses/followers """
+    followers = bind_api(
+        path = '/statuses/followers.json',
+        payload_type = 'user', payload_list = True,
+        allowed_param = ['id', 'user_id', 'screen_name', 'page', 'cursor']
+    )
+
+    """ direct_messages """
+    direct_messages = bind_api(
+        path = '/direct_messages.json',
+        payload_type = 'direct_message', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+
+    """ direct_messages/sent """
+    sent_direct_messages = bind_api(
+        path = '/direct_messages/sent.json',
+        payload_type = 'direct_message', payload_list = True,
+        allowed_param = ['since_id', 'max_id', 'count', 'page'],
+        require_auth = True
+    )
+    """ direct_messages/new """
+    new_direct_message = bind_api(
+        path = '/direct_messages/new.json',
+        method = 'POST',
+        payload_type = 'direct_message',
+        allowed_param = ['id', 'screen_name', 'user_id', 'text'],
+        require_auth = True
+    )
+    
+    """ direct_messages/destroy """
+    destroy_direct_message = bind_api(
+        path = '/direct_messages/destroy/{id}.json',
+        method = 'POST',
+        payload_type = 'direct_message',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ friendships/create """
+    create_friendship = bind_api(
+        path = '/friendships/create.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name', 'follow'],
+        require_auth = True
+    )
+
+    """ friendships/destroy """
+    destroy_friendship = bind_api(
+        path = '/friendships/destroy.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name'],
+        require_auth = True
+    )
+
+    """ friendships/exists """
+    exists_friendship = bind_api(
+        path = '/friendships/exists.json',
+        payload_type = 'json',
+        allowed_param = ['user_a', 'user_b']
+    )
+
+    """ friendships/show """
+    show_friendship = bind_api(
+        path = '/friendships/show.json',
+        payload_type = 'friendship',
+        allowed_param = ['source_id', 'source_screen_name',
+                          'target_id', 'target_screen_name']
+    )
+
+    """ friends/ids """
+    friends_ids = bind_api(
+        path = '/friends/ids.json',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name', 'cursor', 'count'],
+        require_auth = True
+    )
+
+    """ followers/ids """
+    followers_ids = bind_api(        
+        path = '/followers/ids.json',
+        payload_type = 'json',
+        allowed_param = ['id', 'page'],
+    )
+
+    """ account/verify_credentials """
+    def verify_credentials(self):
+        try:
+            return bind_api(
+                path = '/account/verify_credentials.json',
+                payload_type = 'user',
+                require_auth = True
+            )(self)
+        except WeibopError:
+            return False
+
+    """ account/rate_limit_status """
+    rate_limit_status = bind_api(
+        path = '/account/rate_limit_status.json',
+        payload_type = 'json'
+    )
+
+    """ account/update_delivery_device """
+    set_delivery_device = bind_api(
+        path = '/account/update_delivery_device.json',
+        method = 'POST',
+        allowed_param = ['device'],
+        payload_type = 'user',
+        require_auth = True
+    )
+    """account/get_privacy"""
+    get_privacy = bind_api(
+        path = '/account/get_privacy.json',
+        payload_type = 'json'                  
+     )
+    """account/update_privacy"""
+    update_privacy = bind_api(
+        path = '/account/update_privacy.json',
+        payload_type = 'json',
+        method = 'POST',
+        allow_param = ['comment','message','realname','geo','badge'],
+        require_auth = True                      
+     )
+    """ account/update_profile_colors """
+    update_profile_colors = bind_api(
+        path = '/account/update_profile_colors.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['profile_background_color', 'profile_text_color',
+                          'profile_link_color', 'profile_sidebar_fill_color',
+                          'profile_sidebar_border_color'],
+        require_auth = True
+    )
+        
+    """ account/update_profile_image """
+    def update_profile_image(self, filename):
+        headers, post_data = API._pack_image(filename=filename, max_size=700, source=self.source)
+        return bind_api(
+            path = '/account/update_profile_image.json',
+            method = 'POST',
+            payload_type = 'user',
+            require_auth = True
+        )(self, post_data=post_data, headers=headers)
+
+    """ account/update_profile_background_image """
+    def update_profile_background_image(self, filename, *args, **kargs):
+        headers, post_data = API._pack_image(filename, 800)
+        bind_api(
+            path = '/account/update_profile_background_image.json',
+            method = 'POST',
+            payload_type = 'user',
+            allowed_param = ['tile'],
+            require_auth = True
+        )(self, post_data=post_data, headers=headers)
+
+    """ account/update_profile """
+    update_profile = bind_api(
+        path = '/account/update_profile.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['name', 'url', 'location', 'description'],
+        require_auth = True
+    )
+
+    """ favorites """
+    favorites = bind_api(
+        path = '/favorites/{id}.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['id', 'page']
+    )
+
+    """ favorites/create """
+    create_favorite = bind_api(
+        path = '/favorites/create/{id}.json',
+        method = 'POST',
+        payload_type = 'status',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ favorites/destroy """
+    destroy_favorite = bind_api(
+        path = '/favorites/destroy/{id}.json',
+        method = 'POST',
+        payload_type = 'status',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ notifications/follow """
+    enable_notifications = bind_api(
+        path = '/notifications/follow.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name'],
+        require_auth = True
+    )
+
+    """ notifications/leave """
+    disable_notifications = bind_api(
+        path = '/notifications/leave.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name'],
+        require_auth = True
+    )
+
+    """ blocks/create """
+    create_block = bind_api(
+        path = '/blocks/create.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name'],
+        require_auth = True
+    )
+
+    """ blocks/destroy """
+    destroy_block = bind_api(
+        path = '/blocks/destroy.json',
+        method = 'DELETE',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name'],
+        require_auth = True
+    )
+
+    """ blocks/exists """
+    def exists_block(self, *args, **kargs):
+        try:
+            bind_api(
+                path = '/blocks/exists.json',
+                allowed_param = ['id', 'user_id', 'screen_name'],
+                require_auth = True
+            )(self, *args, **kargs)
+        except WeibopError:
+            return False
+        return True
+
+    """ blocks/blocking """
+    blocks = bind_api(
+        path = '/blocks/blocking.json',
+        payload_type = 'user', payload_list = True,
+        allowed_param = ['page'],
+        require_auth = True
+    )
+
+    """ blocks/blocking/ids """
+    blocks_ids = bind_api(
+        path = '/blocks/blocking/ids.json',
+        payload_type = 'json',
+        require_auth = True
+    )
+
+    """ statuses/repost """
+    report_spam = bind_api(
+        path = '/report_spam.json',
+        method = 'POST',
+        payload_type = 'user',
+        allowed_param = ['id', 'user_id', 'screen_name'],
+        require_auth = True
+    )
+
+    """ saved_searches """
+    saved_searches = bind_api(
+        path = '/saved_searches.json',
+        payload_type = 'saved_search', payload_list = True,
+        require_auth = True
+    )
+
+    """ saved_searches/show """
+    get_saved_search = bind_api(
+        path = '/saved_searches/show/{id}.json',
+        payload_type = 'saved_search',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ saved_searches/create """
+    create_saved_search = bind_api(
+        path = '/saved_searches/create.json',
+        method = 'POST',
+        payload_type = 'saved_search',
+        allowed_param = ['query'],
+        require_auth = True
+    )
+
+    """ saved_searches/destroy """
+    destroy_saved_search = bind_api(
+        path = '/saved_searches/destroy/{id}.json',
+        method = 'DELETE',
+        payload_type = 'saved_search',
+        allowed_param = ['id'],
+        require_auth = True
+    )
+
+    """ help/test """
+    def test(self):
+        try:
+            bind_api(
+                path = '/help/test.json',
+            )(self)
+        except WeibopError:
+            return False
+        return True
+
+    def create_list(self, *args, **kargs):
+        return bind_api(
+            path = '/%s/lists.json' % self.auth.get_username(),
+            method = 'POST',
+            payload_type = 'list',
+            allowed_param = ['name', 'mode', 'description'],
+            require_auth = True
+        )(self, *args, **kargs)
+
+    def destroy_list(self, slug):
+        return bind_api(
+            path = '/%s/lists/%s.json' % (self.auth.get_username(), slug),
+            method = 'DELETE',
+            payload_type = 'list',
+            require_auth = True
+        )(self)
+
+    def update_list(self, slug, *args, **kargs):
+        return bind_api(
+            path = '/%s/lists/%s.json' % (self.auth.get_username(), slug),
+            method = 'POST',
+            payload_type = 'list',
+            allowed_param = ['name', 'mode', 'description'],
+            require_auth = True
+        )(self, *args, **kargs)
+
+    lists = bind_api(
+        path = '/{user}/lists.json',
+        payload_type = 'list', payload_list = True,
+        allowed_param = ['user', 'cursor'],
+        require_auth = True
+    )
+
+    lists_memberships = bind_api(
+        path = '/{user}/lists/memberships.json',
+        payload_type = 'list', payload_list = True,
+        allowed_param = ['user', 'cursor'],
+        require_auth = True
+    )
+
+    lists_subscriptions = bind_api(
+        path = '/{user}/lists/subscriptions.json',
+        payload_type = 'list', payload_list = True,
+        allowed_param = ['user', 'cursor'],
+        require_auth = True
+    )
+
+    list_timeline = bind_api(
+        path = '/{owner}/lists/{slug}/statuses.json',
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['owner', 'slug', 'since_id', 'max_id', 'count', 'page']
+    )
+
+    get_list = bind_api(
+        path = '/{owner}/lists/{slug}.json',
+        payload_type = 'list',
+        allowed_param = ['owner', 'slug']
+    )
+
+    def add_list_member(self, slug, *args, **kargs):
+        return bind_api(
+            path = '/%s/%s/members.json' % (self.auth.get_username(), slug),
+            method = 'POST',
+            payload_type = 'list',
+            allowed_param = ['id'],
+            require_auth = True
+        )(self, *args, **kargs)
+
+    def remove_list_member(self, slug, *args, **kargs):
+        return bind_api(
+            path = '/%s/%s/members.json' % (self.auth.get_username(), slug),
+            method = 'DELETE',
+            payload_type = 'list',
+            allowed_param = ['id'],
+            require_auth = True
+        )(self, *args, **kargs)
+
+    list_members = bind_api(
+        path = '/{owner}/{slug}/members.json',
+        payload_type = 'user', payload_list = True,
+        allowed_param = ['owner', 'slug', 'cursor']
+    )
+
+    def is_list_member(self, owner, slug, user_id):
+        try:
+            return bind_api(
+                path = '/%s/%s/members/%s.json' % (owner, slug, user_id),
+                payload_type = 'user'
+            )(self)
+        except WeibopError:
+            return False
+
+    subscribe_list = bind_api(
+        path = '/{owner}/{slug}/subscribers.json',
+        method = 'POST',
+        payload_type = 'list',
+        allowed_param = ['owner', 'slug'],
+        require_auth = True
+    )
+
+    unsubscribe_list = bind_api(
+        path = '/{owner}/{slug}/subscribers.json',
+        method = 'DELETE',
+        payload_type = 'list',
+        allowed_param = ['owner', 'slug'],
+        require_auth = True
+    )
+
+    list_subscribers = bind_api(
+        path = '/{owner}/{slug}/subscribers.json',
+        payload_type = 'user', payload_list = True,
+        allowed_param = ['owner', 'slug', 'cursor']
+    )
+
+    def is_subscribed_list(self, owner, slug, user_id):
+        try:
+            return bind_api(
+                path = '/%s/%s/subscribers/%s.json' % (owner, slug, user_id),
+                payload_type = 'user'
+            )(self)
+        except WeibopError:
+            return False
+
+    """ trends/available """
+    trends_available = bind_api(
+        path = '/trends/available.json',
+        payload_type = 'json',
+        allowed_param = ['lat', 'long']
+    )
+
+    """ trends/location """
+    trends_location = bind_api(
+        path = '/trends/{woeid}.json',
+        payload_type = 'json',
+        allowed_param = ['woeid']
+    )
+
+    """ search """
+    search = bind_api(
+        search_api = True,
+        path = '/search.json',
+        payload_type = 'search_result', payload_list = True,
+        allowed_param = ['q', 'lang', 'locale', 'rpp', 'page', 'since_id', 'geocode', 'show_user']
+    )
+    search.pagination_mode = 'page'
+
+    """ trends """
+    trends = bind_api(
+        path = '/trends.json',
+        payload_type = 'trends', payload_list = True,
+        allowed_param = ['user_id','count','page'],
+        require_auth= True
+        )
+    """trends/statuses"""
+    trends_statuses = bind_api(
+        path = '/trends/statuses.json', 
+        payload_type = 'status', payload_list = True,
+        allowed_param = ['trend_name'],
+        require_auth = True
+        
+        )       
+    """trends/follow"""
+    trends_follow = bind_api(
+        path = '/trends/follow.json',
+        method = 'POST',
+        allowed_param = ['trend_name'],
+        require_auth = True
+        )                     
+    """trends/destroy"""
+    trends_destroy = bind_api(
+        path = '/trends/destroy.json',
+        method = 'DELETE',
+        allowed_param = ['trend_id'],
+        require_auth = True
+        )                                                                   
+    """ trends/current """
+    trends_current = bind_api(
+        search_api = True,
+        path = '/trends/current.json',
+        payload_type = 'json',
+        allowed_param = ['exclude']
+    )
+    """ trends/hourly"""
+    trends_hourly = bind_api(
+        search_api = True,
+        path = '/trends/hourly.json',
+        payload_type = 'trends',
+        allowed_param = []
+    )                      
+    """ trends/daily """
+    trends_daily = bind_api(
+        search_api = True,
+        path = '/trends/daily.json',
+        payload_type = 'trends',
+        allowed_param = []
+    )
+
+    """ trends/weekly """
+    trends_weekly = bind_api(
+        search_api = True,
+        path = '/trends/weekly.json',
+        payload_type = 'json',
+        allowed_param = []
+    )
+    """ Tags """
+    tags = bind_api(
+        path = '/tags.json',
+        payload_type = 'tags', payload_list = True,
+        allowed_param = ['user_id'],
+        require_auth= True,
+        )          
+    tag_create = bind_api(
+        path = '/tags/create.json',
+        payload_type = 'tags',
+        method = 'POST',
+        allowed_param = ['tags'],
+        payload_list = True, 
+        require_auth = True,
+        )                                
+    tag_suggestions = bind_api(
+        path = '/tags/suggestions.json',
+        payload_type = 'tags',
+        require_auth = True,
+        payload_list = True,
+        )
+    tag_destroy = bind_api(
+        path = '/tags/destroy.json',
+        payload_type = 'json',
+        method='POST',   
+        require_auth = True,
+        allowed_param = ['tag_id'],
+        ) 
+    tag_destroy_batch = bind_api(
+        path = '/tags/destroy_batch.json',
+        payload_type = 'json',
+        method='DELETE',   
+        require_auth = True,
+        payload_list = True,
+        allowed_param = ['ids'],
+        )                                                                              
+    """ Internal use only """
+    @staticmethod
+    def _pack_image(filename, max_size, source=None, status=None, lat=None, long=None, contentname="image"):
+        """Pack image from file into multipart-formdata post body"""
+        # image must be less than 700kb in size
+        try:
+            if os.path.getsize(filename) > (max_size * 1024):
+                raise WeibopError('File is too big, must be less than 700kb.')
+        #except os.error, e:
+        except os.error:
+            raise WeibopError('Unable to access file')
+
+        # image must be gif, jpeg, or png
+        file_type = mimetypes.guess_type(filename)
+        if file_type is None:
+            raise WeibopError('Could not determine file type')
+        file_type = file_type[0]
+        if file_type not in ['image/gif', 'image/jpeg', 'image/png']:
+            raise WeibopError('Invalid file type for image: %s' % file_type)
+
+        # build the mulitpart-formdata body
+        fp = open(filename, 'rb')
+        BOUNDARY = 'Tw3ePy'
+        body = []
+        if status is not None:            
+            body.append('--' + BOUNDARY)
+            body.append('Content-Disposition: form-data; name="status"')
+            body.append('Content-Type: text/plain; charset=US-ASCII')
+            body.append('Content-Transfer-Encoding: 8bit')
+            body.append('')
+            body.append(status)
+        if source is not None:            
+            body.append('--' + BOUNDARY)
+            body.append('Content-Disposition: form-data; name="source"')
+            body.append('Content-Type: text/plain; charset=US-ASCII')
+            body.append('Content-Transfer-Encoding: 8bit')
+            body.append('')
+            body.append(source)
+        if lat is not None:            
+            body.append('--' + BOUNDARY)
+            body.append('Content-Disposition: form-data; name="lat"')
+            body.append('Content-Type: text/plain; charset=US-ASCII')
+            body.append('Content-Transfer-Encoding: 8bit')
+            body.append('')
+            body.append(lat)
+        if long is not None:            
+            body.append('--' + BOUNDARY)
+            body.append('Content-Disposition: form-data; name="long"')
+            body.append('Content-Type: text/plain; charset=US-ASCII')
+            body.append('Content-Transfer-Encoding: 8bit')
+            body.append('')
+            body.append(long)
+        body.append('--' + BOUNDARY)
+        body.append('Content-Disposition: form-data; name="'+ contentname +'"; filename="%s"' % filename)
+        body.append('Content-Type: %s' % file_type)
+        body.append('Content-Transfer-Encoding: binary')
+        body.append('')
+        body.append(fp.read())
+        body.append('--' + BOUNDARY + '--')
+        body.append('')
+        fp.close()        
+        body.append('--' + BOUNDARY + '--')
+        body.append('')
+        body = '\r\n'.join(body)
+        # build headers
+        headers = {
+            'Content-Type': 'multipart/form-data; boundary=Tw3ePy',
+            'Content-Length': len(body)
+        }
+
+        return headers, body
+

main/weibopy/api.pyc

Binary file added.

main/weibopy/auth.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+from urllib2 import Request, urlopen
+import base64
+
+from weibopy import oauth
+from weibopy.error import WeibopError
+from weibopy.api import API
+
+
+class AuthHandler(object):
+
+    def apply_auth(self, url, method, headers, parameters):
+        """Apply authentication headers to request"""
+        raise NotImplementedError
+
+    def get_username(self):
+        """Return the username of the authenticated user"""
+        raise NotImplementedError
+
+
+class BasicAuthHandler(AuthHandler):
+
+    def __init__(self, username, password):
+        self.username = username
+        self._b64up = base64.b64encode('%s:%s' % (username, password))
+
+    def apply_auth(self, url, method, headers, parameters):
+        headers['Authorization'] = 'Basic %s' % self._b64up
+        
+    def get_username(self):
+        return self.username
+
+
+class OAuthHandler(AuthHandler):
+    """OAuth authentication handler"""
+
+    OAUTH_HOST = 'api.t.sina.com.cn'
+    OAUTH_ROOT = '/oauth/'
+
+    def __init__(self, consumer_key, consumer_secret, callback=None, secure=False):
+        self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+        self._sigmethod = oauth.OAuthSignatureMethod_HMAC_SHA1()
+        self.request_token = None
+        self.access_token = None
+        self.callback = callback
+        self.username = None
+        self.secure = secure
+
+    def _get_oauth_url(self, endpoint):
+        if self.secure:
+            prefix = 'https://'
+        else:
+            prefix = 'http://'
+
+        return prefix + self.OAUTH_HOST + self.OAUTH_ROOT + endpoint
+
+    def apply_auth(self, url, method, headers, parameters):
+        request = oauth.OAuthRequest.from_consumer_and_token(
+            self._consumer, http_url=url, http_method=method,
+            token=self.access_token, parameters=parameters
+        )
+        request.sign_request(self._sigmethod, self._consumer, self.access_token)
+        headers.update(request.to_header())
+
+    def _get_request_token(self):
+        try:
+            url = self._get_oauth_url('request_token')
+            request = oauth.OAuthRequest.from_consumer_and_token(
+                self._consumer, http_url=url, callback=self.callback
+            )
+            request.sign_request(self._sigmethod, self._consumer, None)
+            print url
+            print request.to_header()
+            resp = urlopen(Request(url, headers=request.to_header()))
+            return oauth.OAuthToken.from_string(resp.read())
+        except Exception, e:
+            raise WeibopError(e)
+
+    def set_request_token(self, key, secret):
+        self.request_token = oauth.OAuthToken(key, secret)
+
+    def set_access_token(self, key, secret):
+        self.access_token = oauth.OAuthToken(key, secret)
+
+    def get_authorization_url(self, signin_with_twitter=False):
+        """Get the authorization URL to redirect the user"""
+        try:
+            # get the request token
+            self.request_token = self._get_request_token()
+
+            # build auth request and return as url
+            if signin_with_twitter:
+                url = self._get_oauth_url('authenticate')
+            else:
+                url = self._get_oauth_url('authorize')
+            request = oauth.OAuthRequest.from_token_and_callback(
+                token=self.request_token, http_url=url, callback=self.callback
+            )
+
+            return request.to_url()
+        except Exception, e:
+            raise WeibopError(e)
+
+    def get_access_token(self, verifier=None):
+        """
+        After user has authorized the request token, get access token
+        with user supplied verifier.
+        """
+        try:
+            url = self._get_oauth_url('access_token')
+
+            # build request
+            request = oauth.OAuthRequest.from_consumer_and_token(
+                self._consumer,
+                token=self.request_token, http_url=url,
+                verifier=str(verifier)
+            )
+            request.sign_request(self._sigmethod, self._consumer, self.request_token)
+
+            # send request                        
+            resp = urlopen(Request(url, headers=request.to_header()))
+            self.access_token = oauth.OAuthToken.from_string(resp.read())
+            
+            print 'Access token key: '+ str(self.access_token.key)
+            print 'Access token secret: '+ str(self.access_token.secret)
+            
+            return self.access_token
+        except Exception, e:
+            raise WeibopError(e)
+        
+    def setToken(self, token, tokenSecret):
+        self.access_token = oauth.OAuthToken(token, tokenSecret)
+        
+    def get_username(self):
+        if self.username is None:
+            api = API(self)
+            user = api.verify_credentials()
+            if user:
+                self.username = user.screen_name
+            else:
+                raise WeibopError("Unable to get username, invalid oauth token!")
+        return self.username

main/weibopy/auth.pyc

Binary file added.

main/weibopy/binder.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+import httplib
+import urllib
+import time
+import re
+from weibopy.error import WeibopError
+from weibopy.utils import convert_to_utf8_str
+
+re_path_template = re.compile('{\w+}')
+
+
+def bind_api(**config):
+
+    class APIMethod(object):
+        
+        path = config['path']
+        payload_type = config.get('payload_type', None)
+        payload_list = config.get('payload_list', False)
+        allowed_param = config.get('allowed_param', [])
+        method = config.get('method', 'GET')
+        require_auth = config.get('require_auth', False)
+        search_api = config.get('search_api', False)
+                
+        def __init__(self, api, args, kargs):
+            # If authentication is required and no credentials
+            # are provided, throw an error.
+            if self.require_auth and not api.auth:
+                raise WeibopError('Authentication required!')
+
+            self.api = api
+            self.post_data = kargs.pop('post_data', None)
+            self.retry_count = kargs.pop('retry_count', api.retry_count)
+            self.retry_delay = kargs.pop('retry_delay', api.retry_delay)
+            self.retry_errors = kargs.pop('retry_errors', api.retry_errors)
+            self.headers = kargs.pop('headers', {})
+            self.build_parameters(args, kargs)
+            # Pick correct URL root to use
+            if self.search_api:
+                self.api_root = api.search_root
+            else:
+                self.api_root = api.api_root
+            
+            # Perform any path variable substitution
+            self.build_path()
+
+            if api.secure:
+                self.scheme = 'https://'
+            else:
+                self.scheme = 'http://'
+
+            if self.search_api:
+                self.host = api.search_host
+            else:
+                self.host = api.host
+
+            # Manually set Host header to fix an issue in python 2.5
+            # or older where Host is set including the 443 port.
+            # This causes Twitter to issue 301 redirect.
+            # See Issue http://github.com/joshthecoder/tweepy/issues/#issue/12
+            self.headers['Host'] = self.host
+
+        def build_parameters(self, args, kargs):
+            self.parameters = {}
+            for idx, arg in enumerate(args):
+                try:
+                    self.parameters[self.allowed_param[idx]] = convert_to_utf8_str(arg)
+                except IndexError:
+                    raise WeibopError('Too many parameters supplied!')
+
+            for k, arg in kargs.items():
+                if arg is None:
+                    continue
+                if k in self.parameters:
+                    raise WeibopError('Multiple values for parameter %s supplied!' % k)
+
+                self.parameters[k] = convert_to_utf8_str(arg)
+
+        def build_path(self):
+            for variable in re_path_template.findall(self.path):
+                name = variable.strip('{}')
+
+                if name == 'user' and self.api.auth:
+                    value = self.api.auth.get_username()
+                else:
+                    try:
+                        value = urllib.quote(self.parameters[name])
+                    except KeyError:
+                        raise WeibopError('No parameter value found for path variable: %s' % name)
+                    del self.parameters[name]
+
+                self.path = self.path.replace(variable, value)
+
+        def execute(self):
+            # Build the request URL
+            url = self.api_root + self.path
+            if self.api.source is not None:
+                self.parameters.setdefault('source',self.api.source)
+            
+            if len(self.parameters):
+                if self.method == 'GET' or self.method == 'DELETE':
+                    url = '%s?%s' % (url, urllib.urlencode(self.parameters))  
+                else:
+                    self.headers.setdefault("User-Agent","python")
+                    if self.post_data is None:
+                        self.headers.setdefault("Accept","text/html")                        
+                        self.headers.setdefault("Content-Type","application/x-www-form-urlencoded")
+                        self.post_data = urllib.urlencode(self.parameters)           
+            # Query the cache if one is available
+            # and this request uses a GET method.
+            if self.api.cache and self.method == 'GET':
+                cache_result = self.api.cache.get(url)
+                # if cache result found and not expired, return it
+                if cache_result:
+                    # must restore api reference
+                    if isinstance(cache_result, list):
+                        for result in cache_result:
+                            result._api = self.api
+                    else:
+                        cache_result._api = self.api
+                    return cache_result
+                #urllib.urlencode(self.parameters)
+            # Continue attempting request until successful
+            # or maximum number of retries is reached.
+            sTime = time.time()
+            retries_performed = 0
+            while retries_performed < self.retry_count + 1:
+                # Open connection
+                # FIXME: add timeout
+                if self.api.secure:
+                    conn = httplib.HTTPSConnection(self.host)
+                else:
+                    conn = httplib.HTTPConnection(self.host)
+                # Apply authentication
+                if self.api.auth:
+                    self.api.auth.apply_auth(
+                            self.scheme + self.host + url,
+                            self.method, self.headers, self.parameters
+                    )
+                # Execute request
+                try:
+                    conn.request(self.method, url, headers=self.headers, body=self.post_data)
+                    resp = conn.getresponse()
+                except Exception, e:
+                    raise WeibopError('Failed to send request: %s' % e + "url=" + str(url) +",self.headers="+ str(self.headers))
+
+                # Exit request loop if non-retry error code
+                if self.retry_errors:
+                    if resp.status not in self.retry_errors: break
+                else:
+                    if resp.status == 200: break
+
+                # Sleep before retrying request again
+                time.sleep(self.retry_delay)
+                retries_performed += 1
+
+            # If an error was returned, throw an exception
+            body = resp.read()
+            self.api.last_response = resp
+            if self.api.log is not None:
+                requestUrl = "URL:http://"+ self.host + url
+                eTime = '%.0f' % ((time.time() - sTime) * 1000)
+                postData = ""
+                if self.post_data is not None:
+                    postData = ",post:"+ self.post_data[0:500]
+                self.api.log.debug(requestUrl +",time:"+ str(eTime)+ postData+",result:"+ body )
+            if resp.status != 200:
+                try:
+                    json = self.api.parser.parse_error(self, body)
+                    error_code =  json['error_code']
+                    error =  json['error']
+                    error_msg = 'error_code:' + error_code +','+ error
+                except Exception:
+                    error_msg = "Weibo error response: status code = %s" % resp.status
+                raise WeibopError(error_msg)
+            
+            # Parse the response payload
+            result = self.api.parser.parse(self, body)
+            conn.close()
+
+            # Store result into cache if one is available.
+            if self.api.cache and self.method == 'GET' and result:
+                self.api.cache.store(url, result)
+            return result
+
+    def _call(api, *args, **kargs):
+
+        method = APIMethod(api, args, kargs)
+        return method.execute()
+
+
+    # Set pagination mode
+    if 'cursor' in APIMethod.allowed_param:
+        _call.pagination_mode = 'cursor'
+    elif 'page' in APIMethod.allowed_param:
+        _call.pagination_mode = 'page'
+
+    return _call
+

main/weibopy/binder.pyc

Binary file added.

main/weibopy/cache.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+import time
+import threading
+import os
+import cPickle as pickle
+
+try:
+    import hashlib
+except ImportError:
+    # python 2.4
+    import md5 as hashlib
+
+try:
+    import fcntl
+except ImportError:
+    # Probably on a windows system
+    # TODO: use win32file
+    pass
+
+
+class Cache(object):
+    """Cache interface"""
+
+    def __init__(self, timeout=60):
+        """Initialize the cache
+            timeout: number of seconds to keep a cached entry
+        """
+        self.timeout = timeout
+
+    def store(self, key, value):
+        """Add new record to cache
+            key: entry key
+            value: data of entry
+        """
+        raise NotImplementedError
+
+    def get(self, key, timeout=None):
+        """Get cached entry if exists and not expired
+            key: which entry to get
+            timeout: override timeout with this value [optional]
+        """
+        raise NotImplementedError
+
+    def count(self):
+        """Get count of entries currently stored in cache"""
+        raise NotImplementedError
+
+    def cleanup(self):
+        """Delete any expired entries in cache."""
+        raise NotImplementedError
+
+    def flush(self):
+        """Delete all cached entries"""
+        raise NotImplementedError
+
+
+class MemoryCache(Cache):
+    """In-memory cache"""
+
+    def __init__(self, timeout=60):
+        Cache.__init__(self, timeout)
+        self._entries = {}
+        self.lock = threading.Lock()
+
+    def __getstate__(self):
+        # pickle
+        return {'entries': self._entries, 'timeout': self.timeout}
+
+    def __setstate__(self, state):
+        # unpickle
+        self.lock = threading.Lock()
+        self._entries = state['entries']
+        self.timeout = state['timeout']
+
+    def _is_expired(self, entry, timeout):
+        return timeout > 0 and (time.time() - entry[0]) >= timeout
+
+    def store(self, key, value):
+        self.lock.acquire()
+        self._entries[key] = (time.time(), value)
+        self.lock.release()
+
+    def get(self, key, timeout=None):
+        self.lock.acquire()
+        try:
+            # check to see if we have this key
+            entry = self._entries.get(key)
+            if not entry:
+                # no hit, return nothing
+                return None
+
+            # use provided timeout in arguments if provided
+            # otherwise use the one provided during init.
+            if timeout is None:
+                timeout = self.timeout
+
+            # make sure entry is not expired
+            if self._is_expired(entry, timeout):
+                # entry expired, delete and return nothing
+                del self._entries[key]
+                return None
+
+            # entry found and not expired, return it
+            return entry[1]
+        finally:
+            self.lock.release()
+
+    def count(self):
+        return len(self._entries)
+
+    def cleanup(self):
+        self.lock.acquire()
+        try:
+            for k, v in self._entries.items():
+                if self._is_expired(v, self.timeout):
+                    del self._entries[k]
+        finally:
+            self.lock.release()
+
+    def flush(self):
+        self.lock.acquire()
+        self._entries.clear()
+        self.lock.release()
+
+
+class FileCache(Cache):
+    """File-based cache"""
+
+    # locks used to make cache thread-safe
+    cache_locks = {}
+
+    def __init__(self, cache_dir, timeout=60):
+        Cache.__init__(self, timeout)
+        if os.path.exists(cache_dir) is False:
+            os.mkdir(cache_dir)
+        self.cache_dir = cache_dir
+        if cache_dir in FileCache.cache_locks:
+            self.lock = FileCache.cache_locks[cache_dir]
+        else:
+            self.lock = threading.Lock()
+            FileCache.cache_locks[cache_dir] = self.lock
+
+        if os.name == 'posix':
+            self._lock_file = self._lock_file_posix
+            self._unlock_file = self._unlock_file_posix
+        elif os.name == 'nt':
+            self._lock_file = self._lock_file_win32
+            self._unlock_file = self._unlock_file_win32
+        else:
+            print 'Warning! FileCache locking not supported on this system!'
+            self._lock_file = self._lock_file_dummy
+            self._unlock_file = self._unlock_file_dummy
+
+    def _get_path(self, key):
+        md5 = hashlib.md5()
+        md5.update(key)
+        return os.path.join(self.cache_dir, md5.hexdigest())
+
+    def _lock_file_dummy(self, path, exclusive=True):
+        return None
+
+    def _unlock_file_dummy(self, lock):
+        return
+
+    def _lock_file_posix(self, path, exclusive=True):
+        lock_path = path + '.lock'
+        if exclusive is True:
+            f_lock = open(lock_path, 'w')
+            fcntl.lockf(f_lock, fcntl.LOCK_EX)
+        else:
+            f_lock = open(lock_path, 'r')
+            fcntl.lockf(f_lock, fcntl.LOCK_SH)
+        if os.path.exists(lock_path) is False:
+            f_lock.close()
+            return None
+        return f_lock
+
+    def _unlock_file_posix(self, lock):
+        lock.close()
+
+    def _lock_file_win32(self, path, exclusive=True):
+        # TODO: implement
+        return None
+
+    def _unlock_file_win32(self, lock):
+        # TODO: implement
+        return
+
+    def _delete_file(self, path):
+        os.remove(path)
+        if os.path.exists(path + '.lock'):
+            os.remove(path + '.lock')
+
+    def store(self, key, value):
+        path = self._get_path(key)
+        self.lock.acquire()
+        try:
+            # acquire lock and open file
+            f_lock = self._lock_file(path)
+            datafile = open(path, 'wb')
+
+            # write data
+            pickle.dump((time.time(), value), datafile)
+
+            # close and unlock file
+            datafile.close()
+            self._unlock_file(f_lock)
+        finally:
+            self.lock.release()
+
+    def get(self, key, timeout=None):
+        return self._get(self._get_path(key), timeout)
+
+    def _get(self, path, timeout):
+        if os.path.exists(path) is False:
+            # no record
+            return None
+        self.lock.acquire()
+        try:
+            # acquire lock and open
+            f_lock = self._lock_file(path, False)
+            datafile = open(path, 'rb')
+
+            # read pickled object
+            created_time, value = pickle.load(datafile)
+            datafile.close()
+
+            # check if value is expired
+            if timeout is None:
+                timeout = self.timeout
+            if timeout > 0 and (time.time() - created_time) >= timeout:
+                # expired! delete from cache
+                value = None
+                self._delete_file(path)
+
+            # unlock and return result
+            self._unlock_file(f_lock)
+            return value
+        finally:
+            self.lock.release()
+
+    def count(self):
+        c = 0
+        for entry in os.listdir(self.cache_dir):
+            if entry.endswith('.lock'):
+                continue
+            c += 1
+        return c
+
+    def cleanup(self):
+        for entry in os.listdir(self.cache_dir):
+            if entry.endswith('.lock'):
+                continue
+            self._get(os.path.join(self.cache_dir, entry), None)
+
+    def flush(self):
+        for entry in os.listdir(self.cache_dir):
+            if entry.endswith('.lock'):
+                continue
+            self._delete_file(os.path.join(self.cache_dir, entry))
+

main/weibopy/cache.pyc

Binary file added.

main/weibopy/cursor.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+from weibopy.error import WeibopError
+
+class Cursor(object):
+    """Pagination helper class"""
+
+    def __init__(self, method, *args, **kargs):
+        if hasattr(method, 'pagination_mode'):
+            if method.pagination_mode == 'cursor':
+                self.iterator = CursorIterator(method, args, kargs)
+            else:
+                self.iterator = PageIterator(method, args, kargs)
+        else:
+            raise WeibopError('This method does not perform pagination')
+
+    def pages(self, limit=0):
+        """Return iterator for pages"""
+        if limit > 0:
+            self.iterator.limit = limit
+        return self.iterator
+
+    def items(self, limit=0):
+        """Return iterator for items in each page"""
+        i = ItemIterator(self.iterator)
+        i.limit = limit
+        return i
+
+class BaseIterator(object):
+
+    def __init__(self, method, args, kargs):
+        self.method = method
+        self.args = args
+        self.kargs = kargs
+        self.limit = 0
+
+    def next(self):
+        raise NotImplementedError
+
+    def prev(self):
+        raise NotImplementedError
+
+    def __iter__(self):
+        return self
+
+class CursorIterator(BaseIterator):
+
+    def __init__(self, method, args, kargs):
+        BaseIterator.__init__(self, method, args, kargs)
+        self.next_cursor = -1
+        self.prev_cursor = 0
+        self.count = 0
+
+    def next(self):
+        if self.next_cursor == 0 or (self.limit and self.count == self.limit):
+            raise StopIteration
+        data, cursors = self.method(
+                cursor=self.next_cursor, *self.args, **self.kargs
+        )
+        self.prev_cursor, self.next_cursor = cursors
+        if len(data) == 0:
+            raise StopIteration
+        self.count += 1
+        return data
+
+    def prev(self):
+        if self.prev_cursor == 0:
+            raise WeibopError('Can not page back more, at first page')
+        data, self.next_cursor, self.prev_cursor = self.method(
+                cursor=self.prev_cursor, *self.args, **self.kargs
+        )
+        self.count -= 1
+        return data
+
+class PageIterator(BaseIterator):
+
+    def __init__(self, method, args, kargs):
+        BaseIterator.__init__(self, method, args, kargs)
+        self.current_page = 0
+
+    def next(self):
+        self.current_page += 1
+        items = self.method(page=self.current_page, *self.args, **self.kargs)
+        if len(items) == 0 or (self.limit > 0 and self.current_page > self.limit):
+            raise StopIteration
+        return items
+
+    def prev(self):
+        if (self.current_page == 1):
+            raise WeibopError('Can not page back more, at first page')
+        self.current_page -= 1
+        return self.method(page=self.current_page, *self.args, **self.kargs)
+
+class ItemIterator(BaseIterator):
+
+    def __init__(self, page_iterator):
+        self.page_iterator = page_iterator
+        self.limit = 0
+        self.current_page = None
+        self.page_index = -1
+        self.count = 0
+
+    def next(self):
+        if self.limit > 0 and self.count == self.limit:
+            raise StopIteration
+        if self.current_page is None or self.page_index == len(self.current_page) - 1:
+            # Reached end of current page, get the next page...
+            self.current_page = self.page_iterator.next()
+            self.page_index = -1
+        self.page_index += 1
+        self.count += 1
+        return self.current_page[self.page_index]
+
+    def prev(self):
+        if self.current_page is None:
+            raise WeibopError('Can not go back more, at first page')
+        if self.page_index == 0:
+            # At the beginning of the current page, move to next...
+            self.current_page = self.page_iterator.prev()
+            self.page_index = len(self.current_page)
+            if self.page_index == 0:
+                raise WeibopError('No more items')
+        self.page_index -= 1
+        self.count -= 1
+        return self.current_page[self.page_index]
+

main/weibopy/cursor.pyc

Binary file added.

main/weibopy/error.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+class WeibopError(Exception):
+    """Weibopy exception"""
+
+    def __init__(self, reason):
+        self.reason = str(reason)
+
+    def __str__(self):
+        return self.reason
+

main/weibopy/error.pyc

Binary file added.

main/weibopy/models.py

+
+# Copyright 2009-2010 Joshua Roesslein
+# See LICENSE for details.
+
+from weibopy.utils import parse_datetime, parse_html_value, parse_a_href, \
+        parse_search_datetime, unescape_html
+
+class ResultSet(list):
+    """A list like object that holds results from a Twitter API query."""
+
+
+class Model(object):
+
+    def __init__(self, api=None):
+        self._api = api
+
+    def __getstate__(self):
+        # pickle
+        pickle = dict(self.__dict__)
+        del pickle['_api']  # do not pickle the API reference
+        return pickle
+
+    @classmethod
+    def parse(cls, api, json):
+        """Parse a JSON object into a model instance."""
+        raise NotImplementedError
+
+    @classmethod
+    def parse_list(cls, api, json_list):
+        """Parse a list of JSON objects into a result set of model instances."""
+        results = ResultSet()
+        for obj in json_list:
+            results.append(cls.parse(api, obj))
+        return results
+
+
+class Status(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        status = cls(api)
+        for k, v in json.items():
+            if k == 'user':
+                user = User.parse(api, v)
+                setattr(status, 'author', user)
+                setattr(status, 'user', user)  # DEPRECIATED
+            elif k == 'screen_name':
+                setattr(status, k, v)
+            elif k == 'created_at':
+                setattr(status, k, parse_datetime(v))
+            elif k == 'source':
+                if '<' in v:
+                    setattr(status, k, parse_html_value(v))
+                    setattr(status, 'source_url', parse_a_href(v))
+                else:
+                    setattr(status, k, v)
+            elif k == 'retweeted_status':
+                setattr(status, k, Status.parse(api, v))
+            elif k == 'geo':
+                setattr(status, k, Geo.parse(api, v))
+            else:
+                setattr(status, k, v)
+        return status
+
+    def destroy(self):
+        return self._api.destroy_status(self.id)
+
+    def retweet(self):
+        return self._api.retweet(self.id)
+
+    def retweets(self):
+        return self._api.retweets(self.id)
+
+    def favorite(self):
+        return self._api.create_favorite(self.id)
+class Geo(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        geo = cls(api)
+        if json is not None:
+            for k, v in json.items():
+                setattr(geo, k, v)
+        return geo
+    
+class Comments(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        comments = cls(api)
+        for k, v in json.items():
+            if k == 'user':
+                user = User.parse(api, v)
+                setattr(comments, 'author', user)
+                setattr(comments, 'user', user)
+            elif k == 'status':
+                status = Status.parse(api, v)
+                setattr(comments, 'user', status)
+            elif k == 'created_at':
+                setattr(comments, k, parse_datetime(v))
+            elif k == 'reply_comment':
+                setattr(comments, k, User.parse(api, v))
+            else:
+                setattr(comments, k, v)
+        return comments
+
+    def destroy(self):
+        return self._api.destroy_status(self.id)
+
+    def retweet(self):
+        return self._api.retweet(self.id)
+
+    def retweets(self):
+        return self._api.retweets(self.id)
+
+    def favorite(self):
+        return self._api.create_favorite(self.id)
+
+class User(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        user = cls(api)
+        for k, v in json.items():
+            if k == 'created_at':
+                setattr(user, k, parse_datetime(v))
+            elif k == 'status':
+                setattr(user, k, Status.parse(api, v))
+            elif k == 'screen_name':
+                setattr(user, k, v)
+            elif k == 'following':
+                # twitter sets this to null if it is false
+                if v is True:
+                    setattr(user, k, True)
+                else:
+                    setattr(user, k, False)
+            else:
+                setattr(user, k, v)
+        return user
+
+    @classmethod
+    def parse_list(cls, api, json_list):
+        if isinstance(json_list, list):
+            item_list = json_list
+        else:
+            item_list = json_list['users']
+
+        results = ResultSet()
+        for obj in item_list:
+            results.append(cls.parse(api, obj))
+        return results
+
+    def timeline(self, **kargs):
+        return self._api.user_timeline(user_id=self.id, **kargs)
+
+    def friends(self, **kargs):
+        return self._api.friends(user_id=self.id, **kargs)
+
+    def followers(self, **kargs):
+        return self._api.followers(user_id=self.id, **kargs)
+
+    def follow(self):
+        self._api.create_friendship(user_id=self.id)
+        self.following = True
+
+    def unfollow(self):
+        self._api.destroy_friendship(user_id=self.id)
+        self.following = False
+
+    def lists_memberships(self, *args, **kargs):
+        return self._api.lists_memberships(user=self.screen_name, *args, **kargs)
+
+    def lists_subscriptions(self, *args, **kargs):
+        return self._api.lists_subscriptions(user=self.screen_name, *args, **kargs)
+
+    def lists(self, *args, **kargs):
+        return self._api.lists(user=self.screen_name, *args, **kargs)