├── example
├── accounts
│ ├── __init__.py
│ ├── tests.py
│ ├── admin.py
│ ├── urls.py
│ ├── models.py
│ └── views.py
├── example
│ ├── __init__.py
│ ├── urls.py
│ ├── wsgi.py
│ ├── views.py
│ └── settings.py
├── manage.py
└── templates
│ └── home.html
├── duoshuo
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── ds_import.py
├── templatetags
│ ├── __init__.py
│ └── duoshuo_tags.py
├── requirements.txt
├── .gitignore
├── models.py
├── tests.py
├── interfaces.json
├── utils.py
└── __init__.py
├── .gitignore
├── setup.py
├── README.md
└── LICENSE.txt
/example/accounts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/example/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/duoshuo/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/duoshuo/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/duoshuo/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/duoshuo/management/commands/ds_import.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/duoshuo/requirements.txt:
--------------------------------------------------------------------------------
1 | PyJWT
2 | requests
--------------------------------------------------------------------------------
/duoshuo/.gitignore:
--------------------------------------------------------------------------------
1 | forms.py
2 | urls.py
3 | views.py
4 | templates
5 | *.pyc
6 |
--------------------------------------------------------------------------------
/example/accounts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/example/accounts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/duoshuo/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.db import models
3 | from django.contrib.auth.models import User
4 |
5 |
6 |
--------------------------------------------------------------------------------
/example/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/example/example/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, include, url
2 | from django.contrib import admin
3 |
4 | urlpatterns = patterns('',
5 | # Examples:
6 | url(r'^$', 'example.views.home', name='home'),
7 | url('', include('accounts.urls')),
8 | # url(r'^blog/', include('blog.urls')),
9 |
10 | url(r'^admin/', include(admin.site.urls)),
11 | )
12 |
--------------------------------------------------------------------------------
/example/accounts/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, include, url
2 | from django.contrib.auth import views as auth_views
3 |
4 |
5 | urlpatterns = patterns('accounts.views',
6 | url(r'^login/$', auth_views.login, name='login'),
7 | url(r'^register/$', 'register'),
8 | url(r'^callback/$', 'callback'),
9 | url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
10 | )
11 |
--------------------------------------------------------------------------------
/example/example/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for example project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
12 |
13 | from django.core.wsgi import get_wsgi_application
14 | application = get_wsgi_application()
15 |
--------------------------------------------------------------------------------
/example/accounts/models.py:
--------------------------------------------------------------------------------
1 | # encoding: utf8
2 | from django.db import models
3 |
4 | from django.contrib.auth.models import User
5 |
6 | class UserProfile(models.Model):
7 | user = models.OneToOneField(User, related_name='profile')
8 | duoshuo_id = models.IntegerField(default=0)
9 | token = models.IntegerField(default=0)
10 | avatar = models.TextField(blank=True, null=True)
11 |
12 | def __unicode__(self):
13 | return self.user.username
14 |
15 | def create_user_profile(sender=None, instance=None, created=False, **kwargs):
16 | if created:
17 | userprofile = UserProfile.objects.create(user=instance)
18 | userprofile.save()
19 | models.signals.post_save.connect(create_user_profile, sender=User)
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | bin/
12 | build/
13 | develop-eggs/
14 | dist/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # Installer logs
26 | pip-log.txt
27 | pip-delete-this-directory.txt
28 |
29 | # Unit test / coverage reports
30 | htmlcov/
31 | .tox/
32 | .coverage
33 | .cache
34 | nosetests.xml
35 | coverage.xml
36 |
37 | # Translations
38 | *.mo
39 |
40 | # Mr Developer
41 | .mr.developer.cfg
42 | .project
43 | .pydevproject
44 |
45 | # Rope
46 | .ropeproject
47 |
48 | # Django stuff:
49 | *.log
50 | *.pot
51 |
52 | # Sphinx documentation
53 | docs/_build/
54 | migrations/
55 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 |
4 | setup(
5 | name="duoshuo-python-sdk",
6 | version="0.1",
7 | description="A Python library for using the duoshuo API",
8 | long_description=open('README.md').read(),
9 | author="Perchouli",
10 | author_email="jp.chenyang@gmail.com",
11 | url="https://github.com/duoshuo/duoshuo-python-sdk",
12 | packages=find_packages(),
13 | package_data={
14 | 'duoshuo' : ['*.json',]
15 | },
16 | install_requires=[
17 | 'PyJWT',
18 | 'requests>=0.14.0'
19 | ],
20 | classifiers=[
21 | "Development Status :: 3 - Alpha",
22 | "Environment :: Web Environment",
23 | "Intended Audience :: Developers",
24 | "License :: OSI Approved :: Apache Software License",
25 | "Operating System :: OS Independent",
26 | "Programming Language :: Python",
27 | ],
28 | include_package_data=True,
29 | zip_safe=False,
30 | )
--------------------------------------------------------------------------------
/example/example/views.py:
--------------------------------------------------------------------------------
1 | # encoding: utf8
2 |
3 | from django.http import HttpResponse
4 | from django.template.response import TemplateResponse
5 | from django.shortcuts import render
6 | from accounts.models import UserProfile
7 |
8 | import settings
9 | import jwt
10 | def home(request):
11 | duoshuo_jwt_token = None
12 | ctx = {
13 | 'settings': settings,
14 | }
15 | if request.user.is_authenticated():
16 | ctx['profile'] = UserProfile.objects.get(user_id=request.user.id) #django 1.7没有get_profile
17 |
18 | # 实现JWT登录,参看:http://dev.duoshuo.com/docs/501e6ce1cff715f71800000d
19 | token = {
20 | "short_name": settings.DUOSHUO_SHORT_NAME,
21 | "user_key": request.user.id,
22 | "name": request.user.username
23 | }
24 | ctx['duoshuo_jwt_token'] = duoshuo_jwt_token = jwt.encode(token, settings.DUOSHUO_SECRET)
25 |
26 | response = TemplateResponse(request, 'home.html', ctx)
27 | response.set_cookie('duoshuo_token', duoshuo_jwt_token)
28 | return response
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Duoshuo Python SDK
4 |
5 | 多说Python SDK支持用Python语言开发的网站,对其提供[多说]插件的支持。使用中遇到的问题请[到多说开发者中心提问](http://dev.duoshuo.com/threads/500c9c58a03193c12400000c "多说开发者中心") 。
6 |
7 | # Requirements
8 |
9 | Python 2.6+
10 |
11 | Django 1.6+ (如果在Django中使用)
12 |
13 | # Install
14 |
15 | python setup.py install
16 |
17 | # Index
18 |
19 | [Python Useage](#python-usage)
20 |
21 | [Django useage](#django-usage)
22 |
23 |
24 | # Python Usage
25 |
26 | 作为Python models来使用
27 |
28 | ### Core (__init__.py)
29 |
30 | sdk核心功能: 交换token,生成授权链接,调用api接口
31 |
32 | #### 实例化duoshuoAPI
33 |
34 | from duoshuo import DuoshuoAPI
35 |
36 | api = DuoshuoAPI(short_name=YOUR_DUOSHUO_SHORT_NAME, secret=YOUR_DUOSHUO_SECRET)
37 |
38 | #例如要获取用户信息
39 | api.users.profile(user_id=1)
40 |
41 |
42 | 更多API可以查看[多说开发文档](http://dev.duoshuo.com/docs "多说开发文档") 。
43 |
44 | #### 交换token
45 | 访问需要登录的接口时要先进行授权,采用OAuth2.0协议,Python SDK提供交换token的处理,实例化api后可以直接传入code来获取token:
46 |
47 | code = request.GET.get('code') #获得GET参数(以Django为例)
48 |
49 | token = api.get_token(code=code)
50 |
51 |
52 | # Django Usage
53 |
54 | 作为Django app来使用
55 |
56 | #### 安装duoshuo插件
57 |
58 | # settings.py
59 | INSTALLED_APPS = (
60 | ...
61 | 'duoshuo',
62 | )
63 |
64 | DUOSHUO_SECRET = '你的多说secret,在多说管理后台 - 设置 - 密钥'
65 | DUOSHUO_SHORT_NAME = '你的多说short name,比如你注册了example.duoshuo.com,short name就是example'
66 |
67 | #### 显示多说评论框
68 |
69 | {% load duoshuo_tags %}
70 |
71 | {% duoshuo_comments %}
72 |
73 | #给多说评论框传递其他short name
74 | {% duoshuo_comments '其他short name' %}
75 |
76 |
--------------------------------------------------------------------------------
/duoshuo/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | #!/usr/bin/env python
3 |
4 | """
5 | 多说API测试文件。作为通用的Python程序,没有使用Django的TestCase
6 | """
7 | import os
8 | import unittest
9 | try:
10 | import json
11 | _parse_json = lambda s: json.loads(s)
12 | except ImportError:
13 | try:
14 | import simplejson
15 | _parse_json = lambda s: simplejson.loads(s)
16 | except ImportError:
17 | from django.utils import simplejson
18 | _parse_json = lambda s: simplejson.loads(s)
19 |
20 | os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21 | import duoshuo
22 |
23 | import utils
24 |
25 |
26 | class DuoshuoAPITest(unittest.TestCase):
27 | DUOSHUO_SHORT_NAME = 'official'
28 | DUOSHUO_SECRET = 'a'*32
29 | API = duoshuo.DuoshuoAPI(short_name=DUOSHUO_SHORT_NAME, secret=DUOSHUO_SECRET)
30 |
31 | def test_host(self):
32 | api = self.API
33 | host = api.host
34 | self.assertEqual(host, 'api.duoshuo.com')
35 |
36 | def test_get_url(self):
37 | redirect_uri = 'example.com'
38 | api = self.API
39 | url = utils.get_url(api, redirect_uri=redirect_uri)
40 | self.assertEqual(url,
41 | 'http://%s/oauth2/authorize?client_id=%s&redirect_uri=%s&response_type=code' %
42 | (api.host, self.DUOSHUO_SHORT_NAME, redirect_uri)
43 | )
44 |
45 | def test_user_api(self):
46 | api = self.API
47 | response = api.users.profile(user_id=1)
48 | user_id = response['response']['user_id']
49 |
50 | self.assertEqual(int(user_id), 1)
51 |
52 | # 以下测试要是short_name和secret正确设置
53 |
54 | # def test_log_api(self):
55 | # api = self.API
56 | # response = api.log.list()
57 | # code = response['code']
58 | # self.assertEqual(int(code), 0)
59 |
60 | if __name__ == '__main__':
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/duoshuo/interfaces.json:
--------------------------------------------------------------------------------
1 | {
2 | "posts": {
3 | "list": {
4 | "required": [
5 | "thread_id"
6 | ],
7 | "method": "GET",
8 | "formats": [
9 | "json",
10 | "jsonp"
11 | ]
12 | },
13 | "details": {
14 | "required": [
15 | "post_id"
16 | ],
17 | "method": "GET",
18 | "formats": [
19 | "json",
20 | "jsonp"
21 | ]
22 | }
23 | },
24 | "threads": {
25 | "counts": {
26 | "required": [
27 | "threads"
28 | ],
29 | "method": "GET",
30 | "formats": [
31 | "json",
32 | "jsonp"
33 | ]
34 | },
35 | "listPosts": {
36 | "required": [],
37 | "method": "GET",
38 | "formats": [
39 | "json",
40 | "jsonp"
41 | ]
42 | },
43 | "sync": {
44 | "required": [
45 | "short_name"
46 | ],
47 | "method": "POST",
48 | "formats": [
49 | "json",
50 | "jsonp"
51 | ]
52 | }
53 | },
54 | "users": {
55 | "profile": {
56 | "required": [
57 | "user_id"
58 | ],
59 | "method": "GET",
60 | "formats": [
61 | "json",
62 | "jsonp"
63 | ]
64 | },
65 | "import": {
66 | "required": [
67 | "users"
68 | ],
69 | "method": "POST",
70 | "formats": [
71 | "json",
72 | "jsonp"
73 | ]
74 | }
75 | },
76 | "log": {
77 | "list": {
78 | "required": [],
79 | "method": "GET",
80 | "formats": [
81 | "json",
82 | "jsonp"
83 | ]
84 | }
85 | },
86 | "sites": {
87 | "listTopThreads": {
88 | "required": [],
89 | "method": "GET",
90 | "formats": [
91 | "json",
92 | "jsonp"
93 | ]
94 | },
95 | "join": {
96 | "required": [],
97 | "method": "POST",
98 | "formats": [
99 | "json",
100 | "jsonp"
101 | ]
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/duoshuo/templatetags/duoshuo_tags.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django import template
3 | from django.conf import settings
4 | from django.template import Library, Node
5 |
6 | DUOSHUO_SHORT_NAME = getattr(settings, "DUOSHUO_SHORT_NAME", None)
7 | DUOSHUO_SECRET = getattr(settings, "DUOSHUO_SECRET", None)
8 |
9 | register = Library()
10 |
11 | class DuoshuoCommentsNode(Node):
12 | def __init__(self, short_name=DUOSHUO_SHORT_NAME):
13 | self.short_name = short_name
14 |
15 | def render(self, context):
16 | code = '''
17 |
18 |
29 | ''' % self.short_name
30 | return code
31 |
32 | def duoshuo_comments(parser, token):
33 | short_name = token.contents.split()
34 | if DUOSHUO_SHORT_NAME:
35 | return DuoshuoCommentsNode(DUOSHUO_SHORT_NAME)
36 | elif len(short_name) == 2:
37 | return DuoshuoCommentsNode(short_name[1])
38 | else:
39 | raise template.TemplateSyntaxError, "duoshuo_comments tag takes SHORT_NAME as exactly one argument"
40 | duoshuo_comments = register.tag(duoshuo_comments)
41 |
42 | # 生成remote_auth,使用JWT后弃用
43 | # @register.filter
44 | # def remote_auth(value):
45 | # user = value
46 | # duoshuo_query = ds_remote_auth(user.id, user.username, user.email)
47 | # code = '''
48 | #
51 | # ''' % duoshuo_query
52 | # return code
53 | # remote_auth.is_safe = True
54 |
--------------------------------------------------------------------------------
/example/example/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for example project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/dev/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/dev/ref/settings/
9 | """
10 |
11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12 | import os
13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__))
14 |
15 |
16 | # Quick-start development settings - unsuitable for production
17 | # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
18 |
19 | # SECURITY WARNING: keep the secret key used in production secret!
20 | SECRET_KEY = 'jrlwrp^m9-9th14&!w@iovxfou#q%wu=@0stif-w)1em=uvdi='
21 |
22 | # SECURITY WARNING: don't run with debug turned on in production!
23 | DEBUG = True
24 |
25 | TEMPLATE_DEBUG = True
26 |
27 | ALLOWED_HOSTS = []
28 |
29 |
30 | # Application definition
31 |
32 | INSTALLED_APPS = (
33 | 'django.contrib.admin',
34 | 'django.contrib.auth',
35 | 'django.contrib.contenttypes',
36 | 'django.contrib.sessions',
37 | 'django.contrib.messages',
38 | 'django.contrib.staticfiles',
39 |
40 | 'accounts',
41 | 'duoshuo',
42 | )
43 |
44 | MIDDLEWARE_CLASSES = (
45 | 'django.contrib.sessions.middleware.SessionMiddleware',
46 | 'django.middleware.common.CommonMiddleware',
47 | 'django.middleware.csrf.CsrfViewMiddleware',
48 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
49 | # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
50 | 'django.contrib.messages.middleware.MessageMiddleware',
51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
52 | )
53 |
54 | ROOT_URLCONF = 'example.urls'
55 |
56 | WSGI_APPLICATION = 'example.wsgi.application'
57 |
58 |
59 | # Database
60 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases
61 |
62 | DATABASES = {
63 | 'default': {
64 | 'ENGINE': 'django.db.backends.sqlite3',
65 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
66 | }
67 | }
68 |
69 | # Internationalization
70 | # https://docs.djangoproject.com/en/dev/topics/i18n/
71 |
72 | LANGUAGE_CODE = 'en-us'
73 |
74 | TIME_ZONE = 'UTC'
75 |
76 | USE_I18N = True
77 |
78 | USE_L10N = True
79 |
80 | USE_TZ = True
81 |
82 |
83 | # Static files (CSS, JavaScript, Images)
84 | # https://docs.djangoproject.com/en/dev/howto/static-files/
85 |
86 | STATIC_URL = '/static/'
87 |
88 | # USER DEFINED
89 | STATICFILES_DIRS = (
90 | os.path.join(BASE_DIR, 'static'),
91 | )
92 |
93 | TEMPLATE_DIRS = [
94 | os.path.join(BASE_DIR, 'templates')
95 | ]
96 |
97 | LOGIN_REDIRECT_URL = '/'
98 |
99 |
100 | import sys
101 | sys.path.append(os.path.join(BASE_DIR, '..'))
102 |
103 | DUOSHUO_SECRET = 'dfd72c59c44cff2a7124390b0edfb0cc'
104 | DUOSHUO_SHORT_NAME = 'dmyzblog'
--------------------------------------------------------------------------------
/example/accounts/views.py:
--------------------------------------------------------------------------------
1 | # encoding: utf8
2 |
3 | from django.contrib.auth.models import User
4 | from django.contrib.auth import login, authenticate
5 | from django.http import HttpResponseRedirect
6 | from django.shortcuts import render
7 |
8 | from duoshuo import DuoshuoAPI
9 |
10 | from example.settings import DUOSHUO_SHORT_NAME, DUOSHUO_SECRET
11 | from .models import UserProfile
12 |
13 | import random
14 |
15 | def callback(request):
16 | code = request.GET.get('code')
17 | api = DuoshuoAPI(short_name=DUOSHUO_SHORT_NAME, secret=DUOSHUO_SECRET)
18 |
19 | response = api.get_token(code=code)
20 |
21 | if response.has_key('user_key'): #此多说账号在本站已经注册过了,直接登录
22 | user = User.objects.get(pk=int(response['user_key']))
23 | user.backend = 'django.contrib.auth.backends.ModelBackend'
24 | login(request, user)
25 | else: #此多说账号在本站未注册,添加一个用户
26 | response = api.users.profile(user_id=response['user_id'])['response']
27 | username = response['name']
28 | if User.objects.filter(username=username).count():
29 | username = username + str(random.randrange(1,9)) #如果多说账号用户名和本站用户名重复,就加上随机数字
30 |
31 | tmp_password = ''.join([random.choice('abcdefg%^*f') for i in range(8)]) #随机长度8字符做密码
32 | new_user = User.objects.create_user(username=username, email='user@example.com', password=tmp_password) #默认密码和邮箱,之后让用户修改
33 |
34 | userprofile = UserProfile.objects.get(user=new_user)
35 | userprofile.duoshuo_id = response['user_id'] #把返回的多说ID存到profile
36 | userprofile.avatar = response['avatar_url']
37 | userprofile.save()
38 |
39 | user = authenticate(username=username, password=tmp_password)
40 | login(request, user)
41 | context = {}
42 | return HttpResponseRedirect('/')
43 |
44 |
45 | def register(request):
46 | if request.method == 'POST':
47 | email = request.POST.get('email')
48 | username = request.POST.get('username')
49 | password = request.POST.get('password')
50 |
51 | if request.user.is_authenticated():
52 | user = request.user
53 | else:
54 | user = User.objects.create_user(username=username, email=email, password=password)
55 |
56 | api = DuoshuoAPI(short_name=DUOSHUO_SHORT_NAME, secret=DUOSHUO_SECRET)
57 |
58 | # 把本站用户导入多说,参看:http://dev.duoshuo.com/docs/51435552047fe92f490225de
59 | response = api.users.imports(data={
60 | 'users[0][user_key]' : user.id,
61 | 'users[0][name]': username,
62 | 'users[0][email]': email,
63 | })['response']
64 |
65 | user_profile = UserProfile.objects.get(user=user)
66 | user_profile.duoshuo_id = int(response[str(user.id)])
67 | user_profile.save()
68 |
69 | if not request.user.is_authenticated():
70 | login_user = authenticate(username=username, password=password)
71 | login(request, login_user)
72 |
73 | return HttpResponseRedirect('/')
74 |
--------------------------------------------------------------------------------
/duoshuo/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 | #!/usr/bin/env python
3 | #
4 | # Copyright 2012 Duoshuo
5 |
6 | import binascii
7 | import base64
8 | import hashlib
9 | import hmac
10 | import time
11 | import urllib
12 | import urllib2
13 | import urlparse
14 | import json
15 | import jwt
16 |
17 | try:
18 | from django.conf import settings
19 | except ValueError, ImportError:
20 | settings = {}
21 | settings.DUOSHUO_SECRET = None
22 | settings.DUOSHUO_SHORT_NAME = None
23 |
24 | """
25 | 实现Remote Auth后可以在评论框显示本地身份(已停用,由set_duoshuo_token代替)
26 | Use:
27 | views.py: sig = remote_auth(key=request.user.id, name=request.user.username, email=request.user.email)
28 | template/xxx.html: duoshuoQuery['remote_auth'] = {{ sig }}
29 | """
30 | def remote_auth(user_id, name, email, url=None, avatar=None, DUOSHUO_SECRET=None):
31 | data = json.dumps({
32 | 'key': user_id,
33 | 'name': name,
34 | 'email': email,
35 | 'url': url,
36 | 'avatar': avatar,
37 | })
38 | message = base64.b64encode(data)
39 | timestamp = int(time.time())
40 | sig = hmac.HMAC(settings.DUOSHUO_SECRET, '%s %s' % (message, timestamp), hashlib.sha1).hexdigest()
41 | duoshuo_query = '%s %s %s' % (message, sig, timestamp)
42 | return duoshuo_query
43 |
44 | """
45 | 在评论框显示本地身份
46 | Use:
47 | from utils import set_duoshuo_token
48 | response = HttpResponse()
49 | return set_duoshuo_token(request, response)
50 |
51 | """
52 | def set_duoshuo_token(request, response):
53 | if (request.user.id):
54 | token = {
55 | 'short_name': settings.DUOSHUO_SHORT_NAME,
56 | 'user_key': request.user.id,
57 | 'name': request.user.username,
58 | }
59 | signed_token = jwt.encode(token, settings.DUOSHUO_SECRET)
60 | response.set_cookie('duoshuo_token', signed_token)
61 | return response
62 |
63 | def sync_article(article):
64 | userprofile = request.user.get_profile()
65 | if userprofile.duoshuo_id:
66 | author_id = userprofile.duoshuo_id
67 | else:
68 | author_id = 0
69 |
70 | api_url = 'http://api.duoshuo.com/threads/sync.json'
71 | #TODO: get article url from urls.py
72 | url_hash = hashlib.md5(article.url).hexdigest()
73 | data = urllib.urlencode({
74 | 'short_name' : DUOSHUO_SHORT_NAME,
75 | 'thread_key' : article.id,
76 | 'url' : article.url,
77 | 'url_hash' : url_hash,
78 | 'author_key' : author_id
79 | })
80 |
81 | response = json.loads(urllib2.urlopen(api_url, data).read())['response']
82 | return response
83 |
84 |
85 | def get_url(api, redirect_uri=None):
86 | if not redirect_uri:
87 | raise ValueError('Missing required argument: redirect_uri')
88 | else:
89 | params = {'client_id': api.short_name, 'redirect_uri': redirect_uri, 'response_type': 'code'}
90 | return '%s://%s/oauth2/%s?%s' % (api.uri_schema, api.host, 'authorize', \
91 | urllib.urlencode(sorted(params.items())))
92 |
93 | def sync_comment(posts):
94 | api_url = 'http://56we.duoshuo.com/api/import/comments.json'
95 | data = urllib.urlencode({
96 | 'data' : posts,
97 | })
98 | response = json.loads(urllib2.urlopen(api_url, data).read())
--------------------------------------------------------------------------------
/duoshuo/__init__.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | #!/usr/bin/env python
3 | #
4 | # Copyright 2012 Duoshuo
5 | #
6 | __version__ = '0.1'
7 |
8 | import os
9 | import sys
10 | reload(sys)
11 | sys.setdefaultencoding('utf-8')
12 | import urllib
13 | import urllib2
14 | import warnings
15 | import urlparse
16 | import hashlib
17 | import httplib
18 |
19 | try:
20 | import json
21 | _parse_json = lambda s: json.loads(s)
22 | except ImportError:
23 | try:
24 | import simplejson
25 | _parse_json = lambda s: simplejson.loads(s)
26 | except ImportError:
27 | from django.utils import simplejson
28 | _parse_json = lambda s: simplejson.loads(s)
29 |
30 | try:
31 | import Cookie
32 | except ImportError:
33 | import https.cookies as cookie
34 |
35 | HOST = 'api.duoshuo.com'
36 | URI_SCHEMA = 'http'
37 | INTERFACES = _parse_json(open(os.path.join(os.path.dirname(__file__), 'interfaces.json'), 'r').read())
38 |
39 | try:
40 | import settings
41 | except ImportError:
42 | DUOSHUO_SHORT_NAME = None
43 | DUOSHUO_SECRET = None
44 | else:
45 | DUOSHUO_SHORT_NAME = getattr(settings, "DUOSHUO_SHORT_NAME", None)
46 | DUOSHUO_SECRET = getattr(settings, "DUOSHUO_SECRET", None)
47 |
48 |
49 | class APIError(Exception):
50 | def __init__(self, code, message):
51 | self.code = code
52 | self.message = message
53 |
54 | def __str__(self):
55 | return '%s: %s' % (self.code, self.message)
56 |
57 |
58 | class Resource(object):
59 | def __init__(self, api, interface=INTERFACES, node=None, tree=()):
60 | self.api = api
61 | self.node = node
62 | self.interface = interface
63 | if node:
64 | if node == 'imports': node = 'import'
65 | tree = tree + (node,)
66 | self.tree = tree
67 |
68 | def __getattr__(self, attr):
69 | if attr in getattr(self, '__dict__'):
70 | return getattr(self, attr)
71 | interface = self.interface
72 | if attr not in interface:
73 | interface[attr] = {}
74 | #raise APIError('03', 'Interface is not defined')
75 | return Resource(self.api, interface[attr], attr, self.tree)
76 |
77 | def __call__(self, **kwargs):
78 | return self._request(**kwargs)
79 |
80 | def _request(self, **kwargs):
81 |
82 | resource = self.interface
83 | for k in resource.get('required', []):
84 | if k not in [x.split(':')[0] for x in kwargs.keys()]:
85 | raise ValueError('Missing required argument: %s' % k)
86 |
87 | method = kwargs.pop('method', resource.get('method'))
88 |
89 | api = self.api
90 |
91 | format = kwargs.pop('format', api.format)
92 | path = '%s://%s/%s.%s' % (URI_SCHEMA, HOST, '/'.join(self.tree), format)
93 |
94 | if 'secret' not in kwargs and api.secret:
95 | kwargs['secret'] = api.secret
96 | if 'short_name' not in kwargs and api.short_name:
97 | kwargs['short_name'] = api.short_name
98 | if 'data' in kwargs and type(kwargs['data']) == dict:
99 | user_data = kwargs.pop('data')
100 | kwargs = dict(kwargs.items() + user_data.items())
101 |
102 | # We need to ensure this is a list so that
103 | # multiple values for a key work
104 | params = []
105 | for k, v in kwargs.iteritems():
106 | if isinstance(v, (list, tuple)):
107 | for val in v:
108 | params.append((k, val))
109 | else:
110 | params.append((k, v))
111 |
112 | if method == 'GET':
113 | path = '%s?%s' % (path, urllib.urlencode(params))
114 | response = urllib2.urlopen(path).read()
115 | else:
116 | data = urllib.urlencode(params)
117 | response = urllib2.urlopen(path, data).read()
118 |
119 | try:
120 | return _parse_json(response)
121 | except:
122 | return _parse_json('{"code": "500"}')
123 |
124 |
125 | class DuoshuoAPI(Resource):
126 | def __init__(self, short_name=DUOSHUO_SHORT_NAME, secret=DUOSHUO_SECRET, format='json', **kwargs):
127 | self.short_name = short_name
128 | self.secret = secret
129 | self.format = format
130 |
131 | self.uri_schema = URI_SCHEMA
132 | self.host = HOST
133 |
134 | if not secret or not short_name:
135 | warnings.warn('You should pass short_name and secret.')
136 | #self.version = version
137 | super(DuoshuoAPI, self).__init__(self)
138 |
139 | def _request(self, **kwargs):
140 | raise SyntaxError('You cannot call the API without a resource.')
141 |
142 | def _get_key(self):
143 | return self.secret
144 | key = property(_get_key)
145 |
146 | def get_token(self, code=None):
147 | if not code:
148 | raise APIError('01', 'Invalid request: code')
149 | #elif not redirect_uri:
150 | # raise APIError('01', 'Invalid request: redirect_uri')
151 | else:
152 | params = {
153 | 'code': code,
154 | 'client_id': self.short_name,
155 | 'client_secret': self.secret,
156 | }
157 | data = urllib.urlencode(params)
158 | url = '%s://%s/oauth2/access_token' % (URI_SCHEMA, HOST)
159 | request = urllib2.Request(url)
160 | response = urllib2.build_opener(urllib2.HTTPCookieProcessor()).open(request, data)
161 |
162 | return _parse_json(response.read())
163 |
164 | def setSecret(self, key):
165 | self.secret = key
166 |
167 | def setFormat(self, key):
168 | self.format = key
169 |
--------------------------------------------------------------------------------
/example/templates/home.html:
--------------------------------------------------------------------------------
1 | {% load duoshuo_tags %}
2 |
3 |
4 |
5 |
6 |
7 |
8 | Duoshuo Python SDK Example
9 |
10 |
26 |
27 |
28 |
33 | {% block content %}{% endblock %}
34 |
35 |
36 |
64 |
65 |
104 | {% duoshuo_comments %}
105 |
106 |
107 |
152 | {% block script %}{% endblock %}
153 |
154 |