├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── bin └── charmander ├── charmander ├── __init__.py ├── charmander.py ├── dummyserver.py ├── extensions │ ├── __init__.py │ ├── calc.py │ ├── commit.py │ ├── emoji.py │ ├── emojidic.py │ ├── google.py │ ├── help.py │ ├── log.py │ ├── urban.py │ ├── wiki.py │ └── youtube.py └── server.py ├── output_ZrAcxK.gif ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | #####=== Python ===##### 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | 58 | 59 | #####====Pycharm======== 60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 61 | 62 | *.iml 63 | 64 | ## Directory-based project format: 65 | .idea/ 66 | # if you remove the above rule, at least ignore the following: 67 | 68 | # User-specific stuff: 69 | # .idea/workspace.xml 70 | # .idea/tasks.xml 71 | # .idea/dictionaries 72 | 73 | # Sensitive or high-churn files: 74 | # .idea/dataSources.ids 75 | # .idea/dataSources.xml 76 | # .idea/sqlDataSources.xml 77 | # .idea/dynamic.xml 78 | # .idea/uiDesigner.xml 79 | 80 | # Gradle: 81 | # .idea/gradle.xml 82 | # .idea/libraries 83 | 84 | # Mongo Explorer plugin: 85 | # .idea/mongoSettings.xml 86 | 87 | ## File-based project format: 88 | *.ipr 89 | *.iws 90 | 91 | ## Plugin-specific files: 92 | 93 | # IntelliJ 94 | out/ 95 | 96 | # mpeltonen/sbt-idea plugin 97 | .idea_modules/ 98 | 99 | # JIRA plugin 100 | # JIRA plugin 101 | atlassian-ide-plugin.xml 102 | 103 | # Crashlytics plugin (for Android Studio and IntelliJ) 104 | com_crashlytics_export_strings.xml 105 | crashlytics.properties 106 | crashlytics-build.properties -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.6' 4 | - '2.7' 5 | - '3.4' 6 | - "pypy" 7 | install: pip install -r requirements.txt && python setup.py install 8 | script: make test 9 | notifications: 10 | slack: 11 | secure: qTTpPJZRjkFNlWUQd4M4odA+/QnuEqoD3ole8tQA+oFo2hzA7toQbAorgKd20mJ0VMLbrTxUDeCHwuLH6EYs0k9gnaDx7J8gaohW3ePHU6vncGfSh/9r8jCLHOdD/23ySm6D4TKR5SV7izNS+4WyqLqeLugdk5rFCwW4JJS118U= 12 | email: false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vikesh Tiwari 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: testall 2 | testall: requirements 3 | tox 4 | 5 | .PHONY: test 6 | test: install 7 | LANG=en_us.UTF-8 NOSE_COVER_PACKAGE=charmander nosetests -s --nologcapture --with-coverage 8 | 9 | .PHONY: clean 10 | clean: 11 | rm -rf build dist charmander.egg-info 12 | 13 | .PHONY: run 14 | run: install 15 | bin/charmander 16 | 17 | .PHONY: repl 18 | repl: install 19 | bin/charmander -t 20 | 21 | # non-empty if we're on python 2.6 22 | PYTHON2_6 = $(shell python --version 2>&1 | grep 2.6) 23 | 24 | .PHONY: requirements 25 | requirements: 26 | pip install -r requirements.txt 27 | ifneq ($(PYTHON2_6), ) 28 | pip install -r requirements-2.6.txt 29 | endif 30 | 31 | .PHONY: install 32 | install: requirements 33 | python setup.py install 34 | make clean 35 | 36 | .PHONY: publish 37 | publish: 38 | pandoc -s -w rst README.md -o README.rs 39 | python setup.py sdist upload 40 | rm README.rs 41 | 42 | .PHONY: flake8 43 | flake8: 44 | flake8 charmander test 45 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python bin/charmander -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Charmander 2 |

3 | charmander 4 |

