From 8671707e4dcf2fc0e3bd90fd42cd4081ee0e4bc0 Mon Sep 17 00:00:00 2001 From: devin Date: Wed, 28 Oct 2015 07:29:57 -0400 Subject: [PATCH 1/7] added telegram lib from repo leandrotoledo/python-telegram-bot --- lib/telegram/__init__.py | 56 ++ lib/telegram/audio.py | 72 +++ lib/telegram/base.py | 68 +++ lib/telegram/bot.py | 741 +++++++++++++++++++++++ lib/telegram/chataction.py | 33 ++ lib/telegram/contact.py | 66 +++ lib/telegram/document.py | 70 +++ lib/telegram/emoji.py | 878 ++++++++++++++++++++++++++++ lib/telegram/error.py | 41 ++ lib/telegram/file.py | 81 +++ lib/telegram/forcereply.py | 59 ++ lib/telegram/groupchat.py | 64 ++ lib/telegram/inputfile.py | 193 ++++++ lib/telegram/location.py | 55 ++ lib/telegram/message.py | 231 ++++++++ lib/telegram/nullhandler.py | 32 + lib/telegram/parsemode.py | 26 + lib/telegram/photosize.py | 85 +++ lib/telegram/replykeyboardhide.py | 60 ++ lib/telegram/replykeyboardmarkup.py | 66 +++ lib/telegram/replymarkup.py | 29 + lib/telegram/sticker.py | 72 +++ lib/telegram/update.py | 60 ++ lib/telegram/user.py | 79 +++ lib/telegram/userprofilephotos.py | 71 +++ lib/telegram/utils/__init__.py | 0 lib/telegram/utils/request.py | 117 ++++ lib/telegram/video.py | 79 +++ lib/telegram/voice.py | 65 ++ 29 files changed, 3549 insertions(+) create mode 100644 lib/telegram/__init__.py create mode 100644 lib/telegram/audio.py create mode 100644 lib/telegram/base.py create mode 100644 lib/telegram/bot.py create mode 100644 lib/telegram/chataction.py create mode 100644 lib/telegram/contact.py create mode 100644 lib/telegram/document.py create mode 100644 lib/telegram/emoji.py create mode 100644 lib/telegram/error.py create mode 100644 lib/telegram/file.py create mode 100644 lib/telegram/forcereply.py create mode 100644 lib/telegram/groupchat.py create mode 100644 lib/telegram/inputfile.py create mode 100644 lib/telegram/location.py create mode 100644 lib/telegram/message.py create mode 100644 lib/telegram/nullhandler.py create mode 100644 lib/telegram/parsemode.py create mode 100644 lib/telegram/photosize.py create mode 100644 lib/telegram/replykeyboardhide.py create mode 100644 lib/telegram/replykeyboardmarkup.py create mode 100644 lib/telegram/replymarkup.py create mode 100644 lib/telegram/sticker.py create mode 100644 lib/telegram/update.py create mode 100644 lib/telegram/user.py create mode 100644 lib/telegram/userprofilephotos.py create mode 100644 lib/telegram/utils/__init__.py create mode 100644 lib/telegram/utils/request.py create mode 100644 lib/telegram/video.py create mode 100644 lib/telegram/voice.py diff --git a/lib/telegram/__init__.py b/lib/telegram/__init__.py new file mode 100644 index 00000000..b3dffe97 --- /dev/null +++ b/lib/telegram/__init__.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""A library that provides a Python interface to the Telegram Bot API""" + +__author__ = 'leandrotoledodesouza@gmail.com' +__version__ = '2.8.7' + +from .base import TelegramObject +from .user import User +from .groupchat import GroupChat +from .photosize import PhotoSize +from .audio import Audio +from .voice import Voice +from .document import Document +from .sticker import Sticker +from .video import Video +from .contact import Contact +from .location import Location +from .chataction import ChatAction +from .userprofilephotos import UserProfilePhotos +from .replymarkup import ReplyMarkup +from .replykeyboardmarkup import ReplyKeyboardMarkup +from .replykeyboardhide import ReplyKeyboardHide +from .forcereply import ForceReply +from .error import TelegramError +from .inputfile import InputFile +from .file import File +from .nullhandler import NullHandler +from .emoji import Emoji +from .parsemode import ParseMode +from .message import Message +from .update import Update +from .bot import Bot + +__all__ = ['Bot', 'Emoji', 'TelegramError', 'InputFile', 'ReplyMarkup', + 'ForceReply', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', + 'UserProfilePhotos', 'ChatAction', 'Location', 'Contact', + 'Video', 'Sticker', 'Document', 'File', 'Audio', 'PhotoSize', + 'GroupChat', 'Update', 'ParseMode', 'Message', 'User', + 'TelegramObject', 'NullHandler', 'Voice'] diff --git a/lib/telegram/audio.py b/lib/telegram/audio.py new file mode 100644 index 00000000..c7a669d3 --- /dev/null +++ b/lib/telegram/audio.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Audio""" + +from telegram import TelegramObject + + +class Audio(TelegramObject): + """This object represents a Telegram Audio. + + Attributes: + file_id (str): + duration (int): + performer (str): + title (str): + mime_type (str): + file_size (int): + + Args: + file_id (str): + duration (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + performer (Optional[str]): + title (Optional[str]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + + def __init__(self, + file_id, + duration, + **kwargs): + # Required + self.file_id = str(file_id) + self.duration = int(duration) + # Optionals + self.performer = kwargs.get('performer', '') + self.title = kwargs.get('title', '') + self.mime_type = str(kwargs.get('mime_type', '')) + self.file_size = int(kwargs.get('file_size', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Audio: + """ + if not data: + return None + + return Audio(**data) diff --git a/lib/telegram/base.py b/lib/telegram/base.py new file mode 100644 index 00000000..ac0deac0 --- /dev/null +++ b/lib/telegram/base.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""Base class for Telegram Objects""" + +import json +from abc import ABCMeta + + +class TelegramObject(object): + """Base class for most telegram objects""" + + __metaclass__ = ABCMeta + + def __str__(self): + return str(self.to_dict()) + + def __getitem__(self, item): + return self.__dict__[item] + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.TelegramObject: + """ + raise NotImplementedError + + def to_json(self): + """ + Returns: + str: + """ + return json.dumps(self.to_dict()) + + def to_dict(self): + """ + Returns: + dict: + """ + data = dict() + + for key, value in self.__dict__.items(): + if value: + if hasattr(value, 'to_dict'): + data[key] = value.to_dict() + else: + data[key] = value + + return data diff --git a/lib/telegram/bot.py b/lib/telegram/bot.py new file mode 100644 index 00000000..3805335b --- /dev/null +++ b/lib/telegram/bot.py @@ -0,0 +1,741 @@ +#!/usr/bin/env python +# pylint: disable=E0611,E0213,E1102,C0103,E1101,W0613,R0913,R0904 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Bot""" + +import functools +import logging + +from telegram import (User, Message, Update, UserProfilePhotos, File, + TelegramError, ReplyMarkup, TelegramObject, NullHandler) +from telegram.utils import request + +H = NullHandler() +logging.getLogger(__name__).addHandler(H) + + +class Bot(TelegramObject): + + """This object represents a Telegram Bot. + + Attributes: + id (int): + first_name (str): + last_name (str): + username (str): + name (str): + + Args: + token (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + base_url (Optional[str]): + """ + + def __init__(self, + token, + base_url=None): + self.token = token + + if base_url is None: + self.base_url = 'https://api.telegram.org/bot%s' % self.token + else: + self.base_url = base_url + self.token + + self.base_file_url = 'https://api.telegram.org/file/bot%s' % self.token + + self.bot = None + + self.logger = logging.getLogger(__name__) + + def info(func): + """ + Returns: + """ + @functools.wraps(func) + def decorator(self, *args, **kwargs): + """ + decorator + """ + if not self.bot: + self.getMe() + + result = func(self, *args, **kwargs) + return result + return decorator + + @property + @info + def id(self): + """int: """ + return self.bot.id + + @property + @info + def first_name(self): + """str: """ + return self.bot.first_name + + @property + @info + def last_name(self): + """str: """ + return self.bot.last_name + + @property + @info + def username(self): + """str: """ + return self.bot.username + + @property + def name(self): + """str: """ + return '@%s' % self.username + + def log(func): + """ + Returns: + A telegram.Message instance representing the message posted. + """ + logger = logging.getLogger(func.__module__) + + @functools.wraps(func) + def decorator(self, *args, **kwargs): + """ + decorator + """ + logger.debug('Entering: %s', func.__name__) + result = func(self, *args, **kwargs) + logger.debug(result) + logger.debug('Exiting: %s', func.__name__) + return result + return decorator + + def message(func): + """ + Returns: + A telegram.Message instance representing the message posted. + """ + @functools.wraps(func) + def decorator(self, *args, **kwargs): + """ + decorator + """ + url, data = func(self, *args, **kwargs) + + if not data.get('chat_id'): + raise TelegramError('Invalid chat_id') + + if kwargs.get('reply_to_message_id'): + reply_to_message_id = kwargs.get('reply_to_message_id') + data['reply_to_message_id'] = reply_to_message_id + + if kwargs.get('reply_markup'): + reply_markup = kwargs.get('reply_markup') + if isinstance(reply_markup, ReplyMarkup): + data['reply_markup'] = reply_markup.to_json() + else: + data['reply_markup'] = reply_markup + + result = request.post(url, data) + + if result is True: + return result + + return Message.de_json(result) + return decorator + + @log + def getMe(self): + """A simple method for testing your bot's auth token. + + Returns: + A telegram.User instance representing that bot if the + credentials are valid, None otherwise. + """ + url = '%s/getMe' % self.base_url + + result = request.get(url) + + self.bot = User.de_json(result) + + return self.bot + + @log + @message + def sendMessage(self, + chat_id, + text, + parse_mode=None, + disable_web_page_preview=None, + **kwargs): + """Use this method to send text messages. + + Args: + chat_id: + Unique identifier for the message recipient - telegram.User or + telegram.GroupChat id. + parse_mode: + Send Markdown, if you want Telegram apps to show bold, italic and + inline URLs in your bot's message. For the moment, only Telegram + for Android supports this. [Optional] + text: + Text of the message to be sent. + disable_web_page_preview: + Disables link previews for links in this message. [Optional] + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a custom + reply keyboard, instructions to hide keyboard or to force a reply + from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendMessage' % self.base_url + + data = {'chat_id': chat_id, + 'text': text} + + if parse_mode: + data['parse_mode'] = parse_mode + if disable_web_page_preview: + data['disable_web_page_preview'] = disable_web_page_preview + + return url, data + + @log + @message + def forwardMessage(self, + chat_id, + from_chat_id, + message_id): + """Use this method to forward messages of any kind. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + from_chat_id: + Unique identifier for the chat where the original message was sent + - User or GroupChat id. + message_id: + Unique message identifier. + + Returns: + A telegram.Message instance representing the message forwarded. + """ + + url = '%s/forwardMessage' % self.base_url + + data = {} + if chat_id: + data['chat_id'] = chat_id + if from_chat_id: + data['from_chat_id'] = from_chat_id + if message_id: + data['message_id'] = message_id + + return url, data + + @log + @message + def sendPhoto(self, + chat_id, + photo, + caption=None, + **kwargs): + """Use this method to send photos. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + photo: + Photo to send. You can either pass a file_id as String to resend a + photo that is already on the Telegram servers, or upload a new + photo using multipart/form-data. + caption: + Photo caption (may also be used when resending photos by file_id). + [Optional] + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a custom + reply keyboard, instructions to hide keyboard or to force a reply + from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendPhoto' % self.base_url + + data = {'chat_id': chat_id, + 'photo': photo} + + if caption: + data['caption'] = caption + + return url, data + + @log + @message + def sendAudio(self, + chat_id, + audio, + duration=None, + performer=None, + title=None, + **kwargs): + """Use this method to send audio files, if you want Telegram clients to + display them in the music player. Your audio must be in an .mp3 format. + On success, the sent Message is returned. Bots can currently send audio + files of up to 50 MB in size, this limit may be changed in the future. + + For backward compatibility, when both fields title and description are + empty and mime-type of the sent file is not "audio/mpeg", file is sent + as playable voice message. In this case, your audio must be in an .ogg + file encoded with OPUS. This will be removed in the future. You need to + use sendVoice method instead. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + audio: + Audio file to send. You can either pass a file_id as String to + resend an audio that is already on the Telegram servers, or upload + a new audio file using multipart/form-data. + duration: + Duration of sent audio in seconds. [Optional] + performer: + Performer of sent audio. [Optional] + title: + Title of sent audio. [Optional] + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a + custom reply keyboard, instructions to hide keyboard or to force a + reply from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendAudio' % self.base_url + + data = {'chat_id': chat_id, + 'audio': audio} + + if duration: + data['duration'] = duration + if performer: + data['performer'] = performer + if title: + data['title'] = title + + return url, data + + @log + @message + def sendDocument(self, + chat_id, + document, + filename=None, + **kwargs): + """Use this method to send general files. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + document: + File to send. You can either pass a file_id as String to resend a + file that is already on the Telegram servers, or upload a new file + using multipart/form-data. + filename: + File name that shows in telegram message (it is usefull when you + send file generated by temp module, for example). [Optional] + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a + custom reply keyboard, instructions to hide keyboard or to force a + reply from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendDocument' % self.base_url + + data = {'chat_id': chat_id, + 'document': document} + + if filename: + data['filename'] = filename + + return url, data + + @log + @message + def sendSticker(self, + chat_id, + sticker, + **kwargs): + """Use this method to send .webp stickers. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + sticker: + Sticker to send. You can either pass a file_id as String to resend + a sticker that is already on the Telegram servers, or upload a new + sticker using multipart/form-data. + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a + custom reply keyboard, instructions to hide keyboard or to force a + reply from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendSticker' % self.base_url + + data = {'chat_id': chat_id, + 'sticker': sticker} + + return url, data + + @log + @message + def sendVideo(self, + chat_id, + video, + duration=None, + caption=None, + **kwargs): + """Use this method to send video files, Telegram clients support mp4 + videos (other formats may be sent as telegram.Document). + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + video: + Video to send. You can either pass a file_id as String to resend a + video that is already on the Telegram servers, or upload a new + video file using multipart/form-data. + duration: + Duration of sent video in seconds. [Optional] + caption: + Video caption (may also be used when resending videos by file_id). + [Optional] + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a + custom reply keyboard, instructions to hide keyboard or to force a + reply from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendVideo' % self.base_url + + data = {'chat_id': chat_id, + 'video': video} + + if duration: + data['duration'] = duration + if caption: + data['caption'] = caption + + return url, data + + @log + @message + def sendVoice(self, + chat_id, + voice, + duration=None, + **kwargs): + """Use this method to send audio files, if you want Telegram clients to + display the file as a playable voice message. For this to work, your + audio must be in an .ogg file encoded with OPUS (other formats may be + sent as Audio or Document). On success, the sent Message is returned. + Bots can currently send audio files of up to 50 MB in size, this limit + may be changed in the future. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + voice: + Audio file to send. You can either pass a file_id as String to + resend an audio that is already on the Telegram servers, or upload + a new audio file using multipart/form-data. + duration: + Duration of sent audio in seconds. [Optional] + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a + custom reply keyboard, instructions to hide keyboard or to force a + reply from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendVoice' % self.base_url + + data = {'chat_id': chat_id, + 'voice': voice} + + if duration: + data['duration'] = duration + + return url, data + + @log + @message + def sendLocation(self, + chat_id, + latitude, + longitude, + **kwargs): + """Use this method to send point on the map. + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + latitude: + Latitude of location. + longitude: + Longitude of location. + reply_to_message_id: + If the message is a reply, ID of the original message. [Optional] + reply_markup: + Additional interface options. A JSON-serialized object for a + custom reply keyboard, instructions to hide keyboard or to force a + reply from the user. [Optional] + + Returns: + A telegram.Message instance representing the message posted. + """ + + url = '%s/sendLocation' % self.base_url + + data = {'chat_id': chat_id, + 'latitude': latitude, + 'longitude': longitude} + + return url, data + + @log + @message + def sendChatAction(self, + chat_id, + action): + """Use this method when you need to tell the user that something is + happening on the bot's side. The status is set for 5 seconds or less + (when a message arrives from your bot, Telegram clients clear its + typing status). + + Args: + chat_id: + Unique identifier for the message recipient - User or GroupChat id. + action: + Type of action to broadcast. Choose one, depending on what the user + is about to receive: + - ChatAction.TYPING for text messages, + - ChatAction.UPLOAD_PHOTO for photos, + - ChatAction.UPLOAD_VIDEO for videos, + - ChatAction.UPLOAD_AUDIO for audio files, + - ChatAction.UPLOAD_DOCUMENT for general files, + - ChatAction.FIND_LOCATION for location data. + """ + + url = '%s/sendChatAction' % self.base_url + + data = {'chat_id': chat_id, + 'action': action} + + return url, data + + @log + def getUserProfilePhotos(self, + user_id, + offset=None, + limit=100): + """Use this method to get a list of profile pictures for a user. + + Args: + user_id: + Unique identifier of the target user. + offset: + Sequential number of the first photo to be returned. By default, + all photos are returned. [Optional] + limit: + Limits the number of photos to be retrieved. Values between 1-100 + are accepted. Defaults to 100. [Optional] + + Returns: + Returns a telegram.UserProfilePhotos object. + """ + + url = '%s/getUserProfilePhotos' % self.base_url + + data = {'user_id': user_id} + + if offset: + data['offset'] = offset + if limit: + data['limit'] = limit + + result = request.post(url, data) + + return UserProfilePhotos.de_json(result) + + @log + def getFile(self, + file_id): + """Use this method to get basic info about a file and prepare it for + downloading. For the moment, bots can download files of up to 20MB in + size. + + Args: + file_id: + File identifier to get info about. + + Returns: + Returns a telegram.File object + """ + + url = '%s/getFile' % self.base_url + + data = {'file_id': file_id} + + result = request.post(url, data) + + if result.get('file_path'): + result['file_path'] = '%s/%s' % (self.base_file_url, + result['file_path']) + + return File.de_json(result) + + @log + def getUpdates(self, + offset=None, + limit=100, + timeout=0): + """Use this method to receive incoming updates using long polling. + + Args: + offset: + Identifier of the first update to be returned. Must be greater by + one than the highest among the identifiers of previously received + updates. By default, updates starting with the earliest unconfirmed + update are returned. An update is considered confirmed as soon as + getUpdates is called with an offset higher than its update_id. + limit: + Limits the number of updates to be retrieved. Values between 1-100 + are accepted. Defaults to 100. + timeout: + Timeout in seconds for long polling. Defaults to 0, i.e. usual + short polling. + + Returns: + A list of telegram.Update objects are returned. + """ + + url = '%s/getUpdates' % self.base_url + + data = {} + if offset: + data['offset'] = offset + if limit: + data['limit'] = limit + if timeout: + data['timeout'] = timeout + + result = request.post(url, data) + + if result: + self.logger.info( + 'Getting updates: %s', [u['update_id'] for u in result]) + else: + self.logger.info('No new updates found.') + + return [Update.de_json(x) for x in result] + + @log + def setWebhook(self, + webhook_url=None, + certificate=None): + """Use this method to specify a url and receive incoming updates via an + outgoing webhook. Whenever there is an update for the bot, we will send + an HTTPS POST request to the specified url, containing a + JSON-serialized Update. In case of an unsuccessful request, we will + give up after a reasonable amount of attempts. + + Args: + url: + HTTPS url to send updates to. + Use an empty string to remove webhook integration + + Returns: + True if successful else TelegramError was raised + """ + url = '%s/setWebhook' % self.base_url + + data = {} + if webhook_url: + data['url'] = webhook_url + if certificate: + data['certificate'] = certificate + + result = request.post(url, data) + + return result + + @staticmethod + def de_json(data): + pass + + def to_dict(self): + """ + Returns: + dict: + """ + data = {'id': self.id, + 'username': self.username, + 'first_name': self.username} + if self.last_name: + data['last_name'] = self.last_name + return data + + def __reduce__(self): + return (self.__class__, (self.token, + self.base_url.replace(self.token, ''))) diff --git a/lib/telegram/chataction.py b/lib/telegram/chataction.py new file mode 100644 index 00000000..fce7e418 --- /dev/null +++ b/lib/telegram/chataction.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# pylint: disable=R0903 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram ChatAction""" + + +class ChatAction(object): + """This object represents a Telegram ChatAction.""" + + TYPING = 'typing' + UPLOAD_PHOTO = 'upload_photo' + RECORD_VIDEO = 'record_video' + UPLOAD_VIDEO = 'upload_video' + RECORD_AUDIO = 'record_audio' + UPLOAD_AUDIO = 'upload_audio' + UPLOAD_DOCUMENT = 'upload_document' + FIND_LOCATION = 'find_location' diff --git a/lib/telegram/contact.py b/lib/telegram/contact.py new file mode 100644 index 00000000..3b9e2b10 --- /dev/null +++ b/lib/telegram/contact.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Contact""" + +from telegram import TelegramObject + + +class Contact(TelegramObject): + """This object represents a Telegram Contact. + + Attributes: + phone_number (str): + first_name (str): + last_name (str): + user_id (int): + + Args: + phone_number (str): + first_name (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + last_name (Optional[str]): + user_id (Optional[int]): + """ + + def __init__(self, + phone_number, + first_name, + **kwargs): + # Required + self.phone_number = str(phone_number) + self.first_name = first_name + # Optionals + self.last_name = kwargs.get('last_name', '') + self.user_id = int(kwargs.get('user_id', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Contact: + """ + if not data: + return None + + return Contact(**data) diff --git a/lib/telegram/document.py b/lib/telegram/document.py new file mode 100644 index 00000000..d492774d --- /dev/null +++ b/lib/telegram/document.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Document""" + +from telegram import PhotoSize, TelegramObject + + +class Document(TelegramObject): + """This object represents a Telegram Document. + + Attributes: + file_id (str): + thumb (:class:`telegram.PhotoSize`): + file_name (str): + mime_type (str): + file_size (int): + + Args: + file_id (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + thumb (Optional[:class:`telegram.PhotoSize`]): + file_name (Optional[str]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + + def __init__(self, + file_id, + **kwargs): + # Required + self.file_id = str(file_id) + # Optionals + self.thumb = kwargs.get('thumb') + self.file_name = kwargs.get('file_name', '') + self.mime_type = str(kwargs.get('mime_type', '')) + self.file_size = int(kwargs.get('file_size', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Document: + """ + if not data: + return None + + data['thumb'] = PhotoSize.de_json(data.get('thumb')) + + return Document(**data) diff --git a/lib/telegram/emoji.py b/lib/telegram/emoji.py new file mode 100644 index 00000000..ee10651a --- /dev/null +++ b/lib/telegram/emoji.py @@ -0,0 +1,878 @@ +#!/usr/bin/env python +# flake8: noqa +# pylint: disable=C0103,C0301,R0903 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents an Emoji""" + + +class Emoji(object): + """This object represents an Emoji.""" + + GRINNING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x81' + FACE_WITH_TEARS_OF_JOY = b'\xF0\x9F\x98\x82' + SMILING_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\x83' + SMILING_FACE_WITH_OPEN_MOUTH_AND_SMILING_EYES = b'\xF0\x9F\x98\x84' + SMILING_FACE_WITH_OPEN_MOUTH_AND_COLD_SWEAT = b'\xF0\x9F\x98\x85' + SMILING_FACE_WITH_OPEN_MOUTH_AND_TIGHTLY_CLOSED_EYES = b'\xF0\x9F\x98\x86' + WINKING_FACE = b'\xF0\x9F\x98\x89' + SMILING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x8A' + FACE_SAVOURING_DELICIOUS_FOOD = b'\xF0\x9F\x98\x8B' + RELIEVED_FACE = b'\xF0\x9F\x98\x8C' + SMILING_FACE_WITH_HEART_SHAPED_EYES = b'\xF0\x9F\x98\x8D' + SMIRKING_FACE = b'\xF0\x9F\x98\x8F' + UNAMUSED_FACE = b'\xF0\x9F\x98\x92' + FACE_WITH_COLD_SWEAT = b'\xF0\x9F\x98\x93' + PENSIVE_FACE = b'\xF0\x9F\x98\x94' + CONFOUNDED_FACE = b'\xF0\x9F\x98\x96' + FACE_THROWING_A_KISS = b'\xF0\x9F\x98\x98' + KISSING_FACE_WITH_CLOSED_EYES = b'\xF0\x9F\x98\x9A' + FACE_WITH_STUCK_OUT_TONGUE_AND_WINKING_EYE = b'\xF0\x9F\x98\x9C' + FACE_WITH_STUCK_OUT_TONGUE_AND_TIGHTLY_CLOSED_EYES = b'\xF0\x9F\x98\x9D' + DISAPPOINTED_FACE = b'\xF0\x9F\x98\x9E' + ANGRY_FACE = b'\xF0\x9F\x98\xA0' + POUTING_FACE = b'\xF0\x9F\x98\xA1' + CRYING_FACE = b'\xF0\x9F\x98\xA2' + PERSEVERING_FACE = b'\xF0\x9F\x98\xA3' + FACE_WITH_LOOK_OF_TRIUMPH = b'\xF0\x9F\x98\xA4' + DISAPPOINTED_BUT_RELIEVED_FACE = b'\xF0\x9F\x98\xA5' + FEARFUL_FACE = b'\xF0\x9F\x98\xA8' + WEARY_FACE = b'\xF0\x9F\x98\xA9' + SLEEPY_FACE = b'\xF0\x9F\x98\xAA' + TIRED_FACE = b'\xF0\x9F\x98\xAB' + LOUDLY_CRYING_FACE = b'\xF0\x9F\x98\xAD' + FACE_WITH_OPEN_MOUTH_AND_COLD_SWEAT = b'\xF0\x9F\x98\xB0' + FACE_SCREAMING_IN_FEAR = b'\xF0\x9F\x98\xB1' + ASTONISHED_FACE = b'\xF0\x9F\x98\xB2' + FLUSHED_FACE = b'\xF0\x9F\x98\xB3' + DIZZY_FACE = b'\xF0\x9F\x98\xB5' + FACE_WITH_MEDICAL_MASK = b'\xF0\x9F\x98\xB7' + GRINNING_CAT_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\xB8' + CAT_FACE_WITH_TEARS_OF_JOY = b'\xF0\x9F\x98\xB9' + SMILING_CAT_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\xBA' + SMILING_CAT_FACE_WITH_HEART_SHAPED_EYES = b'\xF0\x9F\x98\xBB' + CAT_FACE_WITH_WRY_SMILE = b'\xF0\x9F\x98\xBC' + KISSING_CAT_FACE_WITH_CLOSED_EYES = b'\xF0\x9F\x98\xBD' + POUTING_CAT_FACE = b'\xF0\x9F\x98\xBE' + CRYING_CAT_FACE = b'\xF0\x9F\x98\xBF' + WEARY_CAT_FACE = b'\xF0\x9F\x99\x80' + FACE_WITH_NO_GOOD_GESTURE = b'\xF0\x9F\x99\x85' + FACE_WITH_OK_GESTURE = b'\xF0\x9F\x99\x86' + PERSON_BOWING_DEEPLY = b'\xF0\x9F\x99\x87' + SEE_NO_EVIL_MONKEY = b'\xF0\x9F\x99\x88' + HEAR_NO_EVIL_MONKEY = b'\xF0\x9F\x99\x89' + SPEAK_NO_EVIL_MONKEY = b'\xF0\x9F\x99\x8A' + HAPPY_PERSON_RAISING_ONE_HAND = b'\xF0\x9F\x99\x8B' + PERSON_RAISING_BOTH_HANDS_IN_CELEBRATION = b'\xF0\x9F\x99\x8C' + PERSON_FROWNING = b'\xF0\x9F\x99\x8D' + PERSON_WITH_POUTING_FACE = b'\xF0\x9F\x99\x8E' + PERSON_WITH_FOLDED_HANDS = b'\xF0\x9F\x99\x8F' + BLACK_SCISSORS = b'\xE2\x9C\x82' + WHITE_HEAVY_CHECK_MARK = b'\xE2\x9C\x85' + AIRPLANE = b'\xE2\x9C\x88' + ENVELOPE = b'\xE2\x9C\x89' + RAISED_FIST = b'\xE2\x9C\x8A' + RAISED_HAND = b'\xE2\x9C\x8B' + VICTORY_HAND = b'\xE2\x9C\x8C' + PENCIL = b'\xE2\x9C\x8F' + BLACK_NIB = b'\xE2\x9C\x92' + HEAVY_CHECK_MARK = b'\xE2\x9C\x94' + HEAVY_MULTIPLICATION_X = b'\xE2\x9C\x96' + SPARKLES = b'\xE2\x9C\xA8' + EIGHT_SPOKED_ASTERISK = b'\xE2\x9C\xB3' + EIGHT_POINTED_BLACK_STAR = b'\xE2\x9C\xB4' + SNOWFLAKE = b'\xE2\x9D\x84' + SPARKLE = b'\xE2\x9D\x87' + CROSS_MARK = b'\xE2\x9D\x8C' + NEGATIVE_SQUARED_CROSS_MARK = b'\xE2\x9D\x8E' + BLACK_QUESTION_MARK_ORNAMENT = b'\xE2\x9D\x93' + WHITE_QUESTION_MARK_ORNAMENT = b'\xE2\x9D\x94' + WHITE_EXCLAMATION_MARK_ORNAMENT = b'\xE2\x9D\x95' + HEAVY_EXCLAMATION_MARK_SYMBOL = b'\xE2\x9D\x97' + HEAVY_BLACK_HEART = b'\xE2\x9D\xA4' + HEAVY_PLUS_SIGN = b'\xE2\x9E\x95' + HEAVY_MINUS_SIGN = b'\xE2\x9E\x96' + HEAVY_DIVISION_SIGN = b'\xE2\x9E\x97' + BLACK_RIGHTWARDS_ARROW = b'\xE2\x9E\xA1' + CURLY_LOOP = b'\xE2\x9E\xB0' + ROCKET = b'\xF0\x9F\x9A\x80' + RAILWAY_CAR = b'\xF0\x9F\x9A\x83' + HIGH_SPEED_TRAIN = b'\xF0\x9F\x9A\x84' + HIGH_SPEED_TRAIN_WITH_BULLET_NOSE = b'\xF0\x9F\x9A\x85' + METRO = b'\xF0\x9F\x9A\x87' + STATION = b'\xF0\x9F\x9A\x89' + BUS = b'\xF0\x9F\x9A\x8C' + BUS_STOP = b'\xF0\x9F\x9A\x8F' + AMBULANCE = b'\xF0\x9F\x9A\x91' + FIRE_ENGINE = b'\xF0\x9F\x9A\x92' + POLICE_CAR = b'\xF0\x9F\x9A\x93' + TAXI = b'\xF0\x9F\x9A\x95' + AUTOMOBILE = b'\xF0\x9F\x9A\x97' + RECREATIONAL_VEHICLE = b'\xF0\x9F\x9A\x99' + DELIVERY_TRUCK = b'\xF0\x9F\x9A\x9A' + SHIP = b'\xF0\x9F\x9A\xA2' + SPEEDBOAT = b'\xF0\x9F\x9A\xA4' + HORIZONTAL_TRAFFIC_LIGHT = b'\xF0\x9F\x9A\xA5' + CONSTRUCTION_SIGN = b'\xF0\x9F\x9A\xA7' + POLICE_CARS_REVOLVING_LIGHT = b'\xF0\x9F\x9A\xA8' + TRIANGULAR_FLAG_ON_POST = b'\xF0\x9F\x9A\xA9' + DOOR = b'\xF0\x9F\x9A\xAA' + NO_ENTRY_SIGN = b'\xF0\x9F\x9A\xAB' + SMOKING_SYMBOL = b'\xF0\x9F\x9A\xAC' + NO_SMOKING_SYMBOL = b'\xF0\x9F\x9A\xAD' + BICYCLE = b'\xF0\x9F\x9A\xB2' + PEDESTRIAN = b'\xF0\x9F\x9A\xB6' + MENS_SYMBOL = b'\xF0\x9F\x9A\xB9' + WOMENS_SYMBOL = b'\xF0\x9F\x9A\xBA' + RESTROOM = b'\xF0\x9F\x9A\xBB' + BABY_SYMBOL = b'\xF0\x9F\x9A\xBC' + TOILET = b'\xF0\x9F\x9A\xBD' + WATER_CLOSET = b'\xF0\x9F\x9A\xBE' + BATH = b'\xF0\x9F\x9B\x80' + CIRCLED_LATIN_CAPITAL_LETTER_M = b'\xE2\x93\x82' + NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_A = b'\xF0\x9F\x85\xB0' + NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_B = b'\xF0\x9F\x85\xB1' + NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_O = b'\xF0\x9F\x85\xBE' + NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_P = b'\xF0\x9F\x85\xBF' + NEGATIVE_SQUARED_AB = b'\xF0\x9F\x86\x8E' + SQUARED_CL = b'\xF0\x9F\x86\x91' + SQUARED_COOL = b'\xF0\x9F\x86\x92' + SQUARED_FREE = b'\xF0\x9F\x86\x93' + SQUARED_ID = b'\xF0\x9F\x86\x94' + SQUARED_NEW = b'\xF0\x9F\x86\x95' + SQUARED_NG = b'\xF0\x9F\x86\x96' + SQUARED_OK = b'\xF0\x9F\x86\x97' + SQUARED_SOS = b'\xF0\x9F\x86\x98' + SQUARED_UP_WITH_EXCLAMATION_MARK = b'\xF0\x9F\x86\x99' + SQUARED_VS = b'\xF0\x9F\x86\x9A' + REGIONAL_INDICATOR_SYMBOL_LETTER_D_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_E\ + = b'\xF0\x9F\x87\xA9\xF0\x9F\x87\xAA' + REGIONAL_INDICATOR_SYMBOL_LETTER_G_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_B\ + = b'\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7' + REGIONAL_INDICATOR_SYMBOL_LETTER_C_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_N\ + = b'\xF0\x9F\x87\xA8\xF0\x9F\x87\xB3' + REGIONAL_INDICATOR_SYMBOL_LETTER_J_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_P\ + = b'\xF0\x9F\x87\xAF\xF0\x9F\x87\xB5' + REGIONAL_INDICATOR_SYMBOL_LETTER_K_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R\ + = b'\xF0\x9F\x87\xB0\xF0\x9F\x87\xB7' + REGIONAL_INDICATOR_SYMBOL_LETTER_F_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R\ + = b'\xF0\x9F\x87\xAB\xF0\x9F\x87\xB7' + REGIONAL_INDICATOR_SYMBOL_LETTER_E_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S\ + = b'\xF0\x9F\x87\xAA\xF0\x9F\x87\xB8' + REGIONAL_INDICATOR_SYMBOL_LETTER_I_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_T\ + = b'\xF0\x9F\x87\xAE\xF0\x9F\x87\xB9' + REGIONAL_INDICATOR_SYMBOL_LETTER_U_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S\ + = b'\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8' + REGIONAL_INDICATOR_SYMBOL_LETTER_R_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_U\ + = b'\xF0\x9F\x87\xB7\xF0\x9F\x87\xBA' + SQUARED_KATAKANA_KOKO = b'\xF0\x9F\x88\x81' + SQUARED_KATAKANA_SA = b'\xF0\x9F\x88\x82' + SQUARED_CJK_UNIFIED_IDEOGRAPH_7121 = b'\xF0\x9F\x88\x9A' + SQUARED_CJK_UNIFIED_IDEOGRAPH_6307 = b'\xF0\x9F\x88\xAF' + SQUARED_CJK_UNIFIED_IDEOGRAPH_7981 = b'\xF0\x9F\x88\xB2' + SQUARED_CJK_UNIFIED_IDEOGRAPH_7A7A = b'\xF0\x9F\x88\xB3' + SQUARED_CJK_UNIFIED_IDEOGRAPH_5408 = b'\xF0\x9F\x88\xB4' + SQUARED_CJK_UNIFIED_IDEOGRAPH_6E80 = b'\xF0\x9F\x88\xB5' + SQUARED_CJK_UNIFIED_IDEOGRAPH_6709 = b'\xF0\x9F\x88\xB6' + SQUARED_CJK_UNIFIED_IDEOGRAPH_6708 = b'\xF0\x9F\x88\xB7' + SQUARED_CJK_UNIFIED_IDEOGRAPH_7533 = b'\xF0\x9F\x88\xB8' + SQUARED_CJK_UNIFIED_IDEOGRAPH_5272 = b'\xF0\x9F\x88\xB9' + SQUARED_CJK_UNIFIED_IDEOGRAPH_55B6 = b'\xF0\x9F\x88\xBA' + CIRCLED_IDEOGRAPH_ADVANTAGE = b'\xF0\x9F\x89\x90' + CIRCLED_IDEOGRAPH_ACCEPT = b'\xF0\x9F\x89\x91' + COPYRIGHT_SIGN = b'\xC2\xA9' + REGISTERED_SIGN = b'\xC2\xAE' + DOUBLE_EXCLAMATION_MARK = b'\xE2\x80\xBC' + EXCLAMATION_QUESTION_MARK = b'\xE2\x81\x89' + DIGIT_EIGHT_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x38\xE2\x83\xA3' + DIGIT_NINE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x39\xE2\x83\xA3' + DIGIT_SEVEN_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x37\xE2\x83\xA3' + DIGIT_SIX_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x36\xE2\x83\xA3' + DIGIT_ONE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x31\xE2\x83\xA3' + DIGIT_ZERO_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x30\xE2\x83\xA3' + DIGIT_TWO_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x32\xE2\x83\xA3' + DIGIT_THREE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x33\xE2\x83\xA3' + DIGIT_FIVE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x35\xE2\x83\xA3' + DIGIT_FOUR_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x34\xE2\x83\xA3' + NUMBER_SIGN_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x23\xE2\x83\xA3' + TRADE_MARK_SIGN = b'\xE2\x84\xA2' + INFORMATION_SOURCE = b'\xE2\x84\xB9' + LEFT_RIGHT_ARROW = b'\xE2\x86\x94' + UP_DOWN_ARROW = b'\xE2\x86\x95' + NORTH_WEST_ARROW = b'\xE2\x86\x96' + NORTH_EAST_ARROW = b'\xE2\x86\x97' + SOUTH_EAST_ARROW = b'\xE2\x86\x98' + SOUTH_WEST_ARROW = b'\xE2\x86\x99' + LEFTWARDS_ARROW_WITH_HOOK = b'\xE2\x86\xA9' + RIGHTWARDS_ARROW_WITH_HOOK = b'\xE2\x86\xAA' + WATCH = b'\xE2\x8C\x9A' + HOURGLASS = b'\xE2\x8C\x9B' + BLACK_RIGHT_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xA9' + BLACK_LEFT_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xAA' + BLACK_UP_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xAB' + BLACK_DOWN_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xAC' + ALARM_CLOCK = b'\xE2\x8F\xB0' + HOURGLASS_WITH_FLOWING_SAND = b'\xE2\x8F\xB3' + BLACK_SMALL_SQUARE = b'\xE2\x96\xAA' + WHITE_SMALL_SQUARE = b'\xE2\x96\xAB' + BLACK_RIGHT_POINTING_TRIANGLE = b'\xE2\x96\xB6' + BLACK_LEFT_POINTING_TRIANGLE = b'\xE2\x97\x80' + WHITE_MEDIUM_SQUARE = b'\xE2\x97\xBB' + BLACK_MEDIUM_SQUARE = b'\xE2\x97\xBC' + WHITE_MEDIUM_SMALL_SQUARE = b'\xE2\x97\xBD' + BLACK_MEDIUM_SMALL_SQUARE = b'\xE2\x97\xBE' + BLACK_SUN_WITH_RAYS = b'\xE2\x98\x80' + CLOUD = b'\xE2\x98\x81' + BLACK_TELEPHONE = b'\xE2\x98\x8E' + BALLOT_BOX_WITH_CHECK = b'\xE2\x98\x91' + UMBRELLA_WITH_RAIN_DROPS = b'\xE2\x98\x94' + HOT_BEVERAGE = b'\xE2\x98\x95' + WHITE_UP_POINTING_INDEX = b'\xE2\x98\x9D' + WHITE_SMILING_FACE = b'\xE2\x98\xBA' + ARIES = b'\xE2\x99\x88' + TAURUS = b'\xE2\x99\x89' + GEMINI = b'\xE2\x99\x8A' + CANCER = b'\xE2\x99\x8B' + LEO = b'\xE2\x99\x8C' + VIRGO = b'\xE2\x99\x8D' + LIBRA = b'\xE2\x99\x8E' + SCORPIUS = b'\xE2\x99\x8F' + SAGITTARIUS = b'\xE2\x99\x90' + CAPRICORN = b'\xE2\x99\x91' + AQUARIUS = b'\xE2\x99\x92' + PISCES = b'\xE2\x99\x93' + BLACK_SPADE_SUIT = b'\xE2\x99\xA0' + BLACK_CLUB_SUIT = b'\xE2\x99\xA3' + BLACK_HEART_SUIT = b'\xE2\x99\xA5' + BLACK_DIAMOND_SUIT = b'\xE2\x99\xA6' + HOT_SPRINGS = b'\xE2\x99\xA8' + BLACK_UNIVERSAL_RECYCLING_SYMBOL = b'\xE2\x99\xBB' + WHEELCHAIR_SYMBOL = b'\xE2\x99\xBF' + ANCHOR = b'\xE2\x9A\x93' + WARNING_SIGN = b'\xE2\x9A\xA0' + HIGH_VOLTAGE_SIGN = b'\xE2\x9A\xA1' + MEDIUM_WHITE_CIRCLE = b'\xE2\x9A\xAA' + MEDIUM_BLACK_CIRCLE = b'\xE2\x9A\xAB' + SOCCER_BALL = b'\xE2\x9A\xBD' + BASEBALL = b'\xE2\x9A\xBE' + SNOWMAN_WITHOUT_SNOW = b'\xE2\x9B\x84' + SUN_BEHIND_CLOUD = b'\xE2\x9B\x85' + OPHIUCHUS = b'\xE2\x9B\x8E' + NO_ENTRY = b'\xE2\x9B\x94' + CHURCH = b'\xE2\x9B\xAA' + FOUNTAIN = b'\xE2\x9B\xB2' + FLAG_IN_HOLE = b'\xE2\x9B\xB3' + SAILBOAT = b'\xE2\x9B\xB5' + TENT = b'\xE2\x9B\xBA' + FUEL_PUMP = b'\xE2\x9B\xBD' + ARROW_POINTING_RIGHTWARDS_THEN_CURVING_UPWARDS = b'\xE2\xA4\xB4' + ARROW_POINTING_RIGHTWARDS_THEN_CURVING_DOWNWARDS = b'\xE2\xA4\xB5' + LEFTWARDS_BLACK_ARROW = b'\xE2\xAC\x85' + UPWARDS_BLACK_ARROW = b'\xE2\xAC\x86' + DOWNWARDS_BLACK_ARROW = b'\xE2\xAC\x87' + BLACK_LARGE_SQUARE = b'\xE2\xAC\x9B' + WHITE_LARGE_SQUARE = b'\xE2\xAC\x9C' + WHITE_MEDIUM_STAR = b'\xE2\xAD\x90' + HEAVY_LARGE_CIRCLE = b'\xE2\xAD\x95' + WAVY_DASH = b'\xE3\x80\xB0' + PART_ALTERNATION_MARK = b'\xE3\x80\xBD' + CIRCLED_IDEOGRAPH_CONGRATULATION = b'\xE3\x8A\x97' + CIRCLED_IDEOGRAPH_SECRET = b'\xE3\x8A\x99' + MAHJONG_TILE_RED_DRAGON = b'\xF0\x9F\x80\x84' + PLAYING_CARD_BLACK_JOKER = b'\xF0\x9F\x83\x8F' + CYCLONE = b'\xF0\x9F\x8C\x80' + FOGGY = b'\xF0\x9F\x8C\x81' + CLOSED_UMBRELLA = b'\xF0\x9F\x8C\x82' + NIGHT_WITH_STARS = b'\xF0\x9F\x8C\x83' + SUNRISE_OVER_MOUNTAINS = b'\xF0\x9F\x8C\x84' + SUNRISE = b'\xF0\x9F\x8C\x85' + CITYSCAPE_AT_DUSK = b'\xF0\x9F\x8C\x86' + SUNSET_OVER_BUILDINGS = b'\xF0\x9F\x8C\x87' + RAINBOW = b'\xF0\x9F\x8C\x88' + BRIDGE_AT_NIGHT = b'\xF0\x9F\x8C\x89' + WATER_WAVE = b'\xF0\x9F\x8C\x8A' + VOLCANO = b'\xF0\x9F\x8C\x8B' + MILKY_WAY = b'\xF0\x9F\x8C\x8C' + EARTH_GLOBE_ASIA_AUSTRALIA = b'\xF0\x9F\x8C\x8F' + NEW_MOON_SYMBOL = b'\xF0\x9F\x8C\x91' + FIRST_QUARTER_MOON_SYMBOL = b'\xF0\x9F\x8C\x93' + WAXING_GIBBOUS_MOON_SYMBOL = b'\xF0\x9F\x8C\x94' + FULL_MOON_SYMBOL = b'\xF0\x9F\x8C\x95' + CRESCENT_MOON = b'\xF0\x9F\x8C\x99' + FIRST_QUARTER_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9B' + GLOWING_STAR = b'\xF0\x9F\x8C\x9F' + SHOOTING_STAR = b'\xF0\x9F\x8C\xA0' + CHESTNUT = b'\xF0\x9F\x8C\xB0' + SEEDLING = b'\xF0\x9F\x8C\xB1' + PALM_TREE = b'\xF0\x9F\x8C\xB4' + CACTUS = b'\xF0\x9F\x8C\xB5' + TULIP = b'\xF0\x9F\x8C\xB7' + CHERRY_BLOSSOM = b'\xF0\x9F\x8C\xB8' + ROSE = b'\xF0\x9F\x8C\xB9' + HIBISCUS = b'\xF0\x9F\x8C\xBA' + SUNFLOWER = b'\xF0\x9F\x8C\xBB' + BLOSSOM = b'\xF0\x9F\x8C\xBC' + EAR_OF_MAIZE = b'\xF0\x9F\x8C\xBD' + EAR_OF_RICE = b'\xF0\x9F\x8C\xBE' + HERB = b'\xF0\x9F\x8C\xBF' + FOUR_LEAF_CLOVER = b'\xF0\x9F\x8D\x80' + MAPLE_LEAF = b'\xF0\x9F\x8D\x81' + FALLEN_LEAF = b'\xF0\x9F\x8D\x82' + LEAF_FLUTTERING_IN_WIND = b'\xF0\x9F\x8D\x83' + MUSHROOM = b'\xF0\x9F\x8D\x84' + TOMATO = b'\xF0\x9F\x8D\x85' + AUBERGINE = b'\xF0\x9F\x8D\x86' + GRAPES = b'\xF0\x9F\x8D\x87' + MELON = b'\xF0\x9F\x8D\x88' + WATERMELON = b'\xF0\x9F\x8D\x89' + TANGERINE = b'\xF0\x9F\x8D\x8A' + BANANA = b'\xF0\x9F\x8D\x8C' + PINEAPPLE = b'\xF0\x9F\x8D\x8D' + RED_APPLE = b'\xF0\x9F\x8D\x8E' + GREEN_APPLE = b'\xF0\x9F\x8D\x8F' + PEACH = b'\xF0\x9F\x8D\x91' + CHERRIES = b'\xF0\x9F\x8D\x92' + STRAWBERRY = b'\xF0\x9F\x8D\x93' + HAMBURGER = b'\xF0\x9F\x8D\x94' + SLICE_OF_PIZZA = b'\xF0\x9F\x8D\x95' + MEAT_ON_BONE = b'\xF0\x9F\x8D\x96' + POULTRY_LEG = b'\xF0\x9F\x8D\x97' + RICE_CRACKER = b'\xF0\x9F\x8D\x98' + RICE_BALL = b'\xF0\x9F\x8D\x99' + COOKED_RICE = b'\xF0\x9F\x8D\x9A' + CURRY_AND_RICE = b'\xF0\x9F\x8D\x9B' + STEAMING_BOWL = b'\xF0\x9F\x8D\x9C' + SPAGHETTI = b'\xF0\x9F\x8D\x9D' + BREAD = b'\xF0\x9F\x8D\x9E' + FRENCH_FRIES = b'\xF0\x9F\x8D\x9F' + ROASTED_SWEET_POTATO = b'\xF0\x9F\x8D\xA0' + DANGO = b'\xF0\x9F\x8D\xA1' + ODEN = b'\xF0\x9F\x8D\xA2' + SUSHI = b'\xF0\x9F\x8D\xA3' + FRIED_SHRIMP = b'\xF0\x9F\x8D\xA4' + FISH_CAKE_WITH_SWIRL_DESIGN = b'\xF0\x9F\x8D\xA5' + SOFT_ICE_CREAM = b'\xF0\x9F\x8D\xA6' + SHAVED_ICE = b'\xF0\x9F\x8D\xA7' + ICE_CREAM = b'\xF0\x9F\x8D\xA8' + DOUGHNUT = b'\xF0\x9F\x8D\xA9' + COOKIE = b'\xF0\x9F\x8D\xAA' + CHOCOLATE_BAR = b'\xF0\x9F\x8D\xAB' + CANDY = b'\xF0\x9F\x8D\xAC' + LOLLIPOP = b'\xF0\x9F\x8D\xAD' + CUSTARD = b'\xF0\x9F\x8D\xAE' + HONEY_POT = b'\xF0\x9F\x8D\xAF' + SHORTCAKE = b'\xF0\x9F\x8D\xB0' + BENTO_BOX = b'\xF0\x9F\x8D\xB1' + POT_OF_FOOD = b'\xF0\x9F\x8D\xB2' + COOKING = b'\xF0\x9F\x8D\xB3' + FORK_AND_KNIFE = b'\xF0\x9F\x8D\xB4' + TEACUP_WITHOUT_HANDLE = b'\xF0\x9F\x8D\xB5' + SAKE_BOTTLE_AND_CUP = b'\xF0\x9F\x8D\xB6' + WINE_GLASS = b'\xF0\x9F\x8D\xB7' + COCKTAIL_GLASS = b'\xF0\x9F\x8D\xB8' + TROPICAL_DRINK = b'\xF0\x9F\x8D\xB9' + BEER_MUG = b'\xF0\x9F\x8D\xBA' + CLINKING_BEER_MUGS = b'\xF0\x9F\x8D\xBB' + RIBBON = b'\xF0\x9F\x8E\x80' + WRAPPED_PRESENT = b'\xF0\x9F\x8E\x81' + BIRTHDAY_CAKE = b'\xF0\x9F\x8E\x82' + JACK_O_LANTERN = b'\xF0\x9F\x8E\x83' + CHRISTMAS_TREE = b'\xF0\x9F\x8E\x84' + FATHER_CHRISTMAS = b'\xF0\x9F\x8E\x85' + FIREWORKS = b'\xF0\x9F\x8E\x86' + FIREWORK_SPARKLER = b'\xF0\x9F\x8E\x87' + BALLOON = b'\xF0\x9F\x8E\x88' + PARTY_POPPER = b'\xF0\x9F\x8E\x89' + CONFETTI_BALL = b'\xF0\x9F\x8E\x8A' + TANABATA_TREE = b'\xF0\x9F\x8E\x8B' + CROSSED_FLAGS = b'\xF0\x9F\x8E\x8C' + PINE_DECORATION = b'\xF0\x9F\x8E\x8D' + JAPANESE_DOLLS = b'\xF0\x9F\x8E\x8E' + CARP_STREAMER = b'\xF0\x9F\x8E\x8F' + WIND_CHIME = b'\xF0\x9F\x8E\x90' + MOON_VIEWING_CEREMONY = b'\xF0\x9F\x8E\x91' + SCHOOL_SATCHEL = b'\xF0\x9F\x8E\x92' + GRADUATION_CAP = b'\xF0\x9F\x8E\x93' + CAROUSEL_HORSE = b'\xF0\x9F\x8E\xA0' + FERRIS_WHEEL = b'\xF0\x9F\x8E\xA1' + ROLLER_COASTER = b'\xF0\x9F\x8E\xA2' + FISHING_POLE_AND_FISH = b'\xF0\x9F\x8E\xA3' + MICROPHONE = b'\xF0\x9F\x8E\xA4' + MOVIE_CAMERA = b'\xF0\x9F\x8E\xA5' + CINEMA = b'\xF0\x9F\x8E\xA6' + HEADPHONE = b'\xF0\x9F\x8E\xA7' + ARTIST_PALETTE = b'\xF0\x9F\x8E\xA8' + TOP_HAT = b'\xF0\x9F\x8E\xA9' + CIRCUS_TENT = b'\xF0\x9F\x8E\xAA' + TICKET = b'\xF0\x9F\x8E\xAB' + CLAPPER_BOARD = b'\xF0\x9F\x8E\xAC' + PERFORMING_ARTS = b'\xF0\x9F\x8E\xAD' + VIDEO_GAME = b'\xF0\x9F\x8E\xAE' + DIRECT_HIT = b'\xF0\x9F\x8E\xAF' + SLOT_MACHINE = b'\xF0\x9F\x8E\xB0' + BILLIARDS = b'\xF0\x9F\x8E\xB1' + GAME_DIE = b'\xF0\x9F\x8E\xB2' + BOWLING = b'\xF0\x9F\x8E\xB3' + FLOWER_PLAYING_CARDS = b'\xF0\x9F\x8E\xB4' + MUSICAL_NOTE = b'\xF0\x9F\x8E\xB5' + MULTIPLE_MUSICAL_NOTES = b'\xF0\x9F\x8E\xB6' + SAXOPHONE = b'\xF0\x9F\x8E\xB7' + GUITAR = b'\xF0\x9F\x8E\xB8' + MUSICAL_KEYBOARD = b'\xF0\x9F\x8E\xB9' + TRUMPET = b'\xF0\x9F\x8E\xBA' + VIOLIN = b'\xF0\x9F\x8E\xBB' + MUSICAL_SCORE = b'\xF0\x9F\x8E\xBC' + RUNNING_SHIRT_WITH_SASH = b'\xF0\x9F\x8E\xBD' + TENNIS_RACQUET_AND_BALL = b'\xF0\x9F\x8E\xBE' + SKI_AND_SKI_BOOT = b'\xF0\x9F\x8E\xBF' + BASKETBALL_AND_HOOP = b'\xF0\x9F\x8F\x80' + CHEQUERED_FLAG = b'\xF0\x9F\x8F\x81' + SNOWBOARDER = b'\xF0\x9F\x8F\x82' + RUNNER = b'\xF0\x9F\x8F\x83' + SURFER = b'\xF0\x9F\x8F\x84' + TROPHY = b'\xF0\x9F\x8F\x86' + AMERICAN_FOOTBALL = b'\xF0\x9F\x8F\x88' + SWIMMER = b'\xF0\x9F\x8F\x8A' + HOUSE_BUILDING = b'\xF0\x9F\x8F\xA0' + HOUSE_WITH_GARDEN = b'\xF0\x9F\x8F\xA1' + OFFICE_BUILDING = b'\xF0\x9F\x8F\xA2' + JAPANESE_POST_OFFICE = b'\xF0\x9F\x8F\xA3' + HOSPITAL = b'\xF0\x9F\x8F\xA5' + BANK = b'\xF0\x9F\x8F\xA6' + AUTOMATED_TELLER_MACHINE = b'\xF0\x9F\x8F\xA7' + HOTEL = b'\xF0\x9F\x8F\xA8' + LOVE_HOTEL = b'\xF0\x9F\x8F\xA9' + CONVENIENCE_STORE = b'\xF0\x9F\x8F\xAA' + SCHOOL = b'\xF0\x9F\x8F\xAB' + DEPARTMENT_STORE = b'\xF0\x9F\x8F\xAC' + FACTORY = b'\xF0\x9F\x8F\xAD' + IZAKAYA_LANTERN = b'\xF0\x9F\x8F\xAE' + JAPANESE_CASTLE = b'\xF0\x9F\x8F\xAF' + EUROPEAN_CASTLE = b'\xF0\x9F\x8F\xB0' + SNAIL = b'\xF0\x9F\x90\x8C' + SNAKE = b'\xF0\x9F\x90\x8D' + HORSE = b'\xF0\x9F\x90\x8E' + SHEEP = b'\xF0\x9F\x90\x91' + MONKEY = b'\xF0\x9F\x90\x92' + CHICKEN = b'\xF0\x9F\x90\x94' + BOAR = b'\xF0\x9F\x90\x97' + ELEPHANT = b'\xF0\x9F\x90\x98' + OCTOPUS = b'\xF0\x9F\x90\x99' + SPIRAL_SHELL = b'\xF0\x9F\x90\x9A' + BUG = b'\xF0\x9F\x90\x9B' + ANT = b'\xF0\x9F\x90\x9C' + HONEYBEE = b'\xF0\x9F\x90\x9D' + LADY_BEETLE = b'\xF0\x9F\x90\x9E' + FISH = b'\xF0\x9F\x90\x9F' + TROPICAL_FISH = b'\xF0\x9F\x90\xA0' + BLOWFISH = b'\xF0\x9F\x90\xA1' + TURTLE = b'\xF0\x9F\x90\xA2' + HATCHING_CHICK = b'\xF0\x9F\x90\xA3' + BABY_CHICK = b'\xF0\x9F\x90\xA4' + FRONT_FACING_BABY_CHICK = b'\xF0\x9F\x90\xA5' + BIRD = b'\xF0\x9F\x90\xA6' + PENGUIN = b'\xF0\x9F\x90\xA7' + KOALA = b'\xF0\x9F\x90\xA8' + POODLE = b'\xF0\x9F\x90\xA9' + BACTRIAN_CAMEL = b'\xF0\x9F\x90\xAB' + DOLPHIN = b'\xF0\x9F\x90\xAC' + MOUSE_FACE = b'\xF0\x9F\x90\xAD' + COW_FACE = b'\xF0\x9F\x90\xAE' + TIGER_FACE = b'\xF0\x9F\x90\xAF' + RABBIT_FACE = b'\xF0\x9F\x90\xB0' + CAT_FACE = b'\xF0\x9F\x90\xB1' + DRAGON_FACE = b'\xF0\x9F\x90\xB2' + SPOUTING_WHALE = b'\xF0\x9F\x90\xB3' + HORSE_FACE = b'\xF0\x9F\x90\xB4' + MONKEY_FACE = b'\xF0\x9F\x90\xB5' + DOG_FACE = b'\xF0\x9F\x90\xB6' + PIG_FACE = b'\xF0\x9F\x90\xB7' + FROG_FACE = b'\xF0\x9F\x90\xB8' + HAMSTER_FACE = b'\xF0\x9F\x90\xB9' + WOLF_FACE = b'\xF0\x9F\x90\xBA' + BEAR_FACE = b'\xF0\x9F\x90\xBB' + PANDA_FACE = b'\xF0\x9F\x90\xBC' + PIG_NOSE = b'\xF0\x9F\x90\xBD' + PAW_PRINTS = b'\xF0\x9F\x90\xBE' + EYES = b'\xF0\x9F\x91\x80' + EAR = b'\xF0\x9F\x91\x82' + NOSE = b'\xF0\x9F\x91\x83' + MOUTH = b'\xF0\x9F\x91\x84' + TONGUE = b'\xF0\x9F\x91\x85' + WHITE_UP_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x86' + WHITE_DOWN_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x87' + WHITE_LEFT_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x88' + WHITE_RIGHT_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x89' + FISTED_HAND_SIGN = b'\xF0\x9F\x91\x8A' + WAVING_HAND_SIGN = b'\xF0\x9F\x91\x8B' + OK_HAND_SIGN = b'\xF0\x9F\x91\x8C' + THUMBS_UP_SIGN = b'\xF0\x9F\x91\x8D' + THUMBS_DOWN_SIGN = b'\xF0\x9F\x91\x8E' + CLAPPING_HANDS_SIGN = b'\xF0\x9F\x91\x8F' + OPEN_HANDS_SIGN = b'\xF0\x9F\x91\x90' + CROWN = b'\xF0\x9F\x91\x91' + WOMANS_HAT = b'\xF0\x9F\x91\x92' + EYEGLASSES = b'\xF0\x9F\x91\x93' + NECKTIE = b'\xF0\x9F\x91\x94' + T_SHIRT = b'\xF0\x9F\x91\x95' + JEANS = b'\xF0\x9F\x91\x96' + DRESS = b'\xF0\x9F\x91\x97' + KIMONO = b'\xF0\x9F\x91\x98' + BIKINI = b'\xF0\x9F\x91\x99' + WOMANS_CLOTHES = b'\xF0\x9F\x91\x9A' + PURSE = b'\xF0\x9F\x91\x9B' + HANDBAG = b'\xF0\x9F\x91\x9C' + POUCH = b'\xF0\x9F\x91\x9D' + MANS_SHOE = b'\xF0\x9F\x91\x9E' + ATHLETIC_SHOE = b'\xF0\x9F\x91\x9F' + HIGH_HEELED_SHOE = b'\xF0\x9F\x91\xA0' + WOMANS_SANDAL = b'\xF0\x9F\x91\xA1' + WOMANS_BOOTS = b'\xF0\x9F\x91\xA2' + FOOTPRINTS = b'\xF0\x9F\x91\xA3' + BUST_IN_SILHOUETTE = b'\xF0\x9F\x91\xA4' + BOY = b'\xF0\x9F\x91\xA6' + GIRL = b'\xF0\x9F\x91\xA7' + MAN = b'\xF0\x9F\x91\xA8' + WOMAN = b'\xF0\x9F\x91\xA9' + FAMILY = b'\xF0\x9F\x91\xAA' + MAN_AND_WOMAN_HOLDING_HANDS = b'\xF0\x9F\x91\xAB' + POLICE_OFFICER = b'\xF0\x9F\x91\xAE' + WOMAN_WITH_BUNNY_EARS = b'\xF0\x9F\x91\xAF' + BRIDE_WITH_VEIL = b'\xF0\x9F\x91\xB0' + PERSON_WITH_BLOND_HAIR = b'\xF0\x9F\x91\xB1' + MAN_WITH_GUA_PI_MAO = b'\xF0\x9F\x91\xB2' + MAN_WITH_TURBAN = b'\xF0\x9F\x91\xB3' + OLDER_MAN = b'\xF0\x9F\x91\xB4' + OLDER_WOMAN = b'\xF0\x9F\x91\xB5' + BABY = b'\xF0\x9F\x91\xB6' + CONSTRUCTION_WORKER = b'\xF0\x9F\x91\xB7' + PRINCESS = b'\xF0\x9F\x91\xB8' + JAPANESE_OGRE = b'\xF0\x9F\x91\xB9' + JAPANESE_GOBLIN = b'\xF0\x9F\x91\xBA' + GHOST = b'\xF0\x9F\x91\xBB' + BABY_ANGEL = b'\xF0\x9F\x91\xBC' + EXTRATERRESTRIAL_ALIEN = b'\xF0\x9F\x91\xBD' + ALIEN_MONSTER = b'\xF0\x9F\x91\xBE' + IMP = b'\xF0\x9F\x91\xBF' + SKULL = b'\xF0\x9F\x92\x80' + INFORMATION_DESK_PERSON = b'\xF0\x9F\x92\x81' + GUARDSMAN = b'\xF0\x9F\x92\x82' + DANCER = b'\xF0\x9F\x92\x83' + LIPSTICK = b'\xF0\x9F\x92\x84' + NAIL_POLISH = b'\xF0\x9F\x92\x85' + FACE_MASSAGE = b'\xF0\x9F\x92\x86' + HAIRCUT = b'\xF0\x9F\x92\x87' + BARBER_POLE = b'\xF0\x9F\x92\x88' + SYRINGE = b'\xF0\x9F\x92\x89' + PILL = b'\xF0\x9F\x92\x8A' + KISS_MARK = b'\xF0\x9F\x92\x8B' + LOVE_LETTER = b'\xF0\x9F\x92\x8C' + RING = b'\xF0\x9F\x92\x8D' + GEM_STONE = b'\xF0\x9F\x92\x8E' + KISS = b'\xF0\x9F\x92\x8F' + BOUQUET = b'\xF0\x9F\x92\x90' + COUPLE_WITH_HEART = b'\xF0\x9F\x92\x91' + WEDDING = b'\xF0\x9F\x92\x92' + BEATING_HEART = b'\xF0\x9F\x92\x93' + BROKEN_HEART = b'\xF0\x9F\x92\x94' + TWO_HEARTS = b'\xF0\x9F\x92\x95' + SPARKLING_HEART = b'\xF0\x9F\x92\x96' + GROWING_HEART = b'\xF0\x9F\x92\x97' + HEART_WITH_ARROW = b'\xF0\x9F\x92\x98' + BLUE_HEART = b'\xF0\x9F\x92\x99' + GREEN_HEART = b'\xF0\x9F\x92\x9A' + YELLOW_HEART = b'\xF0\x9F\x92\x9B' + PURPLE_HEART = b'\xF0\x9F\x92\x9C' + HEART_WITH_RIBBON = b'\xF0\x9F\x92\x9D' + REVOLVING_HEARTS = b'\xF0\x9F\x92\x9E' + HEART_DECORATION = b'\xF0\x9F\x92\x9F' + DIAMOND_SHAPE_WITH_A_DOT_INSIDE = b'\xF0\x9F\x92\xA0' + ELECTRIC_LIGHT_BULB = b'\xF0\x9F\x92\xA1' + ANGER_SYMBOL = b'\xF0\x9F\x92\xA2' + BOMB = b'\xF0\x9F\x92\xA3' + SLEEPING_SYMBOL = b'\xF0\x9F\x92\xA4' + COLLISION_SYMBOL = b'\xF0\x9F\x92\xA5' + SPLASHING_SWEAT_SYMBOL = b'\xF0\x9F\x92\xA6' + DROPLET = b'\xF0\x9F\x92\xA7' + DASH_SYMBOL = b'\xF0\x9F\x92\xA8' + PILE_OF_POO = b'\xF0\x9F\x92\xA9' + FLEXED_BICEPS = b'\xF0\x9F\x92\xAA' + DIZZY_SYMBOL = b'\xF0\x9F\x92\xAB' + SPEECH_BALLOON = b'\xF0\x9F\x92\xAC' + WHITE_FLOWER = b'\xF0\x9F\x92\xAE' + HUNDRED_POINTS_SYMBOL = b'\xF0\x9F\x92\xAF' + MONEY_BAG = b'\xF0\x9F\x92\xB0' + CURRENCY_EXCHANGE = b'\xF0\x9F\x92\xB1' + HEAVY_DOLLAR_SIGN = b'\xF0\x9F\x92\xB2' + CREDIT_CARD = b'\xF0\x9F\x92\xB3' + BANKNOTE_WITH_YEN_SIGN = b'\xF0\x9F\x92\xB4' + BANKNOTE_WITH_DOLLAR_SIGN = b'\xF0\x9F\x92\xB5' + MONEY_WITH_WINGS = b'\xF0\x9F\x92\xB8' + CHART_WITH_UPWARDS_TREND_AND_YEN_SIGN = b'\xF0\x9F\x92\xB9' + SEAT = b'\xF0\x9F\x92\xBA' + PERSONAL_COMPUTER = b'\xF0\x9F\x92\xBB' + BRIEFCASE = b'\xF0\x9F\x92\xBC' + MINIDISC = b'\xF0\x9F\x92\xBD' + FLOPPY_DISK = b'\xF0\x9F\x92\xBE' + OPTICAL_DISC = b'\xF0\x9F\x92\xBF' + DVD = b'\xF0\x9F\x93\x80' + FILE_FOLDER = b'\xF0\x9F\x93\x81' + OPEN_FILE_FOLDER = b'\xF0\x9F\x93\x82' + PAGE_WITH_CURL = b'\xF0\x9F\x93\x83' + PAGE_FACING_UP = b'\xF0\x9F\x93\x84' + CALENDAR = b'\xF0\x9F\x93\x85' + TEAR_OFF_CALENDAR = b'\xF0\x9F\x93\x86' + CARD_INDEX = b'\xF0\x9F\x93\x87' + CHART_WITH_UPWARDS_TREND = b'\xF0\x9F\x93\x88' + CHART_WITH_DOWNWARDS_TREND = b'\xF0\x9F\x93\x89' + BAR_CHART = b'\xF0\x9F\x93\x8A' + CLIPBOARD = b'\xF0\x9F\x93\x8B' + PUSHPIN = b'\xF0\x9F\x93\x8C' + ROUND_PUSHPIN = b'\xF0\x9F\x93\x8D' + PAPERCLIP = b'\xF0\x9F\x93\x8E' + STRAIGHT_RULER = b'\xF0\x9F\x93\x8F' + TRIANGULAR_RULER = b'\xF0\x9F\x93\x90' + BOOKMARK_TABS = b'\xF0\x9F\x93\x91' + LEDGER = b'\xF0\x9F\x93\x92' + NOTEBOOK = b'\xF0\x9F\x93\x93' + NOTEBOOK_WITH_DECORATIVE_COVER = b'\xF0\x9F\x93\x94' + CLOSED_BOOK = b'\xF0\x9F\x93\x95' + OPEN_BOOK = b'\xF0\x9F\x93\x96' + GREEN_BOOK = b'\xF0\x9F\x93\x97' + BLUE_BOOK = b'\xF0\x9F\x93\x98' + ORANGE_BOOK = b'\xF0\x9F\x93\x99' + BOOKS = b'\xF0\x9F\x93\x9A' + NAME_BADGE = b'\xF0\x9F\x93\x9B' + SCROLL = b'\xF0\x9F\x93\x9C' + MEMO = b'\xF0\x9F\x93\x9D' + TELEPHONE_RECEIVER = b'\xF0\x9F\x93\x9E' + PAGER = b'\xF0\x9F\x93\x9F' + FAX_MACHINE = b'\xF0\x9F\x93\xA0' + SATELLITE_ANTENNA = b'\xF0\x9F\x93\xA1' + PUBLIC_ADDRESS_LOUDSPEAKER = b'\xF0\x9F\x93\xA2' + CHEERING_MEGAPHONE = b'\xF0\x9F\x93\xA3' + OUTBOX_TRAY = b'\xF0\x9F\x93\xA4' + INBOX_TRAY = b'\xF0\x9F\x93\xA5' + PACKAGE = b'\xF0\x9F\x93\xA6' + E_MAIL_SYMBOL = b'\xF0\x9F\x93\xA7' + INCOMING_ENVELOPE = b'\xF0\x9F\x93\xA8' + ENVELOPE_WITH_DOWNWARDS_ARROW_ABOVE = b'\xF0\x9F\x93\xA9' + CLOSED_MAILBOX_WITH_LOWERED_FLAG = b'\xF0\x9F\x93\xAA' + CLOSED_MAILBOX_WITH_RAISED_FLAG = b'\xF0\x9F\x93\xAB' + POSTBOX = b'\xF0\x9F\x93\xAE' + NEWSPAPER = b'\xF0\x9F\x93\xB0' + MOBILE_PHONE = b'\xF0\x9F\x93\xB1' + MOBILE_PHONE_WITH_RIGHTWARDS_ARROW_AT_LEFT = b'\xF0\x9F\x93\xB2' + VIBRATION_MODE = b'\xF0\x9F\x93\xB3' + MOBILE_PHONE_OFF = b'\xF0\x9F\x93\xB4' + ANTENNA_WITH_BARS = b'\xF0\x9F\x93\xB6' + CAMERA = b'\xF0\x9F\x93\xB7' + VIDEO_CAMERA = b'\xF0\x9F\x93\xB9' + TELEVISION = b'\xF0\x9F\x93\xBA' + RADIO = b'\xF0\x9F\x93\xBB' + VIDEOCASSETTE = b'\xF0\x9F\x93\xBC' + CLOCKWISE_DOWNWARDS_AND_UPWARDS_OPEN_CIRCLE_ARROWS = b'\xF0\x9F\x94\x83' + SPEAKER_WITH_THREE_SOUND_WAVES = b'\xF0\x9F\x94\x8A' + BATTERY = b'\xF0\x9F\x94\x8B' + ELECTRIC_PLUG = b'\xF0\x9F\x94\x8C' + LEFT_POINTING_MAGNIFYING_GLASS = b'\xF0\x9F\x94\x8D' + RIGHT_POINTING_MAGNIFYING_GLASS = b'\xF0\x9F\x94\x8E' + LOCK_WITH_INK_PEN = b'\xF0\x9F\x94\x8F' + CLOSED_LOCK_WITH_KEY = b'\xF0\x9F\x94\x90' + KEY = b'\xF0\x9F\x94\x91' + LOCK = b'\xF0\x9F\x94\x92' + OPEN_LOCK = b'\xF0\x9F\x94\x93' + BELL = b'\xF0\x9F\x94\x94' + BOOKMARK = b'\xF0\x9F\x94\x96' + LINK_SYMBOL = b'\xF0\x9F\x94\x97' + RADIO_BUTTON = b'\xF0\x9F\x94\x98' + BACK_WITH_LEFTWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x99' + END_WITH_LEFTWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x9A' + ON_WITH_EXCLAMATION_MARK_WITH_LEFT_RIGHT_ARROW_ABOVE = b'\xF0\x9F\x94\x9B' + SOON_WITH_RIGHTWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x9C' + TOP_WITH_UPWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x9D' + NO_ONE_UNDER_EIGHTEEN_SYMBOL = b'\xF0\x9F\x94\x9E' + KEYCAP_TEN = b'\xF0\x9F\x94\x9F' + INPUT_SYMBOL_FOR_LATIN_CAPITAL_LETTERS = b'\xF0\x9F\x94\xA0' + INPUT_SYMBOL_FOR_LATIN_SMALL_LETTERS = b'\xF0\x9F\x94\xA1' + INPUT_SYMBOL_FOR_NUMBERS = b'\xF0\x9F\x94\xA2' + INPUT_SYMBOL_FOR_SYMBOLS = b'\xF0\x9F\x94\xA3' + INPUT_SYMBOL_FOR_LATIN_LETTERS = b'\xF0\x9F\x94\xA4' + FIRE = b'\xF0\x9F\x94\xA5' + ELECTRIC_TORCH = b'\xF0\x9F\x94\xA6' + WRENCH = b'\xF0\x9F\x94\xA7' + HAMMER = b'\xF0\x9F\x94\xA8' + NUT_AND_BOLT = b'\xF0\x9F\x94\xA9' + HOCHO = b'\xF0\x9F\x94\xAA' + PISTOL = b'\xF0\x9F\x94\xAB' + CRYSTAL_BALL = b'\xF0\x9F\x94\xAE' + SIX_POINTED_STAR_WITH_MIDDLE_DOT = b'\xF0\x9F\x94\xAF' + JAPANESE_SYMBOL_FOR_BEGINNER = b'\xF0\x9F\x94\xB0' + TRIDENT_EMBLEM = b'\xF0\x9F\x94\xB1' + BLACK_SQUARE_BUTTON = b'\xF0\x9F\x94\xB2' + WHITE_SQUARE_BUTTON = b'\xF0\x9F\x94\xB3' + LARGE_RED_CIRCLE = b'\xF0\x9F\x94\xB4' + LARGE_BLUE_CIRCLE = b'\xF0\x9F\x94\xB5' + LARGE_ORANGE_DIAMOND = b'\xF0\x9F\x94\xB6' + LARGE_BLUE_DIAMOND = b'\xF0\x9F\x94\xB7' + SMALL_ORANGE_DIAMOND = b'\xF0\x9F\x94\xB8' + SMALL_BLUE_DIAMOND = b'\xF0\x9F\x94\xB9' + UP_POINTING_RED_TRIANGLE = b'\xF0\x9F\x94\xBA' + DOWN_POINTING_RED_TRIANGLE = b'\xF0\x9F\x94\xBB' + UP_POINTING_SMALL_RED_TRIANGLE = b'\xF0\x9F\x94\xBC' + DOWN_POINTING_SMALL_RED_TRIANGLE = b'\xF0\x9F\x94\xBD' + CLOCK_FACE_ONE_OCLOCK = b'\xF0\x9F\x95\x90' + CLOCK_FACE_TWO_OCLOCK = b'\xF0\x9F\x95\x91' + CLOCK_FACE_THREE_OCLOCK = b'\xF0\x9F\x95\x92' + CLOCK_FACE_FOUR_OCLOCK = b'\xF0\x9F\x95\x93' + CLOCK_FACE_FIVE_OCLOCK = b'\xF0\x9F\x95\x94' + CLOCK_FACE_SIX_OCLOCK = b'\xF0\x9F\x95\x95' + CLOCK_FACE_SEVEN_OCLOCK = b'\xF0\x9F\x95\x96' + CLOCK_FACE_EIGHT_OCLOCK = b'\xF0\x9F\x95\x97' + CLOCK_FACE_NINE_OCLOCK = b'\xF0\x9F\x95\x98' + CLOCK_FACE_TEN_OCLOCK = b'\xF0\x9F\x95\x99' + CLOCK_FACE_ELEVEN_OCLOCK = b'\xF0\x9F\x95\x9A' + CLOCK_FACE_TWELVE_OCLOCK = b'\xF0\x9F\x95\x9B' + MOUNT_FUJI = b'\xF0\x9F\x97\xBB' + TOKYO_TOWER = b'\xF0\x9F\x97\xBC' + STATUE_OF_LIBERTY = b'\xF0\x9F\x97\xBD' + SILHOUETTE_OF_JAPAN = b'\xF0\x9F\x97\xBE' + MOYAI = b'\xF0\x9F\x97\xBF' + GRINNING_FACE = b'\xF0\x9F\x98\x80' + SMILING_FACE_WITH_HALO = b'\xF0\x9F\x98\x87' + SMILING_FACE_WITH_HORNS = b'\xF0\x9F\x98\x88' + SMILING_FACE_WITH_SUNGLASSES = b'\xF0\x9F\x98\x8E' + NEUTRAL_FACE = b'\xF0\x9F\x98\x90' + EXPRESSIONLESS_FACE = b'\xF0\x9F\x98\x91' + CONFUSED_FACE = b'\xF0\x9F\x98\x95' + KISSING_FACE = b'\xF0\x9F\x98\x97' + KISSING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x99' + FACE_WITH_STUCK_OUT_TONGUE = b'\xF0\x9F\x98\x9B' + WORRIED_FACE = b'\xF0\x9F\x98\x9F' + FROWNING_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\xA6' + ANGUISHED_FACE = b'\xF0\x9F\x98\xA7' + GRIMACING_FACE = b'\xF0\x9F\x98\xAC' + FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\xAE' + HUSHED_FACE = b'\xF0\x9F\x98\xAF' + SLEEPING_FACE = b'\xF0\x9F\x98\xB4' + FACE_WITHOUT_MOUTH = b'\xF0\x9F\x98\xB6' + HELICOPTER = b'\xF0\x9F\x9A\x81' + STEAM_LOCOMOTIVE = b'\xF0\x9F\x9A\x82' + TRAIN = b'\xF0\x9F\x9A\x86' + LIGHT_RAIL = b'\xF0\x9F\x9A\x88' + TRAM = b'\xF0\x9F\x9A\x8A' + ONCOMING_BUS = b'\xF0\x9F\x9A\x8D' + TROLLEYBUS = b'\xF0\x9F\x9A\x8E' + MINIBUS = b'\xF0\x9F\x9A\x90' + ONCOMING_POLICE_CAR = b'\xF0\x9F\x9A\x94' + ONCOMING_TAXI = b'\xF0\x9F\x9A\x96' + ONCOMING_AUTOMOBILE = b'\xF0\x9F\x9A\x98' + ARTICULATED_LORRY = b'\xF0\x9F\x9A\x9B' + TRACTOR = b'\xF0\x9F\x9A\x9C' + MONORAIL = b'\xF0\x9F\x9A\x9D' + MOUNTAIN_RAILWAY = b'\xF0\x9F\x9A\x9E' + SUSPENSION_RAILWAY = b'\xF0\x9F\x9A\x9F' + MOUNTAIN_CABLEWAY = b'\xF0\x9F\x9A\xA0' + AERIAL_TRAMWAY = b'\xF0\x9F\x9A\xA1' + ROWBOAT = b'\xF0\x9F\x9A\xA3' + VERTICAL_TRAFFIC_LIGHT = b'\xF0\x9F\x9A\xA6' + PUT_LITTER_IN_ITS_PLACE_SYMBOL = b'\xF0\x9F\x9A\xAE' + DO_NOT_LITTER_SYMBOL = b'\xF0\x9F\x9A\xAF' + POTABLE_WATER_SYMBOL = b'\xF0\x9F\x9A\xB0' + NON_POTABLE_WATER_SYMBOL = b'\xF0\x9F\x9A\xB1' + NO_BICYCLES = b'\xF0\x9F\x9A\xB3' + BICYCLIST = b'\xF0\x9F\x9A\xB4' + MOUNTAIN_BICYCLIST = b'\xF0\x9F\x9A\xB5' + NO_PEDESTRIANS = b'\xF0\x9F\x9A\xB7' + CHILDREN_CROSSING = b'\xF0\x9F\x9A\xB8' + SHOWER = b'\xF0\x9F\x9A\xBF' + BATHTUB = b'\xF0\x9F\x9B\x81' + PASSPORT_CONTROL = b'\xF0\x9F\x9B\x82' + CUSTOMS = b'\xF0\x9F\x9B\x83' + BAGGAGE_CLAIM = b'\xF0\x9F\x9B\x84' + LEFT_LUGGAGE = b'\xF0\x9F\x9B\x85' + EARTH_GLOBE_EUROPE_AFRICA = b'\xF0\x9F\x8C\x8D' + EARTH_GLOBE_AMERICAS = b'\xF0\x9F\x8C\x8E' + GLOBE_WITH_MERIDIANS = b'\xF0\x9F\x8C\x90' + WAXING_CRESCENT_MOON_SYMBOL = b'\xF0\x9F\x8C\x92' + WANING_GIBBOUS_MOON_SYMBOL = b'\xF0\x9F\x8C\x96' + LAST_QUARTER_MOON_SYMBOL = b'\xF0\x9F\x8C\x97' + WANING_CRESCENT_MOON_SYMBOL = b'\xF0\x9F\x8C\x98' + NEW_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9A' + LAST_QUARTER_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9C' + FULL_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9D' + SUN_WITH_FACE = b'\xF0\x9F\x8C\x9E' + EVERGREEN_TREE = b'\xF0\x9F\x8C\xB2' + DECIDUOUS_TREE = b'\xF0\x9F\x8C\xB3' + LEMON = b'\xF0\x9F\x8D\x8B' + PEAR = b'\xF0\x9F\x8D\x90' + BABY_BOTTLE = b'\xF0\x9F\x8D\xBC' + HORSE_RACING = b'\xF0\x9F\x8F\x87' + RUGBY_FOOTBALL = b'\xF0\x9F\x8F\x89' + EUROPEAN_POST_OFFICE = b'\xF0\x9F\x8F\xA4' + RAT = b'\xF0\x9F\x90\x80' + MOUSE = b'\xF0\x9F\x90\x81' + OX = b'\xF0\x9F\x90\x82' + WATER_BUFFALO = b'\xF0\x9F\x90\x83' + COW = b'\xF0\x9F\x90\x84' + TIGER = b'\xF0\x9F\x90\x85' + LEOPARD = b'\xF0\x9F\x90\x86' + RABBIT = b'\xF0\x9F\x90\x87' + CAT = b'\xF0\x9F\x90\x88' + DRAGON = b'\xF0\x9F\x90\x89' + CROCODILE = b'\xF0\x9F\x90\x8A' + WHALE = b'\xF0\x9F\x90\x8B' + RAM = b'\xF0\x9F\x90\x8F' + GOAT = b'\xF0\x9F\x90\x90' + ROOSTER = b'\xF0\x9F\x90\x93' + DOG = b'\xF0\x9F\x90\x95' + PIG = b'\xF0\x9F\x90\x96' + DROMEDARY_CAMEL = b'\xF0\x9F\x90\xAA' + BUSTS_IN_SILHOUETTE = b'\xF0\x9F\x91\xA5' + TWO_MEN_HOLDING_HANDS = b'\xF0\x9F\x91\xAC' + TWO_WOMEN_HOLDING_HANDS = b'\xF0\x9F\x91\xAD' + THOUGHT_BALLOON = b'\xF0\x9F\x92\xAD' + BANKNOTE_WITH_EURO_SIGN = b'\xF0\x9F\x92\xB6' + BANKNOTE_WITH_POUND_SIGN = b'\xF0\x9F\x92\xB7' + OPEN_MAILBOX_WITH_RAISED_FLAG = b'\xF0\x9F\x93\xAC' + OPEN_MAILBOX_WITH_LOWERED_FLAG = b'\xF0\x9F\x93\xAD' + POSTAL_HORN = b'\xF0\x9F\x93\xAF' + NO_MOBILE_PHONES = b'\xF0\x9F\x93\xB5' + TWISTED_RIGHTWARDS_ARROWS = b'\xF0\x9F\x94\x80' + CLOCKWISE_RIGHTWARDS_AND_LEFTWARDS_OPEN_CIRCLE_ARROWS = b'\xF0\x9F\x94\x81' + CLOCKWISE_RIGHTWARDS_AND_LEFTWARDS_OPEN_CIRCLE_ARROWS_WITH_CIRCLED_ONE_OVERLAY = b'\xF0\x9F\x94\x82' + ANTICLOCKWISE_DOWNWARDS_AND_UPWARDS_OPEN_CIRCLE_ARROWS = b'\xF0\x9F\x94\x84' + LOW_BRIGHTNESS_SYMBOL = b'\xF0\x9F\x94\x85' + HIGH_BRIGHTNESS_SYMBOL = b'\xF0\x9F\x94\x86' + SPEAKER_WITH_CANCELLATION_STROKE = b'\xF0\x9F\x94\x87' + SPEAKER_WITH_ONE_SOUND_WAVE = b'\xF0\x9F\x94\x89' + BELL_WITH_CANCELLATION_STROKE = b'\xF0\x9F\x94\x95' + MICROSCOPE = b'\xF0\x9F\x94\xAC' + TELESCOPE = b'\xF0\x9F\x94\xAD' + CLOCK_FACE_ONE_THIRTY = b'\xF0\x9F\x95\x9C' + CLOCK_FACE_TWO_THIRTY = b'\xF0\x9F\x95\x9D' + CLOCK_FACE_THREE_THIRTY = b'\xF0\x9F\x95\x9E' + CLOCK_FACE_FOUR_THIRTY = b'\xF0\x9F\x95\x9F' + CLOCK_FACE_FIVE_THIRTY = b'\xF0\x9F\x95\xA0' + CLOCK_FACE_SIX_THIRTY = b'\xF0\x9F\x95\xA1' + CLOCK_FACE_SEVEN_THIRTY = b'\xF0\x9F\x95\xA2' + CLOCK_FACE_EIGHT_THIRTY = b'\xF0\x9F\x95\xA3' + CLOCK_FACE_NINE_THIRTY = b'\xF0\x9F\x95\xA4' + CLOCK_FACE_TEN_THIRTY = b'\xF0\x9F\x95\xA5' + CLOCK_FACE_ELEVEN_THIRTY = b'\xF0\x9F\x95\xA6' + CLOCK_FACE_TWELVE_THIRTY = b'\xF0\x9F\x95\xA7' diff --git a/lib/telegram/error.py b/lib/telegram/error.py new file mode 100644 index 00000000..f8b7e21f --- /dev/null +++ b/lib/telegram/error.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Error""" + +import re + + +class TelegramError(Exception): + """This object represents a Telegram Error.""" + + def __init__(self, message): + """ + Returns: + str: + """ + super(TelegramError, self).__init__() + + api_error = re.match(r'^Error: (?P.*)', message) + if api_error: + self.message = api_error.group('message').capitalize() + else: + self.message = message + + def __str__(self): + return '%s' % (self.message) diff --git a/lib/telegram/file.py b/lib/telegram/file.py new file mode 100644 index 00000000..9474d2ff --- /dev/null +++ b/lib/telegram/file.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram File""" + +from os.path import basename + +from telegram import TelegramObject +from telegram.utils.request import download as _download + + +class File(TelegramObject): + + """This object represents a Telegram File. + + Attributes: + file_id (str): + file_size (str): + file_path (str): + + Args: + file_id (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + file_size (Optional[int]): + file_path (Optional[str]): + """ + + def __init__(self, + file_id, + **kwargs): + # Required + self.file_id = str(file_id) + # Optionals + self.file_size = int(kwargs.get('file_size', 0)) + self.file_path = str(kwargs.get('file_path', '')) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.File: + """ + if not data: + return None + + return File(**data) + + def download(self, + custom_path=None): + """ + Args: + custom_path (str): + """ + url = self.file_path + + if custom_path: + filename = basename(custom_path) + else: + filename = basename(url) + + _download(url, filename) diff --git a/lib/telegram/forcereply.py b/lib/telegram/forcereply.py new file mode 100644 index 00000000..28057bee --- /dev/null +++ b/lib/telegram/forcereply.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram ForceReply""" + +from telegram import ReplyMarkup + + +class ForceReply(ReplyMarkup): + """This object represents a Telegram ForceReply. + + Attributes: + force_reply (bool): + selective (bool): + + Args: + force_reply (bool): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + selective (Optional[bool]): + """ + + def __init__(self, + force_reply=True, + **kwargs): + # Required + self.force_reply = bool(force_reply) + # Optionals + self.selective = bool(kwargs.get('selective', False)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.ForceReply: + """ + if not data: + return None + + return ForceReply(**data) diff --git a/lib/telegram/groupchat.py b/lib/telegram/groupchat.py new file mode 100644 index 00000000..f4d59723 --- /dev/null +++ b/lib/telegram/groupchat.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# pylint: disable=C0103,W0622 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram GroupChat""" + +from telegram import TelegramObject + + +class GroupChat(TelegramObject): + """This object represents a Telegram GroupChat. + + Attributes: + id (int): + title (str): + type (str): + + Args: + id (int): + title (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + type (Optional[str]): + """ + + def __init__(self, + id, + title, + **kwargs): + # Required + self.id = int(id) + self.title = title + # Optionals + self.type = kwargs.get('type', '') + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.GroupChat: + """ + if not data: + return None + + return GroupChat(**data) diff --git a/lib/telegram/inputfile.py b/lib/telegram/inputfile.py new file mode 100644 index 00000000..0946f972 --- /dev/null +++ b/lib/telegram/inputfile.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# pylint: disable=W0622,E0611 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram InputFile""" + +try: + from email.generator import _make_boundary as choose_boundary + from urllib.request import urlopen + from io import BufferedReader as file +except ImportError: + from mimetools import choose_boundary + from urllib2 import urlopen +import mimetypes +import os +import sys +import imghdr + +from telegram import TelegramError + +DEFAULT_MIME_TYPE = 'application/octet-stream' +USER_AGENT = 'Python Telegram Bot' \ + ' (https://github.com/leandrotoledo/python-telegram-bot)' + + +class InputFile(object): + """This object represents a Telegram InputFile.""" + + def __init__(self, + data): + self.data = data + self.boundary = choose_boundary() + + if 'audio' in data: + self.input_name = 'audio' + self.input_file = data.pop('audio') + if 'document' in data: + self.input_name = 'document' + self.input_file = data.pop('document') + if 'photo' in data: + self.input_name = 'photo' + self.input_file = data.pop('photo') + if 'sticker' in data: + self.input_name = 'sticker' + self.input_file = data.pop('sticker') + if 'video' in data: + self.input_name = 'video' + self.input_file = data.pop('video') + if 'voice' in data: + self.input_name = 'voice' + self.input_file = data.pop('voice') + if 'certificate' in data: + self.input_name = 'certificate' + self.input_file = data.pop('certificate') + + if isinstance(self.input_file, file): + self.input_file_content = self.input_file.read() + if 'filename' in data: + self.filename = self.data.pop('filename') + else: + self.filename = os.path.basename(self.input_file.name) + self.mimetype = mimetypes.guess_type(self.filename)[0] or \ + DEFAULT_MIME_TYPE + + if 'http' in self.input_file: + self.input_file_content = urlopen(self.input_file).read() + self.mimetype = InputFile.is_image(self.input_file_content) + self.filename = self.mimetype.replace('/', '.') + + @property + def headers(self): + """ + Returns: + str: + """ + return {'User-agent': USER_AGENT, + 'Content-type': self.content_type} + + @property + def content_type(self): + """ + Returns: + str: + """ + return 'multipart/form-data; boundary=%s' % self.boundary + + def to_form(self): + """ + Returns: + str: + """ + form = [] + form_boundary = '--' + self.boundary + + # Add data fields + for name, value in self.data.items(): + form.extend([ + form_boundary, + 'Content-Disposition: form-data; name="%s"' % name, + '', + str(value) + ]) + + # Add input_file to upload + form.extend([ + form_boundary, + 'Content-Disposition: form-data; name="%s"; filename="%s"' % ( + self.input_name, self.filename + ), + 'Content-Type: %s' % self.mimetype, + '', + self.input_file_content + ]) + + form.append('--' + self.boundary + '--') + form.append('') + + return InputFile._parse(form) + + @staticmethod + def _parse(form): + """ + Returns: + str: + """ + if sys.version_info > (3,): + # on Python 3 form needs to be byte encoded + encoded_form = [] + for item in form: + try: + encoded_form.append(item.encode()) + except AttributeError: + encoded_form.append(item) + + return b'\r\n'.join(encoded_form) + return '\r\n'.join(form) + + @staticmethod + def is_image(stream): + """Check if the content file is an image by analyzing its headers. + + Args: + stream (str): A str representing the content of a file. + + Returns: + str: The str mimetype of an image. + """ + image = imghdr.what(None, stream) + if image: + return 'image/%s' % image + + raise TelegramError('Could not parse file content') + + @staticmethod + def is_inputfile(data): + """Check if the request is a file request. + + Args: + data (str): A dict of (str, unicode) key/value pairs + + Returns: + bool + """ + if data: + file_types = ['audio', 'document', 'photo', 'sticker', 'video', + 'voice', 'certificate'] + file_type = [i for i in list(data.keys()) if i in file_types] + + if file_type: + file_content = data[file_type[0]] + + if file_type[0] == 'photo' or file_type[0] == 'document': + return isinstance(file_content, file) or \ + str(file_content).startswith('http') + + return isinstance(file_content, file) + + return False diff --git a/lib/telegram/location.py b/lib/telegram/location.py new file mode 100644 index 00000000..66c28fc5 --- /dev/null +++ b/lib/telegram/location.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Location""" + +from telegram import TelegramObject + + +class Location(TelegramObject): + """This object represents a Telegram Sticker. + + Attributes: + longitude (float): + latitude (float): + + Args: + longitude (float): + latitude (float): + """ + + def __init__(self, + longitude, + latitude): + # Required + self.longitude = float(longitude) + self.latitude = float(latitude) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Location: + """ + if not data: + return None + + return Location(**data) diff --git a/lib/telegram/message.py b/lib/telegram/message.py new file mode 100644 index 00000000..e77f3ea4 --- /dev/null +++ b/lib/telegram/message.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python +# pylint: disable=R0902,R0912,R0913 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Message""" + +from datetime import datetime +from time import mktime + +from telegram import (Audio, Contact, Document, GroupChat, Location, PhotoSize, + Sticker, TelegramObject, User, Video, Voice) + + +class Message(TelegramObject): + """This object represents a Telegram Message. + + Note: + * In Python `from` is a reserved word, use `from_user` instead. + + Attributes: + message_id (int): + from_user (:class:`telegram.User`): + date (:class:`datetime.datetime`): + forward_from (:class:`telegram.User`): + forward_date (:class:`datetime.datetime`): + reply_to_message (:class:`telegram.Message`): + text (str): + audio (:class:`telegram.Audio`): + document (:class:`telegram.Document`): + photo (List[:class:`telegram.PhotoSize`]): + sticker (:class:`telegram.Sticker`): + video (:class:`telegram.Video`): + voice (:class:`telegram.Voice`): + caption (str): + contact (:class:`telegram.Contact`): + location (:class:`telegram.Location`): + new_chat_participant (:class:`telegram.User`): + left_chat_participant (:class:`telegram.User`): + new_chat_title (str): + new_chat_photo (List[:class:`telegram.PhotoSize`]): + delete_chat_photo (bool): + group_chat_created (bool): + + Args: + message_id (int): + from_user (:class:`telegram.User`): + date (:class:`datetime.datetime`): + chat (:class:`telegram.User` or :class:`telegram.GroupChat`): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + forward_from (Optional[:class:`telegram.User`]): + forward_date (Optional[:class:`datetime.datetime`]): + reply_to_message (Optional[:class:`telegram.Message`]): + text (Optional[str]): + audio (Optional[:class:`telegram.Audio`]): + document (Optional[:class:`telegram.Document`]): + photo (Optional[List[:class:`telegram.PhotoSize`]]): + sticker (Optional[:class:`telegram.Sticker`]): + video (Optional[:class:`telegram.Video`]): + voice (Optional[:class:`telegram.Voice`]): + caption (Optional[str]): + contact (Optional[:class:`telegram.Contact`]): + location (Optional[:class:`telegram.Location`]): + new_chat_participant (Optional[:class:`telegram.User`]): + left_chat_participant (Optional[:class:`telegram.User`]): + new_chat_title (Optional[str]): + new_chat_photo (Optional[List[:class:`telegram.PhotoSize`]): + delete_chat_photo (Optional[bool]): + group_chat_created (Optional[bool]): + """ + + def __init__(self, + message_id, + from_user, + date, + chat, + **kwargs): + # Required + self.message_id = int(message_id) + self.from_user = from_user + self.date = date + self.chat = chat + # Optionals + self.forward_from = kwargs.get('forward_from') + self.forward_date = kwargs.get('forward_date') + self.reply_to_message = kwargs.get('reply_to_message') + self.text = kwargs.get('text', '') + self.audio = kwargs.get('audio') + self.document = kwargs.get('document') + self.photo = kwargs.get('photo') + self.sticker = kwargs.get('sticker') + self.video = kwargs.get('video') + self.voice = kwargs.get('voice') + self.caption = kwargs.get('caption', '') + self.contact = kwargs.get('contact') + self.location = kwargs.get('location') + self.new_chat_participant = kwargs.get('new_chat_participant') + self.left_chat_participant = kwargs.get('left_chat_participant') + self.new_chat_title = kwargs.get('new_chat_title', '') + self.new_chat_photo = kwargs.get('new_chat_photo') + self.delete_chat_photo = bool(kwargs.get('delete_chat_photo', False)) + self.group_chat_created = bool(kwargs.get('group_chat_created', False)) + + @property + def chat_id(self): + """int: Short for :attr:`Message.chat.id`""" + return self.chat.id + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Message: + """ + if not data: + return None + + data['from_user'] = User.de_json(data['from']) + data['date'] = datetime.fromtimestamp(data['date']) + if 'first_name' in data.get('chat', ''): + data['chat'] = User.de_json(data.get('chat')) + elif 'title' in data.get('chat', ''): + data['chat'] = GroupChat.de_json(data.get('chat')) + data['forward_from'] = \ + User.de_json(data.get('forward_from')) + data['forward_date'] = \ + Message._fromtimestamp(data.get('forward_date')) + data['reply_to_message'] = \ + Message.de_json(data.get('reply_to_message')) + data['audio'] = \ + Audio.de_json(data.get('audio')) + data['document'] = \ + Document.de_json(data.get('document')) + data['photo'] = \ + PhotoSize.de_list(data.get('photo')) + data['sticker'] = \ + Sticker.de_json(data.get('sticker')) + data['video'] = \ + Video.de_json(data.get('video')) + data['voice'] = \ + Voice.de_json(data.get('voice')) + data['contact'] = \ + Contact.de_json(data.get('contact')) + data['location'] = \ + Location.de_json(data.get('location')) + data['new_chat_participant'] = \ + User.de_json(data.get('new_chat_participant')) + data['left_chat_participant'] = \ + User.de_json(data.get('left_chat_participant')) + data['new_chat_photo'] = \ + PhotoSize.de_list(data.get('new_chat_photo')) + + return Message(**data) + + def __getitem__(self, item): + if item in self.__dict__.keys(): + return self.__dict__[item] + elif item == 'chat_id': + return self.chat.id + + def to_dict(self): + """ + Returns: + dict: + """ + data = super(Message, self).to_dict() + + # Required + data['from'] = data.pop('from_user') + data['date'] = self._totimestamp(self.date) + # Optionals + if self.forward_date: + data['forward_date'] = self._totimestamp(self.forward_date) + if self.photo: + data['photo'] = [p.to_dict() for p in self.photo] + if self.new_chat_photo: + data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo] + + return data + + @staticmethod + def _fromtimestamp(unixtime): + """ + Args: + unixtime (int): + + Returns: + datetime.datetime: + """ + if not unixtime: + return None + + return datetime.fromtimestamp(unixtime) + + @staticmethod + def _totimestamp(dt_obj): + """ + Args: + dt_obj (:class:`datetime.datetime`): + + Returns: + int: + """ + if not dt_obj: + return None + + try: + # Python 3.3+ + return int(dt_obj.timestamp()) + except AttributeError: + # Python 3 (< 3.3) and Python 2 + return int(mktime(dt_obj.timetuple())) diff --git a/lib/telegram/nullhandler.py b/lib/telegram/nullhandler.py new file mode 100644 index 00000000..50bd370e --- /dev/null +++ b/lib/telegram/nullhandler.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a logging NullHandler""" + +import logging + + +class NullHandler(logging.Handler): + """This object represents a logging NullHandler.""" + + def emit(self, record): + """ + Args: + record (str): + """ + pass diff --git a/lib/telegram/parsemode.py b/lib/telegram/parsemode.py new file mode 100644 index 00000000..897693ac --- /dev/null +++ b/lib/telegram/parsemode.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# pylint: disable=R0903 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Message Parse Modes""" + + +class ParseMode(object): + """This object represents a Telegram Message Parse Modes.""" + + MARKDOWN = 'Markdown' diff --git a/lib/telegram/photosize.py b/lib/telegram/photosize.py new file mode 100644 index 00000000..adc47649 --- /dev/null +++ b/lib/telegram/photosize.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram PhotoSize""" + +from telegram import TelegramObject + + +class PhotoSize(TelegramObject): + """This object represents a Telegram PhotoSize. + + Attributes: + file_id (str): + width (int): + height (int): + file_size (int): + + Args: + file_id (str): + width (int): + height (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + file_size (Optional[int]): + """ + + def __init__(self, + file_id, + width, + height, + **kwargs): + # Required + self.file_id = file_id + self.width = int(width) + self.height = int(height) + # Optionals + self.file_size = int(kwargs.get('file_size', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.PhotoSize: + """ + if not data: + return None + + return PhotoSize(**data) + + @staticmethod + def de_list(data): + """ + Args: + data (list): + + Returns: + List: + """ + if not data: + return [] + + photos = list() + for photo in data: + photos.append(PhotoSize.de_json(photo)) + + return photos diff --git a/lib/telegram/replykeyboardhide.py b/lib/telegram/replykeyboardhide.py new file mode 100644 index 00000000..f2cb80bb --- /dev/null +++ b/lib/telegram/replykeyboardhide.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram +ReplyKeyboardHide""" + +from telegram import ReplyMarkup + + +class ReplyKeyboardHide(ReplyMarkup): + """This object represents a Telegram ReplyKeyboardHide. + + Attributes: + hide_keyboard (bool): + selective (bool): + + Args: + hide_keyboard (bool): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + selective (Optional[bool]): + """ + + def __init__(self, + hide_keyboard=True, + **kwargs): + # Required + self.hide_keyboard = bool(hide_keyboard) + # Optionals + self.selective = bool(kwargs.get('selective', False)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.ReplyKeyboardHide: + """ + if not data: + return None + + return ReplyKeyboardHide(**data) diff --git a/lib/telegram/replykeyboardmarkup.py b/lib/telegram/replykeyboardmarkup.py new file mode 100644 index 00000000..39912ea3 --- /dev/null +++ b/lib/telegram/replykeyboardmarkup.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram +ReplyKeyboardMarkup""" + +from telegram import ReplyMarkup + + +class ReplyKeyboardMarkup(ReplyMarkup): + """This object represents a Telegram ReplyKeyboardMarkup. + + Attributes: + keyboard (List[List[str]]): + resize_keyboard (bool): + one_time_keyboard (bool): + selective (bool): + + Args: + keyboard (List[List[str]]): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + resize_keyboard (Optional[bool]): + one_time_keyboard (Optional[bool]): + selective (Optional[bool]): + """ + + def __init__(self, + keyboard, + **kwargs): + # Required + self.keyboard = keyboard + # Optionals + self.resize_keyboard = bool(kwargs.get('resize_keyboard', False)) + self.one_time_keyboard = bool(kwargs.get('one_time_keyboard', False)) + self.selective = bool(kwargs.get('selective', False)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.ReplyKeyboardMarkup: + """ + if not data: + return None + + return ReplyKeyboardMarkup(**data) diff --git a/lib/telegram/replymarkup.py b/lib/telegram/replymarkup.py new file mode 100644 index 00000000..1d086c89 --- /dev/null +++ b/lib/telegram/replymarkup.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""Base class for Telegram ReplyMarkup Objects""" + +from telegram import TelegramObject + + +class ReplyMarkup(TelegramObject): + """Base class for Telegram ReplyMarkup Objects""" + + @staticmethod + def de_json(data): + pass diff --git a/lib/telegram/sticker.py b/lib/telegram/sticker.py new file mode 100644 index 00000000..06089e35 --- /dev/null +++ b/lib/telegram/sticker.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Sticker""" + +from telegram import PhotoSize, TelegramObject + + +class Sticker(TelegramObject): + """This object represents a Telegram Sticker. + + Attributes: + file_id (str): + width (int): + height (int): + thumb (:class:`telegram.PhotoSize`): + file_size (int): + + Args: + file_id (str): + width (int): + height (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + thumb (Optional[:class:`telegram.PhotoSize`]): + file_size (Optional[int]): + """ + + def __init__(self, + file_id, + width, + height, + **kwargs): + # Required + self.file_id = str(file_id) + self.width = int(width) + self.height = int(height) + # Optionals + self.thumb = kwargs.get('thumb') + self.file_size = int(kwargs.get('file_size', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Sticker: + """ + if not data: + return None + + data['thumb'] = PhotoSize.de_json(data.get('thumb')) + + return Sticker(**data) diff --git a/lib/telegram/update.py b/lib/telegram/update.py new file mode 100644 index 00000000..3bccc7d2 --- /dev/null +++ b/lib/telegram/update.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Update""" + +from telegram import Message, TelegramObject + + +class Update(TelegramObject): + """This object represents a Telegram Update. + + Attributes: + update_id (int): + message (:class:`telegram.Message`): + + Args: + update_id (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + message (Optional[:class:`telegram.Message`]): + """ + def __init__(self, + update_id, + **kwargs): + # Required + self.update_id = int(update_id) + # Optionals + self.message = kwargs.get('message') + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Update: + """ + if not data: + return None + + data['message'] = Message.de_json(data['message']) + + return Update(**data) diff --git a/lib/telegram/user.py b/lib/telegram/user.py new file mode 100644 index 00000000..4fa88295 --- /dev/null +++ b/lib/telegram/user.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# pylint: disable=C0103,W0622 +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram User""" + +from telegram import TelegramObject + + +class User(TelegramObject): + """This object represents a Telegram User. + + Attributes: + id (int): + first_name (str): + last_name (str): + username (str): + type (str): + + Args: + id (int): + first_name (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + type (Optional[str]): + last_name (Optional[str]): + username (Optional[str]): + """ + + def __init__(self, + id, + first_name, + **kwargs): + # Required + self.id = int(id) + self.first_name = first_name + # Optionals + self.type = kwargs.get('type', '') + self.last_name = kwargs.get('last_name', '') + self.username = kwargs.get('username', '') + + @property + def name(self): + """str: """ + if self.username: + return '@%s' % self.username + if self.last_name: + return '%s %s' % (self.first_name, self.last_name) + return self.first_name + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.User: + """ + if not data: + return None + + return User(**data) diff --git a/lib/telegram/userprofilephotos.py b/lib/telegram/userprofilephotos.py new file mode 100644 index 00000000..bce623b3 --- /dev/null +++ b/lib/telegram/userprofilephotos.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram +UserProfilePhotos""" + +from telegram import PhotoSize, TelegramObject + + +class UserProfilePhotos(TelegramObject): + """This object represents a Telegram UserProfilePhotos. + + Attributes: + total_count (int): + photos (List[List[:class:`telegram.PhotoSize`]]): + + Args: + total_count (int): + photos (List[List[:class:`telegram.PhotoSize`]]): + """ + + def __init__(self, + total_count, + photos): + # Required + self.total_count = int(total_count) + self.photos = photos + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.UserProfilePhotos: + """ + if not data: + return None + + data['photos'] = [PhotoSize.de_list(photo) for photo in data['photos']] + + return UserProfilePhotos(**data) + + def to_dict(self): + """ + Returns: + dict: + """ + data = super(UserProfilePhotos, self).to_dict() + + data['photos'] = [] + for photo in self.photos: + data['photos'].append([x.to_dict() for x in photo]) + + return data diff --git a/lib/telegram/utils/__init__.py b/lib/telegram/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/telegram/utils/request.py b/lib/telegram/utils/request.py new file mode 100644 index 00000000..5347f9c6 --- /dev/null +++ b/lib/telegram/utils/request.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# pylint: disable=no-name-in-module,unused-import +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains methods to make POST and GET requests""" + +import json + +try: + from urllib.parse import urlencode + from urllib.request import urlopen, urlretrieve, Request + from urllib.error import HTTPError, URLError +except ImportError: + from urllib import urlencode, urlretrieve + from urllib2 import urlopen, Request + from urllib2 import HTTPError, URLError + +from telegram import (InputFile, TelegramError) + + +def _parse(json_data): + """Try and parse the JSON returned from Telegram and return an empty + dictionary if there is any error. + + Args: + url: + urllib.urlopen object + + Returns: + A JSON parsed as Python dict with results. + """ + data = json.loads(json_data.decode()) + + if not data.get('ok') and data.get('description'): + return data['description'] + + return data['result'] + + +def get(url): + """Request an URL. + Args: + url: + The web location we want to retrieve. + + Returns: + A JSON object. + """ + result = urlopen(url).read() + + return _parse(result) + + +def post(url, + data): + """Request an URL. + Args: + url: + The web location we want to retrieve. + data: + A dict of (str, unicode) key/value pairs. + + Returns: + A JSON object. + """ + try: + if InputFile.is_inputfile(data): + data = InputFile(data) + request = Request(url, + data=data.to_form(), + headers=data.headers) + else: + data = json.dumps(data) + request = Request(url, + data=data.encode(), + headers={'Content-Type': 'application/json'}) + + result = urlopen(request).read() + except HTTPError as error: + if error.getcode() == 403: + raise TelegramError('Unauthorized') + if error.getcode() == 502: + raise TelegramError('Bad Gateway') + + message = _parse(error.read()) + raise TelegramError(message) + + return _parse(result) + + +def download(url, + filename): + """Download a file by its URL. + Args: + url: + The web location we want to retrieve. + + filename: + The filename wihtin the path to download the file. + """ + + urlretrieve(url, filename) diff --git a/lib/telegram/video.py b/lib/telegram/video.py new file mode 100644 index 00000000..d6930633 --- /dev/null +++ b/lib/telegram/video.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Video""" + +from telegram import PhotoSize, TelegramObject + + +class Video(TelegramObject): + """This object represents a Telegram Video. + + Attributes: + file_id (str): + width (int): + height (int): + duration (int): + thumb (:class:`telegram.PhotoSize`): + mime_type (str): + file_size (int): + + Args: + file_id (str): + width (int): + height (int): + duration (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + thumb (Optional[:class:`telegram.PhotoSize`]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + + def __init__(self, + file_id, + width, + height, + duration, + **kwargs): + # Required + self.file_id = str(file_id) + self.width = int(width) + self.height = int(height) + self.duration = int(duration) + # Optionals + self.thumb = kwargs.get('thumb') + self.mime_type = str(kwargs.get('mime_type', '')) + self.file_size = int(kwargs.get('file_size', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Video: + """ + if not data: + return None + + data['thumb'] = PhotoSize.de_json(data.get('thumb')) + + return Video(**data) diff --git a/lib/telegram/voice.py b/lib/telegram/voice.py new file mode 100644 index 00000000..53dcdc44 --- /dev/null +++ b/lib/telegram/voice.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +"""This module contains a object that represents a Telegram Voice""" + +from telegram import TelegramObject + + +class Voice(TelegramObject): + """This object represents a Telegram Voice. + + Attributes: + file_id (str): + duration (int): + mime_type (str): + file_size (int): + + Args: + file_id (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + duration (Optional[int]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + + def __init__(self, + file_id, + **kwargs): + # Required + self.file_id = str(file_id) + # Optionals + self.duration = int(kwargs.get('duration', 0)) + self.mime_type = str(kwargs.get('mime_type', '')) + self.file_size = int(kwargs.get('file_size', 0)) + + @staticmethod + def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.Voice: + """ + if not data: + return None + + return Voice(**data) From f75fca12c8f3866246c96f8173f611458b7c68dd Mon Sep 17 00:00:00 2001 From: devin Date: Wed, 28 Oct 2015 07:30:41 -0400 Subject: [PATCH 2/7] telegram updates to config.py --- plexpy/config.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plexpy/config.py b/plexpy/config.py index 99f68c2c..88346b9a 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -218,6 +218,16 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ON_CREATED': (int, 'Pushover', 0), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1), + 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), + 'TELEGRAM_ENABLED': (int, 'Telegram', 0), + 'TELEGRAM_CHAT_ID': (int, 'Telegram', 0), + 'TELEGRAM_ON_PLAY': (int, 'Telegram', 0), + 'TELEGRAM_ON_STOP': (int, 'Telegram', 0), + 'TELEGRAM_ON_PAUSE': (int, 'Telegram', 0), + 'TELEGRAM_ON_RESUME': (int, 'Telegram', 0), + 'TELEGRAM_ON_BUFFER': (int, 'Telegram', 0), + 'TELEGRAM_ON_WATCHED': (int, 'Telegram', 0), + 'TELEGRAM_ON_CREATED': (int, 'Telegram', 0), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), 'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0), From c723d33d38763ebb0be9be03dae6741af81022f3 Mon Sep 17 00:00:00 2001 From: devin Date: Wed, 28 Oct 2015 07:31:07 -0400 Subject: [PATCH 3/7] telegram updates to notifiers.py --- plexpy/notifiers.py | 79 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 475ce2bc..3cd40b35 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -35,6 +35,8 @@ import json import oauth2 as oauth import pythontwitter as twitter +import telegram + from email.mime.text import MIMEText import smtplib import email.utils @@ -51,7 +53,8 @@ AGENT_IDS = {"Growl": 0, "Boxcar2": 9, "Email": 10, "Twitter": 11, - "IFTTT": 12} + "IFTTT": 12, + "Telegram": 13} def available_notification_agents(): agents = [{'name': 'Growl', @@ -209,6 +212,19 @@ def available_notification_agents(): 'on_buffer': plexpy.CONFIG.IFTTT_ON_BUFFER, 'on_watched': plexpy.CONFIG.IFTTT_ON_WATCHED, 'on_created': plexpy.CONFIG.IFTTT_ON_CREATED + }, + {'name': 'Telegram', + 'id': AGENT_IDS['Telegram'], + 'config_prefix': 'telegram', + 'has_config': True, + 'state': checked(plexpy.CONFIG.TELEGRAM_ENABLED), + 'on_play': plexpy.CONFIG.TELEGRAM_ON_PLAY, + 'on_stop': plexpy.CONFIG.TELEGRAM_ON_STOP, + 'on_pause': plexpy.CONFIG.TELEGRAM_ON_PAUSE, + 'on_resume': plexpy.CONFIG.TELEGRAM_ON_RESUME, + 'on_buffer': plexpy.CONFIG.TELEGRAM_ON_BUFFER, + 'on_watched': plexpy.CONFIG.TELEGRAM_ON_WATCHED, + 'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED } ] @@ -274,6 +290,9 @@ def get_notification_agent_config(config_id): elif config_id == 12: iftttClient = IFTTT() return iftttClient.return_config_options() + elif config_id == 13: + telegramClient = TELEGRAM() + return telegramClient.return_config_options() else: return [] else: @@ -322,6 +341,9 @@ def send_notification(config_id, subject, body): elif config_id == 12: iftttClient = IFTTT() iftttClient.notify(subject=subject, message=body) + elif config_id == 13: + telegramClient = TELEGRAM() + telegramClient.notify(message=body, event=subject) else: logger.debug(u"PlexPy Notifier :: Unknown agent id received.") else: @@ -1417,3 +1439,58 @@ class IFTTT(object): ] return config_option + +class TELEGRAM(object): + + def __init__(self): + self.enabled = plexpy.CONFIG.TELEGRAM_ENABLED + self.bot_token = plexpy.CONFIG.TELEGRAM_BOT_TOKEN + self.chat_id = plexpy.CONFIG.TELEGRAM_CHAT_ID + + self.on_play = plexpy.CONFIG.TELEGRAM_ON_PLAY + self.on_stop = plexpy.CONFIG.TELEGRAM_ON_STOP + self.on_watched = plexpy.CONFIG.TELEGRAM_ON_WATCHED + + + def conf(self, options): + return cherrypy.config['config'].get('Telegram', options) + + def notify(self, message, event): + if not message or not event: + return + + bot = telegram.Bot(self.bot_token) + + bot.sendMessage(chat_id=self.chat_id, text=message.encode("utf-8")) + + logger.info(u"Telegram notifications sent.") + + return True + + def updateLibrary(self): + #For uniformity reasons not removed + return + + def test(self, bot_token, chat_id): + self.enabled = True + self.bot_token = bot_token + self.chat_id = chat_id + + self.notify('Main Screen Activate', 'Test Message') + + def return_config_options(self): + config_option = [{'label': 'Telegram Bot ID', + 'value': self.bot_token, + 'name': 'telegram_bot_token', + 'description': 'Your Bot ID here.', + 'input_type': 'text' + }, + {'label': 'Telegram Chat ID', + 'value': self.chat_id, + 'name': 'telegram_chat_id', + 'description': 'Your Telegram Chat or Group ID.', + 'input_type': 'text' + } + ] + + return config_option From 5beb4876fb721483146870a060ddc1b198fd573b Mon Sep 17 00:00:00 2001 From: devin Date: Wed, 28 Oct 2015 20:28:41 -0400 Subject: [PATCH 4/7] removed telegram lib, updated wording, and used a simple request for sending the notif --- lib/telegram/__init__.py | 56 -- lib/telegram/audio.py | 72 --- lib/telegram/base.py | 68 --- lib/telegram/bot.py | 741 ----------------------- lib/telegram/chataction.py | 33 -- lib/telegram/contact.py | 66 --- lib/telegram/document.py | 70 --- lib/telegram/emoji.py | 878 ---------------------------- lib/telegram/error.py | 41 -- lib/telegram/file.py | 81 --- lib/telegram/forcereply.py | 59 -- lib/telegram/groupchat.py | 64 -- lib/telegram/inputfile.py | 193 ------ lib/telegram/location.py | 55 -- lib/telegram/message.py | 231 -------- lib/telegram/nullhandler.py | 32 - lib/telegram/parsemode.py | 26 - lib/telegram/photosize.py | 85 --- lib/telegram/replykeyboardhide.py | 60 -- lib/telegram/replykeyboardmarkup.py | 66 --- lib/telegram/replymarkup.py | 29 - lib/telegram/sticker.py | 72 --- lib/telegram/update.py | 60 -- lib/telegram/user.py | 79 --- lib/telegram/userprofilephotos.py | 71 --- lib/telegram/utils/__init__.py | 0 lib/telegram/utils/request.py | 117 ---- lib/telegram/video.py | 79 --- lib/telegram/voice.py | 65 -- plexpy/notifiers.py | 44 +- 30 files changed, 26 insertions(+), 3567 deletions(-) delete mode 100644 lib/telegram/__init__.py delete mode 100644 lib/telegram/audio.py delete mode 100644 lib/telegram/base.py delete mode 100644 lib/telegram/bot.py delete mode 100644 lib/telegram/chataction.py delete mode 100644 lib/telegram/contact.py delete mode 100644 lib/telegram/document.py delete mode 100644 lib/telegram/emoji.py delete mode 100644 lib/telegram/error.py delete mode 100644 lib/telegram/file.py delete mode 100644 lib/telegram/forcereply.py delete mode 100644 lib/telegram/groupchat.py delete mode 100644 lib/telegram/inputfile.py delete mode 100644 lib/telegram/location.py delete mode 100644 lib/telegram/message.py delete mode 100644 lib/telegram/nullhandler.py delete mode 100644 lib/telegram/parsemode.py delete mode 100644 lib/telegram/photosize.py delete mode 100644 lib/telegram/replykeyboardhide.py delete mode 100644 lib/telegram/replykeyboardmarkup.py delete mode 100644 lib/telegram/replymarkup.py delete mode 100644 lib/telegram/sticker.py delete mode 100644 lib/telegram/update.py delete mode 100644 lib/telegram/user.py delete mode 100644 lib/telegram/userprofilephotos.py delete mode 100644 lib/telegram/utils/__init__.py delete mode 100644 lib/telegram/utils/request.py delete mode 100644 lib/telegram/video.py delete mode 100644 lib/telegram/voice.py diff --git a/lib/telegram/__init__.py b/lib/telegram/__init__.py deleted file mode 100644 index b3dffe97..00000000 --- a/lib/telegram/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""A library that provides a Python interface to the Telegram Bot API""" - -__author__ = 'leandrotoledodesouza@gmail.com' -__version__ = '2.8.7' - -from .base import TelegramObject -from .user import User -from .groupchat import GroupChat -from .photosize import PhotoSize -from .audio import Audio -from .voice import Voice -from .document import Document -from .sticker import Sticker -from .video import Video -from .contact import Contact -from .location import Location -from .chataction import ChatAction -from .userprofilephotos import UserProfilePhotos -from .replymarkup import ReplyMarkup -from .replykeyboardmarkup import ReplyKeyboardMarkup -from .replykeyboardhide import ReplyKeyboardHide -from .forcereply import ForceReply -from .error import TelegramError -from .inputfile import InputFile -from .file import File -from .nullhandler import NullHandler -from .emoji import Emoji -from .parsemode import ParseMode -from .message import Message -from .update import Update -from .bot import Bot - -__all__ = ['Bot', 'Emoji', 'TelegramError', 'InputFile', 'ReplyMarkup', - 'ForceReply', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', - 'UserProfilePhotos', 'ChatAction', 'Location', 'Contact', - 'Video', 'Sticker', 'Document', 'File', 'Audio', 'PhotoSize', - 'GroupChat', 'Update', 'ParseMode', 'Message', 'User', - 'TelegramObject', 'NullHandler', 'Voice'] diff --git a/lib/telegram/audio.py b/lib/telegram/audio.py deleted file mode 100644 index c7a669d3..00000000 --- a/lib/telegram/audio.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Audio""" - -from telegram import TelegramObject - - -class Audio(TelegramObject): - """This object represents a Telegram Audio. - - Attributes: - file_id (str): - duration (int): - performer (str): - title (str): - mime_type (str): - file_size (int): - - Args: - file_id (str): - duration (int): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - performer (Optional[str]): - title (Optional[str]): - mime_type (Optional[str]): - file_size (Optional[int]): - """ - - def __init__(self, - file_id, - duration, - **kwargs): - # Required - self.file_id = str(file_id) - self.duration = int(duration) - # Optionals - self.performer = kwargs.get('performer', '') - self.title = kwargs.get('title', '') - self.mime_type = str(kwargs.get('mime_type', '')) - self.file_size = int(kwargs.get('file_size', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Audio: - """ - if not data: - return None - - return Audio(**data) diff --git a/lib/telegram/base.py b/lib/telegram/base.py deleted file mode 100644 index ac0deac0..00000000 --- a/lib/telegram/base.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""Base class for Telegram Objects""" - -import json -from abc import ABCMeta - - -class TelegramObject(object): - """Base class for most telegram objects""" - - __metaclass__ = ABCMeta - - def __str__(self): - return str(self.to_dict()) - - def __getitem__(self, item): - return self.__dict__[item] - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.TelegramObject: - """ - raise NotImplementedError - - def to_json(self): - """ - Returns: - str: - """ - return json.dumps(self.to_dict()) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - for key, value in self.__dict__.items(): - if value: - if hasattr(value, 'to_dict'): - data[key] = value.to_dict() - else: - data[key] = value - - return data diff --git a/lib/telegram/bot.py b/lib/telegram/bot.py deleted file mode 100644 index 3805335b..00000000 --- a/lib/telegram/bot.py +++ /dev/null @@ -1,741 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=E0611,E0213,E1102,C0103,E1101,W0613,R0913,R0904 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Bot""" - -import functools -import logging - -from telegram import (User, Message, Update, UserProfilePhotos, File, - TelegramError, ReplyMarkup, TelegramObject, NullHandler) -from telegram.utils import request - -H = NullHandler() -logging.getLogger(__name__).addHandler(H) - - -class Bot(TelegramObject): - - """This object represents a Telegram Bot. - - Attributes: - id (int): - first_name (str): - last_name (str): - username (str): - name (str): - - Args: - token (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - base_url (Optional[str]): - """ - - def __init__(self, - token, - base_url=None): - self.token = token - - if base_url is None: - self.base_url = 'https://api.telegram.org/bot%s' % self.token - else: - self.base_url = base_url + self.token - - self.base_file_url = 'https://api.telegram.org/file/bot%s' % self.token - - self.bot = None - - self.logger = logging.getLogger(__name__) - - def info(func): - """ - Returns: - """ - @functools.wraps(func) - def decorator(self, *args, **kwargs): - """ - decorator - """ - if not self.bot: - self.getMe() - - result = func(self, *args, **kwargs) - return result - return decorator - - @property - @info - def id(self): - """int: """ - return self.bot.id - - @property - @info - def first_name(self): - """str: """ - return self.bot.first_name - - @property - @info - def last_name(self): - """str: """ - return self.bot.last_name - - @property - @info - def username(self): - """str: """ - return self.bot.username - - @property - def name(self): - """str: """ - return '@%s' % self.username - - def log(func): - """ - Returns: - A telegram.Message instance representing the message posted. - """ - logger = logging.getLogger(func.__module__) - - @functools.wraps(func) - def decorator(self, *args, **kwargs): - """ - decorator - """ - logger.debug('Entering: %s', func.__name__) - result = func(self, *args, **kwargs) - logger.debug(result) - logger.debug('Exiting: %s', func.__name__) - return result - return decorator - - def message(func): - """ - Returns: - A telegram.Message instance representing the message posted. - """ - @functools.wraps(func) - def decorator(self, *args, **kwargs): - """ - decorator - """ - url, data = func(self, *args, **kwargs) - - if not data.get('chat_id'): - raise TelegramError('Invalid chat_id') - - if kwargs.get('reply_to_message_id'): - reply_to_message_id = kwargs.get('reply_to_message_id') - data['reply_to_message_id'] = reply_to_message_id - - if kwargs.get('reply_markup'): - reply_markup = kwargs.get('reply_markup') - if isinstance(reply_markup, ReplyMarkup): - data['reply_markup'] = reply_markup.to_json() - else: - data['reply_markup'] = reply_markup - - result = request.post(url, data) - - if result is True: - return result - - return Message.de_json(result) - return decorator - - @log - def getMe(self): - """A simple method for testing your bot's auth token. - - Returns: - A telegram.User instance representing that bot if the - credentials are valid, None otherwise. - """ - url = '%s/getMe' % self.base_url - - result = request.get(url) - - self.bot = User.de_json(result) - - return self.bot - - @log - @message - def sendMessage(self, - chat_id, - text, - parse_mode=None, - disable_web_page_preview=None, - **kwargs): - """Use this method to send text messages. - - Args: - chat_id: - Unique identifier for the message recipient - telegram.User or - telegram.GroupChat id. - parse_mode: - Send Markdown, if you want Telegram apps to show bold, italic and - inline URLs in your bot's message. For the moment, only Telegram - for Android supports this. [Optional] - text: - Text of the message to be sent. - disable_web_page_preview: - Disables link previews for links in this message. [Optional] - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a custom - reply keyboard, instructions to hide keyboard or to force a reply - from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendMessage' % self.base_url - - data = {'chat_id': chat_id, - 'text': text} - - if parse_mode: - data['parse_mode'] = parse_mode - if disable_web_page_preview: - data['disable_web_page_preview'] = disable_web_page_preview - - return url, data - - @log - @message - def forwardMessage(self, - chat_id, - from_chat_id, - message_id): - """Use this method to forward messages of any kind. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - from_chat_id: - Unique identifier for the chat where the original message was sent - - User or GroupChat id. - message_id: - Unique message identifier. - - Returns: - A telegram.Message instance representing the message forwarded. - """ - - url = '%s/forwardMessage' % self.base_url - - data = {} - if chat_id: - data['chat_id'] = chat_id - if from_chat_id: - data['from_chat_id'] = from_chat_id - if message_id: - data['message_id'] = message_id - - return url, data - - @log - @message - def sendPhoto(self, - chat_id, - photo, - caption=None, - **kwargs): - """Use this method to send photos. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - photo: - Photo to send. You can either pass a file_id as String to resend a - photo that is already on the Telegram servers, or upload a new - photo using multipart/form-data. - caption: - Photo caption (may also be used when resending photos by file_id). - [Optional] - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a custom - reply keyboard, instructions to hide keyboard or to force a reply - from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendPhoto' % self.base_url - - data = {'chat_id': chat_id, - 'photo': photo} - - if caption: - data['caption'] = caption - - return url, data - - @log - @message - def sendAudio(self, - chat_id, - audio, - duration=None, - performer=None, - title=None, - **kwargs): - """Use this method to send audio files, if you want Telegram clients to - display them in the music player. Your audio must be in an .mp3 format. - On success, the sent Message is returned. Bots can currently send audio - files of up to 50 MB in size, this limit may be changed in the future. - - For backward compatibility, when both fields title and description are - empty and mime-type of the sent file is not "audio/mpeg", file is sent - as playable voice message. In this case, your audio must be in an .ogg - file encoded with OPUS. This will be removed in the future. You need to - use sendVoice method instead. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - audio: - Audio file to send. You can either pass a file_id as String to - resend an audio that is already on the Telegram servers, or upload - a new audio file using multipart/form-data. - duration: - Duration of sent audio in seconds. [Optional] - performer: - Performer of sent audio. [Optional] - title: - Title of sent audio. [Optional] - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a - custom reply keyboard, instructions to hide keyboard or to force a - reply from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendAudio' % self.base_url - - data = {'chat_id': chat_id, - 'audio': audio} - - if duration: - data['duration'] = duration - if performer: - data['performer'] = performer - if title: - data['title'] = title - - return url, data - - @log - @message - def sendDocument(self, - chat_id, - document, - filename=None, - **kwargs): - """Use this method to send general files. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - document: - File to send. You can either pass a file_id as String to resend a - file that is already on the Telegram servers, or upload a new file - using multipart/form-data. - filename: - File name that shows in telegram message (it is usefull when you - send file generated by temp module, for example). [Optional] - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a - custom reply keyboard, instructions to hide keyboard or to force a - reply from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendDocument' % self.base_url - - data = {'chat_id': chat_id, - 'document': document} - - if filename: - data['filename'] = filename - - return url, data - - @log - @message - def sendSticker(self, - chat_id, - sticker, - **kwargs): - """Use this method to send .webp stickers. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - sticker: - Sticker to send. You can either pass a file_id as String to resend - a sticker that is already on the Telegram servers, or upload a new - sticker using multipart/form-data. - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a - custom reply keyboard, instructions to hide keyboard or to force a - reply from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendSticker' % self.base_url - - data = {'chat_id': chat_id, - 'sticker': sticker} - - return url, data - - @log - @message - def sendVideo(self, - chat_id, - video, - duration=None, - caption=None, - **kwargs): - """Use this method to send video files, Telegram clients support mp4 - videos (other formats may be sent as telegram.Document). - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - video: - Video to send. You can either pass a file_id as String to resend a - video that is already on the Telegram servers, or upload a new - video file using multipart/form-data. - duration: - Duration of sent video in seconds. [Optional] - caption: - Video caption (may also be used when resending videos by file_id). - [Optional] - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a - custom reply keyboard, instructions to hide keyboard or to force a - reply from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendVideo' % self.base_url - - data = {'chat_id': chat_id, - 'video': video} - - if duration: - data['duration'] = duration - if caption: - data['caption'] = caption - - return url, data - - @log - @message - def sendVoice(self, - chat_id, - voice, - duration=None, - **kwargs): - """Use this method to send audio files, if you want Telegram clients to - display the file as a playable voice message. For this to work, your - audio must be in an .ogg file encoded with OPUS (other formats may be - sent as Audio or Document). On success, the sent Message is returned. - Bots can currently send audio files of up to 50 MB in size, this limit - may be changed in the future. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - voice: - Audio file to send. You can either pass a file_id as String to - resend an audio that is already on the Telegram servers, or upload - a new audio file using multipart/form-data. - duration: - Duration of sent audio in seconds. [Optional] - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a - custom reply keyboard, instructions to hide keyboard or to force a - reply from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendVoice' % self.base_url - - data = {'chat_id': chat_id, - 'voice': voice} - - if duration: - data['duration'] = duration - - return url, data - - @log - @message - def sendLocation(self, - chat_id, - latitude, - longitude, - **kwargs): - """Use this method to send point on the map. - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - latitude: - Latitude of location. - longitude: - Longitude of location. - reply_to_message_id: - If the message is a reply, ID of the original message. [Optional] - reply_markup: - Additional interface options. A JSON-serialized object for a - custom reply keyboard, instructions to hide keyboard or to force a - reply from the user. [Optional] - - Returns: - A telegram.Message instance representing the message posted. - """ - - url = '%s/sendLocation' % self.base_url - - data = {'chat_id': chat_id, - 'latitude': latitude, - 'longitude': longitude} - - return url, data - - @log - @message - def sendChatAction(self, - chat_id, - action): - """Use this method when you need to tell the user that something is - happening on the bot's side. The status is set for 5 seconds or less - (when a message arrives from your bot, Telegram clients clear its - typing status). - - Args: - chat_id: - Unique identifier for the message recipient - User or GroupChat id. - action: - Type of action to broadcast. Choose one, depending on what the user - is about to receive: - - ChatAction.TYPING for text messages, - - ChatAction.UPLOAD_PHOTO for photos, - - ChatAction.UPLOAD_VIDEO for videos, - - ChatAction.UPLOAD_AUDIO for audio files, - - ChatAction.UPLOAD_DOCUMENT for general files, - - ChatAction.FIND_LOCATION for location data. - """ - - url = '%s/sendChatAction' % self.base_url - - data = {'chat_id': chat_id, - 'action': action} - - return url, data - - @log - def getUserProfilePhotos(self, - user_id, - offset=None, - limit=100): - """Use this method to get a list of profile pictures for a user. - - Args: - user_id: - Unique identifier of the target user. - offset: - Sequential number of the first photo to be returned. By default, - all photos are returned. [Optional] - limit: - Limits the number of photos to be retrieved. Values between 1-100 - are accepted. Defaults to 100. [Optional] - - Returns: - Returns a telegram.UserProfilePhotos object. - """ - - url = '%s/getUserProfilePhotos' % self.base_url - - data = {'user_id': user_id} - - if offset: - data['offset'] = offset - if limit: - data['limit'] = limit - - result = request.post(url, data) - - return UserProfilePhotos.de_json(result) - - @log - def getFile(self, - file_id): - """Use this method to get basic info about a file and prepare it for - downloading. For the moment, bots can download files of up to 20MB in - size. - - Args: - file_id: - File identifier to get info about. - - Returns: - Returns a telegram.File object - """ - - url = '%s/getFile' % self.base_url - - data = {'file_id': file_id} - - result = request.post(url, data) - - if result.get('file_path'): - result['file_path'] = '%s/%s' % (self.base_file_url, - result['file_path']) - - return File.de_json(result) - - @log - def getUpdates(self, - offset=None, - limit=100, - timeout=0): - """Use this method to receive incoming updates using long polling. - - Args: - offset: - Identifier of the first update to be returned. Must be greater by - one than the highest among the identifiers of previously received - updates. By default, updates starting with the earliest unconfirmed - update are returned. An update is considered confirmed as soon as - getUpdates is called with an offset higher than its update_id. - limit: - Limits the number of updates to be retrieved. Values between 1-100 - are accepted. Defaults to 100. - timeout: - Timeout in seconds for long polling. Defaults to 0, i.e. usual - short polling. - - Returns: - A list of telegram.Update objects are returned. - """ - - url = '%s/getUpdates' % self.base_url - - data = {} - if offset: - data['offset'] = offset - if limit: - data['limit'] = limit - if timeout: - data['timeout'] = timeout - - result = request.post(url, data) - - if result: - self.logger.info( - 'Getting updates: %s', [u['update_id'] for u in result]) - else: - self.logger.info('No new updates found.') - - return [Update.de_json(x) for x in result] - - @log - def setWebhook(self, - webhook_url=None, - certificate=None): - """Use this method to specify a url and receive incoming updates via an - outgoing webhook. Whenever there is an update for the bot, we will send - an HTTPS POST request to the specified url, containing a - JSON-serialized Update. In case of an unsuccessful request, we will - give up after a reasonable amount of attempts. - - Args: - url: - HTTPS url to send updates to. - Use an empty string to remove webhook integration - - Returns: - True if successful else TelegramError was raised - """ - url = '%s/setWebhook' % self.base_url - - data = {} - if webhook_url: - data['url'] = webhook_url - if certificate: - data['certificate'] = certificate - - result = request.post(url, data) - - return result - - @staticmethod - def de_json(data): - pass - - def to_dict(self): - """ - Returns: - dict: - """ - data = {'id': self.id, - 'username': self.username, - 'first_name': self.username} - if self.last_name: - data['last_name'] = self.last_name - return data - - def __reduce__(self): - return (self.__class__, (self.token, - self.base_url.replace(self.token, ''))) diff --git a/lib/telegram/chataction.py b/lib/telegram/chataction.py deleted file mode 100644 index fce7e418..00000000 --- a/lib/telegram/chataction.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram ChatAction""" - - -class ChatAction(object): - """This object represents a Telegram ChatAction.""" - - TYPING = 'typing' - UPLOAD_PHOTO = 'upload_photo' - RECORD_VIDEO = 'record_video' - UPLOAD_VIDEO = 'upload_video' - RECORD_AUDIO = 'record_audio' - UPLOAD_AUDIO = 'upload_audio' - UPLOAD_DOCUMENT = 'upload_document' - FIND_LOCATION = 'find_location' diff --git a/lib/telegram/contact.py b/lib/telegram/contact.py deleted file mode 100644 index 3b9e2b10..00000000 --- a/lib/telegram/contact.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Contact""" - -from telegram import TelegramObject - - -class Contact(TelegramObject): - """This object represents a Telegram Contact. - - Attributes: - phone_number (str): - first_name (str): - last_name (str): - user_id (int): - - Args: - phone_number (str): - first_name (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - last_name (Optional[str]): - user_id (Optional[int]): - """ - - def __init__(self, - phone_number, - first_name, - **kwargs): - # Required - self.phone_number = str(phone_number) - self.first_name = first_name - # Optionals - self.last_name = kwargs.get('last_name', '') - self.user_id = int(kwargs.get('user_id', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Contact: - """ - if not data: - return None - - return Contact(**data) diff --git a/lib/telegram/document.py b/lib/telegram/document.py deleted file mode 100644 index d492774d..00000000 --- a/lib/telegram/document.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Document""" - -from telegram import PhotoSize, TelegramObject - - -class Document(TelegramObject): - """This object represents a Telegram Document. - - Attributes: - file_id (str): - thumb (:class:`telegram.PhotoSize`): - file_name (str): - mime_type (str): - file_size (int): - - Args: - file_id (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - thumb (Optional[:class:`telegram.PhotoSize`]): - file_name (Optional[str]): - mime_type (Optional[str]): - file_size (Optional[int]): - """ - - def __init__(self, - file_id, - **kwargs): - # Required - self.file_id = str(file_id) - # Optionals - self.thumb = kwargs.get('thumb') - self.file_name = kwargs.get('file_name', '') - self.mime_type = str(kwargs.get('mime_type', '')) - self.file_size = int(kwargs.get('file_size', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Document: - """ - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb')) - - return Document(**data) diff --git a/lib/telegram/emoji.py b/lib/telegram/emoji.py deleted file mode 100644 index ee10651a..00000000 --- a/lib/telegram/emoji.py +++ /dev/null @@ -1,878 +0,0 @@ -#!/usr/bin/env python -# flake8: noqa -# pylint: disable=C0103,C0301,R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents an Emoji""" - - -class Emoji(object): - """This object represents an Emoji.""" - - GRINNING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x81' - FACE_WITH_TEARS_OF_JOY = b'\xF0\x9F\x98\x82' - SMILING_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\x83' - SMILING_FACE_WITH_OPEN_MOUTH_AND_SMILING_EYES = b'\xF0\x9F\x98\x84' - SMILING_FACE_WITH_OPEN_MOUTH_AND_COLD_SWEAT = b'\xF0\x9F\x98\x85' - SMILING_FACE_WITH_OPEN_MOUTH_AND_TIGHTLY_CLOSED_EYES = b'\xF0\x9F\x98\x86' - WINKING_FACE = b'\xF0\x9F\x98\x89' - SMILING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x8A' - FACE_SAVOURING_DELICIOUS_FOOD = b'\xF0\x9F\x98\x8B' - RELIEVED_FACE = b'\xF0\x9F\x98\x8C' - SMILING_FACE_WITH_HEART_SHAPED_EYES = b'\xF0\x9F\x98\x8D' - SMIRKING_FACE = b'\xF0\x9F\x98\x8F' - UNAMUSED_FACE = b'\xF0\x9F\x98\x92' - FACE_WITH_COLD_SWEAT = b'\xF0\x9F\x98\x93' - PENSIVE_FACE = b'\xF0\x9F\x98\x94' - CONFOUNDED_FACE = b'\xF0\x9F\x98\x96' - FACE_THROWING_A_KISS = b'\xF0\x9F\x98\x98' - KISSING_FACE_WITH_CLOSED_EYES = b'\xF0\x9F\x98\x9A' - FACE_WITH_STUCK_OUT_TONGUE_AND_WINKING_EYE = b'\xF0\x9F\x98\x9C' - FACE_WITH_STUCK_OUT_TONGUE_AND_TIGHTLY_CLOSED_EYES = b'\xF0\x9F\x98\x9D' - DISAPPOINTED_FACE = b'\xF0\x9F\x98\x9E' - ANGRY_FACE = b'\xF0\x9F\x98\xA0' - POUTING_FACE = b'\xF0\x9F\x98\xA1' - CRYING_FACE = b'\xF0\x9F\x98\xA2' - PERSEVERING_FACE = b'\xF0\x9F\x98\xA3' - FACE_WITH_LOOK_OF_TRIUMPH = b'\xF0\x9F\x98\xA4' - DISAPPOINTED_BUT_RELIEVED_FACE = b'\xF0\x9F\x98\xA5' - FEARFUL_FACE = b'\xF0\x9F\x98\xA8' - WEARY_FACE = b'\xF0\x9F\x98\xA9' - SLEEPY_FACE = b'\xF0\x9F\x98\xAA' - TIRED_FACE = b'\xF0\x9F\x98\xAB' - LOUDLY_CRYING_FACE = b'\xF0\x9F\x98\xAD' - FACE_WITH_OPEN_MOUTH_AND_COLD_SWEAT = b'\xF0\x9F\x98\xB0' - FACE_SCREAMING_IN_FEAR = b'\xF0\x9F\x98\xB1' - ASTONISHED_FACE = b'\xF0\x9F\x98\xB2' - FLUSHED_FACE = b'\xF0\x9F\x98\xB3' - DIZZY_FACE = b'\xF0\x9F\x98\xB5' - FACE_WITH_MEDICAL_MASK = b'\xF0\x9F\x98\xB7' - GRINNING_CAT_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\xB8' - CAT_FACE_WITH_TEARS_OF_JOY = b'\xF0\x9F\x98\xB9' - SMILING_CAT_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\xBA' - SMILING_CAT_FACE_WITH_HEART_SHAPED_EYES = b'\xF0\x9F\x98\xBB' - CAT_FACE_WITH_WRY_SMILE = b'\xF0\x9F\x98\xBC' - KISSING_CAT_FACE_WITH_CLOSED_EYES = b'\xF0\x9F\x98\xBD' - POUTING_CAT_FACE = b'\xF0\x9F\x98\xBE' - CRYING_CAT_FACE = b'\xF0\x9F\x98\xBF' - WEARY_CAT_FACE = b'\xF0\x9F\x99\x80' - FACE_WITH_NO_GOOD_GESTURE = b'\xF0\x9F\x99\x85' - FACE_WITH_OK_GESTURE = b'\xF0\x9F\x99\x86' - PERSON_BOWING_DEEPLY = b'\xF0\x9F\x99\x87' - SEE_NO_EVIL_MONKEY = b'\xF0\x9F\x99\x88' - HEAR_NO_EVIL_MONKEY = b'\xF0\x9F\x99\x89' - SPEAK_NO_EVIL_MONKEY = b'\xF0\x9F\x99\x8A' - HAPPY_PERSON_RAISING_ONE_HAND = b'\xF0\x9F\x99\x8B' - PERSON_RAISING_BOTH_HANDS_IN_CELEBRATION = b'\xF0\x9F\x99\x8C' - PERSON_FROWNING = b'\xF0\x9F\x99\x8D' - PERSON_WITH_POUTING_FACE = b'\xF0\x9F\x99\x8E' - PERSON_WITH_FOLDED_HANDS = b'\xF0\x9F\x99\x8F' - BLACK_SCISSORS = b'\xE2\x9C\x82' - WHITE_HEAVY_CHECK_MARK = b'\xE2\x9C\x85' - AIRPLANE = b'\xE2\x9C\x88' - ENVELOPE = b'\xE2\x9C\x89' - RAISED_FIST = b'\xE2\x9C\x8A' - RAISED_HAND = b'\xE2\x9C\x8B' - VICTORY_HAND = b'\xE2\x9C\x8C' - PENCIL = b'\xE2\x9C\x8F' - BLACK_NIB = b'\xE2\x9C\x92' - HEAVY_CHECK_MARK = b'\xE2\x9C\x94' - HEAVY_MULTIPLICATION_X = b'\xE2\x9C\x96' - SPARKLES = b'\xE2\x9C\xA8' - EIGHT_SPOKED_ASTERISK = b'\xE2\x9C\xB3' - EIGHT_POINTED_BLACK_STAR = b'\xE2\x9C\xB4' - SNOWFLAKE = b'\xE2\x9D\x84' - SPARKLE = b'\xE2\x9D\x87' - CROSS_MARK = b'\xE2\x9D\x8C' - NEGATIVE_SQUARED_CROSS_MARK = b'\xE2\x9D\x8E' - BLACK_QUESTION_MARK_ORNAMENT = b'\xE2\x9D\x93' - WHITE_QUESTION_MARK_ORNAMENT = b'\xE2\x9D\x94' - WHITE_EXCLAMATION_MARK_ORNAMENT = b'\xE2\x9D\x95' - HEAVY_EXCLAMATION_MARK_SYMBOL = b'\xE2\x9D\x97' - HEAVY_BLACK_HEART = b'\xE2\x9D\xA4' - HEAVY_PLUS_SIGN = b'\xE2\x9E\x95' - HEAVY_MINUS_SIGN = b'\xE2\x9E\x96' - HEAVY_DIVISION_SIGN = b'\xE2\x9E\x97' - BLACK_RIGHTWARDS_ARROW = b'\xE2\x9E\xA1' - CURLY_LOOP = b'\xE2\x9E\xB0' - ROCKET = b'\xF0\x9F\x9A\x80' - RAILWAY_CAR = b'\xF0\x9F\x9A\x83' - HIGH_SPEED_TRAIN = b'\xF0\x9F\x9A\x84' - HIGH_SPEED_TRAIN_WITH_BULLET_NOSE = b'\xF0\x9F\x9A\x85' - METRO = b'\xF0\x9F\x9A\x87' - STATION = b'\xF0\x9F\x9A\x89' - BUS = b'\xF0\x9F\x9A\x8C' - BUS_STOP = b'\xF0\x9F\x9A\x8F' - AMBULANCE = b'\xF0\x9F\x9A\x91' - FIRE_ENGINE = b'\xF0\x9F\x9A\x92' - POLICE_CAR = b'\xF0\x9F\x9A\x93' - TAXI = b'\xF0\x9F\x9A\x95' - AUTOMOBILE = b'\xF0\x9F\x9A\x97' - RECREATIONAL_VEHICLE = b'\xF0\x9F\x9A\x99' - DELIVERY_TRUCK = b'\xF0\x9F\x9A\x9A' - SHIP = b'\xF0\x9F\x9A\xA2' - SPEEDBOAT = b'\xF0\x9F\x9A\xA4' - HORIZONTAL_TRAFFIC_LIGHT = b'\xF0\x9F\x9A\xA5' - CONSTRUCTION_SIGN = b'\xF0\x9F\x9A\xA7' - POLICE_CARS_REVOLVING_LIGHT = b'\xF0\x9F\x9A\xA8' - TRIANGULAR_FLAG_ON_POST = b'\xF0\x9F\x9A\xA9' - DOOR = b'\xF0\x9F\x9A\xAA' - NO_ENTRY_SIGN = b'\xF0\x9F\x9A\xAB' - SMOKING_SYMBOL = b'\xF0\x9F\x9A\xAC' - NO_SMOKING_SYMBOL = b'\xF0\x9F\x9A\xAD' - BICYCLE = b'\xF0\x9F\x9A\xB2' - PEDESTRIAN = b'\xF0\x9F\x9A\xB6' - MENS_SYMBOL = b'\xF0\x9F\x9A\xB9' - WOMENS_SYMBOL = b'\xF0\x9F\x9A\xBA' - RESTROOM = b'\xF0\x9F\x9A\xBB' - BABY_SYMBOL = b'\xF0\x9F\x9A\xBC' - TOILET = b'\xF0\x9F\x9A\xBD' - WATER_CLOSET = b'\xF0\x9F\x9A\xBE' - BATH = b'\xF0\x9F\x9B\x80' - CIRCLED_LATIN_CAPITAL_LETTER_M = b'\xE2\x93\x82' - NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_A = b'\xF0\x9F\x85\xB0' - NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_B = b'\xF0\x9F\x85\xB1' - NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_O = b'\xF0\x9F\x85\xBE' - NEGATIVE_SQUARED_LATIN_CAPITAL_LETTER_P = b'\xF0\x9F\x85\xBF' - NEGATIVE_SQUARED_AB = b'\xF0\x9F\x86\x8E' - SQUARED_CL = b'\xF0\x9F\x86\x91' - SQUARED_COOL = b'\xF0\x9F\x86\x92' - SQUARED_FREE = b'\xF0\x9F\x86\x93' - SQUARED_ID = b'\xF0\x9F\x86\x94' - SQUARED_NEW = b'\xF0\x9F\x86\x95' - SQUARED_NG = b'\xF0\x9F\x86\x96' - SQUARED_OK = b'\xF0\x9F\x86\x97' - SQUARED_SOS = b'\xF0\x9F\x86\x98' - SQUARED_UP_WITH_EXCLAMATION_MARK = b'\xF0\x9F\x86\x99' - SQUARED_VS = b'\xF0\x9F\x86\x9A' - REGIONAL_INDICATOR_SYMBOL_LETTER_D_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_E\ - = b'\xF0\x9F\x87\xA9\xF0\x9F\x87\xAA' - REGIONAL_INDICATOR_SYMBOL_LETTER_G_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_B\ - = b'\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7' - REGIONAL_INDICATOR_SYMBOL_LETTER_C_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_N\ - = b'\xF0\x9F\x87\xA8\xF0\x9F\x87\xB3' - REGIONAL_INDICATOR_SYMBOL_LETTER_J_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_P\ - = b'\xF0\x9F\x87\xAF\xF0\x9F\x87\xB5' - REGIONAL_INDICATOR_SYMBOL_LETTER_K_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R\ - = b'\xF0\x9F\x87\xB0\xF0\x9F\x87\xB7' - REGIONAL_INDICATOR_SYMBOL_LETTER_F_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R\ - = b'\xF0\x9F\x87\xAB\xF0\x9F\x87\xB7' - REGIONAL_INDICATOR_SYMBOL_LETTER_E_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S\ - = b'\xF0\x9F\x87\xAA\xF0\x9F\x87\xB8' - REGIONAL_INDICATOR_SYMBOL_LETTER_I_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_T\ - = b'\xF0\x9F\x87\xAE\xF0\x9F\x87\xB9' - REGIONAL_INDICATOR_SYMBOL_LETTER_U_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S\ - = b'\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8' - REGIONAL_INDICATOR_SYMBOL_LETTER_R_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_U\ - = b'\xF0\x9F\x87\xB7\xF0\x9F\x87\xBA' - SQUARED_KATAKANA_KOKO = b'\xF0\x9F\x88\x81' - SQUARED_KATAKANA_SA = b'\xF0\x9F\x88\x82' - SQUARED_CJK_UNIFIED_IDEOGRAPH_7121 = b'\xF0\x9F\x88\x9A' - SQUARED_CJK_UNIFIED_IDEOGRAPH_6307 = b'\xF0\x9F\x88\xAF' - SQUARED_CJK_UNIFIED_IDEOGRAPH_7981 = b'\xF0\x9F\x88\xB2' - SQUARED_CJK_UNIFIED_IDEOGRAPH_7A7A = b'\xF0\x9F\x88\xB3' - SQUARED_CJK_UNIFIED_IDEOGRAPH_5408 = b'\xF0\x9F\x88\xB4' - SQUARED_CJK_UNIFIED_IDEOGRAPH_6E80 = b'\xF0\x9F\x88\xB5' - SQUARED_CJK_UNIFIED_IDEOGRAPH_6709 = b'\xF0\x9F\x88\xB6' - SQUARED_CJK_UNIFIED_IDEOGRAPH_6708 = b'\xF0\x9F\x88\xB7' - SQUARED_CJK_UNIFIED_IDEOGRAPH_7533 = b'\xF0\x9F\x88\xB8' - SQUARED_CJK_UNIFIED_IDEOGRAPH_5272 = b'\xF0\x9F\x88\xB9' - SQUARED_CJK_UNIFIED_IDEOGRAPH_55B6 = b'\xF0\x9F\x88\xBA' - CIRCLED_IDEOGRAPH_ADVANTAGE = b'\xF0\x9F\x89\x90' - CIRCLED_IDEOGRAPH_ACCEPT = b'\xF0\x9F\x89\x91' - COPYRIGHT_SIGN = b'\xC2\xA9' - REGISTERED_SIGN = b'\xC2\xAE' - DOUBLE_EXCLAMATION_MARK = b'\xE2\x80\xBC' - EXCLAMATION_QUESTION_MARK = b'\xE2\x81\x89' - DIGIT_EIGHT_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x38\xE2\x83\xA3' - DIGIT_NINE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x39\xE2\x83\xA3' - DIGIT_SEVEN_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x37\xE2\x83\xA3' - DIGIT_SIX_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x36\xE2\x83\xA3' - DIGIT_ONE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x31\xE2\x83\xA3' - DIGIT_ZERO_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x30\xE2\x83\xA3' - DIGIT_TWO_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x32\xE2\x83\xA3' - DIGIT_THREE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x33\xE2\x83\xA3' - DIGIT_FIVE_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x35\xE2\x83\xA3' - DIGIT_FOUR_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x34\xE2\x83\xA3' - NUMBER_SIGN_PLUS_COMBINING_ENCLOSING_KEYCAP = b'\x23\xE2\x83\xA3' - TRADE_MARK_SIGN = b'\xE2\x84\xA2' - INFORMATION_SOURCE = b'\xE2\x84\xB9' - LEFT_RIGHT_ARROW = b'\xE2\x86\x94' - UP_DOWN_ARROW = b'\xE2\x86\x95' - NORTH_WEST_ARROW = b'\xE2\x86\x96' - NORTH_EAST_ARROW = b'\xE2\x86\x97' - SOUTH_EAST_ARROW = b'\xE2\x86\x98' - SOUTH_WEST_ARROW = b'\xE2\x86\x99' - LEFTWARDS_ARROW_WITH_HOOK = b'\xE2\x86\xA9' - RIGHTWARDS_ARROW_WITH_HOOK = b'\xE2\x86\xAA' - WATCH = b'\xE2\x8C\x9A' - HOURGLASS = b'\xE2\x8C\x9B' - BLACK_RIGHT_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xA9' - BLACK_LEFT_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xAA' - BLACK_UP_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xAB' - BLACK_DOWN_POINTING_DOUBLE_TRIANGLE = b'\xE2\x8F\xAC' - ALARM_CLOCK = b'\xE2\x8F\xB0' - HOURGLASS_WITH_FLOWING_SAND = b'\xE2\x8F\xB3' - BLACK_SMALL_SQUARE = b'\xE2\x96\xAA' - WHITE_SMALL_SQUARE = b'\xE2\x96\xAB' - BLACK_RIGHT_POINTING_TRIANGLE = b'\xE2\x96\xB6' - BLACK_LEFT_POINTING_TRIANGLE = b'\xE2\x97\x80' - WHITE_MEDIUM_SQUARE = b'\xE2\x97\xBB' - BLACK_MEDIUM_SQUARE = b'\xE2\x97\xBC' - WHITE_MEDIUM_SMALL_SQUARE = b'\xE2\x97\xBD' - BLACK_MEDIUM_SMALL_SQUARE = b'\xE2\x97\xBE' - BLACK_SUN_WITH_RAYS = b'\xE2\x98\x80' - CLOUD = b'\xE2\x98\x81' - BLACK_TELEPHONE = b'\xE2\x98\x8E' - BALLOT_BOX_WITH_CHECK = b'\xE2\x98\x91' - UMBRELLA_WITH_RAIN_DROPS = b'\xE2\x98\x94' - HOT_BEVERAGE = b'\xE2\x98\x95' - WHITE_UP_POINTING_INDEX = b'\xE2\x98\x9D' - WHITE_SMILING_FACE = b'\xE2\x98\xBA' - ARIES = b'\xE2\x99\x88' - TAURUS = b'\xE2\x99\x89' - GEMINI = b'\xE2\x99\x8A' - CANCER = b'\xE2\x99\x8B' - LEO = b'\xE2\x99\x8C' - VIRGO = b'\xE2\x99\x8D' - LIBRA = b'\xE2\x99\x8E' - SCORPIUS = b'\xE2\x99\x8F' - SAGITTARIUS = b'\xE2\x99\x90' - CAPRICORN = b'\xE2\x99\x91' - AQUARIUS = b'\xE2\x99\x92' - PISCES = b'\xE2\x99\x93' - BLACK_SPADE_SUIT = b'\xE2\x99\xA0' - BLACK_CLUB_SUIT = b'\xE2\x99\xA3' - BLACK_HEART_SUIT = b'\xE2\x99\xA5' - BLACK_DIAMOND_SUIT = b'\xE2\x99\xA6' - HOT_SPRINGS = b'\xE2\x99\xA8' - BLACK_UNIVERSAL_RECYCLING_SYMBOL = b'\xE2\x99\xBB' - WHEELCHAIR_SYMBOL = b'\xE2\x99\xBF' - ANCHOR = b'\xE2\x9A\x93' - WARNING_SIGN = b'\xE2\x9A\xA0' - HIGH_VOLTAGE_SIGN = b'\xE2\x9A\xA1' - MEDIUM_WHITE_CIRCLE = b'\xE2\x9A\xAA' - MEDIUM_BLACK_CIRCLE = b'\xE2\x9A\xAB' - SOCCER_BALL = b'\xE2\x9A\xBD' - BASEBALL = b'\xE2\x9A\xBE' - SNOWMAN_WITHOUT_SNOW = b'\xE2\x9B\x84' - SUN_BEHIND_CLOUD = b'\xE2\x9B\x85' - OPHIUCHUS = b'\xE2\x9B\x8E' - NO_ENTRY = b'\xE2\x9B\x94' - CHURCH = b'\xE2\x9B\xAA' - FOUNTAIN = b'\xE2\x9B\xB2' - FLAG_IN_HOLE = b'\xE2\x9B\xB3' - SAILBOAT = b'\xE2\x9B\xB5' - TENT = b'\xE2\x9B\xBA' - FUEL_PUMP = b'\xE2\x9B\xBD' - ARROW_POINTING_RIGHTWARDS_THEN_CURVING_UPWARDS = b'\xE2\xA4\xB4' - ARROW_POINTING_RIGHTWARDS_THEN_CURVING_DOWNWARDS = b'\xE2\xA4\xB5' - LEFTWARDS_BLACK_ARROW = b'\xE2\xAC\x85' - UPWARDS_BLACK_ARROW = b'\xE2\xAC\x86' - DOWNWARDS_BLACK_ARROW = b'\xE2\xAC\x87' - BLACK_LARGE_SQUARE = b'\xE2\xAC\x9B' - WHITE_LARGE_SQUARE = b'\xE2\xAC\x9C' - WHITE_MEDIUM_STAR = b'\xE2\xAD\x90' - HEAVY_LARGE_CIRCLE = b'\xE2\xAD\x95' - WAVY_DASH = b'\xE3\x80\xB0' - PART_ALTERNATION_MARK = b'\xE3\x80\xBD' - CIRCLED_IDEOGRAPH_CONGRATULATION = b'\xE3\x8A\x97' - CIRCLED_IDEOGRAPH_SECRET = b'\xE3\x8A\x99' - MAHJONG_TILE_RED_DRAGON = b'\xF0\x9F\x80\x84' - PLAYING_CARD_BLACK_JOKER = b'\xF0\x9F\x83\x8F' - CYCLONE = b'\xF0\x9F\x8C\x80' - FOGGY = b'\xF0\x9F\x8C\x81' - CLOSED_UMBRELLA = b'\xF0\x9F\x8C\x82' - NIGHT_WITH_STARS = b'\xF0\x9F\x8C\x83' - SUNRISE_OVER_MOUNTAINS = b'\xF0\x9F\x8C\x84' - SUNRISE = b'\xF0\x9F\x8C\x85' - CITYSCAPE_AT_DUSK = b'\xF0\x9F\x8C\x86' - SUNSET_OVER_BUILDINGS = b'\xF0\x9F\x8C\x87' - RAINBOW = b'\xF0\x9F\x8C\x88' - BRIDGE_AT_NIGHT = b'\xF0\x9F\x8C\x89' - WATER_WAVE = b'\xF0\x9F\x8C\x8A' - VOLCANO = b'\xF0\x9F\x8C\x8B' - MILKY_WAY = b'\xF0\x9F\x8C\x8C' - EARTH_GLOBE_ASIA_AUSTRALIA = b'\xF0\x9F\x8C\x8F' - NEW_MOON_SYMBOL = b'\xF0\x9F\x8C\x91' - FIRST_QUARTER_MOON_SYMBOL = b'\xF0\x9F\x8C\x93' - WAXING_GIBBOUS_MOON_SYMBOL = b'\xF0\x9F\x8C\x94' - FULL_MOON_SYMBOL = b'\xF0\x9F\x8C\x95' - CRESCENT_MOON = b'\xF0\x9F\x8C\x99' - FIRST_QUARTER_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9B' - GLOWING_STAR = b'\xF0\x9F\x8C\x9F' - SHOOTING_STAR = b'\xF0\x9F\x8C\xA0' - CHESTNUT = b'\xF0\x9F\x8C\xB0' - SEEDLING = b'\xF0\x9F\x8C\xB1' - PALM_TREE = b'\xF0\x9F\x8C\xB4' - CACTUS = b'\xF0\x9F\x8C\xB5' - TULIP = b'\xF0\x9F\x8C\xB7' - CHERRY_BLOSSOM = b'\xF0\x9F\x8C\xB8' - ROSE = b'\xF0\x9F\x8C\xB9' - HIBISCUS = b'\xF0\x9F\x8C\xBA' - SUNFLOWER = b'\xF0\x9F\x8C\xBB' - BLOSSOM = b'\xF0\x9F\x8C\xBC' - EAR_OF_MAIZE = b'\xF0\x9F\x8C\xBD' - EAR_OF_RICE = b'\xF0\x9F\x8C\xBE' - HERB = b'\xF0\x9F\x8C\xBF' - FOUR_LEAF_CLOVER = b'\xF0\x9F\x8D\x80' - MAPLE_LEAF = b'\xF0\x9F\x8D\x81' - FALLEN_LEAF = b'\xF0\x9F\x8D\x82' - LEAF_FLUTTERING_IN_WIND = b'\xF0\x9F\x8D\x83' - MUSHROOM = b'\xF0\x9F\x8D\x84' - TOMATO = b'\xF0\x9F\x8D\x85' - AUBERGINE = b'\xF0\x9F\x8D\x86' - GRAPES = b'\xF0\x9F\x8D\x87' - MELON = b'\xF0\x9F\x8D\x88' - WATERMELON = b'\xF0\x9F\x8D\x89' - TANGERINE = b'\xF0\x9F\x8D\x8A' - BANANA = b'\xF0\x9F\x8D\x8C' - PINEAPPLE = b'\xF0\x9F\x8D\x8D' - RED_APPLE = b'\xF0\x9F\x8D\x8E' - GREEN_APPLE = b'\xF0\x9F\x8D\x8F' - PEACH = b'\xF0\x9F\x8D\x91' - CHERRIES = b'\xF0\x9F\x8D\x92' - STRAWBERRY = b'\xF0\x9F\x8D\x93' - HAMBURGER = b'\xF0\x9F\x8D\x94' - SLICE_OF_PIZZA = b'\xF0\x9F\x8D\x95' - MEAT_ON_BONE = b'\xF0\x9F\x8D\x96' - POULTRY_LEG = b'\xF0\x9F\x8D\x97' - RICE_CRACKER = b'\xF0\x9F\x8D\x98' - RICE_BALL = b'\xF0\x9F\x8D\x99' - COOKED_RICE = b'\xF0\x9F\x8D\x9A' - CURRY_AND_RICE = b'\xF0\x9F\x8D\x9B' - STEAMING_BOWL = b'\xF0\x9F\x8D\x9C' - SPAGHETTI = b'\xF0\x9F\x8D\x9D' - BREAD = b'\xF0\x9F\x8D\x9E' - FRENCH_FRIES = b'\xF0\x9F\x8D\x9F' - ROASTED_SWEET_POTATO = b'\xF0\x9F\x8D\xA0' - DANGO = b'\xF0\x9F\x8D\xA1' - ODEN = b'\xF0\x9F\x8D\xA2' - SUSHI = b'\xF0\x9F\x8D\xA3' - FRIED_SHRIMP = b'\xF0\x9F\x8D\xA4' - FISH_CAKE_WITH_SWIRL_DESIGN = b'\xF0\x9F\x8D\xA5' - SOFT_ICE_CREAM = b'\xF0\x9F\x8D\xA6' - SHAVED_ICE = b'\xF0\x9F\x8D\xA7' - ICE_CREAM = b'\xF0\x9F\x8D\xA8' - DOUGHNUT = b'\xF0\x9F\x8D\xA9' - COOKIE = b'\xF0\x9F\x8D\xAA' - CHOCOLATE_BAR = b'\xF0\x9F\x8D\xAB' - CANDY = b'\xF0\x9F\x8D\xAC' - LOLLIPOP = b'\xF0\x9F\x8D\xAD' - CUSTARD = b'\xF0\x9F\x8D\xAE' - HONEY_POT = b'\xF0\x9F\x8D\xAF' - SHORTCAKE = b'\xF0\x9F\x8D\xB0' - BENTO_BOX = b'\xF0\x9F\x8D\xB1' - POT_OF_FOOD = b'\xF0\x9F\x8D\xB2' - COOKING = b'\xF0\x9F\x8D\xB3' - FORK_AND_KNIFE = b'\xF0\x9F\x8D\xB4' - TEACUP_WITHOUT_HANDLE = b'\xF0\x9F\x8D\xB5' - SAKE_BOTTLE_AND_CUP = b'\xF0\x9F\x8D\xB6' - WINE_GLASS = b'\xF0\x9F\x8D\xB7' - COCKTAIL_GLASS = b'\xF0\x9F\x8D\xB8' - TROPICAL_DRINK = b'\xF0\x9F\x8D\xB9' - BEER_MUG = b'\xF0\x9F\x8D\xBA' - CLINKING_BEER_MUGS = b'\xF0\x9F\x8D\xBB' - RIBBON = b'\xF0\x9F\x8E\x80' - WRAPPED_PRESENT = b'\xF0\x9F\x8E\x81' - BIRTHDAY_CAKE = b'\xF0\x9F\x8E\x82' - JACK_O_LANTERN = b'\xF0\x9F\x8E\x83' - CHRISTMAS_TREE = b'\xF0\x9F\x8E\x84' - FATHER_CHRISTMAS = b'\xF0\x9F\x8E\x85' - FIREWORKS = b'\xF0\x9F\x8E\x86' - FIREWORK_SPARKLER = b'\xF0\x9F\x8E\x87' - BALLOON = b'\xF0\x9F\x8E\x88' - PARTY_POPPER = b'\xF0\x9F\x8E\x89' - CONFETTI_BALL = b'\xF0\x9F\x8E\x8A' - TANABATA_TREE = b'\xF0\x9F\x8E\x8B' - CROSSED_FLAGS = b'\xF0\x9F\x8E\x8C' - PINE_DECORATION = b'\xF0\x9F\x8E\x8D' - JAPANESE_DOLLS = b'\xF0\x9F\x8E\x8E' - CARP_STREAMER = b'\xF0\x9F\x8E\x8F' - WIND_CHIME = b'\xF0\x9F\x8E\x90' - MOON_VIEWING_CEREMONY = b'\xF0\x9F\x8E\x91' - SCHOOL_SATCHEL = b'\xF0\x9F\x8E\x92' - GRADUATION_CAP = b'\xF0\x9F\x8E\x93' - CAROUSEL_HORSE = b'\xF0\x9F\x8E\xA0' - FERRIS_WHEEL = b'\xF0\x9F\x8E\xA1' - ROLLER_COASTER = b'\xF0\x9F\x8E\xA2' - FISHING_POLE_AND_FISH = b'\xF0\x9F\x8E\xA3' - MICROPHONE = b'\xF0\x9F\x8E\xA4' - MOVIE_CAMERA = b'\xF0\x9F\x8E\xA5' - CINEMA = b'\xF0\x9F\x8E\xA6' - HEADPHONE = b'\xF0\x9F\x8E\xA7' - ARTIST_PALETTE = b'\xF0\x9F\x8E\xA8' - TOP_HAT = b'\xF0\x9F\x8E\xA9' - CIRCUS_TENT = b'\xF0\x9F\x8E\xAA' - TICKET = b'\xF0\x9F\x8E\xAB' - CLAPPER_BOARD = b'\xF0\x9F\x8E\xAC' - PERFORMING_ARTS = b'\xF0\x9F\x8E\xAD' - VIDEO_GAME = b'\xF0\x9F\x8E\xAE' - DIRECT_HIT = b'\xF0\x9F\x8E\xAF' - SLOT_MACHINE = b'\xF0\x9F\x8E\xB0' - BILLIARDS = b'\xF0\x9F\x8E\xB1' - GAME_DIE = b'\xF0\x9F\x8E\xB2' - BOWLING = b'\xF0\x9F\x8E\xB3' - FLOWER_PLAYING_CARDS = b'\xF0\x9F\x8E\xB4' - MUSICAL_NOTE = b'\xF0\x9F\x8E\xB5' - MULTIPLE_MUSICAL_NOTES = b'\xF0\x9F\x8E\xB6' - SAXOPHONE = b'\xF0\x9F\x8E\xB7' - GUITAR = b'\xF0\x9F\x8E\xB8' - MUSICAL_KEYBOARD = b'\xF0\x9F\x8E\xB9' - TRUMPET = b'\xF0\x9F\x8E\xBA' - VIOLIN = b'\xF0\x9F\x8E\xBB' - MUSICAL_SCORE = b'\xF0\x9F\x8E\xBC' - RUNNING_SHIRT_WITH_SASH = b'\xF0\x9F\x8E\xBD' - TENNIS_RACQUET_AND_BALL = b'\xF0\x9F\x8E\xBE' - SKI_AND_SKI_BOOT = b'\xF0\x9F\x8E\xBF' - BASKETBALL_AND_HOOP = b'\xF0\x9F\x8F\x80' - CHEQUERED_FLAG = b'\xF0\x9F\x8F\x81' - SNOWBOARDER = b'\xF0\x9F\x8F\x82' - RUNNER = b'\xF0\x9F\x8F\x83' - SURFER = b'\xF0\x9F\x8F\x84' - TROPHY = b'\xF0\x9F\x8F\x86' - AMERICAN_FOOTBALL = b'\xF0\x9F\x8F\x88' - SWIMMER = b'\xF0\x9F\x8F\x8A' - HOUSE_BUILDING = b'\xF0\x9F\x8F\xA0' - HOUSE_WITH_GARDEN = b'\xF0\x9F\x8F\xA1' - OFFICE_BUILDING = b'\xF0\x9F\x8F\xA2' - JAPANESE_POST_OFFICE = b'\xF0\x9F\x8F\xA3' - HOSPITAL = b'\xF0\x9F\x8F\xA5' - BANK = b'\xF0\x9F\x8F\xA6' - AUTOMATED_TELLER_MACHINE = b'\xF0\x9F\x8F\xA7' - HOTEL = b'\xF0\x9F\x8F\xA8' - LOVE_HOTEL = b'\xF0\x9F\x8F\xA9' - CONVENIENCE_STORE = b'\xF0\x9F\x8F\xAA' - SCHOOL = b'\xF0\x9F\x8F\xAB' - DEPARTMENT_STORE = b'\xF0\x9F\x8F\xAC' - FACTORY = b'\xF0\x9F\x8F\xAD' - IZAKAYA_LANTERN = b'\xF0\x9F\x8F\xAE' - JAPANESE_CASTLE = b'\xF0\x9F\x8F\xAF' - EUROPEAN_CASTLE = b'\xF0\x9F\x8F\xB0' - SNAIL = b'\xF0\x9F\x90\x8C' - SNAKE = b'\xF0\x9F\x90\x8D' - HORSE = b'\xF0\x9F\x90\x8E' - SHEEP = b'\xF0\x9F\x90\x91' - MONKEY = b'\xF0\x9F\x90\x92' - CHICKEN = b'\xF0\x9F\x90\x94' - BOAR = b'\xF0\x9F\x90\x97' - ELEPHANT = b'\xF0\x9F\x90\x98' - OCTOPUS = b'\xF0\x9F\x90\x99' - SPIRAL_SHELL = b'\xF0\x9F\x90\x9A' - BUG = b'\xF0\x9F\x90\x9B' - ANT = b'\xF0\x9F\x90\x9C' - HONEYBEE = b'\xF0\x9F\x90\x9D' - LADY_BEETLE = b'\xF0\x9F\x90\x9E' - FISH = b'\xF0\x9F\x90\x9F' - TROPICAL_FISH = b'\xF0\x9F\x90\xA0' - BLOWFISH = b'\xF0\x9F\x90\xA1' - TURTLE = b'\xF0\x9F\x90\xA2' - HATCHING_CHICK = b'\xF0\x9F\x90\xA3' - BABY_CHICK = b'\xF0\x9F\x90\xA4' - FRONT_FACING_BABY_CHICK = b'\xF0\x9F\x90\xA5' - BIRD = b'\xF0\x9F\x90\xA6' - PENGUIN = b'\xF0\x9F\x90\xA7' - KOALA = b'\xF0\x9F\x90\xA8' - POODLE = b'\xF0\x9F\x90\xA9' - BACTRIAN_CAMEL = b'\xF0\x9F\x90\xAB' - DOLPHIN = b'\xF0\x9F\x90\xAC' - MOUSE_FACE = b'\xF0\x9F\x90\xAD' - COW_FACE = b'\xF0\x9F\x90\xAE' - TIGER_FACE = b'\xF0\x9F\x90\xAF' - RABBIT_FACE = b'\xF0\x9F\x90\xB0' - CAT_FACE = b'\xF0\x9F\x90\xB1' - DRAGON_FACE = b'\xF0\x9F\x90\xB2' - SPOUTING_WHALE = b'\xF0\x9F\x90\xB3' - HORSE_FACE = b'\xF0\x9F\x90\xB4' - MONKEY_FACE = b'\xF0\x9F\x90\xB5' - DOG_FACE = b'\xF0\x9F\x90\xB6' - PIG_FACE = b'\xF0\x9F\x90\xB7' - FROG_FACE = b'\xF0\x9F\x90\xB8' - HAMSTER_FACE = b'\xF0\x9F\x90\xB9' - WOLF_FACE = b'\xF0\x9F\x90\xBA' - BEAR_FACE = b'\xF0\x9F\x90\xBB' - PANDA_FACE = b'\xF0\x9F\x90\xBC' - PIG_NOSE = b'\xF0\x9F\x90\xBD' - PAW_PRINTS = b'\xF0\x9F\x90\xBE' - EYES = b'\xF0\x9F\x91\x80' - EAR = b'\xF0\x9F\x91\x82' - NOSE = b'\xF0\x9F\x91\x83' - MOUTH = b'\xF0\x9F\x91\x84' - TONGUE = b'\xF0\x9F\x91\x85' - WHITE_UP_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x86' - WHITE_DOWN_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x87' - WHITE_LEFT_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x88' - WHITE_RIGHT_POINTING_BACKHAND_INDEX = b'\xF0\x9F\x91\x89' - FISTED_HAND_SIGN = b'\xF0\x9F\x91\x8A' - WAVING_HAND_SIGN = b'\xF0\x9F\x91\x8B' - OK_HAND_SIGN = b'\xF0\x9F\x91\x8C' - THUMBS_UP_SIGN = b'\xF0\x9F\x91\x8D' - THUMBS_DOWN_SIGN = b'\xF0\x9F\x91\x8E' - CLAPPING_HANDS_SIGN = b'\xF0\x9F\x91\x8F' - OPEN_HANDS_SIGN = b'\xF0\x9F\x91\x90' - CROWN = b'\xF0\x9F\x91\x91' - WOMANS_HAT = b'\xF0\x9F\x91\x92' - EYEGLASSES = b'\xF0\x9F\x91\x93' - NECKTIE = b'\xF0\x9F\x91\x94' - T_SHIRT = b'\xF0\x9F\x91\x95' - JEANS = b'\xF0\x9F\x91\x96' - DRESS = b'\xF0\x9F\x91\x97' - KIMONO = b'\xF0\x9F\x91\x98' - BIKINI = b'\xF0\x9F\x91\x99' - WOMANS_CLOTHES = b'\xF0\x9F\x91\x9A' - PURSE = b'\xF0\x9F\x91\x9B' - HANDBAG = b'\xF0\x9F\x91\x9C' - POUCH = b'\xF0\x9F\x91\x9D' - MANS_SHOE = b'\xF0\x9F\x91\x9E' - ATHLETIC_SHOE = b'\xF0\x9F\x91\x9F' - HIGH_HEELED_SHOE = b'\xF0\x9F\x91\xA0' - WOMANS_SANDAL = b'\xF0\x9F\x91\xA1' - WOMANS_BOOTS = b'\xF0\x9F\x91\xA2' - FOOTPRINTS = b'\xF0\x9F\x91\xA3' - BUST_IN_SILHOUETTE = b'\xF0\x9F\x91\xA4' - BOY = b'\xF0\x9F\x91\xA6' - GIRL = b'\xF0\x9F\x91\xA7' - MAN = b'\xF0\x9F\x91\xA8' - WOMAN = b'\xF0\x9F\x91\xA9' - FAMILY = b'\xF0\x9F\x91\xAA' - MAN_AND_WOMAN_HOLDING_HANDS = b'\xF0\x9F\x91\xAB' - POLICE_OFFICER = b'\xF0\x9F\x91\xAE' - WOMAN_WITH_BUNNY_EARS = b'\xF0\x9F\x91\xAF' - BRIDE_WITH_VEIL = b'\xF0\x9F\x91\xB0' - PERSON_WITH_BLOND_HAIR = b'\xF0\x9F\x91\xB1' - MAN_WITH_GUA_PI_MAO = b'\xF0\x9F\x91\xB2' - MAN_WITH_TURBAN = b'\xF0\x9F\x91\xB3' - OLDER_MAN = b'\xF0\x9F\x91\xB4' - OLDER_WOMAN = b'\xF0\x9F\x91\xB5' - BABY = b'\xF0\x9F\x91\xB6' - CONSTRUCTION_WORKER = b'\xF0\x9F\x91\xB7' - PRINCESS = b'\xF0\x9F\x91\xB8' - JAPANESE_OGRE = b'\xF0\x9F\x91\xB9' - JAPANESE_GOBLIN = b'\xF0\x9F\x91\xBA' - GHOST = b'\xF0\x9F\x91\xBB' - BABY_ANGEL = b'\xF0\x9F\x91\xBC' - EXTRATERRESTRIAL_ALIEN = b'\xF0\x9F\x91\xBD' - ALIEN_MONSTER = b'\xF0\x9F\x91\xBE' - IMP = b'\xF0\x9F\x91\xBF' - SKULL = b'\xF0\x9F\x92\x80' - INFORMATION_DESK_PERSON = b'\xF0\x9F\x92\x81' - GUARDSMAN = b'\xF0\x9F\x92\x82' - DANCER = b'\xF0\x9F\x92\x83' - LIPSTICK = b'\xF0\x9F\x92\x84' - NAIL_POLISH = b'\xF0\x9F\x92\x85' - FACE_MASSAGE = b'\xF0\x9F\x92\x86' - HAIRCUT = b'\xF0\x9F\x92\x87' - BARBER_POLE = b'\xF0\x9F\x92\x88' - SYRINGE = b'\xF0\x9F\x92\x89' - PILL = b'\xF0\x9F\x92\x8A' - KISS_MARK = b'\xF0\x9F\x92\x8B' - LOVE_LETTER = b'\xF0\x9F\x92\x8C' - RING = b'\xF0\x9F\x92\x8D' - GEM_STONE = b'\xF0\x9F\x92\x8E' - KISS = b'\xF0\x9F\x92\x8F' - BOUQUET = b'\xF0\x9F\x92\x90' - COUPLE_WITH_HEART = b'\xF0\x9F\x92\x91' - WEDDING = b'\xF0\x9F\x92\x92' - BEATING_HEART = b'\xF0\x9F\x92\x93' - BROKEN_HEART = b'\xF0\x9F\x92\x94' - TWO_HEARTS = b'\xF0\x9F\x92\x95' - SPARKLING_HEART = b'\xF0\x9F\x92\x96' - GROWING_HEART = b'\xF0\x9F\x92\x97' - HEART_WITH_ARROW = b'\xF0\x9F\x92\x98' - BLUE_HEART = b'\xF0\x9F\x92\x99' - GREEN_HEART = b'\xF0\x9F\x92\x9A' - YELLOW_HEART = b'\xF0\x9F\x92\x9B' - PURPLE_HEART = b'\xF0\x9F\x92\x9C' - HEART_WITH_RIBBON = b'\xF0\x9F\x92\x9D' - REVOLVING_HEARTS = b'\xF0\x9F\x92\x9E' - HEART_DECORATION = b'\xF0\x9F\x92\x9F' - DIAMOND_SHAPE_WITH_A_DOT_INSIDE = b'\xF0\x9F\x92\xA0' - ELECTRIC_LIGHT_BULB = b'\xF0\x9F\x92\xA1' - ANGER_SYMBOL = b'\xF0\x9F\x92\xA2' - BOMB = b'\xF0\x9F\x92\xA3' - SLEEPING_SYMBOL = b'\xF0\x9F\x92\xA4' - COLLISION_SYMBOL = b'\xF0\x9F\x92\xA5' - SPLASHING_SWEAT_SYMBOL = b'\xF0\x9F\x92\xA6' - DROPLET = b'\xF0\x9F\x92\xA7' - DASH_SYMBOL = b'\xF0\x9F\x92\xA8' - PILE_OF_POO = b'\xF0\x9F\x92\xA9' - FLEXED_BICEPS = b'\xF0\x9F\x92\xAA' - DIZZY_SYMBOL = b'\xF0\x9F\x92\xAB' - SPEECH_BALLOON = b'\xF0\x9F\x92\xAC' - WHITE_FLOWER = b'\xF0\x9F\x92\xAE' - HUNDRED_POINTS_SYMBOL = b'\xF0\x9F\x92\xAF' - MONEY_BAG = b'\xF0\x9F\x92\xB0' - CURRENCY_EXCHANGE = b'\xF0\x9F\x92\xB1' - HEAVY_DOLLAR_SIGN = b'\xF0\x9F\x92\xB2' - CREDIT_CARD = b'\xF0\x9F\x92\xB3' - BANKNOTE_WITH_YEN_SIGN = b'\xF0\x9F\x92\xB4' - BANKNOTE_WITH_DOLLAR_SIGN = b'\xF0\x9F\x92\xB5' - MONEY_WITH_WINGS = b'\xF0\x9F\x92\xB8' - CHART_WITH_UPWARDS_TREND_AND_YEN_SIGN = b'\xF0\x9F\x92\xB9' - SEAT = b'\xF0\x9F\x92\xBA' - PERSONAL_COMPUTER = b'\xF0\x9F\x92\xBB' - BRIEFCASE = b'\xF0\x9F\x92\xBC' - MINIDISC = b'\xF0\x9F\x92\xBD' - FLOPPY_DISK = b'\xF0\x9F\x92\xBE' - OPTICAL_DISC = b'\xF0\x9F\x92\xBF' - DVD = b'\xF0\x9F\x93\x80' - FILE_FOLDER = b'\xF0\x9F\x93\x81' - OPEN_FILE_FOLDER = b'\xF0\x9F\x93\x82' - PAGE_WITH_CURL = b'\xF0\x9F\x93\x83' - PAGE_FACING_UP = b'\xF0\x9F\x93\x84' - CALENDAR = b'\xF0\x9F\x93\x85' - TEAR_OFF_CALENDAR = b'\xF0\x9F\x93\x86' - CARD_INDEX = b'\xF0\x9F\x93\x87' - CHART_WITH_UPWARDS_TREND = b'\xF0\x9F\x93\x88' - CHART_WITH_DOWNWARDS_TREND = b'\xF0\x9F\x93\x89' - BAR_CHART = b'\xF0\x9F\x93\x8A' - CLIPBOARD = b'\xF0\x9F\x93\x8B' - PUSHPIN = b'\xF0\x9F\x93\x8C' - ROUND_PUSHPIN = b'\xF0\x9F\x93\x8D' - PAPERCLIP = b'\xF0\x9F\x93\x8E' - STRAIGHT_RULER = b'\xF0\x9F\x93\x8F' - TRIANGULAR_RULER = b'\xF0\x9F\x93\x90' - BOOKMARK_TABS = b'\xF0\x9F\x93\x91' - LEDGER = b'\xF0\x9F\x93\x92' - NOTEBOOK = b'\xF0\x9F\x93\x93' - NOTEBOOK_WITH_DECORATIVE_COVER = b'\xF0\x9F\x93\x94' - CLOSED_BOOK = b'\xF0\x9F\x93\x95' - OPEN_BOOK = b'\xF0\x9F\x93\x96' - GREEN_BOOK = b'\xF0\x9F\x93\x97' - BLUE_BOOK = b'\xF0\x9F\x93\x98' - ORANGE_BOOK = b'\xF0\x9F\x93\x99' - BOOKS = b'\xF0\x9F\x93\x9A' - NAME_BADGE = b'\xF0\x9F\x93\x9B' - SCROLL = b'\xF0\x9F\x93\x9C' - MEMO = b'\xF0\x9F\x93\x9D' - TELEPHONE_RECEIVER = b'\xF0\x9F\x93\x9E' - PAGER = b'\xF0\x9F\x93\x9F' - FAX_MACHINE = b'\xF0\x9F\x93\xA0' - SATELLITE_ANTENNA = b'\xF0\x9F\x93\xA1' - PUBLIC_ADDRESS_LOUDSPEAKER = b'\xF0\x9F\x93\xA2' - CHEERING_MEGAPHONE = b'\xF0\x9F\x93\xA3' - OUTBOX_TRAY = b'\xF0\x9F\x93\xA4' - INBOX_TRAY = b'\xF0\x9F\x93\xA5' - PACKAGE = b'\xF0\x9F\x93\xA6' - E_MAIL_SYMBOL = b'\xF0\x9F\x93\xA7' - INCOMING_ENVELOPE = b'\xF0\x9F\x93\xA8' - ENVELOPE_WITH_DOWNWARDS_ARROW_ABOVE = b'\xF0\x9F\x93\xA9' - CLOSED_MAILBOX_WITH_LOWERED_FLAG = b'\xF0\x9F\x93\xAA' - CLOSED_MAILBOX_WITH_RAISED_FLAG = b'\xF0\x9F\x93\xAB' - POSTBOX = b'\xF0\x9F\x93\xAE' - NEWSPAPER = b'\xF0\x9F\x93\xB0' - MOBILE_PHONE = b'\xF0\x9F\x93\xB1' - MOBILE_PHONE_WITH_RIGHTWARDS_ARROW_AT_LEFT = b'\xF0\x9F\x93\xB2' - VIBRATION_MODE = b'\xF0\x9F\x93\xB3' - MOBILE_PHONE_OFF = b'\xF0\x9F\x93\xB4' - ANTENNA_WITH_BARS = b'\xF0\x9F\x93\xB6' - CAMERA = b'\xF0\x9F\x93\xB7' - VIDEO_CAMERA = b'\xF0\x9F\x93\xB9' - TELEVISION = b'\xF0\x9F\x93\xBA' - RADIO = b'\xF0\x9F\x93\xBB' - VIDEOCASSETTE = b'\xF0\x9F\x93\xBC' - CLOCKWISE_DOWNWARDS_AND_UPWARDS_OPEN_CIRCLE_ARROWS = b'\xF0\x9F\x94\x83' - SPEAKER_WITH_THREE_SOUND_WAVES = b'\xF0\x9F\x94\x8A' - BATTERY = b'\xF0\x9F\x94\x8B' - ELECTRIC_PLUG = b'\xF0\x9F\x94\x8C' - LEFT_POINTING_MAGNIFYING_GLASS = b'\xF0\x9F\x94\x8D' - RIGHT_POINTING_MAGNIFYING_GLASS = b'\xF0\x9F\x94\x8E' - LOCK_WITH_INK_PEN = b'\xF0\x9F\x94\x8F' - CLOSED_LOCK_WITH_KEY = b'\xF0\x9F\x94\x90' - KEY = b'\xF0\x9F\x94\x91' - LOCK = b'\xF0\x9F\x94\x92' - OPEN_LOCK = b'\xF0\x9F\x94\x93' - BELL = b'\xF0\x9F\x94\x94' - BOOKMARK = b'\xF0\x9F\x94\x96' - LINK_SYMBOL = b'\xF0\x9F\x94\x97' - RADIO_BUTTON = b'\xF0\x9F\x94\x98' - BACK_WITH_LEFTWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x99' - END_WITH_LEFTWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x9A' - ON_WITH_EXCLAMATION_MARK_WITH_LEFT_RIGHT_ARROW_ABOVE = b'\xF0\x9F\x94\x9B' - SOON_WITH_RIGHTWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x9C' - TOP_WITH_UPWARDS_ARROW_ABOVE = b'\xF0\x9F\x94\x9D' - NO_ONE_UNDER_EIGHTEEN_SYMBOL = b'\xF0\x9F\x94\x9E' - KEYCAP_TEN = b'\xF0\x9F\x94\x9F' - INPUT_SYMBOL_FOR_LATIN_CAPITAL_LETTERS = b'\xF0\x9F\x94\xA0' - INPUT_SYMBOL_FOR_LATIN_SMALL_LETTERS = b'\xF0\x9F\x94\xA1' - INPUT_SYMBOL_FOR_NUMBERS = b'\xF0\x9F\x94\xA2' - INPUT_SYMBOL_FOR_SYMBOLS = b'\xF0\x9F\x94\xA3' - INPUT_SYMBOL_FOR_LATIN_LETTERS = b'\xF0\x9F\x94\xA4' - FIRE = b'\xF0\x9F\x94\xA5' - ELECTRIC_TORCH = b'\xF0\x9F\x94\xA6' - WRENCH = b'\xF0\x9F\x94\xA7' - HAMMER = b'\xF0\x9F\x94\xA8' - NUT_AND_BOLT = b'\xF0\x9F\x94\xA9' - HOCHO = b'\xF0\x9F\x94\xAA' - PISTOL = b'\xF0\x9F\x94\xAB' - CRYSTAL_BALL = b'\xF0\x9F\x94\xAE' - SIX_POINTED_STAR_WITH_MIDDLE_DOT = b'\xF0\x9F\x94\xAF' - JAPANESE_SYMBOL_FOR_BEGINNER = b'\xF0\x9F\x94\xB0' - TRIDENT_EMBLEM = b'\xF0\x9F\x94\xB1' - BLACK_SQUARE_BUTTON = b'\xF0\x9F\x94\xB2' - WHITE_SQUARE_BUTTON = b'\xF0\x9F\x94\xB3' - LARGE_RED_CIRCLE = b'\xF0\x9F\x94\xB4' - LARGE_BLUE_CIRCLE = b'\xF0\x9F\x94\xB5' - LARGE_ORANGE_DIAMOND = b'\xF0\x9F\x94\xB6' - LARGE_BLUE_DIAMOND = b'\xF0\x9F\x94\xB7' - SMALL_ORANGE_DIAMOND = b'\xF0\x9F\x94\xB8' - SMALL_BLUE_DIAMOND = b'\xF0\x9F\x94\xB9' - UP_POINTING_RED_TRIANGLE = b'\xF0\x9F\x94\xBA' - DOWN_POINTING_RED_TRIANGLE = b'\xF0\x9F\x94\xBB' - UP_POINTING_SMALL_RED_TRIANGLE = b'\xF0\x9F\x94\xBC' - DOWN_POINTING_SMALL_RED_TRIANGLE = b'\xF0\x9F\x94\xBD' - CLOCK_FACE_ONE_OCLOCK = b'\xF0\x9F\x95\x90' - CLOCK_FACE_TWO_OCLOCK = b'\xF0\x9F\x95\x91' - CLOCK_FACE_THREE_OCLOCK = b'\xF0\x9F\x95\x92' - CLOCK_FACE_FOUR_OCLOCK = b'\xF0\x9F\x95\x93' - CLOCK_FACE_FIVE_OCLOCK = b'\xF0\x9F\x95\x94' - CLOCK_FACE_SIX_OCLOCK = b'\xF0\x9F\x95\x95' - CLOCK_FACE_SEVEN_OCLOCK = b'\xF0\x9F\x95\x96' - CLOCK_FACE_EIGHT_OCLOCK = b'\xF0\x9F\x95\x97' - CLOCK_FACE_NINE_OCLOCK = b'\xF0\x9F\x95\x98' - CLOCK_FACE_TEN_OCLOCK = b'\xF0\x9F\x95\x99' - CLOCK_FACE_ELEVEN_OCLOCK = b'\xF0\x9F\x95\x9A' - CLOCK_FACE_TWELVE_OCLOCK = b'\xF0\x9F\x95\x9B' - MOUNT_FUJI = b'\xF0\x9F\x97\xBB' - TOKYO_TOWER = b'\xF0\x9F\x97\xBC' - STATUE_OF_LIBERTY = b'\xF0\x9F\x97\xBD' - SILHOUETTE_OF_JAPAN = b'\xF0\x9F\x97\xBE' - MOYAI = b'\xF0\x9F\x97\xBF' - GRINNING_FACE = b'\xF0\x9F\x98\x80' - SMILING_FACE_WITH_HALO = b'\xF0\x9F\x98\x87' - SMILING_FACE_WITH_HORNS = b'\xF0\x9F\x98\x88' - SMILING_FACE_WITH_SUNGLASSES = b'\xF0\x9F\x98\x8E' - NEUTRAL_FACE = b'\xF0\x9F\x98\x90' - EXPRESSIONLESS_FACE = b'\xF0\x9F\x98\x91' - CONFUSED_FACE = b'\xF0\x9F\x98\x95' - KISSING_FACE = b'\xF0\x9F\x98\x97' - KISSING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x99' - FACE_WITH_STUCK_OUT_TONGUE = b'\xF0\x9F\x98\x9B' - WORRIED_FACE = b'\xF0\x9F\x98\x9F' - FROWNING_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\xA6' - ANGUISHED_FACE = b'\xF0\x9F\x98\xA7' - GRIMACING_FACE = b'\xF0\x9F\x98\xAC' - FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\xAE' - HUSHED_FACE = b'\xF0\x9F\x98\xAF' - SLEEPING_FACE = b'\xF0\x9F\x98\xB4' - FACE_WITHOUT_MOUTH = b'\xF0\x9F\x98\xB6' - HELICOPTER = b'\xF0\x9F\x9A\x81' - STEAM_LOCOMOTIVE = b'\xF0\x9F\x9A\x82' - TRAIN = b'\xF0\x9F\x9A\x86' - LIGHT_RAIL = b'\xF0\x9F\x9A\x88' - TRAM = b'\xF0\x9F\x9A\x8A' - ONCOMING_BUS = b'\xF0\x9F\x9A\x8D' - TROLLEYBUS = b'\xF0\x9F\x9A\x8E' - MINIBUS = b'\xF0\x9F\x9A\x90' - ONCOMING_POLICE_CAR = b'\xF0\x9F\x9A\x94' - ONCOMING_TAXI = b'\xF0\x9F\x9A\x96' - ONCOMING_AUTOMOBILE = b'\xF0\x9F\x9A\x98' - ARTICULATED_LORRY = b'\xF0\x9F\x9A\x9B' - TRACTOR = b'\xF0\x9F\x9A\x9C' - MONORAIL = b'\xF0\x9F\x9A\x9D' - MOUNTAIN_RAILWAY = b'\xF0\x9F\x9A\x9E' - SUSPENSION_RAILWAY = b'\xF0\x9F\x9A\x9F' - MOUNTAIN_CABLEWAY = b'\xF0\x9F\x9A\xA0' - AERIAL_TRAMWAY = b'\xF0\x9F\x9A\xA1' - ROWBOAT = b'\xF0\x9F\x9A\xA3' - VERTICAL_TRAFFIC_LIGHT = b'\xF0\x9F\x9A\xA6' - PUT_LITTER_IN_ITS_PLACE_SYMBOL = b'\xF0\x9F\x9A\xAE' - DO_NOT_LITTER_SYMBOL = b'\xF0\x9F\x9A\xAF' - POTABLE_WATER_SYMBOL = b'\xF0\x9F\x9A\xB0' - NON_POTABLE_WATER_SYMBOL = b'\xF0\x9F\x9A\xB1' - NO_BICYCLES = b'\xF0\x9F\x9A\xB3' - BICYCLIST = b'\xF0\x9F\x9A\xB4' - MOUNTAIN_BICYCLIST = b'\xF0\x9F\x9A\xB5' - NO_PEDESTRIANS = b'\xF0\x9F\x9A\xB7' - CHILDREN_CROSSING = b'\xF0\x9F\x9A\xB8' - SHOWER = b'\xF0\x9F\x9A\xBF' - BATHTUB = b'\xF0\x9F\x9B\x81' - PASSPORT_CONTROL = b'\xF0\x9F\x9B\x82' - CUSTOMS = b'\xF0\x9F\x9B\x83' - BAGGAGE_CLAIM = b'\xF0\x9F\x9B\x84' - LEFT_LUGGAGE = b'\xF0\x9F\x9B\x85' - EARTH_GLOBE_EUROPE_AFRICA = b'\xF0\x9F\x8C\x8D' - EARTH_GLOBE_AMERICAS = b'\xF0\x9F\x8C\x8E' - GLOBE_WITH_MERIDIANS = b'\xF0\x9F\x8C\x90' - WAXING_CRESCENT_MOON_SYMBOL = b'\xF0\x9F\x8C\x92' - WANING_GIBBOUS_MOON_SYMBOL = b'\xF0\x9F\x8C\x96' - LAST_QUARTER_MOON_SYMBOL = b'\xF0\x9F\x8C\x97' - WANING_CRESCENT_MOON_SYMBOL = b'\xF0\x9F\x8C\x98' - NEW_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9A' - LAST_QUARTER_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9C' - FULL_MOON_WITH_FACE = b'\xF0\x9F\x8C\x9D' - SUN_WITH_FACE = b'\xF0\x9F\x8C\x9E' - EVERGREEN_TREE = b'\xF0\x9F\x8C\xB2' - DECIDUOUS_TREE = b'\xF0\x9F\x8C\xB3' - LEMON = b'\xF0\x9F\x8D\x8B' - PEAR = b'\xF0\x9F\x8D\x90' - BABY_BOTTLE = b'\xF0\x9F\x8D\xBC' - HORSE_RACING = b'\xF0\x9F\x8F\x87' - RUGBY_FOOTBALL = b'\xF0\x9F\x8F\x89' - EUROPEAN_POST_OFFICE = b'\xF0\x9F\x8F\xA4' - RAT = b'\xF0\x9F\x90\x80' - MOUSE = b'\xF0\x9F\x90\x81' - OX = b'\xF0\x9F\x90\x82' - WATER_BUFFALO = b'\xF0\x9F\x90\x83' - COW = b'\xF0\x9F\x90\x84' - TIGER = b'\xF0\x9F\x90\x85' - LEOPARD = b'\xF0\x9F\x90\x86' - RABBIT = b'\xF0\x9F\x90\x87' - CAT = b'\xF0\x9F\x90\x88' - DRAGON = b'\xF0\x9F\x90\x89' - CROCODILE = b'\xF0\x9F\x90\x8A' - WHALE = b'\xF0\x9F\x90\x8B' - RAM = b'\xF0\x9F\x90\x8F' - GOAT = b'\xF0\x9F\x90\x90' - ROOSTER = b'\xF0\x9F\x90\x93' - DOG = b'\xF0\x9F\x90\x95' - PIG = b'\xF0\x9F\x90\x96' - DROMEDARY_CAMEL = b'\xF0\x9F\x90\xAA' - BUSTS_IN_SILHOUETTE = b'\xF0\x9F\x91\xA5' - TWO_MEN_HOLDING_HANDS = b'\xF0\x9F\x91\xAC' - TWO_WOMEN_HOLDING_HANDS = b'\xF0\x9F\x91\xAD' - THOUGHT_BALLOON = b'\xF0\x9F\x92\xAD' - BANKNOTE_WITH_EURO_SIGN = b'\xF0\x9F\x92\xB6' - BANKNOTE_WITH_POUND_SIGN = b'\xF0\x9F\x92\xB7' - OPEN_MAILBOX_WITH_RAISED_FLAG = b'\xF0\x9F\x93\xAC' - OPEN_MAILBOX_WITH_LOWERED_FLAG = b'\xF0\x9F\x93\xAD' - POSTAL_HORN = b'\xF0\x9F\x93\xAF' - NO_MOBILE_PHONES = b'\xF0\x9F\x93\xB5' - TWISTED_RIGHTWARDS_ARROWS = b'\xF0\x9F\x94\x80' - CLOCKWISE_RIGHTWARDS_AND_LEFTWARDS_OPEN_CIRCLE_ARROWS = b'\xF0\x9F\x94\x81' - CLOCKWISE_RIGHTWARDS_AND_LEFTWARDS_OPEN_CIRCLE_ARROWS_WITH_CIRCLED_ONE_OVERLAY = b'\xF0\x9F\x94\x82' - ANTICLOCKWISE_DOWNWARDS_AND_UPWARDS_OPEN_CIRCLE_ARROWS = b'\xF0\x9F\x94\x84' - LOW_BRIGHTNESS_SYMBOL = b'\xF0\x9F\x94\x85' - HIGH_BRIGHTNESS_SYMBOL = b'\xF0\x9F\x94\x86' - SPEAKER_WITH_CANCELLATION_STROKE = b'\xF0\x9F\x94\x87' - SPEAKER_WITH_ONE_SOUND_WAVE = b'\xF0\x9F\x94\x89' - BELL_WITH_CANCELLATION_STROKE = b'\xF0\x9F\x94\x95' - MICROSCOPE = b'\xF0\x9F\x94\xAC' - TELESCOPE = b'\xF0\x9F\x94\xAD' - CLOCK_FACE_ONE_THIRTY = b'\xF0\x9F\x95\x9C' - CLOCK_FACE_TWO_THIRTY = b'\xF0\x9F\x95\x9D' - CLOCK_FACE_THREE_THIRTY = b'\xF0\x9F\x95\x9E' - CLOCK_FACE_FOUR_THIRTY = b'\xF0\x9F\x95\x9F' - CLOCK_FACE_FIVE_THIRTY = b'\xF0\x9F\x95\xA0' - CLOCK_FACE_SIX_THIRTY = b'\xF0\x9F\x95\xA1' - CLOCK_FACE_SEVEN_THIRTY = b'\xF0\x9F\x95\xA2' - CLOCK_FACE_EIGHT_THIRTY = b'\xF0\x9F\x95\xA3' - CLOCK_FACE_NINE_THIRTY = b'\xF0\x9F\x95\xA4' - CLOCK_FACE_TEN_THIRTY = b'\xF0\x9F\x95\xA5' - CLOCK_FACE_ELEVEN_THIRTY = b'\xF0\x9F\x95\xA6' - CLOCK_FACE_TWELVE_THIRTY = b'\xF0\x9F\x95\xA7' diff --git a/lib/telegram/error.py b/lib/telegram/error.py deleted file mode 100644 index f8b7e21f..00000000 --- a/lib/telegram/error.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Error""" - -import re - - -class TelegramError(Exception): - """This object represents a Telegram Error.""" - - def __init__(self, message): - """ - Returns: - str: - """ - super(TelegramError, self).__init__() - - api_error = re.match(r'^Error: (?P.*)', message) - if api_error: - self.message = api_error.group('message').capitalize() - else: - self.message = message - - def __str__(self): - return '%s' % (self.message) diff --git a/lib/telegram/file.py b/lib/telegram/file.py deleted file mode 100644 index 9474d2ff..00000000 --- a/lib/telegram/file.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram File""" - -from os.path import basename - -from telegram import TelegramObject -from telegram.utils.request import download as _download - - -class File(TelegramObject): - - """This object represents a Telegram File. - - Attributes: - file_id (str): - file_size (str): - file_path (str): - - Args: - file_id (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - file_size (Optional[int]): - file_path (Optional[str]): - """ - - def __init__(self, - file_id, - **kwargs): - # Required - self.file_id = str(file_id) - # Optionals - self.file_size = int(kwargs.get('file_size', 0)) - self.file_path = str(kwargs.get('file_path', '')) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.File: - """ - if not data: - return None - - return File(**data) - - def download(self, - custom_path=None): - """ - Args: - custom_path (str): - """ - url = self.file_path - - if custom_path: - filename = basename(custom_path) - else: - filename = basename(url) - - _download(url, filename) diff --git a/lib/telegram/forcereply.py b/lib/telegram/forcereply.py deleted file mode 100644 index 28057bee..00000000 --- a/lib/telegram/forcereply.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram ForceReply""" - -from telegram import ReplyMarkup - - -class ForceReply(ReplyMarkup): - """This object represents a Telegram ForceReply. - - Attributes: - force_reply (bool): - selective (bool): - - Args: - force_reply (bool): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - selective (Optional[bool]): - """ - - def __init__(self, - force_reply=True, - **kwargs): - # Required - self.force_reply = bool(force_reply) - # Optionals - self.selective = bool(kwargs.get('selective', False)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.ForceReply: - """ - if not data: - return None - - return ForceReply(**data) diff --git a/lib/telegram/groupchat.py b/lib/telegram/groupchat.py deleted file mode 100644 index f4d59723..00000000 --- a/lib/telegram/groupchat.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=C0103,W0622 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram GroupChat""" - -from telegram import TelegramObject - - -class GroupChat(TelegramObject): - """This object represents a Telegram GroupChat. - - Attributes: - id (int): - title (str): - type (str): - - Args: - id (int): - title (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - type (Optional[str]): - """ - - def __init__(self, - id, - title, - **kwargs): - # Required - self.id = int(id) - self.title = title - # Optionals - self.type = kwargs.get('type', '') - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.GroupChat: - """ - if not data: - return None - - return GroupChat(**data) diff --git a/lib/telegram/inputfile.py b/lib/telegram/inputfile.py deleted file mode 100644 index 0946f972..00000000 --- a/lib/telegram/inputfile.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=W0622,E0611 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram InputFile""" - -try: - from email.generator import _make_boundary as choose_boundary - from urllib.request import urlopen - from io import BufferedReader as file -except ImportError: - from mimetools import choose_boundary - from urllib2 import urlopen -import mimetypes -import os -import sys -import imghdr - -from telegram import TelegramError - -DEFAULT_MIME_TYPE = 'application/octet-stream' -USER_AGENT = 'Python Telegram Bot' \ - ' (https://github.com/leandrotoledo/python-telegram-bot)' - - -class InputFile(object): - """This object represents a Telegram InputFile.""" - - def __init__(self, - data): - self.data = data - self.boundary = choose_boundary() - - if 'audio' in data: - self.input_name = 'audio' - self.input_file = data.pop('audio') - if 'document' in data: - self.input_name = 'document' - self.input_file = data.pop('document') - if 'photo' in data: - self.input_name = 'photo' - self.input_file = data.pop('photo') - if 'sticker' in data: - self.input_name = 'sticker' - self.input_file = data.pop('sticker') - if 'video' in data: - self.input_name = 'video' - self.input_file = data.pop('video') - if 'voice' in data: - self.input_name = 'voice' - self.input_file = data.pop('voice') - if 'certificate' in data: - self.input_name = 'certificate' - self.input_file = data.pop('certificate') - - if isinstance(self.input_file, file): - self.input_file_content = self.input_file.read() - if 'filename' in data: - self.filename = self.data.pop('filename') - else: - self.filename = os.path.basename(self.input_file.name) - self.mimetype = mimetypes.guess_type(self.filename)[0] or \ - DEFAULT_MIME_TYPE - - if 'http' in self.input_file: - self.input_file_content = urlopen(self.input_file).read() - self.mimetype = InputFile.is_image(self.input_file_content) - self.filename = self.mimetype.replace('/', '.') - - @property - def headers(self): - """ - Returns: - str: - """ - return {'User-agent': USER_AGENT, - 'Content-type': self.content_type} - - @property - def content_type(self): - """ - Returns: - str: - """ - return 'multipart/form-data; boundary=%s' % self.boundary - - def to_form(self): - """ - Returns: - str: - """ - form = [] - form_boundary = '--' + self.boundary - - # Add data fields - for name, value in self.data.items(): - form.extend([ - form_boundary, - 'Content-Disposition: form-data; name="%s"' % name, - '', - str(value) - ]) - - # Add input_file to upload - form.extend([ - form_boundary, - 'Content-Disposition: form-data; name="%s"; filename="%s"' % ( - self.input_name, self.filename - ), - 'Content-Type: %s' % self.mimetype, - '', - self.input_file_content - ]) - - form.append('--' + self.boundary + '--') - form.append('') - - return InputFile._parse(form) - - @staticmethod - def _parse(form): - """ - Returns: - str: - """ - if sys.version_info > (3,): - # on Python 3 form needs to be byte encoded - encoded_form = [] - for item in form: - try: - encoded_form.append(item.encode()) - except AttributeError: - encoded_form.append(item) - - return b'\r\n'.join(encoded_form) - return '\r\n'.join(form) - - @staticmethod - def is_image(stream): - """Check if the content file is an image by analyzing its headers. - - Args: - stream (str): A str representing the content of a file. - - Returns: - str: The str mimetype of an image. - """ - image = imghdr.what(None, stream) - if image: - return 'image/%s' % image - - raise TelegramError('Could not parse file content') - - @staticmethod - def is_inputfile(data): - """Check if the request is a file request. - - Args: - data (str): A dict of (str, unicode) key/value pairs - - Returns: - bool - """ - if data: - file_types = ['audio', 'document', 'photo', 'sticker', 'video', - 'voice', 'certificate'] - file_type = [i for i in list(data.keys()) if i in file_types] - - if file_type: - file_content = data[file_type[0]] - - if file_type[0] == 'photo' or file_type[0] == 'document': - return isinstance(file_content, file) or \ - str(file_content).startswith('http') - - return isinstance(file_content, file) - - return False diff --git a/lib/telegram/location.py b/lib/telegram/location.py deleted file mode 100644 index 66c28fc5..00000000 --- a/lib/telegram/location.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Location""" - -from telegram import TelegramObject - - -class Location(TelegramObject): - """This object represents a Telegram Sticker. - - Attributes: - longitude (float): - latitude (float): - - Args: - longitude (float): - latitude (float): - """ - - def __init__(self, - longitude, - latitude): - # Required - self.longitude = float(longitude) - self.latitude = float(latitude) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Location: - """ - if not data: - return None - - return Location(**data) diff --git a/lib/telegram/message.py b/lib/telegram/message.py deleted file mode 100644 index e77f3ea4..00000000 --- a/lib/telegram/message.py +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0902,R0912,R0913 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Message""" - -from datetime import datetime -from time import mktime - -from telegram import (Audio, Contact, Document, GroupChat, Location, PhotoSize, - Sticker, TelegramObject, User, Video, Voice) - - -class Message(TelegramObject): - """This object represents a Telegram Message. - - Note: - * In Python `from` is a reserved word, use `from_user` instead. - - Attributes: - message_id (int): - from_user (:class:`telegram.User`): - date (:class:`datetime.datetime`): - forward_from (:class:`telegram.User`): - forward_date (:class:`datetime.datetime`): - reply_to_message (:class:`telegram.Message`): - text (str): - audio (:class:`telegram.Audio`): - document (:class:`telegram.Document`): - photo (List[:class:`telegram.PhotoSize`]): - sticker (:class:`telegram.Sticker`): - video (:class:`telegram.Video`): - voice (:class:`telegram.Voice`): - caption (str): - contact (:class:`telegram.Contact`): - location (:class:`telegram.Location`): - new_chat_participant (:class:`telegram.User`): - left_chat_participant (:class:`telegram.User`): - new_chat_title (str): - new_chat_photo (List[:class:`telegram.PhotoSize`]): - delete_chat_photo (bool): - group_chat_created (bool): - - Args: - message_id (int): - from_user (:class:`telegram.User`): - date (:class:`datetime.datetime`): - chat (:class:`telegram.User` or :class:`telegram.GroupChat`): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - forward_from (Optional[:class:`telegram.User`]): - forward_date (Optional[:class:`datetime.datetime`]): - reply_to_message (Optional[:class:`telegram.Message`]): - text (Optional[str]): - audio (Optional[:class:`telegram.Audio`]): - document (Optional[:class:`telegram.Document`]): - photo (Optional[List[:class:`telegram.PhotoSize`]]): - sticker (Optional[:class:`telegram.Sticker`]): - video (Optional[:class:`telegram.Video`]): - voice (Optional[:class:`telegram.Voice`]): - caption (Optional[str]): - contact (Optional[:class:`telegram.Contact`]): - location (Optional[:class:`telegram.Location`]): - new_chat_participant (Optional[:class:`telegram.User`]): - left_chat_participant (Optional[:class:`telegram.User`]): - new_chat_title (Optional[str]): - new_chat_photo (Optional[List[:class:`telegram.PhotoSize`]): - delete_chat_photo (Optional[bool]): - group_chat_created (Optional[bool]): - """ - - def __init__(self, - message_id, - from_user, - date, - chat, - **kwargs): - # Required - self.message_id = int(message_id) - self.from_user = from_user - self.date = date - self.chat = chat - # Optionals - self.forward_from = kwargs.get('forward_from') - self.forward_date = kwargs.get('forward_date') - self.reply_to_message = kwargs.get('reply_to_message') - self.text = kwargs.get('text', '') - self.audio = kwargs.get('audio') - self.document = kwargs.get('document') - self.photo = kwargs.get('photo') - self.sticker = kwargs.get('sticker') - self.video = kwargs.get('video') - self.voice = kwargs.get('voice') - self.caption = kwargs.get('caption', '') - self.contact = kwargs.get('contact') - self.location = kwargs.get('location') - self.new_chat_participant = kwargs.get('new_chat_participant') - self.left_chat_participant = kwargs.get('left_chat_participant') - self.new_chat_title = kwargs.get('new_chat_title', '') - self.new_chat_photo = kwargs.get('new_chat_photo') - self.delete_chat_photo = bool(kwargs.get('delete_chat_photo', False)) - self.group_chat_created = bool(kwargs.get('group_chat_created', False)) - - @property - def chat_id(self): - """int: Short for :attr:`Message.chat.id`""" - return self.chat.id - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Message: - """ - if not data: - return None - - data['from_user'] = User.de_json(data['from']) - data['date'] = datetime.fromtimestamp(data['date']) - if 'first_name' in data.get('chat', ''): - data['chat'] = User.de_json(data.get('chat')) - elif 'title' in data.get('chat', ''): - data['chat'] = GroupChat.de_json(data.get('chat')) - data['forward_from'] = \ - User.de_json(data.get('forward_from')) - data['forward_date'] = \ - Message._fromtimestamp(data.get('forward_date')) - data['reply_to_message'] = \ - Message.de_json(data.get('reply_to_message')) - data['audio'] = \ - Audio.de_json(data.get('audio')) - data['document'] = \ - Document.de_json(data.get('document')) - data['photo'] = \ - PhotoSize.de_list(data.get('photo')) - data['sticker'] = \ - Sticker.de_json(data.get('sticker')) - data['video'] = \ - Video.de_json(data.get('video')) - data['voice'] = \ - Voice.de_json(data.get('voice')) - data['contact'] = \ - Contact.de_json(data.get('contact')) - data['location'] = \ - Location.de_json(data.get('location')) - data['new_chat_participant'] = \ - User.de_json(data.get('new_chat_participant')) - data['left_chat_participant'] = \ - User.de_json(data.get('left_chat_participant')) - data['new_chat_photo'] = \ - PhotoSize.de_list(data.get('new_chat_photo')) - - return Message(**data) - - def __getitem__(self, item): - if item in self.__dict__.keys(): - return self.__dict__[item] - elif item == 'chat_id': - return self.chat.id - - def to_dict(self): - """ - Returns: - dict: - """ - data = super(Message, self).to_dict() - - # Required - data['from'] = data.pop('from_user') - data['date'] = self._totimestamp(self.date) - # Optionals - if self.forward_date: - data['forward_date'] = self._totimestamp(self.forward_date) - if self.photo: - data['photo'] = [p.to_dict() for p in self.photo] - if self.new_chat_photo: - data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo] - - return data - - @staticmethod - def _fromtimestamp(unixtime): - """ - Args: - unixtime (int): - - Returns: - datetime.datetime: - """ - if not unixtime: - return None - - return datetime.fromtimestamp(unixtime) - - @staticmethod - def _totimestamp(dt_obj): - """ - Args: - dt_obj (:class:`datetime.datetime`): - - Returns: - int: - """ - if not dt_obj: - return None - - try: - # Python 3.3+ - return int(dt_obj.timestamp()) - except AttributeError: - # Python 3 (< 3.3) and Python 2 - return int(mktime(dt_obj.timetuple())) diff --git a/lib/telegram/nullhandler.py b/lib/telegram/nullhandler.py deleted file mode 100644 index 50bd370e..00000000 --- a/lib/telegram/nullhandler.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a logging NullHandler""" - -import logging - - -class NullHandler(logging.Handler): - """This object represents a logging NullHandler.""" - - def emit(self, record): - """ - Args: - record (str): - """ - pass diff --git a/lib/telegram/parsemode.py b/lib/telegram/parsemode.py deleted file mode 100644 index 897693ac..00000000 --- a/lib/telegram/parsemode.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Message Parse Modes""" - - -class ParseMode(object): - """This object represents a Telegram Message Parse Modes.""" - - MARKDOWN = 'Markdown' diff --git a/lib/telegram/photosize.py b/lib/telegram/photosize.py deleted file mode 100644 index adc47649..00000000 --- a/lib/telegram/photosize.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram PhotoSize""" - -from telegram import TelegramObject - - -class PhotoSize(TelegramObject): - """This object represents a Telegram PhotoSize. - - Attributes: - file_id (str): - width (int): - height (int): - file_size (int): - - Args: - file_id (str): - width (int): - height (int): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - file_size (Optional[int]): - """ - - def __init__(self, - file_id, - width, - height, - **kwargs): - # Required - self.file_id = file_id - self.width = int(width) - self.height = int(height) - # Optionals - self.file_size = int(kwargs.get('file_size', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.PhotoSize: - """ - if not data: - return None - - return PhotoSize(**data) - - @staticmethod - def de_list(data): - """ - Args: - data (list): - - Returns: - List: - """ - if not data: - return [] - - photos = list() - for photo in data: - photos.append(PhotoSize.de_json(photo)) - - return photos diff --git a/lib/telegram/replykeyboardhide.py b/lib/telegram/replykeyboardhide.py deleted file mode 100644 index f2cb80bb..00000000 --- a/lib/telegram/replykeyboardhide.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram -ReplyKeyboardHide""" - -from telegram import ReplyMarkup - - -class ReplyKeyboardHide(ReplyMarkup): - """This object represents a Telegram ReplyKeyboardHide. - - Attributes: - hide_keyboard (bool): - selective (bool): - - Args: - hide_keyboard (bool): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - selective (Optional[bool]): - """ - - def __init__(self, - hide_keyboard=True, - **kwargs): - # Required - self.hide_keyboard = bool(hide_keyboard) - # Optionals - self.selective = bool(kwargs.get('selective', False)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.ReplyKeyboardHide: - """ - if not data: - return None - - return ReplyKeyboardHide(**data) diff --git a/lib/telegram/replykeyboardmarkup.py b/lib/telegram/replykeyboardmarkup.py deleted file mode 100644 index 39912ea3..00000000 --- a/lib/telegram/replykeyboardmarkup.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram -ReplyKeyboardMarkup""" - -from telegram import ReplyMarkup - - -class ReplyKeyboardMarkup(ReplyMarkup): - """This object represents a Telegram ReplyKeyboardMarkup. - - Attributes: - keyboard (List[List[str]]): - resize_keyboard (bool): - one_time_keyboard (bool): - selective (bool): - - Args: - keyboard (List[List[str]]): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - resize_keyboard (Optional[bool]): - one_time_keyboard (Optional[bool]): - selective (Optional[bool]): - """ - - def __init__(self, - keyboard, - **kwargs): - # Required - self.keyboard = keyboard - # Optionals - self.resize_keyboard = bool(kwargs.get('resize_keyboard', False)) - self.one_time_keyboard = bool(kwargs.get('one_time_keyboard', False)) - self.selective = bool(kwargs.get('selective', False)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.ReplyKeyboardMarkup: - """ - if not data: - return None - - return ReplyKeyboardMarkup(**data) diff --git a/lib/telegram/replymarkup.py b/lib/telegram/replymarkup.py deleted file mode 100644 index 1d086c89..00000000 --- a/lib/telegram/replymarkup.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""Base class for Telegram ReplyMarkup Objects""" - -from telegram import TelegramObject - - -class ReplyMarkup(TelegramObject): - """Base class for Telegram ReplyMarkup Objects""" - - @staticmethod - def de_json(data): - pass diff --git a/lib/telegram/sticker.py b/lib/telegram/sticker.py deleted file mode 100644 index 06089e35..00000000 --- a/lib/telegram/sticker.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Sticker""" - -from telegram import PhotoSize, TelegramObject - - -class Sticker(TelegramObject): - """This object represents a Telegram Sticker. - - Attributes: - file_id (str): - width (int): - height (int): - thumb (:class:`telegram.PhotoSize`): - file_size (int): - - Args: - file_id (str): - width (int): - height (int): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - thumb (Optional[:class:`telegram.PhotoSize`]): - file_size (Optional[int]): - """ - - def __init__(self, - file_id, - width, - height, - **kwargs): - # Required - self.file_id = str(file_id) - self.width = int(width) - self.height = int(height) - # Optionals - self.thumb = kwargs.get('thumb') - self.file_size = int(kwargs.get('file_size', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Sticker: - """ - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb')) - - return Sticker(**data) diff --git a/lib/telegram/update.py b/lib/telegram/update.py deleted file mode 100644 index 3bccc7d2..00000000 --- a/lib/telegram/update.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Update""" - -from telegram import Message, TelegramObject - - -class Update(TelegramObject): - """This object represents a Telegram Update. - - Attributes: - update_id (int): - message (:class:`telegram.Message`): - - Args: - update_id (int): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - message (Optional[:class:`telegram.Message`]): - """ - def __init__(self, - update_id, - **kwargs): - # Required - self.update_id = int(update_id) - # Optionals - self.message = kwargs.get('message') - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Update: - """ - if not data: - return None - - data['message'] = Message.de_json(data['message']) - - return Update(**data) diff --git a/lib/telegram/user.py b/lib/telegram/user.py deleted file mode 100644 index 4fa88295..00000000 --- a/lib/telegram/user.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=C0103,W0622 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram User""" - -from telegram import TelegramObject - - -class User(TelegramObject): - """This object represents a Telegram User. - - Attributes: - id (int): - first_name (str): - last_name (str): - username (str): - type (str): - - Args: - id (int): - first_name (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - type (Optional[str]): - last_name (Optional[str]): - username (Optional[str]): - """ - - def __init__(self, - id, - first_name, - **kwargs): - # Required - self.id = int(id) - self.first_name = first_name - # Optionals - self.type = kwargs.get('type', '') - self.last_name = kwargs.get('last_name', '') - self.username = kwargs.get('username', '') - - @property - def name(self): - """str: """ - if self.username: - return '@%s' % self.username - if self.last_name: - return '%s %s' % (self.first_name, self.last_name) - return self.first_name - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.User: - """ - if not data: - return None - - return User(**data) diff --git a/lib/telegram/userprofilephotos.py b/lib/telegram/userprofilephotos.py deleted file mode 100644 index bce623b3..00000000 --- a/lib/telegram/userprofilephotos.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram -UserProfilePhotos""" - -from telegram import PhotoSize, TelegramObject - - -class UserProfilePhotos(TelegramObject): - """This object represents a Telegram UserProfilePhotos. - - Attributes: - total_count (int): - photos (List[List[:class:`telegram.PhotoSize`]]): - - Args: - total_count (int): - photos (List[List[:class:`telegram.PhotoSize`]]): - """ - - def __init__(self, - total_count, - photos): - # Required - self.total_count = int(total_count) - self.photos = photos - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.UserProfilePhotos: - """ - if not data: - return None - - data['photos'] = [PhotoSize.de_list(photo) for photo in data['photos']] - - return UserProfilePhotos(**data) - - def to_dict(self): - """ - Returns: - dict: - """ - data = super(UserProfilePhotos, self).to_dict() - - data['photos'] = [] - for photo in self.photos: - data['photos'].append([x.to_dict() for x in photo]) - - return data diff --git a/lib/telegram/utils/__init__.py b/lib/telegram/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/telegram/utils/request.py b/lib/telegram/utils/request.py deleted file mode 100644 index 5347f9c6..00000000 --- a/lib/telegram/utils/request.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=no-name-in-module,unused-import -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains methods to make POST and GET requests""" - -import json - -try: - from urllib.parse import urlencode - from urllib.request import urlopen, urlretrieve, Request - from urllib.error import HTTPError, URLError -except ImportError: - from urllib import urlencode, urlretrieve - from urllib2 import urlopen, Request - from urllib2 import HTTPError, URLError - -from telegram import (InputFile, TelegramError) - - -def _parse(json_data): - """Try and parse the JSON returned from Telegram and return an empty - dictionary if there is any error. - - Args: - url: - urllib.urlopen object - - Returns: - A JSON parsed as Python dict with results. - """ - data = json.loads(json_data.decode()) - - if not data.get('ok') and data.get('description'): - return data['description'] - - return data['result'] - - -def get(url): - """Request an URL. - Args: - url: - The web location we want to retrieve. - - Returns: - A JSON object. - """ - result = urlopen(url).read() - - return _parse(result) - - -def post(url, - data): - """Request an URL. - Args: - url: - The web location we want to retrieve. - data: - A dict of (str, unicode) key/value pairs. - - Returns: - A JSON object. - """ - try: - if InputFile.is_inputfile(data): - data = InputFile(data) - request = Request(url, - data=data.to_form(), - headers=data.headers) - else: - data = json.dumps(data) - request = Request(url, - data=data.encode(), - headers={'Content-Type': 'application/json'}) - - result = urlopen(request).read() - except HTTPError as error: - if error.getcode() == 403: - raise TelegramError('Unauthorized') - if error.getcode() == 502: - raise TelegramError('Bad Gateway') - - message = _parse(error.read()) - raise TelegramError(message) - - return _parse(result) - - -def download(url, - filename): - """Download a file by its URL. - Args: - url: - The web location we want to retrieve. - - filename: - The filename wihtin the path to download the file. - """ - - urlretrieve(url, filename) diff --git a/lib/telegram/video.py b/lib/telegram/video.py deleted file mode 100644 index d6930633..00000000 --- a/lib/telegram/video.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Video""" - -from telegram import PhotoSize, TelegramObject - - -class Video(TelegramObject): - """This object represents a Telegram Video. - - Attributes: - file_id (str): - width (int): - height (int): - duration (int): - thumb (:class:`telegram.PhotoSize`): - mime_type (str): - file_size (int): - - Args: - file_id (str): - width (int): - height (int): - duration (int): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - thumb (Optional[:class:`telegram.PhotoSize`]): - mime_type (Optional[str]): - file_size (Optional[int]): - """ - - def __init__(self, - file_id, - width, - height, - duration, - **kwargs): - # Required - self.file_id = str(file_id) - self.width = int(width) - self.height = int(height) - self.duration = int(duration) - # Optionals - self.thumb = kwargs.get('thumb') - self.mime_type = str(kwargs.get('mime_type', '')) - self.file_size = int(kwargs.get('file_size', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Video: - """ - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb')) - - return Video(**data) diff --git a/lib/telegram/voice.py b/lib/telegram/voice.py deleted file mode 100644 index 53dcdc44..00000000 --- a/lib/telegram/voice.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -"""This module contains a object that represents a Telegram Voice""" - -from telegram import TelegramObject - - -class Voice(TelegramObject): - """This object represents a Telegram Voice. - - Attributes: - file_id (str): - duration (int): - mime_type (str): - file_size (int): - - Args: - file_id (str): - **kwargs: Arbitrary keyword arguments. - - Keyword Args: - duration (Optional[int]): - mime_type (Optional[str]): - file_size (Optional[int]): - """ - - def __init__(self, - file_id, - **kwargs): - # Required - self.file_id = str(file_id) - # Optionals - self.duration = int(kwargs.get('duration', 0)) - self.mime_type = str(kwargs.get('mime_type', '')) - self.file_size = int(kwargs.get('file_size', 0)) - - @staticmethod - def de_json(data): - """ - Args: - data (str): - - Returns: - telegram.Voice: - """ - if not data: - return None - - return Voice(**data) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 3cd40b35..9b211239 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -35,8 +35,6 @@ import json import oauth2 as oauth import pythontwitter as twitter -import telegram - from email.mime.text import MIMEText import smtplib import email.utils @@ -291,8 +289,8 @@ def get_notification_agent_config(config_id): iftttClient = IFTTT() return iftttClient.return_config_options() elif config_id == 13: - telegramClient = TELEGRAM() - return telegramClient.return_config_options() + telegramClient = TELEGRAM() + return telegramClient.return_config_options() else: return [] else: @@ -342,8 +340,8 @@ def send_notification(config_id, subject, body): iftttClient = IFTTT() iftttClient.notify(subject=subject, message=body) elif config_id == 13: - telegramClient = TELEGRAM() - telegramClient.notify(message=body, event=subject) + telegramClient = TELEGRAM() + telegramClient.notify(message=body, event=subject) else: logger.debug(u"PlexPy Notifier :: Unknown agent id received.") else: @@ -1447,11 +1445,6 @@ class TELEGRAM(object): self.bot_token = plexpy.CONFIG.TELEGRAM_BOT_TOKEN self.chat_id = plexpy.CONFIG.TELEGRAM_CHAT_ID - self.on_play = plexpy.CONFIG.TELEGRAM_ON_PLAY - self.on_stop = plexpy.CONFIG.TELEGRAM_ON_STOP - self.on_watched = plexpy.CONFIG.TELEGRAM_ON_WATCHED - - def conf(self, options): return cherrypy.config['config'].get('Telegram', options) @@ -1459,13 +1452,28 @@ class TELEGRAM(object): if not message or not event: return - bot = telegram.Bot(self.bot_token) + http_handler = HTTPSConnection("api.telegram.org") - bot.sendMessage(chat_id=self.chat_id, text=message.encode("utf-8")) + data = {'chat_id': self.chat_id, + 'text': message.encode("utf-8")} - logger.info(u"Telegram notifications sent.") + http_handler.request("POST", + "/bot%s/%s" % (self.bot_token, "sendMessage"), + headers={'Content-type': "application/x-www-form-urlencoded"}, + body=json.dumps(data)) - return True + response = http_handler.getresponse() + request_status = response.status + + if request_status == 200: + logger.info(u"Telegram notifications sent.") + return True + elif request_status >= 400 and request_status < 500: + logger.info(u"Telegram request failed: %s" % response.reason) + return False + else: + logger.info(u"Telegram notification failed serverside.") + return False def updateLibrary(self): #For uniformity reasons not removed @@ -1479,16 +1487,16 @@ class TELEGRAM(object): self.notify('Main Screen Activate', 'Test Message') def return_config_options(self): - config_option = [{'label': 'Telegram Bot ID', + config_option = [{'label': 'Telegram Bot Token', 'value': self.bot_token, 'name': 'telegram_bot_token', - 'description': 'Your Bot ID here.', + 'description': 'Your Bot Token here.', 'input_type': 'text' }, {'label': 'Telegram Chat ID', 'value': self.chat_id, 'name': 'telegram_chat_id', - 'description': 'Your Telegram Chat or Group ID.', + 'description': 'Your Telegram Chat ID or Group ID.', 'input_type': 'text' } ] From 8ebfa20db0bd1b78c2e94098c2f0d5c3e2463325 Mon Sep 17 00:00:00 2001 From: devin Date: Wed, 28 Oct 2015 20:36:09 -0400 Subject: [PATCH 5/7] Fixed notifications, and added strings to describe bot token and chat id --- plexpy/notifiers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 9b211239..82dc281c 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1460,7 +1460,7 @@ class TELEGRAM(object): http_handler.request("POST", "/bot%s/%s" % (self.bot_token, "sendMessage"), headers={'Content-type': "application/x-www-form-urlencoded"}, - body=json.dumps(data)) + body=urlencode(data)) response = http_handler.getresponse() request_status = response.status @@ -1490,13 +1490,13 @@ class TELEGRAM(object): config_option = [{'label': 'Telegram Bot Token', 'value': self.bot_token, 'name': 'telegram_bot_token', - 'description': 'Your Bot Token here.', + 'description': 'Your bot token. Contact @BotFather on Telegram to get one.', 'input_type': 'text' }, {'label': 'Telegram Chat ID', 'value': self.chat_id, 'name': 'telegram_chat_id', - 'description': 'Your Telegram Chat ID or Group ID.', + 'description': 'Your Telegram Chat ID or Group ID. Contact @myidbot on Telegram to get an ID.', 'input_type': 'text' } ] From 6cbfacaeaeddd91d5dc98dd555c8ec60c22d8832 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sat, 31 Oct 2015 01:59:13 -0400 Subject: [PATCH 6/7] added event / suject to notifcation text --- plexpy/notifiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 82dc281c..0ef56425 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1455,7 +1455,7 @@ class TELEGRAM(object): http_handler = HTTPSConnection("api.telegram.org") data = {'chat_id': self.chat_id, - 'text': message.encode("utf-8")} + 'text': event + ': ' + message.encode("utf-8")} http_handler.request("POST", "/bot%s/%s" % (self.bot_token, "sendMessage"), From 9cff20ca160d2b3e9efcab999d8f9c1b8ed80e96 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sun, 1 Nov 2015 11:03:36 -0500 Subject: [PATCH 7/7] set text format on encode --- plexpy/notifiers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 0ef56425..1efefb29 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1455,7 +1455,7 @@ class TELEGRAM(object): http_handler = HTTPSConnection("api.telegram.org") data = {'chat_id': self.chat_id, - 'text': event + ': ' + message.encode("utf-8")} + 'text': event.encode('utf-8') + ': ' + message.encode("utf-8")} http_handler.request("POST", "/bot%s/%s" % (self.bot_token, "sendMessage"),