diff --git a/.gitignore b/.gitignore index 3459640..8eafcd9 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ target/ # Custom files /configs.json +/static/media \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..82f75f9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +sudo: required +language: python +python: '3.6' +branches: + only: + - master +env: "-DJANGO=1.9" +services: +- mysql +before_install: +- openssl aes-256-cbc -K $encrypted_0fbea918c27f_key -iv $encrypted_0fbea918c27f_iv + -in configs.json.enc -out configs.json -d +- mysql -e 'create database wechat_ticket_db;' +install: +- pip install -r requirements.txt +script: +- python manage.py makemigrations +- python manage.py migrate +- python manage.py test diff --git a/README.md b/README.md index 6a44db5..0b8a17b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # WeChatTicket +[![Build Status](https://travis-ci.org/Altria-Ex/WeChatTicket.svg?branch=master)](https://travis-ci.org/Altria-Ex/WeChatTicket)

Ticket management system based on WeChat public platform. diff --git a/WeChatTicket/settings.py b/WeChatTicket/settings.py index 706e469..99c9c79 100644 --- a/WeChatTicket/settings.py +++ b/WeChatTicket/settings.py @@ -38,7 +38,7 @@ WECHAT_APPID = CONFIGS['WECHAT_APPID'] WECHAT_SECRET = CONFIGS['WECHAT_SECRET'] -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['*'] # Application definition @@ -139,13 +139,13 @@ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True -USE_TZ = True +USE_TZ = False # Static files (CSS, JavaScript, Images) @@ -154,7 +154,8 @@ STATIC_URL = '/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') - +MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media') +MEDIA_URL = 'static/media/' # Site and URL SITE_DOMAIN = CONFIGS['SITE_DOMAIN'].rstrip('/') diff --git a/adminpage/urls.py b/adminpage/urls.py index db56849..d23008a 100644 --- a/adminpage/urls.py +++ b/adminpage/urls.py @@ -1,9 +1,21 @@ # -*- coding: utf-8 -*- # +from django.conf.urls import url +from adminpage.views import * __author__ = "Epsirom" -urlpatterns = [] +urlpatterns = [ + url(r'^login/?$', Login.as_view()), + url(r'^logout/?$', Logout.as_view()), + url(r'^activity/list/?$', ActivityList.as_view()), + url(r'^activity/create/?$', ActivityCreate.as_view()), + url(r'^activity/delete/?$', ActivityDelete.as_view()), + url(r'^activity/detail/?$', ActivityDetail.as_view()), + url(r'^activity/menu/?$', ActivityMenu.as_view()), + url(r'^image/upload/?$', ImageUpload.as_view()), + url(r'^activity/checkin/?$', ActivityCheckin.as_view()), +] diff --git a/adminpage/views.py b/adminpage/views.py index 91ea44a..550a6ee 100644 --- a/adminpage/views.py +++ b/adminpage/views.py @@ -1,3 +1,272 @@ from django.shortcuts import render - +from codex.baseerror import * +from codex.baseview import APIView +from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login, logout +from wechat.models import Activity +from wechat.models import Ticket +from wechat.views import CustomWeChatView +from WeChatTicket.settings import SITE_DOMAIN +import time +from datetime import datetime, timedelta # Create your views here. + + +class Login(APIView): + + def get(self): + if not self.request.user.is_authenticated(): + raise ValidateError('User not logged in') + return 0 + + def post(self): + self.check_input('username', 'password') + username = self.input['username'] + password = self.input['password'] + user = authenticate(username=username, password=password) + + if not username: + raise ValidateError('Username is empty!') + if not password: + raise ValidateError('Password is empty!') + if not User.objects.filter(username=username): + raise ValidateError('Username does not exist!') + if not user: + raise ValidateError('Wrong password!') + + if user.is_active: + login(self.request, user) + return 0 + + +class Logout(APIView): + + def post(self): + if self.request.user.is_authenticated(): + logout(self.request) + else: + raise ValidateError('Logout failed!User has not logged in.') + + +class ActivityList(APIView): + + def get(self): + if not self.request.user.is_authenticated(): + raise ValidateError('User not logged in!') + + activityList = Activity.objects.all() + data = [] + for item in activityList: + if item.status >= 0: + temp = { + 'id': item.id, + 'name': item.name, + 'description': item.description, + 'startTime': int(time.mktime(item.start_time.timetuple())), + 'endTime': int(time.mktime(item.end_time.timetuple())), + 'place': item.place, + 'bookStart': int(time.mktime(item.book_start.timetuple())), + 'bookEnd': int(time.mktime(item.book_end.timetuple())), + 'currentTime': int(time.time()), + 'status': item.status, + } + data.append(temp) + else: + continue + return data + + +class ActivityDelete(APIView): + + def post(self): + self.check_input('id') + activity = Activity.get_by_id(self.input['id']) + activity.delete() + + +class ActivityCreate(APIView): + def post(self): + self.check_input('name', 'key', 'place', 'description', 'picUrl', 'startTime', 'endTime', 'bookStart', 'bookEnd', 'totalTickets', 'status') + if not self.request.user.is_authenticated(): + raise ValidateError('User not logged in!') + new_activity = Activity( + name = self.input['name'], + key = self.input['key'], + place = self.input['place'], + description = self.input['description'], + pic_url = self.input['picUrl'], + start_time = datetime.strptime(self.input['startTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8), + end_time = datetime.strptime(self.input['endTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8), + book_start = datetime.strptime(self.input['bookStart'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8), + book_end = datetime.strptime(self.input['bookEnd'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8), + remain_tickets = self.input['totalTickets'], + total_tickets = self.input['totalTickets'], + status = self.input['status'] + ) + new_activity.save() + return new_activity.id + + +class ActivityDetail(APIView): + + def get(self): + self.check_input('id') + if not self.request.user.is_authenticated(): + raise ValidateError('User has not logged in.') + activity = Activity.get_by_id(self.input['id']) + usedTickets = 0 + for ticket in activity.ticket_set.all(): + if ticket.status == Ticket.STATUS_USED: + usedTickets += 1 + data = { + 'name': activity.name, + 'key': activity.key, + 'description': activity.description, + 'startTime': int(time.mktime(activity.start_time.timetuple())), + 'endTime': int(time.mktime(activity.end_time.timetuple())), + 'place': activity.place, + 'bookStart': int(time.mktime(activity.book_start.timetuple())), + 'bookEnd': int(time.mktime(activity.book_end.timetuple())), + 'totalTickets': activity.total_tickets, + 'picUrl': activity.pic_url, + 'bookedTickets': activity.total_tickets - activity.remain_tickets, + 'usedTickets': usedTickets, + 'currentTime': int(time.time()), + 'status': activity.status + } + return data + + def post(self): + self.check_input('id', 'name', 'place', 'description', 'picUrl', + 'startTime', 'endTime', 'bookStart', 'bookEnd', + 'totalTickets', 'status') + if not self.request.user.is_authenticated(): + raise ValidateError('User has not logged in.') + activity = Activity.get_by_id(self.input['id']) + if activity.status == Activity.STATUS_PUBLISHED: + if activity.name != self.input['name']: + raise LogicError('Cannot modify the name of a published activity ') + if activity.place != self.input['place']: + raise LogicError('Cannot modify the place of a published activity') + if activity.book_start != (datetime.strptime(self.input['bookStart'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)): + raise LogicError('Cannot modify the bookStart time of a published activity') + if self.input['status'] != 1: + raise LogicError('you cannot stage published activity') + + if int(time.mktime(activity.end_time.timetuple())) < int(time.time()): + if activity.start_time != (datetime.strptime(self.input['startTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)): + raise LogicError('Cannot modify startTime of a activity ended') + if activity.end_time != (datetime.strptime(self.input['endTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)): + raise LogicError('Cannot modify endTime of a activity ended') + + if int(time.mktime(activity.start_time.timetuple())) < int(time.time()): + if activity.book_end != (datetime.strptime(self.input['bookEnd'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)): + raise LogicError('Cannot modify bookEnd time of a activity started') + + if int(time.mktime(activity.book_start.timetuple())) < int(time.time()): + if activity.total_tickets != self.input['totalTickets']: + raise LogicError('Cannot modify totalTickets after book_start') + + activity.name = self.input['name'] + activity.place = self.input['place'] + activity.description = self.input['description'] + activity.pic_url = self.input['picUrl'] + activity.start_time = (datetime.strptime(self.input['startTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)) + activity.end_time = (datetime.strptime(self.input['endTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)) + activity.book_start = (datetime.strptime(self.input['bookStart'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)) + activity.book_end = (datetime.strptime(self.input['bookEnd'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)) + activity.total_tickets = self.input['totalTickets'] + activity.status = self.input['status'] + + activity.save() + + +class ActivityMenu(APIView): + + def get(self): + if not self.request.user.is_authenticated(): + raise ValidateError('User has not logged in.') + activityListAll = Activity.objects.all() + wechat_menu = CustomWeChatView.lib.get_wechat_menu() + if len(wechat_menu) >= 2: + actiListInMenu = wechat_menu[1]['sub_button'] + else: + actiListInMenu = [] + data = [] + for acti in activityListAll: + if acti.status == Activity.STATUS_DELETED: + continue + temp = { + 'id': acti.id, + 'name': acti.name, + 'menuIndex': 0 + } + i = 0 + while i < len(actiListInMenu): + if actiListInMenu[i]['name'] == acti.name: + temp['menuIndex'] = i+1 + break + i += 1 + + data.append(temp) + return data + + def post(self): + if not self.request.user.is_authenticated(): + raise ValidateError('User has not logged in.') + activityList = [] + for activity_id in self.input: + activity = Activity.get_by_id(activity_id) + activity.status = Activity.STATUS_PUBLISHED + activity.save() + activityList.append(activity) + CustomWeChatView.update_menu(activityList) + + +class ImageUpload(APIView): + + def post(self): + self.check_input('image') + if not self.request.user.is_authenticated(): + raise ValidateError('User has not logged in.') + image = self.input['image'][0] + try: + with open('static/media/img/' + image.name, 'wb') as img_file: + for i in image.chunks(): + img_file.write(i) + return SITE_DOMAIN + '/media/img/' + image.name + except: + raise ValidateError('Fail to upload image') + + +class ActivityCheckin(APIView): + def post(self): + if not self.request.user.is_authenticated(): + raise ValidateError("User not logged in!") + self.check_input('actId') + try: + activity = Activity.objects.get(id=self.input['actId']) + except: + raise LogicError('actID error!Activity not found') + try: + if "ticket" in self.input: + ticket = Ticket.objects.get(activity=activity,unique_id=self.input['ticket']) + elif 'studentId' in self.input.keys(): + ticket = Ticket.objects.get(activity=activity,student_id=self.input['studentId']) + except: + raise LogicError("no ticket!") + + if ticket.status == Ticket.STATUS_CANCELLED: + raise LogicError(" ticket canceled!") + elif ticket.status == Ticket.STATUS_USED: + raise LogicError("ticket used!") + elif ticket.status == Ticket.STATUS_VALID: + data = { + 'ticket': ticket.unique_id, + 'studentId': ticket.student_id + } + ticket.status = Ticket.STATUS_USED + ticket.save() + return data + else: + raise ValidateError("fail to checkin!") diff --git a/configs.example.json b/configs.example.json deleted file mode 100644 index 2050d5e..0000000 --- a/configs.example.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "SECRET_KEY": "ThisIsARandomStringWithLength=50!-----------------", - "DEBUG": true, - "IGNORE_WECHAT_SIGNATURE": false, - "WECHAT_TOKEN": "ThisIsAWeChatTokenWhichCanBeARandomString", - "WECHAT_APPID": "PleaseCopyFromWebsite", - "WECHAT_SECRET": "PleaseCopyFromWebsite", - "DB_NAME": "wechat_ticket", - "DB_USER": "root", - "DB_PASS": "", - "DB_HOST": "127.0.0.1", - "DB_PORT": "3306", - "SITE_DOMAIN": "http://your.domain" -} \ No newline at end of file diff --git a/configs.json.enc b/configs.json.enc new file mode 100644 index 0000000..25951c7 Binary files /dev/null and b/configs.json.enc differ diff --git a/templates/news.xml b/templates/news.xml index 7732a52..ed7d2d5 100644 --- a/templates/news.xml +++ b/templates/news.xml @@ -8,7 +8,7 @@ <![CDATA[{{ Article.Title }}]]> - + {% endfor %} diff --git a/userpage/urls.py b/userpage/urls.py index 2ff76f9..26b11fe 100644 --- a/userpage/urls.py +++ b/userpage/urls.py @@ -10,4 +10,6 @@ urlpatterns = [ url(r'^user/bind/?$', UserBind.as_view()), + url(r'^activity/detail/?$', ActivityDetail.as_view()), + url(r'^ticket/detail/?$', TicketDetail.as_view()), ] diff --git a/userpage/views.py b/userpage/views.py index 3e5c5dc..606d53b 100644 --- a/userpage/views.py +++ b/userpage/views.py @@ -2,6 +2,9 @@ from codex.baseview import APIView from wechat.models import User +from wechat.models import Activity +from wechat.models import Ticket +import time class UserBind(APIView): @@ -11,11 +14,16 @@ def validate_user(self): input: self.input['student_id'] and self.input['password'] raise: ValidateError when validating failed """ - raise NotImplementedError('You should implement UserBind.validate_user method') + if len(self.input['student_id']) > 32: + raise ValidateError('Invalid student id') def get(self): self.check_input('openid') - return User.get_by_openid(self.input['openid']).student_id + try: + studentId = User.get_by_openid(self.input['openid']).student_id + except: + studentId = '' + return studentId def post(self): self.check_input('openid', 'student_id', 'password') @@ -23,3 +31,45 @@ def post(self): self.validate_user() user.student_id = self.input['student_id'] user.save() + + +class ActivityDetail(APIView): + + def get(self): + self.check_input('id') + activity = Activity.get_by_id(self.input['id']) + if activity.status != Activity.STATUS_PUBLISHED: + raise LogicError("Activity hasn't been published") + activity_detail = { + 'name': activity.name, + 'key': activity.key, + 'description': activity.description, + 'startTime': int(time.mktime(activity.start_time.timetuple())), + 'endTime': int(time.mktime(activity.end_time.timetuple())), + 'place': activity.place, + 'bookStart': int(time.mktime(activity.book_start.timetuple())), + 'bookEnd': int(time.mktime(activity.book_end.timetuple())), + 'totalTickets': activity.total_tickets, + 'picUrl': activity.pic_url, + 'remainTickets': activity.remain_tickets, + 'currentTime': int(time.time()) + } + return activity_detail + + +class TicketDetail(APIView): + + def get(self): + self.check_input('openid', 'ticket') + ticket = Ticket.get_by_uniqueid(self.input['ticket']) + ticket_detail = { + 'activityName': ticket.activity.name, + 'place': ticket.activity.place, + 'activityKey': ticket.activity.key, + 'uniqueId': ticket.unique_id, + 'startTime': int(time.mktime(ticket.activity.start_time.timetuple())), + 'endTime': int(time.mktime(ticket.activity.end_time.timetuple())), + 'currentTime': int(time.time()), + 'status': ticket.status + } + return ticket_detail diff --git a/wechat/handlers.py b/wechat/handlers.py index 4211d91..51eb738 100644 --- a/wechat/handlers.py +++ b/wechat/handlers.py @@ -1,10 +1,20 @@ # -*- coding: utf-8 -*- # from wechat.wrapper import WeChatHandler +from wechat.models import Activity, Ticket +from WeChatTicket.settings import WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET +from wechat.wrapper import WeChatLib +import threading +from datetime import datetime +import time +import random +import string + __author__ = "Epsirom" +remainTicketsLock = threading.Lock() class ErrorHandler(WeChatHandler): @@ -65,3 +75,170 @@ def check(self): def handle(self): return self.reply_text(self.get_message('book_empty')) + + +class BookWhatHandler(WeChatHandler): + + def check(self): + return self.is_text('抢啥') or self.is_event_click(self.view.event_keys['book_what']) + + def handle(self): + lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET) + wechat_menu = lib.get_wechat_menu() + if len(wechat_menu) < 2: + return self.reply_text('暂时没有可抢票的活动') + elif len(wechat_menu[1]['sub_button']) == 0: + return self.reply_text('暂时没有可抢票的活动') + else: + acti_list = wechat_menu[1]['sub_button'] + news_list = [] + for acti in acti_list: + try: + activity = Activity.objects.get(name=acti['name']) + news_list.append({ + 'Title': activity.name, + 'Description': activity.description, + 'PicUrl': activity.pic_url, + 'Url': self.url_activity_detail(activity.id) + }) + except: + return self.reply_text('数据库内出现了重名活动,请联系管理员-_-!') + + return self.reply_news(news_list) + + +class BookTicketHandler(WeChatHandler): + + def check(self): + return self.is_text_command('抢票') or self.is_event_click(self.view.event_keys['book_header']) + + def handle(self): + if self.is_msg_type('event'): + """ + lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET) + acti_list = lib.get_wechat_menu()[1]['sub_button'] + for acti in acti_list: + if acti['key'] == self.input['EventKey']: + activity_name = acti['name'] + break + try: + activity = Activity.objects.get(name=activity_name) + """ + activity_id = int(self.input['EventKey'][len(self.view.event_keys['book_header']):]) + try: + activity = Activity.objects.get(id=activity_id) + except: + return self.reply_text('该活动不存在') + elif self.is_msg_type('text'): + try: + activity_name = self.input['Content'].split()[1] + except: + return self.reply_text('格式错误0_0 抢票请输入"抢票 活动名"') + try: + activity = Activity.objects.get(name=activity_name) + except: + return self.reply_text('活动名错误?_? 该活动不存在或有重名活动') + + if not self.user.student_id: + return self.reply_text('你还没绑定学号哟') + + if len(Ticket.objects.filter(activity=activity, student_id=self.user.student_id)) > 0: + return self.reply_text('一个人只能抢一张票哦') + + currentTime = int(time.time()) + if int(time.mktime(activity.book_start.timetuple())) > currentTime: + return self.reply_text('抢票尚未开始') + elif int(time.mktime(activity.book_end.timetuple())) < currentTime: + return self.reply_text('抢票已结束') + + remainTicketsLock.acquire() + if activity.remain_tickets <= 0: + remainTicketsLock.release() + return self.reply_text('来晚啦T_T 票都被抢光啦') + else: + activity.remain_tickets -= 1 + activity.save() + remainTicketsLock.release() + ticket = Ticket( + student_id=self.user.student_id, + unique_id=self.user.student_id + ''.join( + random.choice(string.digits + string.ascii_letters) for x in range(32)), + activity=activity, + status=Ticket.STATUS_VALID, + ) + ticket.save() + return self.reply_text('恭喜^_^抢票成功') + + +class GetTicketHandler(WeChatHandler): + + def check(self): + return self.is_text_command('取票') or self.is_event_click(self.view.event_keys['get_ticket']) + + def handle(self): + if not self.user.student_id: + return self.reply_text('你还没有绑定学号') + + ticket_list = [] + news_list = [] + if self.is_msg_type('event'): + ticket_list = list(Ticket.objects.filter(student_id=self.user.student_id)) + elif self.is_msg_type('text'): + try: + activity_name = self.input['Content'].split()[1] + except: + return self.reply_text('格式错误0_0 取票请输入"取票 活动名"') + try: + activity = Activity.objects.get(name=activity_name) + except: + return self.reply_text('活动名错误?_? 该活动不存在或有重名活动') + + ticket_list = list(Ticket.objects.filter(activity=activity, student_id=self.user.student_id)) + + if len(ticket_list) == 0: + return self.reply_text('你还没有抢到票哦') + + for ticket in ticket_list: + news_list.append({ + 'Title': '电子票:' + ticket.activity.name, + 'Description': ticket.student_id, + 'PicUrl': ticket.activity.pic_url, + 'Url': self.url_ticket_detail(self.user.open_id, ticket.unique_id) + }) + + return self.reply_news(news_list) + + +class CancelTicketHandler(WeChatHandler): + + def check(self): + return self.is_text_command('退票') + + def handle(self): + if not self.user.student_id: + return self.reply_text('你还没有绑定学号哦') + + try: + activity_name = self.input['Content'].split()[1] + except: + return self.reply_text('格式错误0_0 退票请输入"退票 活动名"') + try: + activity = Activity.objects.get(name=activity_name) + except: + return self.reply_text('活动名错误?_? 该活动不存在或有重名活动') + + try: + ticket = Ticket.objects.get(activity=activity, student_id=self.user.student_id) + except: + return self.reply_text('你就没抢到,退啥退→_→') + if int(time.mktime(ticket.activity.book_end.timetuple())) < int(time.time()): + return self.reply_text('抢票结束后就不能再退了哦!') + + ticket.status = Ticket.STATUS_CANCELLED + ticket.save() + remainTicketsLock.acquire() + ticket.activity.remain_tickets += 1 + ticket.activity.save() + remainTicketsLock.release() + + return self.reply_text('退票成功') diff --git a/wechat/models.py b/wechat/models.py index 58dc0ee..31e6ee9 100644 --- a/wechat/models.py +++ b/wechat/models.py @@ -33,6 +33,13 @@ class Activity(models.Model): STATUS_SAVED = 0 STATUS_PUBLISHED = 1 + @classmethod + def get_by_id(cls, activity_id): + try: + return cls.objects.get(id=activity_id) + except cls.DoesNotExist: + raise LogicError('Activity not found') + class Ticket(models.Model): student_id = models.CharField(max_length=32, db_index=True) @@ -43,3 +50,11 @@ class Ticket(models.Model): STATUS_CANCELLED = 0 STATUS_VALID = 1 STATUS_USED = 2 + + @classmethod + def get_by_uniqueid(cls, uniqueid): + try: + return cls.objects.get(unique_id=uniqueid) + except cls.DoesNotExist: + raise LogicError('Ticket not found') + diff --git a/wechat/views.py b/wechat/views.py index c0626fc..05c382c 100644 --- a/wechat/views.py +++ b/wechat/views.py @@ -11,7 +11,14 @@ class CustomWeChatView(WeChatView): lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET) handlers = [ - HelpOrSubscribeHandler, UnbindOrUnsubscribeHandler, BindAccountHandler, BookEmptyHandler, + HelpOrSubscribeHandler, + UnbindOrUnsubscribeHandler, + BindAccountHandler, + BookEmptyHandler, + BookWhatHandler, + BookTicketHandler, + GetTicketHandler, + CancelTicketHandler, ] error_message_handler = ErrorHandler default_handler = DefaultHandler diff --git a/wechat/wrapper.py b/wechat/wrapper.py index 136ff04..0ba23e8 100644 --- a/wechat/wrapper.py +++ b/wechat/wrapper.py @@ -75,7 +75,9 @@ def is_text(self, *texts): return self.is_msg_type('text') and (self.input['Content'].lower() in texts) def is_event_click(self, *event_keys): - return self.is_msg_type('event') and (self.input['Event'] == 'CLICK') and (self.input['EventKey'] in event_keys) + return self.is_msg_type('event') and (self.input['Event'] == 'CLICK') and\ + ((self.input['EventKey'] in event_keys) or (self.input['EventKey'].startswith('BOOKING_ACTIVITY_') and + 'BOOKING_ACTIVITY_' in event_keys)) def is_event(self, *events): return self.is_msg_type('event') and (self.input['Event'] in events) @@ -89,6 +91,12 @@ def url_help(self): def url_bind(self): return settings.get_url('u/bind', {'openid': self.user.open_id}) + def url_activity_detail(self, activity_id): + return settings.get_url('u/activity', {'id': activity_id}) + + def url_ticket_detail(self, open_id, unique_id): + return settings.get_url('u/ticket', {'ticket': unique_id, 'openid': open_id}) + class WeChatEmptyHandler(WeChatHandler): @@ -114,7 +122,7 @@ class WeChatLib(object): logger = logging.getLogger('wechatlib') access_token = '' - access_token_expire = datetime.datetime.fromtimestamp(0) + access_token_expire = datetime.datetime.fromtimestamp(1429417200.0) token = WECHAT_TOKEN appid = WECHAT_APPID secret = WECHAT_SECRET