├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── manage.py ├── requirements.txt ├── runtime.txt ├── screenshots ├── air.png ├── bus.png ├── bus2.png ├── dianping.png ├── direct.png ├── direct2.png ├── events.png ├── help.png ├── issue.png ├── map.png ├── map2.png ├── movie.png ├── pycoders.png ├── pythonweekly.png ├── simsim.png ├── toutiao.png ├── travel.png ├── travel2.png ├── v2ex.png └── weather.png ├── slack_bot ├── __init__.py ├── app.py ├── ext.py ├── local_settings.py.example ├── plugins │ ├── __init__.py │ ├── airpollution.py │ ├── baidumap.py │ ├── bj_bus.py │ ├── consts.py │ ├── data │ │ ├── baidu_tag.pkl │ │ ├── bjbus_lines.pkl │ │ └── cityid │ ├── dianping.py │ ├── earthquake.py │ ├── events.py │ ├── github_issue.py │ ├── help.py │ ├── movie.py │ ├── orz.py │ ├── pycoders.py │ ├── pythonweekly.py │ ├── qiubai.py │ ├── simsimi.py │ ├── toutiao.py │ ├── travel.py │ ├── utils.py │ ├── v2ex.py │ ├── weather.py │ └── wikipedia.py ├── settings.py └── utils.py └── wsgi.py /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | slack_bot/local_settings.py 59 | *~ 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 小明 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn wsgi:app --timeout 240 --log-file - 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slack\_bot 2 | 立志成为一个可被调戏且有用的Bot 3 | 4 | ### 介绍 5 | 6 | 很多人听过甚至曾经用过[小黄鸡](https://github.com/wong2/xiaohuangji-new). 你可以把本bot理解为`增加了很多功能`, `只保留小黄鸡交流的基本逻辑`, 7 | `用于slack`channel里面的`小黄鸡`. 它主要借用slack的[Outgoing Webhooks](https://api.slack.com/outgoing-webhooks), 8 | 也使用了[私聊](https://api.slack.com/methods/chat.postMessage)和[upload](https://api.slack.com/methods/files.upload). 9 | 10 | 项目使用flask, 目前本项目是一个heroku应用. 地址是 https://slack-bot2.herokuapp.com/slack_callback 但是你也可以直接裸跑 11 | 12 | ```python 13 | gunicorn wsgi:app localhost:5000 --timeout 240 --log-file - 14 | ``` 15 | 16 | ### 我们使用slack的特性 17 | 18 | 1. 可以把消息私聊发给自己 19 | 2. 可以使用带图的方式接收结果(支持canvas, 以下会看到例图) 20 | 21 | ### 模块用途 22 | 23 | 继承自小黄鸡的模块们, 但是都做了对应的修改: 24 | 25 | 1. 空气插件 26 | 2. wikipedia 27 | 3. 地震了 28 | 4. 糗百 29 | 5. orz 30 | 31 | 新加的模块们: 32 | 33 | #### 地图插件 - 你可以输入我想从哪去哪, 会给你个路线(步行/开车/公交) 34 | ![](screenshots/map.png) 35 | ![](screenshots/map2.png) 36 | 37 | #### 天气插件 - 使用了百度api, 获得城市的天气情况 38 | ![](screenshots/weather.png) 39 | 40 | #### 北京公交插件 - 可以查询北京公交线路, 获取实时的到某站的信息(有些线路站点不准) 41 | ![](screenshots/bus.png) 42 | ![](screenshots/bus2.png) 43 | 44 | #### 美食插件 - 使用大众点评网api, 获取附近餐饮信息, 评分, 电话地址,距离等等 45 | ![](screenshots/dianping.png) 46 | 47 | #### 技术活动插件 - 从segmentfault/csdn/车库咖啡/活动行获得最近的活动列表 48 | ![](screenshots/events.png) 49 | 50 | #### Github\_issue插件 - 获得你个人或者组织下未处理的Pull requests列表 51 | ![](screenshots/issue.png) 52 | 53 | #### 电影信息插件 - 列出最近上映和即将上映的电影信息 54 | ![](screenshots/movie.png) 55 | 56 | #### pycoders插件 - 获得订阅数据 57 | ![](screenshots/pycoders.png) 58 | 59 | #### pythonweekly插件 - 获得订阅数据 60 | ![](screenshots/pythonweekly.png) 61 | 62 | #### 头条插件 - 获得今日头条新闻 63 | ![](screenshots/toutiao.png) 64 | 65 | #### travel插件 - 旅游推荐/景点介绍 66 | ![](screenshots/travel.png) 67 | ![](screenshots/travel2.png) 68 | 69 | #### v2ex feed插件 - 获得一些节点的最新feed 70 | ![](screenshots/v2ex.png) 71 | 72 | #### help插件 - 列出所有插件的帮助信息 73 | ![](screenshots/help.png) 74 | 75 | #### 最主要的是小黄鸡(simsim) 76 | ![](screenshots/simsim.png) 77 | 78 | #### 也可以直接发到个人的slackbot channel里: 79 | 80 | ![](screenshots/direct.png) 81 | ![](screenshots/direct2.png) 82 | 83 | ### 提交pull request 84 | 85 | 我们欢迎你把你想要的idea实现出来, 或者在看过本项目代码后用于个人以及私有公司的插件遇到bug, 发现瓶颈等时候 86 | 给我们PR 87 | 88 | ### 插件编写 89 | 90 | 插件放在plugins目录下,每个插件是一个python文件,提供两个接口,`test`和`handle`,格式如下: 91 | 92 | ```python 93 | def test(data): 94 | // your code 95 | ``` 96 | 97 | `test`方法返回`True`或`False`,说明是否要用该插件处理这一条请求 98 | 99 | 100 | ```python 101 | def handle(data, **kwargs): 102 | // your code 103 | ``` 104 | 105 | `handle`方法则实际处理请求,它需要返回一个utf-8编码的字符串或者一个tuple,用来作为小黄鸡对这条请求的答复: 106 | 107 | 1. 返回只包含一个字符串表示不支持带图模式 108 | 2. 返回一个(字符串, 一个attachment的列表), 每个attachment可以使用plugins/utils.py的 `gen_attachment` 生成 109 | 110 | `data` 是一个dict, 它是slack在回调的时候的请求, 一般情况下你只需要关注`data['message']`这个`消息全文`字段. 111 | 112 | 注意事项: 113 | 114 | 1. 请把新的插件的名字也加在plugins/\_\_init\_\_.py的 `__all__` 里面 115 | 2. 如果其他插件都不符合, 会默认使用小黄鸡接口 116 | 3. 本地调试可以使用这样的http请求: 117 | 118 | ``` 119 | http -f POST http://localhost:5000/slack_callback token=jLGMzrZn3P1lS2sD848KpPuN text='颐和园景点介绍' team_id=T0001 team_domain=example channel_id=C2147483705 channel_name=test timestamp=1355517523.000005 user_id=U2147483697 user_name=Steve trigger_word='' 120 | ``` 121 | 122 | 4. 你也可以这样调试: 123 | 124 | ``` 125 | $python manage.py send 今天天气很不错 126 | !是啊是啊 好想出去玩 127 | ``` 128 | 129 | ### TODO 130 | 131 | - [ ] 输入xx有什么可玩的 132 | - [ ] 今天中午吃什么 133 | 134 | - [x] 输入我想从哪去哪, 调用地图api.给个路线 135 | - [x] 附近有什么可吃的(大众点评) 136 | - [x] 最近都有神马技术聚会 137 | - [x] Pycoders Weekly & Python Weekly 汇总 138 | - [x] 今日头条 139 | - [x] 公交车离我还有多久/远 140 | - [x] v2ex feed 141 | - [x] github上的issue列表 142 | - [x] 最近(将)上映的电影(豆瓣电影) 143 | - [x] 旅游路线推荐/景点介绍 144 | 145 | 146 | ### Pending 147 | 148 | 1. 打车 149 | 2. 叫快递 150 | 3. 叫外卖 151 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | from pprint import pprint 4 | 5 | from flask import current_app 6 | from flask_script import Manager, Server 7 | 8 | from slack_bot.app import create_app 9 | 10 | manager = Manager(create_app) 11 | manager.add_option('-c', '--config', dest='config', required=False) 12 | manager.add_command( 13 | 'runserver', Server(use_debugger=True, use_reloader=True, host='0.0.0.0') 14 | ) 15 | 16 | 17 | @manager.option('text') 18 | def send(text): 19 | """Send text to slack callback url""" 20 | data = current_app.config['TEST_DATA'] 21 | uri = current_app.config['SLACK_CALLBACK'] 22 | client = current_app.test_client() 23 | 24 | data['text'] = text 25 | rv = client.post(uri, data=data) 26 | if rv.status_code == 200: 27 | body = rv.data 28 | if not body: 29 | print('Response body is empty!') 30 | return 31 | 32 | obj = json.loads(body) 33 | if not obj.get('attachments'): 34 | obj = obj['text'] 35 | print(obj) 36 | else: 37 | pprint(obj) 38 | else: 39 | print('Error!\nstatus code: %s\nbody: %s' % (rv.status_code, rv.data)) 40 | 41 | if __name__ == '__main__': 42 | manager.run() 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | requests 3 | lxml 4 | pytz 5 | gunicorn 6 | Flask-Cache 7 | flask-redis 8 | Flask-Script 9 | jieba 10 | pypinyin 11 | beautifulsoup4 12 | flask-slackbot==0.2.1 13 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-2.7.9 2 | -------------------------------------------------------------------------------- /screenshots/air.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/air.png -------------------------------------------------------------------------------- /screenshots/bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/bus.png -------------------------------------------------------------------------------- /screenshots/bus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/bus2.png -------------------------------------------------------------------------------- /screenshots/dianping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/dianping.png -------------------------------------------------------------------------------- /screenshots/direct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/direct.png -------------------------------------------------------------------------------- /screenshots/direct2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/direct2.png -------------------------------------------------------------------------------- /screenshots/events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/events.png -------------------------------------------------------------------------------- /screenshots/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/help.png -------------------------------------------------------------------------------- /screenshots/issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/issue.png -------------------------------------------------------------------------------- /screenshots/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/map.png -------------------------------------------------------------------------------- /screenshots/map2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/map2.png -------------------------------------------------------------------------------- /screenshots/movie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/movie.png -------------------------------------------------------------------------------- /screenshots/pycoders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/pycoders.png -------------------------------------------------------------------------------- /screenshots/pythonweekly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/pythonweekly.png -------------------------------------------------------------------------------- /screenshots/simsim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/simsim.png -------------------------------------------------------------------------------- /screenshots/toutiao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/toutiao.png -------------------------------------------------------------------------------- /screenshots/travel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/travel.png -------------------------------------------------------------------------------- /screenshots/travel2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/travel2.png -------------------------------------------------------------------------------- /screenshots/v2ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/v2ex.png -------------------------------------------------------------------------------- /screenshots/weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/screenshots/weather.png -------------------------------------------------------------------------------- /slack_bot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cn/slack_bot/713f8431eedc352c729968dfb39a1600b27cd9ab/slack_bot/__init__.py -------------------------------------------------------------------------------- /slack_bot/app.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | 5 | from flask import Flask 6 | 7 | from flask_slackbot import SlackBot 8 | 9 | import settings 10 | import plugins 11 | from ext import redis_store, cache 12 | from utils import timeout 13 | from plugins.utils import convert2str, replaced 14 | 15 | 16 | plugin_modules = [] 17 | for plugin_name in plugins.__all__: 18 | __import__('slack_bot.plugins.%s' % plugin_name) 19 | plugin_modules.append(getattr(plugins, plugin_name)) 20 | 21 | 22 | def create_app(config=None): 23 | app = Flask(__name__) 24 | app.config.from_object(settings) 25 | 26 | if isinstance(config, dict): 27 | app.config.update(config) 28 | elif config: 29 | app.config.from_pyfile(os.path.realpath(config)) 30 | 31 | redis_store.init_app(app) 32 | cache.init_app(app) 33 | app.cache = cache 34 | app.plugin_modules = plugin_modules 35 | 36 | slackbot = SlackBot(app) 37 | slackbot.set_handler(callback) 38 | slackbot.filter_outgoing(_filter) 39 | 40 | return app 41 | 42 | 43 | def callback(kwargs): 44 | s = convert2str(kwargs['text']) 45 | trigger_word = convert2str(kwargs['trigger_word']) 46 | # remove trigger_words 47 | if trigger_word is not None: 48 | s = replaced(s, trigger_word.split(',')) 49 | # remove metion block 50 | s = re.sub(r'(@.*?)\W', '', s) 51 | private = any([word in s for word in ['private', '私聊']]) 52 | attachmented = any([word in s for word in ['带图', '附件']]) 53 | data = { 54 | 'message': replaced(s, ['private', '私聊', '带图', '附件']).strip() 55 | } 56 | 57 | if not data['message']: 58 | return {'text': ''} 59 | 60 | for plugin_module in plugin_modules: 61 | if plugin_module.test(data): 62 | ret = plugin_module.handle(data) 63 | if not isinstance(ret, tuple): 64 | text = ret 65 | attaches = None 66 | else: 67 | text, attaches = ret 68 | if trigger_word is None: 69 | text = '!' + text 70 | if attachmented and attaches: 71 | return {'text': ' ', 'private': private, 72 | 'attachments': attaches} 73 | return {'text': text, 'private': private} 74 | 75 | return {'text': '!呵呵'} 76 | 77 | 78 | def _filter(line): 79 | return line.startswith('!') 80 | 81 | 82 | if __name__ == '__main__': 83 | app = create_app() 84 | app.debug = True 85 | app.run() 86 | -------------------------------------------------------------------------------- /slack_bot/ext.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from flask_redis import FlaskRedis 3 | from flask_cache import Cache 4 | 5 | redis_store = FlaskRedis() 6 | cache = Cache() 7 | -------------------------------------------------------------------------------- /slack_bot/local_settings.py.example: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | DEBUG = False 4 | SECRET_KEY = 'your secert key' 5 | 6 | SLACK_CHAT_TOKEN = 'your slack char token' 7 | SLACK_CALLBACK = 'your slack bot callback uri' 8 | REDIS_URL = 'redis://localhost:6379/0' 9 | ORG_NAME = 'your organization slug' 10 | BAIDU_AK = 'your baidu ak' 11 | SIMSIMI_KEY = 'your simsimi key' 12 | 13 | CACHE_TYPE = 'simple' 14 | 15 | TEST_DATA = { 16 | 'token': 'token', 17 | 'text': 'text', 18 | 'team_id': 'team_id', 19 | 'team_domain': 'team_domain', 20 | 'channel_id': 'channel_id', 21 | 'channel_name': 'channel_name', 22 | 'timestamp': 'timestamp', 23 | 'user_id': 'user_id', 24 | 'user_name': 'user_name', 25 | 'trigger_word': 'trigger_word', 26 | } 27 | -------------------------------------------------------------------------------- /slack_bot/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __all__ = [ 3 | 'help', 4 | 'github_issue', 5 | 'baidumap', 6 | 'bj_bus', 7 | 'pycoders', 8 | 'v2ex', 9 | 'toutiao', 10 | 'movie', 11 | 'events', 12 | 'dianping', 13 | 'pythonweekly', 14 | 'wikipedia', 15 | 'qiubai', 16 | 'earthquake', 17 | 'travel', 18 | 'airpollution', 19 | 'weather', 20 | 'orz', 21 | 'simsimi', 22 | ] 23 | -------------------------------------------------------------------------------- /slack_bot/plugins/airpollution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*-coding:utf-8-*- 3 | 4 | """ 5 | Copyright (c) 2012 Qijiang Fan 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | # 空气污染 28 | import requests 29 | import json 30 | from bs4 import BeautifulSoup 31 | 32 | from slack_bot.ext import cache 33 | from utils import gen_attachment 34 | 35 | description = """ 36 | 空气质量, 触发条件: "[城市名] 空气 [私聊] [带图]",比如: 37 | * 空气 38 | * 天津空气 39 | """ 40 | 41 | 42 | city = json.loads( 43 | """[["\u5317\u4eac", "Beijing"], ["\u5929\u6d25", "Tianjin"], ["\u4e0a\u6d77", "Shanghai"], ["\u91cd\u5e86", "Chongqing"], ["\u77f3\u5bb6\u5e84", "Shijiazhuang"], ["\u5510\u5c71", "Tangshan"], ["\u79e6\u7687\u5c9b", "Qinhuangdao"], ["\u90af\u90f8", "Handan"], ["\u4fdd\u5b9a", "Baoding"], ["\u90a2\u53f0", "Xingtai"], ["\u5f20\u5bb6\u53e3", "Zhangjiakou"], ["\u627f\u5fb7", "Chengde"], ["\u5eca\u574a", "Cangzhou"], ["\u5eca\u574a", "Langfang"], ["\u8861\u6c34", "Hengshui"], ["\u592a\u539f", "Taiyuan"], ["\u5927\u540c", "Datong"], ["\u9633\u6cc9", "Yangquan"], ["\u957f\u6cbb", "Changzhi"], ["\u4e34\u6c7e", "Linfen"], ["\u547c\u548c\u6d69\u7279", "Huhehaote"], ["\u5305\u5934", "Baotou"], ["\u8d64\u5cf0", "Chifeng"], ["\u6c88\u9633", "Shenyang"], ["\u5927\u8fde", "Dalian"], ["\u978d\u5c71", "Anshan"], ["\u629a\u987a", "Fushun"], ["\u672c\u6eaa", "Benxi"], ["\u9526\u5dde", "Jinzhou"], ["\u957f\u6625", "Changchun"], ["\u5409\u6797", "Jilin"], ["\u54c8\u5c14\u6ee8", "Haerbin"], ["\u9f50\u9f50\u54c8\u5c14", "Qiqihaer"], ["\u5927\u5e86", "Daqing"], ["\u7261\u4e39\u6c5f", "Mudanjiang"], ["\u5357\u4eac", "Nanjing"], ["\u65e0\u9521", "Wuxi"], ["\u5f90\u5dde", "Xuzhou"], ["\u5e38\u5dde", "Changzhou"], ["\u82cf\u5dde", "Suzhou"], ["\u5357\u901a", "Nantong"], ["\u8fde\u4e91\u6e2f", "Lianyungang"], ["\u626c\u5dde", "Yangzhou"], ["\u9547\u6c5f", "Zhenjiang"], ["\u6dee\u5b89", "Huaian"], ["\u76d0\u57ce", "Yancheng"], ["\u53f0\u5dde", "Taizhou"], ["\u5bbf\u8fc1", "Suqian"], ["\u676d\u5dde", "Hangzhou"], ["\u5b81\u6ce2", "Ningbo"], ["\u6e29\u5dde", "Wenzhou"], ["\u5609\u5174", "Jiaxing"], ["\u6e56\u5dde", "Huzhou"], ["\u7ecd\u5174", "Shaoxing"], ["\u91d1\u534e", "Jinhua"], ["\u8862\u5dde", "Quzhou"], ["\u821f\u5c71", "Zhoushan"], ["\u4e3d\u6c34", "Lishui"], ["\u5408\u80a5", "Hefei"], ["\u829c\u6e56", "Wuhu"], ["\u9a6c\u978d\u5c71", "Maanshan"], ["\u798f\u5dde", "Fuzhou"], ["\u53a6\u95e8", "Xiamen"], ["\u6cc9\u5dde", "Quanzhou"], ["\u5357\u660c", "Nanchang"], ["\u4e5d\u6c5f", "Jiujiang"], ["\u6d4e\u5357", "Jinan"], ["\u9752\u5c9b", "Qingdao"], ["\u6dc4\u535a", "Zibo"], ["\u67a3\u5e84", "Zaozhuang"], ["\u70df\u53f0", "Yantai"], ["\u6f4d\u574a", "Weifang"], ["\u6d4e\u5b81", "Jining"], ["\u6cf0\u5b89", "Taian"], ["\u5a01\u6d77", "Weihai"], ["\u65e5\u7167", "Rizhao"], ["\u4e1c\u8425", "Dongying"], ["\u83b1\u829c", "Laiwu"], ["\u4e34\u6c82", "Linyi"], ["\u5fb7\u5dde", "Dezhou"], ["\u804a\u57ce", "Liaocheng"], ["\u6ee8\u5dde", "Binzhou"], ["\u83cf\u6cfd", "Heze"], ["\u90d1\u5dde", "Zhengzhou"], ["\u5f00\u5c01", "Kaifeng"], ["\u6d1b\u9633", "Luoyang"], ["\u5e73\u9876\u5c71", "Pingdingshan"], ["\u5b89\u9633", "Anyang"], ["\u7126\u4f5c", "Jiaozuo"], ["\u4e09\u95e8\u5ce1", "Sanmenxia"], ["\u6b66\u6c49", "Wuhan"], ["\u5b9c\u660c", "Yichang"], ["\u8346\u5dde", "Jingzhou"], ["\u957f\u6c99", "Changsha"], ["\u682a\u6d32", "Zhuzhou"], ["\u6e58\u6f6d", "Xiangtan"], ["\u5cb3\u9633", "Yueyang"], ["\u5e38\u5fb7", "Changde"], ["\u5f20\u5bb6\u754c", "Zhangjiajie"], ["\u5e7f\u5dde", "Guangzhou"], ["\u97f6\u5173", "Shaoguan"], ["\u6df1\u5733", "Shenzhen"], ["\u73e0\u6d77", "Zhuhai"], ["\u6c55\u5934", "Shantou"], ["\u4f5b\u5c71", "Foshan"], ["\u6e5b\u6c5f", "Zhanjiang"], ["\u4e2d\u5c71", "Zhongshan"], ["\u6c5f\u95e8", "Jiangmen"], ["\u8087\u5e86", "Zhaoqing"], ["\u4e1c\u839e", "Dongguan"], ["\u60e0\u5dde", "Huizhou"], ["\u987a\u5fb7", "Shunde"], ["\u5357\u5b81", "Nanning"], ["\u67f3\u5dde", "Liuzhou"], ["\u6842\u6797", "Guilin"], ["\u5317\u6d77", "Beihai"], ["\u6d77\u53e3", "Haikou"], ["\u4e09\u4e9a", "Sanya"], ["\u6210\u90fd", "Chengdu"], ["\u81ea\u8d21", "Zigong"], ["\u6500\u679d\u82b1", "Panzhihua"], ["\u6cf8\u5dde", "Luzhou"], ["\u5fb7\u9633", "Deyang"], ["\u7ef5\u9633", "Mianyang"], ["\u5357\u5145", "Nanchong"], ["\u5b9c\u5bbe", "Yibin"], ["\u8d35\u9633", "Guiyang"], ["\u9075\u4e49", "Zunyi"], ["\u6606\u660e", "Kunming"], ["\u66f2\u9756", "Qujing"], ["\u7389\u6eaa", "Yuxi"], ["\u62c9\u8428", "Lhasa"], ["\u897f\u5b89", "Xian"], ["\u94dc\u5ddd", "Tongchuan"], ["\u5b9d\u9e21", "Baoji"], ["\u54b8\u9633", "Xianyang"], ["\u6e2d\u5357", "Weinan"], ["\u5ef6\u5b89", "Yanan"], ["\u5170\u5dde", "Lanzhou"], ["\u91d1\u660c", "Jinchang"], ["\u897f\u5b81", "Xining"], ["\u94f6\u5ddd", "Yinchuan"], ["\u77f3\u5634\u5c71", "Shizuishan"], ["\u4e4c\u9c81\u6728\u9f50", "Wulumuqi"], ["\u514b\u62c9\u739b\u4f9d", "Karamay"]]""") # noqa 44 | headers = {'Referer': 'http://aqicn.org/city/beijing/cn/', 45 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36'} # noqa 46 | 47 | 48 | def test(data): 49 | message = data['message'] 50 | if '空气' not in message: 51 | return False 52 | req = filter(lambda p: p[0].encode('utf-8') in message, city) 53 | return len(req) > 0 54 | 55 | 56 | def get_desc(cityname, cityshort): 57 | if cache is not None: 58 | r = cache.get('airpollution.%s' % (cityshort)) 59 | if r: 60 | return r 61 | 62 | title_link = 'http://aqicn.org/city/{}/cn/'.format(cityshort.lower()) 63 | 64 | r = requests.get(title_link, headers=headers) 65 | r.encoding = 'utf-8' 66 | p = r.text 67 | 68 | soup = BeautifulSoup(p) 69 | aqiwgtinfo = soup.find(id="aqiwgtinfo'").text 70 | aqivalue = soup.find("div", {'class': 'aqivalue'}).text 71 | min_pm25 = soup.find(id='min_pm25').text 72 | max_pm25 = soup.find(id='max_pm25').text 73 | text = '{0}实时空气质量指数(AQI): {1} {2} [最大:{3}, 最小:{4}]'.format( 74 | cityname.encode('utf-8'), aqiwgtinfo.encode('utf-8'), aqivalue, 75 | max_pm25, min_pm25) 76 | if cache is not None: 77 | cache.set('airpollution.%s' % (cityshort), text, 1800) 78 | image_url = soup.find(id='tr_pm25').find(id='td_pm25').find( 79 | 'img').attrs.get('src') 80 | title = soup.find('title').text 81 | attaches = [gen_attachment(text, image_url, title=title, 82 | title_link=title_link)] 83 | return text, attaches 84 | 85 | 86 | def handle(data): 87 | message = data['message'] 88 | reqs = filter(lambda p: p[0].encode('utf-8') in message, city) 89 | req = reqs[0] 90 | try: 91 | return get_desc(req[0], req[1]) 92 | except Exception as e: 93 | print 'Error: {}'.format(e) 94 | return '空气查询失败, 请重试!', [] 95 | 96 | 97 | if __name__ == '__main__': 98 | print handle({'message': '北京空气'}) 99 | -------------------------------------------------------------------------------- /slack_bot/plugins/baidumap.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # 百度地图 4 | import os 5 | import re 6 | import cPickle as pickle 7 | from datetime import datetime 8 | 9 | from flask import current_app as app 10 | import requests 11 | 12 | from utils import to_pinyin 13 | 14 | description = """ 15 | 路线规划, 触发条件: "从 地名 到|去 地名 [出行方式] [私聊]"。比如: 16 | 我想从兆维工业园到北京南站 步行 17 | """ 18 | 19 | HERE = os.path.abspath(os.path.dirname(__file__)) 20 | REGEX = re.compile(ur'从(\w+)[\u53bb|\u5230](\w+)', re.UNICODE) 21 | HTML_REGEX = re.compile(r'(<.*?>)') 22 | SUGGESTION_API = 'http://api.map.baidu.com/place/v2/suggestion' 23 | DIRECTION_API = 'http://api.map.baidu.com/direction/v1' 24 | GEOCODING_API = 'http://api.map.baidu.com/geocoder/v2/' 25 | POINT_API = 'http://api.map.baidu.com/telematics/v3/point' 26 | TRAVEL_ATTRACTIONS_API = 'http://api.map.baidu.com/telematics/v3/travel_attractions' # noqa 27 | TRAVEL_CITY_API = 'http://api.map.baidu.com/telematics/v3/travel_city' 28 | LOCAL_API = 'http://api.map.baidu.com/telematics/v3/local' 29 | WEATHER_API = 'http://api.map.baidu.com/telematics/v3/weather' 30 | 31 | DIRECTION = 0 32 | NODIRECTION = 1 33 | NOSCHEME = 2 34 | 35 | TagList = pickle.load( 36 | open(os.path.join(HERE, 'data' + os.path.sep + 'baidu_tag.pkl'), 'rb')) 37 | 38 | 39 | def address2geo(ak, address, city=u'北京'): 40 | res = requests.get(GEOCODING_API, params={ 41 | 'city': city, 'address': address, 'ak': ak, 'output': 'json'}) 42 | data = res.json() 43 | if data['status']: 44 | # 一般是无相关结果 45 | return False 46 | if not data['result']['precise'] and data['result']['confidence'] <= 50: 47 | # 可信度太低, 需要确认 48 | return False 49 | return data['result']['location'] 50 | 51 | 52 | def geo2address(ak, location): 53 | res = requests.get(GEOCODING_API, params={ 54 | 'location': location, 'ak': ak, 'output': 'json'}) 55 | data = res.json() 56 | if data['status']: 57 | return False 58 | return data['result']['formatted_address'] 59 | 60 | 61 | def place_suggestion(ak, pos): 62 | res = requests.get(SUGGESTION_API, params={ 63 | 'query': pos, 'region': 131, 'ak': ak, 'output': 'json'}) 64 | return [r['name'] for r in res.json()['result']] 65 | 66 | 67 | def place_direction(ak, origin, destination, mode='transit', tactics=11, 68 | region='北京', origin_region='北京', 69 | destination_region='北京'): 70 | params = { 71 | 'origin': origin, 'destination': destination, 'ak': ak, 72 | 'output': 'json', 'mode': mode, 'tactics': tactics 73 | } 74 | if mode != 'transit': 75 | params.update({ 76 | 'origin_region': origin_region, 77 | 'destination_region': destination_region 78 | }) 79 | else: 80 | params.update({'region': region}) 81 | res = requests.get(DIRECTION_API, params=params).json() 82 | result = res.get('result', []) 83 | 84 | # type=1起终点模糊 85 | if res['type'] == 1: 86 | if not result: 87 | return (NOSCHEME, place_suggestion(ak, origin), 88 | place_suggestion(ak, destination)) 89 | if mode != 'transit': 90 | _origin = result['origin']['content'] 91 | _dest = result['destination']['content'] 92 | else: 93 | _origin = result.get('origin', []) 94 | _dest = result.get('destination', []) 95 | o = ['{0}: {1}'.format( 96 | r['name'].encode('utf-8') if r['name'] else '', 97 | r['address'].encode('utf-8') if r['address'] else '') 98 | for r in _origin] 99 | d = ['{0}: {1}'.format( 100 | r['name'].encode('utf-8') if r['name'] else '', 101 | r['address'].encode('utf-8') if r['address'] else '') 102 | for r in _dest] 103 | return (NODIRECTION, o, d) 104 | # 起终点明确 105 | if mode == 'driving': 106 | # 驾车 107 | taxi = result['taxi'] 108 | for d in taxi['detail']: 109 | if u'白天' in d['desc']: 110 | daytime = d 111 | else: 112 | night = d 113 | is_daytime = 5 < datetime.now().hour < 23 114 | price = daytime['total_price'] if is_daytime else night['total_price'] 115 | remark = taxi['remark'] 116 | taxi_text = u'{0} 预计打车费用 {1}元'.format(remark, price) 117 | steps = result['routes'][0]['steps'] 118 | steps = [re.sub(HTML_REGEX, '', s['instructions']) for s in steps] 119 | return (DIRECTION, '\n'.join(steps), taxi_text) 120 | elif mode == 'walking': 121 | steps = result['routes'][0]['steps'] 122 | steps = [re.sub(HTML_REGEX, '', s['instructions']) for s in steps] 123 | return (DIRECTION, '\n'.join(steps), '') 124 | else: 125 | schemes = result['routes'] 126 | steps = [] 127 | for index, scheme in enumerate(schemes, 1): 128 | scheme = scheme['scheme'][0] 129 | step = '*方案{0} [距离: {1}公里, 花费: {2}元, 耗时: {3}分钟]:\n'.format( 130 | index, scheme['distance'] / 1000, 131 | scheme['price'] / 100, 132 | scheme['duration'] / 60) 133 | step += '\n'.join([ 134 | re.sub(HTML_REGEX, '', 135 | s[0]['stepInstruction'].encode('utf-8')) 136 | for s in scheme['steps'] 137 | ]) 138 | step += '\n' + '-' * 40 139 | steps.append(step) 140 | return (DIRECTION, steps, '') 141 | 142 | 143 | # 车联网API 144 | def point(ak, keyword, city=u'北京'): 145 | '''兴趣点查询''' 146 | res = requests.get(POINT_API, params={ 147 | 'keyword': keyword, 'city': city, 'ak': ak, 'output': 'json'}) 148 | data = res.json() 149 | return data['pointList'] 150 | 151 | 152 | def travel_attractions(ak, id): 153 | '''景点详情''' 154 | id = to_pinyin(id) 155 | res = requests.get(TRAVEL_ATTRACTIONS_API, params={ 156 | 'id': id, 'ak': ak, 'output': 'json'}) 157 | data = res.json() 158 | if data['error']: 159 | return '找不到这个景点' 160 | data = res.json()['result'] 161 | return '\n'.join([ 162 | data['description'], 163 | u'票价: ' + data['ticket_info']['price'], 164 | u'开放时间: ' + data['ticket_info']['open_time'] 165 | ]) 166 | 167 | 168 | def travel_city(ak, location=u'北京', day='all'): 169 | '''X日游''' 170 | res = requests.get(TRAVEL_CITY_API, params={ 171 | 'location': location, 'day': day, 'ak': ak, 'output': 'json'}) 172 | return res.json()['result'] 173 | 174 | 175 | def local(ak, tag, keyword, location=u'北京', radius=3000, city=u'北京'): 176 | '''周边检索''' 177 | res = requests.get(LOCAL_API, params={ 178 | 'cityName': city, 'radius': radius, 'tag': tag, 179 | 'keyWord': keyword, 'location': location, 'ak': ak, 'output': 'json'}) 180 | return res.json()['pointList'] 181 | 182 | 183 | def weather(ak, location=u'北京'): 184 | # location可是是城市名, 也可以是geo 185 | res = requests.get(WEATHER_API, params={ 186 | 'location': location, 'ak': ak, 'output': 'json'}) 187 | return res.json()['results'][0]['weather_data'] 188 | 189 | 190 | def test(data): 191 | message = data['message'] 192 | if not isinstance(message, unicode): 193 | message = message.decode('utf-8') 194 | return REGEX.search(message) 195 | 196 | 197 | def handle(data): 198 | if app is None: 199 | ak = '18691b8e4206238f331ad2e1ca88357e' 200 | else: 201 | ak = app.config.get('BAIDU_AK') 202 | message = data['message'] 203 | if not isinstance(message, unicode): 204 | message = message.decode('utf-8') 205 | origin, dest = REGEX.search(message).groups() 206 | 207 | tmpl = '最优路线: {0} {1}' 208 | if any([text in message for text in [u'开车', u'驾车']]): 209 | mode = 'driving' 210 | tmpl = '最优路线: {0} \n[{1}]' 211 | elif u'步行' in message: 212 | mode = 'walking' 213 | else: 214 | # 公交 215 | mode = 'transit' 216 | 217 | result = place_direction(ak, origin, dest, mode) 218 | if result[0] == NOSCHEME: 219 | text = '\n'.join(['输入的太模糊了, 你要找得起点可以选择:', 220 | '|'.join(result[1]), 221 | '终点可以选择:', 222 | '|'.join(result[2])]) 223 | elif result[0] == NODIRECTION: 224 | reg = '' 225 | if result[1]: 226 | reg += '起点' 227 | if result[2]: 228 | reg += '终点' 229 | msg = ['输入的{}太模糊了: 以下是参考:'.format(reg)] + \ 230 | result[1] + result[2] 231 | 232 | text = '\n'.join(msg) 233 | else: 234 | if isinstance(result[1], list): 235 | _result = '\n'.join(result[1]) 236 | else: 237 | _result = result[1].encode('utf-8') 238 | text = tmpl.format(_result, result[2].encode('utf-8')) 239 | return text 240 | 241 | 242 | if __name__ == '__main__': 243 | print handle({'message': '我想从兆维工业园到北京南站'}) 244 | print handle({'message': '我想从人大到北京南站'}) 245 | print handle({'message': '我想从人大到豆瓣'}) 246 | print handle({'message': '我想从兆维工业园到北京南站 步行'}) 247 | print handle({'message': '我想从兆维工业园到北京南站 开车'}) 248 | print handle({'message': '从酒仙桥去798'}) 249 | -------------------------------------------------------------------------------- /slack_bot/plugins/bj_bus.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import urllib2 4 | import hashlib 5 | from datetime import date 6 | import cPickle as pickle 7 | 8 | import lxml.etree as ET 9 | 10 | from utils import timestamp2str 11 | 12 | description = """ 13 | 北京公交信息, 触发条件: "公交 路数 [当前所在第几站] [私聊]", 比如: 14 | * 公交 571 15 | * 公交 571 18 16 | """ 17 | 18 | URL1 = u'/aiguang/bjgj.c?m=checkUpdate&version=1' 19 | URL2 = u'/aiguang/bjgj.c?m=update&id={0}' 20 | URL3 = (u'/bus.php?city=%E5%8C%97%E4%BA%AC&id={0}' 21 | u'&no={1}&type={2}&encrypt={3}&versionid=2') 22 | URL4 = u'http://bjgj.aibang.com:8899' 23 | 24 | 25 | # https://github.com/andelf/beijing-realtime-bus/blob/master/bjgj.py 26 | class Cipher(object): 27 | __doc__ = u'encrypt & decrypt base64 data' 28 | 29 | def __init__(self, key): 30 | self.key = str(key) 31 | 32 | @staticmethod 33 | def new_from_key(key): 34 | return Cipher((u'aibang' + str(key))) 35 | 36 | def _make_translate_table(self): 37 | key_bytes = bytearray(hashlib.md5(self.key).hexdigest(), u'utf-8') 38 | ret_val = list(range(256L)) 39 | k = 0L 40 | m = 0L 41 | for i in range(256L): 42 | k = (255L & ((k + key_bytes[m]) + ret_val[i])) 43 | [ret_val[i], ret_val[k]] = [ret_val[k], ret_val[i]] 44 | m = ((1L + m) % len(key_bytes)) 45 | return ret_val 46 | 47 | def translate(self, raw): 48 | trans_table = self._make_translate_table() 49 | raw_bytes = bytearray(raw) 50 | ret_val = bytearray(len(raw_bytes)) 51 | j = 0L 52 | k = 0L 53 | for i in range(len(raw_bytes)): 54 | k = (255L & (k + 1L)) 55 | j = (255L & (j + trans_table[k])) 56 | [trans_table[j], trans_table[k]] = [trans_table[k], trans_table[j]] 57 | n = (255L & (trans_table[k] + trans_table[j])) 58 | ret_val[i] = (raw_bytes[i] ^ trans_table[n]) 59 | return str(ret_val) 60 | 61 | def decrypt(self, cipher_text): 62 | return self.translate(cipher_text.decode(u'base64')) if cipher_text else '' 63 | 64 | def encrypt(self, plain_text): 65 | return self.translate(plain_text).encode(u'base64') 66 | 67 | 68 | def decrypt_busline_etree(et): 69 | busline = xpath_etree_children_to_dict_list(u'//busline', et)[0L] 70 | stations = xpath_etree_children_to_dict_list( 71 | u'//busline/stations/station', et) 72 | cipher = Cipher.new_from_key(busline[u'lineid']) 73 | busline = dict( 74 | *[busline], 75 | **{k: cipher.decrypt(v).decode(u'utf-8') 76 | for (k, v) in busline.items() 77 | if (k in [u'shotname', u'coord', u'linename'])} 78 | ) 79 | 80 | def _hy_anon_fn_9(): 81 | f_1236 = (lambda it: {k: cipher.decrypt(v).decode( 82 | u'utf-8', u'ignore') for (k, v) in it.items()}) 83 | for v_1235 in stations: 84 | yield f_1236(v_1235) 85 | 86 | stations = list(_hy_anon_fn_9()) 87 | busline[u'stations'] = stations 88 | return busline 89 | 90 | 91 | def decrypt_bus_realtime_info(bus): 92 | cipher = Cipher.new_from_key(bus[u'gt']) 93 | return dict( 94 | *[bus], 95 | **{k: cipher.decrypt(v).decode(u'utf-8') 96 | for (k, v) in bus.items() 97 | if (k in [u'ns', u'nsn', u'sd', 98 | u'srt', u'st', u'x', u'y'])} 99 | ) 100 | 101 | 102 | def etree_xpath_children_to_dict_list(et, path): 103 | return xpath_etree_children_to_dict_list(path, et) 104 | 105 | 106 | def xpath_etree_children_to_dict_list(path, et): 107 | def _hy_anon_fn_15(): 108 | f_1238 = ( 109 | lambda it: {elem.tag: elem.text for elem in it.getchildren()}) 110 | for v_1237 in et.xpath(path): 111 | yield f_1238(v_1237) 112 | return list(_hy_anon_fn_15()) 113 | 114 | 115 | # Updated at Fri Jul 3 00:31:56 2015 116 | # By andelf 117 | INIT_BUSLINE_STORE = pickle.load(file( 118 | os.path.join(os.path.dirname(__file__), 119 | 'data' + os.path.sep + 'bjbus_lines.pkl'), 'r')) 120 | 121 | 122 | class BeijingBusApi(object): 123 | __doc__ = u'Beijing Realtime Bus API.' 124 | 125 | def __init__(self): 126 | self.opener = urllib2.build_opener() 127 | self.uid = u'233333333333333333333333333333333333333' 128 | self.opener.addheaders = [ 129 | (u'SOURCE', u'1'), (u'PKG_SOURCE', u'1'), (u'OS', u'android'), 130 | (u'ROM', u'4.2.1'), (u'RESOLUTION', u'1280*720'), 131 | (u'MANUFACTURER', u'2013022'), (u'MODEL', u'2013022'), 132 | (u'UA', u'2013022,17,4.2.1,HBJ2.0,Unknown,1280*720'), 133 | (u'IMSI', u'233333333333333'), (u'IMEI', u'233333333333333'), 134 | (u'UID', self.uid), (u'CID', self.uid), (u'PRODUCT', u'nextbus'), 135 | (u'PLATFORM', u'android'), (u'VERSION', u'1.0.5'), 136 | (u'FIRST_VERSION', u'2'), (u'PRODUCTID', u'5'), 137 | (u'VERSIONID', u'2'), (u'CUSTOM', u'aibang') 138 | ] 139 | # FIXME: use date as update time flag is not accurate 140 | self.updated_time = date(2015, 7, 3) 141 | self.linename_cache = INIT_BUSLINE_STORE 142 | self.busline_info_cache = dict() 143 | 144 | def check_update(self): 145 | update_flags = self.get_update_flags() 146 | for flag in update_flags: 147 | id = flag['id'] 148 | if flag['version'] != self.linename_cache[id]['version']: 149 | self.linename_cache[id] = flag 150 | info = self.get_busline_info(id) 151 | self.linename_cache[id]['linename'] = info['linename'] 152 | print('update ... ') 153 | self.updated_time = date.today() 154 | 155 | def api_open(self, path, url_base=u'http://mc.aibang.com'): 156 | return self.opener.open((url_base + path)).read() 157 | 158 | # FIXME: this only returns first match 159 | def query_busline_id_by_name(self, name): 160 | if not isinstance(name, unicode): 161 | name = name.decode('utf-8') 162 | 163 | for flag in self.linename_cache.values(): 164 | # A linename uses format: [Variant](StartStation-EndStation) 165 | if flag.get('linename', u'').split(u'(')[0] == name: 166 | return flag['id'] 167 | 168 | return None 169 | 170 | def get_update_flags(self): 171 | def _hy_anon_fn_19(): 172 | f_1240 = (lambda it: {k: int(v) for (k, v) in it.items()}) 173 | for v_1239 in xpath_etree_children_to_dict_list( 174 | u'//line', 175 | ET.fromstring(self.api_open(URL1)) 176 | ): 177 | yield f_1240(v_1239) 178 | return list(_hy_anon_fn_19()) 179 | 180 | def get_busline_info(self, id): 181 | id = int(id) 182 | if id in self.busline_info_cache: 183 | return self.busline_info_cache[id] 184 | # FIXME: multi-line id handling 185 | # xml = self.api_open(URL2.format(u'%2C'.join(map(str, ([id] + list(ids)))))) 186 | xml = self.api_open(URL2.format(id)) 187 | buslines = ET.fromstring(xml) 188 | info = list(map(decrypt_busline_etree, buslines))[0] 189 | 190 | # Save cache 191 | self.busline_info_cache[id] = info 192 | return info 193 | 194 | def get_busline_realtime_info(self, id, no): 195 | def _hy_anon_fn_22(): 196 | f_1242 = (lambda it: decrypt_bus_realtime_info(it)) 197 | for v_1241 in etree_xpath_children_to_dict_list( 198 | ET.fromstring( 199 | self.api_open(URL3.format(id, no, 2L, 1L), URL4)), 200 | u'//data/bus' 201 | ): 202 | yield f_1242(v_1241) 203 | return list(_hy_anon_fn_22()) 204 | # end 205 | 206 | b = BeijingBusApi() 207 | b.check_update() 208 | 209 | 210 | def check_update(b): 211 | if b.updated_time.day != date.today().day: 212 | b.check_update() 213 | 214 | 215 | def _get_busline_info_by_name(busline): 216 | id = b.query_busline_id_by_name(unicode(busline)) 217 | ret = b.get_busline_info(id) if id else None 218 | if ret: 219 | return ret['stations'] 220 | return '' 221 | 222 | 223 | def get_site_id_by_name(busline, name): 224 | for site in _get_busline_info_by_name(busline): 225 | if site['name'] == name: 226 | return site['no'] 227 | return False 228 | 229 | 230 | def get_busline_info(busline): 231 | check_update(b) 232 | stations = _get_busline_info_by_name(busline) 233 | if stations: 234 | return '\n'.join([u'第{0}站: {1}'.format(s['no'], s['name']) 235 | for s in stations]) 236 | return '查不到线路' 237 | 238 | 239 | def get_busline_realtime_info(busline, site): 240 | check_update(b) 241 | id = b.query_busline_id_by_name(busline) 242 | if not site.isdigit(): 243 | site = get_site_id_by_name(busline, site) 244 | if not site: 245 | return '请使用正确地ID或者站点名字, 查询请使用类似`公交 busline`' 246 | realtime_infos = b.get_busline_realtime_info(id, site) 247 | 248 | return '\n'.join([ 249 | (u'*车次{0}: 下站: {1} 离下一站的距离: {2}米, 预计到达下一站的时间: {3}\n' 250 | u'离本站的距离: {4}米, 预计到达本站的时间: {5}').format( 251 | index, r['ns'], r['nsd'] if r['nsd'] != '-1' else u'进站中', 252 | timestamp2str(r['nst']), r['sd'], 253 | timestamp2str(r['st']) if r['st'] != '-1' else u'未知') 254 | for index, r in enumerate(realtime_infos, 1) 255 | if r.values().count('-1') < 3 256 | ]) 257 | 258 | 259 | def test_query_busline(message): 260 | return message.startswith(u'公交') and len(message.split()) == 2 261 | 262 | 263 | def test_query_realtime(message): 264 | return u'公交' in message and len(message.split()) == 3 265 | 266 | 267 | def test(data): 268 | message = data['message'] 269 | if not isinstance(message, unicode): 270 | message = message.decode('utf-8') 271 | return test_query_busline(message) or test_query_realtime(message) 272 | 273 | 274 | def handle(data, **kwargs): 275 | message = data['message'] 276 | if not isinstance(message, unicode): 277 | message = message.decode('utf-8') 278 | # 查询公交线路 279 | if test_query_busline(message): 280 | busline = message.split()[1].strip() 281 | return get_busline_info(busline) 282 | else: 283 | _, busline, site_num = message.split() 284 | # if not busline.isdigit(): 285 | # return '查询公交全部站点ID和名字请使用类似`公交 busline`, 比如: 公交 571' 286 | return get_busline_realtime_info(busline, site_num) or '查不到线路' 287 | 288 | 289 | if __name__ == '__main__': 290 | print(handle({'message': '公交 614'})) 291 | print(handle({'message': '公交 614 中关园'})) 292 | print(handle({'message': '公交 夜2'})) # works only at night 293 | print(handle({'message': '公交 夜2 大红门北站'})) 294 | -------------------------------------------------------------------------------- /slack_bot/plugins/consts.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | ONE_MINUTE = 60 3 | HALF_HOUR = 60 * 30 4 | ONE_HOUR = 60 * 60 5 | HALF_DAY = 60 * 60 * 12 6 | ONE_DAY = 60 * 60 * 24 7 | 8 | COLORS = ( 9 | '#001f3f', # Navy 10 | '#0074D9', # Blue 11 | '#7FDBFF', # Aqua 12 | '#39CCCC', # Teal 13 | '#3D9970', # Olive 14 | '#2ECC40', # Green 15 | '#01FF70', # Lime 16 | '#FFDC00', # Yellow 17 | '#FF851B', # Orange 18 | '#FF4136', # Red 19 | '#85144b', # Maroon 20 | '#F012BE', # Fuchsia 21 | '#b10dc9', # Purple 22 | '#111111', # black 23 | '#aaaaaa', # Gray 24 | '#dddddd', # Silver 25 | ) 26 | -------------------------------------------------------------------------------- /slack_bot/plugins/data/baidu_tag.pkl: -------------------------------------------------------------------------------- 1 | (lp1 2 | V\u7f8e\u98df,\u4e2d\u9910\u9986,\u9c81\u83dc 3 | p2 4 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u5ddd\u83dc 5 | p3 6 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u7ca4\u83dc 7 | p4 8 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u5fbd\u83dc 9 | p5 10 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u53f0\u6e7e\u83dc 11 | p6 12 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u8d35\u5dde\u83dc 13 | p7 14 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u6c5f\u6d59\u83dc 15 | p8 16 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u6e58\u83dc 17 | p9 18 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u6e56\u5317\u83dc 19 | p10 20 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u6e05\u771f\u83dc 21 | p11 22 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u4e91\u5357\u83dc 23 | p12 24 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u4e1c\u5317\u83dc 25 | p13 26 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u5317\u4eac\u83dc 27 | p14 28 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u95fd\u5357\u83dc 29 | p15 30 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u897f\u5317\u83dc 31 | p16 32 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u7d20\u83dc 33 | p17 34 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u706b\u9505 35 | p18 36 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u70e4\u9e2d 37 | p19 38 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u6d77\u9c9c 39 | p20 40 | aV\u7f8e\u98df,\u4e2d\u9910\u9986,\u5bb6\u5e38\u83dc 41 | p21 42 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u610f\u5927\u5229\u83dc 43 | p22 44 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u6cd5\u56fd\u83dc 45 | p23 46 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u5fb7\u56fd\u83dc 47 | p24 48 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u4fc4\u7f57\u65af\u83dc 49 | p25 50 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u62c9\u7f8e\u70e7\u70e4 51 | p26 52 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u4e2d\u4e1c\u6599\u7406 53 | p27 54 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u62ab\u8428 55 | p28 56 | aV\u7f8e\u98df,\u897f\u9910\u5385,\u725b\u6392 57 | p29 58 | aV\u7f8e\u98df,\u65e5\u672c\u83dc,\u65e5\u672c\u6599\u7406 59 | p30 60 | aV\u7f8e\u98df,\u65e5\u672c\u83dc,\u65e5\u5f0f\u70e7\u70e4 61 | p31 62 | aV\u7f8e\u98df,\u65e5\u672c\u83dc,\u5bff\u53f8 63 | p32 64 | aV\u7f8e\u98df,\u4e1c\u5357\u4e9a\u83dc,\u6cf0\u56fd\u83dc 65 | p33 66 | aV\u7f8e\u98df,\u4e1c\u5357\u4e9a\u83dc,\u8d8a\u5357\u83dc 67 | p34 68 | aV\u7f8e\u98df,\u4e1c\u5357\u4e9a\u83dc,\u5370\u5ea6\u83dc 69 | p35 70 | aV\u7f8e\u98df,\u4e1c\u5357\u4e9a\u83dc,\u83f2\u5f8b\u5bbe\u83dc 71 | p36 72 | aV\u7f8e\u98df,\u4e1c\u5357\u4e9a\u83dc,\u5370\u5c3c\u98ce\u5473 73 | p37 74 | aV\u7f8e\u98df,\u5feb\u9910,\u4e2d\u5f0f\u5feb\u9910 75 | p38 76 | aV\u7f8e\u98df,\u5feb\u9910,\u897f\u5f0f\u5feb\u9910 77 | p39 78 | aV\u7f8e\u98df,\u5c0f\u5403,\u7c89\u9762\u9986 79 | p40 80 | aV\u7f8e\u98df,\u5c0f\u5403,\u7ca5\u5e97 81 | p41 82 | aV\u7f8e\u98df,\u5c0f\u5403,\u997a\u5b50\u9986 83 | p42 84 | aV\u7f8e\u98df,\u5c0f\u5403,\u9984\u9968\u5e97 85 | p43 86 | aV\u7f8e\u98df,\u5c0f\u5403,\u9ebb\u8fa3\u70eb 87 | p44 88 | aV\u7f8e\u98df,\u5c0f\u5403,\u5173\u4e1c\u716e 89 | p45 90 | aV\u7f8e\u98df,\u5c0f\u5403,\u719f\u98df 91 | p46 92 | aV\u7f8e\u98df,\u5c0f\u5403,\u96f6\u98df 93 | p47 94 | aV\u7f8e\u98df,\u5c0f\u5403,\u5305\u5b50 95 | p48 96 | aV\u7f8e\u98df,\u86cb\u7cd5\u751c\u70b9,\u86cb\u7cd5\u897f\u70b9 97 | p49 98 | aV\u7f8e\u98df,\u86cb\u7cd5\u751c\u70b9,\u51b0\u6dc7\u6dcb 99 | p50 100 | aV\u7f8e\u98df,\u86cb\u7cd5\u751c\u70b9,\u751c\u70b9\u996e\u54c1 101 | p51 102 | aV\u5bbe\u9986,\u661f\u7ea7\u9152\u5e97,\u4e94\u661f\u7ea7\u9152\u5e97 103 | p52 104 | aV\u5bbe\u9986,\u661f\u7ea7\u9152\u5e97,\u56db\u661f\u7ea7\u9152\u5e97 105 | p53 106 | aV\u5bbe\u9986,\u661f\u7ea7\u9152\u5e97,\u4e09\u661f\u7ea7\u9152\u5e97 107 | p54 108 | aV\u5bbe\u9986,\u65c5\u5e97,\u5bb6\u5ead\u65c5\u9986 109 | p55 110 | aV\u5bbe\u9986,\u65c5\u5e97,\u9752\u5e74\u65c5\u820d 111 | p56 112 | aV\u5bbe\u9986,\u65c5\u5e97,\u62db\u5f85\u6240 113 | p57 114 | aV\u8d2d\u7269,\u5bb6\u7535,\u7a7a\u8c03 115 | p58 116 | aV\u8d2d\u7269,\u5bb6\u7535,\u51b0\u7bb1 117 | p59 118 | aV\u8d2d\u7269,\u5bb6\u7535,\u6d17\u8863\u673a 119 | p60 120 | aV\u8d2d\u7269,\u5bb6\u7535,\u5fae\u6ce2\u7089 121 | p61 122 | aV\u8d2d\u7269,\u6570\u7801,\u7535\u8111 123 | p62 124 | aV\u8d2d\u7269,\u6570\u7801,\u6444\u5f71 125 | p63 126 | aV\u8d2d\u7269,\u6570\u7801,\u624b\u673a 127 | p64 128 | aV\u8d2d\u7269,\u5bb6\u5c45\u5efa\u6750,\u5bb6\u5177 129 | p65 130 | aV\u8d2d\u7269,\u5bb6\u5c45\u5efa\u6750,\u706f\u9970 131 | p66 132 | aV\u8d2d\u7269,\u5bb6\u5c45\u5efa\u6750,\u53a8\u5177 133 | p67 134 | aV\u8d2d\u7269,\u5bb6\u5c45\u5efa\u6750,\u536b\u6d74 135 | p68 136 | aV\u8d2d\u7269,\u5bb6\u5c45\u5efa\u6750,\u4e94\u91d1 137 | p69 138 | aV\u8d2d\u7269,\u5bb6\u7eba,\u5e8a\u4e0a\u7528\u54c1 139 | p70 140 | aV\u8d2d\u7269,\u5bb6\u7eba,\u7a97\u5e18 141 | p71 142 | aV\u8d2d\u7269,\u5bb6\u7eba,\u5750\u57ab 143 | p72 144 | aV\u8d2d\u7269,\u5bb6\u7eba,\u5730\u6bef 145 | p73 146 | aV\u8d2d\u7269,\u4e66\u5e97/\u97f3\u50cf\u5e97,\u4e66\u5e97 147 | p74 148 | aV\u8d2d\u7269,\u4e66\u5e97/\u97f3\u50cf\u5e97,\u97f3\u50cf\u5e97 149 | p75 150 | aV\u8d2d\u7269,\u670d\u88c5/\u978b\u5e3d/\u7bb1\u5305,\u978b\u5b50 151 | p76 152 | aV\u8d2d\u7269,\u670d\u88c5/\u978b\u5e3d/\u7bb1\u5305,\u670d\u88c5 153 | p77 154 | aV\u8d2d\u7269,\u670d\u88c5/\u978b\u5e3d/\u7bb1\u5305,\u7bb1\u5305 155 | p78 156 | aV\u8d2d\u7269,\u73e0\u5b9d\u9970\u54c1,\u5a5a\u6212 157 | p79 158 | aV\u8d2d\u7269,\u82b1\u5e97/\u793c\u54c1\u5e97,\u82b1\u5e97 159 | p80 160 | aV\u8d2d\u7269,\u82b1\u5e97/\u793c\u54c1\u5e97,\u793c\u54c1\u5e97 161 | p81 162 | aV\u8d2d\u7269,\u8336\u53f6/\u8336\u5177,\u8336\u53f6 163 | p82 164 | aV\u8d2d\u7269,\u8336\u53f6/\u8336\u5177,\u8336\u5177 165 | p83 166 | aV\u8d2d\u7269,\u4e50\u5668\u884c,\u7434\u884c 167 | p84 168 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u519c\u8d38\u5e02\u573a 169 | p85 170 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u670d\u88c5\u6279\u53d1\u5e02\u573a 171 | p86 172 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u8f7b\u7eba\u5e02\u573a 173 | p87 174 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u836f\u6750\u6279\u53d1\u5e02\u573a 175 | p88 176 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u6587\u5177\u6279\u53d1\u5e02\u573a 177 | p89 178 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u5c0f\u5546\u54c1\u5e02\u573a 179 | p90 180 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u82b1\u9e1f\u5e02\u573a 181 | p91 182 | aV\u8d2d\u7269,\u96c6\u5e02/\u6279\u53d1\u5e02\u573a,\u4e8c\u624b\u5e02\u573a 183 | p92 184 | aV\u6c7d\u8f66\u670d\u52a1,\u6c7d\u8f66\u9500\u552e,4S\u5e97 185 | p93 186 | aV\u6c7d\u8f66\u670d\u52a1,\u6c7d\u8f66\u9500\u552e,\u6c7d\u8f66\u9500\u552e\u7efc\u5408\u5e97 187 | p94 188 | aV\u6c7d\u8f66\u670d\u52a1,\u6c7d\u8f66\u7f8e\u5bb9,\u6c7d\u8f66\u4fdd\u517b 189 | p95 190 | aV\u6c7d\u8f66\u670d\u52a1,\u6c7d\u8f66\u7f8e\u5bb9,\u6c7d\u8f66\u88c5\u9970 191 | p96 192 | aV\u751f\u6d3b\u670d\u52a1,\u901a\u8baf\u8425\u4e1a\u5385,\u7535\u4fe1\u8425\u4e1a\u5385 193 | p97 194 | aV\u751f\u6d3b\u670d\u52a1,\u901a\u8baf\u8425\u4e1a\u5385,\u79fb\u52a8\u8425\u4e1a\u5385 195 | p98 196 | aV\u751f\u6d3b\u670d\u52a1,\u901a\u8baf\u8425\u4e1a\u5385,\u8054\u901a\u8425\u4e1a\u5385 197 | p99 198 | aV\u751f\u6d3b\u670d\u52a1,\u901a\u8baf\u8425\u4e1a\u5385,\u94c1\u901a\u8425\u4e1a\u5385 199 | p100 200 | aV\u751f\u6d3b\u670d\u52a1,\u901a\u8baf\u8425\u4e1a\u5385,\u7f51\u901a\u8425\u4e1a\u5385 201 | p101 202 | aV\u751f\u6d3b\u670d\u52a1,\u706b\u8f66\u7968/\u673a\u7968/\u6c7d\u8f66\u7968\u552e\u7968\u70b9,\u706b\u8f66\u7968\u552e\u7968\u70b9 203 | p102 204 | aV\u751f\u6d3b\u670d\u52a1,\u706b\u8f66\u7968/\u673a\u7968/\u6c7d\u8f66\u7968\u552e\u7968\u70b9,\u98de\u673a\u7968\u552e\u7968\u70b9 205 | p103 206 | aV\u751f\u6d3b\u670d\u52a1,\u706b\u8f66\u7968/\u673a\u7968/\u6c7d\u8f66\u7968\u552e\u7968\u70b9,\u6c7d\u8f66\u7968\u552e\u7968\u70b9 207 | p104 208 | aV\u751f\u6d3b\u670d\u52a1,\u88c1\u7f1d\u5e97/\u6d17\u8863\u5e97,\u88c1\u7f1d\u5e97 209 | p105 210 | aV\u751f\u6d3b\u670d\u52a1,\u88c1\u7f1d\u5e97/\u6d17\u8863\u5e97,\u6d17\u8863\u5e97 211 | p106 212 | aV\u751f\u6d3b\u670d\u52a1,\u56fe\u6587\u5feb\u5370,\u6253\u5370\u590d\u5370 213 | p107 214 | aV\u751f\u6d3b\u670d\u52a1,\u56fe\u6587\u5feb\u5370,\u4f20\u771f 215 | p108 216 | aV\u751f\u6d3b\u670d\u52a1,\u56fe\u6587\u5feb\u5370,\u5feb\u7167 217 | p109 218 | aV\u751f\u6d3b\u670d\u52a1,\u7167\u76f8\u9986,\u827a\u672f\u5199\u771f 219 | p110 220 | aV\u751f\u6d3b\u670d\u52a1,\u7167\u76f8\u9986,\u8bc1\u4ef6\u7167 221 | p111 222 | aV\u751f\u6d3b\u670d\u52a1,\u6570\u7801\u7ef4\u4fee,\u7535\u8111\u7ef4\u4fee 223 | p112 224 | aV\u751f\u6d3b\u670d\u52a1,\u6570\u7801\u7ef4\u4fee,\u624b\u673a\u7ef4\u4fee 225 | p113 226 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u642c\u5bb6 227 | p114 228 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u4fdd\u59c6 229 | p115 230 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u7ba1\u9053\u758f\u901a 231 | p116 232 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u949f\u70b9\u5de5 233 | p117 234 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u5bb6\u7535\u7ef4\u4fee 235 | p118 236 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u9001\u6c34 237 | p119 238 | aV\u751f\u6d3b\u670d\u52a1,\u5bb6\u653f\u670d\u52a1,\u5f00\u9501 239 | p120 240 | aV\u751f\u6d3b\u670d\u52a1,\u5ba0\u7269,\u5ba0\u7269\u7f8e\u5bb9 241 | p121 242 | aV\u751f\u6d3b\u670d\u52a1,\u5ba0\u7269,\u5ba0\u7269\u7528\u54c1 243 | p122 244 | aV\u751f\u6d3b\u670d\u52a1,\u5ba0\u7269,\u5ba0\u7269\u533b\u9662 245 | p123 246 | aV\u751f\u6d3b\u670d\u52a1,\u5ba0\u7269,\u5ba0\u7269\u5bc4\u517b 247 | p124 248 | aV\u751f\u6d3b\u670d\u52a1,\u5ba0\u7269,\u5ba0\u7269\u98df\u54c1 249 | p125 250 | aV\u751f\u6d3b\u670d\u52a1,\u8f66\u8f86\u7ef4\u4fee,\u6469\u6258\u8f66\u7ef4\u4fee 251 | p126 252 | aV\u751f\u6d3b\u670d\u52a1,\u8f66\u8f86\u7ef4\u4fee,\u81ea\u884c\u8f66\u7ef4\u4fee 253 | p127 254 | aV\u751f\u6d3b\u670d\u52a1,\u65c5\u884c\u793e,\u56fd\u9645\u65c5\u884c\u793e 255 | p128 256 | aV\u7ed3\u5a5a,\u5a5a\u5e86\u670d\u52a1,\u5a5a\u5e86\u7b56\u5212 257 | p129 258 | aV\u7ed3\u5a5a,\u5a5a\u5e86\u670d\u52a1,\u5e86\u5178\u7528\u54c1 259 | p130 260 | aV\u7ed3\u5a5a,\u5a5a\u5e86\u670d\u52a1,\u5a5a\u5e86\u79df\u8f66 261 | p131 262 | aV\u7ed3\u5a5a,\u5a5a\u5e86\u670d\u52a1,\u53f8\u4eea\u7763\u5bfc 263 | p132 264 | aV\u7ed3\u5a5a,\u5a5a\u5e86\u670d\u52a1,\u8ddf\u5986\u9020\u578b 265 | p133 266 | aV\u4e3d\u4eba,\u7f8e\u5bb9,SPA 267 | p134 268 | aV\u4e3d\u4eba,\u7f8e\u5bb9,\u9762\u90e8\u62a4\u7406 269 | p135 270 | aV\u4e3d\u4eba,\u7f8e\u53d1,\u6d17\u67d3\u70eb 271 | p136 272 | aV\u91d1\u878d,\u94f6\u884c,atm 273 | p137 274 | aV\u91d1\u878d,\u4fe1\u7528\u793e,\u519c\u6751\u4fe1\u7528\u793e 275 | p138 276 | aV\u91d1\u878d,\u4fe1\u7528\u793e,\u57ce\u5e02\u4fe1\u7528\u793e 277 | p139 278 | aV\u4f11\u95f2\u5a31\u4e50,\u5ea6\u5047\u6751/\u519c\u5bb6\u9662/\u91c7\u6458\u56ed,\u519c\u5bb6\u9662 279 | p140 280 | aV\u4f11\u95f2\u5a31\u4e50,\u5ea6\u5047\u6751/\u519c\u5bb6\u9662/\u91c7\u6458\u56ed,\u5ea6\u5047 281 | p141 282 | aV\u4f11\u95f2\u5a31\u4e50,\u5ea6\u5047\u6751/\u519c\u5bb6\u9662/\u91c7\u6458\u56ed,\u91c7\u6458\u56ed 283 | p142 284 | aV\u4f11\u95f2\u5a31\u4e50,\u591c\u603b\u4f1a/\u6b4c\u821e\u5385/\u5a31\u4e50\u57ce/\u8fea\u5385,\u591c\u603b\u4f1a 285 | p143 286 | aV\u4f11\u95f2\u5a31\u4e50,\u591c\u603b\u4f1a/\u6b4c\u821e\u5385/\u5a31\u4e50\u57ce/\u8fea\u5385,\u6b4c\u821e\u5385 287 | p144 288 | aV\u4f11\u95f2\u5a31\u4e50,\u591c\u603b\u4f1a/\u6b4c\u821e\u5385/\u5a31\u4e50\u57ce/\u8fea\u5385,\u5a31\u4e50\u57ce 289 | p145 290 | aV\u4f11\u95f2\u5a31\u4e50,\u591c\u603b\u4f1a/\u6b4c\u821e\u5385/\u5a31\u4e50\u57ce/\u8fea\u5385,\u8fea\u5385 291 | p146 292 | aV\u4f11\u95f2\u5a31\u4e50,\u6d17\u6d74/\u6309\u6469/\u8db3\u6d74/\u6e29\u6cc9,\u6d17\u6d74\u4e2d\u5fc3 293 | p147 294 | aV\u4f11\u95f2\u5a31\u4e50,\u6d17\u6d74/\u6309\u6469/\u8db3\u6d74/\u6e29\u6cc9,\u6851\u62ff 295 | p148 296 | aV\u4f11\u95f2\u5a31\u4e50,\u6d17\u6d74/\u6309\u6469/\u8db3\u6d74/\u6e29\u6cc9,\u6e29\u6cc9 297 | p149 298 | aV\u4f11\u95f2\u5a31\u4e50,\u6d17\u6d74/\u6309\u6469/\u8db3\u6d74/\u6e29\u6cc9,\u6309\u6469 299 | p150 300 | aV\u4f11\u95f2\u5a31\u4e50,\u6d17\u6d74/\u6309\u6469/\u8db3\u6d74/\u6e29\u6cc9,\u8db3\u6d74 301 | p151 302 | aV\u4f11\u95f2\u5a31\u4e50,\u6e38\u620f,\u7535\u73a9 303 | p152 304 | aV\u4f11\u95f2\u5a31\u4e50,\u6e38\u620f,\u68cb\u724c\u5ba4 305 | p153 306 | aV\u4f11\u95f2\u5a31\u4e50,\u6e38\u620f,\u684c\u6e38 307 | p154 308 | aV\u4f11\u95f2\u5a31\u4e50,\u6e38\u620f,\u771f\u4ebaCS 309 | p155 310 | aV\u4f11\u95f2\u5a31\u4e50,\u9152\u5427/\u8336\u5ea7/\u5496\u5561\u5385,\u9152\u5427 311 | p156 312 | aV\u4f11\u95f2\u5a31\u4e50,\u9152\u5427/\u8336\u5ea7/\u5496\u5561\u5385,\u8336\u5ea7 313 | p157 314 | aV\u4f11\u95f2\u5a31\u4e50,\u9152\u5427/\u8336\u5ea7/\u5496\u5561\u5385,\u5496\u5561\u5385 315 | p158 316 | aV\u4f11\u95f2\u5a31\u4e50,DIY\u624b\u5de5,DIY\u86cb\u7cd5 317 | p159 318 | aV\u4f11\u95f2\u5a31\u4e50,DIY\u624b\u5de5,DIY\u9970\u54c1 319 | p160 320 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u6e38\u6cf3\u9986 321 | p161 322 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u7fbd\u6bdb\u7403\u9986 323 | p162 324 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u4e52\u4e53\u7403\u9986 325 | p163 326 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u53f0\u7403\u9986 327 | p164 328 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u4fdd\u9f84\u7403\u9986 329 | p165 330 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u6b66\u672f\u9986 331 | p166 332 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u4f53\u64cd\u9986 333 | p167 334 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u7f51\u7403\u573a 335 | p168 336 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u7bee\u7403\u573a 337 | p169 338 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u8db3\u7403\u573a 339 | p170 340 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u6e9c\u51b0\u573a 341 | p171 342 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u9ad8\u5c14\u592b\u7403\u573a 343 | p172 344 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u6ed1\u96ea\u573a 345 | p173 346 | aV\u8fd0\u52a8\u5065\u8eab,\u4f53\u80b2\u573a\u9986,\u8d5b\u9a6c\u573a 347 | p174 348 | aV\u8fd0\u52a8\u5065\u8eab,\u821e\u8e48,\u82ad\u857e 349 | p175 350 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u6f5c\u6c34 351 | p176 352 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u8d5b\u8f66\u573a 353 | p177 354 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u6500\u5ca9 355 | p178 356 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u8e66\u6781 357 | p179 358 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u822a\u7a7a\u6ed1\u7fd4 359 | p180 360 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u51b2\u6d6a 361 | p181 362 | aV\u8fd0\u52a8\u5065\u8eab,\u6781\u9650\u8fd0\u52a8,\u5b9a\u5411\u8d8a\u91ce 363 | p182 364 | aV\u533b\u7597,\u533b\u9662,\u4e09\u7532\u533b\u9662 365 | p183 366 | aV\u533b\u7597,\u533b\u9662,\u4e09\u4e59\u533b\u9662 367 | p184 368 | aV\u533b\u7597,\u533b\u9662,\u4e09\u4e19\u533b\u9662 369 | p185 370 | aV\u533b\u7597,\u533b\u9662,\u4e8c\u7532\u533b\u9662 371 | p186 372 | aV\u533b\u7597,\u533b\u9662,\u4e8c\u4e59\u533b\u9662 373 | p187 374 | aV\u533b\u7597,\u533b\u9662,\u4e8c\u4e19\u533b\u9662 375 | p188 376 | aV\u533b\u7597,\u533b\u9662,\u4e00\u7ea7\u533b\u9662 377 | p189 378 | aV\u533b\u7597,\u533b\u9662,\u7efc\u5408\u533b\u9662 379 | p190 380 | aV\u533b\u7597,\u533b\u9662,\u5987\u4ea7\u79d1\u533b\u9662 381 | p191 382 | aV\u533b\u7597,\u533b\u9662,\u5987\u79d1\u533b\u9662 383 | p192 384 | aV\u533b\u7597,\u533b\u9662,\u513f\u7ae5\u533b\u9662 385 | p193 386 | aV\u533b\u7597,\u533b\u9662,\u53e3\u8154\u533b\u9662 387 | p194 388 | aV\u533b\u7597,\u533b\u9662,\u80bf\u7624\u533b\u9662 389 | p195 390 | aV\u533b\u7597,\u533b\u9662,\u7cbe\u795e\u75c5\u533b\u9662 391 | p196 392 | aV\u533b\u7597,\u533b\u9662,\u7cd6\u5c3f\u75c5\u533b\u9662 393 | p197 394 | aV\u533b\u7597,\u533b\u9662,\u7259\u79d1\u533b\u9662 395 | p198 396 | aV\u533b\u7597,\u533b\u9662,\u773c\u79d1\u533b\u9662 397 | p199 398 | aV\u533b\u7597,\u533b\u9662,\u9aa8\u79d1\u533b\u9662 399 | p200 400 | aV\u533b\u7597,\u533b\u9662,\u7537\u79d1\u533b\u9662 401 | p201 402 | aV\u533b\u7597,\u533b\u9662,\u76ae\u80a4\u75c5\u533b\u9662 403 | p202 404 | aV\u533b\u7597,\u533b\u9662,\u5fc3\u7406\u533b\u9662 405 | p203 406 | aV\u533b\u7597,\u533b\u9662,\u4f20\u67d3\u75c5\u533b\u9662 407 | p204 408 | aV\u533b\u7597,\u533b\u9662,\u5987\u5e7c\u4fdd\u5065\u9662 409 | p205 410 | aV\u533b\u7597,\u533b\u9662,\u809b\u80a0\u79d1\u533b\u9662 411 | p206 412 | aV\u533b\u7597,\u533b\u9662,\u5fc3\u8840\u7ba1\u75c5\u533b\u9662 413 | p207 414 | aV\u533b\u7597,\u533b\u9662,\u4e94\u5b98\u79d1\u533b\u9662 415 | p208 416 | aV\u533b\u7597,\u533b\u9662,\u4e2d\u533b\u9662 417 | p209 418 | aV\u65c5\u6e38\u666f\u70b9,\u516c\u56ed,\u52a8\u7269\u56ed 419 | p210 420 | aV\u65c5\u6e38\u666f\u70b9,\u516c\u56ed,\u690d\u7269\u56ed 421 | p211 422 | aV\u65c5\u6e38\u666f\u70b9,\u516c\u56ed,\u56fd\u5bb6\u516c\u56ed 423 | p212 424 | aV\u65c5\u6e38\u666f\u70b9,\u98ce\u666f\u533a,5A\u98ce\u666f\u533a 425 | p213 426 | aV\u65c5\u6e38\u666f\u70b9,\u98ce\u666f\u533a,4A\u98ce\u666f\u533a 427 | p214 428 | aV\u65c5\u6e38\u666f\u70b9,\u98ce\u666f\u533a,3A\u98ce\u666f\u533a 429 | p215 430 | aV\u6559\u80b2,\u5b66\u6821,\u5e7c\u513f\u56ed 431 | p216 432 | aV\u6559\u80b2,\u5b66\u6821,\u5c0f\u5b66 433 | p217 434 | aV\u6559\u80b2,\u5b66\u6821,\u521d\u4e2d 435 | p218 436 | aV\u6559\u80b2,\u5b66\u6821,\u9ad8\u4e2d 437 | p219 438 | aV\u6559\u80b2,\u5b66\u6821,\u4e2d\u4e13 439 | p220 440 | aV\u6559\u80b2,\u5b66\u6821,\u5927\u5b66 441 | p221 442 | aV\u6559\u80b2,\u5b66\u6821,\u7279\u6b8a\u6559\u80b2\u5b66\u6821 443 | p222 444 | aV\u57f9\u8bad\u673a\u6784,\u6280\u80fd\u57f9\u8bad,\u9a7e\u6821 445 | p223 446 | aV\u57f9\u8bad\u673a\u6784,\u6280\u80fd\u57f9\u8bad,\u7535\u8111\u57f9\u8bad 447 | p224 448 | aV\u57f9\u8bad\u673a\u6784,\u6280\u80fd\u57f9\u8bad,\u7f8e\u5bb9\u57f9\u8bad 449 | p225 450 | aV\u57f9\u8bad\u673a\u6784,\u6280\u80fd\u57f9\u8bad,\u7f8e\u53d1\u57f9\u8bad 451 | p226 452 | aV\u57f9\u8bad\u673a\u6784,\u6280\u80fd\u57f9\u8bad,\u53a8\u5e08\u57f9\u8bad 453 | p227 454 | aV\u57f9\u8bad\u673a\u6784,\u827a\u672f\u57f9\u8bad,\u7f8e\u672f\u57f9\u8bad 455 | p228 456 | aV\u57f9\u8bad\u673a\u6784,\u827a\u672f\u57f9\u8bad,\u5409\u4ed6\u57f9\u8bad 457 | p229 458 | aV\u57f9\u8bad\u673a\u6784,\u827a\u672f\u57f9\u8bad,\u94a2\u7434\u57f9\u8bad 459 | p230 460 | aV\u57f9\u8bad\u673a\u6784,\u827a\u672f\u57f9\u8bad,\u5c0f\u63d0\u7434\u57f9\u8bad 461 | p231 462 | aV\u57f9\u8bad\u673a\u6784,\u827a\u672f\u57f9\u8bad,\u58f0\u4e50\u57f9\u8bad 463 | p232 464 | aV\u57f9\u8bad\u673a\u6784,\u8bed\u8a00\u57f9\u8bad,\u65e5\u8bed\u57f9\u8bad 465 | p233 466 | aV\u57f9\u8bad\u673a\u6784,\u8bed\u8a00\u57f9\u8bad,\u97e9\u8bed\u57f9\u8bad 467 | p234 468 | aV\u57f9\u8bad\u673a\u6784,\u8bed\u8a00\u57f9\u8bad,\u82f1\u8bed\u57f9\u8bad 469 | p235 470 | aV\u57f9\u8bad\u673a\u6784,\u8bed\u8a00\u57f9\u8bad,\u6cd5\u8bed\u57f9\u8bad 471 | p236 472 | aV\u57f9\u8bad\u673a\u6784,\u8bed\u8a00\u57f9\u8bad,\u5fb7\u8bed\u57f9\u8bad 473 | p237 474 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,GRE\u57f9\u8bad 475 | p238 476 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,\u9ad8\u8003\u57f9\u8bad 477 | p239 478 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,\u8003\u7814\u57f9\u8bad 479 | p240 480 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,\u516c\u52a1\u5458\u8003\u8bd5\u57f9\u8bad 481 | p241 482 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,\u53f8\u6cd5\u8003\u8bd5\u57f9\u8bad 483 | p242 484 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,TOFEL\u57f9\u8bad 485 | p243 486 | aV\u57f9\u8bad\u673a\u6784,\u8003\u8bd5\u57f9\u8bad,\u56db\u516d\u7ea7\u57f9\u8bad 487 | p244 488 | aV\u57f9\u8bad\u673a\u6784,\u4f53\u80b2\u57f9\u8bad,\u4e52\u4e53\u7403\u57f9\u8bad 489 | p245 490 | aV\u57f9\u8bad\u673a\u6784,\u4f53\u80b2\u57f9\u8bad,\u7fbd\u6bdb\u7403\u57f9\u8bad 491 | p246 492 | aV\u57f9\u8bad\u673a\u6784,\u4f53\u80b2\u57f9\u8bad,\u7f51\u7403\u57f9\u8bad 493 | p247 494 | aV\u623f\u5730\u4ea7,\u4f4f\u5b85\u533a,\u5c0f\u533a 495 | p248 496 | aV\u623f\u5730\u4ea7,\u4f4f\u5b85\u533a,\u516c\u5bd3 497 | p249 498 | aV\u623f\u5730\u4ea7,\u4f4f\u5b85\u533a,\u522b\u5885 499 | p250 500 | aV\u884c\u653f\u533a\u5212,\u79d1\u6280\u56ed,\u8f6f\u4ef6\u56ed 501 | p251 502 | aV\u884c\u653f\u533a\u5212,\u5f00\u53d1\u533a,\u65c5\u6e38\u5f00\u53d1\u533a 503 | p252 504 | aV\u884c\u653f\u533a\u5212,\u5f00\u53d1\u533a,\u7ecf\u6d4e\u5f00\u53d1\u533a 505 | p253 506 | aV\u884c\u653f\u533a\u5212,\u5f00\u53d1\u533a,\u9ad8\u65b0\u6280\u672f\u5f00\u53d1\u533a 507 | p254 508 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u56fd\u52a1\u9662 509 | p255 510 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u7701\u653f\u5e9c 511 | p256 512 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u5e02\u653f\u5e9c 513 | p257 514 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u53bf\u653f\u5e9c 515 | p258 516 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u9547\u653f\u5e9c 517 | p259 518 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u8857\u9053\u529e\u4e8b\u5904 519 | p260 520 | aV\u653f\u5e9c\u673a\u6784,\u653f\u5e9c,\u653f\u5e9c\u9a7b\u5730\u529e\u4e8b\u5904 521 | p261 522 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u516c\u5b89\u5c40 523 | p262 524 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6d3e\u51fa\u6240 525 | p263 526 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u4ea4\u901a\u5c40 527 | p264 528 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u53f8\u6cd5\u5c40 529 | p265 530 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6d88\u9632\u5c40 531 | p266 532 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u5de5\u5546\u5c40 533 | p267 534 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u5730\u7a0e\u5c40 535 | p268 536 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u56fd\u7a0e\u5c40 537 | p269 538 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u8d22\u653f\u5c40 539 | p270 540 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6c11\u653f\u5c40 541 | p271 542 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u4ea4\u7ba1\u5c40 543 | p272 544 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u7535\u4fe1\u5c40 545 | p273 546 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6d77\u5173 547 | p274 548 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u98df\u54c1\u5c40 549 | p275 550 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u5730\u9707\u5c40 551 | p276 552 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u52b3\u52a8\u5c40 553 | p277 554 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6559\u80b2\u5c40 555 | p278 556 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6c14\u8c61\u5c40 557 | p279 558 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u7269\u4ef7\u5c40 559 | p280 560 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u70df\u8349\u4e13\u5356\u5c40 561 | p281 562 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u8d28\u76d1\u5c40 563 | p282 564 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u536b\u751f\u5c40 565 | p283 566 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u89c4\u5212\u5c40 567 | p284 568 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6c34\u5229\u5c40 569 | p285 570 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6587\u5316\u5c40 571 | p286 572 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u5ba1\u8ba1\u5c40 573 | p287 574 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u65c5\u6e38\u5c40 575 | p288 576 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u4f53\u80b2\u5c40 577 | p289 578 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u7cae\u98df\u5c40 579 | p290 580 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u623f\u7ba1\u6240 581 | p291 582 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u6863\u6848\u9986 583 | p292 584 | aV\u653f\u5e9c\u673a\u6784,\u673a\u5173\u5355\u4f4d,\u673a\u5173\u9a7b\u5730\u529e\u4e8b\u5904 585 | p293 586 | aV\u653f\u5e9c\u673a\u6784,\u6d89\u5916\u673a\u6784,\u5927\u4f7f\u9986 587 | p294 588 | aV\u653f\u5e9c\u673a\u6784,\u6d89\u5916\u673a\u6784,\u7b7e\u8bc1\u5904 589 | p295 590 | aV\u653f\u5e9c\u673a\u6784,\u798f\u5229\u673a\u6784,\u656c\u8001\u9662 591 | p296 592 | aV\u653f\u5e9c\u673a\u6784,\u798f\u5229\u673a\u6784,\u798f\u5229\u9662 593 | p297 594 | aV\u653f\u5e9c\u673a\u6784,\u6148\u5584\u673a\u6784,\u7ea2\u5341\u5b57\u4f1a 595 | p298 596 | aV\u653f\u5e9c\u673a\u6784,\u6148\u5584\u673a\u6784,\u6b8b\u75be\u4eba\u8054\u5408\u4f1a 597 | p299 598 | aV\u653f\u5e9c\u673a\u6784,\u6148\u5584\u673a\u6784,\u9752\u5c11\u5e74\u57fa\u91d1\u4f1a 599 | p300 600 | aV\u516c\u53f8\u4f01\u4e1a,IT\u4f01\u4e1a,\u8f6f\u4ef6\u516c\u53f8 601 | p301 602 | aV\u516c\u53f8\u4f01\u4e1a,IT\u4f01\u4e1a,\u4e92\u8054\u7f51\u516c\u53f8 603 | p302 604 | aV\u516c\u53f8\u4f01\u4e1a,\u4f20\u5a92\u516c\u53f8,\u5e7f\u64ad\u7535\u89c6\u516c\u53f8 605 | p303 606 | aV\u516c\u53f8\u4f01\u4e1a,\u4f20\u5a92\u516c\u53f8,\u62a5\u793e 607 | p304 608 | aV\u516c\u53f8\u4f01\u4e1a,\u4f20\u5a92\u516c\u53f8,\u6742\u5fd7\u793e 609 | p305 610 | aV\u516c\u53f8\u4f01\u4e1a,\u4f20\u5a92\u516c\u53f8,\u5e7f\u544a\u516c\u53f8 611 | p306 612 | aV\u516c\u53f8\u4f01\u4e1a,\u516c\u7528\u4e8b\u4e1a,\u81ea\u6765\u6c34\u516c\u53f8 613 | p307 614 | aV\u516c\u53f8\u4f01\u4e1a,\u516c\u7528\u4e8b\u4e1a,\u7535\u529b\u516c\u53f8 615 | p308 616 | aV\u516c\u53f8\u4f01\u4e1a,\u516c\u7528\u4e8b\u4e1a,\u71c3\u6c14\u516c\u53f8 617 | p309 618 | aV\u516c\u53f8\u4f01\u4e1a,\u623f\u5730\u4ea7\u516c\u53f8,\u623f\u5730\u4ea7\u5f00\u53d1\u516c\u53f8 619 | p310 620 | aV\u516c\u53f8\u4f01\u4e1a,\u623f\u5730\u4ea7\u516c\u53f8,\u7269\u4e1a\u7ba1\u7406\u516c\u53f8 621 | p311 622 | aV\u516c\u53f8\u4f01\u4e1a,\u623f\u5730\u4ea7\u516c\u53f8,\u552e\u697c\u5904 623 | p312 624 | aV\u516c\u53f8\u4f01\u4e1a,\u7269\u6d41\u516c\u53f8,\u8d27\u8fd0\u516c\u53f8 625 | p313 626 | aV\u516c\u53f8\u4f01\u4e1a,\u4e8b\u52a1\u6240,\u5f8b\u5e08\u4e8b\u52a1\u6240 627 | p314 628 | aV\u516c\u53f8\u4f01\u4e1a,\u4e8b\u52a1\u6240,\u4f1a\u8ba1\u4e8b\u52a1\u6240 629 | p315 630 | aV\u516c\u53f8\u4f01\u4e1a,\u4e8b\u52a1\u6240,\u5ba1\u8ba1\u4e8b\u52a1\u6240 631 | p316 632 | aV\u516c\u53f8\u4f01\u4e1a,\u54a8\u8be2\u516c\u53f8,\u7ba1\u7406\u54a8\u8be2\u516c\u53f8 633 | p317 634 | aV\u516c\u53f8\u4f01\u4e1a,\u54a8\u8be2\u516c\u53f8,\u6280\u672f\u54a8\u8be2\u516c\u53f8 635 | p318 636 | aV\u516c\u53f8\u4f01\u4e1a,\u54a8\u8be2\u516c\u53f8,\u5de5\u7a0b\u54a8\u8be2\u516c\u53f8 637 | p319 638 | aV\u516c\u53f8\u4f01\u4e1a,\u54a8\u8be2\u516c\u53f8,\u6295\u8d44\u54a8\u8be2\u516c\u53f8 639 | p320 640 | aV\u516c\u53f8\u4f01\u4e1a,\u54a8\u8be2\u516c\u53f8,\u6559\u80b2\u54a8\u8be2\u516c\u53f8 641 | p321 642 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u7eba\u7ec7\u516c\u53f8 643 | p322 644 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u98df\u54c1\u516c\u53f8 645 | p323 646 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u5236\u836f\u516c\u53f8 647 | p324 648 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u901a\u8baf\u8bbe\u5907\u5236\u9020\u516c\u53f8 649 | p325 650 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u8ba1\u7b97\u673a\u5236\u9020\u516c\u53f8 651 | p326 652 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u5bb6\u7535\u5236\u9020\u516c\u53f8 653 | p327 654 | aV\u516c\u53f8\u4f01\u4e1a,\u5236\u9020\u4e1a,\u6c7d\u8f66\u5236\u9020\u516c\u53f8 655 | p328 656 | a. -------------------------------------------------------------------------------- /slack_bot/plugins/data/cityid: -------------------------------------------------------------------------------- 1 | }q(X平定X 101100303X茂港X 101282006X漳州X 101230601X阜南X 101220802X萧县X 101220705X涪陵X 101041400X河南X 101150304X平安X 101150208X奉节X 101041900X安陆X 101200402X闽清X 101230102X新田X 101251409X谷城X 101200207X 张家港X 101190403X彭水X 101043200X沙县X 101230808X马山X 101300106X芷江X 101251210X香河X 101090604X都安X 101301210X克东X 101050209X陆丰X 101282103X恩施X 101201001X水富X 101291011X沂南X 101120903X 巴里坤X 101131203X贺兰X 101170104X新界X 101320103X赫章X 101260702X崇阳X 101200704X沽源X 101090305X荣昌X 101042700X连山X 101281304X安阳X 101180201X沅江X 101250705X改则X 101140702X 小渠子X 101130103X苏仙X 101250512X桂阳X 101250502X黑河X 101050601X德昌X 101271605X景谷X 101290902X五常X 101050112X乐陵X 101120406X万州X 101041300X永春X 101230504X渑池X 101181703X厦门X 101230201X铁力X 101050804X 东乌旗X 101080909X宿州X 101220701X安国X 101090220X那仁宝力格X 101080809X永昌X 101160602X安图X 101060303X青川X 101272103X青州X 101120602X富裕X 101050205X乌当X 101260104X揭阳X 101281901X河口X 101290313X宁都X 101240707X 加格达奇X 101050708X武山X 101160906X 正蓝旗X 101080914X西沙X 101310217X渭源X 101160204X绥德X 101110410X常山X 101211002X宝兴X 101271708X保康X 101200203X泽当X 101140308X贵德X 101150404X遂溪X 101281007X 乌前旗X 101080804X石门X 101250607X洛浦X 101131305X彭泽X 101240208X潮阳X 101280502X夏邑X 101181008X渝北X 101040700X开封X 101180801X商南X 101110607X横峰X 101240310X 浪卡子X 101140305X古县X 101100717X城固X 101110806X鹿邑X 101181409X淮南X 101220401X泽库X 101150303X杞县X 101180802X兰西X 101050507X梁山X 101120709X秀山X 101043600X罗山X 101180603X定兴X 101090223X榆中X 101160104X麻阳X 101251208X关岭X 101260306X六安X 101221501X腾冲X 101290506X遂平X 101181603X临潼X 101110103X金湾X 101280703X金乡X 101120706X 小店区X 101100107X 五台县X 101101003X淮北X 101221201X莒南X 101120902X镇平X 101180707X金湖X 101190902X温州X 101210701X武川X 101080107X 赫山区X 101250701X常州X 101191101X屏南X 101230309X同江X 101050406X鹰潭X 101241101X 丹江口X 101201107X磐石X 101060205X开鲁X 101080506X莒县X 101121503X 特克斯X 101131008X成县X 101161002X荆州X 101200801X涟源X 101250806X淄川X 101120302X 浦东南汇X 101020600X林芝X 101140401X金溪X 101240405X肃南X 101160702X新和X 101130805X镇康X 101291108X固始X 101180608X民权X 101181004X 瓦房店X 101070202X 新左旗X 101081008X 乌鲁木齐X 101130101X尤溪X 101230809X连州X 101281303X田阳X 101301003X宁冈X 101240613X台东X 101340204X巴音布鲁克X 101130610X塔中X 101130613X义乌X 101210904X昌图X 101071103X肃北X 101160806X叶县X 101180505X保德X 101101011X大荔X 101110504X青岛X 101120201X定日X 101140205X台中X 101340401X兴隆X 101090404X仙桃X 101201601X印江X 101260607X韶山X 101250202X通州X 101190509X刚察X 101150806X叶城X 101130906X中江X 101272002X赞皇X 101090109X政和X 101230909X保山X 101290501X下陆X 101200605X福泉X 101260405X大悟X 101200404X双牌X 101251404X江山X 101211005X帕里X 101140207X蕉岭X 101280403X三峡X 101200911X洪雅X 101271504X平邑X 101120908X高要X 101280908X前郭X 101060803X大埔X 101280404X五峰X 101200906X临城X 101090902X策勒X 101131303X虞城X 101181005X 喀喇沁X 101080611X 鄂伦春旗X 101081005X乡宁X 101100712X喜德X 101271613X宾川X 101290205X泽州X 101100606X蛟河X 101060204X福清X 101230111X清新X 101281308X潮州X 101281501X柳林X 101101105X丹凤X 101110606X如皋X 101190503X玛沁X 101150508X武鸣X 101300108X延寿X 101050110X伊吾X 101131204X北流X 101300903X香港X 101320101X德化X 101230505X焦作X 101181101X商都X 101080404X灌南X 101191005X含山X 101221604X松桃X 101260611X桑植X 101251102X胶州X 101120205X将乐X 101230805X武平X 101230704X舒兰X 101060202X南宁X 101300101X泾县X 101221402X敖汉X 101080614X孝感X 101200401X南安X 101230506X平湖X 101210305X石城X 101240708X逊克X 101050604X海西X 101150701X阳朔X 101300510X洋县X 101110805X镇巴X 101110811X延安X 101110300X南宫X 101090916X青龙X 101091102X邵阳X 101250901X鹤壁X 101181201X全椒X 101221105X泗水X 101120708X崂山X 101120202X盘山X 101071303X冕宁X 101271614X三明X 101230801X萝北X 101051203X峨边X 101271406X礼县X 101161007X卫辉X 101180305X弥渡X 101290206X昔阳X 101100406X晋中X 101100401X高青X 101120304X宁南X 101271608X铜梁X 101042800X察雅X 101140510X定南X 101240715X宜昌X 101200901X万年X 101240306X蓝山X 101251408X三门X 101210604X涟水X 101190905X内乡X 101180706X烟台X 101120501X巩义X 101180102X梁平X 101042300X襄阳X 101200201X罗平X 101290407X南沙X 101310220X石阡X 101260608X桐柏X 101180712X 麦盖提X 101130904X上高X 101240505X肇源X 101050904X楚雄X 101290801X塔河X 101050702X 太仆寺X 101080911X信阳X 101180601X 科右中旗X 101081103X内丘X 101090904X宁化X 101230802X 若尔盖X 101271912X察隅X 101140404X茅箭X 101201108X太原X 101100101X潜山X 101220604X原平X 101101015X泸定X 101271803X 雅布赖X 101081210X广宁X 101280902X 诺尔公X 101081209X 克什克腾X 101080608X德惠X 101060103X 阿尔山X 101081102X武强X 101090804X台江X 101260510X翁源X 101280204X聊城X 101121701X福海X 101131407X益阳X 101250700X扎囊X 101140303X漳平X 101230707X青阳X 101221703X钦州X 101301101X汤原X 101050402X西昌X 101271610X通山X 101200706X平塘X 101260409X肇东X 101050502X福贡X 101291203X 张家界X 101251101X夏县X 101100812X云梦X 101200403X庆城X 101160409X马龙X 101290405X平顺X 101100506X固安X 101090602X同仁X 101150305X济宁X 101120701X泗洪X 101191304X噶尔X 101140707X宁县X 101160407X昭通X 101291001X田林X 101301012X来凤X 101201007X城口X 101041600X明溪X 101230807X招远X 101120506X德钦X 101291302X瑞安X 101210705X宜城X 101200205X 西塞山X 101200606X潼南X 101042100X 承德县X 101090403X屯溪X 101221003X阳曲X 101100103X青神X 101271506X锦州X 101070701X榆次X 101100402X松滋X 101200807X 成山头X 101121305X 大兴安岭X 101050701X城厢X 101230407X锡山X 101190204X绍兴X 101210501X松溪X 101230908X巍山X 101290208X天全X 101271706X赤坎X 101281006X 氹仔岛X 101330102X新兴X 101281403X周村X 101120305X剑川X 101290209X桦川X 101050404X麻城X 101200503X太和X 101220806X仙游X 101230402X蓬莱X 101120504X 梅河口X 101060502X舞钢X 101180506X淮阳X 101181404X海伦X 101050504X建瓯X 101230910X高明X 101280804X亳州X 101220901X温县X 101181107X澧县X 101250605X班戈X 101140604X三穗X 101260509X抚远X 101050403X鄢陵X 101180402X邢台X 101090901X余江X 101241102X阜城X 101090809X南川X 101040400X镇安X 101110605X利辛X 101220903X鹤庆X 101290211X依安X 101050206X自贡X 101270301X简阳X 101271304X 科右前旗X 101081109X延川X 101110302X惠东X 101280304X巴雅尔吐胡硕X 101080511X镇远X 101260504X东海X 101191002X平罗X 101170203X星子X 101240209X辛集X 101090114X尉氏X 101180803X新野X 101180709X凤山X 101301208X赤城X 101090313X东辽X 101060703X绥宁X 101250906X凤庆X 101291105X梧州X 101300601X武城X 101120402X沭阳X 101191302X雷州X 101281003X丽江X 101291401X阳春X 101281802X江孜X 101140206X乳源X 101280202X分宜X 101241002X汝南X 101181605X临邑X 101120403X环江X 101301205X丰宁X 101090408X榆社X 101100403X清苑X 101090224X汉沽X 101030800X黎城X 101100502X 岳普湖X 101130909X泸州X 101271001X东乡X 101240411X轮台X 101130602X涉县X 101091006X平度X 101120208X齐河X 101120405X府谷X 101110402X任县X 101090918X鄯善X 101130504X沙雅X 101130806X安化X 101250704X 扎赉特X 101081105X五寨X 101101014X铜仁X 101260601X丹阳X 101190302X索县X 101140606X拜泉X 101050207X阜阳X 101220801X东丰X 101060702X凉山X 101271601X东丽X 101030400X沁水X 101100602X东港X 101070604X甘谷X 101160905X 小二沟X 101081002X甘泉X 101110308X尚义X 101090306X巧家X 101291006X 驻马店X 101181601X蓬江X 101281107X商城X 101180609X松潘X 101271905X 巴林右旗X 101080606X甘洛X 101271616X新邵X 101250904X陇县X 101110911X沈阳X 101070101X莱西X 101120207X文山X 101290601X嘉荫X 101050805X濉溪X 101221202X海东X 101150201X 阿勒泰X 101131401X锦屏X 101260515X封开X 101280907X新郑X 101180106X东源X 101281206X保定X 101090201X保亭X 101310214X江安X 101271105X孝昌X 101200407X余庆X 101260213X江宁X 101190104X朔州X 101100901X正安X 101260211X长葛X 101180404X宜阳X 101180904X安县X 101270404X龙陵X 101290503X正宁X 101160406X赣榆X 101191003X晋州X 101090116X北海X 101301301X正定X 101090103X南岳X 101250409X 达茂旗X 101080206X 阿克塞X 101160804X新化X 101250805X潼关X 101110503X曲江X 101280209X天水X 101160901X恩平X 101281105X乐都X 101150202X宁陕X 101110710X汤阴X 101180202X利津X 101121204X开阳X 101260106X云浮X 101281401X临县X 101101102X峰峰X 101091002X崇左X 101300201X淇县X 101181203X宣威X 101290409X北京X 101010100X 大佘太X 101080805X炎陵X 101250306X 杜尔伯特X 101050905X宁陵X 101181007X定陶X 101121005X鹤山X 101281108X两当X 101161009X丽水X 101210801X 曹妃甸X 101090512X柘城X 101181006X唐海X 101090509X平川X 101161304X乾县X 101110207X玉树X 101150601X徽县X 101161008X双阳X 101060106X北仑X 101210410X当涂X 101220502X邗江X 101190606X天池X 101130109X达日X 101150504X建阳X 101230907X曲水X 101140106X海淀X 101010200X 满洲里X 101081010X内江X 101271201X酒泉X 101160801X宁阳X 101120806X张北X 101090303X 香格里拉X 101291301X安吉X 101210203X盂县X 101100302X太白X 101110909X汕尾X 101282101X伊通X 101060405X汾西X 101100709X迁安X 101090511X临猗X 101100802X华蓥X 101270805X惠水X 101260406X措勤X 101140710X 防城港X 101301401X陵县X 101120404X马关X 101290603X松江X 101020900X 工布江达X 101140405X新县X 101180605X河间X 101090714X武威X 101160501X惠民X 101121105X汪清X 101060304X宁国X 101221404X银川X 101170101X会同X 101251206X黄梅X 101200508X武穴X 101200509X永胜X 101291402X 调兵山X 101071105X会理X 101271606X墨脱X 101140407X景东X 101290903X万宁X 101310215X曲沃X 101100702X兴文X 101271110X芦溪X 101240905X灯塔X 101071003X罗定X 101281402X鹤峰X 101201006X顺昌X 101230902X昆山X 101190404X 土左旗X 101080102X光山X 101180604X舞阳X 101181503X宝坻X 101030300X 鄂前旗X 101080705X富源X 101290404X兰溪X 101210903X讷河X 101050202X红原X 101271913X衡水X 101090801X榆树X 101060105X大新X 101300205X汉源X 101271704X 井冈山X 101240608X景宁X 101210809X武宣X 101300405X于田X 101131307X平鲁X 101100902X天津X 101030100X奈曼X 101080508X方城X 101180703X奉新X 101240507X武定X 101290807X常宁X 101250406X 淮安区X 101190908X武宁X 101240204X南平X 101230901X北辰X 101030600X武安X 101091016X高县X 101271107X泾阳X 101110205X吉林X 101060201X陆川X 101300905X江夏X 101200105X集宁X 101080401X金秀X 101300403X集安X 101060505X鄂州X 101200301X丘北X 101290606X文安X 101090606X蓟县X 101031400X怀来X 101090311X阳新X 101200603X深泽X 101090108X射阳X 101190705X慈溪X 101210403X叙永X 101271005X巢湖X 101221601X多县X 101150507X 苏左旗X 101080906X歙县X 101221006X凤翔X 101110906X威县X 101090913X卓资X 101080402X唐河X 101180710X望谟X 101260905X宁城X 101080613X缙云X 101210804X丁青X 101140502X 杭锦后旗X 101080810X彰化X 101340403X长春X 101060101X 青铜峡X 101170306X古田X 101230302X织金X 101260707X双城X 101050102X巫溪X 101041800X 蔡家湖X 101130409X梅县X 101280409X法库X 101070105X汉中X 101110801X昌邑X 101120606X 武夷山X 101230905X 西乌旗X 101080910X望江X 101220607X宜君X 101111003X淅川X 101180708X沁源X 101100510X灌阳X 101300509X怀柔X 101010500X微山X 101120703X昌都X 101140501X波密X 101140402X 大柴旦X 101150713X沧州X 101090701X平山X 101090111X康定X 101271802X 辽阳县X 101071002X纳溪X 101271007X浦口X 101190107X石狮X 101230510X蒙山X 101300605X呈贡X 101290108X比如X 101140609X岢岚X 101101013X 马坡岭X 101250104X通城X 101200705X平原X 101120408X奎屯X 101131011X 台儿庄X 101121404X清远X 101281301X紫金X 101281202X同安X 101230202X龙井X 101060307X莲花X 101240902X 镶黄旗X 101080912X大理X 101290201X安平X 101090806X广昌X 101240402X昭平X 101300702X桂平X 101300802X千阳X 101110903X青田X 101210805X遂昌X 101210802X兰考X 101180805X鹤岗X 101051201X正阳X 101181610X绵竹X 101272005X蒲城X 101110507X章丘X 101120104X余干X 101240305X得荣X 101271818X宜宾X 101271101X江阴X 101190202X钟山X 101300704X郓城X 101121003X罗田X 101200504X 邵阳县X 101250910X镇坪X 101110709X罗甸X 101260408X乐平X 101240802X榆林X 101110401X子长X 101110303X全南X 101240713X魏县X 101091014X夷陵X 101200912X岑巩X 101260502X永丰X 101240606X民乐X 101160703X永清X 101090603X 老河口X 101200206X龙游X 101211004X都匀X 101260401X 三门峡X 101181701X陵川X 101100604X民丰X 101131306X托里X 101131105X称多X 101150602X四会X 101280903X雷波X 101271617X沿河X 101260609X藁城X 101090115X阜宁X 101190704X晴隆X 101260902X公安X 101200803X靖江X 101191205X玛曲X 101161206X花溪X 101260103X奇台X 101130406X泊头X 101090711X晋江X 101230509X安康X 101110701X 莫力达瓦X 101081004X嵩明X 101290110X澄海X 101280503X保靖X 101251502X灵丘X 101100206X交口X 101101108X辉南X 101060504X 图里河X 101081016X 五指山X 101310222X 东西湖X 101200106X凤冈X 101260206X随州X 101201301X高安X 101240508X门源X 101150802X裕民X 101131102X和林X 101080104X彰武X 101070902X万安X 101240609X宜章X 101250504X 龙泉驿X 101270102X金昌X 101160601X上栗X 101240903X永修X 101240206X拉孜X 101140202X芮城X 101100811X肃宁X 101090706X会宁X 101161303X横山X 101110407X遵义X 101260201X昌平X 101010700X双江X 101291104X利川X 101201002X冀州X 101090810X南通X 101190501X凤凰X 101251505X柯坪X 101130808X馆陶X 101091013X祁门X 101221004X涞水X 101090222X贵阳X 101260101X丰顺X 101280406X 博克图X 101080916X溆浦X 101251211X铅山X 101240311X石岛X 101121306X苍梧X 101300604X石台X 101221705X巴楚X 101130908X浈江X 101280210X梓潼X 101270405X沧县X 101090716X宜黄X 101240407X 都江堰X 101270111X江城X 101290907X稷山X 101100803X扬州X 101190601X密山X 101051103X敦化X 101060302X建始X 101201003X柳河X 101060503X南部X 101270502X曲阜X 101120710X平利X 101110707X澳门X 101330101X荆门X 101201401X南澳X 101280504X成安X 101091004X禄丰X 101290808X安龙X 101260907X淮安X 101190901X易县X 101090212X枝江X 101200910X玉林X 101300901X漠河X 101050703X镇雄X 101291004X美姑X 101271618X博湖X 101130612X蓬安X 101270504X博乐X 101131601X焉耆X 101130607X明光X 101221103X辉县X 101180304X昌黎X 101091103X景洪X 101291601X岫岩X 101070303X浦北X 101301102X 正镶白旗X 101080913X普格X 101271609X章党X 101070404X炉霍X 101271808X清丰X 101181304X鄞州X 101210411X南华X 101290806X寻甸X 101290104X 乌拉盖X 101080917X 海力素X 101080808X垫江X 101042200X华坪X 101291403X岳西X 101220608X尼玛X 101140602X江陵X 101200802X武隆X 101043100X新都X 101270103X 普兰店X 101070204X龙泉X 101210803X 马尔康X 101271910X澄迈X 101310204X 舍伯吐X 101080502X娄底X 101250801X揭西X 101281902X平凉X 101160301X大方X 101260705X永泰X 101230107X农安X 101060102X蓝田X 101110104X湘潭X 101250201X郫县X 101270107X清涧X 101110412X石楼X 101101106X梅州X 101280401X迭部X 101161205X邱县X 101091010X林西X 101080607X漯河X 101181501X孟州X 101181108X石龙X 101180508X乐山X 101271401X武胜X 101270803X新龙X 101271809X清流X 101230803X尖扎X 101150302X桐乡X 101210304X 莫索湾X 101130303X沐川X 101271405X重庆X 101040100X东至X 101221702X南召X 101180702X文登X 101121302X襄汾X 101100707X祁阳X 101251402X托县X 101080103X郯城X 101120906X都兰X 101150902X 五大连池X 101050605X桓台X 101120307X果洛X 101150501X龙江X 101050203X鲁甸X 101291002X长武X 101110209X绥化X 101050501X桦南X 101050405X安岳X 101271302X东山X 101230608X衡阳X 101250401X武陟X 101181103X南县X 101250702X金华X 101210901X偃师X 101180908X承德X 101090402X日照X 101121501X 四子王旗X 101080411X册亨X 101260908X凤县X 101110910X屯昌X 101310210X湘东X 101240906X闽侯X 101230103X理县X 101271903X台南X 101340203X郸城X 101181408X上犹X 101240703X原阳X 101180303X冷湖X 101150207X金堂X 101270105X 拐子湖X 101081204X靖边X 101110406X济南X 101120101X伊川X 101180906X诸暨X 101210502X江达X 101140509X 石家庄X 101090101X 锡林浩特X 101080901X泸县X 101271003X乌鲁木齐牧试站X 101130108X德格X 101271810X靖远X 101161302X凤台X 101220402X新干X 101240604X新平X 101290706X 太华山X 101290113X茂县X 101271904X辽源X 101060701X 齐齐哈尔X 101050201X湘乡X 101250203X安庆X 101220601X清河X 101090914X芒康X 101140506X双峰X 101250802X隆林X 101301008X盱眙X 101190903X崇州X 101270114X东胜X 101080713X万盛X 101040600X西丰X 101071104X方山X 101101107X温岭X 101210607X和政X 101161105X黑水X 101271909X勃利X 101051003X阳西X 101281804X墨江X 101290906X盘锦X 101071301X漳浦X 101230606X江门X 101281101X 胡尔勒X 101081104X资兴X 101250507X砚山X 101290605X庐山X 101240203X定安X 101310209X宁安X 101050306X新绛X 101100806X陇川X 101291503X 格尔木X 101150901X柏乡X 101090905X句容X 101190304X丰镇X 101080412X连城X 101230703X 阿克陶X 101131503X辽中X 101070103X西乡X 101110807X贵定X 101260402X永济X 101100810X宣城X 101221401X桃园X 101340102X新建X 101240102X广元X 101272101X建宁X 101230806X赤壁X 101200702X措美X 101140312X固原X 101170401X清水X 101160903X吴桥X 101090708X敦煌X 101160808X平遥X 101100410X宾县X 101050105X贵南X 101150407X广河X 101161104X盐池X 101170303X溧阳X 101191102X延长X 101110301X武邑X 101090803X合阳X 101110509X蒲县X 101100708X 朱日和X 101080908X龙胜X 101300503X海林X 101050302X抚松X 101060906X胶南X 101120206X万全X 101090310X 巴林左旗X 101080605X蒙城X 101220904X台州X 101210601X那坡X 101301002X邯郸X 101091001X武都X 101161001X莎车X 101130905X水城X 101260801X扶余X 101060805X呼中X 101050705X桐梓X 101260207X花莲X 101340405X通川X 101270607X 牙克石X 101081011X 八里罕X 101080612X鹿泉X 101090118X潢川X 101180607X灵武X 101170103X攸县X 101250302X无锡X 101190201X甘孜X 101271801u(X温宿X 101130803X大丰X 101190708X镇原X 101160408X徐州X 101190801X广水X 101201302X石首X 101200804X临夏X 101161101X廊坊X 101090601X大港X 101031200X黎平X 101260513X 阿鲁旗X 101080603X泾川X 101160302X昌宁X 101290505X酉阳X 101043400X浦东X 101021300X 察布查尔X 101131002X介休X 101100412X浮山X 101100715X德保X 101301004X运城X 101100801X松阳X 101210808X华亭X 101160305X平坝X 101260304X阳东X 101281803X长海X 101070206X苗栗X 101340402X通化X 101060501X 呼图壁X 101130402X什邡X 101272004X诏安X 101230607X云林X 101340406X 吉兰太X 101081205X合浦X 101301302X吉水X 101240603X西林X 101301009X贺州X 101300701X行唐X 101090105X普洱X 101290901X丹徒X 101190305X青冈X 101050508X资阳X 101271301X长治X 101100501X湛江X 101281001X江华X 101251410X富蕴X 101131408X临颍X 101181502X兴义X 101260901X伊宁X 101131001X交城X 101101113X五原X 101080802X阜平X 101090203X长沙X 101250101X三台X 101270402X曲松X 101140314X固镇X 101220203X丰县X 101190803X汕头X 101280501X常熟X 101190402X武冈X 101250908X阳信X 101121104X潮安X 101281503X兴业X 101300906X三原X 101110201X临西X 101090915X长阳X 101200908X长泰X 101230602X潞城X 101100504X盐津X 101291009X东明X 101121004X丰台X 101010900X大连X 101070201X舟曲X 101161204X卢龙X 101091105X珲春X 101060308X日土X 101140708X上饶X 101240301X钟祥X 101201402X乐安X 101240403X萍乡X 101240901X沙市X 101201406X田东X 101301006X广汉X 101272003X定州X 101090219X麻章X 101281010X留坝X 101110804X隆安X 101300105X萨迦X 101140213X高唐X 101121704X兴仁X 101260903X珙县X 101271108X 嘉峪关X 101161401X白云X 101260102X平阳X 101210704X绥棱X 101050510X绵阳X 101270401X平阴X 101120105X吴中X 101190405X泰来X 101050210X辰溪X 101251204X彝良X 101291003X江口X 101260602X蒙阴X 101120907X磁县X 101091007X安定X 101160208X丰南X 101090502X惠来X 101281904X天柱X 101260514X南和X 101090907X旌德X 101221403X安宁X 101290112X 七台河X 101051002X小河X 101260109X平陆X 101100813X阜康X 101130404X 扎鲁特X 101080509X宝山X 101020300X漾濞X 101290203X项城X 101181407X海晏X 101150804X 牡丹江X 101050301X大余X 101240705X五华X 101280408X蕲春X 101200507X杨凌X 101111101X 狮泉河X 101140704X磐安X 101210908X长汀X 101230702X安塞X 101110307X华池X 101160404X宜川X 101110304X南靖X 101230603X阳泉X 101100301X开化X 101211003X临安X 101210107X咸丰X 101201004X贞丰X 101260904X神木X 101110403X安顺X 101260301X三都X 101260411X嫩江X 101050602X山丹X 101160706X兴海X 101150406X武功X 101110206X湖州X 101210201X康乐X 101161102X元阳X 101290305X峡江X 101240605X象州X 101300404X 白杨沟X 101130110X喀什X 101130901X横县X 101300104X瑞金X 101240709X太康X 101181403X莱芜X 101121601X 苏右旗X 101080907X饶河X 101051304X桑日X 101140310X济阳X 101120106X房县X 101201106X屏山X 101271111X固阳X 101080205X鄱阳X 101240302X漳县X 101160206X渭南X 101110501X丰都X 101043000X铁山X 101200604X乾安X 101060802X濮阳X 101181301X务川X 101260212X醴陵X 101250303X阳江X 101281801X 武陵源X 101251104X玉溪X 101290701X南城X 101240408X九江X 101240201X广丰X 101240313X新宁X 101250907X乌兰X 101150709X和县X 101221605X静乐X 101101012X新安X 101180902X开原X 101071102X定结X 101140212X江都X 101190605X顺义X 101010400X 孪井滩X 101081212X铁岭X 101071101X从江X 101260517X于都X 101240710X开县X 101041500X巴东X 101201008X怀仁X 101100906X 阿左旗X 101081201X遂宁X 101270701X青县X 101090702X株洲X 101250301X新密X 101180105X珠海X 101280701X 库尔勒X 101130601X凉城X 101080407X长岛X 101120503X盐亭X 101270403X丹巴X 101271804X盐源X 101271604X滕州X 101121405X 霍尔果斯X 101131010X吴起X 101110312X聂荣X 101140607X阳谷X 101121703X红安X 101200502X浦江X 101210902X巴中X 101270901X范县X 101181305X 白碱滩X 101130203X信宜X 101282005X虎林X 101051102X白水X 101110505X陕县X 101181706X永宁X 101170102X大足X 101042600X响水X 101190702X鹿寨X 101300304X韶关X 101280201X淄博X 101120301X平和X 101230604X永定X 101230706X綦江X 101043300X景县X 101090808X南陵X 101220304X平武X 101270407X黎川X 101240410X德江X 101260610X岳池X 101270802X宣化X 101090302X祁县X 101100409X盘县X 101260804X威宁X 101260704X安多X 101140605X蚌埠X 101220201X徐闻X 101281004X洪家X 101210609X连南X 101281302X 鄂托克X 101080708X兖州X 101120705X凤城X 101070602X肥城X 101120804X琼结X 101140313X同德X 101150408X文县X 101161003X崇礼X 101090314X东营X 101121201X南阳X 101180701X高淳X 101190103X宁强X 101110809X 乌后旗X 101080807X扶沟X 101181402X合川X 101040300X南雄X 101280207X白河X 101110708X耒阳X 101250408X郧西X 101201103X大洼X 101071302X滑县X 101180203X长丰X 101220102X象山X 101210406X易门X 101290707X名山X 101271702X纳雍X 101260706X庆安X 101050509X昂仁X 101140211X仲巴X 101140208X南康X 101240704X余杭X 101210106X 察右前旗X 101080408X白沙X 101310207X满城X 101090202X剑阁X 101272104X宝鸡X 101110901X 本溪县X 101070502X长清X 101120102X奉贤X 101021000X吴江X 101190407X定远X 101221104X萧山X 101210102X 秦皇岛X 101091101X永兴X 101250510X盐边X 101270204X宜兴X 101190203X启东X 101190507X涞源X 101090209X安福X 101240612X建德X 101210105X洛扎X 101140311X赤峰X 101080601X 尖草坪区X 101100106X全州X 101300508X宜兰X 101340104X南皮X 101090707X汶上X 101120707X新竹X 101340103X郑州X 101180101X紫阳X 101110702X 路环岛X 101330103X宁德X 101230301X进贤X 101240105X竹溪X 101201102X达县X 101270608X当阳X 101200907X肇州X 101050903X南充X 101270501X孟村X 101090710X繁峙X 101101009X青浦X 101020800X溧水X 101190102X 墨竹工卡X 101140108X黄南X 101150301X富顺X 101270302X北碚X 101040800X海宁X 101210303X嘉义X 101340202X加查X 101140304X 石河子X 101130301X海安X 101190502X阿城X 101050104X新蔡X 101181608X隆尧X 101090906X洞口X 101250903X武清X 101030200X黑山X 101070705X三亚X 101310201X独山X 101260410X 聂拉木X 101140204X连江X 101230105X兴化X 101191202X德清X 101210204X洛川X 101110309X茌平X 101121705X崇明X 101021100X鸡西X 101051101X邛崃X 101270113X宣汉X 101270602X眉县X 101110908X颍上X 101220803X通辽X 101080501X彭山X 101271503X宿迁X 101191301X延吉X 101060301X津南X 101031000X景泰X 101161305X茂名X 101282001X盐都X 101190709X六库X 101291206X阿坝X 101271901X永嘉X 101210708X 布尔津X 101131406X 宝国吐X 101080615X周至X 101110105X普兰X 101140705X伊春X 101050801X白玉X 101271811X 伊克乌素X 101080707X小金X 101271908X子洲X 101110409X新洲X 101200104X广灵X 101100205X长兴X 101210202X抚宁X 101091104X江源X 101060907X永登X 101160103X和平X 101281204X东宁X 101050307X周口X 101181401X成都X 101270101X东安X 101251403X兴县X 101101103X 满都拉X 101080203X文水X 101101112X林口X 101050304X邹城X 101120711X灵石X 101100411X武江X 101280211X云安X 101281406X武汉X 101200101X池州X 101221701X孙吴X 101050603X乐昌X 101280205X当雄X 101140102X襄城X 101180403X莱州X 101120502X竹山X 101201105X余姚X 101210404X宿豫X 101191305X罗江X 101272006X 谢通门X 101140214X电白X 101282004X九台X 101060104X朝阳X 101071201X龙海X 101230605X惠安X 101230508X西和X 101161006X大厂X 101090607X 弓长岭X 101071004X和龙X 101060305X绥中X 101071403X无棣X 101121103X金平X 101290312X阳原X 101090308X化隆X 101150205X邻水X 101270804X柳江X 101300305X弋阳X 101240309X安乡X 101250602X 北戴河X 101091106X桃江X 101250703X泌阳X 101181606X泰安X 101120801X开江X 101270603X三水X 101280802X永城X 101181009X襄垣X 101100505X天等X 101300202X贵港X 101300801X沂源X 101120306X高台X 101160705X 额济纳X 101081203X寻乌X 101240716X涵江X 101230404X绥滨X 101051202X融安X 101300306X嵊泗X 101211102X奉化X 101210405X图们X 101060309X泸西X 101290311X邹平X 101121107X岱山X 101211104X大化X 101301211X瑞丽X 101291506X勉县X 101110803X 南昌县X 101240103X孝义X 101101110X卢氏X 101181704X陶乐X 101170204X赣州X 101240701X西吉X 101170402X宜良X 101290106X中方X 101251212X凯里X 101260501X巨鹿X 101090909X青河X 101131409X上街X 101180108X肥西X 101220104X桂林X 101300501X华县X 101110502X彭州X 101270112X渠县X 101270605X通渭X 101160202X衢江X 101211006X 察右中旗X 101080409X呼玛X 101050704X北票X 101071205X博白X 101300902X梁河X 101291507X集贤X 101051302X贵溪X 101241103X包头X 101080201X永靖X 101161103X从化X 101280103X米林X 101140403X五河X 101220204X德兴X 101240307X玉屏X 101260603X高邑X 101090107X高邮X 101190604X睢宁X 101190806X永和X 101100703X玉山X 101240312X万载X 101240504X牟平X 101120509X黄山风景区X 101221008X中山X 101281701X 九华山X 101221704X澄城X 101110508X廉江X 101281005X克山X 101050208X营山X 101270503X镇赉X 101060604X镇海X 101210412X汉寿X 101250604X江永X 101251407X三河X 101090609X休宁X 101221007X苍南X 101210709X静海X 101030900X仪陇X 101270505X民和X 101150203X永仁X 101290810X道县X 101251405X新林X 101050706X三江X 101300308X清镇X 101260108X石林X 101290107X临朐X 101120604X乌海X 101080301X右玉X 101100904X岐山X 101110905X夹江X 101271404X永新X 101240607X福山X 101120508X永吉X 101060203X姜堰X 101191204X泰顺X 101210702X 梁子湖X 101200302X大通X 101150102X常德X 101250601X十堰X 101201101X 石嘴山X 101170201X通海X 101290704X 呼和浩特X 101080101X洱源X 101290210X 乌兰浩特X 101081101X合山X 101300406X黄冈X 101200501X 杭锦旗X 101080709X武乡X 101100507X柳城X 101300302X望奎X 101050506X开远X 101290307X同心X 101170302X江油X 101270408X柘荣X 101230307X石柱X 101042500X椒江X 101210611X金川X 101271907X西青X 101030500X金州X 101070203X吉利X 101180911X武义X 101210906X大冶X 101200602X宜都X 101200909X呼兰X 101050103X丹寨X 101260508X北安X 101050606X怀化X 101251201X永善X 101291008X仁化X 101280206X昌吉X 101130401X浏阳X 101250103X新昌X 101210504X黔江X 101041100X 乌审旗X 101080710X邓州X 101180711X禹城X 101120411X江海X 101281109X临桂X 101300505X修武X 101181102X福州X 101230101X绥江X 101291007X丰润X 101090503X抚顺X 101070401X江浦X 101190106X桐城X 101220609X湘阴X 101251003X金山X 101020700X滦平X 101090406X山南X 101140301X镇江X 101190301X潜江X 101201701X新晃X 101251209X 海拉尔X 101081001X佛坪X 101110808X垦利X 101121203X嘉善X 101210302X洮南X 101060602X西盟X 101290909X龙门X 101280305X天祝X 101160505X吉县X 101100706X 双鸭山X 101051301X循化X 101150206X武进X 101191104X大邑X 101270108X涡阳X 101220902X苏州X 101190401X蔚县X 101090307X江津X 101040500X通河X 101050108X米易X 101270203X巴南X 101040900X师宗X 101290406X镇沅X 101290911X通江X 101270902X增城X 101280104X遂川X 101240610X桃源X 101250603X台山X 101281106X隆德X 101170403X 葫芦岛X 101071401X沾化X 101121106X确山X 101181609X灵璧X 101220703X耀县X 101111002X大兴X 101011100X大关X 101291010X辽阳X 101071001X万源X 101270606X蒲江X 101270109X洪江X 101251213X罗源X 101230104X康县X 101161005X 张家川X 101160907X薛城X 101121402X 阿拉山口X 101131606X肇庆X 101280901X广南X 101290607X安新X 101090211X雅江X 101271806X岗巴X 101140216X靖西X 101301005X马边X 101271407X元谋X 101290803X 清水河X 101080105X 宜宾县X 101271103X元氏X 101090112X番禺X 101280102X遵化X 101090510X淳化X 101110204X射洪X 101270703X民勤X 101160502X闻喜X 101100808X忠县X 101042400X临洮X 101160205X南海X 101280803X宜州X 101301207X井陉X 101090102X久治X 101150505X祁连X 101150803X故城X 101090807X汉川X 101200406X延津X 101180306X平远X 101280407X高雄X 101340201X清原X 101070403X绥阳X 101260204X 阿巴嘎X 101080904X台安X 101070302X华阴X 101110511X资源X 101300514X资溪X 101240406X路桥X 101210613X墨玉X 101131304X通许X 101180804X修水X 101240212X色达X 101271813X迁西X 101090507X 科左后旗X 101080504X雅安X 101271701X铜鼓X 101240502X昭苏X 101131007X兰坪X 101291204X仁和X 101270202X 阿荣旗X 101081003X兴城X 101071404X 公主岭X 101060404X 巴仑台X 101130614X米脂X 101110408X明水X 101050505X布拖X 101271619X开平X 101281103X东莞X 101281601X左权X 101100404X 哈巴河X 101131402X衡南X 101250407X安源X 101240904X高阳X 101090206X黄石X 101200601X荔城X 101230406X会昌X 101240711X 鄂温克旗X 101081006X平南X 101300803X邕宁X 101300103X 麻栗坡X 101290604X饶阳X 101090805X 英吉沙X 101130902X宁蒗X 101291404X 吉木乃X 101131405X衢州X 101211001X盖州X 101070803X南涧X 101290212X资中X 101271204X云龙X 101290202X上海X 101020100X灵台X 101160303X潍坊X 101120601X平潭X 101230108X西充X 101270506X白城X 101060601X 伊金霍洛X 101080711X离石X 101101101X汝州X 101180504X黄陂X 101200103X陈旗X 101081007X 哈尔滨X 101050101X禄劝X 101290111X鞍山X 101070301X阳城X 101100603X南江X 101270903X乡城X 101271816X班玛X 101150502X仁怀X 101260203X东平X 101120805X商州X 101110604X来宾X 101300401X 大石桥X 101070802X内黄X 101180204X勐腊X 101291605X黄陵X 101110310X霞山X 101281009X崆峒X 101160308X合作X 101161201X郧县X 101201104X融水X 101300307X德阳X 101272001X郁南X 101281404X岗子X 101080610X达孜X 101140107X北川X 101270406X黟县X 101221005X 达拉特X 101080703X福安X 101230306X岳阳X 101251001X龙口X 101120505X枞阳X 101220602X乐业X 101301010X巫山X 101042000X耿马X 101291103X 遵义县X 101260202X京山X 101201403X大城X 101090605X浦城X 101230906X双流X 101270106X兰州X 101160101X郎溪X 101221407X乐东X 101310221X麦积X 101160908X 霍林郭勒X 101081108X兴国X 101240717X河曲X 101101004X安溪X 101230502X富川X 101300703X博爱X 101181106X寿宁X 101230304X 阿瓦提X 101130809X中宁X 101170502X 黄山市X 101221001X泉州X 101230501X平乐X 101300512X赵县X 101090113X 黄山区X 101221002X荣县X 101270303X始兴X 101280203X岷县X 101160207X根河X 101081015X佛冈X 101281306X鸡泽X 101091011X龙南X 101240714X凭祥X 101300204X 九寨沟X 101271906X怒江X 101291201X金沙X 101260703X宜春X 101240501X义马X 101181705X壤塘X 101271911X平乡X 101090912X筠连X 101271109X志丹X 101110306X黔西X 101260708X单县X 101121009X卓尼X 101161203X宁晋X 101090908X仙居X 101210606X户县X 101110106X洛宁X 101180905X壶关X 101100511X富平X 101110506X监利X 101200805X咸阳X 101110200X长垣X 101180308X山阳X 101110608X道真X 101260210X山阴X 101100903X任丘X 101090712X合水X 101160405X贡嘎X 101140302X拉萨X 101140101X阿里X 101140701X秀屿X 101230405X犍为X 101271402X林甸X 101050902X泸溪X 101251506X博野X 101090225X 吉木萨尔X 101130405X合江X 101271004X瓜州X 101160805X通榆X 101060605X铜川X 101111001X深州X 101090811X秦安X 101160904X来安X 101221106X吉隆X 101140210X建昌X 101071402X光泽X 101230903X湟中X 101150104X南漳X 101200204X芜湖X 101220301X万山X 101260604X徐水X 101090204X东川X 101290103X抚州X 101240401X霍山X 101221506X 浩尔吐X 101080604X晋宁X 101290105X枣庄X 101121401X肥东X 101220103X潞西X 101291508X大名X 101091005X大同X 101100201X 新右旗X 101081009X瓮安X 101260403X天峻X 101150708X怀集X 101280906X恭城X 101300511X肥乡X 101091008X施甸X 101290504X湟源X 101150103X天峨X 101301202X茫崖X 101150712X和顺X 101100405X 察右后旗X 101080410X 乌伊岭X 101050802X栾川X 101180909X阜新X 101070901X团风X 101200510X忻州X 101101001X西华X 101181405X和硕X 101130608X张掖X 101160701X 阿右旗X 101081202X济源X 101181801X 尼勒克X 101131003X甘德X 101150503X祁东X 101250404X乐至X 101271303X乌什X 101130802X 积石山X 101161107X玛多X 101150506X 阿图什X 101131501X泰州X 101191201X郏县X 101180502X枣强X 101090802X 南木林X 101140203X南乐X 101181303X 神农架X 101201201X琼中X 101310208X茶陵X 101250305X 鄂尔多斯X 101080701X临沭X 101120905X娄烦X 101100104X朗县X 101140406X霍州X 101100711X鸡东X 101051104X隆子X 101140307X思南X 101260605X唐山X 101090501X龙里X 101260407X 呼市郊区X 101080106X嵊州X 101210505X平泉X 101090405X西畴X 101290602X偏关X 101101005X越西X 101271615X金塔X 101160803X富宁X 101290608X林周X 101140104X南丰X 101240409X惠州X 101280301X南丹X 101301209X 五台山X 101101010X大田X 101230811X普陀X 101211105X巴青X 101140608X繁昌X 101220302X古蔺X 101271006X无为X 101221603X祥云X 101290207X 锡林高勒X 101081206X哈密X 101131201X双柏X 101290809X平谷X 101011500X嘉兴X 101210301X凤阳X 101221102X 克拉玛依X 101130201X和布克赛尔X 101131104X 曲麻莱X 101150606X塘沽X 101031100X盐城X 101190701X东岗X 101060904X长白X 101060905X白银X 101161301X平江X 101251005X牟定X 101290805X维西X 101291303X博兴X 101121102X 红花岗X 101260215X兴和X 101080406X坡头X 101281008X 大同县X 101100203X云岩X 101260110X吉首X 101251501X荥阳X 101180103X陆河X 101282104X铜山X 101190802X乳山X 101121304X南京X 101190101X南溪X 101271104X舟山X 101211101X石屏X 101290302X桓仁X 101070504X阳山X 101281305X洪泽X 101190904X阳高X 101100202X芦山X 101271707X新宾X 101070402X寿阳X 101100407X安义X 101240104X嘉黎X 101140603X荔波X 101260412X永康X 101210907X崇义X 101240702X 高碑店X 101090221X巩留X 101131005X佛山X 101280800X麟游X 101110904X丰城X 101240510X 建平县X 101071207X柞水X 101110603X塔城X 101131101X儋州X 101310205X靖州X 101251205X新津X 101270110X海原X 101170504X疏勒X 101130912X安丘X 101120607X海口X 101310101X浮梁X 101240803X大竹X 101270604X桂东X 101250511X临淄X 101120308X清徐X 101100102X杂多X 101150604X乐亭X 101090506X沾益X 101290402X治多X 101150603X金坛X 101191103X花垣X 101251508X中阳X 101101109X化德X 101080403X孟连X 101290908X巴塘X 101271815X蓬溪X 101270702X 阿拉尔X 101130701X唐县X 101090205X崇仁X 101240404X安仁X 101250509X揭东X 101281905X界首X 101220805X尚志X 101050111X东台X 101190707X海北X 101150801X周宁X 101230305X柳州X 101300301X潘集X 101220403X白朗X 101140217X云县X 101291107X绩溪X 101221405X掇刀X 101201404X永平X 101290204X博罗X 101280302X永年X 101091009X兴宁X 101280402X围场X 101090410X 科左中旗X 101080503X德宏X 101291501X临海X 101210610X德安X 101240205X革吉X 101140709X雄县X 101090217X福鼎X 101230308X泗阳X 101191303X凌源X 101071203X乐清X 101210707u(X彬县X 101110208X涿鹿X 101090312X金阳X 101271611X新民X 101070106X勐海X 101291603X定襄X 101101002X 白云鄂博X 101080202X左贡X 101140505X 翁牛特X 101080609X曹县X 101121007X乃东X 101140309X绿春X 101290306X临沂X 101120901X永德X 101291106X安远X 101240712X榆阳X 101110413X陵水X 101310216X临河X 101080801X定西X 101160201X大宁X 101100705X禹州X 101180405X临沧X 101291101X 吐鲁番X 101130501X大安X 101060603X陆良X 101290403X宾阳X 101300109X高密X 101120608X西峰X 101160401X亚东X 101140218X米泉X 101130403X成武X 101121008X容县X 101300904X临泉X 101220804X西峡X 101180705X临泽X 101160704X松原X 101060801X 景德镇X 101240801X获嘉X 101180302X应城X 101200405X昌乐X 101120605X吕梁X 101101100X伽师X 101130910X上思X 101301402X南郑X 101110810X鱼台X 101120704X莆田X 101230401X新沂X 101190807X六枝X 101260802X瑞昌X 101240202X荔浦X 101300513X东方X 101310202X平舆X 101181607X四平X 101060401X新河X 101090910X 攀枝花X 101270201X喀左X 101071204X边坝X 101140503X威海X 101121301X献县X 101090709X鄄城X 101121002X临江X 101060903X索伦X 101081106X长顺X 101260404X旺苍X 101272102X广饶X 101121205X 吉安县X 101240602X黄骅X 101090713X个旧X 101290308X华容X 101251002X石泉X 101110703X费县X 101120909X红河X 101290301X泽普X 101130907X临汾X 101100701X华安X 101230610X新泰X 101120802X安达X 101050503X华宁X 101290705X元江X 101290709X凌云X 101301011X 佳木斯X 101050401X 平顶山X 101180501X宿松X 101220606X宝应X 101190602X新会X 101281104X木兰X 101050113X康马X 101140219X六合X 101190105X且末X 101130605X如东X 101190504X睢县X 101181003X甘南X 101050204X化州X 101282003X东光X 101090703X龙岩X 101230701X浑源X 101100207X东兴X 101301403X诸城X 101120609X东兰X 101301203X 二连浩特X 101080903X即墨X 101120204X彭阳X 101170406X 门头沟X 101011400X临澧X 101250606X施秉X 101260503X磴口X 101080803X长宁X 101271106X长寿X 101041000X普安X 101260909X雷山X 101260512X崇信X 101160304X普宁X 101281903X新余X 101241001X广州X 101280101X普定X 101260302X霍邱X 101221502X峨山X 101290708X旬邑X 101110210X尉犁X 101130603X桦甸X 101060206X 阿合奇X 101131504X紫云X 101260305X道孚X 101271807X桐庐X 101210103X蒙自X 101290309X藤县X 101300602X井研X 101271403X扬中X 101190303X霸州X 101090608X旅顺X 101070205X孟津X 101180903X庆云X 101120407X邳州X 101190805X汨罗X 101251004X五营X 101050803X左云X 101100208X 扎兰屯X 101081012X安泽X 101100716X嘉鱼X 101200703X错那X 101140306X沛县X 101190804X临漳X 101091003X巴马X 101301204X砀山X 101220702X木里X 101271603X天台X 101210605X萨嘎X 101140209X汇川X 101260214X合肥X 101220101X灵川X 101300507X邵东X 101250905X汾阳X 101101111X富民X 101290109X 衡阳县X 101250405X隆回X 101250902X海兴X 101090704X文昌X 101310212X舒城X 101221507X莱阳X 101120510X荣成X 101121303X海丰X 101282102X澄江X 101290702X婺源X 101240303X洞头X 101210706X晋城X 101100601X长子X 101100509X环县X 101160403X临潭X 101161202X龙山X 101251507X沁县X 101100508X襄州X 101200202X凌海X 101070702X洛隆X 101140504X张湾X 101201109X威远X 101271203X远安X 101200902X威信X 101291005X新丰X 101280208X杭州X 101210101X 达坂城X 101130105X许昌X 101180401X宁武X 101101007X广宗X 101090911X麻江X 101260507X扶风X 101110907X广安X 101270801X石渠X 101271812X稻城X 101271817X惠农X 101170202X密云X 101011300X新乐X 101090117X静宁X 101160307X泰宁X 101230804X洪洞X 101100710X吉安X 101240601X信丰X 101240706X 乌斯泰X 101081211X龙川X 101281205X龙州X 101300203X 准格尔X 101080704X温江X 101270104X玉田X 101090508X灵山X 101301103X大姚X 101290802X望都X 101090210X贡觉X 101140511X新乡X 101180301X永州X 101251401X永川X 101040200X炮台X 101130302X昌江X 101310206X中甸X 101291304X新源X 101131006X怀宁X 101220605X镇宁X 101260303X方正X 101050109X怀安X 101090309X樟树X 101240509X衡山X 101250402X皋兰X 101160102X和田X 101131301X 日喀则X 101140201X丹棱X 101271505X临清X 101121707X互助X 101150204X洛阳X 101180901X临高X 101310203X泰兴X 101191203X咸宁X 101200701X隰县X 101100704X玉门X 101160807X峨眉X 101271408X蠡县X 101090215X 芜湖县X 101220303X仪征X 101190603X吴堡X 101110411X 淮阴区X 101190906X临湘X 101251006X岚皋X 101110706X 玛纳斯X 101130407X西平X 101181602X南投X 101340404X秭归X 101200903X富县X 101110305X岑溪X 101300606X栖霞X 101120507X 乌审召X 101080712X 马鞍山X 101220501X多伦X 101080915X沧源X 101291102X庐江X 101221602X衡东X 101250403X温泉X 101131602X慈利X 101251103X和静X 101130606X博山X 101120303X仁寿X 101271502X郴州X 101250501X盐山X 101090705X平果X 101301007X古交X 101100105X精河X 101131603X浠水X 101200506X中牟X 101180107X义县X 101070704X本溪X 101070501X大庆X 101050901X屏东X 101340205X延庆X 101010800X汶川X 101271902X宣恩X 101201005X商丘X 101181001X 乌中旗X 101080806X贡山X 101291207X碌曲X 101161207X建水X 101290303X望城X 101250105X江川X 101290703X湖口X 101240207X 类乌齐X 101140507X曲阳X 101090214X海城X 101070304X绛县X 101100807X阆中X 101270507X云霄X 101230609X 头道湖X 101081207X 中泉子X 101081208X吴忠X 101170301X礼泉X 101110202X灵宝X 101181702X姚安X 101290804X城步X 101250909X惠阳X 101280303X淳安X 101210104X饶平X 101281502X昆明X 101290101X淮滨X 101180606X深圳X 101280601X宁波X 101210401X古丈X 101251504X隆昌X 101271205X疏附X 101130911X韩城X 101110510X 绥芬河X 101050305X上蔡X 101181604X霞浦X 101230303X琼海X 101310211X台北X 101340101X 希拉穆仁X 101080207X理塘X 101271814X太谷X 101100408X宽甸X 101070603X会东X 101271607X塔什库尔干X 101130903X 徐家汇X 101021200X黄龙X 101110311X 冷水滩X 101251411X夏河X 101161208X英山X 101200505X若羌X 101130604X灵寿X 101090106X依兰X 101050106X木垒X 101130408X斗门X 101280702X 额尔古纳X 101081014X宁河X 101030700X旬阳X 101110705X栾城X 101090104X神池X 101101006X高平X 101100605X札达X 101140706X忻城X 101300402X应县X 101100905X曲靖X 101290401X扶绥X 101300206X五莲X 101121502X长岭X 101060804X封丘X 101180307X蔡甸X 101200102X 峨眉山X 101271409X榕江X 101260516X永安X 101230810X佳县X 101110404X容城X 101090207X乌苏X 101131106X霍城X 101131009X兴平X 101110211X中卫X 101170501X津市X 101250608X息县X 101180602X崇武X 101230507X浚县X 101181202X泸水X 101291205X宁海X 101210408X定海X 101211106X八宿X 101140508X黄平X 101260505X富锦X 101050407X云阳X 101041700X嘉定X 101020500X长安X 101110102X嵩县X 101180907X夏津X 101120410X九龙X 101320102X沅陵X 101251203X菏泽X 101121001X苍山X 101120904X永寿X 101110203X苍溪X 101272105X宝清X 101051303X宁津X 101120409X东阳X 101210905X东阿X 101121706X 涠洲岛X 101301303X宁洱X 101290912X宕昌X 101161004X丹东X 101070601X海盐X 101210306X泗县X 101220704X寿县X 101221503X沈丘X 101181410X 高力板X 101080510X 铁干里克X 101130611X海阳X 101120511X申扎X 101140703X屏边X 101290310X平昌X 101270904X共和X 101150409X台前X 101181302X连平X 101281203X澜沧X 101290904X宁远X 101251406X登封X 101180104X都昌X 101240210X通道X 101251207X北镇X 101070706X河源X 101281201X营口X 101070801X修文X 101260107X石棉X 101271705X那曲X 101140601X耀州X 101111004X略阳X 101110802X滁州X 101221101X德庆X 101280905X 张家口X 101090301X铜陵X 101221301X宜丰X 101240503X社旗X 101180704X玉环X 101210603X沙湾X 101131107X宁明X 101300207X高陵X 101110107X兴安X 101300506X 堆龙德庆X 101140105X仁布X 101140220X盈江X 101291504X 德令哈X 101150716X陈仓X 101110912X上林X 101300107X剑河X 101260511X康平X 101070104X枣阳X 101200208X金寨X 101221505X 土右旗X 101080204X宁乡X 101250102X无极X 101090110X太仓X 101190408X双湖X 101140610X会泽X 101290408X万荣X 101100804X西安X 101110101X建湖X 101190706X宽城X 101090409X曲周X 101091015X庄河X 101070207X西宁X 101150101X吴川X 101281002X璧山X 101042900X 连云港X 101191001X海门X 101190508X代县X 101101008X富阳X 101210108X拜城X 101130804X康保X 101090304X泰和X 101240611X商水X 101181406X达州X 101270601X滦南X 101090505X 乌尔禾X 101130202X 冷水江X 101250803X息烽X 101260105X巨野X 101121006X河津X 101100805X峄城X 101121403X沙洋X 101201405X滦县X 101090504X尼木X 101140103X顺平X 101090216X 伊宁县X 101131004X湄潭X 101260205X友谊X 101051305X太湖X 101220603X德州X 101120401X毕节X 101260701X洛南X 101110602X文成X 101210703X广平X 101091012X滨海X 101190703X永顺X 101251503X闵行X 101020200X林州X 101180205X商河X 101120103X汝阳X 101180910X上杭X 101230705X岚县X 101101104X临武X 101250505X皮山X 101131302X 青龙山X 101080505X 上饶县X 101240308X隆化X 101090407X靖宇X 101060902X广德X 101221406X靖安X 101240506X习水X 101260209X赤水X 101260208X汝城X 101250508X防城X 101301405X鲁山X 101180507X荥经X 101271703X侯马X 101100714X永福X 101300504X英德X 101281307X花都X 101280105X兴山X 101200904X沂水X 101120910X 通化县X 101060506X怀远X 101220202X罗城X 101301206X翼城X 101100713X沙河X 101090917X河池X 101301201X白山X 101060901X天长X 101221107X南明X 101260111X商洛X 101110601X 秀屿港X 101230403X高州X 101282002X顺德X 101280801X冠县X 101121702X滨州X 101121101X嘉禾X 101250503X陇西X 101160203X垣曲X 101100809X 阿克苏X 101130801X沁阳X 101181104X莘县X 101121709X寿光X 101120603X灌云X 101191004X天镇X 101100204X 托克逊X 101130502X突泉X 101081107X百色X 101301001X昭觉X 101271612X房山X 101011200X 石景山X 101011000X乌恰X 101131502X长乐X 101230110X库车X 101130807X穆棱X 101050303X宝丰X 101180503X眉山X 101271501X汉阴X 101110704X巴彦X 101050107X邵武X 101230904X嘉祥X 101120702X上虞X 101210503X古浪X 101160503X涿州X 101090218X南昌X 101240101X天门X 101201501X赣县X 101240718X屯留X 101100503X额敏X 101131103X双辽X 101060402X库伦X 101080507X庄浪X 101160306X黄岩X 101210612X泾源X 101170404X梨树X 101060403X海南X 101150401X云和X 101210806X庆元X 101210807X定边X 101110405X囊谦X 101150605X洪湖X 101200806X弥勒X 101290304u. -------------------------------------------------------------------------------- /slack_bot/plugins/dianping.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import re 3 | import hashlib 4 | from functools import partial 5 | 6 | from flask import current_app as app 7 | import requests 8 | 9 | from baidumap import address2geo 10 | from utils import gen_attachment 11 | 12 | description = """ 13 | [大众点评]查找附近美食: "[城市名(默认北京市, 要带`市`)] xx有什么美食|xx附近美食 [带图] [私聊]",比如: 14 | * 酒仙桥附近美食 15 | * 上海市 宜山路455号有什么美食(上海uber) 16 | """ 17 | 18 | API_URL = 'http://api.dianping.com/v1/{0}/{1}' 19 | TEST_TXT_REGEX = re.compile(r'(.*)\(.*\.\.\.\)') 20 | GOODS_REGEX = re.compile(r'(.*)附近美食|(.*)有什么美食|(.*?)美食(.*?)') 21 | CITY_REGEX = re.compile(ur'(\W?)(.*)市', re.UNICODE) 22 | 23 | 24 | def real_name(name): 25 | match = TEST_TXT_REGEX.search(name) 26 | if match: 27 | name = match.groups()[0] 28 | return name 29 | 30 | 31 | def concat_params(appkey, secret, params): 32 | codec = appkey 33 | for key in sorted(params.keys()): 34 | codec += key + str(params[key]) 35 | 36 | codec += secret 37 | 38 | # 签名计算 39 | sign = (hashlib.sha1(codec).hexdigest()).upper() 40 | 41 | url_trail = 'appkey=' + appkey + '&sign=' + sign 42 | for pair in params.items(): 43 | url_trail += '&' + pair[0] + '=' + str(pair[1]) 44 | return '?' + url_trail 45 | 46 | 47 | class DianpingApi(object): 48 | def __init__(self, appkey, secret): 49 | self.concat_params = partial(concat_params, appkey, secret) 50 | 51 | def bind_api(self, path, subpath, params): 52 | params.pop('self') 53 | path_url = API_URL.format(path, subpath) + self.concat_params(params) 54 | r = requests.get(path_url) 55 | return r.json() 56 | 57 | def get_business_info(self, business, details=False): 58 | url = business['business_url'] # 商户页面URL链接 59 | # id = business['business_id'] 60 | distance = business['distance'] # 商户与参数坐标的距离,单位为米 61 | # coupon_description = business['coupon_description'] # 优惠券描述 62 | # deals_description = ','.join([ 63 | # c['description'] for c in business['deals']]) # 团购描述 64 | name = real_name(business['name']) # 商户名 65 | # branch_name = business['branch_name'] # 分店名 66 | address = business['address'] # 地址 67 | telephone = business['telephone'] # 电话 68 | # avg_rating = business['avg_rating'] # 星级评分,5.0代表五星,4.5代表四星半,依此类推 69 | photo_url = business['photo_url'] 70 | if details: 71 | product_grade = business['product_grade'] # noqa 产品/食品口味评价,1:一般,2:尚可,3:好,4:很好,5:非常好 72 | # decoration_grade = business['decoration_grade'] # 环境评价 同上 73 | # service_grade = business['service_grade'] # 服务评价 同上 74 | # avg_price = business['avg_price'] # 均价格,单位:元,若没有人均,返回-1 75 | text = u'<{0}|{1}> {2} {3} 距离: {4} '.format( 76 | url, name, address, telephone, distance) 77 | attach = gen_attachment( 78 | u'{0} {1} 距离: {2}'.format(address, telephone, distance), 79 | photo_url, image_type='thumb', title=name, title_link=url) 80 | return text, attach 81 | 82 | def find_businesses(self, latitude, longitude, city='上海', 83 | category='美食', sort=1, limit=20, offset_type=1, 84 | out_offset_type=1, platform=2): 85 | return self.bind_api('business', 'find_businesses', 86 | locals())['businesses'] 87 | 88 | def get_single_business(self, business_id, out_offset_type=1, platform=1): 89 | return self.bind_api('business', 'get_single_business', 90 | locals())['businesses'] 91 | 92 | def get_all_id_list(self, city='北京'): 93 | return self.bind_api('reservation', 'get_all_id_list', 94 | locals())['id_list'] 95 | 96 | def get_batch_businesses_by_id(self, business_ids, out_offset_type=1): 97 | if isinstance(business_ids, str): 98 | business_ids = business_ids.split(',') 99 | return self.bind_api('reservation', 'get_batch_businesses_by_id', 100 | locals())['businesses'] 101 | 102 | def find_businesses_with_reservations(self, reservation_date, 103 | reservation_time, number_of_people): 104 | return self.bind_api('reservation', 105 | 'find_businesses_with_reservations', 106 | locals())['businesses'] 107 | 108 | 109 | def test(data): 110 | return any([i in data['message'] 111 | for i in ['有什么美食', '大众点评', '附近美食']]) 112 | 113 | 114 | def handle(data): 115 | message = data['message'] 116 | if app is None: 117 | appkey = '41502445' 118 | secret = 'f0c2cc0b4f1048bebffc1527acbaeeb8' 119 | ak = '18691b8e4206238f331ad2e1ca88357e' 120 | else: 121 | appkey = app.config.get('DIANPING_APPKEY') 122 | secret = app.config.get('DIANPING_SECRET') 123 | ak = app.config.get('BAIDU_AK') 124 | api = DianpingApi(appkey, secret) 125 | 126 | match = CITY_REGEX.search(message.decode('utf-8')) 127 | city = match.groups()[1].encode('utf-8') if match else '北京' 128 | 129 | match = GOODS_REGEX.search(message) 130 | limit = 20 131 | if match: 132 | address = next((m for m in match.groups() if m is not None), '') 133 | geo = address2geo(ak, address) 134 | if not geo: 135 | return '找不到这个地址的数据' 136 | res = api.find_businesses(geo['lat'], geo['lng'], city=city) 137 | else: 138 | business_ids = api.get_all_id_list(city) 139 | res = api.get_batch_businesses_by_id(business_ids[:limit]) 140 | ret = [api.get_business_info(r) for r in res] 141 | return '\n'.join([r[0] for r in ret]), [r[1] for r in ret] 142 | 143 | 144 | if __name__ == '__main__': 145 | print handle({'message': '酒仙桥附近美食'}) 146 | print handle({'message': '上海市 宜山路455号有什么美食(上海uber)'}) 147 | -------------------------------------------------------------------------------- /slack_bot/plugins/earthquake.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # -*-coding:utf-8-*- 3 | 4 | """ 5 | Copyright (c) 2012 Qijiang Fan 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | """ 26 | 27 | # 地震 28 | import urllib2 29 | import re 30 | 31 | description = """ 32 | 最近发生的地震信息。触发条件:"地震了 [私聊]", 比如: 33 | * 地震了 34 | """ 35 | 36 | 37 | def test(data): 38 | return '地震了' in data['message'] 39 | 40 | 41 | def jw(a, b): 42 | aa = '' 43 | bb = '' 44 | if int(a.replace('.', '')) > 0: 45 | aa = '北纬' + a + '度' 46 | elif int(a.replace('.', '')) < 0: 47 | aa = '南纬' + a.replace('-', '') + '度' 48 | else: 49 | aa = '赤道附近' 50 | if int(b.replace('.', '')) > 0: 51 | bb = '东经' + b + '度' 52 | elif int(b.replace('.', '')) < 0: 53 | bb = '西经' + b.replace('-', '') + '度' 54 | else: 55 | bb = '本初子午线附近' 56 | return ','.join((aa, bb)) 57 | 58 | 59 | def handle(data): 60 | r = urllib2.urlopen( 61 | 'http://data.earthquake.cn/datashare/globeEarthquake_csn.html', 62 | timeout=5) 63 | t = [re.sub('(<[^>]*>|[\r\n])', '', a) 64 | for a in r.read().decode('gbk').encode('utf-8').split('\n')[170:178]] 65 | return '最近一次地震发生在%s(%s),发生时间%s,震级%s,震源深度%s千米,地震类型为%s。' %\ 66 | (t[7], jw(t[2], t[3]), ' '.join(t[0:2]), t[5], t[4], t[6]) 67 | 68 | if __name__ == '__main__': 69 | print handle({'message': '地震了吗?'}) 70 | -------------------------------------------------------------------------------- /slack_bot/plugins/events.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import division 3 | from datetime import date 4 | 5 | import jieba 6 | import requests 7 | from bs4 import BeautifulSoup 8 | 9 | from slack_bot.ext import cache 10 | from utils import check_cache 11 | 12 | description = """ 13 | 获取以下网站的活动列表: 14 | http://segmentfault.com/events 15 | http://huiyi.csdn.net/activity/home 16 | http://www.chekucafe.com/Party 17 | http://www.huodongxing.com/events 18 | 触发条件: "最近有什么活动 [城市名称] [私聊]"。比如: 19 | * 最近有什么活动 20 | * 最近有什么活动 北京 21 | """ 22 | 23 | TODAY = date.today().strftime('%m-%d') 24 | 25 | # segmentfault 26 | SF_URL = 'http://segmentfault.com' 27 | SF_EVENT_URL = 'http://segmentfault.com/events?city={0}' 28 | SF_CITIES_MAP = { 29 | u'\u4e0a\u6d77': 310100, 30 | u'\u5317\u4eac': 110100, 31 | u'\u5357\u4eac': 320100, 32 | u'\u53a6\u95e8': 350200, 33 | u'\u53f0\u5317': 710100, 34 | u'\u5408\u80a5': 340100, 35 | u'\u5e7f\u5dde': 440100, 36 | u'\u6210\u90fd': 510100, 37 | u'\u65b0\u52a0\u5761': 190, 38 | u'\u676d\u5dde': 330100, 39 | u'\u6b66\u6c49': 420100, 40 | u'\u6df1\u5733': 440300, 41 | u'\u70df\u53f0': 370600, 42 | u'\u897f\u5b89': 610100, 43 | u'\u957f\u6c99': 430100, 44 | u'\u97e9\u56fd': 198 45 | } 46 | 47 | # 活动行(默认是科技频道) 48 | HDX_EVENT_URL = 'http://www.huodongxing.com/events?orderby=n&tag=%E7%A7%91%E6%8A%80&city{0}&page={1}' # noqa 49 | HDX_URL = 'http://www.huodongxing.com' 50 | HDX_MAX_PAGE = 10 51 | 52 | # 车库咖啡 53 | CK_EVENT_URL = 'http://www.chekucafe.com/Party' 54 | CK_URL = 'http://www.chekucafe.com' 55 | 56 | # CSDN 最近一个月的 57 | CSDN_EVENT_URL = 'http://huiyi.csdn.net/activity/home?c={0}&s=one_month&page={1}' # noqa 58 | CSDN_CITIES_MAP = { 59 | u'\u4e0a\u6d77': '2', 60 | u'\u4e91\u5357': '25', 61 | u'\u5185\u8499\u53e4': '7', 62 | u'\u5317\u4eac': '1', 63 | u'\u53f0\u6e7e': '34', 64 | u'\u5409\u6797': '9', 65 | u'\u56db\u5ddd': '23', 66 | u'\u5929\u6d25': '3', 67 | u'\u5b81\u590f': '29', 68 | u'\u5b89\u5fbd': '13', 69 | u'\u5c71\u4e1c': '16', 70 | u'\u5c71\u897f': '6', 71 | u'\u5e7f\u4e1c': '20', 72 | u'\u5e7f\u897f': '21', 73 | u'\u65b0\u7586': '31', 74 | u'\u6c5f\u82cf': '11', 75 | u'\u6c5f\u897f': '15', 76 | u'\u6cb3\u5317': '5', 77 | u'\u6cb3\u5357': '17', 78 | u'\u6d59\u6c5f': '12', 79 | u'\u6d77\u5357': '22', 80 | u'\u6e56\u5317': '18', 81 | u'\u6e56\u5357': '19', 82 | u'\u6fb3\u95e8': '33', 83 | u'\u7518\u8083': '28', 84 | u'\u798f\u5efa': '14', 85 | u'\u897f\u85cf': '26', 86 | u'\u8d35\u5dde': '24', 87 | u'\u8fbd\u5b81': '8', 88 | u'\u91cd\u5e86': '4', 89 | u'\u9655\u897f': '27', 90 | u'\u9752\u6d77': '30', 91 | u'\u9999\u6e2f': '32', 92 | u'\u9ed1\u9f99\u6c5f': '10' 93 | } 94 | 95 | FILTER_WORDS = [ 96 | u'推广', u'论坛', u'产业', u'敏捷', u'管理', u'形势', u'研讨会', u'选拔', 97 | u'寻找', u'博览会', u'展', u'招募', u'会员', u'职业', u'嘉年华', u'内测', 98 | 'office', u'报名', u'交流', u'讲座' 99 | ] 100 | THRESHOLD = 0.7 101 | 102 | 103 | def check_filter(title): 104 | for word in FILTER_WORDS: 105 | if word in title: 106 | return True 107 | return False 108 | 109 | 110 | def get_df_events(city): 111 | id = SF_CITIES_MAP.get(city, SF_CITIES_MAP[u'北京']) 112 | r = requests.get(SF_EVENT_URL.format(id)) 113 | soup = BeautifulSoup(r.text) 114 | for event in soup.findAll('div', {'class': 'widget-event'}): 115 | if u'报名' in event.find('a', {'class': 'btn-sm'}).text: 116 | h2 = event.find('h2') 117 | title = h2.text 118 | if check_filter(title): 119 | continue 120 | time, others = [i.text for i in event.findAll('li')] 121 | url = SF_URL + h2.find('a').attrs.get('href') 122 | yield title, url, time, others 123 | else: 124 | break 125 | 126 | 127 | def get_hdx_events(city, res=[], page=1): 128 | r = requests.get(HDX_EVENT_URL.format(city.encode('utf-8'), page)) 129 | soup = BeautifulSoup(r.text) 130 | uls = soup.findAll('ul', {'class': 'event-vertical-list-new'}) 131 | for ul in uls: 132 | for li in ul.findAll('li'): 133 | a = li.find('h3').find('a') 134 | title = a.text 135 | if check_filter(title): 136 | continue 137 | pull = li.find('span', {'class': 'pull-right'}).text 138 | time = li.find( 139 | 'div', {'class': 'time'}).text.replace(pull, '').strip() 140 | if time <= TODAY: 141 | continue 142 | favorites, user = pull.split('|') 143 | url = HDX_URL + a.attrs.get('href') 144 | res.append((title, url, time, 145 | u'收藏: {0}| 报名: {1}'.format(favorites, user))) 146 | if page == HDX_MAX_PAGE: 147 | return res 148 | page += 1 149 | return get_hdx_events(city, res=res, page=page) 150 | 151 | 152 | def get_ck_events(city): 153 | r = requests.get(CK_EVENT_URL) 154 | soup = BeautifulSoup(r.text) 155 | for li in soup.find(id='party-list').findAll('li'): 156 | title = li.find('h3').text 157 | if check_filter(title): 158 | continue 159 | url = CK_URL + li.find('a').attrs.get('href') 160 | tds = li.findAll('td') 161 | time = tds[0].text 162 | others = u'|'.join([td.text for td in tds[2:]]) 163 | yield title, url, time, others 164 | 165 | 166 | def get_csdn_events(city, res=[], page=1): 167 | id = CSDN_CITIES_MAP.get(city, CSDN_CITIES_MAP[u'北京']) 168 | r = requests.get(CSDN_EVENT_URL.format(id, page)) 169 | soup = BeautifulSoup(r.text) 170 | for item in soup.find('div', {'class': 'list-wraper'}).findAll( 171 | 'div', {'class': 'item'}): 172 | a = item.find('a') 173 | title = a.attrs.get('title') 174 | if check_filter(title): 175 | continue 176 | url = a.attrs.get('href') 177 | dd = item.findAll('dd') 178 | time = dd.pop(1).text 179 | others = u'|'.join([d.text.replace('\n', '.') for d in dd]) 180 | res.append((title, url, time, others)) 181 | 182 | # 判断是否有下一页 183 | nav = soup.find('span', {'class': 'page-nav'}) 184 | if nav.find( 185 | 'a', {'class': 'btn-next'}).attrs.get('href').endswith(str(page)): 186 | return res 187 | page += 1 188 | return get_csdn_events(city, res=res, page=page) 189 | 190 | 191 | def check_similar(seg_, seg_lists): 192 | seg_len = len(seg_) 193 | for seg_list in seg_lists: 194 | seg_list_len = len(seg_list) 195 | len_ = seg_list_len if seg_len > seg_len else seg_len 196 | if len(seg_.intersection(seg_list)) / len_ > THRESHOLD: 197 | return True 198 | return False 199 | 200 | 201 | def get_events(city): 202 | all_events = [ 203 | ('SegmentFault', get_df_events(city)), 204 | (u'活动行', get_hdx_events(city)), 205 | ('csdn', get_csdn_events(city)) 206 | ] 207 | if city == u'北京': 208 | all_events.insert(1, (u'车库咖啡', get_ck_events(city))) 209 | 210 | events = [] 211 | seg_lists = [] 212 | for org_name, org_events in all_events: 213 | for title, url, time, others in org_events: 214 | seg_ = set(jieba.cut(title)) 215 | if check_similar(seg_, seg_lists): 216 | continue 217 | seg_lists.append(seg_) 218 | events.append( 219 | u'<{0}|[{1}] {2}> {3} {4}'.format( 220 | url, org_name, title, time, others)) 221 | return events 222 | 223 | 224 | def test(data): 225 | return '最近有什么活动' in data['message'] 226 | 227 | 228 | def handle(data): 229 | message = data['message'] 230 | if not isinstance(message, unicode): 231 | message = message.decode('utf-8') 232 | msg = message.split() 233 | if len(msg) == 1 or (len(msg) == 2 and u'私聊' in msg[1]): 234 | city = u'北京' 235 | else: 236 | city = msg[1] 237 | return '\n'.join(check_cache(cache, get_events, city)) 238 | 239 | 240 | if __name__ == '__main__': 241 | print handle({'message': '最近有什么活动'}) 242 | print handle({'message': '最近有什么活动 上海'}) 243 | -------------------------------------------------------------------------------- /slack_bot/plugins/github_issue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from flask import current_app as app 4 | import requests 5 | 6 | description = """ 7 | 当前 github 组织下所有未关闭的 issues && PR。触发条件: "issue [私聊]"。比如: 8 | * issue 9 | """ 10 | 11 | ISSUE_API = "https://api.github.com/repos/{org}/{repo}/issues" 12 | REPO_API = "https://api.github.com/orgs/{org}/repos" 13 | 14 | 15 | def test(data): 16 | return 'issue' in data['message'] 17 | 18 | 19 | def handle(data): 20 | org_name = app.config.get('ORG_NAME', 'python-cn') 21 | repos = requests.get(REPO_API.format(org=org_name)).json() 22 | rv = '' 23 | for repo in repos: 24 | repo_name = repo['name'] 25 | issues_count = repo['open_issues_count'] 26 | if issues_count != 0: 27 | issues = requests.get(ISSUE_API.format(org=org_name, 28 | repo=repo_name)).json() 29 | rv += '*{repo_name}\n'.format(repo_name=repo_name) 30 | for issue in issues: 31 | rv += 'Issue {}:'.format(issue['number']) 32 | rv += issue['title'].encode('utf-8') + '\n' 33 | rv += issue['html_url'].encode('utf-8') + '\n' 34 | rv += '\n' 35 | 36 | return rv if rv else 'no issue' 37 | 38 | 39 | if __name__ == '__main__': 40 | from flask import Flask 41 | app = Flask(__name__) 42 | app.config['org_name'] = 'python-cn' 43 | print handle(None) 44 | -------------------------------------------------------------------------------- /slack_bot/plugins/help.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from flask import current_app 3 | 4 | description = """ 5 | 帮助信息,触发条件: "help [私聊]". 比如: 6 | * help 7 | """ 8 | 9 | 10 | def format_desc(plugin, prefix=' '): 11 | name = plugin.__name__.split('.')[-1] 12 | desc = getattr(plugin, 'description', '').strip() 13 | # 为每行内容增加前缀 14 | desc = ('\n' + prefix).join(desc.split('\n')) 15 | return '{name}:\n{prefix}{desc}'.format( 16 | name=name, prefix=prefix, desc=desc 17 | ) 18 | 19 | 20 | def test(data): 21 | return 'help' in data['message'] 22 | 23 | 24 | def handle(data): 25 | app = current_app 26 | plugin_modules = app.plugin_modules if app else [] 27 | docs = [] 28 | for plugin in plugin_modules: 29 | docs.append(format_desc(plugin)) 30 | return '\n'.join(docs) 31 | -------------------------------------------------------------------------------- /slack_bot/plugins/movie.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from flask import current_app 4 | import requests 5 | from bs4 import BeautifulSoup 6 | 7 | from .utils import to_pinyin, gen_attachment, upload_image 8 | from ..utils import timeout 9 | 10 | description = """ 11 | 最近上映的电影信息。触发条件: 12 | "[上映 | 热映 | 有什么 | 将] 电影 [上映 | 热映 | 有什么 | 将] [城市名称] [带图] [私聊]" 13 | 比如: 14 | * 最近要将上映的电影 15 | * 有什么电影 上海 16 | """ 17 | 18 | CURRENT_URL = 'http://movie.douban.com/nowplaying/{0}/' 19 | LATER_URL = 'http://movie.douban.com/later/{0}/' 20 | 21 | 22 | def get_later_movie_info(city, app): 23 | r = requests.get(LATER_URL.format(city)) 24 | soup = BeautifulSoup(r.text) 25 | items = soup.find(id='showing-soon').findAll('div', {'item'}) 26 | for i in items: 27 | h = i.find('h3').find('a') 28 | url = h.attrs['href'] 29 | title = h.text 30 | content = '|'.join([li.text for li in i.findAll('li')[:4]]) 31 | image_url = i.find('a').find('img').attrs.get('src', '') 32 | # SA好变态, 感觉是防盗链了,下同 33 | image_url = upload_image(image_url, 'thumb', app) 34 | yield u'<{url}|{title}> {content}'.format(**locals()), gen_attachment( 35 | content, image_url, image_type='thumb', title=title, 36 | title_link=url) 37 | 38 | 39 | def get_current_movie_info(city, app): 40 | r = requests.get(CURRENT_URL.format(city)) 41 | soup = BeautifulSoup(r.text) 42 | items = soup.find(id='nowplaying').find('ul', {'class': 'lists'}).findAll( 43 | 'li', {'class': 'poster'}) 44 | count = 0 45 | for i in items: 46 | if count >= 10: 47 | continue 48 | img = i.find('img') 49 | title = img.attrs.get('alt', '') 50 | content = '|'.join([li.text for li in i.findAll('li')[:4]]) 51 | url = i.find('a').attrs.get('href', '') 52 | image_url = img.attrs.get('src', '') 53 | image_url = upload_image(image_url, 'thumb', app) 54 | count += 1 55 | yield u'<{url}|{title}>'.format(**locals()), gen_attachment( 56 | content, image_url, image_type='thumb', title=title, 57 | title_link=url) 58 | 59 | 60 | def test(data): 61 | return '电影' in data['message'] and \ 62 | any([i in data['message'] for i in ['上映', '热映', '有什么', '将']]) 63 | 64 | 65 | def handle(data): 66 | ret = [] 67 | 68 | def timeout_handle(): 69 | return '\n'.join([r[0] for r in ret]), [r[1] for r in ret] 70 | 71 | @timeout(15, default=timeout_handle) 72 | def _handle(data, app): 73 | message = data['message'] 74 | if not isinstance(message, unicode): 75 | message = message.decode('utf-8') 76 | msg = message.split() 77 | if len(msg) == 1 or (len(msg) == 2 and u'私聊' in msg[1]): 78 | city = 'beijing' 79 | else: 80 | city = to_pinyin(msg[1]) 81 | if u'将' in message: 82 | fn = get_later_movie_info 83 | else: 84 | fn = get_current_movie_info 85 | for r in fn(city, app): 86 | ret.append(r) 87 | return '\n'.join([r[0] for r in ret]), [r[1] for r in ret] 88 | app = current_app._get_current_object() 89 | return _handle(data, app) 90 | 91 | if __name__ == '__main__': 92 | print handle({'message': '最近要将上映的电影'}) 93 | print handle({'message': '有什么电影 上海'}) 94 | -------------------------------------------------------------------------------- /slack_bot/plugins/orz.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8-*- 2 | 3 | """ 4 | Copyright (c) 2012 wong2 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | """ 25 | 26 | # 来膜拜 27 | 28 | import random 29 | 30 | description = """ 31 | 快来膜拜我!, 触发条件: "膜拜 | orz [私聊]"。比如: 32 | * 膜拜 33 | * orz 34 | """ 35 | 36 | 37 | def test(data): 38 | message = data['message'] 39 | for word in ['膜拜', 'orz']: 40 | if word in message: 41 | return True 42 | return False 43 | 44 | 45 | def handle(data): 46 | mobai_icon = ':mb:' 47 | return mobai_icon * random.randrange(1, 10) 48 | 49 | if __name__ == '__main__': 50 | print test({'message': 'orz'}) 51 | print test({'message': 'rz'}) 52 | print handle({'message': '来膜拜'}) 53 | -------------------------------------------------------------------------------- /slack_bot/plugins/pycoders.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import re 3 | from HTMLParser import HTMLParser 4 | 5 | import requests 6 | from bs4 import BeautifulSoup 7 | 8 | from slack_bot.ext import cache 9 | from utils import check_cache 10 | 11 | description = """ 12 | Pycoders Weekly。触发条件: "pycoders [list | ISSUE_ID] [私聊]"。比如: 13 | * pycoders 14 | * pycoders list 15 | * pycoders 20 16 | """ 17 | 18 | API = 'http://us4.campaign-archive1.com/generate-js/?u=9735795484d2e4c204da82a29&fid=1817&show=500' # noqa 19 | ISSUES_REGEX = re.compile(r'
(.*?)<\\/a><\\/div>') 20 | ISSUE_REGEX = re.compile( 21 | ur'(\d+\\/\d+\\/\d+).*href=\\"(.*)\\" title=\\"(.*?)\\"', re.UNICODE) # noqa 22 | TITLE_REGEX = re.compile(ur'Issue(.*)\)(\W*?):(\W*?)(.*)', re.UNICODE) # noqa 23 | SPAN_REGEX = re.compile(r'(.*)') 24 | 25 | GET_ISSUE_KEY = 'pycoders:issue:{0}' 26 | LIST_ISSUE_KEY = 'pycoders:issue:list' 27 | 28 | 29 | class MyHTMLParser(HTMLParser): 30 | def handle_starttag(self, tag, attrs): 31 | self._tag = tag 32 | for name, value in attrs: 33 | if name == 'href': 34 | self._href = value 35 | 36 | def handle_data(self, data): 37 | self._data = data 38 | 39 | 40 | def get_all_issues(): 41 | r = requests.get(API) 42 | issues = ISSUES_REGEX.findall(r.text) 43 | for issue in issues: 44 | date_, url, title = ISSUE_REGEX.search(issue).groups() 45 | date_ = date_.replace('\\', '') 46 | url = url.replace('\\', '') 47 | match = TITLE_REGEX.search(title) 48 | if match: 49 | no, _, _, title = match.groups() 50 | no = no.strip() 51 | title = title.strip().replace('\u00a0', '') 52 | else: 53 | title = title.replace('Pycoders Weekly', '').strip() 54 | no = '' 55 | yield date_, url, no, title 56 | 57 | 58 | def prase_tag(tag): 59 | text = '' 60 | parser = MyHTMLParser() 61 | count = 0 62 | block = [None] * 3 # [url, title, content] 63 | for t in tag.contents: 64 | t = unicode(t).strip() 65 | if not t or t in ('
') or 'Shared by' in t: 66 | continue 67 | parser.feed(t) 68 | if parser._tag == 'h2': 69 | if text: 70 | text += '\n\n' 71 | text += parser._data + '\n' 72 | elif parser._tag == 'a': 73 | if not count % 2: 74 | block[0] = parser._href 75 | count += 1 76 | elif parser._tag == 'span': 77 | if t.startswith('{2}'.format(*block) + '\n' 85 | block = [None] * 3 86 | parser._tag = None 87 | parser._href = None 88 | parser._data = None 89 | return text 90 | 91 | 92 | def parse_issue_page(url): 93 | r = requests.get(url) 94 | soup = BeautifulSoup(r.text) 95 | return '\n'.join([ 96 | prase_tag(soup.findAll('td', {'class': 'mcnTextContent'})[index]) 97 | for index in (6, 8, 9) 98 | ]) 99 | 100 | 101 | def get_issue(num=None): 102 | issues = list(get_all_issues()) 103 | if num is None: 104 | num = len(issues) 105 | try: 106 | issue = list(get_all_issues())[::-1][num-1] 107 | except IndexError: 108 | return u'找不到这期咯' 109 | return parse_issue_page(issue[1]) 110 | 111 | 112 | def list_issues(): 113 | return '\n'.join([ 114 | '{0} <{1} |Issue {2}: {3}>'.format(date_, url, no, title) 115 | for date_, url, no, title in get_all_issues() 116 | ]) 117 | 118 | 119 | def test(data): 120 | return 'pycoder' in data['message'] 121 | 122 | 123 | def handle(data): 124 | msg = data['message'].split() 125 | if len(msg) == 1: 126 | return check_cache(cache, get_issue) 127 | elif msg[1] == 'list': 128 | return check_cache(cache, list_issues) 129 | elif msg[1].isdigit(): 130 | return check_cache(cache, get_issue, int(msg[1])) 131 | return ('`pycoder`默认获得最近一次的weekly\n' 132 | '`pycoder list`获取全部weekly列表\n' 133 | '`pycoder X`获得第X次weekly') 134 | 135 | 136 | if __name__ == '__main__': 137 | print handle({'message': 'pycoder'}) 138 | print handle({'message': 'pycoder list'}) 139 | print handle({'message': 'pycoder 167'}) 140 | -------------------------------------------------------------------------------- /slack_bot/plugins/pythonweekly.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import re 3 | 4 | import requests 5 | from bs4 import BeautifulSoup 6 | 7 | from slack_bot.ext import cache 8 | from utils import check_cache 9 | from pycoders import MyHTMLParser 10 | 11 | URL = 'http://us2.campaign-archive1.com/home/?u=e2e180baf855ac797ef407fc7&id=9e26887fc5' # noqa 12 | ISSUE_REGEX = re.compile(r'(\d+\/\d+\/\d+).*Issue\W+(\d+)') 13 | GET_ISSUE_KEY = 'pythonweekly:issue:{0}' 14 | LIST_ISSUE_KEY = 'pythonweekly:issue:list' 15 | MAX_LENGTH = 120 16 | 17 | description = """ 18 | Python Weekly。触发条件: "pythonweekly [list | ISSUE_ID] [私聊]"。比如: 19 | * pythonweekly 20 | * pythonweekly list 21 | * pythonweekly 20 22 | """ 23 | 24 | 25 | def get_all_issues(): 26 | r = requests.get(URL) 27 | soup = BeautifulSoup(r.text) 28 | for li in soup.findAll('li', {'class': 'campaign'}): 29 | url = li.find('a').attrs.get('href') 30 | time, no = ISSUE_REGEX.search(li.text).groups() 31 | yield url, no, time 32 | 33 | 34 | def parse_issue_page(url): 35 | r = requests.get(url) 36 | soup = BeautifulSoup(r.text) 37 | tag = soup.find('td', {'class': 'defaultText'}) 38 | text = [] 39 | parser = MyHTMLParser() 40 | start = False 41 | block = [None] * 3 # [url, title, content] 42 | for t in tag.contents: 43 | t = unicode(t).strip() 44 | if not t or t in ('
'): 45 | continue 46 | parser.feed(t) 47 | if parser._data == 'News': 48 | start = True 49 | if not start: 50 | continue 51 | if parser._tag == 'a': 52 | block[1] = parser._data 53 | block[0] = parser._href 54 | elif '<' not in t: 55 | if parser._data < MAX_LENGTH: 56 | block[2] = parser._data 57 | else: 58 | block[2] = parser._data[:MAX_LENGTH] + '...' 59 | elif parser._tag == 'span': 60 | text.append('\n{}'.format(parser._data)) 61 | parser._tag = None 62 | parser._href = None 63 | parser._data = None 64 | if not filter(lambda x: x is None, block): 65 | text.append(u'<{0} |{1}>{2}'.format(*block)) 66 | block = [None] * 3 67 | return '\n'.join(text) 68 | 69 | 70 | def list_lastest_issues(): 71 | return '\n'.join([ 72 | '<{0} |Issue {1}: {2}>'.format(url, no, time) 73 | for url, no, time in get_all_issues() 74 | ]) 75 | 76 | 77 | def get_issue_pw(num=None): 78 | issues = list(get_all_issues()) 79 | if num is None: 80 | num = len(issues) 81 | try: 82 | issue = list(get_all_issues())[::-1][num-1] 83 | except IndexError: 84 | return u'找不到这期咯' 85 | return parse_issue_page(issue[0]) 86 | 87 | 88 | def test(data): 89 | return all([i in data['message'] for i in ['python', 'weekly']]) 90 | 91 | 92 | def handle(data): 93 | msg = data['message'].split() 94 | if len(msg) == 1: 95 | return check_cache(cache, get_issue_pw) 96 | elif msg[1] == 'list': 97 | return check_cache(cache, list_lastest_issues) 98 | elif msg[1].isdigit(): 99 | return check_cache(cache, get_issue_pw, int(msg[1])) 100 | return ('`pythonweekly`默认获得最近一次的weekly\n' 101 | '`pythonweekly list`获取最近20个weekly列表(找不到更早的了)\n' 102 | '`pythonweekly X`获得倒数第X次weekly(X不能超过20)') 103 | 104 | 105 | if __name__ == '__main__': 106 | print handle({'message': 'pythonweekly'}) 107 | print handle({'message': 'pythonweekly list'}) 108 | print handle({'message': 'pythonweekly 1'}) 109 | -------------------------------------------------------------------------------- /slack_bot/plugins/qiubai.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | """ 4 | Copyright (c) 2013 Xiangyu Ye 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | """ 25 | 26 | description = """ 27 | 糗事百科TOP10。触发条件: "糗百 | 笑话 [私聊]"。比如: 28 | * 糗百 29 | * 笑话 30 | """ 31 | 32 | # 糗事百科TOP10 33 | import urllib2 34 | import re 35 | import time 36 | import random 37 | 38 | from slack_bot.ext import cache 39 | 40 | key = time.strftime('%y-%m-%d') 41 | 42 | 43 | def test(data): 44 | return any(w in data['message'] for w in ['糗百', '笑话']) 45 | 46 | 47 | def handle(data): 48 | if cache is not None: 49 | r = cache.get(key) 50 | if r: 51 | return random.choice(r) 52 | r = urllib2.urlopen('http://feedproxy.feedburner.com/qiubai', timeout=60) 53 | p = r.read() 54 | r = re.findall('<\!\[CDATA\[