5 | 6 | --- 7 | ### Donate ($1) - Help me in creating more awesome open source projects 8 | 9 | [](https://www.paypal.me/vikeshtiwari/1) 10 | 11 | --- 12 | 13 | Everything right in your slack channel! 14 | ------------------------------------ 15 | 16 | Charmander is a bot for Slack to do everything for you. You don't need to open browser to search for something because Charmander searches everything from Google and shows top results to you right in your slack channel! 17 | 18 | [![Charmander Bot Demo](http://img.youtube.com/vi/u4D6xyIHjY4/0.jpg)](http://www.youtube.com/watch?v=u4D6xyIHjY4) 19 | 20 | Charmander Supports 21 | ------------------ 22 | - Google [Search Anything from slack] - [Done](https://github.com/vicky002/Charmander/blob/master/charmander/extensions/google.py)! 23 | - Mathematical Calculations [Do mathematical calculations] - done! 24 | - Weather [Check current weather in your city] 25 | - Maps [See maps ] 26 | - urban [See meaning of words] - [Done](https://github.com/vicky002/Charmander/blob/master/charmander/extensions/urban.py)! 27 | - Commits [Get random commit messages] - [Done](https://github.com/vicky002/Charmander/blob/master/charmander/extensions/commit.py)! 28 | - Gifs [Post gifs] 29 | - Emoji [Post emojis] - [Done](https://github.com/vicky002/Charmander/blob/master/charmander/extensions/emoji.py)! 30 | - Stock [Check stocks ] 31 | - Images [Post images] 32 | - YouTube [search videos] - [Done](https://github.com/vicky002/Charmander/blob/master/charmander/extensions/youtube.py)! 33 | - Shuffler [Shuffle anything] 34 | - Wiki [Search from Wikipedia] - [Done](https://github.com/vicky002/Charmander/blob/master/charmander/extensions/wiki.py)! 35 | 36 | **Why am I doing this?** 37 | I created a bot for Slack [The L](https://github.com/vicky002/slack-TheL) which uses Wolframalpha API, check [Tutorial here](http://eulercoder.me/posts/How-to-create-Slack-Bot-using-wolframalpha-API/). Wolframalpha allows only 2000 calls per month. It was hard to set up and was not of that much use. Charmander directly searches from the Google to answer your queries. And it's all free to use. Its very easy to setup in your slack channel. 38 | 39 | # Installation 40 | 41 | [Will be updated soon] 42 | 43 | # Getting Started 44 | 45 | [Will be updated soon] 46 | 47 | # Contributing 48 | 49 | [Will be updated soon] 50 | 51 | 52 | # Issues 53 | 54 | Check [Issue tracker](https://github.com/vicky002/Charmander/issues) for all the issues. 55 | 56 | # Licence 57 | 58 | [MIT](https://github.com/vicky002/Charmander/blob/master/LICENSE) Licence (c) [Vikesh Tiwari](https://github.com/vicky002) 59 | 60 | Built with :heart: in python 61 | -------------------------------------------------------------------------------- /bin/charmander: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from charmander import main 4 | import argparse 5 | 6 | parser = argparse.ArgumentParser(description="Run the charmander chatbot for slack") 7 | parser.add_argument('--test', '-t', dest='test', action='store_true', required=False, 8 | help='Enter command line mode to enter a charmander repl') 9 | parser.add_argument('--hook',dest='hook', action='store', default='message', 10 | help='Specify the hook to test. (Defaults to "message")') 11 | parser.add_argument('-c', dest="command", help='run a single command') 12 | parser.add_argument('--database', '-d', dest='database_name', default='charmander.sqlite3', 13 | help="Where to store the charmander sqlite database. Defaults to charmander.sqlite") 14 | parser.add_argument('--pluginpath', '-pp', dest='pluginpath', default=None, 15 | help="The path where charmander should look to find its plugins") 16 | args = parser.parse_args() 17 | 18 | main(args) -------------------------------------------------------------------------------- /charmander/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | -------------------------------------------------------------------------------- /charmander/charmander.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function # why? Read here : http://python3porting.com/noconv.html 4 | import copy # read here : https://docs.python.org/2/library/copy.html 5 | import traceback # read : https://docs.python.org/2/library/traceback.html 6 | import sqlite3 # read : https://docs.python.org/2/library/sqlite3.html 7 | import logging # read : https://docs.python.org/2/library/logging.html 8 | import os # read : https://docs.python.org/2/library/os.html 9 | import functools # read : https://docs.python.org/2/library/functools.html 10 | import re # read : https://docs.python.org/2/library/re.html 11 | import sys # read : https://docs.python.org/2/library/sys.html 12 | import importlib 13 | import ipdb 14 | from glob import glob 15 | 16 | 17 | from server import CharmanderServer 18 | from dummyserver import DummyServer 19 | 20 | from slackrtm import SlackClient 21 | from slackrtm.server import SlackConnectionError, SlackLoginError 22 | 23 | 24 | CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) 25 | DIR = functools.partial(os.path.join,CURRENT_DIR) 26 | 27 | PYTHON_3 = sys.version_info[0] > 2 28 | 29 | logger = logging.getLogger(__name__) 30 | 31 | 32 | class InvalidExtensionDir(Exception): 33 | def __int__(self, extensiondir): 34 | ''' 35 | :param extensiondir: check if the extension directory is valid 36 | :return: 37 | ''' 38 | message = "Unable to find extension directory {0}".format(extensiondir) 39 | super(InvalidExtensionDir, self).__init__(message) 40 | 41 | 42 | def init_log(config): 43 | ''' 44 | :param config: log configuration 45 | :return: 46 | ''' 47 | loglevel = config.get("loglevel", logging.INFO) 48 | logformat = config.get("logformat", '%(asctime)s:%(levelname)s:%(name)s:%(message)s') 49 | if config.get("logfile"): 50 | logging.basicConfig(filename=config.get("logfile"), format=logformat, level=loglevel) 51 | else: 52 | logging.basicConfig(format=logformat, level=loglevel) 53 | 54 | 55 | def init_extensions(extensiondir): 56 | ''' 57 | :param extensiondir: get all the extensions (python files) also check for errors 58 | :return: 59 | ''' 60 | if extensiondir and not os.path.isdir(extensiondir): 61 | raise InvalidExtensionDir(extensiondir) 62 | 63 | if not extensiondir: 64 | extensiondir = DIR("extensions") 65 | 66 | logger.debug("externsiondir: {0}".format(extensiondir)) 67 | 68 | if os.path.isdir(extensiondir): 69 | extensionfiles = glob(os.path.join(extensiondir, "[!_]*.py")) 70 | extensions = strip_extension(os.path.basename(e) for e in extensionfiles ) 71 | else: 72 | 73 | logger.debug("trying to get pkg_resources") 74 | import pkg_resources 75 | try: 76 | extensions = strip_extension( 77 | pkg_resources.resource_listdir(__name__, "extensions")) 78 | except OSError: 79 | raise InvalidExtensionDir(extensiondir) 80 | 81 | hooks = {} 82 | 83 | old_path = copy.deepcopy(sys.path) 84 | sys.path.insert(0,extensiondir) 85 | 86 | for extension in extensions: 87 | logger.debug("extension: {0}".format(extension)) 88 | try: 89 | mod = importlib.import_module(extension) 90 | modname = mod.__name__ 91 | for hook in re.findall("on_(\w+)"," ".join(dir(mod))): 92 | hook_fun = getattr(mod, "on_"+hook) 93 | logger.debug("extension : attching %s hook for %s ", hook, modname) 94 | hooks.setdefault(hook, []).append(hook_fun) 95 | 96 | if mod.__doc__: 97 | firstline = mod.__doc__.split('\n')[0] 98 | hooks.setdefault('help', {})[modname] = firstline 99 | hooks.setdefault('extension Help', {})[modname] = mod.__doc__ 100 | # we will have exntensions , they can have any number of errors 101 | # we have to make sure that they don't affect our servers 102 | except: 103 | logger.warning("import failed on module {0}, module not loaded".format(extension)) 104 | logger.warning("{0}".format(sys.exc_info()[0])) 105 | logger.warning("{0}".format(traceback.format_exc())) 106 | 107 | sys.path = old_path 108 | return hooks 109 | 110 | 111 | def strip_extension(list): 112 | ''' 113 | :param list: Get list and strip the list 114 | :return: 115 | ''' 116 | return (os.path.splitext(l)[0] for l in list) 117 | 118 | 119 | def run_hook(hooks, hook, *args): 120 | ''' 121 | :param hooks: Get hooks and loop through it 122 | :param hook: 123 | :param args: 124 | :return: return responses 125 | ''' 126 | responses = [] 127 | for hook in hooks.get(hook, []): 128 | try: 129 | h = hook(*args) 130 | if h: 131 | responses.append(h) 132 | except: 133 | logger.warning("Failed to run the extension {0}, module not loaded".format(hook)) 134 | logger.warning("{0}".format(sys.exc_info()[0])) 135 | logger.warning("{0}".format(traceback.format_exc())) 136 | 137 | return responses 138 | 139 | 140 | def handle_bot_message(event, server): 141 | ''' 142 | :param event: Handle bot events 143 | :param server: 144 | :return: 145 | ''' 146 | try: 147 | bot = server.slack.server.bots[event["bot_id"]] 148 | except KeyError: 149 | logger.debug('bot_meesage event {0} has no bot'.format(event)) 150 | return 151 | return "\n".join(run_hook(server.hooks, "bot_message", event, server)) 152 | 153 | 154 | def handle_message(event, server): 155 | # Bot message handling 156 | subtype = event.get("subtype", "") 157 | if subtype == "message_changed": 158 | return 159 | 160 | if subtype == "bot_message": 161 | return handle_bot_message(event, server) 162 | 163 | try: 164 | msg_user = server.slack.server.users[event["user"]] 165 | except KeyError: 166 | logger.debug("event {0} has no user".format(event)) 167 | return 168 | 169 | return "\n".join(run_hook(server.hooks, "message", event, server)) 170 | 171 | event_handlers = { 172 | "message": handle_message, 173 | } 174 | 175 | 176 | def handle_event(event, server): 177 | # Event Handling 178 | handler = event_handlers.get(event.get("type")) 179 | if handler: 180 | return handler(event, server) 181 | 182 | 183 | def getif(config, name, envvar): 184 | if envvar in os.environ: 185 | config[name] = os.environ.get(envvar) 186 | 187 | 188 | def init_config(): 189 | # get and initialize all the configurations 190 | config = {} 191 | getif(config, "token", "SLACK_TOKEN") 192 | getif(config, "loglevel", "CHARMANDER_LOGLEVEL") 193 | getif(config, "logfile", "CHARMANDER_LOGFILE") 194 | getif(config, "logformat", "CHARMANDER_LOGFORMAT") 195 | return config 196 | 197 | 198 | def loop(server , test_loop=None): 199 | """ 200 | :param server: is a charmander server object 201 | :param test_loop: is the number of times to run the loop 202 | :return: 203 | """ 204 | try: 205 | loop_without_activity = 0 206 | while test_loop is None or test_loop > 0: 207 | start = time.time() 208 | loop_without_activity += 1 209 | 210 | events = server.slack.rtm_read() 211 | for event in events: 212 | loop_without_activity = 0 213 | logger.debug("got {0}".format(event.get("type", event))) 214 | response = handle_event(event, server) 215 | if response: 216 | server.slack.rtm_send_message(event["channel"], response) 217 | 218 | # Server.slack.post_message to send messages from a loop hook 219 | run_hook(server.hooks, "loop", server) 220 | # The Slack RTM API docs say: 221 | # 222 | # > When there is no other activity clients should send a ping 223 | # > every few seconds 224 | # 225 | # So, if we've gone >5 seconds without any activity, send a ping. 226 | # If the connection has broken, this will reveal it so slack can 227 | # quit 228 | if loop_without_activity > 5: 229 | server.slack.server.ping() 230 | loop_without_activity = 0 231 | 232 | end = time.time() 233 | runtime = start - end 234 | time.sleep(max(1-runtime, 0)) 235 | 236 | if test_loop: 237 | test_loop -= 1 238 | except KeyboardInterrupt: 239 | if os.environ.get("CHARMANDER_DEBUG"): 240 | import ipdb; ipdb.set_trace() 241 | raise 242 | 243 | 244 | def relevant_environ(): 245 | return dict((key, os.environ[key]) 246 | for key in os.environ 247 | if key.startswith("SLACK") or key.startswith("CHARMANDER")) 248 | 249 | 250 | def init_server(args, config, Server=CharmanderServer, Client=SlackClient): 251 | # Initialize Server using SlackClient 252 | 253 | init_log(config) 254 | logger.debug("config: {0}".format(config)) 255 | db = init_db(args.database_name) 256 | hooks = init_extensions(args.extensionpath) 257 | try: 258 | slack = Client(config["token"]) 259 | except KeyError: 260 | logger.error(""" Charmander is unable to find a slack token. The environment variables charmander sees are: 261 | {0} and the current config is: {1} 262 | """.format(relevant_environ(), config)) 263 | raise 264 | server = Server(slack, config, hooks, db) 265 | return server 266 | 267 | 268 | def decode(str_, codec='utf-8'): 269 | # Decode string 270 | if PYTHON_3: 271 | return str_ 272 | else: 273 | return str_.decode(codec) 274 | 275 | 276 | def encode(str_, codec='utf-8'): 277 | # encode string 278 | if PYTHON_3: 279 | return str_ 280 | else: 281 | return str_.encode(codec) 282 | 283 | 284 | def main(args): 285 | config = init_config() 286 | if args.test: 287 | init_log(config) 288 | return repl(DummyServer(), args) 289 | elif args.command is not None: 290 | init_log(config) 291 | cmd = decode(args.command) 292 | print(run_cmd(cmd, DummyServer(), args.hook, args.extensionpath)) 293 | 294 | server = init_server(args, config) 295 | 296 | try: 297 | server.slack.rtm_connect() 298 | 299 | run_hook(server.hooks, "init", server) 300 | 301 | loop(server) 302 | except SlackConnectionError: 303 | logger.warn("Unable to connect to slack. Bad Gateway? :( ") 304 | raise 305 | except SlackLoginError: 306 | logger.warn("Login Failed, invalid token <{0}>".format(config["token"])) 307 | 308 | 309 | def run_cmd(cmd, server, hook, extennsionpath): 310 | # command line handling 311 | server.hooks = init_extensions(extennsionpath) 312 | event = {'type': hook, 'text': cmd, "user": "2", "ts": time.time(), "team": None, "channel": "repl_channel"} 313 | return encode(handle_event(event, server)) 314 | 315 | 316 | try: 317 | input = raw_input 318 | except NameError: 319 | pass 320 | 321 | 322 | def repl(server, args): 323 | try: 324 | while 1: 325 | cmd = decode(input("charmander> ")) 326 | if cmd.lower() == "quit" or cmd.lower() == "exit": 327 | return 328 | 329 | print(run_cmd(cmd, server, args.hook, args.extensionpath)) 330 | except (EOFError, KeyboardInterrupt): 331 | print() 332 | pass 333 | 334 | 335 | def init_db(database_file): 336 | # initialize db 337 | return sqlite3.connect(database_file) -------------------------------------------------------------------------------- /charmander/dummyserver.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | from slackrtm.server import User, Bot 4 | 5 | 6 | class DummyServer(object): 7 | def __init__(self, slack=None, config=None, hooks=None, db=None): 8 | self.slack = slack or DummySlack() 9 | self.config = config 10 | self.hooks = hooks 11 | self.db = db 12 | 13 | def query(self, sql, *params): 14 | if not self.db: 15 | return None 16 | 17 | cursor = self.db.cursor() 18 | cursor.execute(sql, params) 19 | rows = cursor.fetchall() 20 | cursor.close() 21 | self.db.commit() 22 | return rows 23 | 24 | 25 | class DummySlack(object): 26 | def __init__(self, server=None, users=None, events=None): 27 | self.server = server or DummySlackServer(users=users) 28 | self.posted_messages = None 29 | self.events = events if events else [] 30 | 31 | def post_message(self, message, **kwargs): 32 | self.posted_messages = (message, kwargs) 33 | 34 | def rtm_read(self): 35 | return self.events.pop() if self.events else [] 36 | 37 | 38 | class DummySlackServer(object): 39 | def __init__(self, botname="Charmander", users=None, bots=None): 40 | self.login_data = { 41 | "self": { 42 | "name": botname, 43 | } 44 | } 45 | self.username = "replbot" 46 | 47 | self.users = users if users else { 48 | "1": User(self, "charmander_test", 1, "", 0), 49 | "2": User(self, "vickyuser", 2, "", 0), 50 | "3": User(self, "slackbot", 3, "", 0), 51 | } 52 | 53 | self.bots = bots if bots else { 54 | "1": Bot("1", "otherbot", [], False) 55 | } 56 | -------------------------------------------------------------------------------- /charmander/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | -------------------------------------------------------------------------------- /charmander/extensions/calc.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | " " " calc will return the google calculator result for " " " 4 | 5 | from bs4 import BeautifulSoup 6 | import re 7 | 8 | try: 9 | from urllib import quote 10 | except ImportError: 11 | from urllib2 import quote 12 | import requests 13 | 14 | 15 | def calc(eq): 16 | query = quote(eq) 17 | url = "https://encrypted.google.com/search?hl=en&q={0}".format(query) 18 | soup = BeautifulSoup(requests.get(url).text, "html5lib") 19 | 20 | answer = soup.findAll("h2", attrs={"class": "r"}) 21 | if not answer: 22 | answer = soup.findAll("span", attrs={"class": "_m3b"}) 23 | if not answer: 24 | return ":crying_cat_face: sorry, google doesn't have an answer for this equation :(" 25 | 26 | answer = answer[0].text.replace(u"\xa0", ",") 27 | return answer 28 | 29 | 30 | def on_message(msg, server): 31 | text = msg.get("text", "") 32 | match = re.findall(r"~calc (.*)", text) 33 | if not match: 34 | return 35 | 36 | return calc(match[0].encode("utf8")) 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /charmander/extensions/commit.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | # Get random commit messages 4 | """ ~commit returns random commit messages""" 5 | 6 | import re 7 | import requests 8 | 9 | 10 | def commit(): 11 | return requests.get("http://whatthecommit.com/index.txt").text 12 | 13 | 14 | def on_message(msg, server): 15 | text = msg.get("text", "") 16 | match = re.findall(r"~commit( .*)?", text) 17 | 18 | if not match: 19 | return 20 | 21 | return commit() 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /charmander/extensions/emoji.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | """ 3 | <~emoji> N : This will return N random emojis! 4 | """ 5 | 6 | import re 7 | import random 8 | from emojidic import emojiDict 9 | 10 | 11 | def randomelect(dic): 12 | keys = list(dic.keys()) 13 | i = random.randint(0, len(keys)-1) # get random emoji 14 | return dic[keys[i]] 15 | 16 | 17 | def emoji(n=1): 18 | emoji = [] 19 | for i in range(n): 20 | emoji.append(randomelect(emojiDict)) 21 | 22 | return "".join(emoji) 23 | 24 | 25 | def on_message(msg, server): 26 | text = msg.get("text", "") 27 | match = re.findall(r"(~emoji)\s*(\d+)*", text) 28 | if not match: 29 | return 30 | 31 | N = 1 if not match[0][1] else int(match[0][1]) 32 | 33 | return emoji(N) 34 | 35 | 36 | -------------------------------------------------------------------------------- /charmander/extensions/emojidic.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is based on emoji (https://github.com/kyokomi/emoji). 3 | # 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2014 kyokomi 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | # 26 | 27 | emojiDict = { 28 | ":capricorn:": u"\U00002651", 29 | ":end:": u"\U0001f51a", 30 | ":no_mobile_phones:": u"\U0001f4f5", 31 | ":couple:": u"\U0001f46b", 32 | ":snowman:": u"\U000026c4", 33 | ":sunrise_over_mountains:": u"\U0001f304", 34 | ":suspension_railway:": u"\U0001f69f", 35 | ":arrows_counterclockwise:": u"\U0001f504", 36 | ":bug:": u"\U0001f41b", 37 | ":confused:": u"\U0001f615", 38 | ":dress:": u"\U0001f457", 39 | ":honeybee:": u"\U0001f41d", 40 | ":waning_crescent_moon:": u"\U0001f318", 41 | ":balloon:": u"\U0001f388", 42 | ":bus:": u"\U0001f68c", 43 | ":package:": u"\U0001f4e6", 44 | ":pencil2:": u"\U0000270f", 45 | ":rage:": u"\U0001f621", 46 | ":space_invader:": u"\U0001f47e", 47 | ":white_medium_small_square:": u"\U000025fd", 48 | ":fast_forward:": u"\U000023e9", 49 | ":rice_cracker:": u"\U0001f358", 50 | ":incoming_envelope:": u"\U0001f4e8", 51 | ":sa:": u"\U0001f202", 52 | ":womens:": u"\U0001f6ba", 53 | ":arrow_right:": u"\U000027a1", 54 | ":construction_worker:": u"\U0001f477", 55 | ":notes:": u"\U0001f3b6", 56 | ":goat:": u"\U0001f410", 57 | ":grey_question:": u"\U00002754", 58 | ":lantern:": u"\U0001f3ee", 59 | ":rice_scene:": u"\U0001f391", 60 | ":running:": u"\U0001f3c3", 61 | ":ferris_wheel:": u"\U0001f3a1", 62 | ":musical_score:": u"\U0001f3bc", 63 | ":sparkle:": u"\U00002747", 64 | ":wink:": u"\U0001f609", 65 | ":art:": u"\U0001f3a8", 66 | ":clock330:": u"\U0001f55e", 67 | ":minidisc:": u"\U0001f4bd", 68 | ":no_entry_sign:": u"\U0001f6ab", 69 | ":wind_chime:": u"\U0001f390", 70 | ":cyclone:": u"\U0001f300", 71 | ":herb:": u"\U0001f33f", 72 | ":leopard:": u"\U0001f406", 73 | ":banana:": u"\U0001f34c", 74 | ":handbag:": u"\U0001f45c", 75 | ":honey_pot:": u"\U0001f36f", 76 | ":ok:": u"\U0001f197", 77 | ":hearts:": u"\U00002665", 78 | ":passport_control:": u"\U0001f6c2", 79 | ":moyai:": u"\U0001f5ff", 80 | ":smile:": u"\U0001f604", 81 | ":tiger2:": u"\U0001f405", 82 | ":twisted_rightwards_arrows:": u"\U0001f500", 83 | ":children_crossing:": u"\U0001f6b8", 84 | ":cow:": u"\U0001f42e", 85 | ":point_up:": u"\U0000261d", 86 | ":house:": u"\U0001f3e0", 87 | ":man_with_turban:": u"\U0001f473", 88 | ":mountain_railway:": u"\U0001f69e", 89 | ":vibration_mode:": u"\U0001f4f3", 90 | ":blowfish:": u"\U0001f421", 91 | ":it:": u"\U0001f1ee\U0001f1f9", 92 | ":oden:": u"\U0001f362", 93 | ":clock3:": u"\U0001f552", 94 | ":lollipop:": u"\U0001f36d", 95 | ":train:": u"\U0001f68b", 96 | ":scissors:": u"\U00002702", 97 | ":triangular_ruler:": u"\U0001f4d0", 98 | ":wedding:": u"\U0001f492", 99 | ":flashlight:": u"\U0001f526", 100 | ":secret:": u"\U00003299", 101 | ":sushi:": u"\U0001f363", 102 | ":blue_car:": u"\U0001f699", 103 | ":cd:": u"\U0001f4bf", 104 | ":milky_way:": u"\U0001f30c", 105 | ":mortar_board:": u"\U0001f393", 106 | ":crown:": u"\U0001f451", 107 | ":speech_balloon:": u"\U0001f4ac", 108 | ":bento:": u"\U0001f371", 109 | ":grey_exclamation:": u"\U00002755", 110 | ":hotel:": u"\U0001f3e8", 111 | ":keycap_ten:": u"\U0001f51f", 112 | ":newspaper:": u"\U0001f4f0", 113 | ":outbox_tray:": u"\U0001f4e4", 114 | ":racehorse:": u"\U0001f40e", 115 | ":laughing:": u"\U0001f606", 116 | ":black_large_square:": u"\U00002b1b", 117 | ":books:": u"\U0001f4da", 118 | ":eight_spoked_asterisk:": u"\U00002733", 119 | ":heavy_check_mark:": u"\U00002714", 120 | ":m:": u"\U000024c2", 121 | ":wave:": u"\U0001f44b", 122 | ":bicyclist:": u"\U0001f6b4", 123 | ":cocktail:": u"\U0001f378", 124 | ":european_castle:": u"\U0001f3f0", 125 | ":point_down:": u"\U0001f447", 126 | ":tokyo_tower:": u"\U0001f5fc", 127 | ":battery:": u"\U0001f50b", 128 | ":dancer:": u"\U0001f483", 129 | ":repeat:": u"\U0001f501", 130 | ":ru:": u"\U0001f1f7\U0001f1fa", 131 | ":new_moon:": u"\U0001f311", 132 | ":church:": u"\U000026ea", 133 | ":date:": u"\U0001f4c5", 134 | ":earth_americas:": u"\U0001f30e", 135 | ":footprints:": u"\U0001f463", 136 | ":libra:": u"\U0000264e", 137 | ":mountain_cableway:": u"\U0001f6a0", 138 | ":small_red_triangle_down:": u"\U0001f53b", 139 | ":top:": u"\U0001f51d", 140 | ":sunglasses:": u"\U0001f60e", 141 | ":abcd:": u"\U0001f521", 142 | ":cl:": u"\U0001f191", 143 | ":ski:": u"\U0001f3bf", 144 | ":book:": u"\U0001f4d6", 145 | ":hourglass_flowing_sand:": u"\U000023f3", 146 | ":stuck_out_tongue_closed_eyes:": u"\U0001f61d", 147 | ":cold_sweat:": u"\U0001f630", 148 | ":headphones:": u"\U0001f3a7", 149 | ":confetti_ball:": u"\U0001f38a", 150 | ":gemini:": u"\U0000264a", 151 | ":new:": u"\U0001f195", 152 | ":pray:": u"\U0001f64f", 153 | ":watch:": u"\U0000231a", 154 | ":coffee:": u"\U00002615", 155 | ":ghost:": u"\U0001f47b", 156 | ":on:": u"\U0001f51b", 157 | ":pouch:": u"\U0001f45d", 158 | ":taxi:": u"\U0001f695", 159 | ":hocho:": u"\U0001f52a", 160 | ":yum:": u"\U0001f60b", 161 | ":heavy_plus_sign:": u"\U00002795", 162 | ":tada:": u"\U0001f389", 163 | ":arrow_heading_down:": u"\U00002935", 164 | ":clock530:": u"\U0001f560", 165 | ":poultry_leg:": u"\U0001f357", 166 | ":elephant:": u"\U0001f418", 167 | ":gb:": u"\U0001f1ec\U0001f1e7", 168 | ":mahjong:": u"\U0001f004", 169 | ":rice:": u"\U0001f35a", 170 | ":musical_note:": u"\U0001f3b5", 171 | ":beginner:": u"\U0001f530", 172 | ":small_red_triangle:": u"\U0001f53a", 173 | ":tomato:": u"\U0001f345", 174 | ":clock1130:": u"\U0001f566", 175 | ":japanese_castle:": u"\U0001f3ef", 176 | ":sun_with_face:": u"\U0001f31e", 177 | ":four:": u"\U00000034\U000020e3", 178 | ":microphone:": u"\U0001f3a4", 179 | ":tennis:": u"\U0001f3be", 180 | ":arrow_up_down:": u"\U00002195", 181 | ":cn:": u"\U0001f1e8\U0001f1f3", 182 | ":horse_racing:": u"\U0001f3c7", 183 | ":no_bicycles:": u"\U0001f6b3", 184 | ":snail:": u"\U0001f40c", 185 | ":free:": u"\U0001f193", 186 | ":beetle:": u"\U0001f41e", 187 | ":black_small_square:": u"\U000025aa", 188 | ":file_folder:": u"\U0001f4c1", 189 | ":hushed:": u"\U0001f62f", 190 | ":skull:": u"\U0001f480", 191 | ":ab:": u"\U0001f18e", 192 | ":rocket:": u"\U0001f680", 193 | ":sweet_potato:": u"\U0001f360", 194 | ":guitar:": u"\U0001f3b8", 195 | ":poodle:": u"\U0001f429", 196 | ":tulip:": u"\U0001f337", 197 | ":large_orange_diamond:": u"\U0001f536", 198 | ":-1:": u"\U0001f44e", 199 | ":chart_with_upwards_trend:": u"\U0001f4c8", 200 | ":de:": u"\U0001f1e9\U0001f1ea", 201 | ":grapes:": u"\U0001f347", 202 | ":ideograph_advantage:": u"\U0001f250", 203 | ":japanese_ogre:": u"\U0001f479", 204 | ":telephone:": u"\U0000260e", 205 | ":clock230:": u"\U0001f55d", 206 | ":hourglass:": u"\U0000231b", 207 | ":leftwards_arrow_with_hook:": u"\U000021a9", 208 | ":sparkler:": u"\U0001f387", 209 | ":black_joker:": u"\U0001f0cf", 210 | ":clock730:": u"\U0001f562", 211 | ":first_quarter_moon_with_face:": u"\U0001f31b", 212 | ":man:": u"\U0001f468", 213 | ":clock4:": u"\U0001f553", 214 | ":fishing_pole_and_fish:": u"\U0001f3a3", 215 | ":tophat:": u"\U0001f3a9", 216 | ":white_medium_square:": u"\U000025fb", 217 | ":mega:": u"\U0001f4e3", 218 | ":spaghetti:": u"\U0001f35d", 219 | ":dart:": u"\U0001f3af", 220 | ":girl:": u"\U0001f467", 221 | ":womans_hat:": u"\U0001f452", 222 | ":bullettrain_front:": u"\U0001f685", 223 | ":department_store:": u"\U0001f3ec", 224 | ":heartbeat:": u"\U0001f493", 225 | ":palm_tree:": u"\U0001f334", 226 | ":swimmer:": u"\U0001f3ca", 227 | ":yellow_heart:": u"\U0001f49b", 228 | ":arrow_upper_right:": u"\U00002197", 229 | ":clock2:": u"\U0001f551", 230 | ":high_heel:": u"\U0001f460", 231 | ":arrow_double_up:": u"\U000023eb", 232 | ":cry:": u"\U0001f622", 233 | ":dvd:": u"\U0001f4c0", 234 | ":e-mail:": u"\U0001f4e7", 235 | ":baby_bottle:": u"\U0001f37c", 236 | ":cool:": u"\U0001f192", 237 | ":floppy_disk:": u"\U0001f4be", 238 | ":iphone:": u"\U0001f4f1", 239 | ":minibus:": u"\U0001f690", 240 | ":rooster:": u"\U0001f413", 241 | ":three:": u"\U00000033\U000020e3", 242 | ":white_small_square:": u"\U000025ab", 243 | ":cancer:": u"\U0000264b", 244 | ":question:": u"\U00002753", 245 | ":sake:": u"\U0001f376", 246 | ":birthday:": u"\U0001f382", 247 | ":dog2:": u"\U0001f415", 248 | ":loudspeaker:": u"\U0001f4e2", 249 | ":arrow_up_small:": u"\U0001f53c", 250 | ":camel:": u"\U0001f42b", 251 | ":koala:": u"\U0001f428", 252 | ":mag_right:": u"\U0001f50e", 253 | ":soccer:": u"\U000026bd", 254 | ":bike:": u"\U0001f6b2", 255 | ":ear_of_rice:": u"\U0001f33e", 256 | ":shit:": u"\U0001f4a9", 257 | ":u7981:": u"\U0001f232", 258 | ":bath:": u"\U0001f6c0", 259 | ":baby:": u"\U0001f476", 260 | ":lock_with_ink_pen:": u"\U0001f50f", 261 | ":necktie:": u"\U0001f454", 262 | ":bikini:": u"\U0001f459", 263 | ":blush:": u"\U0001f60a", 264 | ":heartpulse:": u"\U0001f497", 265 | ":pig_nose:": u"\U0001f43d", 266 | ":straight_ruler:": u"\U0001f4cf", 267 | ":u6e80:": u"\U0001f235", 268 | ":gift:": u"\U0001f381", 269 | ":traffic_light:": u"\U0001f6a5", 270 | ":hibiscus:": u"\U0001f33a", 271 | ":couple_with_heart:": u"\U0001f491", 272 | ":pushpin:": u"\U0001f4cc", 273 | ":u6709:": u"\U0001f236", 274 | ":walking:": u"\U0001f6b6", 275 | ":grinning:": u"\U0001f600", 276 | ":hash:": u"\U00000023\U000020e3", 277 | ":radio_button:": u"\U0001f518", 278 | ":raised_hand:": u"\U0000270b", 279 | ":shaved_ice:": u"\U0001f367", 280 | ":barber:": u"\U0001f488", 281 | ":cat:": u"\U0001f431", 282 | ":heavy_exclamation_mark:": u"\U00002757", 283 | ":ice_cream:": u"\U0001f368", 284 | ":mask:": u"\U0001f637", 285 | ":pig2:": u"\U0001f416", 286 | ":triangular_flag_on_post:": u"\U0001f6a9", 287 | ":arrow_upper_left:": u"\U00002196", 288 | ":bee:": u"\U0001f41d", 289 | ":beer:": u"\U0001f37a", 290 | ":black_nib:": u"\U00002712", 291 | ":exclamation:": u"\U00002757", 292 | ":dog:": u"\U0001f436", 293 | ":fire:": u"\U0001f525", 294 | ":ant:": u"\U0001f41c", 295 | ":broken_heart:": u"\U0001f494", 296 | ":chart:": u"\U0001f4b9", 297 | ":clock1:": u"\U0001f550", 298 | ":bomb:": u"\U0001f4a3", 299 | ":virgo:": u"\U0000264d", 300 | ":a:": u"\U0001f170", 301 | ":fork_and_knife:": u"\U0001f374", 302 | ":copyright:": u"\U000000a9", 303 | ":curly_loop:": u"\U000027b0", 304 | ":full_moon:": u"\U0001f315", 305 | ":shoe:": u"\U0001f45e", 306 | ":european_post_office:": u"\U0001f3e4", 307 | ":ng:": u"\U0001f196", 308 | ":office:": u"\U0001f3e2", 309 | ":raising_hand:": u"\U0001f64b", 310 | ":revolving_hearts:": u"\U0001f49e", 311 | ":aquarius:": u"\U00002652", 312 | ":electric_plug:": u"\U0001f50c", 313 | ":meat_on_bone:": u"\U0001f356", 314 | ":mens:": u"\U0001f6b9", 315 | ":briefcase:": u"\U0001f4bc", 316 | ":ship:": u"\U0001f6a2", 317 | ":anchor:": u"\U00002693", 318 | ":ballot_box_with_check:": u"\U00002611", 319 | ":bear:": u"\U0001f43b", 320 | ":beers:": u"\U0001f37b", 321 | ":dromedary_camel:": u"\U0001f42a", 322 | ":nut_and_bolt:": u"\U0001f529", 323 | ":construction:": u"\U0001f6a7", 324 | ":golf:": u"\U000026f3", 325 | ":toilet:": u"\U0001f6bd", 326 | ":blue_book:": u"\U0001f4d8", 327 | ":boom:": u"\U0001f4a5", 328 | ":deciduous_tree:": u"\U0001f333", 329 | ":kissing_closed_eyes:": u"\U0001f61a", 330 | ":smiley_cat:": u"\U0001f63a", 331 | ":fuelpump:": u"\U000026fd", 332 | ":kiss:": u"\U0001f48b", 333 | ":clock10:": u"\U0001f559", 334 | ":sheep:": u"\U0001f411", 335 | ":white_flower:": u"\U0001f4ae", 336 | ":boar:": u"\U0001f417", 337 | ":currency_exchange:": u"\U0001f4b1", 338 | ":facepunch:": u"\U0001f44a", 339 | ":flower_playing_cards:": u"\U0001f3b4", 340 | ":person_frowning:": u"\U0001f64d", 341 | ":poop:": u"\U0001f4a9", 342 | ":satisfied:": u"\U0001f606", 343 | ":8ball:": u"\U0001f3b1", 344 | ":disappointed_relieved:": u"\U0001f625", 345 | ":panda_face:": u"\U0001f43c", 346 | ":ticket:": u"\U0001f3ab", 347 | ":us:": u"\U0001f1fa\U0001f1f8", 348 | ":waxing_crescent_moon:": u"\U0001f312", 349 | ":dragon:": u"\U0001f409", 350 | ":gun:": u"\U0001f52b", 351 | ":mount_fuji:": u"\U0001f5fb", 352 | ":new_moon_with_face:": u"\U0001f31a", 353 | ":star2:": u"\U0001f31f", 354 | ":grimacing:": u"\U0001f62c", 355 | ":confounded:": u"\U0001f616", 356 | ":congratulations:": u"\U00003297", 357 | ":custard:": u"\U0001f36e", 358 | ":frowning:": u"\U0001f626", 359 | ":maple_leaf:": u"\U0001f341", 360 | ":police_car:": u"\U0001f693", 361 | ":cloud:": u"\U00002601", 362 | ":jeans:": u"\U0001f456", 363 | ":fish:": u"\U0001f41f", 364 | ":wavy_dash:": u"\U00003030", 365 | ":clock5:": u"\U0001f554", 366 | ":santa:": u"\U0001f385", 367 | ":japan:": u"\U0001f5fe", 368 | ":oncoming_taxi:": u"\U0001f696", 369 | ":whale:": u"\U0001f433", 370 | ":arrow_forward:": u"\U000025b6", 371 | ":kissing_heart:": u"\U0001f618", 372 | ":bullettrain_side:": u"\U0001f684", 373 | ":fearful:": u"\U0001f628", 374 | ":moneybag:": u"\U0001f4b0", 375 | ":runner:": u"\U0001f3c3", 376 | ":mailbox:": u"\U0001f4eb", 377 | ":sandal:": u"\U0001f461", 378 | ":zzz:": u"\U0001f4a4", 379 | ":apple:": u"\U0001f34e", 380 | ":arrow_heading_up:": u"\U00002934", 381 | ":family:": u"\U0001f46a", 382 | ":heavy_minus_sign:": u"\U00002796", 383 | ":saxophone:": u"\U0001f3b7", 384 | ":u5272:": u"\U0001f239", 385 | ":black_square_button:": u"\U0001f532", 386 | ":bouquet:": u"\U0001f490", 387 | ":love_letter:": u"\U0001f48c", 388 | ":metro:": u"\U0001f687", 389 | ":small_blue_diamond:": u"\U0001f539", 390 | ":thought_balloon:": u"\U0001f4ad", 391 | ":arrow_up:": u"\U00002b06", 392 | ":no_pedestrians:": u"\U0001f6b7", 393 | ":smirk:": u"\U0001f60f", 394 | ":blue_heart:": u"\U0001f499", 395 | ":large_blue_diamond:": u"\U0001f537", 396 | ":vs:": u"\U0001f19a", 397 | ":v:": u"\U0000270c", 398 | ":wheelchair:": u"\U0000267f", 399 | ":couplekiss:": u"\U0001f48f", 400 | ":tent:": u"\U000026fa", 401 | ":purple_heart:": u"\U0001f49c", 402 | ":relaxed:": u"\U0000263a", 403 | ":accept:": u"\U0001f251", 404 | ":green_heart:": u"\U0001f49a", 405 | ":pouting_cat:": u"\U0001f63e", 406 | ":tram:": u"\U0001f68a", 407 | ":bangbang:": u"\U0000203c", 408 | ":collision:": u"\U0001f4a5", 409 | ":convenience_store:": u"\U0001f3ea", 410 | ":person_with_blond_hair:": u"\U0001f471", 411 | ":uk:": u"\U0001f1ec\U0001f1e7", 412 | ":peach:": u"\U0001f351", 413 | ":tired_face:": u"\U0001f62b", 414 | ":bread:": u"\U0001f35e", 415 | ":mailbox_closed:": u"\U0001f4ea", 416 | ":open_mouth:": u"\U0001f62e", 417 | ":pig:": u"\U0001f437", 418 | ":put_litter_in_its_place:": u"\U0001f6ae", 419 | ":u7a7a:": u"\U0001f233", 420 | ":bulb:": u"\U0001f4a1", 421 | ":clock9:": u"\U0001f558", 422 | ":envelope_with_arrow:": u"\U0001f4e9", 423 | ":pisces:": u"\U00002653", 424 | ":baggage_claim:": u"\U0001f6c4", 425 | ":egg:": u"\U0001f373", 426 | ":sweat_smile:": u"\U0001f605", 427 | ":boat:": u"\U000026f5", 428 | ":fr:": u"\U0001f1eb\U0001f1f7", 429 | ":heavy_division_sign:": u"\U00002797", 430 | ":muscle:": u"\U0001f4aa", 431 | ":paw_prints:": u"\U0001f43e", 432 | ":arrow_left:": u"\U00002b05", 433 | ":black_circle:": u"\U000026ab", 434 | ":kissing_smiling_eyes:": u"\U0001f619", 435 | ":star:": u"\U00002b50", 436 | ":steam_locomotive:": u"\U0001f682", 437 | ":1234:": u"\U0001f522", 438 | ":clock130:": u"\U0001f55c", 439 | ":kr:": u"\U0001f1f0\U0001f1f7", 440 | ":monorail:": u"\U0001f69d", 441 | ":school:": u"\U0001f3eb", 442 | ":seven:": u"\U00000037\U000020e3", 443 | ":baby_chick:": u"\U0001f424", 444 | ":bridge_at_night:": u"\U0001f309", 445 | ":hotsprings:": u"\U00002668", 446 | ":rose:": u"\U0001f339", 447 | ":love_hotel:": u"\U0001f3e9", 448 | ":princess:": u"\U0001f478", 449 | ":ramen:": u"\U0001f35c", 450 | ":scroll:": u"\U0001f4dc", 451 | ":tropical_fish:": u"\U0001f420", 452 | ":heart_eyes_cat:": u"\U0001f63b", 453 | ":information_desk_person:": u"\U0001f481", 454 | ":mouse:": u"\U0001f42d", 455 | ":no_smoking:": u"\U0001f6ad", 456 | ":post_office:": u"\U0001f3e3", 457 | ":stars:": u"\U0001f320", 458 | ":arrow_double_down:": u"\U000023ec", 459 | ":unlock:": u"\U0001f513", 460 | ":arrow_backward:": u"\U000025c0", 461 | ":hand:": u"\U0000270b", 462 | ":hospital:": u"\U0001f3e5", 463 | ":ocean:": u"\U0001f30a", 464 | ":mountain_bicyclist:": u"\U0001f6b5", 465 | ":octopus:": u"\U0001f419", 466 | ":sos:": u"\U0001f198", 467 | ":dizzy_face:": u"\U0001f635", 468 | ":tongue:": u"\U0001f445", 469 | ":train2:": u"\U0001f686", 470 | ":checkered_flag:": u"\U0001f3c1", 471 | ":orange_book:": u"\U0001f4d9", 472 | ":sound:": u"\U0001f509", 473 | ":aerial_tramway:": u"\U0001f6a1", 474 | ":bell:": u"\U0001f514", 475 | ":dragon_face:": u"\U0001f432", 476 | ":flipper:": u"\U0001f42c", 477 | ":ok_woman:": u"\U0001f646", 478 | ":performing_arts:": u"\U0001f3ad", 479 | ":postal_horn:": u"\U0001f4ef", 480 | ":clock1030:": u"\U0001f565", 481 | ":email:": u"\U00002709", 482 | ":green_book:": u"\U0001f4d7", 483 | ":point_up_2:": u"\U0001f446", 484 | ":high_brightness:": u"\U0001f506", 485 | ":running_shirt_with_sash:": u"\U0001f3bd", 486 | ":bookmark:": u"\U0001f516", 487 | ":sob:": u"\U0001f62d", 488 | ":arrow_lower_right:": u"\U00002198", 489 | ":point_left:": u"\U0001f448", 490 | ":purse:": u"\U0001f45b", 491 | ":sparkles:": u"\U00002728", 492 | ":black_medium_small_square:": u"\U000025fe", 493 | ":pound:": u"\U0001f4b7", 494 | ":rabbit:": u"\U0001f430", 495 | ":woman:": u"\U0001f469", 496 | ":negative_squared_cross_mark:": u"\U0000274e", 497 | ":open_book:": u"\U0001f4d6", 498 | ":smiling_imp:": u"\U0001f608", 499 | ":spades:": u"\U00002660", 500 | ":baseball:": u"\U000026be", 501 | ":fountain:": u"\U000026f2", 502 | ":joy:": u"\U0001f602", 503 | ":lipstick:": u"\U0001f484", 504 | ":partly_sunny:": u"\U000026c5", 505 | ":ram:": u"\U0001f40f", 506 | ":red_circle:": u"\U0001f534", 507 | ":cop:": u"\U0001f46e", 508 | ":green_apple:": u"\U0001f34f", 509 | ":registered:": u"\U000000ae", 510 | ":+1:": u"\U0001f44d", 511 | ":crying_cat_face:": u"\U0001f63f", 512 | ":innocent:": u"\U0001f607", 513 | ":mobile_phone_off:": u"\U0001f4f4", 514 | ":underage:": u"\U0001f51e", 515 | ":dolphin:": u"\U0001f42c", 516 | ":busts_in_silhouette:": u"\U0001f465", 517 | ":umbrella:": u"\U00002614", 518 | ":angel:": u"\U0001f47c", 519 | ":small_orange_diamond:": u"\U0001f538", 520 | ":sunflower:": u"\U0001f33b", 521 | ":link:": u"\U0001f517", 522 | ":notebook:": u"\U0001f4d3", 523 | ":oncoming_bus:": u"\U0001f68d", 524 | ":bookmark_tabs:": u"\U0001f4d1", 525 | ":calendar:": u"\U0001f4c6", 526 | ":izakaya_lantern:": u"\U0001f3ee", 527 | ":mans_shoe:": u"\U0001f45e", 528 | ":name_badge:": u"\U0001f4db", 529 | ":closed_lock_with_key:": u"\U0001f510", 530 | ":fist:": u"\U0000270a", 531 | ":id:": u"\U0001f194", 532 | ":ambulance:": u"\U0001f691", 533 | ":musical_keyboard:": u"\U0001f3b9", 534 | ":ribbon:": u"\U0001f380", 535 | ":seedling:": u"\U0001f331", 536 | ":tv:": u"\U0001f4fa", 537 | ":football:": u"\U0001f3c8", 538 | ":nail_care:": u"\U0001f485", 539 | ":seat:": u"\U0001f4ba", 540 | ":alarm_clock:": u"\U000023f0", 541 | ":money_with_wings:": u"\U0001f4b8", 542 | ":relieved:": u"\U0001f60c", 543 | ":womans_clothes:": u"\U0001f45a", 544 | ":lips:": u"\U0001f444", 545 | ":clubs:": u"\U00002663", 546 | ":house_with_garden:": u"\U0001f3e1", 547 | ":sunrise:": u"\U0001f305", 548 | ":monkey:": u"\U0001f412", 549 | ":six:": u"\U00000036\U000020e3", 550 | ":smiley:": u"\U0001f603", 551 | ":feet:": u"\U0001f43e", 552 | ":waning_gibbous_moon:": u"\U0001f316", 553 | ":yen:": u"\U0001f4b4", 554 | ":baby_symbol:": u"\U0001f6bc", 555 | ":signal_strength:": u"\U0001f4f6", 556 | ":boy:": u"\U0001f466", 557 | ":busstop:": u"\U0001f68f", 558 | ":computer:": u"\U0001f4bb", 559 | ":night_with_stars:": u"\U0001f303", 560 | ":older_woman:": u"\U0001f475", 561 | ":parking:": u"\U0001f17f", 562 | ":trumpet:": u"\U0001f3ba", 563 | ":100:": u"\U0001f4af", 564 | ":sweat_drops:": u"\U0001f4a6", 565 | ":wc:": u"\U0001f6be", 566 | ":b:": u"\U0001f171", 567 | ":cupid:": u"\U0001f498", 568 | ":five:": u"\U00000035\U000020e3", 569 | ":part_alternation_mark:": u"\U0000303d", 570 | ":snowboarder:": u"\U0001f3c2", 571 | ":warning:": u"\U000026a0", 572 | ":white_large_square:": u"\U00002b1c", 573 | ":zap:": u"\U000026a1", 574 | ":arrow_down_small:": u"\U0001f53d", 575 | ":clock430:": u"\U0001f55f", 576 | ":expressionless:": u"\U0001f611", 577 | ":phone:": u"\U0000260e", 578 | ":roller_coaster:": u"\U0001f3a2", 579 | ":lemon:": u"\U0001f34b", 580 | ":one:": u"\U00000031\U000020e3", 581 | ":christmas_tree:": u"\U0001f384", 582 | ":hankey:": u"\U0001f4a9", 583 | ":hatched_chick:": u"\U0001f425", 584 | ":u7533:": u"\U0001f238", 585 | ":large_blue_circle:": u"\U0001f535", 586 | ":up:": u"\U0001f199", 587 | ":wine_glass:": u"\U0001f377", 588 | ":x:": u"\U0000274c", 589 | ":nose:": u"\U0001f443", 590 | ":rewind:": u"\U000023ea", 591 | ":two_hearts:": u"\U0001f495", 592 | ":envelope:": u"\U00002709", 593 | ":oncoming_automobile:": u"\U0001f698", 594 | ":ophiuchus:": u"\U000026ce", 595 | ":ring:": u"\U0001f48d", 596 | ":tropical_drink:": u"\U0001f379", 597 | ":turtle:": u"\U0001f422", 598 | ":crescent_moon:": u"\U0001f319", 599 | ":koko:": u"\U0001f201", 600 | ":microscope:": u"\U0001f52c", 601 | ":rugby_football:": u"\U0001f3c9", 602 | ":smoking:": u"\U0001f6ac", 603 | ":anger:": u"\U0001f4a2", 604 | ":aries:": u"\U00002648", 605 | ":city_sunset:": u"\U0001f306", 606 | ":clock1230:": u"\U0001f567", 607 | ":mailbox_with_no_mail:": u"\U0001f4ed", 608 | ":movie_camera:": u"\U0001f3a5", 609 | ":pager:": u"\U0001f4df", 610 | ":zero:": u"\U00000030\U000020e3", 611 | ":bank:": u"\U0001f3e6", 612 | ":eight_pointed_black_star:": u"\U00002734", 613 | ":knife:": u"\U0001f52a", 614 | ":u7121:": u"\U0001f21a", 615 | ":customs:": u"\U0001f6c3", 616 | ":melon:": u"\U0001f348", 617 | ":rowboat:": u"\U0001f6a3", 618 | ":corn:": u"\U0001f33d", 619 | ":eggplant:": u"\U0001f346", 620 | ":heart_decoration:": u"\U0001f49f", 621 | ":rotating_light:": u"\U0001f6a8", 622 | ":round_pushpin:": u"\U0001f4cd", 623 | ":cat2:": u"\U0001f408", 624 | ":chocolate_bar:": u"\U0001f36b", 625 | ":no_bell:": u"\U0001f515", 626 | ":radio:": u"\U0001f4fb", 627 | ":droplet:": u"\U0001f4a7", 628 | ":hamburger:": u"\U0001f354", 629 | ":fire_engine:": u"\U0001f692", 630 | ":heart:": u"\U00002764", 631 | ":potable_water:": u"\U0001f6b0", 632 | ":telephone_receiver:": u"\U0001f4de", 633 | ":dash:": u"\U0001f4a8", 634 | ":globe_with_meridians:": u"\U0001f310", 635 | ":guardsman:": u"\U0001f482", 636 | ":heavy_multiplication_x:": u"\U00002716", 637 | ":chart_with_downwards_trend:": u"\U0001f4c9", 638 | ":imp:": u"\U0001f47f", 639 | ":earth_asia:": u"\U0001f30f", 640 | ":mouse2:": u"\U0001f401", 641 | ":notebook_with_decorative_cover:": u"\U0001f4d4", 642 | ":telescope:": u"\U0001f52d", 643 | ":trolleybus:": u"\U0001f68e", 644 | ":card_index:": u"\U0001f4c7", 645 | ":euro:": u"\U0001f4b6", 646 | ":dollar:": u"\U0001f4b5", 647 | ":fax:": u"\U0001f4e0", 648 | ":mailbox_with_mail:": u"\U0001f4ec", 649 | ":raised_hands:": u"\U0001f64c", 650 | ":disappointed:": u"\U0001f61e", 651 | ":foggy:": u"\U0001f301", 652 | ":person_with_pouting_face:": u"\U0001f64e", 653 | ":statue_of_liberty:": u"\U0001f5fd", 654 | ":dolls:": u"\U0001f38e", 655 | ":light_rail:": u"\U0001f688", 656 | ":pencil:": u"\U0001f4dd", 657 | ":speak_no_evil:": u"\U0001f64a", 658 | ":calling:": u"\U0001f4f2", 659 | ":clock830:": u"\U0001f563", 660 | ":cow2:": u"\U0001f404", 661 | ":hear_no_evil:": u"\U0001f649", 662 | ":scream_cat:": u"\U0001f640", 663 | ":smile_cat:": u"\U0001f638", 664 | ":tractor:": u"\U0001f69c", 665 | ":clock11:": u"\U0001f55a", 666 | ":doughnut:": u"\U0001f369", 667 | ":hammer:": u"\U0001f528", 668 | ":loop:": u"\U000027bf", 669 | ":moon:": u"\U0001f314", 670 | ":soon:": u"\U0001f51c", 671 | ":cinema:": u"\U0001f3a6", 672 | ":factory:": u"\U0001f3ed", 673 | ":flushed:": u"\U0001f633", 674 | ":mute:": u"\U0001f507", 675 | ":neutral_face:": u"\U0001f610", 676 | ":scorpius:": u"\U0000264f", 677 | ":wolf:": u"\U0001f43a", 678 | ":clapper:": u"\U0001f3ac", 679 | ":joy_cat:": u"\U0001f639", 680 | ":pensive:": u"\U0001f614", 681 | ":sleeping:": u"\U0001f634", 682 | ":credit_card:": u"\U0001f4b3", 683 | ":leo:": u"\U0000264c", 684 | ":man_with_gua_pi_mao:": u"\U0001f472", 685 | ":open_hands:": u"\U0001f450", 686 | ":tea:": u"\U0001f375", 687 | ":arrow_down:": u"\U00002b07", 688 | ":nine:": u"\U00000039\U000020e3", 689 | ":punch:": u"\U0001f44a", 690 | ":slot_machine:": u"\U0001f3b0", 691 | ":clap:": u"\U0001f44f", 692 | ":information_source:": u"\U00002139", 693 | ":tiger:": u"\U0001f42f", 694 | ":city_sunrise:": u"\U0001f307", 695 | ":dango:": u"\U0001f361", 696 | ":thumbsdown:": u"\U0001f44e", 697 | ":u6307:": u"\U0001f22f", 698 | ":curry:": u"\U0001f35b", 699 | ":cherries:": u"\U0001f352", 700 | ":clock6:": u"\U0001f555", 701 | ":clock7:": u"\U0001f556", 702 | ":older_man:": u"\U0001f474", 703 | ":oncoming_police_car:": u"\U0001f694", 704 | ":syringe:": u"\U0001f489", 705 | ":heavy_dollar_sign:": u"\U0001f4b2", 706 | ":open_file_folder:": u"\U0001f4c2", 707 | ":arrow_right_hook:": u"\U000021aa", 708 | ":articulated_lorry:": u"\U0001f69b", 709 | ":dancers:": u"\U0001f46f", 710 | ":kissing_cat:": u"\U0001f63d", 711 | ":rainbow:": u"\U0001f308", 712 | ":u5408:": u"\U0001f234", 713 | ":boot:": u"\U0001f462", 714 | ":carousel_horse:": u"\U0001f3a0", 715 | ":fried_shrimp:": u"\U0001f364", 716 | ":lock:": u"\U0001f512", 717 | ":non-potable_water:": u"\U0001f6b1", 718 | ":o:": u"\U00002b55", 719 | ":persevere:": u"\U0001f623", 720 | ":diamond_shape_with_a_dot_inside:": u"\U0001f4a0", 721 | ":fallen_leaf:": u"\U0001f342", 722 | ":massage:": u"\U0001f486", 723 | ":volcano:": u"\U0001f30b", 724 | ":gem:": u"\U0001f48e", 725 | ":shower:": u"\U0001f6bf", 726 | ":speaker:": u"\U0001f508", 727 | ":last_quarter_moon_with_face:": u"\U0001f31c", 728 | ":mag:": u"\U0001f50d", 729 | ":anguished:": u"\U0001f627", 730 | ":monkey_face:": u"\U0001f435", 731 | ":sunny:": u"\U00002600", 732 | ":tangerine:": u"\U0001f34a", 733 | ":point_right:": u"\U0001f449", 734 | ":railway_car:": u"\U0001f683", 735 | ":triumph:": u"\U0001f624", 736 | ":two:": u"\U00000032\U000020e3", 737 | ":gift_heart:": u"\U0001f49d", 738 | ":ledger:": u"\U0001f4d2", 739 | ":sagittarius:": u"\U00002650", 740 | ":snowflake:": u"\U00002744", 741 | ":abc:": u"\U0001f524", 742 | ":horse:": u"\U0001f434", 743 | ":ok_hand:": u"\U0001f44c", 744 | ":video_camera:": u"\U0001f4f9", 745 | ":sparkling_heart:": u"\U0001f496", 746 | ":taurus:": u"\U00002649", 747 | ":frog:": u"\U0001f438", 748 | ":hamster:": u"\U0001f439", 749 | ":helicopter:": u"\U0001f681", 750 | ":fries:": u"\U0001f35f", 751 | ":mushroom:": u"\U0001f344", 752 | ":penguin:": u"\U0001f427", 753 | ":truck:": u"\U0001f69a", 754 | ":bar_chart:": u"\U0001f4ca", 755 | ":evergreen_tree:": u"\U0001f332", 756 | ":bow:": u"\U0001f647", 757 | ":clock12:": u"\U0001f55b", 758 | ":four_leaf_clover:": u"\U0001f340", 759 | ":inbox_tray:": u"\U0001f4e5", 760 | ":smirk_cat:": u"\U0001f63c", 761 | ":two_men_holding_hands:": u"\U0001f46c", 762 | ":water_buffalo:": u"\U0001f403", 763 | ":alien:": u"\U0001f47d", 764 | ":video_game:": u"\U0001f3ae", 765 | ":candy:": u"\U0001f36c", 766 | ":page_facing_up:": u"\U0001f4c4", 767 | ":watermelon:": u"\U0001f349", 768 | ":white_check_mark:": u"\U00002705", 769 | ":blossom:": u"\U0001f33c", 770 | ":crocodile:": u"\U0001f40a", 771 | ":no_mouth:": u"\U0001f636", 772 | ":o2:": u"\U0001f17e", 773 | ":shirt:": u"\U0001f455", 774 | ":clock8:": u"\U0001f557", 775 | ":eyes:": u"\U0001f440", 776 | ":rabbit2:": u"\U0001f407", 777 | ":tanabata_tree:": u"\U0001f38b", 778 | ":wrench:": u"\U0001f527", 779 | ":es:": u"\U0001f1ea\U0001f1f8", 780 | ":trophy:": u"\U0001f3c6", 781 | ":two_women_holding_hands:": u"\U0001f46d", 782 | ":clock630:": u"\U0001f561", 783 | ":pineapple:": u"\U0001f34d", 784 | ":stuck_out_tongue:": u"\U0001f61b", 785 | ":angry:": u"\U0001f620", 786 | ":athletic_shoe:": u"\U0001f45f", 787 | ":cookie:": u"\U0001f36a", 788 | ":flags:": u"\U0001f38f", 789 | ":game_die:": u"\U0001f3b2", 790 | ":bird:": u"\U0001f426", 791 | ":jack_o_lantern:": u"\U0001f383", 792 | ":ox:": u"\U0001f402", 793 | ":paperclip:": u"\U0001f4ce", 794 | ":sleepy:": u"\U0001f62a", 795 | ":astonished:": u"\U0001f632", 796 | ":back:": u"\U0001f519", 797 | ":closed_book:": u"\U0001f4d5", 798 | ":hatching_chick:": u"\U0001f423", 799 | ":arrows_clockwise:": u"\U0001f503", 800 | ":car:": u"\U0001f697", 801 | ":ear:": u"\U0001f442", 802 | ":haircut:": u"\U0001f487", 803 | ":icecream:": u"\U0001f366", 804 | ":bust_in_silhouette:": u"\U0001f464", 805 | ":diamonds:": u"\U00002666", 806 | ":no_good:": u"\U0001f645", 807 | ":pizza:": u"\U0001f355", 808 | ":chicken:": u"\U0001f414", 809 | ":eyeglasses:": u"\U0001f453", 810 | ":see_no_evil:": u"\U0001f648", 811 | ":earth_africa:": u"\U0001f30d", 812 | ":fireworks:": u"\U0001f386", 813 | ":page_with_curl:": u"\U0001f4c3", 814 | ":rice_ball:": u"\U0001f359", 815 | ":white_square_button:": u"\U0001f533", 816 | ":cake:": u"\U0001f370", 817 | ":red_car:": u"\U0001f697", 818 | ":tm:": u"\U00002122", 819 | ":unamused:": u"\U0001f612", 820 | ":fish_cake:": u"\U0001f365", 821 | ":key:": u"\U0001f511", 822 | ":speedboat:": u"\U0001f6a4", 823 | ":closed_umbrella:": u"\U0001f302", 824 | ":pear:": u"\U0001f350", 825 | ":satellite:": u"\U0001f4e1", 826 | ":scream:": u"\U0001f631", 827 | ":first_quarter_moon:": u"\U0001f313", 828 | ":jp:": u"\U0001f1ef\U0001f1f5", 829 | ":repeat_one:": u"\U0001f502", 830 | ":shell:": u"\U0001f41a", 831 | ":interrobang:": u"\U00002049", 832 | ":trident:": u"\U0001f531", 833 | ":u55b6:": u"\U0001f23a", 834 | ":atm:": u"\U0001f3e7", 835 | ":door:": u"\U0001f6aa", 836 | ":kissing:": u"\U0001f617", 837 | ":six_pointed_star:": u"\U0001f52f", 838 | ":thumbsup:": u"\U0001f44d", 839 | ":u6708:": u"\U0001f237", 840 | ":do_not_litter:": u"\U0001f6af", 841 | ":whale2:": u"\U0001f40b", 842 | ":school_satchel:": u"\U0001f392", 843 | ":cactus:": u"\U0001f335", 844 | ":clipboard:": u"\U0001f4cb", 845 | ":dizzy:": u"\U0001f4ab", 846 | ":waxing_gibbous_moon:": u"\U0001f314", 847 | ":camera:": u"\U0001f4f7", 848 | ":capital_abcd:": u"\U0001f520", 849 | ":leaves:": u"\U0001f343", 850 | ":left_luggage:": u"\U0001f6c5", 851 | ":bamboo:": u"\U0001f38d", 852 | ":bowling:": u"\U0001f3b3", 853 | ":eight:": u"\U00000038\U000020e3", 854 | ":kimono:": u"\U0001f458", 855 | ":left_right_arrow:": u"\U00002194", 856 | ":stuck_out_tongue_winking_eye:": u"\U0001f61c", 857 | ":surfer:": u"\U0001f3c4", 858 | ":sweat:": u"\U0001f613", 859 | ":violin:": u"\U0001f3bb", 860 | ":postbox:": u"\U0001f4ee", 861 | ":bride_with_veil:": u"\U0001f470", 862 | ":recycle:": u"\U0000267b", 863 | ":station:": u"\U0001f689", 864 | ":vhs:": u"\U0001f4fc", 865 | ":crossed_flags:": u"\U0001f38c", 866 | ":memo:": u"\U0001f4dd", 867 | ":no_entry:": u"\U000026d4", 868 | ":white_circle:": u"\U000026aa", 869 | ":arrow_lower_left:": u"\U00002199", 870 | ":chestnut:": u"\U0001f330", 871 | ":crystal_ball:": u"\U0001f52e", 872 | ":last_quarter_moon:": u"\U0001f317", 873 | ":loud_sound:": u"\U0001f50a", 874 | ":strawberry:": u"\U0001f353", 875 | ":worried:": u"\U0001f61f", 876 | ":circus_tent:": u"\U0001f3aa", 877 | ":weary:": u"\U0001f629", 878 | ":bathtub:": u"\U0001f6c1", 879 | ":snake:": u"\U0001f40d", 880 | ":grin:": u"\U0001f601", 881 | ":symbols:": u"\U0001f523", 882 | ":airplane:": u"\U00002708", 883 | ":heart_eyes:": u"\U0001f60d", 884 | ":sailboat:": u"\U000026f5", 885 | ":stew:": u"\U0001f372", 886 | ":tshirt:": u"\U0001f455", 887 | ":rat:": u"\U0001f400", 888 | ":black_medium_square:": u"\U000025fc", 889 | ":clock930:": u"\U0001f564", 890 | ":full_moon_with_face:": u"\U0001f31d", 891 | ":japanese_goblin:": u"\U0001f47a", 892 | ":restroom:": u"\U0001f6bb", 893 | ":vertical_traffic_light:": u"\U0001f6a6", 894 | ":basketball:": u"\U0001f3c0", 895 | ":cherry_blossom:": u"\U0001f338", 896 | ":low_brightness:": u"\U0001f505", 897 | ":pill:": u"\U0001f48a", 898 | } 899 | -------------------------------------------------------------------------------- /charmander/extensions/google.py: -------------------------------------------------------------------------------- 1 | """ ~search will return the top google result for that query """ 2 | 3 | from bs4 import BeautifulSoup 4 | import re 5 | import requests 6 | 7 | __author__ = 'vikesh' 8 | 9 | try: 10 | from urllib import quote, unquote 11 | except ImportError: 12 | from urllib.request import quote, unquote 13 | 14 | 15 | def google(query): 16 | query = quote(query) 17 | url = "https://encrypted.google.com/search?q={0}".format(query) 18 | soup = BeautifulSoup(requests.get(url).text, "html5lib") 19 | 20 | answer = soup.findAll("h3", attrs={"class": "r"}) 21 | if not answer: 22 | return ":fire: Sorry, Google doesn't have an answer for your Query :fire:" 23 | 24 | return unquote(re.findall(r"q=(.*?)&", str(answer[0]))[0]) 25 | 26 | 27 | def message(msg, server): 28 | text = msg.get("text", "") 29 | match = re.findall(r"~(?:google|search) (.*)", text) 30 | 31 | if not match: 32 | return 33 | 34 | return google(match[0]) 35 | -------------------------------------------------------------------------------- /charmander/extensions/help.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def on_message(msg,server): 5 | text = msg.get("text","") 6 | match = re.findall(r"~help( .*)?", text) 7 | if not match: 8 | return 9 | 10 | 11 | help_topic = match[0].strip() 12 | if help_topic: 13 | return server.hooks["extendedhelp"].get(help_topic, 14 | ":fire: no help found for {0}".format(help_topic)) 15 | else: 16 | help_dir = server.hooks.get("help",{}) 17 | return "\n".join(sorted(help_dir[key]) for key in help_dir) 18 | -------------------------------------------------------------------------------- /charmander/extensions/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | Log all messages to the database only active 3 | If the CHARMANDER_LOG environment variable is set. 4 | """ 5 | 6 | import os 7 | 8 | DO_LOG = os.environ.get("CHARMANDER_LOG", False) 9 | 10 | 11 | def on_message(msg, server): 12 | if DO_LOG: 13 | server.query("INSERT INTO log VALUES (?, ?, ?, ?)", 14 | msg["text"], msg["user"], msg["ts"], msg["team"], msg["channel"]) 15 | 16 | 17 | def on_init_(server): 18 | if DO_LOG: 19 | server.query(""" 20 | CREATE TABLE IF NOT EXISTS log (msg STRING, sender STRING, time STRING, team STRING, channel STRING) 21 | """) -------------------------------------------------------------------------------- /charmander/extensions/urban.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # urban returns the urban dictionary definition and example of a term 3 | 4 | import requests 5 | import re 6 | 7 | try: 8 | from urllib import quote 9 | except ImportError: 10 | from urllib.request import quote 11 | 12 | 13 | def reply_to(string): 14 | lines = string.split("\n") 15 | return "\n".join("> _{0}_".format(l.strip()) for l in lines) 16 | 17 | 18 | def urban(term): 19 | 20 | # Slack replaces the quote character with a smart quote. 21 | # Undo that 22 | term = term.replace(u'’', "'").encode("utf8") 23 | 24 | baseurl = u"http://api.urbandictionary.com/v0/define?term={0}" 25 | data = requests.get(baseurl.format(quote(term))).json() 26 | 27 | try: 28 | result = data['list'][0] 29 | result["example"] = reply_to(result.get("example", "")) 30 | definition = (u"*{word}*: {definition}.\n" 31 | "*Example:*\n{example}".format(**result)) 32 | 33 | return definition 34 | except IndexError: 35 | return ":boom: No results found for {0}, please try again".format(term) 36 | 37 | 38 | def on_message(msg, server): 39 | text = msg.get("text", "") 40 | match = re.findall(r"~urban (.*)", text) 41 | if not match: 42 | return 43 | searchterm = match[0] 44 | return urban(searchterm) 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /charmander/extensions/wiki.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | """ 4 | ~wiki returns a wiki link for that 5 | """ 6 | 7 | import re 8 | try: 9 | from urllib import quote 10 | except ImportError: 11 | from urllib2.request import quote 12 | 13 | 14 | import requests 15 | from bs4 import BeautifulSoup 16 | 17 | 18 | def return_wiki(search_term): 19 | search_term = quote(search_term) 20 | 21 | url = "https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch={0}&format=json" 22 | url = url.format(search_term) 23 | 24 | result = requests.get(url).json() 25 | total_pages = result["query"]["search"] 26 | 27 | total_pages = [p for p in total_pages if 'may refer to' not in p["snippets"]] 28 | 29 | if not total_pages: 30 | return "" 31 | 32 | total_pages = quote(total_pages[0]["title"].encode("utf-8")) 33 | 34 | link = "http://en.wikipedia.org/wiki/{0}".format(total_pages) 35 | 36 | res = requests.get("https://en.wikipedia.org/w/api.php?format=json&action=parse&page={0}".format(total_pages)).json() 37 | 38 | soup = BeautifulSoup(res["parse"]["text"]["*"], "html5lib") 39 | p = soup.find('p').get_text() 40 | p = p[:8000] 41 | 42 | return u"{0}\n{1}".format(p, link) 43 | 44 | 45 | def on_message(msg, server): 46 | text = msg.get("text","") 47 | match = re.findall(r"~wiki (.*)", text) 48 | if not match: 49 | return 50 | 51 | search_term = match[0] 52 | return return_wiki(search_term.encode("utf-8")) -------------------------------------------------------------------------------- /charmander/extensions/youtube.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | """ ~youtube will return the first youtube search result for the given term. """ 4 | 5 | import re 6 | 7 | try: 8 | from urllib import quote 9 | except ImportError: 10 | from urllib.request import quote 11 | 12 | import requests 13 | 14 | 15 | def youtube(searchquery): 16 | url = "https://www.youtube.com/results?search_query={0}" 17 | url = url.format(quote(searchquery)) 18 | 19 | r = requests.get(url) 20 | results = re.findall('a href="(/watch[^&]*?)"', r.text) 21 | 22 | if not results: 23 | return "Sorry, charmander couldn't find any videos :crying_cat:" 24 | 25 | return "https://www.youtube.com{0}".format(results[0]) 26 | 27 | 28 | def on_message(msg, server): 29 | text = msg.get("text", "") 30 | match = re.findall(r"~youtube (.*)", text) 31 | 32 | if not match: 33 | return 34 | 35 | searchquery = match[0] 36 | return youtube(searchquery.encode("utf8")) 37 | -------------------------------------------------------------------------------- /charmander/server.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | class CharmanderServer(object): 4 | def __init__(self, slack, config, hooks, db): 5 | self.slack = slack 6 | self.config = config 7 | self.hooks = hooks 8 | self.db = db 9 | 10 | def query(self, sql, *params): 11 | curser = self.db.cursor() 12 | curser.execute(sql, params) 13 | rows = curser.fetchall() 14 | curser.close() 15 | self.db.commit() 16 | return rows -------------------------------------------------------------------------------- /output_ZrAcxK.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicky002/charmander/33a91e9a7b508d8106ba1644e9bec386d1f50eb4/output_ZrAcxK.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | html5lib==0.9999999 2 | pyflakes==0.8.1 3 | beautifulsoup4==4.4.1 4 | flake8==2.3.0 5 | slackrtm==0.2.1 6 | requests==2.5.2 7 | websocket-client==0.32.0 8 | mccabe==0.3 9 | wrapt==1.10.2 10 | ipdb -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # Read more her : http://flake8.readthedocs.org/en/latest/ 3 | # And error codes here : http://pep8.readthedocs.org/en/latest/intro.html#error-codes 4 | ignore = E302,E241 5 | max-line-length = 120 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vikesh' 2 | 3 | import os 4 | import sys 5 | 6 | 7 | try: 8 | from setuptools import setup 9 | except ImportError: 10 | from distutils.core import setup 11 | 12 | 13 | PYTHON3 = sys.version_info[0] > 2 14 | 15 | required = ['requests>=2.5', 'websocket-client==0.32.0', 16 | 'beautifulsoup4==4.4.1', 'html5lib==0.9999999', 'pyfiglet==0.7.4', 17 | 'slackrtm==0.2.1'] 18 | 19 | if not PYTHON3: 20 | required += ['importlib>=1.0.3'] 21 | 22 | packages = [] 23 | 24 | try: 25 | longdesc = open("README.rs").read() 26 | except: 27 | longdesc = '' 28 | 29 | # Thanks to Kenneth Reitz, I stole the template for this : https://github.com/kennethreitz 30 | setup( 31 | name='charmander', 32 | version='1.0.0', 33 | description='An Intelligent bot for slack', 34 | long_description=longdesc, 35 | author='Vikesh Tiwari', 36 | author_email='hi@eulercoder.me', 37 | url='https://github.com/vicky002/Charmander', 38 | packages=packages, 39 | scripts= ['bin/charmander'], 40 | package_data = {'': ['LICENCE',], '':['charmander/extensions/*.py']}, 41 | include_package_data=True, 42 | install_requires=required, 43 | license='MIT', 44 | classifiers=( 45 | 'Development Status :: 1 - Beta', 46 | 'Intended Audience :: Developers', 47 | 'Natural Language :: English', 48 | 'License :: OSI Approved :: MIT License', 49 | 'Programming Language :: Python', 50 | 'Programming Language :: Python :: 2.6', 51 | 'Programming Language :: Python :: 2.7', 52 | 'Programming Language :: Python :: 3.4', 53 | ), 54 | 55 | ) --------------------------------------------------------------------------------