(.*)
', p) 55 | if r: 56 | if cache is not None: 57 | cache.set(key, r, 1800) 58 | return random.choice(r) 59 | else: 60 | raise Exception 61 | 62 | 63 | if __name__ == '__main__': 64 | print handle({'message': '糗百'}) 65 | print handle({'message': '笑话'}) 66 | -------------------------------------------------------------------------------- /slack_bot/plugins/simsimi.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8-*- 2 | 3 | """ 4 | Copyright (c) 2012 wong2 5 | Copyright (c) 2012 hupili 6 | 7 | Original Author: 8 | Wong2 9 | Changes Statement: 10 | Changes made by Pili Hu on 11 | Jan 13 2013: 12 | Support Keepalive by using requests.Session 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining 15 | a copy of this software and associated documentation files (the 16 | 'Software'), to deal in the Software without restriction, including 17 | without limitation the rights to use, copy, modify, merge, publish, 18 | distribute, sublicense, and/or sell copies of the Software, and to 19 | permit persons to whom the Software is furnished to do so, subject to 20 | the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | """ 33 | 34 | 35 | # 从simsimi读数据 36 | 37 | import sys 38 | sys.path.append('..') 39 | 40 | import requests 41 | import random 42 | 43 | try: 44 | from settings import SIMSIMI_KEY 45 | except: 46 | SIMSIMI_KEY = '50c086cb-5ea3-4190-bdd6-69787a540ec4' 47 | 48 | description = """ 49 | 色色的小黄鸡。触发条件:所有未触发其他插件的内容。 50 | """ 51 | 52 | COOKIE = """ 53 | sid=s%3AcsxS39Tq1oLXQj5WKdBN7UZz.T%2FdtU%2BGkt056rKQb 54 | %2BwmwD0iJXguRCsyRsv6745ftwfk; Filtering=0.0; Filtering=0.0; 55 | isFirst=1; isFirst=1; simsimi_uid=102256985; simsimi_uid=102256985; 56 | selected_nc_name=Chinese%20%u2013%20Simplified%20%28%u7C21%u9AD4%29; 57 | selected_nc_name=Chinese%20%u2013%20Simplified%20%28%u7C21%u9AD4%29; 58 | simsimi_makeup=undefined; simsimi_makeup=undefined; selected_nc=ch; 59 | selected_nc=ch; __utmt=1; __utma=119922954.1015526052.1433822720. 60 | 1433826650.1433836017.4; __utmb=119922954.8.9.1433836034315; 61 | __utmc=119922954; __utmz=119922954.1433822720.1.1.utmcsr=(direct) 62 | |utmccn=(direct)|utmcmd=(none) 63 | """ 64 | 65 | 66 | class SimSimi: 67 | 68 | def __init__(self): 69 | 70 | self.session = requests.Session() 71 | 72 | self.chat_url = ( 73 | 'http://www.simsimi.com/func/reqN?lc=ch&ft=0.0&req={0}' 74 | '&fl=http%3A%2F%2Fwww.simsimi.com%2Ftalk.htm&reqType=' 75 | ) 76 | self.api_url = ('http://sandbox.api.simsimi.com/request.p?' 77 | 'key=%s&lc=ch&ft=1.0&text=%s') 78 | 79 | if not SIMSIMI_KEY: 80 | self.initSimSimiCookie() 81 | 82 | def initSimSimiCookie(self): 83 | self.session.headers.update( 84 | {'User-Agent': ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5)' 85 | ' AppleWebKit/537.36 (KHTML, like Gecko)' 86 | ' Chrome/43.0.2357.81 Safari/537.36')} 87 | ) 88 | self.session.get('http://www.simsimi.com/talk.htm') 89 | self.session.headers.update( 90 | {'Referer': 'http://www.simsimi.com/talk.htm'}) 91 | self.session.get('http://www.simsimi.com/talk.htm?lc=ch') 92 | self.session.headers.update( 93 | {'Referer': 'http://www.simsimi.com/talk.htm?lc=ch'}) 94 | self.session.headers.update( 95 | {'Accept': 'application/json, text/javascript, */*; q=0.01'}) 96 | self.session.headers.update({'Accept-Encoding': 'gzip, deflate, sdch'}) 97 | self.session.headers.update( 98 | {'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4'}) 99 | self.session.headers.update({'Cache-Control': 'no-cache'}) 100 | self.session.headers.update({'Connection': 'keep-alive'}) 101 | self.session.headers.update( 102 | {'Content-Type': 'application/json; charset=utf-8'}) 103 | self.session.headers.update({'Host': 'www.simsimi.com'}) 104 | self.session.headers.update({'Pragma': 'no-cache'}) 105 | self.session.headers.update({'X-Requested-With': 'XMLHttpRequest'}) 106 | self.session.headers.update( 107 | {'Cookie': COOKIE}) 108 | 109 | def getSimSimiResult(self, message, method='normal'): 110 | if method == 'normal': 111 | print self.chat_url.format(message) 112 | r = self.session.get(self.chat_url.format(message)) 113 | else: 114 | url = self.api_url % (SIMSIMI_KEY, message) 115 | r = requests.get(url) 116 | return r 117 | 118 | def chat(self, message=''): 119 | if message: 120 | r = self.getSimSimiResult( 121 | message, 'normal' if not SIMSIMI_KEY else 'api') 122 | try: 123 | answer = r.json()['response'].encode('utf-8') 124 | return answer 125 | except: 126 | return random.choice(['呵呵', '。。。', '= =', '=。=']) 127 | else: 128 | return '叫我干嘛' 129 | 130 | simsimi = SimSimi() 131 | 132 | 133 | def test(data): 134 | return True 135 | 136 | 137 | def handle(data): 138 | return simsimi.chat(data['message']), None 139 | 140 | if __name__ == '__main__': 141 | print handle({'message': '最后一个问题'}) 142 | print handle({'message': '还有一个问题'}) 143 | print handle({'message': '其实我有三个问题'}) 144 | -------------------------------------------------------------------------------- /slack_bot/plugins/toutiao.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import requests 4 | 5 | from utils import datetime2timestamp, gen_attachment, trunc_utf8 6 | 7 | CHANNEL_MAPS = { 8 | '推荐': '__all__', 9 | '热点': 'news_hot', 10 | '社会': 'news_society', 11 | '娱乐': 'news_entertainment', 12 | '科技': 'news_tech', 13 | '军事': 'news_military', 14 | '美食': 'news_food', 15 | '游戏': 'news_game', 16 | '体育': 'news_sports' 17 | } 18 | 19 | description = """ 20 | 今日头条。触发条件: "头条 [%s] [带图] [私聊]"。比如: 21 | * 头条 22 | * 头条 娱乐 23 | """ % ' | '.join(CHANNEL_MAPS.keys()) 24 | 25 | 26 | API = 'http://toutiao.com/api/article/recent/?source=2&count=20&category={0}&max_behot_time={1}&utm_source=toutiao&offset=0' # noqa 27 | 28 | 29 | def get_content(channel): 30 | category = CHANNEL_MAPS.get(channel) 31 | r = requests.get(API.format(category, datetime2timestamp())) 32 | data = r.json()['data'] 33 | 34 | for i in data: 35 | text = (u'<{seo_url}|{title}> 赞{bury_count} 踩{digg_count} - ' 36 | '{source} {datetime}').format(**i) 37 | image_url = i.get('middle_image', '') 38 | if isinstance(image_url, dict): 39 | image_url = image_url['url'] 40 | attach = gen_attachment(trunc_utf8(i['abstract']), image_url, 41 | image_type='thumb', title=i['title'], 42 | title_link=i['seo_url'], fallback=False) 43 | yield text, attach 44 | 45 | 46 | def test(data): 47 | return any(w in data['message'] for w in ['toutiao', '头条']) 48 | 49 | 50 | def handle(data): 51 | msg = data['message'].split() 52 | channel = '推荐' if len(msg) == 1 else msg[1].strip() 53 | if channel not in CHANNEL_MAPS: 54 | return '目前可选的频道包含: {}'.format('|'.join(CHANNEL_MAPS.keys())) 55 | ret = [r for r in get_content(channel)] 56 | return '\n'.join([r[0] for r in ret]), [r[1] for r in ret] 57 | 58 | 59 | if __name__ == '__main__': 60 | print handle({'message': '头条'}) 61 | -------------------------------------------------------------------------------- /slack_bot/plugins/travel.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import re 3 | 4 | from flask import current_app 5 | 6 | from baidumap import travel_city, travel_attractions 7 | from weather import get_city 8 | from utils import chinese2digit, to_pinyin 9 | 10 | description = ''' 11 | 国内旅游推荐/景点介绍/X日游。触发条件: "xx旅游推荐|颐和园景点介绍|xx3(/三)日游"。比如: 12 | * 沈阳旅游推荐 13 | * 景点介绍颐和园 14 | * 颐和园景点介绍 15 | * 北京3日游 16 | ''' 17 | 18 | ATTRACTIONS_REGEX = re.compile(r'(.*?)景点介绍(.*?)') 19 | CITY_REGEX = re.compile(r'(旅游推荐)') 20 | DAYS_REGEX = re.compile(r'(.*)日游') 21 | 22 | 23 | def get_desc(regex, message): 24 | match = regex.search(message) 25 | if match: 26 | return next((m for m in match.groups() if m), None) 27 | 28 | 29 | def get_itinerary(res, details=False): 30 | if not res['itineraries']: 31 | return '没找到对应的行程' 32 | text = [] 33 | if details: 34 | text.append(res['description']) 35 | for i in res['itineraries']: 36 | text.append(u'类型: {0}: {1}'.format(i['name'], i['description'])) 37 | for index, it in enumerate(i['itineraries'], 1): 38 | text.append( 39 | u'第{}天 '.format(index) + u' ->'.join( 40 | [p['name'] for p in it['path']])) 41 | for t in ['description', 'dinning', 'accommodation']: 42 | text.append(it[t]) 43 | text.append('\n') 44 | return '\n'.join(text) 45 | 46 | 47 | def test(data): 48 | return get_city(data) and any([ 49 | regex.search(data['message']) for regex in [CITY_REGEX, DAYS_REGEX]]) \ 50 | or ATTRACTIONS_REGEX.search(data['message']) 51 | 52 | 53 | def handle(data): 54 | app = current_app 55 | if app is None: 56 | ak = '18691b8e4206238f331ad2e1ca88357e' 57 | else: 58 | ak = app.config.get('BAIDU_AK') 59 | message = data['message'] 60 | location = get_city(data) 61 | if location: 62 | message = message.replace(location.encode('utf-8'), '') 63 | days = get_desc(DAYS_REGEX, message) 64 | if days: 65 | if not isinstance(days, int): 66 | days = chinese2digit(days) 67 | res = travel_city(ak, location, days) 68 | return get_itinerary(res), None 69 | city = get_desc(CITY_REGEX, message) 70 | if city: 71 | res = travel_city(ak, location) 72 | return get_itinerary(res, details=True), None 73 | attractions = get_desc(ATTRACTIONS_REGEX, message) 74 | if attractions: 75 | return travel_attractions(ak, to_pinyin(attractions)), None 76 | return '没找到对应的旅游行程', None 77 | 78 | if __name__ == '__main__': 79 | print handle({'message': '北京三日游'}) 80 | print handle({'message': '北京旅游推荐'}) 81 | print handle({'message': '颐和园景点介绍'}) 82 | -------------------------------------------------------------------------------- /slack_bot/plugins/utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import re 4 | import random 5 | import shutil 6 | import calendar 7 | from datetime import datetime 8 | 9 | from flask import current_app 10 | import pytz 11 | import requests 12 | from slacker import Slacker 13 | from pypinyin import lazy_pinyin 14 | 15 | from consts import ONE_DAY, COLORS 16 | 17 | CANVAS_REGEX = re.compile(r'base64,(.*)') 18 | 19 | 20 | def check_cache(cache, fn, *args, **kwargs): 21 | timeout = kwargs.get('timeout', ONE_DAY) 22 | return (cache.cached(timeout=timeout)(fn) 23 | if cache is not None else fn)(*args, **kwargs) 24 | 25 | 26 | def timestamp2str(timestamp, fmt='%H:%M:%S', timezone='Asia/Shanghai'): 27 | dt = datetime.utcfromtimestamp(float(timestamp)).replace(tzinfo=pytz.utc) 28 | tz = pytz.timezone(timezone) 29 | return tz.normalize(dt.astimezone(tz)).strftime(fmt) 30 | 31 | 32 | def datetime2timestamp(dt=None, timezone='Asia/Shanghai'): 33 | if dt is None: 34 | dt = datetime.now() 35 | tz = pytz.timezone(timezone) 36 | dt = dt.replace(tzinfo=pytz.utc).astimezone(tz) 37 | return calendar.timegm(dt.timetuple()) 38 | 39 | 40 | def check_time(dt=None, timezone='Asia/Shanghai'): 41 | if dt is None: 42 | dt = datetime.now() 43 | tz = pytz.timezone(timezone) 44 | dt = dt.replace(tzinfo=pytz.utc).astimezone(tz) 45 | return 'day' if 6 <= dt.hour <= 18 else 'night' 46 | 47 | 48 | def to_pinyin(word): 49 | if not isinstance(word, unicode): 50 | word = word.decode('utf-8') 51 | return ''.join(lazy_pinyin(word)) 52 | 53 | 54 | def chinese2digit(ch): 55 | try: 56 | return ['一', '二', '三', '四', '五', '六', '七', 57 | '八', '九'].index(ch) + 1 58 | except ValueError: 59 | return ch 60 | 61 | 62 | def upload_image(canvas_or_url, image_type, app=None, filename=None, 63 | tmp_dir=None, deleted=False): 64 | here = os.path.abspath(os.path.dirname(__file__)) 65 | if tmp_dir is None: 66 | tmp_dir = os.path.join(here, 'data') 67 | match = CANVAS_REGEX.search(canvas_or_url) 68 | if match: 69 | imgstr = match.group(1) 70 | if filename is None: 71 | filename = os.path.join(tmp_dir, '{}.png'.format(imgstr[:20])) 72 | output = open(filename, 'wb') 73 | output.write(imgstr.decode('base64')) 74 | output.close() 75 | else: 76 | r = requests.get(canvas_or_url, stream=True) 77 | if filename is None: 78 | filename = canvas_or_url.rsplit('/', 1)[1] 79 | with open(filename, 'wb') as f: 80 | r.raw.decode_content = True 81 | shutil.copyfileobj(r.raw, f) 82 | 83 | if image_type == 'thumb': 84 | image_type = 'thumb_360' 85 | if app is None: 86 | token = 'xoxp-4231087425-4231087427-4463321974-03a74ae' 87 | else: 88 | token = app.config.get('SLACK_CHAT_TOKEN') 89 | slack = Slacker(token) 90 | ret = slack.files.upload(filename) 91 | if deleted: 92 | os.remove(filename) 93 | try: 94 | return ret.body['file'][image_type] 95 | except KeyError: 96 | return ret.body['file']['url'] 97 | 98 | 99 | def check_canvas(image_url, image_type): 100 | match = CANVAS_REGEX.search(image_url) 101 | if match: 102 | return upload_image(image_url, image_type) 103 | else: 104 | return image_url 105 | 106 | 107 | def convert2unicode(s): 108 | if not isinstance(s, unicode): 109 | return s.decode('utf-8') 110 | return s 111 | 112 | 113 | def convert2str(s): 114 | if isinstance(s, unicode): 115 | return s.encode('utf-8') 116 | return s 117 | 118 | 119 | def gen_attachment(text, image_url='', image_type='url', title='', 120 | title_link='', color='random', fallback=True): 121 | if color == 'random': 122 | color = random.choice(COLORS) 123 | key = 'thumb_url' if image_type == 'thumb' else 'image_url' 124 | attachment = {'text': text, 'title_link': title_link, 'color': color, 125 | key: check_canvas(image_url, image_type), 126 | 'title': title} 127 | if fallback: 128 | attachment.update({ 129 | 'fallback': u'{0} {1}'.format( 130 | convert2unicode(title), convert2unicode(text)) 131 | }) 132 | return attachment 133 | 134 | 135 | def trunc_utf8(s, length=50): 136 | s = convert2unicode(s) 137 | if s > length: 138 | s = s[:length] + '...' 139 | return s 140 | 141 | 142 | def replaced(message, rep_words): 143 | for word in rep_words: 144 | message = message.replace(word, '', 1) 145 | return message 146 | -------------------------------------------------------------------------------- /slack_bot/plugins/v2ex.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import cgi 3 | from datetime import datetime 4 | 5 | from lxml import etree 6 | import requests 7 | 8 | from slack_bot.ext import cache 9 | from consts import ONE_DAY, ONE_MINUTE 10 | 11 | NODES = ['linux', 'macosx', 'create', 'android', 'python', 'programmer', 'vim', 12 | 'jobs', 'react', 'beijing', 'redis', 'mongodb', 'tornado', 'emacs', 13 | 'django', 'flask', 'go', 'lisp'] 14 | TAGS = ['title', 'published', 'updated', 'content'] 15 | MAX_FEEDS_LEN = 50 16 | FEED_URL = 'https://www.v2ex.com/feed/{0}.xml' 17 | TOPIC_URL = 'http://www.v2ex.com/t/{0}' 18 | NODE_URL = 'http://www.v2ex.com/go/{0}' 19 | NODE_KEY = 'v2ex:node:{0}' 20 | TOPIC_KEY = 'v2ex:topic:{0}' 21 | NODE_UPDATE_KEY = 'v2ex:update:{0}' # node 缓存时间根据发布topic频繁程度而不同 22 | description = """ 23 | v2ex feed. 触发条件: "v2ex [%s] [私聊]". 比如: 24 | * v2ex 25 | * v2ex python 26 | """ % ' | '.join(NODES) 27 | 28 | 29 | def get_updated_interval(node_name, feeds, default=ONE_DAY): 30 | updated_times = [] 31 | for id in feeds: 32 | topic = cache.get(TOPIC_KEY.format(id)) 33 | if topic: 34 | updated_times.append(topic['updated']) 35 | else: 36 | print 'topic {} not cached!'.format(id) 37 | min = default 38 | for i in range(len(updated_times) - 1): 39 | sec = (updated_times[i] - updated_times[i + 1]).total_seconds() 40 | if sec < min: 41 | min = sec 42 | if min < ONE_MINUTE: 43 | min = ONE_MINUTE 44 | break 45 | return min 46 | 47 | 48 | def fetch2cache(node_name): 49 | print 'Fetch {}'.format(node_name) 50 | r = requests.get(FEED_URL.format(node_name), verify=False) 51 | root = etree.fromstring(r.text.encode('utf-8')) 52 | entries = root.findall('{http://www.w3.org/2005/Atom}entry') 53 | node_key = NODE_KEY.format(node_name) 54 | feeds = cache.get(node_key) or [] 55 | new_feeds = [] 56 | for entry in entries: 57 | topic = {} 58 | id = entry[2].text.rpartition('/')[-1] 59 | key = TOPIC_KEY.format(id) 60 | for el in entry: 61 | for tag in TAGS: 62 | if el.tag.endswith(tag): 63 | res = el.text 64 | if tag in ('published', 'updated'): 65 | res = datetime.strptime(res, '%Y-%m-%dT%H:%M:%SZ') 66 | topic[tag] = res 67 | topic['node'] = node_name 68 | cache.set(key, topic, ONE_DAY) 69 | new_feeds.append(id) 70 | if new_feeds: 71 | new_feeds += feeds[:MAX_FEEDS_LEN - len(new_feeds)] 72 | interval = get_updated_interval(node_name, new_feeds) 73 | cache.set(node_key, new_feeds, interval) 74 | 75 | 76 | def fetch(force=False): 77 | ids = set() 78 | for node in NODES: 79 | node_key = NODE_KEY.format(node) 80 | res = cache.get(node_key) 81 | if res and not force: 82 | ids.update(res) 83 | continue 84 | fetch2cache(node) 85 | ids.update(cache.get(node_key)) 86 | ids = list(set(ids)) 87 | 88 | def _key(id): 89 | topic = cache.get(TOPIC_KEY.format(id)) 90 | if not topic: 91 | return datetime(1970, 1, 1) 92 | return topic['published'] 93 | 94 | return sorted(ids, key=_key, reverse=True)[:MAX_FEEDS_LEN] 95 | 96 | 97 | def test(data): 98 | return data['message'].startswith('v2ex') 99 | 100 | 101 | def handle(data): 102 | message = data['message'] 103 | ids = fetch(force=(True if u'刷新' in message else False)) 104 | contents = [] 105 | for id in ids: 106 | topic = cache.get(TOPIC_KEY.format(id)) 107 | if not topic: 108 | continue 109 | node = topic['node'] 110 | msg = u'<{0}|{1} [{2}]> <{3}|{4}>'.format(TOPIC_URL.format(id), 111 | cgi.escape(topic['title']), 112 | topic['published'], 113 | NODE_URL.format(node), 114 | node) 115 | contents.append(msg) 116 | return '\n'.join(contents) 117 | 118 | 119 | if __name__ == '__main__': 120 | pass 121 | # 由于更换了cache引入方式,这里的测试暂不可用,如有需要在`python manager send v2ex` 122 | # from flask import Flask 123 | # from flask_cache import Cache 124 | # app = Flask(__name__) 125 | # cache = Cache() 126 | # cache.init_app(app, config={'CACHE_TYPE': 'simple'}) 127 | # with app.app_context(): 128 | # print handle({'message': 'v2ex'}) 129 | -------------------------------------------------------------------------------- /slack_bot/plugins/weather.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # 天气 4 | import os 5 | import re 6 | import cPickle as pickle 7 | 8 | from flask import current_app 9 | 10 | from baidumap import weather 11 | from utils import gen_attachment, check_time 12 | 13 | 14 | description = """ 15 | 今天的天气情况, 触发条件: "[城市名称] 天气 [私聊]"。比如: 16 | * 天气 17 | * 上海天气 18 | """ 19 | 20 | DAY = {0: u'今天', 1: u'明天', 2: u'后天'} 21 | TEMPERATURE_REGEX = re.compile(ur'(\d+)℃') 22 | 23 | 24 | def get_city(data): 25 | cityidDict = pickle.load(file( 26 | os.path.join(os.path.dirname(__file__), 27 | 'data' + os.path.sep + 'cityid'), 'r')) 28 | return next( 29 | (c for c in cityidDict if c.encode('utf8') in data['message']), False) 30 | 31 | 32 | def test(data): 33 | return '天气' in data['message'] and get_city(data) 34 | 35 | 36 | def handle(data): 37 | app = current_app 38 | if app is None: 39 | ak = '18691b8e4206238f331ad2e1ca88357e' 40 | else: 41 | ak = app.config.get('BAIDU_AK') 42 | city = get_city(data) 43 | if not city: 44 | return '不会自己去看天气预报啊' 45 | res = weather(ak, city)[:3] 46 | ret = [] 47 | attaches = [] 48 | for idx, day in enumerate(res): 49 | if idx == 0: 50 | current = TEMPERATURE_REGEX.search(day['date']).groups()[0] 51 | text = u'{0}: {1} {2} {3} 温度: {4}'.format( 52 | DAY[idx], current, day['weather'], 53 | day['wind'], day['temperature']) 54 | else: 55 | text = u'{0}: {1} {2} 温度: {3}'.format( 56 | DAY[idx], day['weather'], 57 | day['wind'], day['temperature']) 58 | ret.append(text) 59 | type = 'dayPictureUrl' if check_time() == 'day' else 'dayPictureUrl' 60 | attaches.append(gen_attachment(text, day[type], image_type='thumb', 61 | title=u'{}天气预报'.format(city), 62 | title_link='')) 63 | return '\n'.join(ret), attaches 64 | 65 | 66 | if __name__ == '__main__': 67 | print test({'message': '天气怎么样'}) 68 | print test({'message': '北京天气怎么样'}) 69 | print handle({'message': '北京天气怎么样'}) 70 | -------------------------------------------------------------------------------- /slack_bot/plugins/wikipedia.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8-*- 2 | 3 | """ 4 | Copyright (c) 2012 yangzhe1991 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | """ 25 | 26 | # 维基百科 27 | 28 | from bs4 import BeautifulSoup 29 | 30 | import requests 31 | import re 32 | 33 | description = """ 34 | 维基百科。触发条件: "什么是 内容 [私聊]"。比如: 35 | * 什么是薛定谔方程啊 36 | * 什么是CSS 37 | """ 38 | 39 | 40 | def test(data): 41 | return '什么是' in data['message'] 42 | 43 | 44 | def handle(data): 45 | m = re.search('(?<=什么是)(.+?)(?=啊|那|呢|哈|!|。|?|\?|\s|\Z)', data['message']) 46 | if m and m.groups(): 47 | return wikipedia(m.groups()[0]) 48 | raise Exception 49 | 50 | 51 | def wikipedia(title): 52 | r = requests.get('http://zh.wikipedia.org/wiki/{0}'.format(title), 53 | timeout=10) 54 | soup = BeautifulSoup(r.text) 55 | result = soup.find(id='mw-content-text').find('p').text 56 | return result if result else '我还不知道哎' 57 | 58 | if __name__ == '__main__': 59 | for message in ['什么是SVM ????', '什么是薛定谔方程啊', '什么是CSS?']: 60 | data = {'message': message} 61 | print message, test(data) 62 | if test(data): 63 | print handle(data) 64 | -------------------------------------------------------------------------------- /slack_bot/settings.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # debug模式主要用在gunicorn启动的时候可能看到错误堆栈 4 | DEBUG = True 5 | SECRET_KEY = 'o\xdd\x02I\x0b\xbbBP4\x97\xab\xe0GF\xfba\x14_\x03\xa9\xe8\xfa\xf8c' # noqa 6 | 7 | # clask chat token 注册地址: https://api.slack.com/web 8 | SLACK_CHAT_TOKEN = 'xoxp-4231087425-4231087427-4463321974-03a74a' 9 | # 你希望slack的outgoing-webhook调用你的回调的路由 10 | SLACK_CALLBACK = '/slack_callback' 11 | # 使用Github-issue插件需要指定组织的地址 12 | ORG_NAME = 'python-cn' 13 | # 百度地图api key, 注册地址: http://lbsyun.baidu.com/apiconsole/key 14 | BAIDU_AK = '18691b8e4206238f331ad2e1ca88357e' 15 | # simsim key, 注册地址: http://developer.simsimi.com/ 16 | SIMSIMI_KEY = '50c086cb-5ea3-4190-bdd6-69787a540ec4' 17 | # 大众点评应用, 注册地址: http://developer.dianping.com/dashboard/info/app 18 | DIANPING_APPKEY = '41502445' 19 | DIANPING_SECRET = 'f0c2cc0b4f1048bebffc1527acbaeeb8' 20 | 21 | # Flask-cache的类型, 默认为SimpleCache 22 | CACHE_TYPE = 'simple' 23 | # 假如使用RedisCache, 也就是CACHE_TYPE = 'redis'需要配置CACHE_REDIS_URL 24 | CACHE_REDIS_URL = 'redis://user:password@localhost:6379/0' 25 | 26 | # 使用`python manage.py send`时候的模拟数据 27 | TEST_DATA = { 28 | 'token': 'jLGMzrZn3P1lS2sD848KpPuN', 29 | 'text': 'text', 30 | 'team_id': 'T0001', 31 | 'team_domain': 'example', 32 | 'channel_id': 'C2147483705', 33 | 'channel_name': 'channel_name', 34 | 'timestamp': '1355517523', 35 | 'user_id': 'U2147483697', 36 | 'user_name': 'Steve', 37 | 'trigger_word': '', 38 | } 39 | 40 | try: 41 | from local_settings import * # noqa 42 | except ImportError: 43 | print('You may need rename local_settings.py.example ' 44 | 'to local_settings.py, then update your settings') 45 | -------------------------------------------------------------------------------- /slack_bot/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from multiprocessing import TimeoutError 4 | from multiprocessing.pool import ThreadPool 5 | from functools import wraps 6 | 7 | 8 | def timeout(seconds, default="timeout"): 9 | def decorator(fn): 10 | @wraps(fn) 11 | def wrapper(*args, **kwargs): 12 | pool = ThreadPool(processes=1) 13 | async_result = pool.apply_async(fn, args=args, kwds=kwargs) 14 | try: 15 | return async_result.get(seconds) 16 | except TimeoutError: 17 | if callable(default): 18 | return default() 19 | return default 20 | return wrapper 21 | return decorator 22 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from slack_bot.app import create_app 3 | 4 | app = create_app() 5 | --------------------------------------------------------------------------------