From 85755e32d414594407a2ad87eed3d35c2bacaf35 Mon Sep 17 00:00:00 2001 From: Shayan Heidari <119259115+shayanheidari01@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:55:43 +0330 Subject: [PATCH] Add files via upload --- rubpy/__init__.py | 9 + rubpy/client.py | 1149 ++++++++ rubpy/crypto/__init__.py | 1 + rubpy/crypto/crypto.py | 168 ++ rubpy/emoji.py | 4003 ++++++++++++++++++++++++++ rubpy/gadgets/__init__.py | 5 + rubpy/gadgets/classino.py | 26 + rubpy/gadgets/exceptions.py | 141 + rubpy/gadgets/grouping.py | 752 +++++ rubpy/gadgets/methods.py | 422 +++ rubpy/gadgets/models/__init__.py | 1 + rubpy/gadgets/models/chats.py | 134 + rubpy/gadgets/models/contacts.py | 55 + rubpy/gadgets/models/extras.py | 238 ++ rubpy/gadgets/models/groups.py | 267 ++ rubpy/gadgets/models/messages.py | 827 ++++++ rubpy/gadgets/models/stickers.py | 51 + rubpy/gadgets/models/users.py | 102 + rubpy/gadgets/thumbnail.py | 84 + rubpy/network/__init__.py | 2 + rubpy/network/connection.py | 316 ++ rubpy/network/proxies.py | 387 +++ rubpy/sessions/__init__.py | 2 + rubpy/sessions/sqliteSession.py | 67 + rubpy/sessions/stringSession.py | 39 + rubpy/structs/__init__.py | 5 + rubpy/structs/handlers.py | 72 + rubpy/structs/models.py | 155 + rubpy/structs/results.py | 27 + rubpy/structs/struct.py | 443 +++ rubpy/sync/__init__.py | 9 + rubpy/sync/client.py | 1061 +++++++ rubpy/sync/crypto/__init__.py | 1 + rubpy/sync/crypto/crypto.py | 84 + rubpy/sync/gadgets/__init__.py | 4 + rubpy/sync/gadgets/classino.py | 27 + rubpy/sync/gadgets/exceptions.py | 141 + rubpy/sync/gadgets/grouping.py | 689 +++++ rubpy/sync/gadgets/methods.py | 410 +++ rubpy/sync/gadgets/thumbnail.py | 83 + rubpy/sync/network/__init__.py | 2 + rubpy/sync/network/connection.py | 316 ++ rubpy/sync/network/proxies.py | 59 + rubpy/sync/sessions/__init__.py | 2 + rubpy/sync/sessions/sqliteSession.py | 67 + rubpy/sync/sessions/stringSession.py | 39 + rubpy/sync/structs/__init__.py | 5 + rubpy/sync/structs/handlers.py | 53 + rubpy/sync/structs/models.py | 144 + rubpy/sync/structs/results.py | 27 + rubpy/sync/structs/struct.py | 441 +++ 51 files changed, 13614 insertions(+) create mode 100644 rubpy/__init__.py create mode 100644 rubpy/client.py create mode 100644 rubpy/crypto/__init__.py create mode 100644 rubpy/crypto/crypto.py create mode 100644 rubpy/emoji.py create mode 100644 rubpy/gadgets/__init__.py create mode 100644 rubpy/gadgets/classino.py create mode 100644 rubpy/gadgets/exceptions.py create mode 100644 rubpy/gadgets/grouping.py create mode 100644 rubpy/gadgets/methods.py create mode 100644 rubpy/gadgets/models/__init__.py create mode 100644 rubpy/gadgets/models/chats.py create mode 100644 rubpy/gadgets/models/contacts.py create mode 100644 rubpy/gadgets/models/extras.py create mode 100644 rubpy/gadgets/models/groups.py create mode 100644 rubpy/gadgets/models/messages.py create mode 100644 rubpy/gadgets/models/stickers.py create mode 100644 rubpy/gadgets/models/users.py create mode 100644 rubpy/gadgets/thumbnail.py create mode 100644 rubpy/network/__init__.py create mode 100644 rubpy/network/connection.py create mode 100644 rubpy/network/proxies.py create mode 100644 rubpy/sessions/__init__.py create mode 100644 rubpy/sessions/sqliteSession.py create mode 100644 rubpy/sessions/stringSession.py create mode 100644 rubpy/structs/__init__.py create mode 100644 rubpy/structs/handlers.py create mode 100644 rubpy/structs/models.py create mode 100644 rubpy/structs/results.py create mode 100644 rubpy/structs/struct.py create mode 100644 rubpy/sync/__init__.py create mode 100644 rubpy/sync/client.py create mode 100644 rubpy/sync/crypto/__init__.py create mode 100644 rubpy/sync/crypto/crypto.py create mode 100644 rubpy/sync/gadgets/__init__.py create mode 100644 rubpy/sync/gadgets/classino.py create mode 100644 rubpy/sync/gadgets/exceptions.py create mode 100644 rubpy/sync/gadgets/grouping.py create mode 100644 rubpy/sync/gadgets/methods.py create mode 100644 rubpy/sync/gadgets/thumbnail.py create mode 100644 rubpy/sync/network/__init__.py create mode 100644 rubpy/sync/network/connection.py create mode 100644 rubpy/sync/network/proxies.py create mode 100644 rubpy/sync/sessions/__init__.py create mode 100644 rubpy/sync/sessions/sqliteSession.py create mode 100644 rubpy/sync/sessions/stringSession.py create mode 100644 rubpy/sync/structs/__init__.py create mode 100644 rubpy/sync/structs/handlers.py create mode 100644 rubpy/sync/structs/models.py create mode 100644 rubpy/sync/structs/results.py create mode 100644 rubpy/sync/structs/struct.py diff --git a/rubpy/__init__.py b/rubpy/__init__.py new file mode 100644 index 0000000..a3ff162 --- /dev/null +++ b/rubpy/__init__.py @@ -0,0 +1,9 @@ +from .client import Client +from .network import Proxies +from .structs import handlers, models +from .structs.struct import Struct as Message +from .gadgets import exceptions, methods +from . import emoji + +__version__ = '6.4.8a2' +__author__ = 'Shayan Heidari' \ No newline at end of file diff --git a/rubpy/client.py b/rubpy/client.py new file mode 100644 index 0000000..8f9e1f6 --- /dev/null +++ b/rubpy/client.py @@ -0,0 +1,1149 @@ +from .gadgets.models import users, chats, extras, groups, messages, stickers, contacts +from .gadgets import exceptions, methods, thumbnail +from .sessions import StringSession, SQLiteSession +from .network import Connection, Proxies +from . import __name__ as logger_name +from .structs import Struct +from .crypto import Crypto + + +import logging +import asyncio +import aiofiles +import os +import re + + +from typing import Union, Optional +from pathlib import Path +from io import BytesIO + + +from mutagen.ogg import OggFileType +from mutagen.flac import FLAC +from mutagen.mp3 import MP3 +from mutagen.mp4 import MP4 +from mutagen.aac import AAC + + +class Client: + configuire = { + 'package': 'web.rubika.ir', + 'platform': 'Web', + 'app_name': 'Main', + 'user_agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko)' + 'Chrome/102.0.0.0 Safari/537.36'), + 'api_version': '6', + 'app_version': '4.4.5' + } + + def __init__(self, + session: str, + auth: str = None, + private_key: str = None, + proxy=None, + logger=None, + timeout: int = 20, + lang_code: str = 'fa', + user_agent: Optional[str] = None, + request_retries: int = 5, *args, **kwargs): + + """Client + Args: + session_name (`str` | `rubpy.sessions.StringSession`): + The file name of the session file that is used + if there is a string Given (may be a complete path) + or it could be a string session + [rubpy.sessions.StringSession] + + proxy (` rubpy.network.Proxies `, optional): To set up a proxy + + user_agent (`str`, optional): + Client uses the web version, You can set the usr-user_agent + + timeout (`int` | `float`, optional): + To set the timeout `` default( `20 seconds` )`` + + logger (`logging.Logger`, optional): + Logger base for use. + + lang_code(`str`, optional): + To set the lang_code `` default( `fa` ) `` + """ + + if isinstance(session, str): + session = SQLiteSession(session) + + elif not isinstance(session, StringSession): + raise TypeError('The given session must be a ' + 'str or [rubpy.sessions.StringSession]') + + if not isinstance(logger, logging.Logger): + logger = logging.getLogger(logger_name) + + if proxy and not isinstance(proxy, Proxies): + raise TypeError( + 'The given proxy must be a [rubpy.network.Proxies]') + + self._dcs = None + self._key = None + self._auth = auth + self._private_key = private_key + self._guid = None + self._proxy = proxy + self._logger = logger + self._timeout = timeout + self._session = session + self._handlers = {} + self._request_retries = request_retries + self._user_agent = user_agent or self.configuire['user_agent'] + self._platform = { + 'package': kwargs.get('package', self.configuire['package']), + 'platform': kwargs.get('platform', self.configuire['platform']), + 'app_name': kwargs.get('app_name', self.configuire['app_name']), + 'app_version': kwargs.get('app_version', + self.configuire['app_version']), + 'lang_code': lang_code} + + async def __call__(self, request: object): + try: + result = await self._connection.execute(request) + + # update session + if result.__name__ == 'signIn' and result.status == 'OK': + result.auth = Crypto.decrypt_RSA_OAEP(self._private_key, result.auth) + self._key = Crypto.passphrase(result.auth) + self._auth = result.auth + self._session.insert( + auth=self._auth, + guid=result.user.user_guid, + user_agent=self._user_agent, + phone_number=result.user.phone, + private_key=self._private_key) + + await self( + methods.authorisations.RegisterDevice( + self._user_agent, + lang_code=self._platform['lang_code'], + app_version=self._platform['app_version']) + ) + + return result + + except AttributeError: + raise exceptions.NoConnection( + 'You must first connect the Client' + ' with the *.connect() method') + + async def __aenter__(self) -> "Client": + return await self.start(phone_number=None) + + async def __aexit__(self, *args, **kwargs): + return await self.disconnect() + + async def start(self, phone_number: str = None, *args, **kwargs): + if not hasattr(self, '_connection'): + await self.connect() + + try: + self._logger.info('user info', extra={'data': await self.get_me()}) + + except (exceptions.NotRegistrred, exceptions.InvalidInput): + self._logger.debug('user not registered!') + if phone_number is None: + phone_number = input('Phone Number: ') + is_phone_number_true = True + while is_phone_number_true: + if input(f'Is the {phone_number} correct[y or n] > ').lower() == 'y': + is_phone_number_true = False + else: + phone_number = input('Phone Number: ') + + if phone_number.startswith('0'): + phone_number = '98{}'.format(phone_number[1:]) + elif phone_number.startswith('+98'): + phone_number = phone_number[1:] + elif phone_number.startswith('0098'): + phone_number = phone_number[2:] + + result = await self( + methods.authorisations.SendCode( + phone_number=phone_number, *args, **kwargs)) + + if result.status == 'SendPassKey': + while True: + pass_key = input(f'Password [{result.hint_pass_key}] > ') + result = await self( + methods.authorisations.SendCode( + phone_number=phone_number, + pass_key=pass_key, *args, **kwargs)) + + if result.status == 'OK': + break + + public_key, self._private_key = Crypto.create_keys() + while True: + phone_code = input('Code: ') + result = await self( + methods.authorisations.SignIn( + phone_code=phone_code, + phone_number=phone_number, + phone_code_hash=result.phone_code_hash, + public_key=public_key, + *args, **kwargs)) + + if result.status == 'OK': + break + + return self + + def run(self, phone_number: str = None): + async def main_runner(): + await self.start(phone_number=phone_number) + await self._connection.receive_updates() + + asyncio.run(main_runner()) + + async def connect(self): + self._connection = Connection(client=self) + + if self._auth and self._private_key is not None: + get_me = await self.get_me() + self._guid = get_me.user.user_guid + + information = self._session.information() + self._logger.info(f'the session information was read {information}') + if information: + self._auth = information[1] + self._guid = information[2] + self._private_key = information[4] + if isinstance(information[3], str): + self._user_agent = information[3] or self._user_agent + + return self + + async def disconnect(self): + try: + await self._connection.close() + self._logger.info(f'the client was disconnected') + + except AttributeError: + raise exceptions.NoConnection( + 'You must first connect the Client' + ' with the *.connect() method') + + async def run_until_disconnected(self): + return await self._connection.receive_updates() + + # handler methods + + def on(self, handler): + def MetaHandler(func): + self.add_handler(func, handler) + return func + return MetaHandler + + def add_handler(self, func, handler): + self._handlers[func] = handler + + def remove_handler(self, func): + try: + self._handlers.pop(func) + except KeyError: + pass + + # async methods + + async def get_me(self, *args, **kwargs) -> users.GetUserInfo: + result = await self(methods.users.GetUserInfo(*args, **kwargs)) + return users.GetUserInfo(**result.to_dict()) + + async def upload(self, file: bytes, *args, **kwargs): + return await self._connection.upload_file(file=file, *args, **kwargs) + + async def download_file_inline(self, file_inline: Struct, save_as: str = None, chunk_size: int = 131072, callback=None, *args, **kwargs): + result = await self._connection.download( + file_inline.dc_id, + file_inline.file_id, + file_inline.access_hash_rec, + file_inline.size, + chunk=chunk_size, + callback=callback) + + if isinstance(save_as, str): + async with aiofiles.open(save_as, 'wb+') as _file: + await _file.write(result) + return save_as + + return result + +# ---------------- Users Methods ---------------- + + async def get_user_info(self, user_guid: str) -> users.GetUserInfo: + result = await self(methods.users.GetUserInfo(user_guid)) + return users.GetUserInfo(**result.to_dict()) + + async def block_user(self, user_guid: str) -> users.BlockUser: + result = await self(methods.users.SetBlockUser(user_guid)) + return users.BlockUser(**result.to_dict()) + + async def unblock_user(self, user_guid: str) -> users.BlockUser: + result = await self(methods.users.SetBlockUser(user_guid, 'Unblock')) + return users.BlockUser(**result.to_dict()) + + async def delete_user_chat(self, user_guid: str, last_deleted_message_id: str) -> users.DeleteUserChat: + result = await self(methods.users.DeleteUserChat(user_guid, last_deleted_message_id)) + return users.DeleteUserChat(**result.to_dict()) + + async def check_user_username(self, username: str) -> users.CheckUserName: + result = await self(methods.users.CheckUserUsername(username.replace('@', ''))) + return users.CheckUserName(**result.to_dict()) + +# ---------------- Chats Methods ---------------- + + async def upload_avatar(self, object_guid: str, main_file_id: str, thumbnail_file_id: str): + if object_guid.lower() in ('me', 'cloud', 'self'): + object_guid = self._guid + + return await self(methods.chats.UploadAvatar(object_guid, main_file_id, thumbnail_file_id)) + + async def delete_avatar(self, object_guid: str, avatar_id: str) -> chats.DeleteAvatar: + if object_guid.lower() in ('me', 'cloud', 'self'): + object_guid = self._guid + + result = await self(methods.chats.DeleteAvatar(object_guid, avatar_id)) + return chats.DeleteAvatar(**result.to_dict()) + + async def get_avatars(self, object_guid: str) -> chats.GetAvatars: + if object_guid.lower() in ('me', 'cloud', 'self'): + object_guid = self._guid + + result = await self(methods.chats.GetAvatars(object_guid)) + return chats.GetAvatars(**result.to_dict()) + + async def get_chats(self, start_id: Optional[int] = None) -> chats.GetChats: + result = await self(methods.chats.GetChats(start_id)) + return chats.GetChats(**result.to_dict()) + + async def seen_chats(self, seen_list: dict) -> chats.SeenChats: + result = await self(methods.chats.SeenChats(seen_list)) + return chats.SeenChats(**result.to_dict()) + + async def get_chat_ads(self, state: Optional[int] = None): + return await self(methods.chats.GetChatAds(state)) + + async def set_action_chat(self, object_guid: str, action: str) -> chats.SetActionChat: + ''' + alloweds: ["Mute", "Unmute"] + result = await client.set_action_chat('object_guid', 'Mute') + print(result) + ''' + result = await self(methods.chats.SetActionChat(object_guid, action)) + return chats.SetActionChat(**result.to_dict()) + + async def get_chats_updates(self, state: Optional[int] = None) -> chats.GetChatsUpdates: + result = await self(methods.chats.GetChatsUpdates(state)) + return chats.GetChatsUpdates(**result.to_dict()) + + async def send_chat_activity(self, object_guid: str, activity: Optional[str] = None) -> chats.SendChatActivity: + result = await self(methods.chats.SendChatActivity(object_guid, activity)) + return chats.SendChatActivity(**result.to_dict()) + + async def delete_chat_history(self, object_guid: str, last_message_id: str) -> chats.DeleteChatHistory: + result = await self(methods.chats.DeleteChatHistory(object_guid, last_message_id)) + return chats.DeleteChatHistory(**result.to_dict()) + + async def search_chat_messages(self, object_guid: str, search_text: str, type: str = 'Hashtag') -> chats.SearchChatMessages: + result = await self(methods.chats.SearchChatMessages(object_guid, search_text, type)) + return chats.SearchChatMessages(**result.to_dict()) + +# ---------------- Extras Methods ---------------- + + async def search_global_objects(self, search_text: str) -> extras.SearchGlobalObjects: + result = await self(methods.extras.SearchGlobalObjects(search_text)) + return extras.SearchGlobalObjects(**result.to_dict()) + + async def get_abs_objects(self, object_guids: list): + return await self(methods.extras.GetAbsObjects(object_guids)) + + async def get_object_by_username(self, username: str) -> extras.GetObjectByUsername: + result = await self(methods.extras.GetObjectByUsername(username.replace('@', ''))) + return extras.GetObjectByUsername(**result.to_dict()) + + async def get_link_from_app_url(self, app_url: str) -> extras.GetLinkFromAppUrl: + result = await self(methods.extras.GetLinkFromAppUrl(app_url)) + return extras.GetLinkFromAppUrl(**result.to_dict()) + + async def create_voice_call(self, object_guid: str) -> extras.CreateVoiceCall: + if object_guid.startswith('c'): + return await self(methods.channels.CreateChannelVoiceChat(object_guid)) + elif object_guid.startswith('g'): + result = await self(methods.groups.CreateGroupVoiceChat(object_guid)) + return extras.CreateVoiceCall(**result.to_dict()) + else: + print('Invalid Object Guid') + return False + + async def set_voice_chat_setting(self, object_guid: str, voice_chat_id: str, title: Optional[str] = None): + if object_guid.startswith('c'): + return await self(methods.channels.SetChannelVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) + elif object_guid.startswith('g'): + return await self(methods.groups.SetGroupVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) + else: + print('Invalid Object Guid') + return False + + async def discard_voice_chat(self, object_guid: str, voice_chat_id: str): + if object_guid.startswith('c'): + return await self(methods.channels.DiscardChannelVoiceChat(object_guid, voice_chat_id)) + elif object_guid.startswith('g'): + return await self(methods.channels.DiscardGroupVoiceChat(object_guid, voice_chat_id)) + else: + print('Invalid Object Guid') + return None + +# ---------------- Groups Methods ---------------- + + async def add_group(self, title: str, member_guids: list) -> groups.AddGroup: + result = await self(methods.groups.AddGroup(title, member_guids)) + return groups.AddGroup(**result.to_dict()) + + async def join_group(self, link: str) -> groups.JoinGroup: + result = await self(methods.groups.JoinGroup(link)) + return groups.JoinGroup(**result.to_dict()) + + async def leave_group(self, group_guid: str) -> groups.LeaveGroup: + result = await self(methods.groups.LeaveGroup(group_guid)) + return groups.LeaveGroup(**result.to_dict()) + + async def remove_group(self, group_guid: str) -> groups.RemoveGroup: + result = await self(methods.groups.RemoveGroup(group_guid)) + return groups.RemoveGroup(**result.to_dict()) + + async def get_group_info(self, group_guid: str) -> groups.GetGroupInfo: + result = await self(methods.groups.GetGroupInfo(group_guid)) + return groups.GetGroupInfo(**result.to_dict()) + + async def get_group_link(self, group_guid: str) -> groups.GroupLink: + result = await self(methods.groups.GetGroupLink(group_guid)) + return groups.GroupLink(**result.to_dict()) + + async def set_group_link(self, group_guid: str) -> groups.GroupLink: + result = await self(methods.groups.SetGroupLink(group_guid)) + return groups.GroupLink(**result.to_dict()) + + async def edit_group_info(self, + group_guid: str, + title: Optional[str] = None, + description: Optional[str] = None, + reaction_type = None, + selected_reactions: Optional[list] = None, + event_messages: Optional[bool] = None, + chat_history_for_new_members: str = 'Visible' + ) -> groups.EditGroupInfo: + updated_parameters = [] + updated_parameters.append('chat_history_for_new_members') + + if title: + updated_parameters.append('title') + if description: + updated_parameters.append('description') + if event_messages is not None: + updated_parameters.append('event_messages') + + chat_reaction_setting = None + + if reaction_type is not None: + chat_reaction_setting = {'reaction_type': reaction_type} + + if selected_reactions: + chat_reaction_setting['selected_reactions'] = selected_reactions + + result = await self(methods.groups.EditGroupInfo( + group_guid, updated_parameters, title, description, event_messages=event_messages, + chat_history_for_new_members=chat_history_for_new_members, + chat_reaction_setting=chat_reaction_setting)) + + return groups.EditGroupInfo(**result.to_dict()) + + async def set_group_admin(self, + group_guid: str, + member_guid: str, + access_list: list, + action: str = 'SetAdmin', + ) -> groups.InChatMember: + result = await self(methods.groups.SetGroupAdmin(group_guid, member_guid, access_list, action)) + return groups.InChatMember(**result.to_dict()) + + async def ban_group_member(self, group_guid: str, member_guid: str) -> groups.BanGroupMember: + result = await self(methods.groups.BanGroupMember(group_guid, member_guid, 'Set')) + return groups.BanGroupMember(**result.to_dict()) + + async def unban_group_member(self, group_guid: str, member_guid: str) -> groups.BanGroupMember: + result = await self(methods.groups.BanGroupMember(group_guid, member_guid, 'Unset')) + return groups.BanGroupMember(**result.to_dict()) + + async def add_group_members(self, group_guid: str, member_guids: list) -> groups.AddGroupMembers: + result = await self(methods.groups.AddGroupMembers(group_guid, member_guids)) + return groups.AddGroupMembers(**result.to_dict()) + + async def get_group_all_members(self, group_guid: str, search_text: str = None, start_id: int = None) -> groups.GetAllGroupMembers: + result = await self(methods.groups.GetGroupAllMembers(group_guid, search_text, start_id)) + return groups.GetAllGroupMembers(**result.to_dict()) + + async def get_group_admin_members(self, group_guid: str, start_id: int = None) -> groups.GetGroupAdminMembers: + result = await self(methods.groups.GetGroupAdminMembers(group_guid, start_id)) + return groups.GetGroupAdminMembers(**result.to_dict()) + + async def get_group_mention_list(self, group_guid: str, search_mention: str = None) -> groups.GetGroupMentionList: + result = await self(methods.groups.GetGroupMentionList(group_guid, search_mention)) + return groups.GetGroupMentionList(**result.to_dict()) + + async def get_group_default_access(self, group_guid: str) -> groups.GetGroupDefaultAccess: + result = await self(methods.groups.GetGroupDefaultAccess(group_guid)) + return groups.GetGroupDefaultAccess(**result.to_dict()) + + async def set_group_default_access(self, group_guid: str, access_list: list): + return await self(methods.groups.SetGroupDefaultAccess(group_guid, access_list)) + + async def group_preview_by_join_link(self, group_link: str) -> groups.GroupPreviewByJoinLink: + result = await self(methods.groups.GroupPreviewByJoinLink(group_link)) + return groups.GroupPreviewByJoinLink(**result.to_dict()) + + async def delete_no_access_group_chat(self, group_guid: str) -> groups.DeleteNoAccessGroupChat: + result = await self(methods.groups.DeleteNoAccessGroupChat(group_guid)) + return groups.DeleteNoAccessGroupChat(**result.to_dict()) + + async def get_group_admin_access_list(self, group_guid: str, member_guid: str) -> groups.GetGroupAdminAccessList: + result = await self(methods.groups.GetGroupAdminAccessList(group_guid, member_guid)) + return groups.GetGroupAdminAccessList(**result.to_dict()) + + async def get_banned_group_members(self, group_guid: str, start_id: int = None) -> groups.GetBannedGroupMembers: + result = await self(methods.groups.GetBannedGroupMembers(group_guid, start_id)) + return groups.GetBannedGroupMembers(**result.to_dict()) + + async def set_group_timer(self, group_guid: str, time: int) -> groups.EditGroupInfo: + result = await self(methods.groups.EditGroupInfo(group_guid, slow_mode=time, updated_parameters=['slow_mode'])) + return groups.EditGroupInfo(**result.to_dict()) + +# ---------------- Messages Methods ---------------- + + async def custom_send_message(self, + object_guid: str, + message=None, + reply_to_message_id: str = None, + file: bytes = None, + file_inline: dict = None, + *args, **kwargs + ) -> messages.SendMessage: + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if file: + file = await self.upload(file, *args, **kwargs) + for key, value in file_inline.items(): + file[key] = value + + result = await self( + methods.messages.SendMessage( + object_guid, + message=message, + file_inline=file, + reply_to_message_id=reply_to_message_id)) + + return messages.SendMessage(**result.to_dict()) + + async def auto_delete_message(self, object_guid: str, message_id: str, after_time: int): + await asyncio.sleep(after_time) + return await self.delete_messages(object_guid, message_id) + + async def send_message(self, + object_guid: str, + message: Optional[str] = None, + reply_to_message_id: Optional[str] = None, + file_inline: Optional[Union[Path, bytes]] = None, + type: str = methods.messages.File, + thumb: bool = True, + auto_delete: Optional[int] = None, + *args, **kwargs) -> messages.SendMessage: + """_send message_ + + Args: + object_guid (str): + _object guid_ + + message (Any, optional): + _message or cation or sticker_ . Defaults to None. + + reply_to_message_id (str, optional): + _reply to message id_. Defaults to None. + + file_inline (typing.Union[pathlib.Path, bytes], optional): + _file_. Defaults to None. + + type (str, optional): + _file type_. Defaults to methods.messages.File.( + methods.messages.Gif, + methods.messages.Image, + methods.messages.Voice, + methods.messages.Music, + methods.messages.Video + ) + + thumb (bool, optional): + if value is "True", + the lib will try to build the thumb ( require cv2 ) + if value is thumbnail.Thumbnail, to set custom + Defaults to True. + """ + + if object_guid.lower() in ('me', 'cloud', 'self'): + object_guid = self._guid + + if file_inline: + if not isinstance(file_inline, Struct): + if isinstance(file_inline, str): + async with aiofiles.open(file_inline, 'rb') as file: + kwargs['file_name'] = kwargs.get( + 'file_name', os.path.basename(file_inline)) + file_inline = await file.read() + + if type == methods.messages.Music: + thumb = None + kwargs['time'] = kwargs.get('time', self.get_audio_duration(file_inline, kwargs.get('file_name'))) + + elif type == methods.messages.Voice: + thumb = None + kwargs['time'] = kwargs.get('time', self.get_audio_duration(file_inline, kwargs.get('file_name'))) + + if thumb: + if type in (methods.messages.Video, methods.messages.Gif): + thumb = thumbnail.MakeThumbnail.from_video(file_inline) + elif type == methods.messages.Image: + thumb = thumbnail.MakeThumbnail(file_inline) + + if not hasattr(thumb, 'image'): + type = methods.messages.File + thumb = None + + file_inline = await self.upload(file_inline, *args, **kwargs) + file_inline['type'] = type + file_inline['time'] = kwargs.get('time', 1) + file_inline['width'] = kwargs.get('width', 200) + file_inline['height'] = kwargs.get('height', 200) + file_inline['music_performer'] = kwargs.get('performer', '') + + if isinstance(thumb, thumbnail.Thumbnail): + file_inline['time'] = thumb.seconds + file_inline['width'] = thumb.width + file_inline['height'] = thumb.height + file_inline['thumb_inline'] = thumb.to_base64() + + result = await self( + methods.messages.SendMessage( + object_guid, + message=message, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + if auto_delete is not None: + asyncio.create_task(self.auto_delete_message(result.object_guid, + result.message_id, + auto_delete)) + + message = messages.SendMessage(**result.to_dict()) + await message.set_shared_data(self, message) + return message + + async def send_photo(self, object_guid: str, photo: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: + """ + Send a photo. + + Args: + object_guid (str): + The GUID of the recipient. + + photo (bytes): + The photo data. + + caption (str, optional): + The caption for the photo. Defaults to None. + + reply_to_message_id (str, optional): + The ID of the message to which this is a reply. Defaults to None. + """ + return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=photo, type=methods.messages.Image, auto_delete=auto_delete, *args, **kwargs) + + async def send_document(self, object_guid: str, document: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: + """ + Send a document. + + Args: + object_guid (str): + The GUID of the recipient. + + document (bytes): + The document data. + + caption (str, optional): + The caption for the document. Defaults to None. + + reply_to_message_id (str, optional): + The ID of the message to which this is a reply. Defaults to None. + """ + return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=document, thumb=False, auto_delete=auto_delete, *args, **kwargs) + + async def send_gif(self, object_guid: str, gif: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: + """ + Send a GIF. + + Args: + object_guid (str): + The GUID of the recipient. + + gif (bytes): + The GIF data. + + caption (str, optional): + The caption for the GIF. Defaults to None. + + reply_to_message_id (str, optional): + The ID of the message to which this is a reply. Defaults to None. + """ + return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=gif, type=methods.messages.Gif, auto_delete=auto_delete, *args, **kwargs) + + async def send_video(self, object_guid: str, video: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: + """ + Send a video. + + Args: + object_guid (str): + The GUID of the recipient. + + video (bytes): + The video data. + + caption (str, optional): + The caption for the video. Defaults to None. + + reply_to_message_id (str, optional): + The ID of the message to which this is a reply. Defaults to None. + """ + return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=video, type=methods.messages.Video, auto_delete=auto_delete, *args, **kwargs) + + async def send_music(self, object_guid: str, music: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: + """ + Send music. + + Args: + object_guid (str): + The GUID of the recipient. + + music (bytes): + The music data. + + caption (str, optional): + The caption for the music. Defaults to None. + + reply_to_message_id (str, optional): + The ID of the message to which this is a reply. Defaults to None. + """ + return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=music, type=methods.messages.Music, thumb=False, auto_delete=auto_delete, *args, **kwargs) + + async def send_voice(self, object_guid: str, voice: bytes, caption: str = None, reply_to_message_id: str = None, auto_delete: int=None, *args, **kwargs) -> messages.SendMessage: + """ + Send a voice message. + + Args: + object_guid (str): + The GUID of the recipient. + + voice (bytes): + The voice message data. + + caption (str, optional): + The caption for the voice message. Defaults to None. + + reply_to_message_id (str, optional): + The ID of the message to which this is a reply. Defaults to None. + """ + return await self.send_message(object_guid=object_guid, message=caption, reply_to_message_id=reply_to_message_id, file_inline=voice, type=methods.messages.Voice, auto_delete=auto_delete, *args, **kwargs) + + async def edit_message(self, object_guid: str, message_id: str, text: str) -> messages.EditMessage: + if object_guid.lower() in ('me', 'cloud', 'self'): + object_guid = self._guid + + result = await self(methods.messages.EditMessage(object_guid, message_id, text)) + return messages.EditMessage(**result.to_dict()) + + async def delete_messages(self, object_guid: str, message_ids: list, type: str = 'Global') -> messages.DeleteMessages: + if object_guid.lower() in ('me', 'cloud', 'self'): + object_guid = self._guid + + result = await self(methods.messages.DeleteMessages(object_guid, message_ids, type)) + return messages.DeleteMessages(**result.to_dict()) + + async def request_send_file(self, file_name: str, size: int, mime: str) -> messages.RequestSendFile: + result = await self(methods.messages.RequestSendFile(file_name, size, mime)) + return messages.RequestSendFile(**result.to_dict()) + + async def forward_messages(self, from_object_guid: str, to_object_guid: str, message_ids: list) -> messages.ForwardMessages: + if from_object_guid.lower() in ('me', 'cloud', 'self'): + from_object_guid = self._guid + + result = await self(methods.messages.ForwardMessages(from_object_guid, to_object_guid, message_ids)) + return messages.ForwardMessages(**result.to_dict()) + + async def create_poll(self, + object_guid: str, + question: str, + options: list, + type: str = 'Regular', + is_anonymous: bool = True, + allows_multiple_answers: bool = False, + correct_option_index: int = 0, + explanation: str = None, + reply_to_message_id: int = 0, + ) -> messages.SendMessage: + if type == 'Regular': + result = await self(methods.messages.CreatePoll( + object_guid=object_guid, + question=question, + options=options, + allows_multiple_answers=allows_multiple_answers, + is_anonymous=is_anonymous, + reply_to_message_id=reply_to_message_id, + type=type, + )) + + else: + result = await self(methods.messages.CreatePoll( + object_guid=object_guid, + question=question, + options=options, + allows_multiple_answers=allows_multiple_answers, + is_anonymous=is_anonymous, + reply_to_message_id=reply_to_message_id, + correct_option_index=correct_option_index, + explanation=explanation, + type=type, + )) + + return messages.SendMessage(**result.to_dict()) + + async def vote_poll(self, poll_id: str, selection_index: int) -> messages.VotePoll: + result = await self(methods.messages.VotePoll(poll_id, selection_index)) + return messages.VotePoll(**result.to_dict()) + + async def get_poll_status(self, poll_id: str) -> messages.VotePoll: + result = await self(methods.messages.GetPollStatus(poll_id)) + return messages.VotePoll(**result.to_dict()) + + async def get_poll_option_voters(self, poll_id: str, selection_index: int, start_id: int = None): + return await self(methods.messages.GetPollOptionVoters(poll_id, selection_index, start_id)) + + async def set_pin_message(self, object_guid: str, message_id: str, action: str = 'Pin') -> messages.SetPinMessage: + result = await self(methods.messages.SetPinMessage(object_guid, message_id, action)) + return messages.SetPinMessage(**result.to_dict()) + + async def unset_pin_message(self, object_guid: str, message_id: str, action: str = 'Unpin') -> messages.SetPinMessage: + result = await self(methods.messages.SetPinMessage(object_guid, message_id, action)) + return messages.SetPinMessage(**result.to_dict()) + + async def get_messages_updates(self, object_guid: str, state: int = None) -> messages.GetMessagesUpdates: + result = await self(methods.messages.GetMessagesUpdates(object_guid, state)) + return messages.GetMessagesUpdates(**result.to_dict()) + + async def search_global_messages(self, search_text: str, type: str = 'Text') -> messages.SearchGlobalMessages: + result = await self(methods.messages.SearchGlobalMessages(search_text, type)) + return messages.SearchGlobalMessages(**result.to_dict()) + + async def click_message_url(self, object_guid: str, message_id: str, link_url: str): + return await self(methods.messages.ClickMessageUrl(object_guid, message_id, link_url)) + + async def get_messages_by_ID(self, object_guid: str, message_ids: list) -> messages.GetMessagesByID: + result = await self(methods.messages.GetMessagesByID(object_guid, message_ids)) + return messages.GetMessagesByID(**result.to_dict()) + + async def get_messages(self, object_guid: str, min_id: int, max_id: int, sort: str = 'FromMin', limit: int = 10): + return await self(methods.messages.GetMessages(object_guid, min_id, max_id, sort, limit)) + + async def get_messages_interval(self, object_guid: str, middle_message_id: str) -> messages.GetMessagesInterval: + result = await self(methods.messages.GetMessagesInterval(object_guid, middle_message_id)) + return messages.GetMessagesInterval(**result.to_dict()) + + async def get_message_url(self, object_guid: str, message_id: int): + if type(message_id) == str: + message_id = int(message_id) + + return await self(methods.messages.GetMessageShareUrl(object_guid, message_id)) + + async def reaction(self, object_guid: str, message_id: str, reaction_id: str) -> messages.Reacion: + result = await self(methods.messages.ActionOnMessageReaction(object_guid, message_id, 'Add', reaction_id)) + return messages.Reacion(**result.to_dict()) + + async def delete_reaction(self, object_guid: str, message_id: str) -> messages.Reacion: + result = await self(methods.messages.ActionOnMessageReaction(object_guid, message_id, 'Remove')) + return messages.Reacion(**result.to_dict()) + +# ---------------- Channels Methods ---------------- + + async def add_channel(self, title: str, description: str = None): + return await self(methods.channels.AddChannel(title, description)) + + async def remove_channel(self, channel_guid: str): + return await self(methods.channels.RemoveChannel(channel_guid)) + + async def get_channel_info(self, channel_guid: str): + return await self(methods.channels.GetChannelInfo(channel_guid)) + + async def edit_channel_info(self, + channel_guid: str, + title: str = None, + description: str = None, + channel_type: str = None, + sign_messages: str = None, + ): + updated_parameters = [] + + if title: + updated_parameters.append('title') + if description: + updated_parameters.append('description') + if channel_type: + updated_parameters.append('channel_type') + if sign_messages: + updated_parameters.append('sign_messages') + + return await self(methods.channels.EditChannelInfo( + channel_guid, updated_parameters, title, description, channel_type, sign_messages)) + + async def join_channel(self, channel_guid: str): + return await self(methods.channels.JoinChannelAction(channel_guid, 'Join')) + + async def leave_channel(self, channel_guid: str): + return await self(methods.channels.JoinChannelAction(channel_guid, 'Remove')) + + async def archive_channel(self, channel_guid: str): + return await self(methods.channels.JoinChannelAction(channel_guid, 'Archive')) + + async def join_channel_by_link(self, link: str): + return await self(methods.channels.JoinChannelByLink(link)) + + async def add_channel_members(self, channel_guid: str, member_guids: list): + return await self(methods.channels.AddChannelMembers(channel_guid, member_guids)) + + async def ban_channel_member(self, channel_guid: str, member_guid: str): + return await self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Set')) + + async def unban_channel_member(self, channel_guid: str, member_guid: str): + return await self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Unset')) + + async def check_channel_username(self, username: str): + return await self(methods.channels.CheckChannelUsername(username)) + + async def channel_preview_by_join_link(self, link: str): + return await self(methods.channels.ChannelPreviewByJoinLink(link)) + + async def get_channel_all_members(self, channel_guid: str, search_text: str = None, start_id: int = None): + return await self(methods.channels.GetChannelAllMembers(channel_guid, search_text, start_id)) + + async def check_join(self, channel_guid: str, username: str) -> bool: + result = await self.get_channel_all_members(channel_guid, username.replace('@', '')) + in_chat_members: dict = result['in_chat_members'] + for member in in_chat_members: + if username in member.values(): + return True + else: + continue + else: + return False + + async def get_channel_admin_members(self, channel_guid: str, start_id: int = None): + return await self(methods.channels.GetChannelAdminMembers(channel_guid, start_id)) + + async def update_channel_username(self, channel_guid: str, username: str): + return await self(methods.channels.UpdateChannelUsername(channel_guid, username)) + + async def get_channel_link(self, channel_guid: str): + return await self(methods.channels.GetChannelLink(channel_guid)) + + async def set_channel_link(self, channel_guid: str): + return await self(methods.channels.SetChannelLink(channel_guid)) + + async def get_channel_admin_access_list(self, channel_guid: str, member_guid: str): + return await self(methods.channels.GetChannelAdminAccessList(channel_guid, member_guid)) + +# ---------------- Contacts Methods ---------------- + + async def delete_contact(self, user_guid: str) -> contacts.DeleteContact: + result = await self(methods.contacts.DeleteContact(user_guid)) + return contacts.DeleteContact(**result.to_dict()) + + async def add_address_book(self, phone: str, first_name: str, last_name: str = '') -> contacts.AddAddressBook: + result = await self(methods.contacts.AddAddressBook(phone, first_name, last_name)) + return contacts.AddAddressBook(**result.to_dict()) + + async def get_contacts_updates(self, state: int = None): + return await self(methods.contacts.GetContactsUpdates(state)) + + async def get_contacts(self, start_id: str = None) -> contacts.GetContacts: + result = await self(methods.contacts.GetContacts(start_id)) + return contacts.GetContacts(**result.to_dict()) + +# ---------------- Settings Methods ---------------- + + async def set_setting(self, + show_my_last_online: str = None, + show_my_phone_number: str = None, + show_my_profile_photo: str = None, + link_forward_message: str = None, + can_join_chat_by: str = None + ): + updated_parameters = [] + + if show_my_last_online: + updated_parameters.append('show_my_last_online') + if show_my_phone_number: + updated_parameters.append('show_my_phone_number') + if show_my_profile_photo: + updated_parameters.append('show_my_profile_photo') + if link_forward_message: + updated_parameters.append('link_forward_message') + if can_join_chat_by: + updated_parameters.append('can_join_chat_by') + + return await self(methods.settings.SetSetting( + updated_parameters=updated_parameters, + show_my_last_online=show_my_last_online, + show_my_phone_number=show_my_phone_number, + show_my_profile_photo=show_my_profile_photo, + link_forward_message=link_forward_message, + can_join_chat_by=can_join_chat_by)) + + async def add_folder(self, + include_chat_types: list = None, + exclude_chat_types: list = None, + include_object_guids: list = None, + exclude_object_guids: list = None + ): + return await self(methods.settings.AddFolder( + include_chat_types, + exclude_chat_types, + include_object_guids, + exclude_object_guids)) + + async def get_folders(self, last_state: int): + return await self(methods.settings.GetFolders(last_state)) + + async def edit_folder(self, + include_chat_types: list = None, + exclude_chat_types: list = None, + include_object_guids: list = None, + exclude_object_guids: list = None + ): + updated_parameters = [] + + if include_chat_types: + updated_parameters.append('include_chat_types') + if exclude_chat_types: + updated_parameters.append('exclude_chat_types') + if include_object_guids: + updated_parameters.append('include_object_guids') + if exclude_object_guids: + updated_parameters.append('exclude_object_guids') + + return await self(methods.settings.EditFolder( + updated_parameters, + include_chat_types, + exclude_chat_types, + include_object_guids, + exclude_object_guids)) + + async def delete_folder(self, folder_id: str): + return await self(methods.settings.DeleteFolder(folder_id)) + + async def update_profile(self, first_name: str = None, last_name: str = None, bio: str = None): + updated_parameters = [] + + if first_name: + updated_parameters.append('first_name') + if last_name: + updated_parameters.append('last_name') + if bio: + updated_parameters.append('bio') + + return await self(methods.settings.UpdateProfile(updated_parameters, first_name, last_name, bio)) + + async def update_username(self, username: str): + return await self(methods.settings.UpdateUsername(username)) + + async def get_two_passcode_status(self): + return await self(methods.settings.GetTwoPasscodeStatus()) + + async def get_suggested_folders(self): + return await self(methods.settings.GetSuggestedFolders()) + + async def get_privacy_setting(self): + return await self(methods.settings.GetPrivacySetting()) + + async def get_blocked_users(self): + return await self(methods.settings.GetBlockedUsers()) + + async def get_my_sessions(self): + return await self(methods.settings.GetMySessions()) + + async def terminate_session(self, session_key: str): + return await self(methods.settings.TerminateSession(session_key)) + + async def setup_two_step_verification(self, password: str, hint: str, recovery_email: str): + return await self(methods.settings.SetupTwoStepVerification(password, hint, recovery_email)) + +# ---------------- Stickers Methods ---------------- + + async def get_my_sticker_sets(self) -> stickers.GetMyStickerSets: + result = await self(methods.stickers.GetMyStickerSets()) + return stickers.GetMyStickerSets(**result.to_dict()) + + async def search_stickers(self, search_text: str = '', start_id: int = None): + return await self(methods.stickers.SearchStickers(search_text, start_id)) + + async def get_sticker_set_by_ID(self, sticker_set_id: str): + return await self(methods.stickers.GetStickerSetByID(sticker_set_id)) + + async def action_on_sticker_set(self, sticker_set_id: str, action: str = 'Add'): + return await self(methods.stickers.ActionOnStickerSet(sticker_set_id, action)) + + async def get_stickers_by_emoji(self, emoji: str, suggest_by: str = 'Add'): + return await self(methods.stickers.GetStickersByEmoji(emoji, suggest_by)) + + async def get_stickers_by_set_IDs(self, sticker_set_ids: list) -> stickers.GetStickersBySetIDs: + result = await self(methods.stickers.GetStickersBySetIDs(sticker_set_ids)) + return stickers.GetStickersBySetIDs(**result.to_dict()) + + async def get_trend_sticker_sets(self, start_id: int = None) -> stickers.GetTrendStickerSets: + result = await self(methods.stickers.GetTrendStickerSets(start_id)) + return stickers.GetTrendStickerSets(**result.to_dict()) + + def get_audio_duration(self, file: bytes, file_name: str) -> float: + file_format = re.search(r'\.([a-zA-Z0-9]+)$', file_name) + if not file_format: + raise ValueError('The file format is not specified.') + + file_format = file_format.group(1).lower() + + if file_format == 'mp3': + audio = MP3(BytesIO(file)) + elif file_format == 'm4a': + audio = MP4(BytesIO(file)) + elif file_format in ['flac', 'fla']: + audio = FLAC(BytesIO(file)) + elif file_format == 'aac': + audio = AAC(BytesIO(file)) + elif file_format == 'ogg': + audio = OggFileType(BytesIO(file)) + else: + raise ValueError(f'Format {file_format} is not supported.') + + return float(audio.info.length) \ No newline at end of file diff --git a/rubpy/crypto/__init__.py b/rubpy/crypto/__init__.py new file mode 100644 index 0000000..9aeadc9 --- /dev/null +++ b/rubpy/crypto/__init__.py @@ -0,0 +1 @@ +from .crypto import Crypto \ No newline at end of file diff --git a/rubpy/crypto/crypto.py b/rubpy/crypto/crypto.py new file mode 100644 index 0000000..3858140 --- /dev/null +++ b/rubpy/crypto/crypto.py @@ -0,0 +1,168 @@ +import re +import json +import base64 +import string +import secrets +from json import JSONDecoder +from Crypto.Cipher import AES +from Crypto.Hash import SHA256 +from Crypto.Signature import pkcs1_15 +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP +from string import ascii_lowercase, ascii_uppercase + +class Crypto: + AES_IV = b'\x00' * 16 + + @staticmethod + def decode_auth(auth: str) -> str: + """ + Decode an auth string by applying character substitutions. + + Args: + auth (str): The input auth string. + + Returns: + str: The decoded auth string. + """ + result_list, digits = [], '0123456789' + translation_table_lower = str.maketrans( + ascii_lowercase, + ''.join([chr(((32 - (ord(c) - 97)) % 26) + 97) for c in ascii_lowercase]) + ) + translation_table_upper = str.maketrans( + ascii_uppercase, + ''.join([chr(((29 - (ord(c) - 65)) % 26) + 65) for c in ascii_uppercase]) + ) + + for char in auth: + if char in ascii_lowercase: + result_list.append(char.translate(translation_table_lower)) + elif char in ascii_uppercase: + result_list.append(char.translate(translation_table_upper)) + elif char in digits: + result_list.append(chr(((13 - (ord(char) - 48)) % 10) + 48)) + else: + result_list.append(char) + + return ''.join(result_list) + + @classmethod + def passphrase(cls, auth): + """ + Generate a passphrase from an auth string. + + Args: + auth (str): The input auth string. + + Returns: + str: The generated passphrase. + """ + if len(auth) != 32: + raise ValueError('auth length should be 32 digits') + + result_list = [] + chunks = re.findall(r'\S{8}', auth) + for character in (chunks[2] + chunks[0] + chunks[3] + chunks[1]): + result_list.append(chr(((ord(character) - 97 + 9) % 26) + 97)) + return ''.join(result_list) + + @classmethod + def secret(cls, length): + """ + Generate a random secret of the given length. + + Args: + length (int): Length of the secret. + + Returns: + str: The generated secret. + """ + return ''.join(secrets.choice(string.ascii_lowercase) + for _ in range(length)) + + @classmethod + def decrypt(cls, data, key): + """ + Decrypt data using AES encryption. + + Args: + data (str): The encrypted data. + key (str): The encryption key. + + Returns: + dict: The decrypted data as a dictionary. + """ + decoder = JSONDecoder() + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, cls.AES_IV) + decoded_data = base64.b64decode(data) + result, _ = decoder.raw_decode(cipher.decrypt(decoded_data).decode('utf-8')) + return result + + @classmethod + def encrypt(cls, data, key): + """ + Encrypt data using AES encryption. + + Args: + data (str or dict): The data to be encrypted. + key (str): The encryption key. + + Returns: + str: The encrypted data as a string. + """ + if not isinstance(data, str): + data = json.dumps(data, default=lambda x: str(x)) + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, cls.AES_IV) + length = 16 - (len(data) % 16) + data += chr(length) * length + return ( + base64.b64encode(cipher.encrypt(data.encode('utf-8'))) + .decode('utf-8') + ) + + @staticmethod + def sign(private_key: str, data: str) -> str: + """ + Sign data using an RSA private key. + + Args: + private_key (str): The RSA private key. + data (str): The data to be signed. + + Returns: + str: The base64-encoded signature. + """ + key = RSA.import_key(private_key.encode('utf-8')) + signature = pkcs1_15.new(key).sign( + SHA256.new(data.encode('utf-8')) + ) + return base64.b64encode(signature).decode('utf-8') + + @staticmethod + def create_keys() -> tuple: + """ + Generate RSA public and private keys. + + Returns: + tuple: A tuple containing the base64-encoded public key and the private key. + """ + keys = RSA.generate(1024) + public_key = Crypto.decode_auth(base64.b64encode(keys.publickey().export_key()).decode('utf-8')) + private_key = keys.export_key().decode('utf-8') + return public_key, private_key + + @staticmethod + def decrypt_RSA_OAEP(private_key: str, data: str): + """ + Decrypt data using RSA OAEP encryption. + + Args: + private_key (str): The RSA private key. + data (str): The encrypted data. + + Returns: + str: The decrypted data as a string. + """ + key = RSA.import_key(private_key.encode('utf-8')) + return PKCS1_OAEP.new(key).decrypt(base64.b64decode(data)).decode('utf-8') \ No newline at end of file diff --git a/rubpy/emoji.py b/rubpy/emoji.py new file mode 100644 index 0000000..1574f5e --- /dev/null +++ b/rubpy/emoji.py @@ -0,0 +1,4003 @@ +# RUBPY - Rubika API Client Library for Python + +GRINNING_FACE = "\U0001f600" +GRINNING_FACE_WITH_BIG_EYES = "\U0001f603" +GRINNING_FACE_WITH_SMILING_EYES = "\U0001f604" +BEAMING_FACE_WITH_SMILING_EYES = "\U0001f601" +GRINNING_SQUINTING_FACE = "\U0001f606" +GRINNING_FACE_WITH_SWEAT = "\U0001f605" +ROLLING_ON_THE_FLOOR_LAUGHING = "\U0001f923" +FACE_WITH_TEARS_OF_JOY = "\U0001f602" +SLIGHTLY_SMILING_FACE = "\U0001f642" +UPSIDE_DOWN_FACE = "\U0001f643" +MELTING_FACE = "\U0001fae0" +WINKING_FACE = "\U0001f609" +SMILING_FACE_WITH_SMILING_EYES = "\U0001f60a" +SMILING_FACE_WITH_HALO = "\U0001f607" +SMILING_FACE_WITH_HEARTS = "\U0001f970" +SMILING_FACE_WITH_HEART_EYES = "\U0001f60d" +STAR_STRUCK = "\U0001f929" +FACE_BLOWING_A_KISS = "\U0001f618" +KISSING_FACE = "\U0001f617" +SMILING_FACE = "\u263a\ufe0f" +KISSING_FACE_WITH_CLOSED_EYES = "\U0001f61a" +KISSING_FACE_WITH_SMILING_EYES = "\U0001f619" +SMILING_FACE_WITH_TEAR = "\U0001f972" +FACE_SAVORING_FOOD = "\U0001f60b" +FACE_WITH_TONGUE = "\U0001f61b" +WINKING_FACE_WITH_TONGUE = "\U0001f61c" +ZANY_FACE = "\U0001f92a" +SQUINTING_FACE_WITH_TONGUE = "\U0001f61d" +MONEY_MOUTH_FACE = "\U0001f911" +SMILING_FACE_WITH_OPEN_HANDS = "\U0001f917" +FACE_WITH_HAND_OVER_MOUTH = "\U0001f92d" +FACE_WITH_OPEN_EYES_AND_HAND_OVER_MOUTH = "\U0001fae2" +FACE_WITH_PEEKING_EYE = "\U0001fae3" +SHUSHING_FACE = "\U0001f92b" +THINKING_FACE = "\U0001f914" +SALUTING_FACE = "\U0001fae1" +ZIPPER_MOUTH_FACE = "\U0001f910" +FACE_WITH_RAISED_EYEBROW = "\U0001f928" +NEUTRAL_FACE = "\U0001f610" +EXPRESSIONLESS_FACE = "\U0001f611" +FACE_WITHOUT_MOUTH = "\U0001f636" +DOTTED_LINE_FACE = "\U0001fae5" +FACE_IN_CLOUDS = "\U0001f636\u200d\U0001f32b\ufe0f" +SMIRKING_FACE = "\U0001f60f" +UNAMUSED_FACE = "\U0001f612" +FACE_WITH_ROLLING_EYES = "\U0001f644" +GRIMACING_FACE = "\U0001f62c" +FACE_EXHALING = "\U0001f62e\u200d\U0001f4a8" +LYING_FACE = "\U0001f925" +RELIEVED_FACE = "\U0001f60c" +PENSIVE_FACE = "\U0001f614" +SLEEPY_FACE = "\U0001f62a" +DROOLING_FACE = "\U0001f924" +SLEEPING_FACE = "\U0001f634" +FACE_WITH_MEDICAL_MASK = "\U0001f637" +FACE_WITH_THERMOMETER = "\U0001f912" +FACE_WITH_HEAD_BANDAGE = "\U0001f915" +NAUSEATED_FACE = "\U0001f922" +FACE_VOMITING = "\U0001f92e" +SNEEZING_FACE = "\U0001f927" +HOT_FACE = "\U0001f975" +COLD_FACE = "\U0001f976" +WOOZY_FACE = "\U0001f974" +FACE_WITH_CROSSED_OUT_EYES = "\U0001f635" +FACE_WITH_SPIRAL_EYES = "\U0001f635\u200d\U0001f4ab" +EXPLODING_HEAD = "\U0001f92f" +COWBOY_HAT_FACE = "\U0001f920" +PARTYING_FACE = "\U0001f973" +DISGUISED_FACE = "\U0001f978" +SMILING_FACE_WITH_SUNGLASSES = "\U0001f60e" +NERD_FACE = "\U0001f913" +FACE_WITH_MONOCLE = "\U0001f9d0" +CONFUSED_FACE = "\U0001f615" +FACE_WITH_DIAGONAL_MOUTH = "\U0001fae4" +WORRIED_FACE = "\U0001f61f" +SLIGHTLY_FROWNING_FACE = "\U0001f641" +FROWNING_FACE = "\u2639\ufe0f" +FACE_WITH_OPEN_MOUTH = "\U0001f62e" +HUSHED_FACE = "\U0001f62f" +ASTONISHED_FACE = "\U0001f632" +FLUSHED_FACE = "\U0001f633" +PLEADING_FACE = "\U0001f97a" +FACE_HOLDING_BACK_TEARS = "\U0001f979" +FROWNING_FACE_WITH_OPEN_MOUTH = "\U0001f626" +ANGUISHED_FACE = "\U0001f627" +FEARFUL_FACE = "\U0001f628" +ANXIOUS_FACE_WITH_SWEAT = "\U0001f630" +SAD_BUT_RELIEVED_FACE = "\U0001f625" +CRYING_FACE = "\U0001f622" +LOUDLY_CRYING_FACE = "\U0001f62d" +FACE_SCREAMING_IN_FEAR = "\U0001f631" +CONFOUNDED_FACE = "\U0001f616" +PERSEVERING_FACE = "\U0001f623" +DISAPPOINTED_FACE = "\U0001f61e" +DOWNCAST_FACE_WITH_SWEAT = "\U0001f613" +WEARY_FACE = "\U0001f629" +TIRED_FACE = "\U0001f62b" +YAWNING_FACE = "\U0001f971" +FACE_WITH_STEAM_FROM_NOSE = "\U0001f624" +ENRAGED_FACE = "\U0001f621" +ANGRY_FACE = "\U0001f620" +FACE_WITH_SYMBOLS_ON_MOUTH = "\U0001f92c" +SMILING_FACE_WITH_HORNS = "\U0001f608" +ANGRY_FACE_WITH_HORNS = "\U0001f47f" +SKULL = "\U0001f480" +SKULL_AND_CROSSBONES = "\u2620\ufe0f" +PILE_OF_POO = "\U0001f4a9" +CLOWN_FACE = "\U0001f921" +OGRE = "\U0001f479" +GOBLIN = "\U0001f47a" +GHOST = "\U0001f47b" +ALIEN = "\U0001f47d" +ALIEN_MONSTER = "\U0001f47e" +ROBOT = "\U0001f916" +GRINNING_CAT = "\U0001f63a" +GRINNING_CAT_WITH_SMILING_EYES = "\U0001f638" +CAT_WITH_TEARS_OF_JOY = "\U0001f639" +SMILING_CAT_WITH_HEART_EYES = "\U0001f63b" +CAT_WITH_WRY_SMILE = "\U0001f63c" +KISSING_CAT = "\U0001f63d" +WEARY_CAT = "\U0001f640" +CRYING_CAT = "\U0001f63f" +POUTING_CAT = "\U0001f63e" +SEE_NO_EVIL_MONKEY = "\U0001f648" +HEAR_NO_EVIL_MONKEY = "\U0001f649" +SPEAK_NO_EVIL_MONKEY = "\U0001f64a" +KISS_MARK = "\U0001f48b" +LOVE_LETTER = "\U0001f48c" +HEART_WITH_ARROW = "\U0001f498" +HEART_WITH_RIBBON = "\U0001f49d" +SPARKLING_HEART = "\U0001f496" +GROWING_HEART = "\U0001f497" +BEATING_HEART = "\U0001f493" +REVOLVING_HEARTS = "\U0001f49e" +TWO_HEARTS = "\U0001f495" +HEART_DECORATION = "\U0001f49f" +HEART_EXCLAMATION = "\u2763\ufe0f" +BROKEN_HEART = "\U0001f494" +HEART_ON_FIRE = "\u2764\ufe0f\u200d\U0001f525" +MENDING_HEART = "\u2764\ufe0f\u200d\U0001fa79" +RED_HEART = "\u2764\ufe0f" +ORANGE_HEART = "\U0001f9e1" +YELLOW_HEART = "\U0001f49b" +GREEN_HEART = "\U0001f49a" +BLUE_HEART = "\U0001f499" +PURPLE_HEART = "\U0001f49c" +BROWN_HEART = "\U0001f90e" +BLACK_HEART = "\U0001f5a4" +WHITE_HEART = "\U0001f90d" +HUNDRED_POINTS = "\U0001f4af" +ANGER_SYMBOL = "\U0001f4a2" +COLLISION = "\U0001f4a5" +DIZZY = "\U0001f4ab" +SWEAT_DROPLETS = "\U0001f4a6" +DASHING_AWAY = "\U0001f4a8" +HOLE = "\U0001f573\ufe0f" +BOMB = "\U0001f4a3" +SPEECH_BALLOON = "\U0001f4ac" +EYE_IN_SPEECH_BUBBLE = "\U0001f441\ufe0f\u200d\U0001f5e8\ufe0f" +LEFT_SPEECH_BUBBLE = "\U0001f5e8\ufe0f" +RIGHT_ANGER_BUBBLE = "\U0001f5ef\ufe0f" +THOUGHT_BALLOON = "\U0001f4ad" +ZZZ = "\U0001f4a4" +WAVING_HAND = "\U0001f44b" +WAVING_HAND_LIGHT_SKIN_TONE = "\U0001f44b\U0001f3fb" +WAVING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44b\U0001f3fc" +WAVING_HAND_MEDIUM_SKIN_TONE = "\U0001f44b\U0001f3fd" +WAVING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f44b\U0001f3fe" +WAVING_HAND_DARK_SKIN_TONE = "\U0001f44b\U0001f3ff" +RAISED_BACK_OF_HAND = "\U0001f91a" +RAISED_BACK_OF_HAND_LIGHT_SKIN_TONE = "\U0001f91a\U0001f3fb" +RAISED_BACK_OF_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91a\U0001f3fc" +RAISED_BACK_OF_HAND_MEDIUM_SKIN_TONE = "\U0001f91a\U0001f3fd" +RAISED_BACK_OF_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f91a\U0001f3fe" +RAISED_BACK_OF_HAND_DARK_SKIN_TONE = "\U0001f91a\U0001f3ff" +HAND_WITH_FINGERS_SPLAYED = "\U0001f590\ufe0f" +HAND_WITH_FINGERS_SPLAYED_LIGHT_SKIN_TONE = "\U0001f590\U0001f3fb" +HAND_WITH_FINGERS_SPLAYED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f590\U0001f3fc" +HAND_WITH_FINGERS_SPLAYED_MEDIUM_SKIN_TONE = "\U0001f590\U0001f3fd" +HAND_WITH_FINGERS_SPLAYED_MEDIUM_DARK_SKIN_TONE = "\U0001f590\U0001f3fe" +HAND_WITH_FINGERS_SPLAYED_DARK_SKIN_TONE = "\U0001f590\U0001f3ff" +RAISED_HAND = "\u270b" +RAISED_HAND_LIGHT_SKIN_TONE = "\u270b\U0001f3fb" +RAISED_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270b\U0001f3fc" +RAISED_HAND_MEDIUM_SKIN_TONE = "\u270b\U0001f3fd" +RAISED_HAND_MEDIUM_DARK_SKIN_TONE = "\u270b\U0001f3fe" +RAISED_HAND_DARK_SKIN_TONE = "\u270b\U0001f3ff" +VULCAN_SALUTE = "\U0001f596" +VULCAN_SALUTE_LIGHT_SKIN_TONE = "\U0001f596\U0001f3fb" +VULCAN_SALUTE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f596\U0001f3fc" +VULCAN_SALUTE_MEDIUM_SKIN_TONE = "\U0001f596\U0001f3fd" +VULCAN_SALUTE_MEDIUM_DARK_SKIN_TONE = "\U0001f596\U0001f3fe" +VULCAN_SALUTE_DARK_SKIN_TONE = "\U0001f596\U0001f3ff" +RIGHTWARDS_HAND = "\U0001faf1" +RIGHTWARDS_HAND_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fb" +RIGHTWARDS_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fc" +RIGHTWARDS_HAND_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fd" +RIGHTWARDS_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fe" +RIGHTWARDS_HAND_DARK_SKIN_TONE = "\U0001faf1\U0001f3ff" +LEFTWARDS_HAND = "\U0001faf2" +LEFTWARDS_HAND_LIGHT_SKIN_TONE = "\U0001faf2\U0001f3fb" +LEFTWARDS_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf2\U0001f3fc" +LEFTWARDS_HAND_MEDIUM_SKIN_TONE = "\U0001faf2\U0001f3fd" +LEFTWARDS_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf2\U0001f3fe" +LEFTWARDS_HAND_DARK_SKIN_TONE = "\U0001faf2\U0001f3ff" +PALM_DOWN_HAND = "\U0001faf3" +PALM_DOWN_HAND_LIGHT_SKIN_TONE = "\U0001faf3\U0001f3fb" +PALM_DOWN_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf3\U0001f3fc" +PALM_DOWN_HAND_MEDIUM_SKIN_TONE = "\U0001faf3\U0001f3fd" +PALM_DOWN_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf3\U0001f3fe" +PALM_DOWN_HAND_DARK_SKIN_TONE = "\U0001faf3\U0001f3ff" +PALM_UP_HAND = "\U0001faf4" +PALM_UP_HAND_LIGHT_SKIN_TONE = "\U0001faf4\U0001f3fb" +PALM_UP_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf4\U0001f3fc" +PALM_UP_HAND_MEDIUM_SKIN_TONE = "\U0001faf4\U0001f3fd" +PALM_UP_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf4\U0001f3fe" +PALM_UP_HAND_DARK_SKIN_TONE = "\U0001faf4\U0001f3ff" +OK_HAND = "\U0001f44c" +OK_HAND_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fb" +OK_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fc" +OK_HAND_MEDIUM_SKIN_TONE = "\U0001f44c\U0001f3fd" +OK_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f44c\U0001f3fe" +OK_HAND_DARK_SKIN_TONE = "\U0001f44c\U0001f3ff" +PINCHED_FINGERS = "\U0001f90c" +PINCHED_FINGERS_LIGHT_SKIN_TONE = "\U0001f90c\U0001f3fb" +PINCHED_FINGERS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f90c\U0001f3fc" +PINCHED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f90c\U0001f3fd" +PINCHED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f90c\U0001f3fe" +PINCHED_FINGERS_DARK_SKIN_TONE = "\U0001f90c\U0001f3ff" +PINCHING_HAND = "\U0001f90f" +PINCHING_HAND_LIGHT_SKIN_TONE = "\U0001f90f\U0001f3fb" +PINCHING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f90f\U0001f3fc" +PINCHING_HAND_MEDIUM_SKIN_TONE = "\U0001f90f\U0001f3fd" +PINCHING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f90f\U0001f3fe" +PINCHING_HAND_DARK_SKIN_TONE = "\U0001f90f\U0001f3ff" +VICTORY_HAND = "\u270c\ufe0f" +VICTORY_HAND_LIGHT_SKIN_TONE = "\u270c\U0001f3fb" +VICTORY_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270c\U0001f3fc" +VICTORY_HAND_MEDIUM_SKIN_TONE = "\u270c\U0001f3fd" +VICTORY_HAND_MEDIUM_DARK_SKIN_TONE = "\u270c\U0001f3fe" +VICTORY_HAND_DARK_SKIN_TONE = "\u270c\U0001f3ff" +CROSSED_FINGERS = "\U0001f91e" +CROSSED_FINGERS_LIGHT_SKIN_TONE = "\U0001f91e\U0001f3fb" +CROSSED_FINGERS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91e\U0001f3fc" +CROSSED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f91e\U0001f3fd" +CROSSED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f91e\U0001f3fe" +CROSSED_FINGERS_DARK_SKIN_TONE = "\U0001f91e\U0001f3ff" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED = "\U0001faf0" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_LIGHT_SKIN_TONE = "\U0001faf0\U0001f3fb" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf0\U0001f3fc" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_SKIN_TONE = "\U0001faf0\U0001f3fd" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_DARK_SKIN_TONE = "\U0001faf0\U0001f3fe" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_DARK_SKIN_TONE = "\U0001faf0\U0001f3ff" +LOVE_YOU_GESTURE = "\U0001f91f" +LOVE_YOU_GESTURE_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fb" +LOVE_YOU_GESTURE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fc" +LOVE_YOU_GESTURE_MEDIUM_SKIN_TONE = "\U0001f91f\U0001f3fd" +LOVE_YOU_GESTURE_MEDIUM_DARK_SKIN_TONE = "\U0001f91f\U0001f3fe" +LOVE_YOU_GESTURE_DARK_SKIN_TONE = "\U0001f91f\U0001f3ff" +SIGN_OF_THE_HORNS = "\U0001f918" +SIGN_OF_THE_HORNS_LIGHT_SKIN_TONE = "\U0001f918\U0001f3fb" +SIGN_OF_THE_HORNS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f918\U0001f3fc" +SIGN_OF_THE_HORNS_MEDIUM_SKIN_TONE = "\U0001f918\U0001f3fd" +SIGN_OF_THE_HORNS_MEDIUM_DARK_SKIN_TONE = "\U0001f918\U0001f3fe" +SIGN_OF_THE_HORNS_DARK_SKIN_TONE = "\U0001f918\U0001f3ff" +CALL_ME_HAND = "\U0001f919" +CALL_ME_HAND_LIGHT_SKIN_TONE = "\U0001f919\U0001f3fb" +CALL_ME_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f919\U0001f3fc" +CALL_ME_HAND_MEDIUM_SKIN_TONE = "\U0001f919\U0001f3fd" +CALL_ME_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f919\U0001f3fe" +CALL_ME_HAND_DARK_SKIN_TONE = "\U0001f919\U0001f3ff" +BACKHAND_INDEX_POINTING_LEFT = "\U0001f448" +BACKHAND_INDEX_POINTING_LEFT_LIGHT_SKIN_TONE = "\U0001f448\U0001f3fb" +BACKHAND_INDEX_POINTING_LEFT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f448\U0001f3fc" +BACKHAND_INDEX_POINTING_LEFT_MEDIUM_SKIN_TONE = "\U0001f448\U0001f3fd" +BACKHAND_INDEX_POINTING_LEFT_MEDIUM_DARK_SKIN_TONE = "\U0001f448\U0001f3fe" +BACKHAND_INDEX_POINTING_LEFT_DARK_SKIN_TONE = "\U0001f448\U0001f3ff" +BACKHAND_INDEX_POINTING_RIGHT = "\U0001f449" +BACKHAND_INDEX_POINTING_RIGHT_LIGHT_SKIN_TONE = "\U0001f449\U0001f3fb" +BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f449\U0001f3fc" +BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_SKIN_TONE = "\U0001f449\U0001f3fd" +BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_DARK_SKIN_TONE = "\U0001f449\U0001f3fe" +BACKHAND_INDEX_POINTING_RIGHT_DARK_SKIN_TONE = "\U0001f449\U0001f3ff" +BACKHAND_INDEX_POINTING_UP = "\U0001f446" +BACKHAND_INDEX_POINTING_UP_LIGHT_SKIN_TONE = "\U0001f446\U0001f3fb" +BACKHAND_INDEX_POINTING_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f446\U0001f3fc" +BACKHAND_INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\U0001f446\U0001f3fd" +BACKHAND_INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\U0001f446\U0001f3fe" +BACKHAND_INDEX_POINTING_UP_DARK_SKIN_TONE = "\U0001f446\U0001f3ff" +MIDDLE_FINGER = "\U0001f595" +MIDDLE_FINGER_LIGHT_SKIN_TONE = "\U0001f595\U0001f3fb" +MIDDLE_FINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f595\U0001f3fc" +MIDDLE_FINGER_MEDIUM_SKIN_TONE = "\U0001f595\U0001f3fd" +MIDDLE_FINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f595\U0001f3fe" +MIDDLE_FINGER_DARK_SKIN_TONE = "\U0001f595\U0001f3ff" +BACKHAND_INDEX_POINTING_DOWN = "\U0001f447" +BACKHAND_INDEX_POINTING_DOWN_LIGHT_SKIN_TONE = "\U0001f447\U0001f3fb" +BACKHAND_INDEX_POINTING_DOWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f447\U0001f3fc" +BACKHAND_INDEX_POINTING_DOWN_MEDIUM_SKIN_TONE = "\U0001f447\U0001f3fd" +BACKHAND_INDEX_POINTING_DOWN_MEDIUM_DARK_SKIN_TONE = "\U0001f447\U0001f3fe" +BACKHAND_INDEX_POINTING_DOWN_DARK_SKIN_TONE = "\U0001f447\U0001f3ff" +INDEX_POINTING_UP = "\u261d\ufe0f" +INDEX_POINTING_UP_LIGHT_SKIN_TONE = "\u261d\U0001f3fb" +INDEX_POINTING_UP_MEDIUM_LIGHT_SKIN_TONE = "\u261d\U0001f3fc" +INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\u261d\U0001f3fd" +INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\u261d\U0001f3fe" +INDEX_POINTING_UP_DARK_SKIN_TONE = "\u261d\U0001f3ff" +INDEX_POINTING_AT_THE_VIEWER = "\U0001faf5" +INDEX_POINTING_AT_THE_VIEWER_LIGHT_SKIN_TONE = "\U0001faf5\U0001f3fb" +INDEX_POINTING_AT_THE_VIEWER_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf5\U0001f3fc" +INDEX_POINTING_AT_THE_VIEWER_MEDIUM_SKIN_TONE = "\U0001faf5\U0001f3fd" +INDEX_POINTING_AT_THE_VIEWER_MEDIUM_DARK_SKIN_TONE = "\U0001faf5\U0001f3fe" +INDEX_POINTING_AT_THE_VIEWER_DARK_SKIN_TONE = "\U0001faf5\U0001f3ff" +THUMBS_UP = "\U0001f44d" +THUMBS_UP_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fb" +THUMBS_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fc" +THUMBS_UP_MEDIUM_SKIN_TONE = "\U0001f44d\U0001f3fd" +THUMBS_UP_MEDIUM_DARK_SKIN_TONE = "\U0001f44d\U0001f3fe" +THUMBS_UP_DARK_SKIN_TONE = "\U0001f44d\U0001f3ff" +THUMBS_DOWN = "\U0001f44e" +THUMBS_DOWN_LIGHT_SKIN_TONE = "\U0001f44e\U0001f3fb" +THUMBS_DOWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44e\U0001f3fc" +THUMBS_DOWN_MEDIUM_SKIN_TONE = "\U0001f44e\U0001f3fd" +THUMBS_DOWN_MEDIUM_DARK_SKIN_TONE = "\U0001f44e\U0001f3fe" +THUMBS_DOWN_DARK_SKIN_TONE = "\U0001f44e\U0001f3ff" +RAISED_FIST = "\u270a" +RAISED_FIST_LIGHT_SKIN_TONE = "\u270a\U0001f3fb" +RAISED_FIST_MEDIUM_LIGHT_SKIN_TONE = "\u270a\U0001f3fc" +RAISED_FIST_MEDIUM_SKIN_TONE = "\u270a\U0001f3fd" +RAISED_FIST_MEDIUM_DARK_SKIN_TONE = "\u270a\U0001f3fe" +RAISED_FIST_DARK_SKIN_TONE = "\u270a\U0001f3ff" +ONCOMING_FIST = "\U0001f44a" +ONCOMING_FIST_LIGHT_SKIN_TONE = "\U0001f44a\U0001f3fb" +ONCOMING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44a\U0001f3fc" +ONCOMING_FIST_MEDIUM_SKIN_TONE = "\U0001f44a\U0001f3fd" +ONCOMING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f44a\U0001f3fe" +ONCOMING_FIST_DARK_SKIN_TONE = "\U0001f44a\U0001f3ff" +LEFT_FACING_FIST = "\U0001f91b" +LEFT_FACING_FIST_LIGHT_SKIN_TONE = "\U0001f91b\U0001f3fb" +LEFT_FACING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91b\U0001f3fc" +LEFT_FACING_FIST_MEDIUM_SKIN_TONE = "\U0001f91b\U0001f3fd" +LEFT_FACING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f91b\U0001f3fe" +LEFT_FACING_FIST_DARK_SKIN_TONE = "\U0001f91b\U0001f3ff" +RIGHT_FACING_FIST = "\U0001f91c" +RIGHT_FACING_FIST_LIGHT_SKIN_TONE = "\U0001f91c\U0001f3fb" +RIGHT_FACING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91c\U0001f3fc" +RIGHT_FACING_FIST_MEDIUM_SKIN_TONE = "\U0001f91c\U0001f3fd" +RIGHT_FACING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f91c\U0001f3fe" +RIGHT_FACING_FIST_DARK_SKIN_TONE = "\U0001f91c\U0001f3ff" +CLAPPING_HANDS = "\U0001f44f" +CLAPPING_HANDS_LIGHT_SKIN_TONE = "\U0001f44f\U0001f3fb" +CLAPPING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44f\U0001f3fc" +CLAPPING_HANDS_MEDIUM_SKIN_TONE = "\U0001f44f\U0001f3fd" +CLAPPING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f44f\U0001f3fe" +CLAPPING_HANDS_DARK_SKIN_TONE = "\U0001f44f\U0001f3ff" +RAISING_HANDS = "\U0001f64c" +RAISING_HANDS_LIGHT_SKIN_TONE = "\U0001f64c\U0001f3fb" +RAISING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64c\U0001f3fc" +RAISING_HANDS_MEDIUM_SKIN_TONE = "\U0001f64c\U0001f3fd" +RAISING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64c\U0001f3fe" +RAISING_HANDS_DARK_SKIN_TONE = "\U0001f64c\U0001f3ff" +HEART_HANDS = "\U0001faf6" +HEART_HANDS_LIGHT_SKIN_TONE = "\U0001faf6\U0001f3fb" +HEART_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf6\U0001f3fc" +HEART_HANDS_MEDIUM_SKIN_TONE = "\U0001faf6\U0001f3fd" +HEART_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001faf6\U0001f3fe" +HEART_HANDS_DARK_SKIN_TONE = "\U0001faf6\U0001f3ff" +OPEN_HANDS = "\U0001f450" +OPEN_HANDS_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fb" +OPEN_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fc" +OPEN_HANDS_MEDIUM_SKIN_TONE = "\U0001f450\U0001f3fd" +OPEN_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f450\U0001f3fe" +OPEN_HANDS_DARK_SKIN_TONE = "\U0001f450\U0001f3ff" +PALMS_UP_TOGETHER = "\U0001f932" +PALMS_UP_TOGETHER_LIGHT_SKIN_TONE = "\U0001f932\U0001f3fb" +PALMS_UP_TOGETHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f932\U0001f3fc" +PALMS_UP_TOGETHER_MEDIUM_SKIN_TONE = "\U0001f932\U0001f3fd" +PALMS_UP_TOGETHER_MEDIUM_DARK_SKIN_TONE = "\U0001f932\U0001f3fe" +PALMS_UP_TOGETHER_DARK_SKIN_TONE = "\U0001f932\U0001f3ff" +HANDSHAKE = "\U0001f91d" +HANDSHAKE_LIGHT_SKIN_TONE = "\U0001f91d\U0001f3fb" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91d\U0001f3fc" +HANDSHAKE_MEDIUM_SKIN_TONE = "\U0001f91d\U0001f3fd" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE = "\U0001f91d\U0001f3fe" +HANDSHAKE_DARK_SKIN_TONE = "\U0001f91d\U0001f3ff" +HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fe" +HANDSHAKE_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fe" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fe" +HANDSHAKE_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fe" +FOLDED_HANDS = "\U0001f64f" +FOLDED_HANDS_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fb" +FOLDED_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fc" +FOLDED_HANDS_MEDIUM_SKIN_TONE = "\U0001f64f\U0001f3fd" +FOLDED_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64f\U0001f3fe" +FOLDED_HANDS_DARK_SKIN_TONE = "\U0001f64f\U0001f3ff" +WRITING_HAND = "\u270d\ufe0f" +WRITING_HAND_LIGHT_SKIN_TONE = "\u270d\U0001f3fb" +WRITING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270d\U0001f3fc" +WRITING_HAND_MEDIUM_SKIN_TONE = "\u270d\U0001f3fd" +WRITING_HAND_MEDIUM_DARK_SKIN_TONE = "\u270d\U0001f3fe" +WRITING_HAND_DARK_SKIN_TONE = "\u270d\U0001f3ff" +NAIL_POLISH = "\U0001f485" +NAIL_POLISH_LIGHT_SKIN_TONE = "\U0001f485\U0001f3fb" +NAIL_POLISH_MEDIUM_LIGHT_SKIN_TONE = "\U0001f485\U0001f3fc" +NAIL_POLISH_MEDIUM_SKIN_TONE = "\U0001f485\U0001f3fd" +NAIL_POLISH_MEDIUM_DARK_SKIN_TONE = "\U0001f485\U0001f3fe" +NAIL_POLISH_DARK_SKIN_TONE = "\U0001f485\U0001f3ff" +SELFIE = "\U0001f933" +SELFIE_LIGHT_SKIN_TONE = "\U0001f933\U0001f3fb" +SELFIE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f933\U0001f3fc" +SELFIE_MEDIUM_SKIN_TONE = "\U0001f933\U0001f3fd" +SELFIE_MEDIUM_DARK_SKIN_TONE = "\U0001f933\U0001f3fe" +SELFIE_DARK_SKIN_TONE = "\U0001f933\U0001f3ff" +FLEXED_BICEPS = "\U0001f4aa" +FLEXED_BICEPS_LIGHT_SKIN_TONE = "\U0001f4aa\U0001f3fb" +FLEXED_BICEPS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f4aa\U0001f3fc" +FLEXED_BICEPS_MEDIUM_SKIN_TONE = "\U0001f4aa\U0001f3fd" +FLEXED_BICEPS_MEDIUM_DARK_SKIN_TONE = "\U0001f4aa\U0001f3fe" +FLEXED_BICEPS_DARK_SKIN_TONE = "\U0001f4aa\U0001f3ff" +MECHANICAL_ARM = "\U0001f9be" +MECHANICAL_LEG = "\U0001f9bf" +LEG = "\U0001f9b5" +LEG_LIGHT_SKIN_TONE = "\U0001f9b5\U0001f3fb" +LEG_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b5\U0001f3fc" +LEG_MEDIUM_SKIN_TONE = "\U0001f9b5\U0001f3fd" +LEG_MEDIUM_DARK_SKIN_TONE = "\U0001f9b5\U0001f3fe" +LEG_DARK_SKIN_TONE = "\U0001f9b5\U0001f3ff" +FOOT = "\U0001f9b6" +FOOT_LIGHT_SKIN_TONE = "\U0001f9b6\U0001f3fb" +FOOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b6\U0001f3fc" +FOOT_MEDIUM_SKIN_TONE = "\U0001f9b6\U0001f3fd" +FOOT_MEDIUM_DARK_SKIN_TONE = "\U0001f9b6\U0001f3fe" +FOOT_DARK_SKIN_TONE = "\U0001f9b6\U0001f3ff" +EAR = "\U0001f442" +EAR_LIGHT_SKIN_TONE = "\U0001f442\U0001f3fb" +EAR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f442\U0001f3fc" +EAR_MEDIUM_SKIN_TONE = "\U0001f442\U0001f3fd" +EAR_MEDIUM_DARK_SKIN_TONE = "\U0001f442\U0001f3fe" +EAR_DARK_SKIN_TONE = "\U0001f442\U0001f3ff" +EAR_WITH_HEARING_AID = "\U0001f9bb" +EAR_WITH_HEARING_AID_LIGHT_SKIN_TONE = "\U0001f9bb\U0001f3fb" +EAR_WITH_HEARING_AID_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9bb\U0001f3fc" +EAR_WITH_HEARING_AID_MEDIUM_SKIN_TONE = "\U0001f9bb\U0001f3fd" +EAR_WITH_HEARING_AID_MEDIUM_DARK_SKIN_TONE = "\U0001f9bb\U0001f3fe" +EAR_WITH_HEARING_AID_DARK_SKIN_TONE = "\U0001f9bb\U0001f3ff" +NOSE = "\U0001f443" +NOSE_LIGHT_SKIN_TONE = "\U0001f443\U0001f3fb" +NOSE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f443\U0001f3fc" +NOSE_MEDIUM_SKIN_TONE = "\U0001f443\U0001f3fd" +NOSE_MEDIUM_DARK_SKIN_TONE = "\U0001f443\U0001f3fe" +NOSE_DARK_SKIN_TONE = "\U0001f443\U0001f3ff" +BRAIN = "\U0001f9e0" +ANATOMICAL_HEART = "\U0001fac0" +LUNGS = "\U0001fac1" +TOOTH = "\U0001f9b7" +BONE = "\U0001f9b4" +EYES = "\U0001f440" +EYE = "\U0001f441\ufe0f" +TONGUE = "\U0001f445" +MOUTH = "\U0001f444" +BITING_LIP = "\U0001fae6" +BABY = "\U0001f476" +BABY_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fb" +BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fc" +BABY_MEDIUM_SKIN_TONE = "\U0001f476\U0001f3fd" +BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f476\U0001f3fe" +BABY_DARK_SKIN_TONE = "\U0001f476\U0001f3ff" +CHILD = "\U0001f9d2" +CHILD_LIGHT_SKIN_TONE = "\U0001f9d2\U0001f3fb" +CHILD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d2\U0001f3fc" +CHILD_MEDIUM_SKIN_TONE = "\U0001f9d2\U0001f3fd" +CHILD_MEDIUM_DARK_SKIN_TONE = "\U0001f9d2\U0001f3fe" +CHILD_DARK_SKIN_TONE = "\U0001f9d2\U0001f3ff" +BOY = "\U0001f466" +BOY_LIGHT_SKIN_TONE = "\U0001f466\U0001f3fb" +BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f466\U0001f3fc" +BOY_MEDIUM_SKIN_TONE = "\U0001f466\U0001f3fd" +BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f466\U0001f3fe" +BOY_DARK_SKIN_TONE = "\U0001f466\U0001f3ff" +GIRL = "\U0001f467" +GIRL_LIGHT_SKIN_TONE = "\U0001f467\U0001f3fb" +GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f467\U0001f3fc" +GIRL_MEDIUM_SKIN_TONE = "\U0001f467\U0001f3fd" +GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f467\U0001f3fe" +GIRL_DARK_SKIN_TONE = "\U0001f467\U0001f3ff" +PERSON = "\U0001f9d1" +PERSON_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb" +PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc" +PERSON_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd" +PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe" +PERSON_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff" +PERSON_BLOND_HAIR = "\U0001f471" +PERSON_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fb" +PERSON_MEDIUM_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fc" +PERSON_MEDIUM_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fd" +PERSON_MEDIUM_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fe" +PERSON_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3ff" +MAN = "\U0001f468" +MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb" +MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc" +MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd" +MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe" +MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff" +PERSON_BEARD = "\U0001f9d4" +PERSON_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb" +PERSON_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc" +PERSON_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd" +PERSON_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe" +PERSON_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff" +MAN_BEARD = "\U0001f9d4\u200d\u2642\ufe0f" +MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2642\ufe0f" +MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2642\ufe0f" +MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2642\ufe0f" +MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2642\ufe0f" +MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BEARD = "\U0001f9d4\u200d\u2640\ufe0f" +WOMAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2640\ufe0f" +MAN_RED_HAIR = "\U0001f468\u200d\U0001f9b0" +MAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b0" +MAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b0" +MAN_MEDIUM_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fd\u200d\U0001f9b0" +MAN_MEDIUM_DARK_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fe\u200d\U0001f9b0" +MAN_DARK_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3ff\u200d\U0001f9b0" +MAN_CURLY_HAIR = "\U0001f468\u200d\U0001f9b1" +MAN_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b1" +MAN_MEDIUM_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b1" +MAN_MEDIUM_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fd\u200d\U0001f9b1" +MAN_MEDIUM_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fe\u200d\U0001f9b1" +MAN_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3ff\u200d\U0001f9b1" +MAN_WHITE_HAIR = "\U0001f468\u200d\U0001f9b3" +MAN_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b3" +MAN_MEDIUM_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b3" +MAN_MEDIUM_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fd\u200d\U0001f9b3" +MAN_MEDIUM_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fe\u200d\U0001f9b3" +MAN_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3ff\u200d\U0001f9b3" +MAN_BALD = "\U0001f468\u200d\U0001f9b2" +MAN_LIGHT_SKIN_TONE_BALD = "\U0001f468\U0001f3fb\u200d\U0001f9b2" +MAN_MEDIUM_LIGHT_SKIN_TONE_BALD = "\U0001f468\U0001f3fc\u200d\U0001f9b2" +MAN_MEDIUM_SKIN_TONE_BALD = "\U0001f468\U0001f3fd\u200d\U0001f9b2" +MAN_MEDIUM_DARK_SKIN_TONE_BALD = "\U0001f468\U0001f3fe\u200d\U0001f9b2" +MAN_DARK_SKIN_TONE_BALD = "\U0001f468\U0001f3ff\u200d\U0001f9b2" +WOMAN = "\U0001f469" +WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb" +WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc" +WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd" +WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe" +WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff" +WOMAN_RED_HAIR = "\U0001f469\u200d\U0001f9b0" +WOMAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fb\u200d\U0001f9b0" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fc\u200d\U0001f9b0" +WOMAN_MEDIUM_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fd\u200d\U0001f9b0" +WOMAN_MEDIUM_DARK_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fe\u200d\U0001f9b0" +WOMAN_DARK_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3ff\u200d\U0001f9b0" +PERSON_RED_HAIR = "\U0001f9d1\u200d\U0001f9b0" +PERSON_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fb\u200d\U0001f9b0" +PERSON_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fc\u200d\U0001f9b0" +PERSON_MEDIUM_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fd\u200d\U0001f9b0" +PERSON_MEDIUM_DARK_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fe\u200d\U0001f9b0" +PERSON_DARK_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3ff\u200d\U0001f9b0" +WOMAN_CURLY_HAIR = "\U0001f469\u200d\U0001f9b1" +WOMAN_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fb\u200d\U0001f9b1" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fc\u200d\U0001f9b1" +WOMAN_MEDIUM_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fd\u200d\U0001f9b1" +WOMAN_MEDIUM_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fe\u200d\U0001f9b1" +WOMAN_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3ff\u200d\U0001f9b1" +PERSON_CURLY_HAIR = "\U0001f9d1\u200d\U0001f9b1" +PERSON_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fb\u200d\U0001f9b1" +PERSON_MEDIUM_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fc\u200d\U0001f9b1" +PERSON_MEDIUM_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fd\u200d\U0001f9b1" +PERSON_MEDIUM_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fe\u200d\U0001f9b1" +PERSON_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3ff\u200d\U0001f9b1" +WOMAN_WHITE_HAIR = "\U0001f469\u200d\U0001f9b3" +WOMAN_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fb\u200d\U0001f9b3" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fc\u200d\U0001f9b3" +WOMAN_MEDIUM_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fd\u200d\U0001f9b3" +WOMAN_MEDIUM_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fe\u200d\U0001f9b3" +WOMAN_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3ff\u200d\U0001f9b3" +PERSON_WHITE_HAIR = "\U0001f9d1\u200d\U0001f9b3" +PERSON_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fb\u200d\U0001f9b3" +PERSON_MEDIUM_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fc\u200d\U0001f9b3" +PERSON_MEDIUM_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fd\u200d\U0001f9b3" +PERSON_MEDIUM_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fe\u200d\U0001f9b3" +PERSON_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3ff\u200d\U0001f9b3" +WOMAN_BALD = "\U0001f469\u200d\U0001f9b2" +WOMAN_LIGHT_SKIN_TONE_BALD = "\U0001f469\U0001f3fb\u200d\U0001f9b2" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BALD = "\U0001f469\U0001f3fc\u200d\U0001f9b2" +WOMAN_MEDIUM_SKIN_TONE_BALD = "\U0001f469\U0001f3fd\u200d\U0001f9b2" +WOMAN_MEDIUM_DARK_SKIN_TONE_BALD = "\U0001f469\U0001f3fe\u200d\U0001f9b2" +WOMAN_DARK_SKIN_TONE_BALD = "\U0001f469\U0001f3ff\u200d\U0001f9b2" +PERSON_BALD = "\U0001f9d1\u200d\U0001f9b2" +PERSON_LIGHT_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fb\u200d\U0001f9b2" +PERSON_MEDIUM_LIGHT_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fc\u200d\U0001f9b2" +PERSON_MEDIUM_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fd\u200d\U0001f9b2" +PERSON_MEDIUM_DARK_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fe\u200d\U0001f9b2" +PERSON_DARK_SKIN_TONE_BALD = "\U0001f9d1\U0001f3ff\u200d\U0001f9b2" +WOMAN_BLOND_HAIR = "\U0001f471\u200d\u2640\ufe0f" +WOMAN_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MEDIUM_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MEDIUM_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3ff\u200d\u2640\ufe0f" +MAN_BLOND_HAIR = "\U0001f471\u200d\u2642\ufe0f" +MAN_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fb\u200d\u2642\ufe0f" +MAN_MEDIUM_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fc\u200d\u2642\ufe0f" +MAN_MEDIUM_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fd\u200d\u2642\ufe0f" +MAN_MEDIUM_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fe\u200d\u2642\ufe0f" +MAN_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3ff\u200d\u2642\ufe0f" +OLDER_PERSON = "\U0001f9d3" +OLDER_PERSON_LIGHT_SKIN_TONE = "\U0001f9d3\U0001f3fb" +OLDER_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d3\U0001f3fc" +OLDER_PERSON_MEDIUM_SKIN_TONE = "\U0001f9d3\U0001f3fd" +OLDER_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9d3\U0001f3fe" +OLDER_PERSON_DARK_SKIN_TONE = "\U0001f9d3\U0001f3ff" +OLD_MAN = "\U0001f474" +OLD_MAN_LIGHT_SKIN_TONE = "\U0001f474\U0001f3fb" +OLD_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f474\U0001f3fc" +OLD_MAN_MEDIUM_SKIN_TONE = "\U0001f474\U0001f3fd" +OLD_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f474\U0001f3fe" +OLD_MAN_DARK_SKIN_TONE = "\U0001f474\U0001f3ff" +OLD_WOMAN = "\U0001f475" +OLD_WOMAN_LIGHT_SKIN_TONE = "\U0001f475\U0001f3fb" +OLD_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f475\U0001f3fc" +OLD_WOMAN_MEDIUM_SKIN_TONE = "\U0001f475\U0001f3fd" +OLD_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f475\U0001f3fe" +OLD_WOMAN_DARK_SKIN_TONE = "\U0001f475\U0001f3ff" +PERSON_FROWNING = "\U0001f64d" +PERSON_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb" +PERSON_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc" +PERSON_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd" +PERSON_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe" +PERSON_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff" +MAN_FROWNING = "\U0001f64d\u200d\u2642\ufe0f" +MAN_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f" +MAN_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f" +MAN_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f" +MAN_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f" +MAN_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_FROWNING = "\U0001f64d\u200d\u2640\ufe0f" +WOMAN_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f" +PERSON_POUTING = "\U0001f64e" +PERSON_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb" +PERSON_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc" +PERSON_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd" +PERSON_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe" +PERSON_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff" +MAN_POUTING = "\U0001f64e\u200d\u2642\ufe0f" +MAN_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f" +MAN_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f" +MAN_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f" +MAN_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f" +MAN_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_POUTING = "\U0001f64e\u200d\u2640\ufe0f" +WOMAN_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f" +PERSON_GESTURING_NO = "\U0001f645" +PERSON_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb" +PERSON_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc" +PERSON_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd" +PERSON_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe" +PERSON_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff" +MAN_GESTURING_NO = "\U0001f645\u200d\u2642\ufe0f" +MAN_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb\u200d\u2642\ufe0f" +MAN_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc\u200d\u2642\ufe0f" +MAN_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd\u200d\u2642\ufe0f" +MAN_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe\u200d\u2642\ufe0f" +MAN_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GESTURING_NO = "\U0001f645\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff\u200d\u2640\ufe0f" +PERSON_GESTURING_OK = "\U0001f646" +PERSON_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb" +PERSON_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc" +PERSON_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd" +PERSON_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe" +PERSON_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff" +MAN_GESTURING_OK = "\U0001f646\u200d\u2642\ufe0f" +MAN_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb\u200d\u2642\ufe0f" +MAN_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc\u200d\u2642\ufe0f" +MAN_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd\u200d\u2642\ufe0f" +MAN_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe\u200d\u2642\ufe0f" +MAN_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GESTURING_OK = "\U0001f646\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff\u200d\u2640\ufe0f" +PERSON_TIPPING_HAND = "\U0001f481" +PERSON_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb" +PERSON_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc" +PERSON_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd" +PERSON_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe" +PERSON_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff" +MAN_TIPPING_HAND = "\U0001f481\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_TIPPING_HAND = "\U0001f481\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff\u200d\u2640\ufe0f" +PERSON_RAISING_HAND = "\U0001f64b" +PERSON_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb" +PERSON_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc" +PERSON_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd" +PERSON_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe" +PERSON_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff" +MAN_RAISING_HAND = "\U0001f64b\u200d\u2642\ufe0f" +MAN_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f" +MAN_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f" +MAN_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f" +MAN_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f" +MAN_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_RAISING_HAND = "\U0001f64b\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f" +DEAF_PERSON = "\U0001f9cf" +DEAF_PERSON_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fb" +DEAF_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fc" +DEAF_PERSON_MEDIUM_SKIN_TONE = "\U0001f9cf\U0001f3fd" +DEAF_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9cf\U0001f3fe" +DEAF_PERSON_DARK_SKIN_TONE = "\U0001f9cf\U0001f3ff" +DEAF_MAN = "\U0001f9cf\u200d\u2642\ufe0f" +DEAF_MAN_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f" +DEAF_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f" +DEAF_MAN_MEDIUM_SKIN_TONE = "\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f" +DEAF_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f" +DEAF_MAN_DARK_SKIN_TONE = "\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f" +DEAF_WOMAN = "\U0001f9cf\u200d\u2640\ufe0f" +DEAF_WOMAN_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f" +DEAF_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f" +DEAF_WOMAN_MEDIUM_SKIN_TONE = "\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f" +DEAF_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f" +DEAF_WOMAN_DARK_SKIN_TONE = "\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f" +PERSON_BOWING = "\U0001f647" +PERSON_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb" +PERSON_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc" +PERSON_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd" +PERSON_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe" +PERSON_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff" +MAN_BOWING = "\U0001f647\u200d\u2642\ufe0f" +MAN_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb\u200d\u2642\ufe0f" +MAN_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc\u200d\u2642\ufe0f" +MAN_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd\u200d\u2642\ufe0f" +MAN_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe\u200d\u2642\ufe0f" +MAN_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BOWING = "\U0001f647\u200d\u2640\ufe0f" +WOMAN_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff\u200d\u2640\ufe0f" +PERSON_FACEPALMING = "\U0001f926" +PERSON_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb" +PERSON_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc" +PERSON_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd" +PERSON_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe" +PERSON_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff" +MAN_FACEPALMING = "\U0001f926\u200d\u2642\ufe0f" +MAN_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb\u200d\u2642\ufe0f" +MAN_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc\u200d\u2642\ufe0f" +MAN_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd\u200d\u2642\ufe0f" +MAN_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe\u200d\u2642\ufe0f" +MAN_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_FACEPALMING = "\U0001f926\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff\u200d\u2640\ufe0f" +PERSON_SHRUGGING = "\U0001f937" +PERSON_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb" +PERSON_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc" +PERSON_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd" +PERSON_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe" +PERSON_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff" +MAN_SHRUGGING = "\U0001f937\u200d\u2642\ufe0f" +MAN_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb\u200d\u2642\ufe0f" +MAN_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc\u200d\u2642\ufe0f" +MAN_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd\u200d\u2642\ufe0f" +MAN_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe\u200d\u2642\ufe0f" +MAN_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SHRUGGING = "\U0001f937\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff\u200d\u2640\ufe0f" +HEALTH_WORKER = "\U0001f9d1\u200d\u2695\ufe0f" +HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f" +HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f" +HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f" +HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f" +HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER = "\U0001f468\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER = "\U0001f469\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2695\ufe0f" +STUDENT = "\U0001f9d1\u200d\U0001f393" +STUDENT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f393" +STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f393" +STUDENT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f393" +STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f393" +STUDENT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f393" +MAN_STUDENT = "\U0001f468\u200d\U0001f393" +MAN_STUDENT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f393" +MAN_STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f393" +MAN_STUDENT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f393" +MAN_STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f393" +MAN_STUDENT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f393" +WOMAN_STUDENT = "\U0001f469\u200d\U0001f393" +WOMAN_STUDENT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f393" +WOMAN_STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f393" +WOMAN_STUDENT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f393" +WOMAN_STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f393" +WOMAN_STUDENT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f393" +TEACHER = "\U0001f9d1\u200d\U0001f3eb" +TEACHER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3eb" +TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3eb" +TEACHER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3eb" +TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3eb" +TEACHER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3eb" +MAN_TEACHER = "\U0001f468\u200d\U0001f3eb" +MAN_TEACHER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3eb" +MAN_TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3eb" +MAN_TEACHER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3eb" +MAN_TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3eb" +MAN_TEACHER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3eb" +WOMAN_TEACHER = "\U0001f469\u200d\U0001f3eb" +WOMAN_TEACHER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3eb" +WOMAN_TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3eb" +WOMAN_TEACHER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3eb" +WOMAN_TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3eb" +WOMAN_TEACHER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3eb" +JUDGE = "\U0001f9d1\u200d\u2696\ufe0f" +JUDGE_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f" +JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f" +JUDGE_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f" +JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f" +JUDGE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f" +MAN_JUDGE = "\U0001f468\u200d\u2696\ufe0f" +MAN_JUDGE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2696\ufe0f" +MAN_JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2696\ufe0f" +MAN_JUDGE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2696\ufe0f" +MAN_JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2696\ufe0f" +MAN_JUDGE_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2696\ufe0f" +WOMAN_JUDGE = "\U0001f469\u200d\u2696\ufe0f" +WOMAN_JUDGE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2696\ufe0f" +WOMAN_JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2696\ufe0f" +WOMAN_JUDGE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2696\ufe0f" +WOMAN_JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2696\ufe0f" +WOMAN_JUDGE_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2696\ufe0f" +FARMER = "\U0001f9d1\u200d\U0001f33e" +FARMER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f33e" +FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f33e" +FARMER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f33e" +FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f33e" +FARMER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f33e" +MAN_FARMER = "\U0001f468\u200d\U0001f33e" +MAN_FARMER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f33e" +MAN_FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f33e" +MAN_FARMER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f33e" +MAN_FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f33e" +MAN_FARMER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f33e" +WOMAN_FARMER = "\U0001f469\u200d\U0001f33e" +WOMAN_FARMER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f33e" +WOMAN_FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f33e" +WOMAN_FARMER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f33e" +WOMAN_FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f33e" +WOMAN_FARMER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f33e" +COOK = "\U0001f9d1\u200d\U0001f373" +COOK_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f373" +COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f373" +COOK_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f373" +COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f373" +COOK_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f373" +MAN_COOK = "\U0001f468\u200d\U0001f373" +MAN_COOK_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f373" +MAN_COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f373" +MAN_COOK_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f373" +MAN_COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f373" +MAN_COOK_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f373" +WOMAN_COOK = "\U0001f469\u200d\U0001f373" +WOMAN_COOK_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f373" +WOMAN_COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f373" +WOMAN_COOK_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f373" +WOMAN_COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f373" +WOMAN_COOK_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f373" +MECHANIC = "\U0001f9d1\u200d\U0001f527" +MECHANIC_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f527" +MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f527" +MECHANIC_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f527" +MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f527" +MECHANIC_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f527" +MAN_MECHANIC = "\U0001f468\u200d\U0001f527" +MAN_MECHANIC_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f527" +MAN_MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f527" +MAN_MECHANIC_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f527" +MAN_MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f527" +MAN_MECHANIC_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f527" +WOMAN_MECHANIC = "\U0001f469\u200d\U0001f527" +WOMAN_MECHANIC_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f527" +WOMAN_MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f527" +WOMAN_MECHANIC_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f527" +WOMAN_MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f527" +WOMAN_MECHANIC_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f527" +FACTORY_WORKER = "\U0001f9d1\u200d\U0001f3ed" +FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3ed" +FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3ed" +FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3ed" +FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3ed" +FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3ed" +MAN_FACTORY_WORKER = "\U0001f468\u200d\U0001f3ed" +MAN_FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3ed" +MAN_FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3ed" +MAN_FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3ed" +MAN_FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3ed" +MAN_FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER = "\U0001f469\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3ed" +OFFICE_WORKER = "\U0001f9d1\u200d\U0001f4bc" +OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f4bc" +OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f4bc" +OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f4bc" +OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f4bc" +OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f4bc" +MAN_OFFICE_WORKER = "\U0001f468\u200d\U0001f4bc" +MAN_OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f4bc" +MAN_OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f4bc" +MAN_OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f4bc" +MAN_OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f4bc" +MAN_OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER = "\U0001f469\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f4bc" +SCIENTIST = "\U0001f9d1\u200d\U0001f52c" +SCIENTIST_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f52c" +SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f52c" +SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f52c" +SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f52c" +SCIENTIST_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f52c" +MAN_SCIENTIST = "\U0001f468\u200d\U0001f52c" +MAN_SCIENTIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f52c" +MAN_SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f52c" +MAN_SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f52c" +MAN_SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f52c" +MAN_SCIENTIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f52c" +WOMAN_SCIENTIST = "\U0001f469\u200d\U0001f52c" +WOMAN_SCIENTIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f52c" +WOMAN_SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f52c" +WOMAN_SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f52c" +WOMAN_SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f52c" +WOMAN_SCIENTIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f52c" +TECHNOLOGIST = "\U0001f9d1\u200d\U0001f4bb" +TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f4bb" +TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f4bb" +TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f4bb" +TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f4bb" +TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f4bb" +MAN_TECHNOLOGIST = "\U0001f468\u200d\U0001f4bb" +MAN_TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f4bb" +MAN_TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f4bb" +MAN_TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f4bb" +MAN_TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f4bb" +MAN_TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST = "\U0001f469\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f4bb" +SINGER = "\U0001f9d1\u200d\U0001f3a4" +SINGER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3a4" +SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3a4" +SINGER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3a4" +SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3a4" +SINGER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3a4" +MAN_SINGER = "\U0001f468\u200d\U0001f3a4" +MAN_SINGER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3a4" +MAN_SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3a4" +MAN_SINGER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3a4" +MAN_SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3a4" +MAN_SINGER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3a4" +WOMAN_SINGER = "\U0001f469\u200d\U0001f3a4" +WOMAN_SINGER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3a4" +WOMAN_SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3a4" +WOMAN_SINGER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3a4" +WOMAN_SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3a4" +WOMAN_SINGER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3a4" +ARTIST = "\U0001f9d1\u200d\U0001f3a8" +ARTIST_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3a8" +ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3a8" +ARTIST_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3a8" +ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3a8" +ARTIST_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3a8" +MAN_ARTIST = "\U0001f468\u200d\U0001f3a8" +MAN_ARTIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3a8" +MAN_ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3a8" +MAN_ARTIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3a8" +MAN_ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3a8" +MAN_ARTIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3a8" +WOMAN_ARTIST = "\U0001f469\u200d\U0001f3a8" +WOMAN_ARTIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3a8" +WOMAN_ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3a8" +WOMAN_ARTIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3a8" +WOMAN_ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3a8" +WOMAN_ARTIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3a8" +PILOT = "\U0001f9d1\u200d\u2708\ufe0f" +PILOT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f" +PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f" +PILOT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f" +PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f" +PILOT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f" +MAN_PILOT = "\U0001f468\u200d\u2708\ufe0f" +MAN_PILOT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2708\ufe0f" +MAN_PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2708\ufe0f" +MAN_PILOT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2708\ufe0f" +MAN_PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2708\ufe0f" +MAN_PILOT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2708\ufe0f" +WOMAN_PILOT = "\U0001f469\u200d\u2708\ufe0f" +WOMAN_PILOT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2708\ufe0f" +WOMAN_PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2708\ufe0f" +WOMAN_PILOT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2708\ufe0f" +WOMAN_PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2708\ufe0f" +WOMAN_PILOT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2708\ufe0f" +ASTRONAUT = "\U0001f9d1\u200d\U0001f680" +ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f680" +ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f680" +ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f680" +ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f680" +ASTRONAUT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f680" +MAN_ASTRONAUT = "\U0001f468\u200d\U0001f680" +MAN_ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f680" +MAN_ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f680" +MAN_ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f680" +MAN_ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f680" +MAN_ASTRONAUT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f680" +WOMAN_ASTRONAUT = "\U0001f469\u200d\U0001f680" +WOMAN_ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f680" +WOMAN_ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f680" +WOMAN_ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f680" +WOMAN_ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f680" +WOMAN_ASTRONAUT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f680" +FIREFIGHTER = "\U0001f9d1\u200d\U0001f692" +FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f692" +FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f692" +FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f692" +FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f692" +FIREFIGHTER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f692" +MAN_FIREFIGHTER = "\U0001f468\u200d\U0001f692" +MAN_FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f692" +MAN_FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f692" +MAN_FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f692" +MAN_FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f692" +MAN_FIREFIGHTER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f692" +WOMAN_FIREFIGHTER = "\U0001f469\u200d\U0001f692" +WOMAN_FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f692" +WOMAN_FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f692" +WOMAN_FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f692" +WOMAN_FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f692" +WOMAN_FIREFIGHTER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f692" +POLICE_OFFICER = "\U0001f46e" +POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb" +POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc" +POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd" +POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe" +POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff" +MAN_POLICE_OFFICER = "\U0001f46e\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_POLICE_OFFICER = "\U0001f46e\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f" +DETECTIVE = "\U0001f575\ufe0f" +DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb" +DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc" +DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd" +DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe" +DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff" +MAN_DETECTIVE = "\U0001f575\ufe0f\u200d\u2642\ufe0f" +MAN_DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb\u200d\u2642\ufe0f" +MAN_DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc\u200d\u2642\ufe0f" +MAN_DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd\u200d\u2642\ufe0f" +MAN_DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe\u200d\u2642\ufe0f" +MAN_DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_DETECTIVE = "\U0001f575\ufe0f\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff\u200d\u2640\ufe0f" +GUARD = "\U0001f482" +GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb" +GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc" +GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd" +GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe" +GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff" +MAN_GUARD = "\U0001f482\u200d\u2642\ufe0f" +MAN_GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb\u200d\u2642\ufe0f" +MAN_GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc\u200d\u2642\ufe0f" +MAN_GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd\u200d\u2642\ufe0f" +MAN_GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe\u200d\u2642\ufe0f" +MAN_GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GUARD = "\U0001f482\u200d\u2640\ufe0f" +WOMAN_GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff\u200d\u2640\ufe0f" +NINJA = "\U0001f977" +NINJA_LIGHT_SKIN_TONE = "\U0001f977\U0001f3fb" +NINJA_MEDIUM_LIGHT_SKIN_TONE = "\U0001f977\U0001f3fc" +NINJA_MEDIUM_SKIN_TONE = "\U0001f977\U0001f3fd" +NINJA_MEDIUM_DARK_SKIN_TONE = "\U0001f977\U0001f3fe" +NINJA_DARK_SKIN_TONE = "\U0001f977\U0001f3ff" +CONSTRUCTION_WORKER = "\U0001f477" +CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb" +CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc" +CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd" +CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe" +CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff" +MAN_CONSTRUCTION_WORKER = "\U0001f477\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_CONSTRUCTION_WORKER = "\U0001f477\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_CROWN = "\U0001fac5" +PERSON_WITH_CROWN_LIGHT_SKIN_TONE = "\U0001fac5\U0001f3fb" +PERSON_WITH_CROWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac5\U0001f3fc" +PERSON_WITH_CROWN_MEDIUM_SKIN_TONE = "\U0001fac5\U0001f3fd" +PERSON_WITH_CROWN_MEDIUM_DARK_SKIN_TONE = "\U0001fac5\U0001f3fe" +PERSON_WITH_CROWN_DARK_SKIN_TONE = "\U0001fac5\U0001f3ff" +PRINCE = "\U0001f934" +PRINCE_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fb" +PRINCE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fc" +PRINCE_MEDIUM_SKIN_TONE = "\U0001f934\U0001f3fd" +PRINCE_MEDIUM_DARK_SKIN_TONE = "\U0001f934\U0001f3fe" +PRINCE_DARK_SKIN_TONE = "\U0001f934\U0001f3ff" +PRINCESS = "\U0001f478" +PRINCESS_LIGHT_SKIN_TONE = "\U0001f478\U0001f3fb" +PRINCESS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f478\U0001f3fc" +PRINCESS_MEDIUM_SKIN_TONE = "\U0001f478\U0001f3fd" +PRINCESS_MEDIUM_DARK_SKIN_TONE = "\U0001f478\U0001f3fe" +PRINCESS_DARK_SKIN_TONE = "\U0001f478\U0001f3ff" +PERSON_WEARING_TURBAN = "\U0001f473" +PERSON_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb" +PERSON_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc" +PERSON_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd" +PERSON_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe" +PERSON_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff" +MAN_WEARING_TURBAN = "\U0001f473\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_WEARING_TURBAN = "\U0001f473\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_SKULLCAP = "\U0001f472" +PERSON_WITH_SKULLCAP_LIGHT_SKIN_TONE = "\U0001f472\U0001f3fb" +PERSON_WITH_SKULLCAP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f472\U0001f3fc" +PERSON_WITH_SKULLCAP_MEDIUM_SKIN_TONE = "\U0001f472\U0001f3fd" +PERSON_WITH_SKULLCAP_MEDIUM_DARK_SKIN_TONE = "\U0001f472\U0001f3fe" +PERSON_WITH_SKULLCAP_DARK_SKIN_TONE = "\U0001f472\U0001f3ff" +WOMAN_WITH_HEADSCARF = "\U0001f9d5" +WOMAN_WITH_HEADSCARF_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fb" +WOMAN_WITH_HEADSCARF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fc" +WOMAN_WITH_HEADSCARF_MEDIUM_SKIN_TONE = "\U0001f9d5\U0001f3fd" +WOMAN_WITH_HEADSCARF_MEDIUM_DARK_SKIN_TONE = "\U0001f9d5\U0001f3fe" +WOMAN_WITH_HEADSCARF_DARK_SKIN_TONE = "\U0001f9d5\U0001f3ff" +PERSON_IN_TUXEDO = "\U0001f935" +PERSON_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb" +PERSON_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc" +PERSON_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd" +PERSON_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe" +PERSON_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff" +MAN_IN_TUXEDO = "\U0001f935\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_IN_TUXEDO = "\U0001f935\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_VEIL = "\U0001f470" +PERSON_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb" +PERSON_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc" +PERSON_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd" +PERSON_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe" +PERSON_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff" +MAN_WITH_VEIL = "\U0001f470\u200d\u2642\ufe0f" +MAN_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb\u200d\u2642\ufe0f" +MAN_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc\u200d\u2642\ufe0f" +MAN_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd\u200d\u2642\ufe0f" +MAN_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe\u200d\u2642\ufe0f" +MAN_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_WITH_VEIL = "\U0001f470\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff\u200d\u2640\ufe0f" +PREGNANT_WOMAN = "\U0001f930" +PREGNANT_WOMAN_LIGHT_SKIN_TONE = "\U0001f930\U0001f3fb" +PREGNANT_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f930\U0001f3fc" +PREGNANT_WOMAN_MEDIUM_SKIN_TONE = "\U0001f930\U0001f3fd" +PREGNANT_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f930\U0001f3fe" +PREGNANT_WOMAN_DARK_SKIN_TONE = "\U0001f930\U0001f3ff" +PREGNANT_MAN = "\U0001fac3" +PREGNANT_MAN_LIGHT_SKIN_TONE = "\U0001fac3\U0001f3fb" +PREGNANT_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac3\U0001f3fc" +PREGNANT_MAN_MEDIUM_SKIN_TONE = "\U0001fac3\U0001f3fd" +PREGNANT_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001fac3\U0001f3fe" +PREGNANT_MAN_DARK_SKIN_TONE = "\U0001fac3\U0001f3ff" +PREGNANT_PERSON = "\U0001fac4" +PREGNANT_PERSON_LIGHT_SKIN_TONE = "\U0001fac4\U0001f3fb" +PREGNANT_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac4\U0001f3fc" +PREGNANT_PERSON_MEDIUM_SKIN_TONE = "\U0001fac4\U0001f3fd" +PREGNANT_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001fac4\U0001f3fe" +PREGNANT_PERSON_DARK_SKIN_TONE = "\U0001fac4\U0001f3ff" +BREAST_FEEDING = "\U0001f931" +BREAST_FEEDING_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fb" +BREAST_FEEDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fc" +BREAST_FEEDING_MEDIUM_SKIN_TONE = "\U0001f931\U0001f3fd" +BREAST_FEEDING_MEDIUM_DARK_SKIN_TONE = "\U0001f931\U0001f3fe" +BREAST_FEEDING_DARK_SKIN_TONE = "\U0001f931\U0001f3ff" +WOMAN_FEEDING_BABY = "\U0001f469\u200d\U0001f37c" +WOMAN_FEEDING_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f37c" +WOMAN_FEEDING_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f37c" +WOMAN_FEEDING_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f37c" +WOMAN_FEEDING_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f37c" +WOMAN_FEEDING_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f37c" +MAN_FEEDING_BABY = "\U0001f468\u200d\U0001f37c" +MAN_FEEDING_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f37c" +MAN_FEEDING_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f37c" +MAN_FEEDING_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f37c" +MAN_FEEDING_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f37c" +MAN_FEEDING_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f37c" +PERSON_FEEDING_BABY = "\U0001f9d1\u200d\U0001f37c" +PERSON_FEEDING_BABY_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f37c" +PERSON_FEEDING_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f37c" +PERSON_FEEDING_BABY_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f37c" +PERSON_FEEDING_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f37c" +PERSON_FEEDING_BABY_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f37c" +BABY_ANGEL = "\U0001f47c" +BABY_ANGEL_LIGHT_SKIN_TONE = "\U0001f47c\U0001f3fb" +BABY_ANGEL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f47c\U0001f3fc" +BABY_ANGEL_MEDIUM_SKIN_TONE = "\U0001f47c\U0001f3fd" +BABY_ANGEL_MEDIUM_DARK_SKIN_TONE = "\U0001f47c\U0001f3fe" +BABY_ANGEL_DARK_SKIN_TONE = "\U0001f47c\U0001f3ff" +SANTA_CLAUS = "\U0001f385" +SANTA_CLAUS_LIGHT_SKIN_TONE = "\U0001f385\U0001f3fb" +SANTA_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f385\U0001f3fc" +SANTA_CLAUS_MEDIUM_SKIN_TONE = "\U0001f385\U0001f3fd" +SANTA_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f385\U0001f3fe" +SANTA_CLAUS_DARK_SKIN_TONE = "\U0001f385\U0001f3ff" +MRS_CLAUS = "\U0001f936" +MRS_CLAUS_LIGHT_SKIN_TONE = "\U0001f936\U0001f3fb" +MRS_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f936\U0001f3fc" +MRS_CLAUS_MEDIUM_SKIN_TONE = "\U0001f936\U0001f3fd" +MRS_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f936\U0001f3fe" +MRS_CLAUS_DARK_SKIN_TONE = "\U0001f936\U0001f3ff" +MX_CLAUS = "\U0001f9d1\u200d\U0001f384" +MX_CLAUS_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f384" +MX_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f384" +MX_CLAUS_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f384" +MX_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f384" +MX_CLAUS_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f384" +SUPERHERO = "\U0001f9b8" +SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb" +SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc" +SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd" +SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe" +SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff" +MAN_SUPERHERO = "\U0001f9b8\u200d\u2642\ufe0f" +MAN_SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f" +MAN_SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f" +MAN_SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f" +MAN_SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f" +MAN_SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SUPERHERO = "\U0001f9b8\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f" +SUPERVILLAIN = "\U0001f9b9" +SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb" +SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc" +SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd" +SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe" +SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff" +MAN_SUPERVILLAIN = "\U0001f9b9\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SUPERVILLAIN = "\U0001f9b9\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f" +MAGE = "\U0001f9d9" +MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb" +MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc" +MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd" +MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe" +MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff" +MAN_MAGE = "\U0001f9d9\u200d\u2642\ufe0f" +MAN_MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f" +MAN_MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f" +MAN_MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f" +MAN_MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f" +MAN_MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_MAGE = "\U0001f9d9\u200d\u2640\ufe0f" +WOMAN_MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f" +FAIRY = "\U0001f9da" +FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb" +FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc" +FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd" +FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe" +FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff" +MAN_FAIRY = "\U0001f9da\u200d\u2642\ufe0f" +MAN_FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f" +MAN_FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f" +MAN_FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f" +MAN_FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f" +MAN_FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_FAIRY = "\U0001f9da\u200d\u2640\ufe0f" +WOMAN_FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f" +VAMPIRE = "\U0001f9db" +VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb" +VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc" +VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd" +VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe" +VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff" +MAN_VAMPIRE = "\U0001f9db\u200d\u2642\ufe0f" +MAN_VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f" +MAN_VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f" +MAN_VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f" +MAN_VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f" +MAN_VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_VAMPIRE = "\U0001f9db\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f" +MERPERSON = "\U0001f9dc" +MERPERSON_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb" +MERPERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc" +MERPERSON_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd" +MERPERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe" +MERPERSON_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff" +MERMAN = "\U0001f9dc\u200d\u2642\ufe0f" +MERMAN_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f" +MERMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f" +MERMAN_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f" +MERMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f" +MERMAN_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f" +MERMAID = "\U0001f9dc\u200d\u2640\ufe0f" +MERMAID_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f" +MERMAID_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f" +MERMAID_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f" +MERMAID_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f" +MERMAID_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f" +ELF = "\U0001f9dd" +ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb" +ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc" +ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd" +ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe" +ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff" +MAN_ELF = "\U0001f9dd\u200d\u2642\ufe0f" +MAN_ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f" +MAN_ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f" +MAN_ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f" +MAN_ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f" +MAN_ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_ELF = "\U0001f9dd\u200d\u2640\ufe0f" +WOMAN_ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f" +GENIE = "\U0001f9de" +MAN_GENIE = "\U0001f9de\u200d\u2642\ufe0f" +WOMAN_GENIE = "\U0001f9de\u200d\u2640\ufe0f" +ZOMBIE = "\U0001f9df" +MAN_ZOMBIE = "\U0001f9df\u200d\u2642\ufe0f" +WOMAN_ZOMBIE = "\U0001f9df\u200d\u2640\ufe0f" +TROLL = "\U0001f9cc" +PERSON_GETTING_MASSAGE = "\U0001f486" +PERSON_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb" +PERSON_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc" +PERSON_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd" +PERSON_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe" +PERSON_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff" +MAN_GETTING_MASSAGE = "\U0001f486\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GETTING_MASSAGE = "\U0001f486\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff\u200d\u2640\ufe0f" +PERSON_GETTING_HAIRCUT = "\U0001f487" +PERSON_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb" +PERSON_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc" +PERSON_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd" +PERSON_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe" +PERSON_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff" +MAN_GETTING_HAIRCUT = "\U0001f487\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GETTING_HAIRCUT = "\U0001f487\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WALKING = "\U0001f6b6" +PERSON_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb" +PERSON_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc" +PERSON_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd" +PERSON_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe" +PERSON_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff" +MAN_WALKING = "\U0001f6b6\u200d\u2642\ufe0f" +MAN_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f" +MAN_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f" +MAN_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f" +MAN_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f" +MAN_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_WALKING = "\U0001f6b6\u200d\u2640\ufe0f" +WOMAN_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f" +PERSON_STANDING = "\U0001f9cd" +PERSON_STANDING_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fb" +PERSON_STANDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fc" +PERSON_STANDING_MEDIUM_SKIN_TONE = "\U0001f9cd\U0001f3fd" +PERSON_STANDING_MEDIUM_DARK_SKIN_TONE = "\U0001f9cd\U0001f3fe" +PERSON_STANDING_DARK_SKIN_TONE = "\U0001f9cd\U0001f3ff" +MAN_STANDING = "\U0001f9cd\u200d\u2642\ufe0f" +MAN_STANDING_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f" +MAN_STANDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f" +MAN_STANDING_MEDIUM_SKIN_TONE = "\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f" +MAN_STANDING_MEDIUM_DARK_SKIN_TONE = "\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f" +MAN_STANDING_DARK_SKIN_TONE = "\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_STANDING = "\U0001f9cd\u200d\u2640\ufe0f" +WOMAN_STANDING_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_STANDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_STANDING_MEDIUM_SKIN_TONE = "\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_STANDING_MEDIUM_DARK_SKIN_TONE = "\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_STANDING_DARK_SKIN_TONE = "\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f" +PERSON_KNEELING = "\U0001f9ce" +PERSON_KNEELING_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fb" +PERSON_KNEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fc" +PERSON_KNEELING_MEDIUM_SKIN_TONE = "\U0001f9ce\U0001f3fd" +PERSON_KNEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f9ce\U0001f3fe" +PERSON_KNEELING_DARK_SKIN_TONE = "\U0001f9ce\U0001f3ff" +MAN_KNEELING = "\U0001f9ce\u200d\u2642\ufe0f" +MAN_KNEELING_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f" +MAN_KNEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f" +MAN_KNEELING_MEDIUM_SKIN_TONE = "\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f" +MAN_KNEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f" +MAN_KNEELING_DARK_SKIN_TONE = "\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_KNEELING = "\U0001f9ce\u200d\u2640\ufe0f" +WOMAN_KNEELING_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_KNEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_KNEELING_MEDIUM_SKIN_TONE = "\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_KNEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_KNEELING_DARK_SKIN_TONE = "\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_WHITE_CANE = "\U0001f9d1\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f9af" +MAN_WITH_WHITE_CANE = "\U0001f468\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE = "\U0001f469\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9af" +PERSON_IN_MOTORIZED_WHEELCHAIR = "\U0001f9d1\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR = "\U0001f468\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR = "\U0001f469\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9bc" +PERSON_IN_MANUAL_WHEELCHAIR = "\U0001f9d1\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR = "\U0001f468\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR = "\U0001f469\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9bd" +PERSON_RUNNING = "\U0001f3c3" +PERSON_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb" +PERSON_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc" +PERSON_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd" +PERSON_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe" +PERSON_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff" +MAN_RUNNING = "\U0001f3c3\u200d\u2642\ufe0f" +MAN_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f" +MAN_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f" +MAN_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f" +MAN_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f" +MAN_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_RUNNING = "\U0001f3c3\u200d\u2640\ufe0f" +WOMAN_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f" +WOMAN_DANCING = "\U0001f483" +WOMAN_DANCING_LIGHT_SKIN_TONE = "\U0001f483\U0001f3fb" +WOMAN_DANCING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f483\U0001f3fc" +WOMAN_DANCING_MEDIUM_SKIN_TONE = "\U0001f483\U0001f3fd" +WOMAN_DANCING_MEDIUM_DARK_SKIN_TONE = "\U0001f483\U0001f3fe" +WOMAN_DANCING_DARK_SKIN_TONE = "\U0001f483\U0001f3ff" +MAN_DANCING = "\U0001f57a" +MAN_DANCING_LIGHT_SKIN_TONE = "\U0001f57a\U0001f3fb" +MAN_DANCING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f57a\U0001f3fc" +MAN_DANCING_MEDIUM_SKIN_TONE = "\U0001f57a\U0001f3fd" +MAN_DANCING_MEDIUM_DARK_SKIN_TONE = "\U0001f57a\U0001f3fe" +MAN_DANCING_DARK_SKIN_TONE = "\U0001f57a\U0001f3ff" +PERSON_IN_SUIT_LEVITATING = "\U0001f574\ufe0f" +PERSON_IN_SUIT_LEVITATING_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fb" +PERSON_IN_SUIT_LEVITATING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fc" +PERSON_IN_SUIT_LEVITATING_MEDIUM_SKIN_TONE = "\U0001f574\U0001f3fd" +PERSON_IN_SUIT_LEVITATING_MEDIUM_DARK_SKIN_TONE = "\U0001f574\U0001f3fe" +PERSON_IN_SUIT_LEVITATING_DARK_SKIN_TONE = "\U0001f574\U0001f3ff" +PEOPLE_WITH_BUNNY_EARS = "\U0001f46f" +MEN_WITH_BUNNY_EARS = "\U0001f46f\u200d\u2642\ufe0f" +WOMEN_WITH_BUNNY_EARS = "\U0001f46f\u200d\u2640\ufe0f" +PERSON_IN_STEAMY_ROOM = "\U0001f9d6" +PERSON_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb" +PERSON_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc" +PERSON_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd" +PERSON_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe" +PERSON_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff" +MAN_IN_STEAMY_ROOM = "\U0001f9d6\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_IN_STEAMY_ROOM = "\U0001f9d6\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f" +PERSON_CLIMBING = "\U0001f9d7" +PERSON_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb" +PERSON_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc" +PERSON_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd" +PERSON_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe" +PERSON_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff" +MAN_CLIMBING = "\U0001f9d7\u200d\u2642\ufe0f" +MAN_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f" +MAN_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f" +MAN_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f" +MAN_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f" +MAN_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_CLIMBING = "\U0001f9d7\u200d\u2640\ufe0f" +WOMAN_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f" +PERSON_FENCING = "\U0001f93a" +HORSE_RACING = "\U0001f3c7" +HORSE_RACING_LIGHT_SKIN_TONE = "\U0001f3c7\U0001f3fb" +HORSE_RACING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c7\U0001f3fc" +HORSE_RACING_MEDIUM_SKIN_TONE = "\U0001f3c7\U0001f3fd" +HORSE_RACING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c7\U0001f3fe" +HORSE_RACING_DARK_SKIN_TONE = "\U0001f3c7\U0001f3ff" +SKIER = "\u26f7\ufe0f" +SNOWBOARDER = "\U0001f3c2" +SNOWBOARDER_LIGHT_SKIN_TONE = "\U0001f3c2\U0001f3fb" +SNOWBOARDER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c2\U0001f3fc" +SNOWBOARDER_MEDIUM_SKIN_TONE = "\U0001f3c2\U0001f3fd" +SNOWBOARDER_MEDIUM_DARK_SKIN_TONE = "\U0001f3c2\U0001f3fe" +SNOWBOARDER_DARK_SKIN_TONE = "\U0001f3c2\U0001f3ff" +PERSON_GOLFING = "\U0001f3cc\ufe0f" +PERSON_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb" +PERSON_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc" +PERSON_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd" +PERSON_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe" +PERSON_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff" +MAN_GOLFING = "\U0001f3cc\ufe0f\u200d\u2642\ufe0f" +MAN_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f" +MAN_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f" +MAN_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f" +MAN_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f" +MAN_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GOLFING = "\U0001f3cc\ufe0f\u200d\u2640\ufe0f" +WOMAN_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f" +PERSON_SURFING = "\U0001f3c4" +PERSON_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb" +PERSON_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc" +PERSON_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd" +PERSON_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe" +PERSON_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff" +MAN_SURFING = "\U0001f3c4\u200d\u2642\ufe0f" +MAN_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f" +MAN_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f" +MAN_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f" +MAN_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f" +MAN_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SURFING = "\U0001f3c4\u200d\u2640\ufe0f" +WOMAN_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f" +PERSON_ROWING_BOAT = "\U0001f6a3" +PERSON_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb" +PERSON_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc" +PERSON_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd" +PERSON_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe" +PERSON_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff" +MAN_ROWING_BOAT = "\U0001f6a3\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_ROWING_BOAT = "\U0001f6a3\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f" +PERSON_SWIMMING = "\U0001f3ca" +PERSON_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb" +PERSON_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc" +PERSON_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd" +PERSON_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe" +PERSON_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff" +MAN_SWIMMING = "\U0001f3ca\u200d\u2642\ufe0f" +MAN_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f" +MAN_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f" +MAN_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f" +MAN_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f" +MAN_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SWIMMING = "\U0001f3ca\u200d\u2640\ufe0f" +WOMAN_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f" +PERSON_BOUNCING_BALL = "\u26f9\ufe0f" +PERSON_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb" +PERSON_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc" +PERSON_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd" +PERSON_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe" +PERSON_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff" +MAN_BOUNCING_BALL = "\u26f9\ufe0f\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BOUNCING_BALL = "\u26f9\ufe0f\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff\u200d\u2640\ufe0f" +PERSON_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f" +PERSON_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb" +PERSON_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc" +PERSON_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd" +PERSON_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe" +PERSON_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff" +MAN_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f" +PERSON_BIKING = "\U0001f6b4" +PERSON_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb" +PERSON_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc" +PERSON_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd" +PERSON_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe" +PERSON_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff" +MAN_BIKING = "\U0001f6b4\u200d\u2642\ufe0f" +MAN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f" +MAN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f" +MAN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f" +MAN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f" +MAN_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BIKING = "\U0001f6b4\u200d\u2640\ufe0f" +WOMAN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f" +PERSON_MOUNTAIN_BIKING = "\U0001f6b5" +PERSON_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb" +PERSON_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc" +PERSON_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd" +PERSON_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe" +PERSON_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff" +MAN_MOUNTAIN_BIKING = "\U0001f6b5\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_MOUNTAIN_BIKING = "\U0001f6b5\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f" +PERSON_CARTWHEELING = "\U0001f938" +PERSON_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb" +PERSON_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc" +PERSON_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd" +PERSON_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe" +PERSON_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff" +MAN_CARTWHEELING = "\U0001f938\u200d\u2642\ufe0f" +MAN_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb\u200d\u2642\ufe0f" +MAN_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc\u200d\u2642\ufe0f" +MAN_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd\u200d\u2642\ufe0f" +MAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2642\ufe0f" +MAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_CARTWHEELING = "\U0001f938\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f" +PEOPLE_WRESTLING = "\U0001f93c" +MEN_WRESTLING = "\U0001f93c\u200d\u2642\ufe0f" +WOMEN_WRESTLING = "\U0001f93c\u200d\u2640\ufe0f" +PERSON_PLAYING_WATER_POLO = "\U0001f93d" +PERSON_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb" +PERSON_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc" +PERSON_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd" +PERSON_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe" +PERSON_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff" +MAN_PLAYING_WATER_POLO = "\U0001f93d\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_PLAYING_WATER_POLO = "\U0001f93d\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f" +PERSON_PLAYING_HANDBALL = "\U0001f93e" +PERSON_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb" +PERSON_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc" +PERSON_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd" +PERSON_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe" +PERSON_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff" +MAN_PLAYING_HANDBALL = "\U0001f93e\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_PLAYING_HANDBALL = "\U0001f93e\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f" +PERSON_JUGGLING = "\U0001f939" +PERSON_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb" +PERSON_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc" +PERSON_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd" +PERSON_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe" +PERSON_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff" +MAN_JUGGLING = "\U0001f939\u200d\u2642\ufe0f" +MAN_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb\u200d\u2642\ufe0f" +MAN_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc\u200d\u2642\ufe0f" +MAN_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd\u200d\u2642\ufe0f" +MAN_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe\u200d\u2642\ufe0f" +MAN_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_JUGGLING = "\U0001f939\u200d\u2640\ufe0f" +WOMAN_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff\u200d\u2640\ufe0f" +PERSON_IN_LOTUS_POSITION = "\U0001f9d8" +PERSON_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb" +PERSON_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc" +PERSON_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd" +PERSON_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe" +PERSON_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff" +MAN_IN_LOTUS_POSITION = "\U0001f9d8\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_IN_LOTUS_POSITION = "\U0001f9d8\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f" +PERSON_TAKING_BATH = "\U0001f6c0" +PERSON_TAKING_BATH_LIGHT_SKIN_TONE = "\U0001f6c0\U0001f3fb" +PERSON_TAKING_BATH_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6c0\U0001f3fc" +PERSON_TAKING_BATH_MEDIUM_SKIN_TONE = "\U0001f6c0\U0001f3fd" +PERSON_TAKING_BATH_MEDIUM_DARK_SKIN_TONE = "\U0001f6c0\U0001f3fe" +PERSON_TAKING_BATH_DARK_SKIN_TONE = "\U0001f6c0\U0001f3ff" +PERSON_IN_BED = "\U0001f6cc" +PERSON_IN_BED_LIGHT_SKIN_TONE = "\U0001f6cc\U0001f3fb" +PERSON_IN_BED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6cc\U0001f3fc" +PERSON_IN_BED_MEDIUM_SKIN_TONE = "\U0001f6cc\U0001f3fd" +PERSON_IN_BED_MEDIUM_DARK_SKIN_TONE = "\U0001f6cc\U0001f3fe" +PERSON_IN_BED_DARK_SKIN_TONE = "\U0001f6cc\U0001f3ff" +PEOPLE_HOLDING_HANDS = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +WOMEN_HOLDING_HANDS = "\U0001f46d" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f46d\U0001f3fb" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46d\U0001f3fc" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f46d\U0001f3fd" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f46d\U0001f3fe" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46d\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS = "\U0001f46b" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f46b\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46b\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f46b\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f46b\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46b\U0001f3ff" +MEN_HOLDING_HANDS = "\U0001f46c" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f46c\U0001f3fb" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46c\U0001f3fc" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f46c\U0001f3fd" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f46c\U0001f3fe" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46c\U0001f3ff" +KISS = "\U0001f48f" +KISS_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fb" +KISS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fc" +KISS_MEDIUM_SKIN_TONE = "\U0001f48f\U0001f3fd" +KISS_MEDIUM_DARK_SKIN_TONE = "\U0001f48f\U0001f3fe" +KISS_DARK_SKIN_TONE = "\U0001f48f\U0001f3ff" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" +KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART = "\U0001f491" +COUPLE_WITH_HEART_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fb" +COUPLE_WITH_HEART_MEDIUM_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fc" +COUPLE_WITH_HEART_MEDIUM_SKIN_TONE = "\U0001f491\U0001f3fd" +COUPLE_WITH_HEART_MEDIUM_DARK_SKIN_TONE = "\U0001f491\U0001f3fe" +COUPLE_WITH_HEART_DARK_SKIN_TONE = "\U0001f491\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +FAMILY = "\U0001f46a" +FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466" +FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467" +FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" +FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466" +FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467" +FAMILY_MAN_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_MAN_BOY_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467" +FAMILY_WOMAN_WOMAN_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f466" +FAMILY_WOMAN_WOMAN_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f467" +FAMILY_WOMAN_WOMAN_GIRL_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_WOMAN_WOMAN_BOY_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_WOMAN_WOMAN_GIRL_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" +FAMILY_MAN_BOY = "\U0001f468\u200d\U0001f466" +FAMILY_MAN_BOY_BOY = "\U0001f468\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_GIRL = "\U0001f468\u200d\U0001f467" +FAMILY_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f467\u200d\U0001f467" +FAMILY_WOMAN_BOY = "\U0001f469\u200d\U0001f466" +FAMILY_WOMAN_BOY_BOY = "\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_WOMAN_GIRL = "\U0001f469\u200d\U0001f467" +FAMILY_WOMAN_GIRL_BOY = "\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_WOMAN_GIRL_GIRL = "\U0001f469\u200d\U0001f467\u200d\U0001f467" +SPEAKING_HEAD = "\U0001f5e3\ufe0f" +BUST_IN_SILHOUETTE = "\U0001f464" +BUSTS_IN_SILHOUETTE = "\U0001f465" +PEOPLE_HUGGING = "\U0001fac2" +FOOTPRINTS = "\U0001f463" +LIGHT_SKIN_TONE = "\U0001f3fb" +MEDIUM_LIGHT_SKIN_TONE = "\U0001f3fc" +MEDIUM_SKIN_TONE = "\U0001f3fd" +MEDIUM_DARK_SKIN_TONE = "\U0001f3fe" +DARK_SKIN_TONE = "\U0001f3ff" +RED_HAIR = "\U0001f9b0" +CURLY_HAIR = "\U0001f9b1" +WHITE_HAIR = "\U0001f9b3" +BALD = "\U0001f9b2" +MONKEY_FACE = "\U0001f435" +MONKEY = "\U0001f412" +GORILLA = "\U0001f98d" +ORANGUTAN = "\U0001f9a7" +DOG_FACE = "\U0001f436" +DOG = "\U0001f415" +GUIDE_DOG = "\U0001f9ae" +SERVICE_DOG = "\U0001f415\u200d\U0001f9ba" +POODLE = "\U0001f429" +WOLF = "\U0001f43a" +FOX = "\U0001f98a" +RACCOON = "\U0001f99d" +CAT_FACE = "\U0001f431" +CAT = "\U0001f408" +BLACK_CAT = "\U0001f408\u200d\u2b1b" +LION = "\U0001f981" +TIGER_FACE = "\U0001f42f" +TIGER = "\U0001f405" +LEOPARD = "\U0001f406" +HORSE_FACE = "\U0001f434" +HORSE = "\U0001f40e" +UNICORN = "\U0001f984" +ZEBRA = "\U0001f993" +DEER = "\U0001f98c" +BISON = "\U0001f9ac" +COW_FACE = "\U0001f42e" +OX = "\U0001f402" +WATER_BUFFALO = "\U0001f403" +COW = "\U0001f404" +PIG_FACE = "\U0001f437" +PIG = "\U0001f416" +BOAR = "\U0001f417" +PIG_NOSE = "\U0001f43d" +RAM = "\U0001f40f" +EWE = "\U0001f411" +GOAT = "\U0001f410" +CAMEL = "\U0001f42a" +TWO_HUMP_CAMEL = "\U0001f42b" +LLAMA = "\U0001f999" +GIRAFFE = "\U0001f992" +ELEPHANT = "\U0001f418" +MAMMOTH = "\U0001f9a3" +RHINOCEROS = "\U0001f98f" +HIPPOPOTAMUS = "\U0001f99b" +MOUSE_FACE = "\U0001f42d" +MOUSE = "\U0001f401" +RAT = "\U0001f400" +HAMSTER = "\U0001f439" +RABBIT_FACE = "\U0001f430" +RABBIT = "\U0001f407" +CHIPMUNK = "\U0001f43f\ufe0f" +BEAVER = "\U0001f9ab" +HEDGEHOG = "\U0001f994" +BAT = "\U0001f987" +BEAR = "\U0001f43b" +POLAR_BEAR = "\U0001f43b\u200d\u2744\ufe0f" +KOALA = "\U0001f428" +PANDA = "\U0001f43c" +SLOTH = "\U0001f9a5" +OTTER = "\U0001f9a6" +SKUNK = "\U0001f9a8" +KANGAROO = "\U0001f998" +BADGER = "\U0001f9a1" +PAW_PRINTS = "\U0001f43e" +TURKEY = "\U0001f983" +CHICKEN = "\U0001f414" +ROOSTER = "\U0001f413" +HATCHING_CHICK = "\U0001f423" +BABY_CHICK = "\U0001f424" +FRONT_FACING_BABY_CHICK = "\U0001f425" +BIRD = "\U0001f426" +PENGUIN = "\U0001f427" +DOVE = "\U0001f54a\ufe0f" +EAGLE = "\U0001f985" +DUCK = "\U0001f986" +SWAN = "\U0001f9a2" +OWL = "\U0001f989" +DODO = "\U0001f9a4" +FEATHER = "\U0001fab6" +FLAMINGO = "\U0001f9a9" +PEACOCK = "\U0001f99a" +PARROT = "\U0001f99c" +FROG = "\U0001f438" +CROCODILE = "\U0001f40a" +TURTLE = "\U0001f422" +LIZARD = "\U0001f98e" +SNAKE = "\U0001f40d" +DRAGON_FACE = "\U0001f432" +DRAGON = "\U0001f409" +SAUROPOD = "\U0001f995" +T_REX = "\U0001f996" +SPOUTING_WHALE = "\U0001f433" +WHALE = "\U0001f40b" +DOLPHIN = "\U0001f42c" +SEAL = "\U0001f9ad" +FISH = "\U0001f41f" +TROPICAL_FISH = "\U0001f420" +BLOWFISH = "\U0001f421" +SHARK = "\U0001f988" +OCTOPUS = "\U0001f419" +SPIRAL_SHELL = "\U0001f41a" +CORAL = "\U0001fab8" +SNAIL = "\U0001f40c" +BUTTERFLY = "\U0001f98b" +BUG = "\U0001f41b" +ANT = "\U0001f41c" +HONEYBEE = "\U0001f41d" +BEETLE = "\U0001fab2" +LADY_BEETLE = "\U0001f41e" +CRICKET = "\U0001f997" +COCKROACH = "\U0001fab3" +SPIDER = "\U0001f577\ufe0f" +SPIDER_WEB = "\U0001f578\ufe0f" +SCORPION = "\U0001f982" +MOSQUITO = "\U0001f99f" +FLY = "\U0001fab0" +WORM = "\U0001fab1" +MICROBE = "\U0001f9a0" +BOUQUET = "\U0001f490" +CHERRY_BLOSSOM = "\U0001f338" +WHITE_FLOWER = "\U0001f4ae" +LOTUS = "\U0001fab7" +ROSETTE = "\U0001f3f5\ufe0f" +ROSE = "\U0001f339" +WILTED_FLOWER = "\U0001f940" +HIBISCUS = "\U0001f33a" +SUNFLOWER = "\U0001f33b" +BLOSSOM = "\U0001f33c" +TULIP = "\U0001f337" +SEEDLING = "\U0001f331" +POTTED_PLANT = "\U0001fab4" +EVERGREEN_TREE = "\U0001f332" +DECIDUOUS_TREE = "\U0001f333" +PALM_TREE = "\U0001f334" +CACTUS = "\U0001f335" +SHEAF_OF_RICE = "\U0001f33e" +HERB = "\U0001f33f" +SHAMROCK = "\u2618\ufe0f" +FOUR_LEAF_CLOVER = "\U0001f340" +MAPLE_LEAF = "\U0001f341" +FALLEN_LEAF = "\U0001f342" +LEAF_FLUTTERING_IN_WIND = "\U0001f343" +EMPTY_NEST = "\U0001fab9" +NEST_WITH_EGGS = "\U0001faba" +GRAPES = "\U0001f347" +MELON = "\U0001f348" +WATERMELON = "\U0001f349" +TANGERINE = "\U0001f34a" +LEMON = "\U0001f34b" +BANANA = "\U0001f34c" +PINEAPPLE = "\U0001f34d" +MANGO = "\U0001f96d" +RED_APPLE = "\U0001f34e" +GREEN_APPLE = "\U0001f34f" +PEAR = "\U0001f350" +PEACH = "\U0001f351" +CHERRIES = "\U0001f352" +STRAWBERRY = "\U0001f353" +BLUEBERRIES = "\U0001fad0" +KIWI_FRUIT = "\U0001f95d" +TOMATO = "\U0001f345" +OLIVE = "\U0001fad2" +COCONUT = "\U0001f965" +AVOCADO = "\U0001f951" +EGGPLANT = "\U0001f346" +POTATO = "\U0001f954" +CARROT = "\U0001f955" +EAR_OF_CORN = "\U0001f33d" +HOT_PEPPER = "\U0001f336\ufe0f" +BELL_PEPPER = "\U0001fad1" +CUCUMBER = "\U0001f952" +LEAFY_GREEN = "\U0001f96c" +BROCCOLI = "\U0001f966" +GARLIC = "\U0001f9c4" +ONION = "\U0001f9c5" +MUSHROOM = "\U0001f344" +PEANUTS = "\U0001f95c" +BEANS = "\U0001fad8" +CHESTNUT = "\U0001f330" +BREAD = "\U0001f35e" +CROISSANT = "\U0001f950" +BAGUETTE_BREAD = "\U0001f956" +FLATBREAD = "\U0001fad3" +PRETZEL = "\U0001f968" +BAGEL = "\U0001f96f" +PANCAKES = "\U0001f95e" +WAFFLE = "\U0001f9c7" +CHEESE_WEDGE = "\U0001f9c0" +MEAT_ON_BONE = "\U0001f356" +POULTRY_LEG = "\U0001f357" +CUT_OF_MEAT = "\U0001f969" +BACON = "\U0001f953" +HAMBURGER = "\U0001f354" +FRENCH_FRIES = "\U0001f35f" +PIZZA = "\U0001f355" +HOT_DOG = "\U0001f32d" +SANDWICH = "\U0001f96a" +TACO = "\U0001f32e" +BURRITO = "\U0001f32f" +TAMALE = "\U0001fad4" +STUFFED_FLATBREAD = "\U0001f959" +FALAFEL = "\U0001f9c6" +EGG = "\U0001f95a" +COOKING = "\U0001f373" +SHALLOW_PAN_OF_FOOD = "\U0001f958" +POT_OF_FOOD = "\U0001f372" +FONDUE = "\U0001fad5" +BOWL_WITH_SPOON = "\U0001f963" +GREEN_SALAD = "\U0001f957" +POPCORN = "\U0001f37f" +BUTTER = "\U0001f9c8" +SALT = "\U0001f9c2" +CANNED_FOOD = "\U0001f96b" +BENTO_BOX = "\U0001f371" +RICE_CRACKER = "\U0001f358" +RICE_BALL = "\U0001f359" +COOKED_RICE = "\U0001f35a" +CURRY_RICE = "\U0001f35b" +STEAMING_BOWL = "\U0001f35c" +SPAGHETTI = "\U0001f35d" +ROASTED_SWEET_POTATO = "\U0001f360" +ODEN = "\U0001f362" +SUSHI = "\U0001f363" +FRIED_SHRIMP = "\U0001f364" +FISH_CAKE_WITH_SWIRL = "\U0001f365" +MOON_CAKE = "\U0001f96e" +DANGO = "\U0001f361" +DUMPLING = "\U0001f95f" +FORTUNE_COOKIE = "\U0001f960" +TAKEOUT_BOX = "\U0001f961" +CRAB = "\U0001f980" +LOBSTER = "\U0001f99e" +SHRIMP = "\U0001f990" +SQUID = "\U0001f991" +OYSTER = "\U0001f9aa" +SOFT_ICE_CREAM = "\U0001f366" +SHAVED_ICE = "\U0001f367" +ICE_CREAM = "\U0001f368" +DOUGHNUT = "\U0001f369" +COOKIE = "\U0001f36a" +BIRTHDAY_CAKE = "\U0001f382" +SHORTCAKE = "\U0001f370" +CUPCAKE = "\U0001f9c1" +PIE = "\U0001f967" +CHOCOLATE_BAR = "\U0001f36b" +CANDY = "\U0001f36c" +LOLLIPOP = "\U0001f36d" +CUSTARD = "\U0001f36e" +HONEY_POT = "\U0001f36f" +BABY_BOTTLE = "\U0001f37c" +GLASS_OF_MILK = "\U0001f95b" +HOT_BEVERAGE = "\u2615" +TEAPOT = "\U0001fad6" +TEACUP_WITHOUT_HANDLE = "\U0001f375" +SAKE = "\U0001f376" +BOTTLE_WITH_POPPING_CORK = "\U0001f37e" +WINE_GLASS = "\U0001f377" +COCKTAIL_GLASS = "\U0001f378" +TROPICAL_DRINK = "\U0001f379" +BEER_MUG = "\U0001f37a" +CLINKING_BEER_MUGS = "\U0001f37b" +CLINKING_GLASSES = "\U0001f942" +TUMBLER_GLASS = "\U0001f943" +POURING_LIQUID = "\U0001fad7" +CUP_WITH_STRAW = "\U0001f964" +BUBBLE_TEA = "\U0001f9cb" +BEVERAGE_BOX = "\U0001f9c3" +MATE = "\U0001f9c9" +ICE = "\U0001f9ca" +CHOPSTICKS = "\U0001f962" +FORK_AND_KNIFE_WITH_PLATE = "\U0001f37d\ufe0f" +FORK_AND_KNIFE = "\U0001f374" +SPOON = "\U0001f944" +KITCHEN_KNIFE = "\U0001f52a" +JAR = "\U0001fad9" +AMPHORA = "\U0001f3fa" +GLOBE_SHOWING_EUROPE_AFRICA = "\U0001f30d" +GLOBE_SHOWING_AMERICAS = "\U0001f30e" +GLOBE_SHOWING_ASIA_AUSTRALIA = "\U0001f30f" +GLOBE_WITH_MERIDIANS = "\U0001f310" +WORLD_MAP = "\U0001f5fa\ufe0f" +MAP_OF_JAPAN = "\U0001f5fe" +COMPASS = "\U0001f9ed" +SNOW_CAPPED_MOUNTAIN = "\U0001f3d4\ufe0f" +MOUNTAIN = "\u26f0\ufe0f" +VOLCANO = "\U0001f30b" +MOUNT_FUJI = "\U0001f5fb" +CAMPING = "\U0001f3d5\ufe0f" +BEACH_WITH_UMBRELLA = "\U0001f3d6\ufe0f" +DESERT = "\U0001f3dc\ufe0f" +DESERT_ISLAND = "\U0001f3dd\ufe0f" +NATIONAL_PARK = "\U0001f3de\ufe0f" +STADIUM = "\U0001f3df\ufe0f" +CLASSICAL_BUILDING = "\U0001f3db\ufe0f" +BUILDING_CONSTRUCTION = "\U0001f3d7\ufe0f" +BRICK = "\U0001f9f1" +ROCK = "\U0001faa8" +WOOD = "\U0001fab5" +HUT = "\U0001f6d6" +HOUSES = "\U0001f3d8\ufe0f" +DERELICT_HOUSE = "\U0001f3da\ufe0f" +HOUSE = "\U0001f3e0" +HOUSE_WITH_GARDEN = "\U0001f3e1" +OFFICE_BUILDING = "\U0001f3e2" +JAPANESE_POST_OFFICE = "\U0001f3e3" +POST_OFFICE = "\U0001f3e4" +HOSPITAL = "\U0001f3e5" +BANK = "\U0001f3e6" +HOTEL = "\U0001f3e8" +LOVE_HOTEL = "\U0001f3e9" +CONVENIENCE_STORE = "\U0001f3ea" +SCHOOL = "\U0001f3eb" +DEPARTMENT_STORE = "\U0001f3ec" +FACTORY = "\U0001f3ed" +JAPANESE_CASTLE = "\U0001f3ef" +CASTLE = "\U0001f3f0" +WEDDING = "\U0001f492" +TOKYO_TOWER = "\U0001f5fc" +STATUE_OF_LIBERTY = "\U0001f5fd" +CHURCH = "\u26ea" +MOSQUE = "\U0001f54c" +HINDU_TEMPLE = "\U0001f6d5" +SYNAGOGUE = "\U0001f54d" +SHINTO_SHRINE = "\u26e9\ufe0f" +KAABA = "\U0001f54b" +FOUNTAIN = "\u26f2" +TENT = "\u26fa" +FOGGY = "\U0001f301" +NIGHT_WITH_STARS = "\U0001f303" +CITYSCAPE = "\U0001f3d9\ufe0f" +SUNRISE_OVER_MOUNTAINS = "\U0001f304" +SUNRISE = "\U0001f305" +CITYSCAPE_AT_DUSK = "\U0001f306" +SUNSET = "\U0001f307" +BRIDGE_AT_NIGHT = "\U0001f309" +HOT_SPRINGS = "\u2668\ufe0f" +CAROUSEL_HORSE = "\U0001f3a0" +PLAYGROUND_SLIDE = "\U0001f6dd" +FERRIS_WHEEL = "\U0001f3a1" +ROLLER_COASTER = "\U0001f3a2" +BARBER_POLE = "\U0001f488" +CIRCUS_TENT = "\U0001f3aa" +LOCOMOTIVE = "\U0001f682" +RAILWAY_CAR = "\U0001f683" +HIGH_SPEED_TRAIN = "\U0001f684" +BULLET_TRAIN = "\U0001f685" +TRAIN = "\U0001f686" +METRO = "\U0001f687" +LIGHT_RAIL = "\U0001f688" +STATION = "\U0001f689" +TRAM = "\U0001f68a" +MONORAIL = "\U0001f69d" +MOUNTAIN_RAILWAY = "\U0001f69e" +TRAM_CAR = "\U0001f68b" +BUS = "\U0001f68c" +ONCOMING_BUS = "\U0001f68d" +TROLLEYBUS = "\U0001f68e" +MINIBUS = "\U0001f690" +AMBULANCE = "\U0001f691" +FIRE_ENGINE = "\U0001f692" +POLICE_CAR = "\U0001f693" +ONCOMING_POLICE_CAR = "\U0001f694" +TAXI = "\U0001f695" +ONCOMING_TAXI = "\U0001f696" +AUTOMOBILE = "\U0001f697" +ONCOMING_AUTOMOBILE = "\U0001f698" +SPORT_UTILITY_VEHICLE = "\U0001f699" +PICKUP_TRUCK = "\U0001f6fb" +DELIVERY_TRUCK = "\U0001f69a" +ARTICULATED_LORRY = "\U0001f69b" +TRACTOR = "\U0001f69c" +RACING_CAR = "\U0001f3ce\ufe0f" +MOTORCYCLE = "\U0001f3cd\ufe0f" +MOTOR_SCOOTER = "\U0001f6f5" +MANUAL_WHEELCHAIR = "\U0001f9bd" +MOTORIZED_WHEELCHAIR = "\U0001f9bc" +AUTO_RICKSHAW = "\U0001f6fa" +BICYCLE = "\U0001f6b2" +KICK_SCOOTER = "\U0001f6f4" +SKATEBOARD = "\U0001f6f9" +ROLLER_SKATE = "\U0001f6fc" +BUS_STOP = "\U0001f68f" +MOTORWAY = "\U0001f6e3\ufe0f" +RAILWAY_TRACK = "\U0001f6e4\ufe0f" +OIL_DRUM = "\U0001f6e2\ufe0f" +FUEL_PUMP = "\u26fd" +WHEEL = "\U0001f6de" +POLICE_CAR_LIGHT = "\U0001f6a8" +HORIZONTAL_TRAFFIC_LIGHT = "\U0001f6a5" +VERTICAL_TRAFFIC_LIGHT = "\U0001f6a6" +STOP_SIGN = "\U0001f6d1" +CONSTRUCTION = "\U0001f6a7" +ANCHOR = "\u2693" +RING_BUOY = "\U0001f6df" +SAILBOAT = "\u26f5" +CANOE = "\U0001f6f6" +SPEEDBOAT = "\U0001f6a4" +PASSENGER_SHIP = "\U0001f6f3\ufe0f" +FERRY = "\u26f4\ufe0f" +MOTOR_BOAT = "\U0001f6e5\ufe0f" +SHIP = "\U0001f6a2" +AIRPLANE = "\u2708\ufe0f" +SMALL_AIRPLANE = "\U0001f6e9\ufe0f" +AIRPLANE_DEPARTURE = "\U0001f6eb" +AIRPLANE_ARRIVAL = "\U0001f6ec" +PARACHUTE = "\U0001fa82" +SEAT = "\U0001f4ba" +HELICOPTER = "\U0001f681" +SUSPENSION_RAILWAY = "\U0001f69f" +MOUNTAIN_CABLEWAY = "\U0001f6a0" +AERIAL_TRAMWAY = "\U0001f6a1" +SATELLITE = "\U0001f6f0\ufe0f" +ROCKET = "\U0001f680" +FLYING_SAUCER = "\U0001f6f8" +BELLHOP_BELL = "\U0001f6ce\ufe0f" +LUGGAGE = "\U0001f9f3" +HOURGLASS_DONE = "\u231b" +HOURGLASS_NOT_DONE = "\u23f3" +WATCH = "\u231a" +ALARM_CLOCK = "\u23f0" +STOPWATCH = "\u23f1\ufe0f" +TIMER_CLOCK = "\u23f2\ufe0f" +MANTELPIECE_CLOCK = "\U0001f570\ufe0f" +TWELVE_O_CLOCK = "\U0001f55b" +TWELVE_THIRTY = "\U0001f567" +ONE_O_CLOCK = "\U0001f550" +ONE_THIRTY = "\U0001f55c" +TWO_O_CLOCK = "\U0001f551" +TWO_THIRTY = "\U0001f55d" +THREE_O_CLOCK = "\U0001f552" +THREE_THIRTY = "\U0001f55e" +FOUR_O_CLOCK = "\U0001f553" +FOUR_THIRTY = "\U0001f55f" +FIVE_O_CLOCK = "\U0001f554" +FIVE_THIRTY = "\U0001f560" +SIX_O_CLOCK = "\U0001f555" +SIX_THIRTY = "\U0001f561" +SEVEN_O_CLOCK = "\U0001f556" +SEVEN_THIRTY = "\U0001f562" +EIGHT_O_CLOCK = "\U0001f557" +EIGHT_THIRTY = "\U0001f563" +NINE_O_CLOCK = "\U0001f558" +NINE_THIRTY = "\U0001f564" +TEN_O_CLOCK = "\U0001f559" +TEN_THIRTY = "\U0001f565" +ELEVEN_O_CLOCK = "\U0001f55a" +ELEVEN_THIRTY = "\U0001f566" +NEW_MOON = "\U0001f311" +WAXING_CRESCENT_MOON = "\U0001f312" +FIRST_QUARTER_MOON = "\U0001f313" +WAXING_GIBBOUS_MOON = "\U0001f314" +FULL_MOON = "\U0001f315" +WANING_GIBBOUS_MOON = "\U0001f316" +LAST_QUARTER_MOON = "\U0001f317" +WANING_CRESCENT_MOON = "\U0001f318" +CRESCENT_MOON = "\U0001f319" +NEW_MOON_FACE = "\U0001f31a" +FIRST_QUARTER_MOON_FACE = "\U0001f31b" +LAST_QUARTER_MOON_FACE = "\U0001f31c" +THERMOMETER = "\U0001f321\ufe0f" +SUN = "\u2600\ufe0f" +FULL_MOON_FACE = "\U0001f31d" +SUN_WITH_FACE = "\U0001f31e" +RINGED_PLANET = "\U0001fa90" +STAR = "\u2b50" +GLOWING_STAR = "\U0001f31f" +SHOOTING_STAR = "\U0001f320" +MILKY_WAY = "\U0001f30c" +CLOUD = "\u2601\ufe0f" +SUN_BEHIND_CLOUD = "\u26c5" +CLOUD_WITH_LIGHTNING_AND_RAIN = "\u26c8\ufe0f" +SUN_BEHIND_SMALL_CLOUD = "\U0001f324\ufe0f" +SUN_BEHIND_LARGE_CLOUD = "\U0001f325\ufe0f" +SUN_BEHIND_RAIN_CLOUD = "\U0001f326\ufe0f" +CLOUD_WITH_RAIN = "\U0001f327\ufe0f" +CLOUD_WITH_SNOW = "\U0001f328\ufe0f" +CLOUD_WITH_LIGHTNING = "\U0001f329\ufe0f" +TORNADO = "\U0001f32a\ufe0f" +FOG = "\U0001f32b\ufe0f" +WIND_FACE = "\U0001f32c\ufe0f" +CYCLONE = "\U0001f300" +RAINBOW = "\U0001f308" +CLOSED_UMBRELLA = "\U0001f302" +UMBRELLA = "\u2602\ufe0f" +UMBRELLA_WITH_RAIN_DROPS = "\u2614" +UMBRELLA_ON_GROUND = "\u26f1\ufe0f" +HIGH_VOLTAGE = "\u26a1" +SNOWFLAKE = "\u2744\ufe0f" +SNOWMAN = "\u2603\ufe0f" +SNOWMAN_WITHOUT_SNOW = "\u26c4" +COMET = "\u2604\ufe0f" +FIRE = "\U0001f525" +DROPLET = "\U0001f4a7" +WATER_WAVE = "\U0001f30a" +JACK_O_LANTERN = "\U0001f383" +CHRISTMAS_TREE = "\U0001f384" +FIREWORKS = "\U0001f386" +SPARKLER = "\U0001f387" +FIRECRACKER = "\U0001f9e8" +SPARKLES = "\u2728" +BALLOON = "\U0001f388" +PARTY_POPPER = "\U0001f389" +CONFETTI_BALL = "\U0001f38a" +TANABATA_TREE = "\U0001f38b" +PINE_DECORATION = "\U0001f38d" +JAPANESE_DOLLS = "\U0001f38e" +CARP_STREAMER = "\U0001f38f" +WIND_CHIME = "\U0001f390" +MOON_VIEWING_CEREMONY = "\U0001f391" +RED_ENVELOPE = "\U0001f9e7" +RIBBON = "\U0001f380" +WRAPPED_GIFT = "\U0001f381" +REMINDER_RIBBON = "\U0001f397\ufe0f" +ADMISSION_TICKETS = "\U0001f39f\ufe0f" +TICKET = "\U0001f3ab" +MILITARY_MEDAL = "\U0001f396\ufe0f" +TROPHY = "\U0001f3c6" +SPORTS_MEDAL = "\U0001f3c5" +FIRST_PLACE_MEDAL = "\U0001f947" +SECOND_PLACE_MEDAL = "\U0001f948" +THIRD_PLACE_MEDAL = "\U0001f949" +SOCCER_BALL = "\u26bd" +BASEBALL = "\u26be" +SOFTBALL = "\U0001f94e" +BASKETBALL = "\U0001f3c0" +VOLLEYBALL = "\U0001f3d0" +AMERICAN_FOOTBALL = "\U0001f3c8" +RUGBY_FOOTBALL = "\U0001f3c9" +TENNIS = "\U0001f3be" +FLYING_DISC = "\U0001f94f" +BOWLING = "\U0001f3b3" +CRICKET_GAME = "\U0001f3cf" +FIELD_HOCKEY = "\U0001f3d1" +ICE_HOCKEY = "\U0001f3d2" +LACROSSE = "\U0001f94d" +PING_PONG = "\U0001f3d3" +BADMINTON = "\U0001f3f8" +BOXING_GLOVE = "\U0001f94a" +MARTIAL_ARTS_UNIFORM = "\U0001f94b" +GOAL_NET = "\U0001f945" +FLAG_IN_HOLE = "\u26f3" +ICE_SKATE = "\u26f8\ufe0f" +FISHING_POLE = "\U0001f3a3" +DIVING_MASK = "\U0001f93f" +RUNNING_SHIRT = "\U0001f3bd" +SKIS = "\U0001f3bf" +SLED = "\U0001f6f7" +CURLING_STONE = "\U0001f94c" +BULLSEYE = "\U0001f3af" +YO_YO = "\U0001fa80" +KITE = "\U0001fa81" +POOL_8_BALL = "\U0001f3b1" +CRYSTAL_BALL = "\U0001f52e" +MAGIC_WAND = "\U0001fa84" +NAZAR_AMULET = "\U0001f9ff" +HAMSA = "\U0001faac" +VIDEO_GAME = "\U0001f3ae" +JOYSTICK = "\U0001f579\ufe0f" +SLOT_MACHINE = "\U0001f3b0" +GAME_DIE = "\U0001f3b2" +PUZZLE_PIECE = "\U0001f9e9" +TEDDY_BEAR = "\U0001f9f8" +PINATA = "\U0001fa85" +MIRROR_BALL = "\U0001faa9" +NESTING_DOLLS = "\U0001fa86" +SPADE_SUIT = "\u2660\ufe0f" +HEART_SUIT = "\u2665\ufe0f" +DIAMOND_SUIT = "\u2666\ufe0f" +CLUB_SUIT = "\u2663\ufe0f" +CHESS_PAWN = "\u265f\ufe0f" +JOKER = "\U0001f0cf" +MAHJONG_RED_DRAGON = "\U0001f004" +FLOWER_PLAYING_CARDS = "\U0001f3b4" +PERFORMING_ARTS = "\U0001f3ad" +FRAMED_PICTURE = "\U0001f5bc\ufe0f" +ARTIST_PALETTE = "\U0001f3a8" +THREAD = "\U0001f9f5" +SEWING_NEEDLE = "\U0001faa1" +YARN = "\U0001f9f6" +KNOT = "\U0001faa2" +GLASSES = "\U0001f453" +SUNGLASSES = "\U0001f576\ufe0f" +GOGGLES = "\U0001f97d" +LAB_COAT = "\U0001f97c" +SAFETY_VEST = "\U0001f9ba" +NECKTIE = "\U0001f454" +T_SHIRT = "\U0001f455" +JEANS = "\U0001f456" +SCARF = "\U0001f9e3" +GLOVES = "\U0001f9e4" +COAT = "\U0001f9e5" +SOCKS = "\U0001f9e6" +DRESS = "\U0001f457" +KIMONO = "\U0001f458" +SARI = "\U0001f97b" +ONE_PIECE_SWIMSUIT = "\U0001fa71" +BRIEFS = "\U0001fa72" +SHORTS = "\U0001fa73" +BIKINI = "\U0001f459" +WOMAN_S_CLOTHES = "\U0001f45a" +PURSE = "\U0001f45b" +HANDBAG = "\U0001f45c" +CLUTCH_BAG = "\U0001f45d" +SHOPPING_BAGS = "\U0001f6cd\ufe0f" +BACKPACK = "\U0001f392" +THONG_SANDAL = "\U0001fa74" +MAN_S_SHOE = "\U0001f45e" +RUNNING_SHOE = "\U0001f45f" +HIKING_BOOT = "\U0001f97e" +FLAT_SHOE = "\U0001f97f" +HIGH_HEELED_SHOE = "\U0001f460" +WOMAN_S_SANDAL = "\U0001f461" +BALLET_SHOES = "\U0001fa70" +WOMAN_S_BOOT = "\U0001f462" +CROWN = "\U0001f451" +WOMAN_S_HAT = "\U0001f452" +TOP_HAT = "\U0001f3a9" +GRADUATION_CAP = "\U0001f393" +BILLED_CAP = "\U0001f9e2" +MILITARY_HELMET = "\U0001fa96" +RESCUE_WORKER_S_HELMET = "\u26d1\ufe0f" +PRAYER_BEADS = "\U0001f4ff" +LIPSTICK = "\U0001f484" +RING = "\U0001f48d" +GEM_STONE = "\U0001f48e" +MUTED_SPEAKER = "\U0001f507" +SPEAKER_LOW_VOLUME = "\U0001f508" +SPEAKER_MEDIUM_VOLUME = "\U0001f509" +SPEAKER_HIGH_VOLUME = "\U0001f50a" +LOUDSPEAKER = "\U0001f4e2" +MEGAPHONE = "\U0001f4e3" +POSTAL_HORN = "\U0001f4ef" +BELL = "\U0001f514" +BELL_WITH_SLASH = "\U0001f515" +MUSICAL_SCORE = "\U0001f3bc" +MUSICAL_NOTE = "\U0001f3b5" +MUSICAL_NOTES = "\U0001f3b6" +STUDIO_MICROPHONE = "\U0001f399\ufe0f" +LEVEL_SLIDER = "\U0001f39a\ufe0f" +CONTROL_KNOBS = "\U0001f39b\ufe0f" +MICROPHONE = "\U0001f3a4" +HEADPHONE = "\U0001f3a7" +RADIO = "\U0001f4fb" +SAXOPHONE = "\U0001f3b7" +ACCORDION = "\U0001fa97" +GUITAR = "\U0001f3b8" +MUSICAL_KEYBOARD = "\U0001f3b9" +TRUMPET = "\U0001f3ba" +VIOLIN = "\U0001f3bb" +BANJO = "\U0001fa95" +DRUM = "\U0001f941" +LONG_DRUM = "\U0001fa98" +MOBILE_PHONE = "\U0001f4f1" +MOBILE_PHONE_WITH_ARROW = "\U0001f4f2" +TELEPHONE = "\u260e\ufe0f" +TELEPHONE_RECEIVER = "\U0001f4de" +PAGER = "\U0001f4df" +FAX_MACHINE = "\U0001f4e0" +BATTERY = "\U0001f50b" +LOW_BATTERY = "\U0001faab" +ELECTRIC_PLUG = "\U0001f50c" +LAPTOP = "\U0001f4bb" +DESKTOP_COMPUTER = "\U0001f5a5\ufe0f" +PRINTER = "\U0001f5a8\ufe0f" +KEYBOARD = "\u2328\ufe0f" +COMPUTER_MOUSE = "\U0001f5b1\ufe0f" +TRACKBALL = "\U0001f5b2\ufe0f" +COMPUTER_DISK = "\U0001f4bd" +FLOPPY_DISK = "\U0001f4be" +OPTICAL_DISK = "\U0001f4bf" +DVD = "\U0001f4c0" +ABACUS = "\U0001f9ee" +MOVIE_CAMERA = "\U0001f3a5" +FILM_FRAMES = "\U0001f39e\ufe0f" +FILM_PROJECTOR = "\U0001f4fd\ufe0f" +CLAPPER_BOARD = "\U0001f3ac" +TELEVISION = "\U0001f4fa" +CAMERA = "\U0001f4f7" +CAMERA_WITH_FLASH = "\U0001f4f8" +VIDEO_CAMERA = "\U0001f4f9" +VIDEOCASSETTE = "\U0001f4fc" +MAGNIFYING_GLASS_TILTED_LEFT = "\U0001f50d" +MAGNIFYING_GLASS_TILTED_RIGHT = "\U0001f50e" +CANDLE = "\U0001f56f\ufe0f" +LIGHT_BULB = "\U0001f4a1" +FLASHLIGHT = "\U0001f526" +RED_PAPER_LANTERN = "\U0001f3ee" +DIYA_LAMP = "\U0001fa94" +NOTEBOOK_WITH_DECORATIVE_COVER = "\U0001f4d4" +CLOSED_BOOK = "\U0001f4d5" +OPEN_BOOK = "\U0001f4d6" +GREEN_BOOK = "\U0001f4d7" +BLUE_BOOK = "\U0001f4d8" +ORANGE_BOOK = "\U0001f4d9" +BOOKS = "\U0001f4da" +NOTEBOOK = "\U0001f4d3" +LEDGER = "\U0001f4d2" +PAGE_WITH_CURL = "\U0001f4c3" +SCROLL = "\U0001f4dc" +PAGE_FACING_UP = "\U0001f4c4" +NEWSPAPER = "\U0001f4f0" +ROLLED_UP_NEWSPAPER = "\U0001f5de\ufe0f" +BOOKMARK_TABS = "\U0001f4d1" +BOOKMARK = "\U0001f516" +LABEL = "\U0001f3f7\ufe0f" +MONEY_BAG = "\U0001f4b0" +COIN = "\U0001fa99" +YEN_BANKNOTE = "\U0001f4b4" +DOLLAR_BANKNOTE = "\U0001f4b5" +EURO_BANKNOTE = "\U0001f4b6" +POUND_BANKNOTE = "\U0001f4b7" +MONEY_WITH_WINGS = "\U0001f4b8" +CREDIT_CARD = "\U0001f4b3" +RECEIPT = "\U0001f9fe" +CHART_INCREASING_WITH_YEN = "\U0001f4b9" +ENVELOPE = "\u2709\ufe0f" +E_MAIL = "\U0001f4e7" +INCOMING_ENVELOPE = "\U0001f4e8" +ENVELOPE_WITH_ARROW = "\U0001f4e9" +OUTBOX_TRAY = "\U0001f4e4" +INBOX_TRAY = "\U0001f4e5" +PACKAGE = "\U0001f4e6" +CLOSED_MAILBOX_WITH_RAISED_FLAG = "\U0001f4eb" +CLOSED_MAILBOX_WITH_LOWERED_FLAG = "\U0001f4ea" +OPEN_MAILBOX_WITH_RAISED_FLAG = "\U0001f4ec" +OPEN_MAILBOX_WITH_LOWERED_FLAG = "\U0001f4ed" +POSTBOX = "\U0001f4ee" +BALLOT_BOX_WITH_BALLOT = "\U0001f5f3\ufe0f" +PENCIL = "\u270f\ufe0f" +BLACK_NIB = "\u2712\ufe0f" +FOUNTAIN_PEN = "\U0001f58b\ufe0f" +PEN = "\U0001f58a\ufe0f" +PAINTBRUSH = "\U0001f58c\ufe0f" +CRAYON = "\U0001f58d\ufe0f" +MEMO = "\U0001f4dd" +BRIEFCASE = "\U0001f4bc" +FILE_FOLDER = "\U0001f4c1" +OPEN_FILE_FOLDER = "\U0001f4c2" +CARD_INDEX_DIVIDERS = "\U0001f5c2\ufe0f" +CALENDAR = "\U0001f4c5" +TEAR_OFF_CALENDAR = "\U0001f4c6" +SPIRAL_NOTEPAD = "\U0001f5d2\ufe0f" +SPIRAL_CALENDAR = "\U0001f5d3\ufe0f" +CARD_INDEX = "\U0001f4c7" +CHART_INCREASING = "\U0001f4c8" +CHART_DECREASING = "\U0001f4c9" +BAR_CHART = "\U0001f4ca" +CLIPBOARD = "\U0001f4cb" +PUSHPIN = "\U0001f4cc" +ROUND_PUSHPIN = "\U0001f4cd" +PAPERCLIP = "\U0001f4ce" +LINKED_PAPERCLIPS = "\U0001f587\ufe0f" +STRAIGHT_RULER = "\U0001f4cf" +TRIANGULAR_RULER = "\U0001f4d0" +SCISSORS = "\u2702\ufe0f" +CARD_FILE_BOX = "\U0001f5c3\ufe0f" +FILE_CABINET = "\U0001f5c4\ufe0f" +WASTEBASKET = "\U0001f5d1\ufe0f" +LOCKED = "\U0001f512" +UNLOCKED = "\U0001f513" +LOCKED_WITH_PEN = "\U0001f50f" +LOCKED_WITH_KEY = "\U0001f510" +KEY = "\U0001f511" +OLD_KEY = "\U0001f5dd\ufe0f" +HAMMER = "\U0001f528" +AXE = "\U0001fa93" +PICK = "\u26cf\ufe0f" +HAMMER_AND_PICK = "\u2692\ufe0f" +HAMMER_AND_WRENCH = "\U0001f6e0\ufe0f" +DAGGER = "\U0001f5e1\ufe0f" +CROSSED_SWORDS = "\u2694\ufe0f" +WATER_PISTOL = "\U0001f52b" +BOOMERANG = "\U0001fa83" +BOW_AND_ARROW = "\U0001f3f9" +SHIELD = "\U0001f6e1\ufe0f" +CARPENTRY_SAW = "\U0001fa9a" +WRENCH = "\U0001f527" +SCREWDRIVER = "\U0001fa9b" +NUT_AND_BOLT = "\U0001f529" +GEAR = "\u2699\ufe0f" +CLAMP = "\U0001f5dc\ufe0f" +BALANCE_SCALE = "\u2696\ufe0f" +WHITE_CANE = "\U0001f9af" +LINK = "\U0001f517" +CHAINS = "\u26d3\ufe0f" +HOOK = "\U0001fa9d" +TOOLBOX = "\U0001f9f0" +MAGNET = "\U0001f9f2" +LADDER = "\U0001fa9c" +ALEMBIC = "\u2697\ufe0f" +TEST_TUBE = "\U0001f9ea" +PETRI_DISH = "\U0001f9eb" +DNA = "\U0001f9ec" +MICROSCOPE = "\U0001f52c" +TELESCOPE = "\U0001f52d" +SATELLITE_ANTENNA = "\U0001f4e1" +SYRINGE = "\U0001f489" +DROP_OF_BLOOD = "\U0001fa78" +PILL = "\U0001f48a" +ADHESIVE_BANDAGE = "\U0001fa79" +CRUTCH = "\U0001fa7c" +STETHOSCOPE = "\U0001fa7a" +X_RAY = "\U0001fa7b" +DOOR = "\U0001f6aa" +ELEVATOR = "\U0001f6d7" +MIRROR = "\U0001fa9e" +WINDOW = "\U0001fa9f" +BED = "\U0001f6cf\ufe0f" +COUCH_AND_LAMP = "\U0001f6cb\ufe0f" +CHAIR = "\U0001fa91" +TOILET = "\U0001f6bd" +PLUNGER = "\U0001faa0" +SHOWER = "\U0001f6bf" +BATHTUB = "\U0001f6c1" +MOUSE_TRAP = "\U0001faa4" +RAZOR = "\U0001fa92" +LOTION_BOTTLE = "\U0001f9f4" +SAFETY_PIN = "\U0001f9f7" +BROOM = "\U0001f9f9" +BASKET = "\U0001f9fa" +ROLL_OF_PAPER = "\U0001f9fb" +BUCKET = "\U0001faa3" +SOAP = "\U0001f9fc" +BUBBLES = "\U0001fae7" +TOOTHBRUSH = "\U0001faa5" +SPONGE = "\U0001f9fd" +FIRE_EXTINGUISHER = "\U0001f9ef" +SHOPPING_CART = "\U0001f6d2" +CIGARETTE = "\U0001f6ac" +COFFIN = "\u26b0\ufe0f" +HEADSTONE = "\U0001faa6" +FUNERAL_URN = "\u26b1\ufe0f" +MOAI = "\U0001f5ff" +PLACARD = "\U0001faa7" +IDENTIFICATION_CARD = "\U0001faaa" +ATM_SIGN = "\U0001f3e7" +LITTER_IN_BIN_SIGN = "\U0001f6ae" +POTABLE_WATER = "\U0001f6b0" +WHEELCHAIR_SYMBOL = "\u267f" +MEN_S_ROOM = "\U0001f6b9" +WOMEN_S_ROOM = "\U0001f6ba" +RESTROOM = "\U0001f6bb" +BABY_SYMBOL = "\U0001f6bc" +WATER_CLOSET = "\U0001f6be" +PASSPORT_CONTROL = "\U0001f6c2" +CUSTOMS = "\U0001f6c3" +BAGGAGE_CLAIM = "\U0001f6c4" +LEFT_LUGGAGE = "\U0001f6c5" +WARNING = "\u26a0\ufe0f" +CHILDREN_CROSSING = "\U0001f6b8" +NO_ENTRY = "\u26d4" +PROHIBITED = "\U0001f6ab" +NO_BICYCLES = "\U0001f6b3" +NO_SMOKING = "\U0001f6ad" +NO_LITTERING = "\U0001f6af" +NON_POTABLE_WATER = "\U0001f6b1" +NO_PEDESTRIANS = "\U0001f6b7" +NO_MOBILE_PHONES = "\U0001f4f5" +NO_ONE_UNDER_EIGHTEEN = "\U0001f51e" +RADIOACTIVE = "\u2622\ufe0f" +BIOHAZARD = "\u2623\ufe0f" +UP_ARROW = "\u2b06\ufe0f" +UP_RIGHT_ARROW = "\u2197\ufe0f" +RIGHT_ARROW = "\u27a1\ufe0f" +DOWN_RIGHT_ARROW = "\u2198\ufe0f" +DOWN_ARROW = "\u2b07\ufe0f" +DOWN_LEFT_ARROW = "\u2199\ufe0f" +LEFT_ARROW = "\u2b05\ufe0f" +UP_LEFT_ARROW = "\u2196\ufe0f" +UP_DOWN_ARROW = "\u2195\ufe0f" +LEFT_RIGHT_ARROW = "\u2194\ufe0f" +RIGHT_ARROW_CURVING_LEFT = "\u21a9\ufe0f" +LEFT_ARROW_CURVING_RIGHT = "\u21aa\ufe0f" +RIGHT_ARROW_CURVING_UP = "\u2934\ufe0f" +RIGHT_ARROW_CURVING_DOWN = "\u2935\ufe0f" +CLOCKWISE_VERTICAL_ARROWS = "\U0001f503" +COUNTERCLOCKWISE_ARROWS_BUTTON = "\U0001f504" +BACK_ARROW = "\U0001f519" +END_ARROW = "\U0001f51a" +ON_ARROW = "\U0001f51b" +SOON_ARROW = "\U0001f51c" +TOP_ARROW = "\U0001f51d" +PLACE_OF_WORSHIP = "\U0001f6d0" +ATOM_SYMBOL = "\u269b\ufe0f" +OM = "\U0001f549\ufe0f" +STAR_OF_DAVID = "\u2721\ufe0f" +WHEEL_OF_DHARMA = "\u2638\ufe0f" +YIN_YANG = "\u262f\ufe0f" +LATIN_CROSS = "\u271d\ufe0f" +ORTHODOX_CROSS = "\u2626\ufe0f" +STAR_AND_CRESCENT = "\u262a\ufe0f" +PEACE_SYMBOL = "\u262e\ufe0f" +MENORAH = "\U0001f54e" +DOTTED_SIX_POINTED_STAR = "\U0001f52f" +ARIES = "\u2648" +TAURUS = "\u2649" +GEMINI = "\u264a" +CANCER = "\u264b" +LEO = "\u264c" +VIRGO = "\u264d" +LIBRA = "\u264e" +SCORPIO = "\u264f" +SAGITTARIUS = "\u2650" +CAPRICORN = "\u2651" +AQUARIUS = "\u2652" +PISCES = "\u2653" +OPHIUCHUS = "\u26ce" +SHUFFLE_TRACKS_BUTTON = "\U0001f500" +REPEAT_BUTTON = "\U0001f501" +REPEAT_SINGLE_BUTTON = "\U0001f502" +PLAY_BUTTON = "\u25b6\ufe0f" +FAST_FORWARD_BUTTON = "\u23e9" +NEXT_TRACK_BUTTON = "\u23ed\ufe0f" +PLAY_OR_PAUSE_BUTTON = "\u23ef\ufe0f" +REVERSE_BUTTON = "\u25c0\ufe0f" +FAST_REVERSE_BUTTON = "\u23ea" +LAST_TRACK_BUTTON = "\u23ee\ufe0f" +UPWARDS_BUTTON = "\U0001f53c" +FAST_UP_BUTTON = "\u23eb" +DOWNWARDS_BUTTON = "\U0001f53d" +FAST_DOWN_BUTTON = "\u23ec" +PAUSE_BUTTON = "\u23f8\ufe0f" +STOP_BUTTON = "\u23f9\ufe0f" +RECORD_BUTTON = "\u23fa\ufe0f" +EJECT_BUTTON = "\u23cf\ufe0f" +CINEMA = "\U0001f3a6" +DIM_BUTTON = "\U0001f505" +BRIGHT_BUTTON = "\U0001f506" +ANTENNA_BARS = "\U0001f4f6" +VIBRATION_MODE = "\U0001f4f3" +MOBILE_PHONE_OFF = "\U0001f4f4" +FEMALE_SIGN = "\u2640\ufe0f" +MALE_SIGN = "\u2642\ufe0f" +TRANSGENDER_SYMBOL = "\u26a7\ufe0f" +MULTIPLY = "\u2716\ufe0f" +PLUS = "\u2795" +MINUS = "\u2796" +DIVIDE = "\u2797" +HEAVY_EQUALS_SIGN = "\U0001f7f0" +INFINITY = "\u267e\ufe0f" +DOUBLE_EXCLAMATION_MARK = "\u203c\ufe0f" +EXCLAMATION_QUESTION_MARK = "\u2049\ufe0f" +RED_QUESTION_MARK = "\u2753" +WHITE_QUESTION_MARK = "\u2754" +WHITE_EXCLAMATION_MARK = "\u2755" +RED_EXCLAMATION_MARK = "\u2757" +WAVY_DASH = "\u3030\ufe0f" +CURRENCY_EXCHANGE = "\U0001f4b1" +HEAVY_DOLLAR_SIGN = "\U0001f4b2" +MEDICAL_SYMBOL = "\u2695\ufe0f" +RECYCLING_SYMBOL = "\u267b\ufe0f" +FLEUR_DE_LIS = "\u269c\ufe0f" +TRIDENT_EMBLEM = "\U0001f531" +NAME_BADGE = "\U0001f4db" +JAPANESE_SYMBOL_FOR_BEGINNER = "\U0001f530" +HOLLOW_RED_CIRCLE = "\u2b55" +CHECK_MARK_BUTTON = "\u2705" +CHECK_BOX_WITH_CHECK = "\u2611\ufe0f" +CHECK_MARK = "\u2714\ufe0f" +CROSS_MARK = "\u274c" +CROSS_MARK_BUTTON = "\u274e" +CURLY_LOOP = "\u27b0" +DOUBLE_CURLY_LOOP = "\u27bf" +PART_ALTERNATION_MARK = "\u303d\ufe0f" +EIGHT_SPOKED_ASTERISK = "\u2733\ufe0f" +EIGHT_POINTED_STAR = "\u2734\ufe0f" +SPARKLE = "\u2747\ufe0f" +COPYRIGHT = "\xa9\ufe0f" +REGISTERED = "\xae\ufe0f" +TRADE_MARK = "\u2122\ufe0f" +KEYCAP_NUMBER_SIGN = "#\ufe0f\u20e3" +KEYCAP_ASTERISK = "*\ufe0f\u20e3" +KEYCAP_DIGIT_ZERO = "0\ufe0f\u20e3" +KEYCAP_DIGIT_ONE = "1\ufe0f\u20e3" +KEYCAP_DIGIT_TWO = "2\ufe0f\u20e3" +KEYCAP_DIGIT_THREE = "3\ufe0f\u20e3" +KEYCAP_DIGIT_FOUR = "4\ufe0f\u20e3" +KEYCAP_DIGIT_FIVE = "5\ufe0f\u20e3" +KEYCAP_DIGIT_SIX = "6\ufe0f\u20e3" +KEYCAP_DIGIT_SEVEN = "7\ufe0f\u20e3" +KEYCAP_DIGIT_EIGHT = "8\ufe0f\u20e3" +KEYCAP_DIGIT_NINE = "9\ufe0f\u20e3" +KEYCAP_10 = "\U0001f51f" +INPUT_LATIN_UPPERCASE = "\U0001f520" +INPUT_LATIN_LOWERCASE = "\U0001f521" +INPUT_NUMBERS = "\U0001f522" +INPUT_SYMBOLS = "\U0001f523" +INPUT_LATIN_LETTERS = "\U0001f524" +A_BUTTON_BLOOD_TYPE = "\U0001f170\ufe0f" +AB_BUTTON_BLOOD_TYPE = "\U0001f18e" +B_BUTTON_BLOOD_TYPE = "\U0001f171\ufe0f" +CL_BUTTON = "\U0001f191" +COOL_BUTTON = "\U0001f192" +FREE_BUTTON = "\U0001f193" +INFORMATION = "\u2139\ufe0f" +ID_BUTTON = "\U0001f194" +CIRCLED_M = "\u24c2\ufe0f" +NEW_BUTTON = "\U0001f195" +NG_BUTTON = "\U0001f196" +O_BUTTON_BLOOD_TYPE = "\U0001f17e\ufe0f" +OK_BUTTON = "\U0001f197" +P_BUTTON = "\U0001f17f\ufe0f" +SOS_BUTTON = "\U0001f198" +UP_BUTTON = "\U0001f199" +VS_BUTTON = "\U0001f19a" +JAPANESE_HERE_BUTTON = "\U0001f201" +JAPANESE_SERVICE_CHARGE_BUTTON = "\U0001f202\ufe0f" +JAPANESE_MONTHLY_AMOUNT_BUTTON = "\U0001f237\ufe0f" +JAPANESE_NOT_FREE_OF_CHARGE_BUTTON = "\U0001f236" +JAPANESE_RESERVED_BUTTON = "\U0001f22f" +JAPANESE_BARGAIN_BUTTON = "\U0001f250" +JAPANESE_DISCOUNT_BUTTON = "\U0001f239" +JAPANESE_FREE_OF_CHARGE_BUTTON = "\U0001f21a" +JAPANESE_PROHIBITED_BUTTON = "\U0001f232" +JAPANESE_ACCEPTABLE_BUTTON = "\U0001f251" +JAPANESE_APPLICATION_BUTTON = "\U0001f238" +JAPANESE_PASSING_GRADE_BUTTON = "\U0001f234" +JAPANESE_VACANCY_BUTTON = "\U0001f233" +JAPANESE_CONGRATULATIONS_BUTTON = "\u3297\ufe0f" +JAPANESE_SECRET_BUTTON = "\u3299\ufe0f" +JAPANESE_OPEN_FOR_BUSINESS_BUTTON = "\U0001f23a" +JAPANESE_NO_VACANCY_BUTTON = "\U0001f235" +RED_CIRCLE = "\U0001f534" +ORANGE_CIRCLE = "\U0001f7e0" +YELLOW_CIRCLE = "\U0001f7e1" +GREEN_CIRCLE = "\U0001f7e2" +BLUE_CIRCLE = "\U0001f535" +PURPLE_CIRCLE = "\U0001f7e3" +BROWN_CIRCLE = "\U0001f7e4" +BLACK_CIRCLE = "\u26ab" +WHITE_CIRCLE = "\u26aa" +RED_SQUARE = "\U0001f7e5" +ORANGE_SQUARE = "\U0001f7e7" +YELLOW_SQUARE = "\U0001f7e8" +GREEN_SQUARE = "\U0001f7e9" +BLUE_SQUARE = "\U0001f7e6" +PURPLE_SQUARE = "\U0001f7ea" +BROWN_SQUARE = "\U0001f7eb" +BLACK_LARGE_SQUARE = "\u2b1b" +WHITE_LARGE_SQUARE = "\u2b1c" +BLACK_MEDIUM_SQUARE = "\u25fc\ufe0f" +WHITE_MEDIUM_SQUARE = "\u25fb\ufe0f" +BLACK_MEDIUM_SMALL_SQUARE = "\u25fe" +WHITE_MEDIUM_SMALL_SQUARE = "\u25fd" +BLACK_SMALL_SQUARE = "\u25aa\ufe0f" +WHITE_SMALL_SQUARE = "\u25ab\ufe0f" +LARGE_ORANGE_DIAMOND = "\U0001f536" +LARGE_BLUE_DIAMOND = "\U0001f537" +SMALL_ORANGE_DIAMOND = "\U0001f538" +SMALL_BLUE_DIAMOND = "\U0001f539" +RED_TRIANGLE_POINTED_UP = "\U0001f53a" +RED_TRIANGLE_POINTED_DOWN = "\U0001f53b" +DIAMOND_WITH_A_DOT = "\U0001f4a0" +RADIO_BUTTON = "\U0001f518" +WHITE_SQUARE_BUTTON = "\U0001f533" +BLACK_SQUARE_BUTTON = "\U0001f532" +CHEQUERED_FLAG = "\U0001f3c1" +TRIANGULAR_FLAG = "\U0001f6a9" +CROSSED_FLAGS = "\U0001f38c" +BLACK_FLAG = "\U0001f3f4" +WHITE_FLAG = "\U0001f3f3\ufe0f" +RAINBOW_FLAG = "\U0001f3f3\ufe0f\u200d\U0001f308" +TRANSGENDER_FLAG = "\U0001f3f3\ufe0f\u200d\u26a7\ufe0f" +PIRATE_FLAG = "\U0001f3f4\u200d\u2620\ufe0f" +FLAG_ASCENSION_ISLAND = "\U0001f1e6\U0001f1e8" +FLAG_ANDORRA = "\U0001f1e6\U0001f1e9" +FLAG_UNITED_ARAB_EMIRATES = "\U0001f1e6\U0001f1ea" +FLAG_AFGHANISTAN = "\U0001f1e6\U0001f1eb" +FLAG_ANTIGUA_ANDAMP_BARBUDA = "\U0001f1e6\U0001f1ec" +FLAG_ANGUILLA = "\U0001f1e6\U0001f1ee" +FLAG_ALBANIA = "\U0001f1e6\U0001f1f1" +FLAG_ARMENIA = "\U0001f1e6\U0001f1f2" +FLAG_ANGOLA = "\U0001f1e6\U0001f1f4" +FLAG_ANTARCTICA = "\U0001f1e6\U0001f1f6" +FLAG_ARGENTINA = "\U0001f1e6\U0001f1f7" +FLAG_AMERICAN_SAMOA = "\U0001f1e6\U0001f1f8" +FLAG_AUSTRIA = "\U0001f1e6\U0001f1f9" +FLAG_AUSTRALIA = "\U0001f1e6\U0001f1fa" +FLAG_ARUBA = "\U0001f1e6\U0001f1fc" +FLAG_ALAND_ISLANDS = "\U0001f1e6\U0001f1fd" +FLAG_AZERBAIJAN = "\U0001f1e6\U0001f1ff" +FLAG_BOSNIA_ANDAMP_HERZEGOVINA = "\U0001f1e7\U0001f1e6" +FLAG_BARBADOS = "\U0001f1e7\U0001f1e7" +FLAG_BANGLADESH = "\U0001f1e7\U0001f1e9" +FLAG_BELGIUM = "\U0001f1e7\U0001f1ea" +FLAG_BURKINA_FASO = "\U0001f1e7\U0001f1eb" +FLAG_BULGARIA = "\U0001f1e7\U0001f1ec" +FLAG_BAHRAIN = "\U0001f1e7\U0001f1ed" +FLAG_BURUNDI = "\U0001f1e7\U0001f1ee" +FLAG_BENIN = "\U0001f1e7\U0001f1ef" +FLAG_ST_BARTHELEMY = "\U0001f1e7\U0001f1f1" +FLAG_BERMUDA = "\U0001f1e7\U0001f1f2" +FLAG_BRUNEI = "\U0001f1e7\U0001f1f3" +FLAG_BOLIVIA = "\U0001f1e7\U0001f1f4" +FLAG_CARIBBEAN_NETHERLANDS = "\U0001f1e7\U0001f1f6" +FLAG_BRAZIL = "\U0001f1e7\U0001f1f7" +FLAG_BAHAMAS = "\U0001f1e7\U0001f1f8" +FLAG_BHUTAN = "\U0001f1e7\U0001f1f9" +FLAG_BOUVET_ISLAND = "\U0001f1e7\U0001f1fb" +FLAG_BOTSWANA = "\U0001f1e7\U0001f1fc" +FLAG_BELARUS = "\U0001f1e7\U0001f1fe" +FLAG_BELIZE = "\U0001f1e7\U0001f1ff" +FLAG_CANADA = "\U0001f1e8\U0001f1e6" +FLAG_COCOS_KEELING_ISLANDS = "\U0001f1e8\U0001f1e8" +FLAG_CONGO_KINSHASA = "\U0001f1e8\U0001f1e9" +FLAG_CENTRAL_AFRICAN_REPUBLIC = "\U0001f1e8\U0001f1eb" +FLAG_CONGO_BRAZZAVILLE = "\U0001f1e8\U0001f1ec" +FLAG_SWITZERLAND = "\U0001f1e8\U0001f1ed" +FLAG_COTE_D_IVOIRE = "\U0001f1e8\U0001f1ee" +FLAG_COOK_ISLANDS = "\U0001f1e8\U0001f1f0" +FLAG_CHILE = "\U0001f1e8\U0001f1f1" +FLAG_CAMEROON = "\U0001f1e8\U0001f1f2" +FLAG_CHINA = "\U0001f1e8\U0001f1f3" +FLAG_COLOMBIA = "\U0001f1e8\U0001f1f4" +FLAG_CLIPPERTON_ISLAND = "\U0001f1e8\U0001f1f5" +FLAG_COSTA_RICA = "\U0001f1e8\U0001f1f7" +FLAG_CUBA = "\U0001f1e8\U0001f1fa" +FLAG_CAPE_VERDE = "\U0001f1e8\U0001f1fb" +FLAG_CURACAO = "\U0001f1e8\U0001f1fc" +FLAG_CHRISTMAS_ISLAND = "\U0001f1e8\U0001f1fd" +FLAG_CYPRUS = "\U0001f1e8\U0001f1fe" +FLAG_CZECHIA = "\U0001f1e8\U0001f1ff" +FLAG_GERMANY = "\U0001f1e9\U0001f1ea" +FLAG_DIEGO_GARCIA = "\U0001f1e9\U0001f1ec" +FLAG_DJIBOUTI = "\U0001f1e9\U0001f1ef" +FLAG_DENMARK = "\U0001f1e9\U0001f1f0" +FLAG_DOMINICA = "\U0001f1e9\U0001f1f2" +FLAG_DOMINICAN_REPUBLIC = "\U0001f1e9\U0001f1f4" +FLAG_ALGERIA = "\U0001f1e9\U0001f1ff" +FLAG_CEUTA_ANDAMP_MELILLA = "\U0001f1ea\U0001f1e6" +FLAG_ECUADOR = "\U0001f1ea\U0001f1e8" +FLAG_ESTONIA = "\U0001f1ea\U0001f1ea" +FLAG_EGYPT = "\U0001f1ea\U0001f1ec" +FLAG_WESTERN_SAHARA = "\U0001f1ea\U0001f1ed" +FLAG_ERITREA = "\U0001f1ea\U0001f1f7" +FLAG_SPAIN = "\U0001f1ea\U0001f1f8" +FLAG_ETHIOPIA = "\U0001f1ea\U0001f1f9" +FLAG_EUROPEAN_UNION = "\U0001f1ea\U0001f1fa" +FLAG_FINLAND = "\U0001f1eb\U0001f1ee" +FLAG_FIJI = "\U0001f1eb\U0001f1ef" +FLAG_FALKLAND_ISLANDS = "\U0001f1eb\U0001f1f0" +FLAG_MICRONESIA = "\U0001f1eb\U0001f1f2" +FLAG_FAROE_ISLANDS = "\U0001f1eb\U0001f1f4" +FLAG_FRANCE = "\U0001f1eb\U0001f1f7" +FLAG_GABON = "\U0001f1ec\U0001f1e6" +FLAG_UNITED_KINGDOM = "\U0001f1ec\U0001f1e7" +FLAG_GRENADA = "\U0001f1ec\U0001f1e9" +FLAG_GEORGIA = "\U0001f1ec\U0001f1ea" +FLAG_FRENCH_GUIANA = "\U0001f1ec\U0001f1eb" +FLAG_GUERNSEY = "\U0001f1ec\U0001f1ec" +FLAG_GHANA = "\U0001f1ec\U0001f1ed" +FLAG_GIBRALTAR = "\U0001f1ec\U0001f1ee" +FLAG_GREENLAND = "\U0001f1ec\U0001f1f1" +FLAG_GAMBIA = "\U0001f1ec\U0001f1f2" +FLAG_GUINEA = "\U0001f1ec\U0001f1f3" +FLAG_GUADELOUPE = "\U0001f1ec\U0001f1f5" +FLAG_EQUATORIAL_GUINEA = "\U0001f1ec\U0001f1f6" +FLAG_GREECE = "\U0001f1ec\U0001f1f7" +FLAG_SOUTH_GEORGIA_ANDAMP_SOUTH_SANDWICH_ISLANDS = "\U0001f1ec\U0001f1f8" +FLAG_GUATEMALA = "\U0001f1ec\U0001f1f9" +FLAG_GUAM = "\U0001f1ec\U0001f1fa" +FLAG_GUINEA_BISSAU = "\U0001f1ec\U0001f1fc" +FLAG_GUYANA = "\U0001f1ec\U0001f1fe" +FLAG_HONG_KONG_SAR_CHINA = "\U0001f1ed\U0001f1f0" +FLAG_HEARD_ANDAMP_MCDONALD_ISLANDS = "\U0001f1ed\U0001f1f2" +FLAG_HONDURAS = "\U0001f1ed\U0001f1f3" +FLAG_CROATIA = "\U0001f1ed\U0001f1f7" +FLAG_HAITI = "\U0001f1ed\U0001f1f9" +FLAG_HUNGARY = "\U0001f1ed\U0001f1fa" +FLAG_CANARY_ISLANDS = "\U0001f1ee\U0001f1e8" +FLAG_INDONESIA = "\U0001f1ee\U0001f1e9" +FLAG_IRELAND = "\U0001f1ee\U0001f1ea" +FLAG_ISRAEL = "\U0001f1ee\U0001f1f1" +FLAG_ISLE_OF_MAN = "\U0001f1ee\U0001f1f2" +FLAG_INDIA = "\U0001f1ee\U0001f1f3" +FLAG_BRITISH_INDIAN_OCEAN_TERRITORY = "\U0001f1ee\U0001f1f4" +FLAG_IRAQ = "\U0001f1ee\U0001f1f6" +FLAG_IRAN = "\U0001f1ee\U0001f1f7" +FLAG_ICELAND = "\U0001f1ee\U0001f1f8" +FLAG_ITALY = "\U0001f1ee\U0001f1f9" +FLAG_JERSEY = "\U0001f1ef\U0001f1ea" +FLAG_JAMAICA = "\U0001f1ef\U0001f1f2" +FLAG_JORDAN = "\U0001f1ef\U0001f1f4" +FLAG_JAPAN = "\U0001f1ef\U0001f1f5" +FLAG_KENYA = "\U0001f1f0\U0001f1ea" +FLAG_KYRGYZSTAN = "\U0001f1f0\U0001f1ec" +FLAG_CAMBODIA = "\U0001f1f0\U0001f1ed" +FLAG_KIRIBATI = "\U0001f1f0\U0001f1ee" +FLAG_COMOROS = "\U0001f1f0\U0001f1f2" +FLAG_ST_KITTS_ANDAMP_NEVIS = "\U0001f1f0\U0001f1f3" +FLAG_NORTH_KOREA = "\U0001f1f0\U0001f1f5" +FLAG_SOUTH_KOREA = "\U0001f1f0\U0001f1f7" +FLAG_KUWAIT = "\U0001f1f0\U0001f1fc" +FLAG_CAYMAN_ISLANDS = "\U0001f1f0\U0001f1fe" +FLAG_KAZAKHSTAN = "\U0001f1f0\U0001f1ff" +FLAG_LAOS = "\U0001f1f1\U0001f1e6" +FLAG_LEBANON = "\U0001f1f1\U0001f1e7" +FLAG_ST_LUCIA = "\U0001f1f1\U0001f1e8" +FLAG_LIECHTENSTEIN = "\U0001f1f1\U0001f1ee" +FLAG_SRI_LANKA = "\U0001f1f1\U0001f1f0" +FLAG_LIBERIA = "\U0001f1f1\U0001f1f7" +FLAG_LESOTHO = "\U0001f1f1\U0001f1f8" +FLAG_LITHUANIA = "\U0001f1f1\U0001f1f9" +FLAG_LUXEMBOURG = "\U0001f1f1\U0001f1fa" +FLAG_LATVIA = "\U0001f1f1\U0001f1fb" +FLAG_LIBYA = "\U0001f1f1\U0001f1fe" +FLAG_MOROCCO = "\U0001f1f2\U0001f1e6" +FLAG_MONACO = "\U0001f1f2\U0001f1e8" +FLAG_MOLDOVA = "\U0001f1f2\U0001f1e9" +FLAG_MONTENEGRO = "\U0001f1f2\U0001f1ea" +FLAG_ST_MARTIN = "\U0001f1f2\U0001f1eb" +FLAG_MADAGASCAR = "\U0001f1f2\U0001f1ec" +FLAG_MARSHALL_ISLANDS = "\U0001f1f2\U0001f1ed" +FLAG_NORTH_MACEDONIA = "\U0001f1f2\U0001f1f0" +FLAG_MALI = "\U0001f1f2\U0001f1f1" +FLAG_MYANMAR_BURMA = "\U0001f1f2\U0001f1f2" +FLAG_MONGOLIA = "\U0001f1f2\U0001f1f3" +FLAG_MACAO_SAR_CHINA = "\U0001f1f2\U0001f1f4" +FLAG_NORTHERN_MARIANA_ISLANDS = "\U0001f1f2\U0001f1f5" +FLAG_MARTINIQUE = "\U0001f1f2\U0001f1f6" +FLAG_MAURITANIA = "\U0001f1f2\U0001f1f7" +FLAG_MONTSERRAT = "\U0001f1f2\U0001f1f8" +FLAG_MALTA = "\U0001f1f2\U0001f1f9" +FLAG_MAURITIUS = "\U0001f1f2\U0001f1fa" +FLAG_MALDIVES = "\U0001f1f2\U0001f1fb" +FLAG_MALAWI = "\U0001f1f2\U0001f1fc" +FLAG_MEXICO = "\U0001f1f2\U0001f1fd" +FLAG_MALAYSIA = "\U0001f1f2\U0001f1fe" +FLAG_MOZAMBIQUE = "\U0001f1f2\U0001f1ff" +FLAG_NAMIBIA = "\U0001f1f3\U0001f1e6" +FLAG_NEW_CALEDONIA = "\U0001f1f3\U0001f1e8" +FLAG_NIGER = "\U0001f1f3\U0001f1ea" +FLAG_NORFOLK_ISLAND = "\U0001f1f3\U0001f1eb" +FLAG_NIGERIA = "\U0001f1f3\U0001f1ec" +FLAG_NICARAGUA = "\U0001f1f3\U0001f1ee" +FLAG_NETHERLANDS = "\U0001f1f3\U0001f1f1" +FLAG_NORWAY = "\U0001f1f3\U0001f1f4" +FLAG_NEPAL = "\U0001f1f3\U0001f1f5" +FLAG_NAURU = "\U0001f1f3\U0001f1f7" +FLAG_NIUE = "\U0001f1f3\U0001f1fa" +FLAG_NEW_ZEALAND = "\U0001f1f3\U0001f1ff" +FLAG_OMAN = "\U0001f1f4\U0001f1f2" +FLAG_PANAMA = "\U0001f1f5\U0001f1e6" +FLAG_PERU = "\U0001f1f5\U0001f1ea" +FLAG_FRENCH_POLYNESIA = "\U0001f1f5\U0001f1eb" +FLAG_PAPUA_NEW_GUINEA = "\U0001f1f5\U0001f1ec" +FLAG_PHILIPPINES = "\U0001f1f5\U0001f1ed" +FLAG_PAKISTAN = "\U0001f1f5\U0001f1f0" +FLAG_POLAND = "\U0001f1f5\U0001f1f1" +FLAG_ST_PIERRE_ANDAMP_MIQUELON = "\U0001f1f5\U0001f1f2" +FLAG_PITCAIRN_ISLANDS = "\U0001f1f5\U0001f1f3" +FLAG_PUERTO_RICO = "\U0001f1f5\U0001f1f7" +FLAG_PALESTINIAN_TERRITORIES = "\U0001f1f5\U0001f1f8" +FLAG_PORTUGAL = "\U0001f1f5\U0001f1f9" +FLAG_PALAU = "\U0001f1f5\U0001f1fc" +FLAG_PARAGUAY = "\U0001f1f5\U0001f1fe" +FLAG_QATAR = "\U0001f1f6\U0001f1e6" +FLAG_REUNION = "\U0001f1f7\U0001f1ea" +FLAG_ROMANIA = "\U0001f1f7\U0001f1f4" +FLAG_SERBIA = "\U0001f1f7\U0001f1f8" +FLAG_RUSSIA = "\U0001f1f7\U0001f1fa" +FLAG_RWANDA = "\U0001f1f7\U0001f1fc" +FLAG_SAUDI_ARABIA = "\U0001f1f8\U0001f1e6" +FLAG_SOLOMON_ISLANDS = "\U0001f1f8\U0001f1e7" +FLAG_SEYCHELLES = "\U0001f1f8\U0001f1e8" +FLAG_SUDAN = "\U0001f1f8\U0001f1e9" +FLAG_SWEDEN = "\U0001f1f8\U0001f1ea" +FLAG_SINGAPORE = "\U0001f1f8\U0001f1ec" +FLAG_ST_HELENA = "\U0001f1f8\U0001f1ed" +FLAG_SLOVENIA = "\U0001f1f8\U0001f1ee" +FLAG_SVALBARD_ANDAMP_JAN_MAYEN = "\U0001f1f8\U0001f1ef" +FLAG_SLOVAKIA = "\U0001f1f8\U0001f1f0" +FLAG_SIERRA_LEONE = "\U0001f1f8\U0001f1f1" +FLAG_SAN_MARINO = "\U0001f1f8\U0001f1f2" +FLAG_SENEGAL = "\U0001f1f8\U0001f1f3" +FLAG_SOMALIA = "\U0001f1f8\U0001f1f4" +FLAG_SURINAME = "\U0001f1f8\U0001f1f7" +FLAG_SOUTH_SUDAN = "\U0001f1f8\U0001f1f8" +FLAG_SAO_TOME_ANDAMP_PRINCIPE = "\U0001f1f8\U0001f1f9" +FLAG_EL_SALVADOR = "\U0001f1f8\U0001f1fb" +FLAG_SINT_MAARTEN = "\U0001f1f8\U0001f1fd" +FLAG_SYRIA = "\U0001f1f8\U0001f1fe" +FLAG_ESWATINI = "\U0001f1f8\U0001f1ff" +FLAG_TRISTAN_DA_CUNHA = "\U0001f1f9\U0001f1e6" +FLAG_TURKS_ANDAMP_CAICOS_ISLANDS = "\U0001f1f9\U0001f1e8" +FLAG_CHAD = "\U0001f1f9\U0001f1e9" +FLAG_FRENCH_SOUTHERN_TERRITORIES = "\U0001f1f9\U0001f1eb" +FLAG_TOGO = "\U0001f1f9\U0001f1ec" +FLAG_THAILAND = "\U0001f1f9\U0001f1ed" +FLAG_TAJIKISTAN = "\U0001f1f9\U0001f1ef" +FLAG_TOKELAU = "\U0001f1f9\U0001f1f0" +FLAG_TIMOR_LESTE = "\U0001f1f9\U0001f1f1" +FLAG_TURKMENISTAN = "\U0001f1f9\U0001f1f2" +FLAG_TUNISIA = "\U0001f1f9\U0001f1f3" +FLAG_TONGA = "\U0001f1f9\U0001f1f4" +FLAG_TURKEY = "\U0001f1f9\U0001f1f7" +FLAG_TRINIDAD_ANDAMP_TOBAGO = "\U0001f1f9\U0001f1f9" +FLAG_TUVALU = "\U0001f1f9\U0001f1fb" +FLAG_TAIWAN = "\U0001f1f9\U0001f1fc" +FLAG_TANZANIA = "\U0001f1f9\U0001f1ff" +FLAG_UKRAINE = "\U0001f1fa\U0001f1e6" +FLAG_UGANDA = "\U0001f1fa\U0001f1ec" +FLAG_U_S_OUTLYING_ISLANDS = "\U0001f1fa\U0001f1f2" +FLAG_UNITED_NATIONS = "\U0001f1fa\U0001f1f3" +FLAG_UNITED_STATES = "\U0001f1fa\U0001f1f8" +FLAG_URUGUAY = "\U0001f1fa\U0001f1fe" +FLAG_UZBEKISTAN = "\U0001f1fa\U0001f1ff" +FLAG_VATICAN_CITY = "\U0001f1fb\U0001f1e6" +FLAG_ST_VINCENT_ANDAMP_GRENADINES = "\U0001f1fb\U0001f1e8" +FLAG_VENEZUELA = "\U0001f1fb\U0001f1ea" +FLAG_BRITISH_VIRGIN_ISLANDS = "\U0001f1fb\U0001f1ec" +FLAG_U_S_VIRGIN_ISLANDS = "\U0001f1fb\U0001f1ee" +FLAG_VIETNAM = "\U0001f1fb\U0001f1f3" +FLAG_VANUATU = "\U0001f1fb\U0001f1fa" +FLAG_WALLIS_ANDAMP_FUTUNA = "\U0001f1fc\U0001f1eb" +FLAG_SAMOA = "\U0001f1fc\U0001f1f8" +FLAG_KOSOVO = "\U0001f1fd\U0001f1f0" +FLAG_YEMEN = "\U0001f1fe\U0001f1ea" +FLAG_MAYOTTE = "\U0001f1fe\U0001f1f9" +FLAG_SOUTH_AFRICA = "\U0001f1ff\U0001f1e6" +FLAG_ZAMBIA = "\U0001f1ff\U0001f1f2" +FLAG_ZIMBABWE = "\U0001f1ff\U0001f1fc" +FLAG_ENGLAND = "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f" +FLAG_SCOTLAND = "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f" +FLAG_WALES = "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f" +REGIONAL_INDICATOR_SYMBOL_LETTER_A = "\U0001f1e6" +REGIONAL_INDICATOR_SYMBOL_LETTER_B = "\U0001f1e7" +REGIONAL_INDICATOR_SYMBOL_LETTER_C = "\U0001f1e8" +REGIONAL_INDICATOR_SYMBOL_LETTER_D = "\U0001f1e9" +REGIONAL_INDICATOR_SYMBOL_LETTER_E = "\U0001f1ea" +REGIONAL_INDICATOR_SYMBOL_LETTER_F = "\U0001f1eb" +REGIONAL_INDICATOR_SYMBOL_LETTER_G = "\U0001f1ec" +REGIONAL_INDICATOR_SYMBOL_LETTER_H = "\U0001f1ed" +REGIONAL_INDICATOR_SYMBOL_LETTER_I = "\U0001f1ee" +REGIONAL_INDICATOR_SYMBOL_LETTER_J = "\U0001f1ef" +REGIONAL_INDICATOR_SYMBOL_LETTER_K = "\U0001f1f0" +REGIONAL_INDICATOR_SYMBOL_LETTER_L = "\U0001f1f1" +REGIONAL_INDICATOR_SYMBOL_LETTER_M = "\U0001f1f2" +REGIONAL_INDICATOR_SYMBOL_LETTER_N = "\U0001f1f3" +REGIONAL_INDICATOR_SYMBOL_LETTER_O = "\U0001f1f4" +REGIONAL_INDICATOR_SYMBOL_LETTER_P = "\U0001f1f5" +REGIONAL_INDICATOR_SYMBOL_LETTER_Q = "\U0001f1f6" +REGIONAL_INDICATOR_SYMBOL_LETTER_R = "\U0001f1f7" +REGIONAL_INDICATOR_SYMBOL_LETTER_S = "\U0001f1f8" +REGIONAL_INDICATOR_SYMBOL_LETTER_T = "\U0001f1f9" +REGIONAL_INDICATOR_SYMBOL_LETTER_U = "\U0001f1fa" +REGIONAL_INDICATOR_SYMBOL_LETTER_V = "\U0001f1fb" +REGIONAL_INDICATOR_SYMBOL_LETTER_W = "\U0001f1fc" +REGIONAL_INDICATOR_SYMBOL_LETTER_X = "\U0001f1fd" +REGIONAL_INDICATOR_SYMBOL_LETTER_Y = "\U0001f1fe" +REGIONAL_INDICATOR_SYMBOL_LETTER_Z = "\U0001f1ff" +TAG_RIGHT_CURLY_BRACKET = "\U000e007d" +DIGIT_FIVE = "5\ufe0f" +TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" +TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" +TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" +COMBINING_ENCLOSING_KEYCAP = "\u20e3" +TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" +TAG_ASTERISK = "\U000e002a" +TAG_FULL_STOP = "\U000e002e" +TAG_CIRCUMFLEX_ACCENT = "\U000e005e" +DIGIT_ONE = "1\ufe0f" +TAG_COMMA = "\U000e002c" +DIGIT_ZERO = "0\ufe0f" +TAG_EQUALS_SIGN = "\U000e003d" +TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" +TAG_COMMERCIAL_AT = "\U000e0040" +DIGIT_EIGHT = "8\ufe0f" +TAG_NUMBER_SIGN = "\U000e0023" +TAG_LATIN_CAPITAL_LETTER_T = "\U000e0054" +TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" +DIGIT_SIX = "6\ufe0f" +TAG_PERCENT_SIGN = "\U000e0025" +VARIATION_SELECTOR_16 = "\ufe0f" +TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057" +TAG_DOLLAR_SIGN = "\U000e0024" +TAG_LOW_LINE = "\U000e005f" +TAG_DIGIT_EIGHT = "\U000e0038" +TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d" +TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" +TAG_REVERSE_SOLIDUS = "\U000e005c" +TAG_SOLIDUS = "\U000e002f" +TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" +TAG_DIGIT_NINE = "\U000e0039" +TAG_LEFT_CURLY_BRACKET = "\U000e007b" +TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" +TAG_LATIN_SMALL_LETTER_W = "\U000e0077" +TAG_DIGIT_ZERO = "\U000e0030" +TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" +TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" +TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059" +TAG_TILDE = "\U000e007e" +TAG_LATIN_SMALL_LETTER_P = "\U000e0070" +TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a" +TAG_GREATER_THAN_SIGN = "\U000e003e" +TAG_LATIN_SMALL_LETTER_S = "\U000e0073" +TAG_LATIN_SMALL_LETTER_G = "\U000e0067" +TAG_APOSTROPHE = "\U000e0027" +TAG_RIGHT_PARENTHESIS = "\U000e0029" +TAG_DIGIT_THREE = "\U000e0033" +TAG_LEFT_PARENTHESIS = "\U000e0028" +TAG_DIGIT_SEVEN = "\U000e0037" +TAG_LATIN_SMALL_LETTER_O = "\U000e006f" +TAG_DIGIT_SIX = "\U000e0036" +TAG_DIGIT_TWO = "\U000e0032" +TAG_LATIN_SMALL_LETTER_F = "\U000e0066" +TAG_LATIN_SMALL_LETTER_K = "\U000e006b" +TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" +TAG_SPACE = "\U000e0020" +TAG_LATIN_SMALL_LETTER_I = "\U000e0069" +DIGIT_TWO = "2\ufe0f" +TAG_DIGIT_ONE = "\U000e0031" +TAG_RIGHT_SQUARE_BRACKET = "\U000e005d" +TAG_LATIN_SMALL_LETTER_R = "\U000e0072" +HASH_SIGN = "#\ufe0f" +TAG_SEMICOLON = "\U000e003b" +TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" +TAG_HYPHEN_MINUS = "\U000e002d" +ASTERISK = "*\ufe0f" +TAG_LATIN_SMALL_LETTER_A = "\U000e0061" +TAG_EXCLAMATION_MARK = "\U000e0021" +TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" +TAG_LATIN_SMALL_LETTER_C = "\U000e0063" +TAG_GRAVE_ACCENT = "\U000e0060" +ZERO_WIDTH_JOINER = "\u200d" +TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047" +DIGIT_NINE = "9\ufe0f" +TAG_VERTICAL_LINE = "\U000e007c" +TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" +TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" +TAG_LATIN_SMALL_LETTER_J = "\U000e006a" +TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" +TAG_AMPERSAND = "\U000e0026" +TAG_LATIN_SMALL_LETTER_L = "\U000e006c" +TAG_LATIN_SMALL_LETTER_X = "\U000e0078" +DIGIT_SEVEN = "7\ufe0f" +TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" +TAG_LATIN_SMALL_LETTER_T = "\U000e0074" +TAG_QUESTION_MARK = "\U000e003f" +TAG_LATIN_SMALL_LETTER_B = "\U000e0062" +TAG_LEFT_SQUARE_BRACKET = "\U000e005b" +TAG_LATIN_SMALL_LETTER_D = "\U000e0064" +TAG_LATIN_SMALL_LETTER_E = "\U000e0065" +TAG_LATIN_SMALL_LETTER_M = "\U000e006d" +TAG_LESS_THAN_SIGN = "\U000e003c" +TAG_DIGIT_FIVE = "\U000e0035" +TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" +TAG_LATIN_SMALL_LETTER_N = "\U000e006e" +TAG_PLUS_SIGN = "\U000e002b" +TAG_COLON = "\U000e003a" +DIGIT_THREE = "3\ufe0f" +TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" +TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052" +TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" +DIGIT_FOUR = "4\ufe0f" +TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" +TAG_QUOTATION_MARK = "\U000e0022" +CANCEL_TAG = "\U000e007f" +TAG_LATIN_SMALL_LETTER_V = "\U000e0076" +TAG_LATIN_SMALL_LETTER_H = "\U000e0068" +TAG_LATIN_SMALL_LETTER_U = "\U000e0075" +TAG_DIGIT_FOUR = "\U000e0034" diff --git a/rubpy/gadgets/__init__.py b/rubpy/gadgets/__init__.py new file mode 100644 index 0000000..799a023 --- /dev/null +++ b/rubpy/gadgets/__init__.py @@ -0,0 +1,5 @@ +from . import methods +from . import thumbnail +from . import exceptions +from .classino import Classino +from . import models \ No newline at end of file diff --git a/rubpy/gadgets/classino.py b/rubpy/gadgets/classino.py new file mode 100644 index 0000000..548c451 --- /dev/null +++ b/rubpy/gadgets/classino.py @@ -0,0 +1,26 @@ +import difflib +import inspect +import warnings + +class Classino: + @classmethod + def create(cls, name, __base, authorise: list = [], exception: bool = True, *args, **kwargs): + result = None + if name in authorise: + result = name + + else: + proposal = difflib.get_close_matches(name, authorise, n=1) + if proposal: + result = proposal[0] + caller = inspect.getframeinfo(inspect.stack()[2][0]) + warnings.warn( + f'{caller.filename}:{caller.lineno}: do you mean' + f' "{name}", "{result}"? correct it') + + if result is not None or not exception: + if result is None: + result = name + return type(result, __base, {'__name__': result, **kwargs}) + + raise AttributeError(f'module has no attribute ({name})') \ No newline at end of file diff --git a/rubpy/gadgets/exceptions.py b/rubpy/gadgets/exceptions.py new file mode 100644 index 0000000..3e8654c --- /dev/null +++ b/rubpy/gadgets/exceptions.py @@ -0,0 +1,141 @@ +import sys + +class ClientError(Exception): + pass + +class SocksError(ClientError): + pass + +class StopHandler(ClientError): + pass + +class CancelledError(ClientError): + pass + + +class UnknownAuthMethod(SocksError): + pass + + +class InvalidServerReply(SocksError): + pass + + +class SocksConnectionError(SocksError): + pass + + +class InvalidServerVersion(SocksError): + pass + + +class NoAcceptableAuthMethods(SocksError): + pass + + +class LoginAuthenticationFailed(SocksError): + pass + + +class RequestError(ClientError): + def __init__(self, message, request=None): + self.message = str(message) + self.request = request + + +class CodeIsUsed(RequestError): + pass + + +class TooRequests(RequestError): + pass + + +class InvalidAuth(RequestError): + pass + + +class ServerError(RequestError): + pass + + +class UrlNotFound(RequestError): + pass + + +class ErrorAction(RequestError): + pass + + +class ErrorIgnore(RequestError): + pass + + +class ErrorGeneric(RequestError): + pass + + +class NoConnection(RequestError): + pass + + +class InvalidInput(RequestError): + pass + + +class Undeliverable(RequestError): + pass + + +class NotRegistered(RequestError): + pass + + +class CodeIsExpired(RequestError): + pass + + +class InvalidMethod(RequestError): + pass + + +class UsernameExist(RequestError): + pass + + +class NotRegistrred(RequestError): + pass + + +class ErrorTryAgain(RequestError): + pass + + +class ErrorMessageTry(RequestError): + pass + + +class InternalProblem(RequestError): + pass + + +class ErrorMessageIgn(RequestError): + pass + + +class NotSupportedApiVersion(RequestError): + pass + + +class ExcetionsHandler: + def __init__(self, name) -> None: + self.name = name + + def __getattr__(self, name): + name = ''.join([chunk.title() for chunk in name.split('_')]) + return globals().get(name, ClientError) + + def __call__(self, name, *args, **kwargs): + return getattr(self, name) + +sys.modules[__name__] = ExcetionsHandler(__name__) \ No newline at end of file diff --git a/rubpy/gadgets/grouping.py b/rubpy/gadgets/grouping.py new file mode 100644 index 0000000..ae322d6 --- /dev/null +++ b/rubpy/gadgets/grouping.py @@ -0,0 +1,752 @@ +grouping = { + "users": { + "Values": ["Block", "Unblock"], + "GetUserInfo": { + "params": { + "user_guid": {"types": ["str", "optional"]} + } + }, + "SetBlockUser": { + "params": { + "user_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Block", "Unblock"], "default": "Block"} + } + }, + "DeleteUserChat": { + "params": { + "user_guid": {"types": "str"}, + "last_deleted_message_id": {"types": ["str", "int"], "func": "to_string"} + } + }, + "CheckUserUsername": { + "params": { + "username": {"types": "str"} + } + } + }, + "chats": { + "Values": ["Mute", "Unmute", "Typing", "Uploading", "Recording", "Text", "Hashtag"], + "UploadAvatar": { + "params": { + "object_guid": {"types": "str"}, + "main_file_id": {"types": "str"}, + "thumbnail_file_id": {"types": "str"} + } + }, + "DeleteAvatar": { + "params": { + "object_guid": {"types": "str"}, + "avatar_id": {"types": "str"} + } + }, + "GetAvatars": { + "params": { + "object_guid": {"types": "str"} + } + }, + "GetChats": { + "params": { + "start_id": {"types": ["str", "optional"]} + } + }, + "SeenChats": { + "params": { + "seen_list": {"types": "dict", "func": "to_array"} + } + }, + "GetChatAds": { + "params": { + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "SetActionChat": { + "params": { + "object_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Mute", "Unmute"], "default": "Mute"} + } + }, + "GetChatsUpdates": { + "params": { + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "SendChatActivity": { + "params": { + "object_guid": {"types": "str"}, + "activity": {"types": ["str", "optional"], "alloweds": ["Typing", "Uploading", "Recording"], "default": "Typing"} + } + }, + "DeleteChatHistory": { + "params": { + "object_guid": {"types": "str"}, + "last_message_id": {"types": ["int", "str"], "func": "to_string"} + } + }, + "SearchChatMessages": { + "params": { + "object_guid": {"types": "str"}, + "search_text": {"types": "str"}, + "type": {"types": ["str", "optional"], "alloweds": ["Text", "Hashtag"], "default": "Hashtag"} + } + } + + }, + "extras": { + "Values": [], + "SearchGlobalObjects": { + "params": { + "search_text": {"types": "str"} + } + }, + "GetAbsObjects": { + "params": { + "object_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "GetObjectByUsername": { + "params": { + "username": {"types": "str"} + } + }, + "GetLinkFromAppUrl": { + "params": { + "app_url": {"types": "str"} + } + } + }, + "groups": { + "Values": ["Set", "Unset", "SetAdmin", "UnsetAdmin", "Hidden", "Visible", "AddMember", "ViewAdmins", "ViewMembers", "SendMessages", "SetAdmin", "BanMember", "ChangeInfo", "PinMessages", "SetJoinLink", "SetMemberAccess", "DeleteGlobalAllMessages"], + "AddGroup": { + "params": { + "title": {"types": "str"}, + "member_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "JoinGroup": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "LeaveGroup": { + "params": { + "group_guid": {"types": "str"} + } + }, + "RemoveGroup": { + "params": { + "group_guid": {"types": "str"} + } + }, + "GetGroupInfo": { + "params": { + "group_guid": {"types": "str"} + } + }, + "GetGroupLink": { + "params": { + "group_guid": {"types": "str"} + } + }, + "SetGroupLink": { + "params": { + "group_guid": {"types": "str"} + } + }, + "EditGroupInfo": { + "updated_parameters": True, + "params": { + "group_guid": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title", "description", "slow_mode", "chat_history_for_new_members", "event_messages", "chat_reaction_setting"]}, + "title": {"types": ["str", "optional"]}, + "description": {"types": ["str", "optional"]}, + "slow_mode": {"types": ["int", "str", "optional"]}, + "event_messages": {"types": ["bool", "optional"]}, + "chat_reaction_setting": {"types": ["dict", "optional"]}, + "chat_history_for_new_members": {"types": ["str", "optional"], "alloweds": ["Hidden", "Visible"]}, + } + }, + "SetGroupAdmin": { + "params": { + "group_guid": {"types": "str"}, + "member_guid": {"types": "str"}, + "access_list": {"types": ["str", "list"], "alloweds": ["SetAdmin", "BanMember", "ChangeInfo", "PinMessages", "SetJoinLink", "SetMemberAccess", "DeleteGlobalAllMessages"], "func": "to_array"}, + "action": {"types": ["str", "optional"], "alloweds": ["SetAdmin", "UnsetAdmin"], "default": "SetAdmin"} + } + }, + "BanGroupMember": { + "params": { + "group_guid": {"types": "str"}, + "member_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Set", "Unset"], "default": "Set"} + } + }, + "AddGroupMembers": { + "params": { + "group_guid": {"types": "str"}, + "member_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "GetGroupAllMembers": { + "params": { + "group_guid": {"types": "str"}, + "search_text": {"types": ["str", "optional"], "default": ""}, + "start_id": {"types": ["str", "optional"]} + } + }, + "GetGroupAdminMembers": { + "params": { + "group_guid": {"types": "str"}, + "start_id": {"types": ["str", "optional"]} + } + }, + "GetGroupMentionList": { + "params": { + "group_guid": {"types": "str"}, + "search_mention": {"types": ["str", "optional"]} + } + }, + "GetGroupDefaultAccess": { + "params": { + "group_guid": {"types": "str"} + + } + }, + "SetGroupDefaultAccess": { + "params": { + "group_guid": {"types": "str"}, + "access_list": {"types": ["str", "list"], "alloweds": ["AddMember", "ViewAdmins", "ViewMembers", "SendMessages"]} + } + }, + "GetBannedGroupMembers": { + "params": { + "group_guid": {"types": "str"}, + "start_id": {"types": ["str", "optional"]} + } + }, + "GroupPreviewByJoinLink": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "DeleteNoAccessGroupChat": { + "params": { + "group_guid": {"types": "str"} + } + }, + "GetGroupAdminAccessList": { + "params": { + "group_guid": {"types": "str"}, + "member_guid": {"types": "str"} + } + }, + "CreateGroupVoiceChat": { + "params": { + "group_guid": {"types": "str"}, + } + }, + "SetGroupVoiceChatSetting": { + "updated_parameters": True, + "params": { + "group_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "title": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title"]} + } + }, + "LeaveGroupVoiceChat": { + "params": { + "chat_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"} + } + }, + "GetGroupVoiceChatUpdates": { + "params": { + "chat_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "state": {"types": "int"} + } + }, + "SetGroupVoiceChatState": { + "params": { + "chat_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "activity": {"types": "str"} + } + }, + "SendGroupVoiceChatActivity": { + "params": { + "chat_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "action": {"types": "str"}, + "participant_object_guid": {"types": "str"} + } + }, + "GetGroupVoiceChatParticipants": { + "params": { + "chat_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + } + }, + "DiscardGroupVoiceChat": { + "params": { + "group_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"} + } + } + }, + "messages": { + "Values": [ + "Pin","Unpin", "Text", "Gif", "File", "Image", "Voice", "Music", "Video", "FileInline", "Quiz", "Regular", "FromMin", "FromMax", "Local", "Global"], + "SendMessage": { + "params": { + "object_guid": {"types": "str"}, + "message": { + "types": ["dict", "Struct", "str", "optional"], + "ifs": { + "str": {"func": "to_metadata", "unpack": True}, + "otherwise": {"cname": "sticker", "func": "to_array"} + } + }, + "reply_to_message_id": { + "types": ["str", "int", "optional"], + "func": "to_string" + }, + "file_inline": { + "types": ["Struct", "dict", "optional"], + "func": "to_array" + }, + "type": {"types": ["str", "optional"], "alloweds": ["FileInlineCaption", "FileInline"], "default": "FileInline"}, + "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} + } + }, + "EditMessage": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "text": {"types": "str", "func": "to_metadata", "unpack": True} + } + }, + "DeleteMessages": { + "params": { + "object_guid": {"types": "str"}, + "message_ids": {"types": ["int", "str", "list"], "func": "to_array"}, + "type": {"types": ["str", "optional"], "alloweds": ["Local", "Global"], "default": "Global"} + } + }, + "RequestSendFile": { + "params": { + "file_name": {"types": "str"}, + "size": {"types": ["str", "int", "float"], "func": "to_number"}, + "mime": {"types": ["str", "optional"], "heirship": ["file_name"], "func": "get_format"} + } + }, + "ForwardMessages": { + "params": { + "from_object_guid": {"types": "str"}, + "to_object_guid": {"types": "str"}, + "message_ids": {"types": ["int", "str", "list"], "func": "to_array"}, + "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} + } + }, + "CreatePoll": { + "params": { + "object_guid": {"types": "str"}, + "question": {"types": "str", }, + "options": {"types": "list", "minimum": 2}, + "type": {"types": ["str", "optional"], "alloweds": ["Quiz", "Regular"], "default": "Regular"}, + "is_anonymous": {"types": ["bool", "optional"]}, + "allows_multiple_answers": {"types": ["bool", "optional"]}, + "correct_option_index": {"types": ["str", "int", "optional"], "func": "to_number"}, + "explanation": {"types": ["str", "optional"]}, + "reply_to_message_id": {"types": ["int", "optional"], "default": 0}, + "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} + } + }, + "VotePoll": { + "params": { + "poll_id": {"types": "str"}, + "selection_index": {"types": ["int", "str"], "func": "to_number"} + } + }, + "GetPollStatus": { + "params": { + "poll_id": {"types": "str"} + } + }, + "GetPollOptionVoters": { + "params": { + "poll_id": {"types": "str"}, + "selection_index": {"types": ["int", "str"], "func": "to_number"}, + "start_id": {"types": ["str", "optional"]} + } + }, + "SetPinMessage": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "action": {"types": ["str", "optional"], "alloweds": ["Pin", "Unpin"], "default": "Pin"} + } + }, + "GetMessagesUpdates": { + "params": { + "object_guid": {"types": "str"}, + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "SearchGlobalMessages": { + "params": { + "search_text": {"types": "str"}, + "type": {"types": ["str", "optional"], "alloweds": ["Text"], "default": "Text"} + } + }, + "ClickMessageUrl": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "link_url": {"types": "str"} + } + }, + "GetMessagesByID": { + "params": { + "object_guid": {"types": "str"}, + "message_ids": {"types": ["int", "str", "list"], "func": "to_array"} + } + }, + "GetMessages": { + "params": { + "object_guid": {"types": "str"}, + "min_id": {"types": ["str", "int"], "func": "to_number"}, + "max_id": {"types": ["str", "int"], "func": "to_number"}, + "sort": {"types": ["str", "optional"], "alloweds": ["FromMin", "FromMax"], "default": "FromMin"}, + "limit": {"types": ["str", "int"], "func": "to_number", "default": 10}, + } + }, + "GetMessagesInterval": { + "params": { + "object_guid": {"types": "str"}, + "middle_message_id": {"types": ["str", "int"], "func": "to_string"}, + } + }, + "GetMessageShareUrl": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"]}, + } + }, + "ActionOnMessageReaction": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "action": {"types": "str", "alloweds": ["Add", "Remove"], "default": "Add"}, + "reaction_id": {"types": ["str", "int", "optional"], "func": "to_string"} + } + } + }, + "channels": { + "Values": ["Join", "Remove", "Archive", "Set", "Unset"], + "AddChannel": { + "params": { + "title": {"types": "str"}, + "description": {"types": ["str", "optional"]} + } + }, + "RemoveChannel": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "GetChannelInfo": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "EditChannelInfo": { + "updated_parameters": True, + "params": { + "channel_guid": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title", "description", "channel_type", "sign_messages"]}, + "title": {"types": "str"}, + "description": {"types": ["str", "optional"]}, + "channel_type": {"types": ["str", "optional"], "alloweds": ["Public", "Private"], "default": "Public" }, + "sign_messages": {"types": ["str", "optional"]} + } + }, + "JoinChannelAction": { + "params": { + "channel_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Join", "Remove", "Archive"], "default": "Join"} + } + }, + "JoinChannelByLink": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "AddChannelMembers": { + "params": { + "channel_guid": {"types": "str"}, + "member_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "BanChannelMember": { + "params": { + "channel_guid": {"types": "str"}, + "member_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Set", "Unset"], "default": "Set"} + } + }, + "CheckChannelUsername": { + "params": { + "username": {"types": "str"} + } + }, + "ChannelPreviewByJoinLink": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "GetChannelAllMembers": { + "params": { + "channel_guid": {"types": "str"}, + "search_text": {"types": ["str", "optional"]}, + "start_id": {"types": ["str", "optional"]} + } + }, + "GetChannelAdminMembers": { + "params": { + "channel_guid": {"types": "str"}, + "start_id": {"types": ["str", "optional"]} + } + }, + "UpdateChannelUsername": { + "params": { + "channel_guid": {"types": "str"}, + "username": {"types": "str"} + } + }, + "GetChannelLink": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "SetChannelLink": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "GetChannelAdminAccessList": { + "params": { + "channel_guid": {"types": "str"}, + "member_guid": {"types": "str"} + } + }, + "CreateChannelVoiceChat": { + "params": { + "channel_guid": {"types": "str"}, + } + }, + "SetChannelVoiceChatSetting": { + "updated_parameters": True, + "params": { + "channel_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "title": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title"]} + } + }, + "DiscardChannelVoiceChat": { + "params": { + "channel_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"} + } + } + }, + "contacts": { + "Values": [], + "DeleteContact": { + "params": { + "user_guid": {"types": "str"} + } + }, + "AddAddressBook": { + "params": { + "phone": {"types": "str", "func": "get_phone"}, + "first_name": {"types": "str"}, + "last_name": {"types": ["str", "optional"], "default": ""} + } + }, + "GetContactsUpdates": { + "params": { + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "GetContacts": { + "params": { + "start_id": {"types": ["int", "str", "optional"], "func": "to_string"} + } + } + }, + "settings": { + "Values": ["Nobody", "Everybody", "MyContacts", "Bots", "Groups", "Contacts", "Channels", "NonConatcts"], + "SetSetting": { + "updated_parameters": True, + "params": { + "updated_parameters": {"types": ["list"], "alloweds": ["show_my_last_online", "show_my_phone_number", "show_my_profile_photo", "link_forward_message", "can_join_chat_by"]}, + "show_my_last_online": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, + "show_my_phone_number": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, + "show_my_profile_photo": {"types": ["str", "optional"], "alloweds": ["Everybody", "MyContacts"]}, + "link_forward_message": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, + "can_join_chat_by": {"types": ["str", "optional"], "alloweds": ["Everybody", "MyContacts"]} + } + }, + "AddFolder": { + "params": { + "cname": {"types": "str"}, + "include_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "exclude_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "include_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []}, + "exclude_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []} + } + }, + "GetFolders": { + "params": { + "last_state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "EditFolder": { + "updated_parameters": True, + "params": { + "updated_parameters": {"types": ["list"], "alloweds": ["include_chat_types", "exclude_chat_types", "include_object_guids", "exclude_object_guids"]}, + "cname": {"types": "str"}, + "include_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "exclude_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "include_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []}, + "exclude_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []} + } + }, + "DeleteFolder": { + "params": { + "folder_id": {"types": "str"} + } + }, + "UpdateProfile": { + "updated_parameters": True, + "params": { + "updated_parameters": {"types": ["list"], "alloweds": ["first_name", "last_name", "bio"]}, + "first_name": {"types": ["str", "optional"]}, + "last_name": {"types": ["str", "optional"]}, + "bio": {"types": ["str", "optional"]} + } + }, + "UpdateUsername": { + "params": { + "username": {"types": "str"} + } + }, + "GetTwoPasscodeStatus": None, + "GetSuggestedFolders": None, + "GetPrivacySetting": None, + "GetBlockedUsers": None, + "GetMySessions": None, + "TerminateSession": { + "params": { + "session_key": {"types": "str"} + } + }, + "SetupTwoStepVerification": { + "params": { + "password": {"types": ["str", "int"], "func": "to_string"}, + "hint": {"types": ["str", "int"], "func": "to_string"}, + "recovery_email": {"types": "str"} + } + } + }, + "stickers": { + "Values": ["All", "Add", "Remove"], + "GetMyStickerSets": None, + "SearchStickers": { + "params": { + "search_text": {"types": ["str", "optional"], "default": ""}, + "start_id": {"types": ["str", "optional"]} + } + }, + "GetStickerSetByID": { + "params": { + "sticker_set_id": {"types": "str"} + } + }, + "ActionOnStickerSet": { + "params": { + "sticker_set_id": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Add", "Remove"], "default": "Add"} + } + }, + "GetStickersByEmoji": { + "params": { + "emoji": {"types": "str", "cname": "emoji_character"}, + "suggest_by": {"types": ["str", "optional"], "default": "Add"} + } + }, + "GetStickersBySetIDs": { + "params": { + "sticker_set_ids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "GetTrendStickerSets": { + "params": { + "start_id": {"types": ["str", "optional"]} + } + } + + }, + "authorisations": { + "Values": ["SMS", "Internal"], + "GetDCs": { + "urls": ["https://getdcmess.iranlms.ir/", "https://getdcmess1.iranlms.ir/", "https://getdcmess2.iranlms.ir/"], + "encrypt": False, + "params": { + "api_version": {"types": ["int", "str"], "func": "to_string", "default": "4"} + } + }, + "SignIn": { + "tmp_session": True, + "params": { + "phone_code": {"types": "str"}, + "phone_number": {"types": "str", "func": "get_phone"}, + "phone_code_hash": {"types": "str"}, + "public_key": {"types": "str"}, + } + }, + "SendCode": { + "tmp_session": True, + "params": { + "phone_number": {"types": "str", "func": "get_phone"}, + "pass_key": {"types": ["str", "optional"], "default": None}, + "send_type": {"types": ["str", "optional"], "alloweds": ["SMS", "Internal"], "default": "SMS"} + } + }, + "RegisterDevice": { + "params": { + "uaer_agent": {"types": "str", "func": "get_browser", "unpack": True}, + "app_version": {"types": "str"}, + "lang_code": {"types": ["str", "optional"], "default": "fa"} + } + }, + "LoginDisableTwoStep": { + "tmp_session": True, + "params": { + "phone_number": {"types": "str", "func": "get_phone"}, + "email_code": {"types": ["str", "int"], "func": "to_string"}, + "forget_password_code_hash": {"types": "str"} + } + } + } +} \ No newline at end of file diff --git a/rubpy/gadgets/methods.py b/rubpy/gadgets/methods.py new file mode 100644 index 0000000..a7bcd97 --- /dev/null +++ b/rubpy/gadgets/methods.py @@ -0,0 +1,422 @@ +import re +import sys +import time +import random +import warnings +from .classino import Classino +from .grouping import grouping + + +class Functions: + system_versions = { + 'Windows NT 10.0': 'Windows 10', + 'Windows NT 6.2': 'Windows 8', + 'Windows NT 6.1': 'Windows 7', + 'Windows NT 6.0': 'Windows Vista', + 'Windows NT 5.1': 'windows XP', + 'Windows NT 5.0': 'Windows 2000', + 'Mac': 'Mac/iOS', + 'X11': 'UNIX', + 'Linux': 'Linux' + } + + @classmethod + def get_phone(cls, value, *args, **kwargs): + phone_number = ''.join(re.findall(r'\d+', value)) + if not phone_number.startswith('98'): + phone_number = '98' + phone_number + return phone_number + + @classmethod + def get_browser(cls, user_agent, lang_code, app_version, *args, **kwargs): + device_model = re.search(r'(opera|chrome|safari|firefox|msie' + r'|trident)\/(\d+)', user_agent.lower()) + if not device_model: + device_model = 'Unknown' + warnings.warn(f'can not parse user-agent ({user_agent})') + + else: + device_model = device_model.group(1) + ' ' + device_model.group(2) + + system_version = 'Unknown' + for key, value in cls.system_versions.items(): + if key in user_agent: + system_version = value + break + + # window.navigator.mimeTypes.length (outdated . Defaults to '2') + device_hash = '2' + return { + 'token': '', + 'lang_code': lang_code, + 'token_type': 'Web', + 'app_version': f'WB_{app_version}', + 'system_version': system_version, + 'device_model': device_model.title(), + 'device_hash': device_hash + ''.join(re.findall(r'\d+', user_agent))} + + @classmethod + def random_number(cls, *args, **kwargs): + return int(random.random() * 1e6 + 1) + + @classmethod + def timestamp(cls, *args, **kwargs): + return int(time.time()) + + @classmethod + def get_format(cls, value, *args, **kwargs): + return value.split('.')[-1] + + @classmethod + def get_hash_link(cls, value, *args, **kwargs): + return value.split('/')[-1] + + @classmethod + def to_float(cls, value, *args, **kwargs): + return float(value) + + @classmethod + def to_number(cls, value, *args, **kwargs): + return int(value) + + @classmethod + def to_string(cls, value, *args, **kwargs): + return str(value) + + @classmethod + def to_array(cls, value, *args, **kwargs): + if isinstance(value, list): + return value + + elif isinstance(value, str): + return [value] + + try: + return value.to_dict() + + except AttributeError: + try: + return dict(value) + + except Exception: + return value + + @classmethod + def to_metadata(cls, value, *args, **kwargs): + pattern = r'`(.*?)`|\*\*(.*?)\*\*|__(.*?)__|~~(.*?)~~|--(.*?)--|\|\|(.*?)\|\||\[(.*?)\]\((\S+)\)' + conflict = 0 + meta_data_parts = [] + for markdown in re.finditer(pattern, value): + span = markdown.span() + if markdown.group(0).startswith('`'): + value = re.sub(pattern, r'\1', value, count=1) + meta_data_parts.append( + { + 'type': 'Mono', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 2 + } + ) + conflict += 2 + + elif markdown.group(0).startswith('**'): + value = re.sub(pattern, r'\2', value, count=1) + meta_data_parts.append( + { + 'type': 'Bold', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('__'): + value = re.sub(pattern, r'\3', value, count=1) + meta_data_parts.append( + { + 'type': 'Italic', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('~~'): + value = re.sub(pattern, r'\4', value, count=1) + meta_data_parts.append( + { + 'type': 'Strike', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('--'): + value = re.sub(pattern, r'\5', value, count=1) + meta_data_parts.append( + { + 'type': 'Underline', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('||'): + value = re.sub(pattern, r'\6', value, count=1) + meta_data_parts.append( + { + 'type': 'Spoiler', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + else: + value = re.sub(pattern, r'\7', value, count=1) + mention_text_object_guid = markdown.group(7) + mention_type = 'MentionText' + + if mention_text_object_guid.startswith('g'): + mention_text_object_type = 'Group' + + elif mention_text_object_guid.startswith('c'): + mention_text_object_type = 'Channel' + + elif mention_text_object_guid.startswith('u'): + mention_text_object_type = 'User' + + else: + mention_text_object_type = 'hyperlink' + mention_type = 'Link' + + if mention_type == 'MentionText': + meta_data_parts.append({ + 'type': 'MentionText', + 'from_index': span[0] - conflict, + 'length': len(markdown.group(7)), + 'mention_text_object_guid': mention_text_object_guid, + 'mention_text_object_type': mention_text_object_type + }) + conflict += 4 + len(mention_text_object_guid) + + else: + meta_data_parts.append({ + 'from_index': span[0] - conflict, + 'length': len(markdown.group(7)), + 'link': { + 'hyperlink_data': { + 'url': mention_text_object_guid + }, + 'type': mention_text_object_type, + }, + 'type': mention_type, + }) + conflict += 4 + len(mention_text_object_guid) + + result = {'text': value.strip()} + if meta_data_parts: + result['metadata'] = { + 'meta_data_parts': meta_data_parts + } + + return result + + +class BaseMethod: + __name__ = 'CustomMethod' + + def __init__(self, method: dict, *args, **kwargs): + self.method = method + + def __str__(self): + result = f'{self.method_name}(*, *args, **kwargs)' + + if self.method_param: + result += '\nArgs:\n' + for name, param in self.method_param.items(): + types = param.get('types') + default = param.get('default') + alloweds = param.get('alloweds') + heirship = param.get('heirship') + + if not isinstance(types, list): + types = [types] + + types = ', '.join(types) + result += f'\t{name} ({types})\n' + if alloweds is not None: + result += '\t\tthe allowed values are: ' + result += str([alloweds]) + + if default is not None: + result += f'\n\t\tthe default value is {default}' + + if heirship is not None: + result += ('\n\t\tif it is not set, it takes the' + f' value from the ({[alloweds]}) argument\'s') + result += '\n' + return result + + @property + def method_name(self): + return self.__name__[0].lower() + self.__name__[1:] + + @property + def method_param(self): + if self.method: + if isinstance(self.method['params'], dict): + return self.method['params'] + + def build(self, argument, param, *args, **kwargs): + ifs = param.get('ifs') + func = param.get('func') + types = param.get('types') + alloweds = param.get('alloweds') + + # set defualt value + try: + value = self.request[argument] + + except KeyError: + value = param['default'] + if isinstance(value, dict): + default_func = value.get('func') + if isinstance(default_func, str): + value = getattr(Functions, default_func)( + **value, **self.request) + + # get value heirship + for heirship in param.get('heirship', []): + try: + value = self.request[heirship] + except KeyError: + pass + + # clall func method + if isinstance(func, str) and value is not None: + value = getattr(Functions, func)(value, **self.request) + argument = param.get('cname', argument) + + # check value types + if types and not type(value).__name__ in types: + if value is not None and 'optional' in types: + raise TypeError( + f'The given {argument} must be' + f' {types} not {type(value).__name__}') + + if alloweds is not None: + if isinstance(value, list): + for _value in value: + if _value not in alloweds: + raise ValueError( + f'the {argument}({_value}) value is' + f' not in the allowed list {alloweds}') + + elif value not in alloweds: + raise ValueError( + f'the {argument}({value}) value is' + f' not in the allowed list {alloweds}') + + # get ifs + if isinstance(ifs, dict): + + # move to the last key + if 'otherwise' in ifs: + ifs['otherwise'] = ifs.pop('otherwise') + + for operator, work in ifs.items(): + if type(value).__name__ == operator or operator == 'otherwise': + func = work.get('func') + param = work + if isinstance(func, str): + value = getattr(Functions, func)(value, **self.request) + break + + # to avoid adding an extra value if there is "cname" + if argument in self.request: + self.request.pop(argument) + + if value is not None: + if param.get('unpack'): + self.request.update(value) + + else: + self.request[param.get('cname', argument)] = value + + def __call__(self, *args, **kwargs): + if self.method_param: + self.request = {} + params = list(self.method['params'].keys()) + for index, value in enumerate(args): + try: + self.request[params[index]] = value + + except IndexError: + pass + + for argument, value in kwargs.items(): + if self.method['params'].get(argument): + self.request[argument] = value + + for argument, param in self.method['params'].items(): + try: + self.build(argument, param) + except KeyError: + if 'optional' not in param['types']: + raise TypeError( + f'{self.__name__}() ' + f'required argument ({argument})') + + if self.method.get('urls') is not None: + self.request['method'] = self.method_name + + else: + self.request = { + 'method': self.method_name, 'input': self.request} + + self.request['urls'] = self.method.get('urls') + self.request['encrypt'] = self.method.get('encrypt', True) + self.request['tmp_session'] = bool(self.method.get('tmp_session')) + + return self.request + else: + return { + 'urls': None, + 'input': {}, + 'method': self.method_name, + 'encrypt': True, + 'tmp_session': False} + + +class BaseGrouping(Classino): + def __init__(self, methods: dict, *args, **kwargs): + self.methods = methods + + def __dir__(self): + methods = list(self.methods.keys()) + methods.remove('Values') + return self.methods['Values'] + methods + + def __getattr__(self, name) -> BaseMethod: + if name in self.methods['Values']: + return name + + method = self.create(name, (BaseMethod,), dir(self)) + return method(self.methods[method.__name__]) + + +class Methods(Classino): + def __init__(self, name, *args, **kwargs): + self.__name__ = name + + def __dir__(self): + return grouping.keys() + + def __getattr__(self, name) -> BaseGrouping: + group = self.create(name, (BaseGrouping,), dir(self)) + return group(methods=grouping[group.__name__]) + +sys.modules[__name__] = Methods(__name__) \ No newline at end of file diff --git a/rubpy/gadgets/models/__init__.py b/rubpy/gadgets/models/__init__.py new file mode 100644 index 0000000..4991480 --- /dev/null +++ b/rubpy/gadgets/models/__init__.py @@ -0,0 +1 @@ +from . import users, chats, extras, groups, messages, stickers, contacts \ No newline at end of file diff --git a/rubpy/gadgets/models/chats.py b/rubpy/gadgets/models/chats.py new file mode 100644 index 0000000..72d0617 --- /dev/null +++ b/rubpy/gadgets/models/chats.py @@ -0,0 +1,134 @@ +from pydantic import BaseModel +from typing import Optional + +class AvatarThumbnail(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + +class OnlineTime(BaseModel): + type: Optional[str] = None + exact_time: Optional[int] = None + +class LastMessage(BaseModel): + message_id: Optional[str] = None + type: Optional[str] = None + text: Optional[str] = None + author_object_guid: Optional[str] = None + is_mine: Optional[bool] = None + author_title: Optional[str] = None + author_type: Optional[str] = None + +class ChatInfo(BaseModel): + object_guid: Optional[str] = None + access: Optional[list[str]] = [] + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + +class UserInfo(BaseModel): + user_guid: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + phone: Optional[str] = None + username: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + last_online: Optional[int] = None + bio: Optional[str] = None + is_deleted: Optional[bool] = None + is_verified: Optional[bool] = None + online_time: Optional[OnlineTime] = None + +class AvatarData(BaseModel): + avatar_id: Optional[str] = None + thumbnail: Optional[AvatarThumbnail] = None + main: Optional[AvatarThumbnail] = None + create_time: Optional[int] = None + +class GetAvatars(BaseModel): + avatars: Optional[list[AvatarData]] = [] + _client: Optional[str] = None + original_update: Optional[str] = None + +class ChatAccess(BaseModel): + access: Optional[list[str]] = [] + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + pinned_message_id: Optional[str] = None + abs_object: Optional[dict] = None + is_blocked: Optional[bool] = None + last_message_id: Optional[str] = None + last_deleted_mid: Optional[str] = None + slow_mode_duration: Optional[int] = None + group_my_last_send_time: Optional[int] = None + pinned_message_ids: Optional[list[str]] = [] + +class ChatUpdate(BaseModel): + object_guid: Optional[str] = None + action: Optional[str] = None + chat: Optional[ChatAccess] = None + updated_parameters: Optional[list[str]] = [] + timestamp: Optional[str] = None + type: Optional[str] = None + +class DeleteAvatar(BaseModel): + user: Optional[UserInfo] = None + chat_update: Optional[ChatUpdate] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetChats(BaseModel): + chats: Optional[list[ChatInfo]] = [] + next_start_id: Optional[str] = None + state: Optional[int] = None + has_continue: Optional[bool] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class SeenChats(BaseModel): + chat_updates: Optional[list[ChatUpdate]] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class SetActionChat(BaseModel): + chat_update: Optional[ChatUpdate] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetChatsUpdates(BaseModel): + chats: list[ChatInfo] + new_state: Optional[int] = None + status: Optional[str] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class SendChatActivity(BaseModel): + _client: Optional[str] = None + original_update: Optional[str] = None + +class DeleteChatHistory(BaseModel): + chat_update: Optional[ChatUpdate] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class SearchChatMessages(BaseModel): + message_ids: list[str] + _client: Optional[str] = None + original_update: Optional[str] = None diff --git a/rubpy/gadgets/models/contacts.py b/rubpy/gadgets/models/contacts.py new file mode 100644 index 0000000..fc281c0 --- /dev/null +++ b/rubpy/gadgets/models/contacts.py @@ -0,0 +1,55 @@ +from pydantic import BaseModel +from typing import Optional, List + +class OnlineTime(BaseModel): + type: Optional[str] = None + approximate_period: Optional[str] = None + +class AvatarThumbnail(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + file_name: Optional[str] = None + cdn_tag: Optional[str] = None + +class WarningInfo(BaseModel): + warning_id: Optional[str] = None + title: Optional[str] = None + text: Optional[str] = None + link: Optional[dict] = None + title_color: Optional[dict] = None + +class User(BaseModel): + user_guid: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + phone: Optional[str] = None + username: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + last_online: Optional[int] = None + bio: Optional[str] = None + is_deleted: Optional[bool] = None + is_verified: Optional[bool] = None + online_time: Optional[OnlineTime] = None + warning_info: Optional[WarningInfo] = None + +class DeleteContact(BaseModel): + user: Optional[User] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class AddAddressBook(BaseModel): + user: Optional[User] = None + user_exist: Optional[bool] = None + timestamp: Optional[str] = None + can_receive_call: Optional[bool] = None + can_video_call: Optional[bool] = None + +class GetContacts(BaseModel): + users: Optional[List[User]] = [] + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + state: Optional[int] = None + timestamp: Optional[str] = None \ No newline at end of file diff --git a/rubpy/gadgets/models/extras.py b/rubpy/gadgets/models/extras.py new file mode 100644 index 0000000..5cce602 --- /dev/null +++ b/rubpy/gadgets/models/extras.py @@ -0,0 +1,238 @@ +from pydantic import BaseModel, HttpUrl, Field, PositiveInt +from typing import Optional, List + +class AvatarThumbnail(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + +class Object(BaseModel): + object_guid: Optional[str] = None + type: Optional[str] = None + title: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + count_members: Optional[int] = None + username: Optional[str] = None + +class SearchGlobalObjects(BaseModel): + objects: list[Object] + has_continue: Optional[bool] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class OnlineTime(BaseModel): + type: Optional[str] = None + approximate_period: Optional[str] = None + +class AvatarThumbnail(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + +class LastMessage(BaseModel): + message_id: Optional[str] = None + type: Optional[str] = None + text: Optional[str] = None + author_object_guid: Optional[str] = None + is_mine: Optional[bool] = None + author_type: Optional[str] = None + +class AbsObject(BaseModel): + object_guid: Optional[str] = None + type: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + +class Chat(BaseModel): + object_guid: Optional[str] = None + access: Optional[list[str]] = [] + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + abs_object: Optional[AbsObject] = None + is_blocked: Optional[bool] = None + last_message_id: Optional[str] = None + last_deleted_mid: Optional[str] = None + is_in_contact: Optional[bool] = None + +class ChatReactionSetting(BaseModel): + reaction_type: Optional[str] = Field(None, description="Type of reactions in chat") + selected_reactions: Optional[List[str]] = Field(None, description="List of selected reactions in the chat") + +class User(BaseModel): + user_guid: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + phone: Optional[str] = None + username: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + last_online: Optional[int] = None + bio: Optional[str] = None + is_deleted: Optional[bool] = None + is_verified: Optional[bool] = None + online_time: Optional[OnlineTime] = None + +class Channel(BaseModel): + channel_guid: Optional[str] = Field(None, description="Channel ID") + channel_title: Optional[str] = Field(None, description="Channel title") + avatar_thumbnail: Optional[AvatarThumbnail] = Field(None, description="Profile image information") + count_members: Optional[PositiveInt] = Field(None, description="Number of channel members") + description: Optional[str] = Field(None, description="Channel description") + username: Optional[str] = Field(None, description="Channel username") + is_deleted: Optional[bool] = Field(None, description="Channel deletion status") + is_verified: Optional[bool] = Field(None, description="Channel verification status") + share_url: Optional[HttpUrl] = Field(None, description="Channel sharing URL") + channel_type: Optional[str] = Field(None, description="Channel type") + sign_messages: Optional[bool] = Field(None, description="Message signing capability status") + chat_reaction_setting: Optional[ChatReactionSetting] = Field(None, description="Conversation reaction settings") + +class GetObjectByUsername(BaseModel): + exist: Optional[bool] = None + type: Optional[str] = None + user: Optional[User] = None + channel: Optional[Channel] = None + chat: Optional[Chat] = None + timestamp: Optional[str] = None + is_in_contact: Optional[bool] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class OpenChatData(BaseModel): + object_guid: Optional[str] = None + object_type: Optional[str] = None + message_id: Optional[int] = None + +class LinkData(BaseModel): + type: Optional[str] = None + link_url: Optional[str] = None + open_chat_data: Optional[OpenChatData] = None + +class GetLinkFromAppUrl(BaseModel): + link: Optional[LinkData] = None + +class ChatGuidType(BaseModel): + type: Optional[str] = None + object_guid: Optional[str] = None + +class MessageInfo(BaseModel): + message_id: Optional[str] = None + type: Optional[str] = None + text: Optional[str] = None + is_mine: Optional[bool] = None + +class ChatInfo(BaseModel): + time_string: Optional[str] = None + last_message: Optional[MessageInfo] = None + time: Optional[int] = None + last_message_id: Optional[str] = None + group_voice_chat_id: Optional[str] = None + +class ChatUpdate(BaseModel): + object_guid: Optional[str] = None + action: Optional[str] = None + chat: Optional[ChatInfo] = None + updated_parameters: Optional[list[str]] = None + timestamp: Optional[str] = None + type: Optional[str] = None + +class PerformerData(BaseModel): + type: Optional[str] = None + object_guid: Optional[str] = None + +class EventData(BaseModel): + type: Optional[str] = None + performer_object: Optional[PerformerData] = None + +class MessageData(BaseModel): + message_id: Optional[str] = None + text: Optional[str] = None + time: Optional[str] = None + is_edited: Optional[bool] = None + type: Optional[str] = None + event_data: Optional[EventData] = None + +class MessageUpdate(BaseModel): + message_id: Optional[str] = None + action: Optional[str] = None + message: Optional[MessageData] = None + updated_parameters: Optional[list[str]] = None + timestamp: Optional[str] = None + prev_message_id: Optional[str] = None + object_guid: Optional[str] = None + type: Optional[str] = None + state: Optional[str] = None + +class GroupVoiceChatData(BaseModel): + voice_chat_id: Optional[str] = None + state: Optional[str] = None + join_muted: Optional[bool] = None + participant_count: Optional[int] = None + title: Optional[str] = None + version: Optional[int] = None + +class ChannelVoiceChatData(BaseModel): + voice_chat_id: Optional[str] = None + state: Optional[str] = None + join_muted: Optional[bool] = None + participant_count: Optional[int] = None + title: Optional[str] = None + version: Optional[int] = None + +class GroupVoiceChatUpdate(BaseModel): + voice_chat_id: Optional[str] = None + group_guid: Optional[str] = None + action: Optional[str] = None + group_voice_chat: Optional[GroupVoiceChatData] = None + updated_parameters: Optional[list[str]] = None + timestamp: Optional[str] = None + chat_guid_type: Optional[ChatGuidType] = None + +class ChannelVoiceChatUpdate(BaseModel): + voice_chat_id: Optional[str] = None + channel_guid: Optional[str] = None + action: Optional[str] = None + channel_voice_chat: Optional[ChannelVoiceChatData] = None + updated_parameters: Optional[list[str]] = None + timestamp: Optional[str] = None + chat_guid_type: Optional[ChatGuidType] = None + +class ExistGroupVoiceChat(BaseModel): + voice_chat_id: Optional[str] = None + state: Optional[str] = None + join_muted: Optional[bool] = None + participant_count: Optional[int] = None + title: Optional[str] = None + version: Optional[int] = None + +class ExistChannelVoiceChat(BaseModel): + voice_chat_id: Optional[str] = None + state: Optional[str] = None + join_muted: Optional[bool] = None + participant_count: Optional[int] = None + title: Optional[str] = None + version: Optional[int] = None + +class CreateVoiceCall(BaseModel): + status: Optional[str] = None + chat_update: Optional[ChatUpdate] = None + message_update: Optional[MessageUpdate] = None + group_voice_chat_update: Optional[GroupVoiceChatUpdate] = None + channel_voice_chat_update: Optional[ChannelVoiceChatUpdate] = None + exist_group_voice_chat: Optional[ExistGroupVoiceChat] = None + exist_channel_voice_chat: Optional[ExistChannelVoiceChat] = None + _client: Optional[str] = None + original_update: Optional[str] = None diff --git a/rubpy/gadgets/models/groups.py b/rubpy/gadgets/models/groups.py new file mode 100644 index 0000000..f16a26e --- /dev/null +++ b/rubpy/gadgets/models/groups.py @@ -0,0 +1,267 @@ +from pydantic import BaseModel +from typing import Optional, List, Dict, Union + +class LastMessage(BaseModel): + message_id: Optional[str] = None + type: Optional[str] = None + text: Optional[str] = None + author_object_guid: Optional[str] = None + is_mine: Optional[bool] = None + author_title: Optional[str] = None + author_type: Optional[str] = None + +class ChatAccess(BaseModel): + access: Optional[List[str]] = [] + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + pinned_message_id: Optional[str] = None + abs_object: Optional[Dict] = None + is_blocked: Optional[bool] = None + last_message_id: Optional[str] = None + last_deleted_mid: Optional[str] = None + slow_mode_duration: Optional[int] = None + group_my_last_send_time: Optional[int] = None + pinned_message_ids: Optional[List[str]] = [] + +class ChatUpdate(BaseModel): + object_guid: Optional[str] = None + action: Optional[str] = None + chat: Optional[ChatAccess] = None + updated_parameters: Optional[List[str]] = None + timestamp: Optional[str] = None + type: Optional[str] = None + +class LeaveGroup(BaseModel): + chat_update: Optional[ChatUpdate] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class Group(BaseModel): + group_guid: Optional[str] = None + group_title: Optional[str] = None + count_members: Optional[int] = None + is_deleted: Optional[bool] = None + is_verified: Optional[bool] = None + slow_mode: Optional[int] = None + chat_history_for_new_members: Optional[str] = None + event_messages: Optional[bool] = None + chat_reaction_setting: Optional[Union[str, Dict]] = None + +class Chat(BaseModel): + object_guid: Optional[str] = None + action: Optional[str] = None + access: Optional[List[str]] = None + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[Dict] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + abs_object: Optional[Dict] = None + is_blocked: Optional[bool] = None + last_message_id: Optional[str] = None + last_deleted_mid: Optional[str] = None + slow_mode_duration: Optional[int] = None + +class MessageUpdate(BaseModel): + message_id: Optional[str] = None + action: Optional[str] = None + message: Optional[Dict] = None + updated_parameters: Optional[List[str]] = None + timestamp: Optional[str] = None + prev_message_id: Optional[str] = None + object_guid: Optional[str] = None + type: Optional[str] = None + state: Optional[str] = None + +class JoinGroup(BaseModel): + group: Optional[Group] = None + is_valid: Optional[bool] = None + chat_update: Optional[Chat] = None + message_update: Optional[MessageUpdate] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class AddGroup(BaseModel): + group: Optional[Group] = None + chat_update: Optional[Chat] = None + message_update: Optional[MessageUpdate] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class RemoveGroup(BaseModel): + chat_update: Optional[Chat] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetGroupInfo(BaseModel): + group: Optional[Group] = None + chat: Optional[Chat] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + message_update: Optional[MessageUpdate] = None + +class GroupLink(BaseModel): + join_link: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class EditGroupInfo(BaseModel): + group: Optional[Group] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class OnlineTime(BaseModel): + type: Optional[str] = None + approximate_period: Optional[str] = None + +class InChatMemberList(BaseModel): + member_type: Optional[str] = None + member_guid: Optional[str] = None + first_name: Optional[str] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + promoted_by_object_guid: Optional[str] = None + promoted_by_object_type: Optional[str] = None + join_type: Optional[str] = None + online_time: Optional[OnlineTime] = None + +class InChatMember(BaseModel): + in_chat_member: Optional[InChatMemberList] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class BanGroupMember(BaseModel): + group: Optional[Group] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[Dict] = None + +class AvatarThumbnail(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + +class AddedInChatMember(BaseModel): + member_type: Optional[str] = None + member_guid: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + last_online: Optional[int] = None + join_type: Optional[str] = None + username: Optional[str] = None + online_time: Optional[OnlineTime] = None + +class AddGroupMembers(BaseModel): + chat_update: Optional[ChatUpdate] = None + message_update: Optional[MessageUpdate] = None + added_in_chat_members: Optional[List[AddedInChatMember]] = None + timestamp: Optional[str] = None + group: Optional[Group] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class InChatGroupMember(BaseModel): + member_type: Optional[str] = None + member_guid: Optional[str] = None + first_name: Optional[str] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + last_online: Optional[int] = None + join_type: Optional[str] = None + online_time: Optional[OnlineTime] = None + promoted_by_object_guid: Optional[str] = None + promoted_by_object_type: Optional[str] = None + removed_by_object_guid: Optional[str] = None + removed_by_object_type: Optional[str] = None + +class GetAllGroupMembers(BaseModel): + in_chat_members: Optional[List[InChatGroupMember]] = None + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetGroupAdminMembers(BaseModel): + in_chat_members: Optional[List[InChatGroupMember]] = None + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetGroupMentionList(BaseModel): + in_chat_members: Optional[List[InChatGroupMember]] = None + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetGroupDefaultAccess(BaseModel): + access_list: Optional[List] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class OnlineTime(BaseModel): + type: Optional[str] = None + approximate_period: Optional[str] = None + +class TopParticipants(BaseModel): + member_type: Optional[str] = None + member_guid: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + username: Optional[str] = None + online_time: Optional[OnlineTime] = None + +class GroupPreviewByJoinLink(BaseModel): + is_valid: Optional[bool] = None + group: Optional[Group] = None + has_joined: Optional[bool] = None + top_participants: Optional[List[TopParticipants]] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class DeleteNoAccessGroupChat(BaseModel): + chat_update: Optional[ChatUpdate] = None + status: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetGroupAdminAccessList(BaseModel): + access_list: Optional[List] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetBannedGroupMembers(BaseModel): + in_chat_members: Optional[List[InChatGroupMember]] = [] + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None \ No newline at end of file diff --git a/rubpy/gadgets/models/messages.py b/rubpy/gadgets/models/messages.py new file mode 100644 index 0000000..2aa0d77 --- /dev/null +++ b/rubpy/gadgets/models/messages.py @@ -0,0 +1,827 @@ +from .. import methods +from .. import thumbnail +from base64 import b64decode +from pydantic import BaseModel +from typing import Optional, List + +class FileInline(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + file_name: Optional[str] = None + thumb_inline: Optional[str] = None + music_performer: Optional[str] = None + width: Optional[int] = None + height: Optional[int] = None + time: Optional[int] = None + size: Optional[int] = None + type: Optional[str] = None + +class ForwardedFrom(BaseModel): + type_from: Optional[str] = None + message_id: Optional[str] = None + object_guid: Optional[str] = None + +class PollOption(BaseModel): + options: Optional[List[str]] = [] + poll_status: Optional[dict] = None + is_anonymous: Optional[bool] = None + type: Optional[str] = None + allows_multiple_answers: Optional[bool] = None + +class Poll(BaseModel): + poll_id: Optional[str] = None + question: Optional[str] = None + options: Optional[List[str]] = None + poll_status: Optional[dict] = None + is_anonymous: Optional[bool] = None + type: Optional[str] = None + allows_multiple_answers: Optional[bool] = None + +class AvatarThumbnail(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + +class MessageData(BaseModel): + message_id: Optional[str] = None + text: Optional[str] = None + file_inline: Optional[FileInline] = None + time: Optional[str] = None + is_edited: Optional[bool] = None + type: Optional[str] = None + author_type: Optional[str] = None + author_object_guid: Optional[str] = None + forwarded_from: Optional[ForwardedFrom] = None + poll: Optional[Poll] = None + +class AbsObject(BaseModel): + object_guid: Optional[str] = None + type: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + avatar_thumbnail: Optional[AvatarThumbnail] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + +class Message(BaseModel): + message_id: Optional[str] = None + text: Optional[str] = None + file_inline: Optional[FileInline] = None + time: Optional[str] = None + is_edited: Optional[bool] = None + type: Optional[str] = None + author_type: Optional[str] = None + author_object_guid: Optional[str] = None + forwarded_from: Optional[ForwardedFrom] = None + poll: Optional[Poll] = None + object_guid: Optional[str] = None + message: Optional[MessageData] = None + abs_object: Optional[AbsObject] = None + last_seen_peer_mid: Optional[int] = None + +class MessageUpdate(BaseModel): + message_id: Optional[str] = None + action: Optional[str] = None + message: Optional[Message] = None + updated_parameters: Optional[list] = None + timestamp: Optional[str] = None + prev_message_id: Optional[str] = None + object_guid: Optional[str] = None + type: Optional[str] = None + state: Optional[str] = None + +class LastMessage(BaseModel): + message_id: Optional[str] = None + type: Optional[str] = None + text: Optional[str] = None + author_object_guid: Optional[str] = None + is_mine: Optional[bool] = None + author_title: Optional[str] = None + author_type: Optional[str] = None + +class Chat(BaseModel): + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[int] = None + status: Optional[str] = None + time: Optional[int] = None + last_message_id: Optional[str] = None + +class ChatUpdate(BaseModel): + object_guid: Optional[str] = None + action: Optional[str] = None + chat: Optional[Chat] = None + updated_parameters: Optional[list] = None + timestamp: Optional[str] = None + type: Optional[str] = None + +class SendMessage(BaseModel): + message_update: Optional[MessageUpdate] = None + status: Optional[str] = None + chat_update: Optional[ChatUpdate] = None + + @classmethod + async def set_shared_data(cls, client, message): + cls._client = client + cls._message = message + + @classmethod + async def pin(cls, + object_guid: str = None, + message_id: str = None, + action: str = methods.messages.Pin, *args, **kwargs): + """_pin_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + action (bool, optional): + _pin or unpin_. Defaults to methods.messages.Pin. ( + methods.messages.Pin, + methods.messages.Unpin + ) + Returns: + BaseResults: result + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_id is None: + message_id = cls._message.message_update.message_id + + return await cls._client( + methods.messages.SetPinMessage( + object_guid=object_guid, + message_id=message_id, + action=action)) + + @classmethod + async def edit(cls, + text: str, + object_guid: str = None, + message_id: str = None, *args, **kwargs): + """_edit_ + Args: + text (str): + _message text_ + object_guid (str, optional): + _custom objec guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_id is None: + message_id = cls._message.message_update.message_id + + return await cls._client( + methods.messages.EditMessage( + object_guid=object_guid, + message_id=message_id, + text=text)) + + @classmethod + async def copy(cls, + to_object_guid: str, + from_object_guid: str = None, message_ids=None, *args, **kwargs): + """_copy_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + result = await cls.get_messages(from_object_guid, message_ids) + messages = [] + if result.messages: + for message in result.messages: + + try: + file_inline = message.file_inline + kwargs.update(file_inline.to_dict()) + + except AttributeError: + file_inline = None + + try: + thumb = thumbnail.Thumbnail( + b64decode(message.thumb_inline), *args, **kwargs) + + except AttributeError: + thumb = kwargs.get('thumb', True) + + try: + message = message.sticker + + except AttributeError: + message = message.raw_text + + if file_inline is not None: + if file_inline.type not in [methods.messages.Gif, + methods.messages.Sticker]: + file_inline = await cls.download(file_inline) + messages.append(await cls._client.send_message( + thumb=thumb, + message=message, + file_inline=file_inline, + object_guid=to_object_guid, *args, **kwargs)) + continue + + messages.append(await cls._client.send_message( + message=message, + object_guid=to_object_guid, + file_inline=file_inline, *args, **kwargs)) + + return {'status': 'OK', 'messages': messages} + + @classmethod + async def seen(cls, seen_list: dict = None, *args, **kwargs): + """_seen_ + Args: + seen_list (dict, optional): + _description_. Defaults t + {update.object_guid: update.message_id} + """ + + if seen_list is None: + seen_list = {cls._message.message_update.object_guid: cls._message.message_update.message_id} + return await cls._client(methods.chats.SeenChats(seen_list=seen_list)) + + @classmethod + async def reply(cls, + message=None, + object_guid: str = None, + reply_to_message_id: str = None, + file_inline: str = None, *args, **kwargs): + """_reply_ + Args: + message (Any, optional): + _message or cation or sticker_ . Defaults to None. + object_guid (str): + _object guid_ . Defaults to update.object_guid + reply_to_message_id (str, optional): + _reply to message id_. Defaults to None. + file_inline (typing.Union[pathlib.Path, bytes], optional): + _file_. Defaults to None. + is_gif (bool, optional): + _is it a gif file or not_. Defaults to None. + is_image (bool, optional): + _is it a image file or not_. Defaults to None. + is_voice (bool, optional): + _is it a voice file or not_. Defaults to None. + is_music (bool, optional): + _is it a music file or not_. Defaults to None. + is_video (bool, optional): + _is it a video file or not_. Defaults to None. + thumb (bool, optional): + if value is "True", + the lib will try to build the thumb ( require cv2 ) + if value is thumbnail.Thumbnail, to set custom + Defaults to True. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_id is None: + message_id = cls._message.message_update.message_id + + return await cls._client.send_message( + message=message, + object_guid=object_guid, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id, *args, **kwargs) + + @classmethod + async def forwards(cls, + to_object_guid: str, + from_object_guid: str = None, + message_ids=None, *args, **kwargs): + """_forwards_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + return await cls._client( + methods.messages.ForwardMessages( + from_object_guid=from_object_guid, + to_object_guid=to_object_guid, + message_ids=message_ids)) + + @classmethod + async def download(cls, file_inline=None, file=None, *args, **kwargs): + return await cls._client.download_file_inline( + file_inline or cls._message.file_inline, + file=file, *args, **kwargs) + + @classmethod + async def get_author(cls, author_guid: str = None, *args, **kwargs): + """_get user or author information_ + Args: + author_guid (str, optional): + _custom author guid_. Defaults to update.author_guid + """ + + if author_guid is None: + author_guid = cls._message.message_update.message.author_object_guid + + return await cls.get_object(object_guid=author_guid, *args, **kwargs) + + @classmethod + async def get_object(cls, object_guid: str = None, *args, **kwargs): + """_get object information_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + + if cls.guid_type(object_guid) == 'User': + return await cls._client( + methods.users.GetUserInfo( + user_guid=object_guid)) + + elif cls.guid_type(object_guid) == 'Group': + return await cls._client( + methods.groups.GetGroupInfo( + object_guid=object_guid)) + + elif cls.guid_type(object_guid) == 'Channel': + return await cls._client( + methods.channels.GetChannelInfo( + object_guid=object_guid)) + + @classmethod + async def get_message(cls, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_get messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + return await cls._client( + methods.messages.GetMessagesByID( + object_guid=object_guid, message_ids=message_ids)) + + @classmethod + async def delete(cls, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_delete messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, list, optional): + _custom message ids_. Defaults to update.message_id. + type(str, optional): + the message should be deleted for everyone or not. + Defaults to methods.messages.Global ( + methods.messages.Local, + methods.messages.Global + ) + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + return await cls._client( + methods.messages.DeleteMessages( + object_guid=object_guid, + message_ids=message_ids, *args, **kwargs)) + +class EditMessage(BaseModel): + message_update: Optional[MessageUpdate] = None + chat_update: Optional[ChatUpdate] = None + + @classmethod + async def set_shared_data(cls, client, message): + cls._client = client + cls._message = message + + @classmethod + async def pin(cls, + object_guid: str = None, + message_id: str = None, + action: str = methods.messages.Pin, *args, **kwargs): + """_pin_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + action (bool, optional): + _pin or unpin_. Defaults to methods.messages.Pin. ( + methods.messages.Pin, + methods.messages.Unpin + ) + Returns: + BaseResults: result + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_id is None: + message_id = cls._message.message_update.message_id + + return await cls._client( + methods.messages.SetPinMessage( + object_guid=object_guid, + message_id=message_id, + action=action)) + + @classmethod + async def edit(cls, + text: str, + object_guid: str = None, + message_id: str = None, *args, **kwargs): + """_edit_ + Args: + text (str): + _message text_ + object_guid (str, optional): + _custom objec guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_id is None: + message_id = cls._message.message_update.message_id + + return await cls._client( + methods.messages.EditMessage( + object_guid=object_guid, + message_id=message_id, + text=text)) + + @classmethod + async def copy(cls, + to_object_guid: str, + from_object_guid: str = None, message_ids=None, *args, **kwargs): + """_copy_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + result = await cls.get_messages(from_object_guid, message_ids) + messages = [] + if result.messages: + for message in result.messages: + + try: + file_inline = message.file_inline + kwargs.update(file_inline.to_dict()) + + except AttributeError: + file_inline = None + + try: + thumb = thumbnail.Thumbnail( + b64decode(message.thumb_inline), *args, **kwargs) + + except AttributeError: + thumb = kwargs.get('thumb', True) + + try: + message = message.sticker + + except AttributeError: + message = message.raw_text + + if file_inline is not None: + if file_inline.type not in [methods.messages.Gif, + methods.messages.Sticker]: + file_inline = await cls.download(file_inline) + messages.append(await cls._client.send_message( + thumb=thumb, + message=message, + file_inline=file_inline, + object_guid=to_object_guid, *args, **kwargs)) + continue + + messages.append(await cls._client.send_message( + message=message, + object_guid=to_object_guid, + file_inline=file_inline, *args, **kwargs)) + + return {'status': 'OK', 'messages': messages} + + @classmethod + async def seen(cls, seen_list: dict = None, *args, **kwargs): + """_seen_ + Args: + seen_list (dict, optional): + _description_. Defaults t + {update.object_guid: update.message_id} + """ + + if seen_list is None: + seen_list = {cls._message.message_update.object_guid: cls._message.message_update.message_id} + return await cls._client(methods.chats.SeenChats(seen_list=seen_list)) + + @classmethod + async def reply(cls, + message=None, + object_guid: str = None, + reply_to_message_id: str = None, + file_inline: str = None, *args, **kwargs): + """_reply_ + Args: + message (Any, optional): + _message or cation or sticker_ . Defaults to None. + object_guid (str): + _object guid_ . Defaults to update.object_guid + reply_to_message_id (str, optional): + _reply to message id_. Defaults to None. + file_inline (typing.Union[pathlib.Path, bytes], optional): + _file_. Defaults to None. + is_gif (bool, optional): + _is it a gif file or not_. Defaults to None. + is_image (bool, optional): + _is it a image file or not_. Defaults to None. + is_voice (bool, optional): + _is it a voice file or not_. Defaults to None. + is_music (bool, optional): + _is it a music file or not_. Defaults to None. + is_video (bool, optional): + _is it a video file or not_. Defaults to None. + thumb (bool, optional): + if value is "True", + the lib will try to build the thumb ( require cv2 ) + if value is thumbnail.Thumbnail, to set custom + Defaults to True. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_id is None: + message_id = cls._message.message_update.message_id + + return await cls._client.send_message( + message=message, + object_guid=object_guid, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id, *args, **kwargs) + + @classmethod + async def forwards(cls, + to_object_guid: str, + from_object_guid: str = None, + message_ids=None, *args, **kwargs): + """_forwards_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + return await cls._client( + methods.messages.ForwardMessages( + from_object_guid=from_object_guid, + to_object_guid=to_object_guid, + message_ids=message_ids)) + + @classmethod + async def download(cls, file_inline=None, file=None, *args, **kwargs): + return await cls._client.download_file_inline( + file_inline or cls._message.file_inline, + file=file, *args, **kwargs) + + @classmethod + async def get_author(cls, author_guid: str = None, *args, **kwargs): + """_get user or author information_ + Args: + author_guid (str, optional): + _custom author guid_. Defaults to update.author_guid + """ + + if author_guid is None: + author_guid = cls._message.message_update.message.author_object_guid + + return await cls.get_object(object_guid=author_guid, *args, **kwargs) + + @classmethod + async def get_object(cls, object_guid: str = None, *args, **kwargs): + """_get object information_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + + if cls.guid_type(object_guid) == 'User': + return await cls._client( + methods.users.GetUserInfo( + user_guid=object_guid)) + + elif cls.guid_type(object_guid) == 'Group': + return await cls._client( + methods.groups.GetGroupInfo( + object_guid=object_guid)) + + elif cls.guid_type(object_guid) == 'Channel': + return await cls._client( + methods.channels.GetChannelInfo( + object_guid=object_guid)) + + @classmethod + async def get_message(cls, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_get messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + return await cls._client( + methods.messages.GetMessagesByID( + object_guid=object_guid, message_ids=message_ids)) + + @classmethod + async def delete(cls, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_delete messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, list, optional): + _custom message ids_. Defaults to update.message_id. + type(str, optional): + the message should be deleted for everyone or not. + Defaults to methods.messages.Global ( + methods.messages.Local, + methods.messages.Global + ) + """ + + if object_guid is None: + object_guid = cls._message.message_update.object_guid + + if message_ids is None: + message_ids = cls._message.message_update.message_id + + return await cls._client( + methods.messages.DeleteMessages( + object_guid=object_guid, + message_ids=message_ids, *args, **kwargs)) + +class DeleteMessages(BaseModel): + chat_update: Optional[ChatUpdate] = None + message_updates: Optional[List[MessageUpdate]] = [] + _client: Optional[str] = None + original_update: Optional[str] = None + +class RequestSendFile(BaseModel): + id: Optional[str] = None + dc_id: Optional[str] = None + access_hash_send: Optional[str] = None + upload_url: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class ForwardMessages(BaseModel): + chat_update: Optional[ChatUpdate] = None + message_updates: Optional[List[MessageUpdate]] = [] + status: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class PollStatus(BaseModel): + state: Optional[str] = None + selection_index: Optional[int] = None + percent_vote_options: Optional[list[int]] = None + total_vote: Optional[int] = None + show_total_votes: Optional[bool] = None + +class VotePoll(BaseModel): + poll_status: Optional[PollStatus] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class SetPinMessage(BaseModel): + chat_update: Optional[ChatUpdate] = None + status: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetMessagesUpdates(BaseModel): + updated_messages: Optional[List[MessageUpdate]] = [] + new_state: Optional[int] = None + status: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class SearchGlobalMessages(BaseModel): + messages: Optional[List[Message]] = [] + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetMessagesByID(BaseModel): + messages: Optional[List[Message]] = [] + timestamp: Optional[int] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class GetMessagesInterval(BaseModel): + messages: Optional[List[Message]] = [] + state: Optional[str] = None + new_has_continue: Optional[bool] = None + old_has_continue: Optional[bool] = None + new_min_id: Optional[int] = None + old_max_id: Optional[int] = None + timestamp: Optional[str] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class Reactions(BaseModel): + user_guids: Optional[List] = [] + reaction_count: Optional[int] = None + emoji_char: Optional[str] = None + reaction_id: Optional[int] = None + is_selected: Optional[bool] = None + +class Reacion(BaseModel): + reactions: Optional[List[Reactions]] = [] \ No newline at end of file diff --git a/rubpy/gadgets/models/stickers.py b/rubpy/gadgets/models/stickers.py new file mode 100644 index 0000000..ba78707 --- /dev/null +++ b/rubpy/gadgets/models/stickers.py @@ -0,0 +1,51 @@ +from pydantic import BaseModel, HttpUrl +from typing import List, Optional + +class File(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + file_name: Optional[str] = None + cdn_tag: Optional[str] = None + +class Sticker(BaseModel): + emoji_character: Optional[str] = None + w_h_ratio: Optional[str] = None + sticker_id: Optional[str] = None + file: Optional[File] = None + sticker_set_id: Optional[str] = None + +class SetImage(BaseModel): + file_id: Optional[str] = None + mime: Optional[str] = None + dc_id: Optional[str] = None + access_hash_rec: Optional[str] = None + cdn_tag: Optional[str] = None + +class TrendStickerSet(BaseModel): + sticker_set_id: Optional[str] = None + title: Optional[str] = None + count_stickers: Optional[int] = None + set_image: Optional[SetImage] = None + top_stickers: Optional[List[Sticker]] = None + share_string: Optional[HttpUrl] = None + update_time: Optional[int] = None + +class GetTrendStickerSets(BaseModel): + sticker_sets: Optional[List[TrendStickerSet]] = None + next_start_id: Optional[str] = None + has_continue: Optional[bool] = None + +class GetStickersBySetIDs(BaseModel): + stickers: Optional[List[Sticker]] = [] + +class StickerSets(BaseModel): + sticker_set_id: Optional[str] = None + title: Optional[str] = None + count_stickers: Optional[int] = None + set_image: Optional[SetImage] = None + top_stickers: Optional[List[Sticker]] = [] + +class GetMyStickerSets(BaseModel): + sticker_sets: Optional[List[StickerSets]] = [] \ No newline at end of file diff --git a/rubpy/gadgets/models/users.py b/rubpy/gadgets/models/users.py new file mode 100644 index 0000000..4d39c97 --- /dev/null +++ b/rubpy/gadgets/models/users.py @@ -0,0 +1,102 @@ +from pydantic import BaseModel +from typing import List, Optional + +class OnlineTime(BaseModel): + type: Optional[str] = None + exact_time: Optional[int] = None + +class LastMessage(BaseModel): + message_id: Optional[str] = None + type: Optional[str] = None + text: Optional[str] = None + author_object_guid: Optional[str] = None + is_mine: Optional[bool] = None + author_title: Optional[str] = None + author_type: Optional[str] = None + +class AbsObject(BaseModel): + object_guid: Optional[str] = None + type: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + is_verified: Optional[bool] = None + is_deleted: Optional[bool] = None + +class Chat(BaseModel): + object_guid: Optional[str] = None + access: Optional[List[str]] = None + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + abs_object: Optional[AbsObject] = None + is_blocked: Optional[bool] = None + last_message_id: Optional[str] = None + last_deleted_mid: Optional[str] = None + +class UserInfo(BaseModel): + user_guid: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + phone: Optional[str] = None + username: Optional[str] = None + last_online: Optional[int] = None + bio: Optional[str] = None + is_deleted: Optional[bool] = None + is_verified: Optional[bool] = None + online_time: Optional[OnlineTime] = None + +class GetUserInfo(BaseModel): + user: Optional[UserInfo] = None + chat: Optional[Chat] = None + timestamp: Optional[str] = None + can_receive_call: Optional[bool] = None + can_video_call: Optional[bool] = None + +class ChatAccess(BaseModel): + access: Optional[list[str]] = None + count_unseen: Optional[int] = None + is_mute: Optional[bool] = None + is_pinned: Optional[bool] = None + time_string: Optional[str] = None + last_message: Optional[LastMessage] = None + last_seen_my_mid: Optional[str] = None + last_seen_peer_mid: Optional[str] = None + status: Optional[str] = None + time: Optional[int] = None + pinned_message_id: Optional[str] = None + abs_object: Optional[AbsObject] = None + is_blocked: Optional[bool] = None + last_message_id: Optional[str] = None + last_deleted_mid: Optional[str] = None + slow_mode_duration: Optional[int] = None + group_my_last_send_time: Optional[int] = None + pinned_message_ids: Optional[list[str]] = None + +class ChatUpdate(BaseModel): + object_guid: Optional[str] = None + action: Optional[str] = None + chat: Optional[ChatAccess] = None + updated_parameters: Optional[list[str]] = None + timestamp: Optional[str] = None + type: Optional[str] = None + +class BlockUser(BaseModel): + chat_update: Optional[ChatUpdate] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class DeleteUserChat(BaseModel): + chat_update: Optional[ChatUpdate] = None + _client: Optional[str] = None + original_update: Optional[str] = None + +class CheckUserName(BaseModel): + exist: Optional[bool] = None + _client: Optional[str] = None + original_update: Optional[str] = None diff --git a/rubpy/gadgets/thumbnail.py b/rubpy/gadgets/thumbnail.py new file mode 100644 index 0000000..3d103a1 --- /dev/null +++ b/rubpy/gadgets/thumbnail.py @@ -0,0 +1,84 @@ +import io +import base64 +import tempfile + + +try: + import cv2 + import numpy + +except ImportError: + cv2 = None + numpy = None + +DEFAULT_IMAGE_THUMB = 'iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAMC0lEQVRoBd3Bf3Cb9X0H8PfnkfQ8kmwhKXYsK8Q2QU6MpCSkSWywu9sCSeNe7YL5oXJXH2itbzsI58SUba0pvd1wmceVHWY1Ky3ntYB7S3Fz52ZzSd0YwqAx0DkJBMWExJA4P5Q4QZYdY/3+flfkE5WQvo8SYu8Pv16ERYqwSBEWKcIiRVikCIsUYZEi/H+57wHTxU+kKK7Z+8IpLDzCwttzgJA0GrAFwiZ8ijoaP8BCIiwYSaI9IwbGQ0gZDdgCYRP+TPnhbT7OOBYAYWG8ergsEjuNTKMBWyBsQiaL/tqHt+zDfCPMt9GLa0+OH0YuowFbIGxCFs4R9i998m/3Y/4Q5tWeAwSx0YAtEDZBgAM/bDyGeUKYJ3sOEPIZDdgCYRPy0HY0juKqEa7a5D8UKxusR0sunjcHoWo0YAuETRDjHO/zu8Zjq66rKntp9VdwFQhXIf7vN1/68DgApdoKgBF/a8XJmCYOgdGALRA2QeAc33gosgUAETmcFQA2lLn/zlSKL4Twhez7jutGaQIpSrUVKQnih8rOfqKEkGU0YAuETcgyiaq3wncghYgczgqk7Ln7/skjx3CFCFdIInz8nSIQIY1SbUWmWTl2oOwUkxjSjAZsgbAJaWK8YG9kO4EjDRE5nBVIw4Gdrs24EoQrEfxeKY/HkUWptiKXGTl6oGIcKaMBWyBsQhIHDYb/nkNCFiJyOCuQxVSy9GfFa3F5CJeH//zLQd9RCCjVVogdLw6ctQYAjAZsgbCJcz4c+/Y0s0GAiBzOCgi8+8Szvuf7kA/hMkx+txSJOMSUaivy8dnPvREteGv2rrHoeqgiIoezAmIcfKdrC1QR8pl8uAhEUKVUW5EPL7wutuW3d+zZOROLQhUROZwVUMXAf+XaAjGCqn9au+6BdQZdyTGoUqqtUMGZ9raRsTNT8Xhi9Q0rTk5e9LyyG2JE5HBWQIyDL/tn31O/bOOcQ4Cgyv+120EEQFc6prH6IaBUW5Eb1/xVr2y7qbDqzsnDvwJR0dp7pkd3AXjOd+i59w8gFyJyOCsgYP7NaeveswBMRvMj/7UNAgRV/oYm/BkplW+TLoIsSrUVWajsDn3tE0iyrvZMvtcHwLraM/leH5I4S9y5t//MpSlkIiKHswKZOOfa06GyJ94D4TOdQ+0QIKjyNzTh87j+hv0gjjRKtRXpdBb97cMkaZCiVN1xZn9POBZdecuDodFdSPNxJNw4sDPBGVKIyOGsQBo+Hb3uB+8Q48jUOdQOAYIqf0MTciFlVnGMgBOSlGor5vCYVL9HsVQhy5GjJ/z+i5s3bUQu+8/7d7z+WyICQEQOZwWSGKG8/ZD2UhS5dA61Q4Cgyt/QBDHJPCEv+wCAUm3Fn9z4qKHqPlyFR99+bfDUGBE5nBUAljx9/JrjAYh1DrVDgKDK39CEfOQVBw23rDPc2Y95wdmtAzvLP9Qu2/0h8ukcaocAQZW/oQmq4qCfhjdse/FbxUVLdLIegNl198x0iMUZ5kiARCDCZxhHgiNFo2iNBcr0kV8jKRqNhGdD3q92Ows0IIKqzqF2CBBU+RuaIEDArmjVaWYB0NrrZQnGOV/xF9tiJ3bjyhkqmy6NvjQbCuNPGGuu7wawVJFKZQlinUPtECCo8jc0IZd3WfG+qAMprb1elmAA3Fvbjvz+6dJSmyRpcNkYY0vW3DM+3IM5jDXXdyOlslBrIOTUOdQOAYIqf0MTMl3ghv+MrAEIaVp7vSzBALi3tvkGuzjneoNStKSYiKCKcz4zc4lzlNe2jA/3YA5jzfXdSEMEV6FWwud1DrVDgKDK39CEFAn82fCGMHTI0trrBefOLTsA+Aa7kHTrXf848cn05OGXkIt1zTcMpHv/jZ8gqby2hQin3/x5gjEw1lzfjSxGDTmMGqTpHGqHAEGVv6EJSa/Fy9+J2yHQ2ut1bt4e9PVZ3B7fYBcAInJ9ZQeS/P/7gsFgQBqr28PxqVNv/gfnHEB5bUvQ12dxe8aHe8BYc303BJYrklWWkNQ51A4Bgip/Q9NRvuR3kZVQ1drrdW7eHvT1Wdwe32AXktxb2wDEYokPXv0x59xmK9Hp5Hg8Njsbsq33KooWwPhwD5LKa1uCvj6L2zM+3APGmuu7ocpZqNESdQ61Q4Cg6gebH49DQj6tvV7n5u1BX5/F7fENdiHl0kzIVGhAiqKXZZ2CpJmZkMlk5Jwjqby2Jejrs7g948M9YKy5vhv5SBLtfvNRCBBUNdZ0lOgkm16CqtZer3Pz9qCvz+L2+Aa7IKAosiwryKW8tiXo67O4PePDPWCsub4bqr5uunib/qR9oB8CBFWNNR0AOFBllBSNBIHWXq9GkqpubeWc+wa7kMW9tW3qxEVoJHPZkvHhHmQpr20honMjz0ciMTDWXN8NAYfuk3bL+xyfsg/0Q4CgqrGmAykawGnSEnJo7fWyBAPg3trmG+yCgKLIsqwgl/LalvHhHsxhrLm+G1kk8GeWHtSCI8U+0A8BgqrGmg5k0kt8ZYEOmVp7vSzBALi3tvkGuyCgKLIsK8ilvLZlfLgHcxhrru9GGgn88aIjxVIYmewD/RAgqGqs6UAuVi1fbtAhpbXXyxIMQOVfPnj8f56BgKLoZFmPXEq+dN/EwRcwh7Hm+m6kPFJ0bIU0jVzsA/0QIKhqrOmA2AoDFWo1AFp7vSzBkOSufygaT5x649lwJIokjYZcmx8C5xpJQkqCcyTNhMJGozy+vwefYay5vhvAA0tOr9ech5h9oB8CBFWNNR3Ig7sLdTt+6WUJhjTLV917+oMXAVxft818jfHgnicBKHpZ1inIi7Gf3vX4Nw1jGnCosg/0Q4CgqrGmA/n8Yd3ym+5xP7nSZpE0SFm+6t7gmb6KL9/vG+xCit6g12l1UDd1RPeHe0OvI3pyNfKxD/RDgKCqsaYDYmOO4rNmA4A6jwsAB39mlV0hCYB7a5sco4OvPoU0RqNRo9FAIBEK6vdtAmnBeGQkCCB2rioxuRRi9oF+CBBUNdZ0IJfg0sLDZRaAkFTncSHlhmvkh+3FyMVoNGo0GmRJsITyyi0Un8UcxiMjQczhUmRsA48pyMU+0A8BgqrGmg5kiuh1B6pK4hoJaeo8LmSqL1XuNhchk8Fg0Gq1yMC1b++gwH6kYzwyEkQaHjVGxtYji32gHwIEVY01HUjzjts+rWiRpc7jQhYOPFhe/CWDjBRFr8g6GSnaj35BR38MED6H8chIEFkSU8tiZ69HGvtAPwQIqho2PkYSAThXYjq23AKBOo8LAgT8W6VNr9EAUBRZlhUA2tgJ7L2bCLkxHhkJIhcORI7eDKYFUFJQqHmpFwKEfBpv6nh9XRkIKuo8Lqgq1EhPV5bq9IoWM/LvG0iKQQXjkZEgxHhCGzlae+3L/YxzCBDyWbrjxWgkAVV1HhfyIR73Wv7662MceTEeGQlCFRkLLI+dhBjhMizd3huNxiFW53FBVUmopxyvVFombMZpl39Z8YwRKhiPjAQhJi2xmB85DlWEy2Pf5Jm9oQECdR4XBAojr1Wx5wgcQKVlwmacBiBxqeajCjmhQU6MR0aCEKh+Yfb4xCzyIVyJiu/vCn58CVnqPC5k0SHiDm3TIoyUSsuEzTiNFDmuvfmj65CN8chIEFksDic98DouD+HKWR98niU40tR5XMjAXaHvGnEGmSotEzbjNDKVTplXTSxFOsYjI0GkYYqx6PFxXAnCF2W+/xdIqfO4kFISf6089jPkUmmesBVMI5c1Z5ZbZ/WYw3hkJIg5BOuPLuLKEa7Chof+9XioCECdxwWgEBerQm0EDgGHeaK0YBoCurh248lyHZPAeGQkCOCJdzX/MngeXwjhqm3q3G2qWVI5+71COg9VDvOF0oIpqLLMFqw5VWqMlpH3VVwFwjzpeHltNBGCquvNF+wFU1Cl11RsuvEErhph/pCE7++uJBAErjdfsBdMQYh/dT3mC2G+Xbuy8NtPlQKELA7zhdKCKeTAv7YRjGEeERbGrpPfPHT4j8jksEyUGqeR6W/ulE+diGC+ERbST/bdfnbmCFIqzRO2gmmk3Li6wS7/NxYGYeE99vLaWCIEoNIyYTNOA7CYl9/sOIWFRFikCIsUYZEiLFKERYqwSBEWqf8DvC1ahbLUnEsAAAAASUVORK5CYII=' + +class Thumbnail: + def __init__(self, + image: bytes, + width: int = 200, + height: int = 200, + seconds: int = 1, *args, **kwargs) -> None: + + self.image = image + self.width = width + self.height = height + self.seconds = seconds + + if isinstance(self.image, str): + with open(image, 'rb') as file: + self.image = file.read() + + def to_base64(self, *args, **kwargs) -> str: + if self.image is not None: + return base64.b64encode(self.image).decode('utf-8') + + return DEFAULT_IMAGE_THUMB + + +class MakeThumbnail(Thumbnail): + def __init__(self, + image, + width: int = 200, + height: int = 200, + seconds: int = 1, *args, **kwargs) -> None: + self.image = None + self.width = width + self.height = height + self.seconds = seconds + + if hasattr(cv2, 'imdecode'): + if not isinstance(image, numpy.ndarray): + image = numpy.frombuffer(image, dtype=numpy.uint8) + image = cv2.imdecode(image, flags=1) + + self.image = self.ndarray_to_bytes(image) + + def ndarray_to_bytes(self, image, *args, **kwargs) -> str: + if hasattr(cv2, 'resize'): + self.width = image.shape[1] + self.height = image.shape[0] + image = cv2.resize(image, + (round(self.width / 10), round(self.height / 10)), + interpolation=cv2.INTER_CUBIC) + + status, buffer = cv2.imencode('.png', image) + if status is True: + return io.BytesIO(buffer).read() + + @classmethod + def from_video(cls, video: bytes, *args, **kwargs): + if hasattr(cv2, 'VideoCapture'): + with tempfile.TemporaryFile(mode='wb+') as file: + file.write(video) + capture = cv2.VideoCapture(file.name) + status, image = capture.read() + + if status is True: + fps = capture.get(cv2.CAP_PROP_FPS) + frames = capture.get(cv2.CAP_PROP_FRAME_COUNT) + return MakeThumbnail( + image=image, + seconds=int(frames / fps) * 1000, *args, **kwargs) + + return MakeThumbnail(image=DEFAULT_IMAGE_THUMB, *args, **kwargs) \ No newline at end of file diff --git a/rubpy/network/__init__.py b/rubpy/network/__init__.py new file mode 100644 index 0000000..2f85043 --- /dev/null +++ b/rubpy/network/__init__.py @@ -0,0 +1,2 @@ +from .proxies import Proxies +from .connection import Connection \ No newline at end of file diff --git a/rubpy/network/connection.py b/rubpy/network/connection.py new file mode 100644 index 0000000..cbfe79a --- /dev/null +++ b/rubpy/network/connection.py @@ -0,0 +1,316 @@ +import os +import asyncio +import aiohttp +from time import time +from ..crypto import Crypto +from ..structs import results +from ..gadgets import exceptions, methods + + +def capitalize(text): + return ''.join([ + c.title() for c in text.split('_')]) + + +class Connection: + """Internal class""" + + def __init__(self, client): + self._client = client + self._connection = aiohttp.ClientSession( + connector=self._client._proxy, + headers={'user-agent': self._client._user_agent, + 'origin': 'https://web.rubika.ir', + 'referer': 'https://web.rubika.ir/'}, + timeout=aiohttp.ClientTimeout(total=self._client._timeout)) + + async def _dcs(self): + if not self._client._dcs: + self._client._dcs = await self.execute( + methods.authorisations.GetDCs()) + + return self._client._dcs + + async def close(self): + await self._connection.close() + + async def execute(self, request: dict): + if not isinstance(request, dict): + request = request() + + self._client._logger.info('execute method', extra={'data': request}) + method_urls = request.pop('urls') + if method_urls is None: + method_urls = (await self._dcs()).default_api_urls + + if not method_urls: + raise exceptions.UrlNotFound( + 'It seems that the client could not' + ' get the list of Rubika api\'s.' + ' Please wait and try again.', + request=request) + + method = request['method'] + tmp_session = request.pop('tmp_session') + if self._client._auth is None: + self._client._auth = Crypto.secret(length=32) + self._client._logger.info( + 'create auth secret', extra={'data': self._client._auth}) + + if self._client._key is None: + self._client._key = Crypto.passphrase(self._client._auth) + self._client._logger.info( + 'create key passphrase', extra={'data': self._client._key}) + + request['client'] = self._client._platform + if request.get('encrypt') is True: + request = {'data_enc': Crypto.encrypt(request, key=self._client._key)} + + request['tmp_session' if tmp_session else 'auth'] = self._client._auth + + if 'api_version' not in request: + request['api_version'] = self._client.configuire['api_version'] + + if request['api_version'] == '6' and tmp_session == False: + request['auth'] = Crypto.decode_auth(request['auth']) + request['sign'] = Crypto.sign(self._client._private_key, request['data_enc']) + + if not method_urls[0].startswith('https://getdcmess'): + method_urls = [method_urls[0]] * 3 + + for _ in range(self._client._request_retries): + for url in method_urls: + try: + #async with self._connection.options(url) as result: + # if result.status != 200: + # continue + + async with self._connection.post(url, json=request) as result: + if result.status != 200: + continue + + result = await result.json() + if result.get('data_enc'): + result = Crypto.decrypt(result['data_enc'], + key=self._client._key) + status = result['status'] + status_det = result['status_det'] + if status == 'OK' and status_det == 'OK': + result['data']['_client'] = self._client + return results(method, update=result['data']) + + self._client._logger.warning( + 'request status ' + + capitalize(status_det), extra={'data': request}) + + raise exceptions(status_det)(result, request=request) + + except aiohttp.ServerTimeoutError: + pass + + raise exceptions.InternalProblem( + 'rubika server has an internal problem', request=request) + + async def handel_update(self, name, update): + handlers = self._client._handlers.copy() + for func, handler in handlers.items(): + try: + # if handler is empty filters + if isinstance(handler, type): + handler = handler() + + if handler.__name__ != capitalize(name): + continue + + # analyze handlers + if not await handler(update=update): + continue + + await func(handler) + + except exceptions.StopHandler: + break + + except Exception: + self._client._logger.error( + 'handler raised an exception', + extra={'data': update}, exc_info=True) + + async def receive_updates(self): + default_sockets = (await self._dcs()).default_sockets + asyncio.create_task(self.to_keep_alive()) + for url in default_sockets: + async with self._connection.ws_connect(url) as wss: + await self.send_data_to_ws(wss) + asyncio.create_task(self.keep_socket(wss)) + self._client._logger.info( + 'start receiving updates', extra={'data': url}) + async for message in wss: + if message.type in (aiohttp.WSMsgType.CLOSED, + aiohttp.WSMsgType.ERROR): + await wss.send_json() + try: + result = message.json() + if not result.get('data_enc'): + self._client._logger.debug( + 'the data_enc key was not found', + extra={'data': result}) + continue + + result = Crypto.decrypt(result['data_enc'], + key=self._client._key) + user_guid = result.pop('user_guid') + for name, package in result.items(): + if not isinstance(package, list): + continue + + for update in package: + update['_client'] = self._client + update['user_guid'] = user_guid + asyncio.create_task( + self.handel_update(name, update)) + + except Exception: + self._client._logger.error( + 'websocket raised an exception', + extra={'data': url}, exc_info=True) + + async def send_data_to_ws(self, wss, data='handSnake'): + if data == 'handSnake': + await wss.send_json({ + 'method': 'handShake', + 'data': '', + 'auth': self._client._auth, + 'api_version': '5',#self._client.configuire['api_version'] + }) + elif data == 'keep': + await wss.send_json({}) + + async def to_keep_alive(self): + while True: + await asyncio.sleep(5) + try: + await self._client(methods.chats.GetChatsUpdates(state=round(time()) - 200)) + except: continue + + async def keep_socket(self, wss): + while True: + await asyncio.sleep(5) + try: + await self.send_data_to_ws(wss, data='keep') + except: continue + + async def upload_file(self, file, + mime: str = None, file_name: str = None, + chunk: int = 1048576 * 2, callback=None, *args, **kwargs): + if isinstance(file, str): + if not os.path.exists(file): + raise ValueError('file not found in the given path') + if file_name is None: + file_name = os.path.basename(file) + + with open(file, 'rb') as file: + file = file.read() + + elif not isinstance(file, bytes): + raise TypeError('file arg value must be file path or bytes') + + if file_name is None: + raise ValueError('the file_name is not set') + + if mime is None: + mime = file_name.split('.')[-1] + + result = await self.execute( + methods.messages.RequestSendFile( + mime=mime, size=len(file), file_name=file_name)) + + id = result.id + index = 0 + dc_id = result.dc_id + total = int(len(file) / chunk + 1) + upload_url = result.upload_url + access_hash_send = result.access_hash_send + + while index < total: + data = file[index * chunk: index * chunk + chunk] + try: + result = await self._connection.post( + upload_url, + headers={ + 'auth': self._client._auth, + 'file-id': id, + 'total-part': str(total), + 'part-number': str(index + 1), + 'chunk-size': str(len(data)), + 'access-hash-send': access_hash_send + }, + data=data + ) + result = await result.json() + if callable(callback): + try: + await callback(len(file), index * chunk) + + except exceptions.CancelledError: + return None + + except Exception: + pass + + index += 1 + except Exception: + pass + + status = result['status'] + status_det = result['status_det'] + if status == 'OK' and status_det == 'OK': + result = { + 'mime': mime, + 'size': len(file), + 'dc_id': dc_id, + 'file_id': id, + 'file_name': file_name, + 'access_hash_rec': result['data']['access_hash_rec'] + } + + return results('UploadFile', result) + + self._client._logger.debug('upload failed', extra={'data': result}) + raise exceptions(status_det)(result, request=result) + + async def download(self, dc_id: int, file_id: int, access_hash: str, size: int, chunk=131072, callback=None): + url = f'https://messenger{dc_id}.iranlms.ir/GetFile.ashx' + start_index = 0 + result = b'' + + headers = { + 'auth': self._client._auth, + 'access-hash-rec': access_hash, + 'file-id': str(file_id), + 'user-agent': self._client._user_agent + } + + async with aiohttp.ClientSession() as session: + while True: + last_index = start_index + chunk - 1 if start_index + chunk < size else size - 1 + + headers['start-index'] = str(start_index) + headers['last-index'] = str(last_index) + + async with session.post(url, headers=headers) as response: + if response.status == 200: + data = await response.read() + if data: + result += data + if callback: + await callback(size, len(result)) + + # بررسی پایان فایل + if len(result) >= size: + break + + # بروزرسانی مقدار start_index برای دریافت بخش بعدی فایل + start_index = last_index + 1 + + return result \ No newline at end of file diff --git a/rubpy/network/proxies.py b/rubpy/network/proxies.py new file mode 100644 index 0000000..c513b6d --- /dev/null +++ b/rubpy/network/proxies.py @@ -0,0 +1,387 @@ +import struct +import socket +import asyncio +from ..gadgets import exceptions +from aiohttp import TCPConnector +from urllib.parse import urlparse +from aiohttp.abc import AbstractResolver + + +class BaseSocketWrapper(object): + def __init__(self, loop, host, port, family=socket.AF_INET): + self._loop = loop + self._socket = None + self._family = family + self._dest_host = None + self._dest_port = None + self._socks_host = host + self._socks_port = port + + async def _send(self, request): + data = bytearray() + for item in request: + if isinstance(item, int): + data.append(item) + elif isinstance(item, (bytearray, bytes)): + data += item + else: + raise ValueError('Unsupported item') + await self._loop.sock_sendall(self._socket, data) + + async def _receive(self, n): + data = b'' + while len(data) < n: + packet = await self._loop.sock_recv(self._socket, n - len(data)) + if not packet: + raise exceptions.InvalidServerReply('Not all data available') + data += packet + return bytearray(data) + + async def _resolve_addr(self, host, port): + addresses = await self._loop.getaddrinfo(host=host, port=port, + family=socket.AF_UNSPEC, + type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP, + flags=socket.AI_ADDRCONFIG) + if not addresses: + raise OSError(f'Can`t resolve address {host}:{host}') + return addresses[0][0], addresses[0][4][0] + + async def negotiate(self): + raise NotImplementedError + + async def connect(self, address): + self._dest_host = address[0] + self._dest_port = address[1] + self._socket = socket.socket(family=self._family, + type=socket.SOCK_STREAM) + self._socket.setblocking(False) + + try: + await self._loop.sock_connect(sock=self._socket, + address=(self._socks_host, + self._socks_port)) + except OSError as x: + self.close() + raise exceptions.SocksConnectionError( + x.errno, + 'Can not connect to proxy' + f'{self._socks_host}:{self._socks_port} [{x.strerror}]' + ) from x + except asyncio.CancelledError: + self.close() + + try: + await self.negotiate() + except exceptions.SocksError: + self.close() + + except asyncio.CancelledError: + if isinstance(self._loop, asyncio.ProactorEventLoop): + self.close() + + def close(self): + self._socket.close() + + async def sendall(self, data): + await self._loop.sock_sendall(self._socket, data) + + async def recv(self, nbytes): + return await self._loop.sock_recv(self._socket, nbytes) + + @property + def socket(self): + return self._socket + + +class Socks4SocketWrapper(BaseSocketWrapper): + def __init__(self, loop, host, port, user_id=None, resolver=False): + BaseSocketWrapper.__init__(self, loop=loop, host=host, + port=port, family=socket.AF_INET) + self._user_id = user_id + self._resolver = resolver + + async def _socks_connect(self): + host = self._dest_host + include_hostname = False + try: + host_bytes = socket.inet_aton(self._dest_host) + except socket.error: + if self._resolver: + host_bytes = bytes([0x00, 0x00, 0x00, 0x01]) + include_hostname = True + else: + _, host = await self._resolve_addr(self._dest_host, + self._dest_port) + host_bytes = socket.inet_aton(self._dest_host) + + request = [0x04, 0x01, struct.pack('>H', self._dest_port), host_bytes] + + if self._user_id: + request.append(self._user_id.encode()) + + request.append(0x00) + + if include_hostname: + request += [host.encode('idna'), 0x00] + + await self._send(request) + + respond = await self._receive(8) + + if respond[0] != 0x00: + raise exceptions.InvalidServerReply( + 'SOCKS4 proxy server sent invalid data') + + if respond[1] == 0x5B: + raise exceptions.SocksError('Request rejected or failed') + + elif respond[1] == 0x5C: + raise exceptions.SocksError('Request rejected because SOCKS server') + + elif respond[1] == 0x5D: + raise exceptions.SocksError( + 'Request rejected because the client program') + + elif respond[1] != 0x5A: + raise exceptions.SocksError('Unknown error') + + return ((host, self._dest_port), + socket.inet_ntoa(respond[4:]), + struct.unpack('>H', respond[2:4])[0]) + + async def negotiate(self): + await self._socks_connect() + + +class Socks5SocketWrapper(BaseSocketWrapper): + def __init__(self, loop, host, port, username=None, + password=None, resolver=True, family=socket.AF_INET): + BaseSocketWrapper.__init__(self, loop=loop, host=host, + port=port, family=family) + self._resolver = resolver + self._username = username + self._password = password + + async def _socks_auth(self): + auth_methods = [0x00] + if self._username and self._password: + auth_methods = [0x02, 0x00] + + await self._send([0x05, len(auth_methods)] + auth_methods) + respond = await self._receive(2) + if respond[0] != 0x05: + raise exceptions.InvalidServerVersion( + f'Unexpected SOCKS version number: {respond[0]}') + + if respond[1] == 0xFF: + raise exceptions.NoAcceptableAuthMethods( + 'No acceptable authentication methods were offered') + + if respond[1] not in auth_methods: + raise exceptions.UnknownAuthMethod( + f'Unexpected SOCKS authentication method: {respond[1]}') + + if respond[1] == 0x02: + await self._send([0x01, + chr(len(self._username)).encode(), + self._username.encode(), + chr(len(self._password)).encode(), + self._password.encode()]) + + respond = await self._receive(2) + if respond[0] != 0x01: + raise exceptions.InvalidServerReply( + 'Invalid authentication response') + + if respond[1] != 0x00: + raise exceptions.LoginAuthenticationFailed( + 'Username and password authentication failure') + + async def _socks_connect(self): + req_addr, resolved_addr = await self._build_dest_address() + await self._send([0x05, 0x01, 0x00] + req_addr) + respond = await self._receive(3) + if respond[0] != 0x05: + raise exceptions.InvalidServerVersion( + f'Unexpected SOCKS version number: {respond[0]}') + + if respond[1] == 0x01: + raise exceptions.SocksError('General SOCKS server failure') + + elif respond[1] == 0x02: + raise exceptions.SocksError('Connection not allowed by ruleset') + + elif respond[1] == 0x03: + raise exceptions.SocksError('Network unreachable') + + elif respond[1] == 0x04: + raise exceptions.SocksError('Host unreachable') + + elif respond[1] == 0x05: + raise exceptions.SocksError('Connection refused') + + elif respond[1] == 0x06: + raise exceptions.SocksError('TTL expired') + + elif respond[1] == 0x07: + raise exceptions.SocksError('Command not supported, or protocol error') + + elif respond[1] == 0x08: + raise exceptions.SocksError('Address type not supported') + + elif respond[1] != 0x00: + raise exceptions.SocksError('Unknown error') + + if respond[2] != 0x00: + raise exceptions.InvalidServerReply('The reserved byte must be 0x00') + + return resolved_addr, await self._read_binded_address() + + async def _build_dest_address(self): + port_bytes = struct.pack('>H', self._dest_port) + for family in (socket.AF_INET, socket.AF_INET6): + try: + host_bytes = socket.inet_pton(family, self._dest_host) + return ( + [0x01 if family == socket.AF_INET else 0x04, + host_bytes, port_bytes], + (self._dest_host, self._dest_port) + ) + + except socket.error: + pass + + if self._resolver: + host_bytes = self._dest_host.encode('idna') + return [0x03, chr(len(host_bytes)).encode(), + host_bytes, port_bytes], (self._dest_host, self._dest_port) + + family, _ = await self._resolve_addr(host=self._dest_host, + port=self._dest_port) + return ( + [0x01 if family == socket.AF_INET else 0x04, + host_bytes, port_bytes], + (socket.inet_ntop(family, host_bytes), self._dest_port) + ) + + async def _read_binded_address(self): + atype = (await self._receive(1))[0] + if atype == 0x01: + addr = await self._receive(4) + addr = socket.inet_ntoa(addr) + elif atype == 0x03: + length = await self._receive(1) + addr = await self._receive(ord(length)) + elif atype == 0x04: + addr = await self._receive(16) + addr = socket.inet_ntop(socket.AF_INET6, addr) + else: + raise exceptions.InvalidServerReply( + 'SOCKS5 proxy server sent invalid data') + port = await self._receive(2) + return addr, struct.unpack('>H', port)[0] + + async def negotiate(self): + await self._socks_auth() + await self._socks_connect() + + +class Resolver(AbstractResolver): + async def resolve(self, host, port=0, family=socket.AF_INET): + return [{ + 'proto': 0, 'flags': 0, + 'host': host, 'port': port, + 'family': family, 'hostname': host}] + + async def close(self): + pass + + +class Proxies(TCPConnector): + + def __init__(self, + host: str, + port: int, + type: str = 'http', + resolver: bool = False, + username: str = None, + password: str = None, + family=socket.AF_INET, *args, **kwargs): + + if isinstance(type, str): + if type.lower() not in ['http', 'https', 'socks5', 'socks4']: + raise ValueError( + 'proxy type must be' + '(`socks5` | `socks4` | `http` | `https` )') + if resolver: + kwargs['resolver'] = Resolver() + + TCPConnector.__init__(self, **kwargs) + self._resolver = resolver + self._proxy_host = host + self._proxy_port = port + self._proxy_type = type + self._proxy_family = family + self._proxy_username = username + self._proxy_password = password + + @property + def proxy_url(self): + pattern = '{scheme}://{host}:{port}' + if self._proxy_username: + pattern = '{scheme}://{username}:{password}@{host}:{port}' + return urlparse(pattern.format(scheme=self._proxy_type, + username=self._proxy_username, + password=self._proxy_password, + host=self._proxy_host, + port=self._proxy_port)) + + @classmethod + def from_url(cls, url, **kwargs): + """_from_url_ + + Args: + url (str): proxy url + http exmaple: http://login:password@127.0.0.1:1080 + https exmaple: https://login:password@127.0.0.1:1080 + socks4 exmaple: socks4://username:password@127.0.0.1:1080 + socks5 exmaple: socks5://username:password@127.0.0.1:1080 + """ + parse = urlparse(url) + return cls( + type=parse.scheme, port=int(parse.port), host=parse.hostname, + username=parse.username, password=parse.password, **kwargs) + + async def connect(self, req, traces, timeout): + if self.proxy_url.scheme in ['http', 'https']: + req.update_proxy(self.proxy_url._replace(scheme='https').geturl(), + None, req.proxy_headers) + return await TCPConnector.connect( + self, req=req, traces=traces, timeout=timeout) + + async def _wrap_create_connection(self, protocol_factory, host=None, + port=None, *args, **kwargs): + if self.proxy_url.scheme not in ['http', 'https']: + if self.proxy_url.scheme == 'socks5': + sock = Socks5SocketWrapper(resolver=self._resolver, + loop=self._loop, + host=self._proxy_host, + port=self._proxy_port, + family=self._proxy_family, + username=self._proxy_username, + password=self._proxy_password) + else: + sock = Socks4SocketWrapper(resolver=self._resolver, + loop=self._loop, + host=self._proxy_host, + port=self._proxy_port, + user_id=self._proxy_username) + await sock.connect((host, port)) + return await TCPConnector._wrap_create_connection( + self, protocol_factory, None, None, + *args, sock=sock.socket, **kwargs) + else: + return await TCPConnector._wrap_create_connection( + self, protocol_factory, host, port, + *args, **kwargs) diff --git a/rubpy/sessions/__init__.py b/rubpy/sessions/__init__.py new file mode 100644 index 0000000..710ef4b --- /dev/null +++ b/rubpy/sessions/__init__.py @@ -0,0 +1,2 @@ +from .sqliteSession import SQLiteSession +from .stringSession import StringSession \ No newline at end of file diff --git a/rubpy/sessions/sqliteSession.py b/rubpy/sessions/sqliteSession.py new file mode 100644 index 0000000..d9da93d --- /dev/null +++ b/rubpy/sessions/sqliteSession.py @@ -0,0 +1,67 @@ +# import os +import sqlite3 + +suffix = '.rbs' +rbs_version = 1 + + +class SQLiteSession(object): + + def __init__(self, session: str) -> None: + self.filename = session + if not session.endswith(suffix): + self.filename += suffix + + self._connection = sqlite3.connect(self.filename, + check_same_thread=False) + cursor = self._connection.cursor() + cursor.execute('select name from sqlite_master ' + 'where type=? and name=?', ('table', 'version')) + if cursor.fetchone(): + cursor.execute('select version from version') + version = cursor.fetchone()[0] + if rbs_version != version: + self.upgrade_database(version) + + else: + cursor.execute( + 'create table version (version integer primary key)') + cursor.execute('insert into version values (?)', (rbs_version,)) + cursor.execute('create table session (phone text primary key' + ', auth text, guid text, agent text, private_key text)') + self._connection.commit() + cursor.close() + + def upgrade_database(self, version): + pass + + def information(self): + cursor = self._connection.cursor() + cursor.execute('select * from session') + result = cursor.fetchone() + cursor.close() + return result + + def insert(self, phone_number, auth, guid, user_agent, private_key, *args, **kwargs): + cursor = self._connection.cursor() + cursor.execute( + 'insert or replace into session (phone, auth, guid, agent, private_key)' + ' values (?, ?, ?, ?, ?)', + (phone_number, auth, guid, user_agent, private_key) + ) + self._connection.commit() + cursor.close() + + @classmethod + def from_string(cls, session, file_name=None): + info = session.information() + if file_name is None: + if info is None: + raise ValueError('file_name arg is not set') + file_name = info[0] + + session = SQLiteSession(file_name) + if info is not None: + session.insert(*info) + + return session diff --git a/rubpy/sessions/stringSession.py b/rubpy/sessions/stringSession.py new file mode 100644 index 0000000..60138cb --- /dev/null +++ b/rubpy/sessions/stringSession.py @@ -0,0 +1,39 @@ +import json +import base64 + + +class StringSession(object): + def __init__(self, session: str = None) -> None: + self.session = self.load(session) + + @classmethod + def load(cls, session): + if isinstance(session, str): + return json.loads(base64.b64decode(session)) + + @classmethod + def dump(cls, session): + if isinstance(session, list): + session = json.dumps(session).encode('utf-8') + return base64.b64encode(session).decode('utf-8') + + @classmethod + def from_sqlite(cls, session): + session = cls.dump(session.information()) + return StringSession(session) + + def insert(self, phone_number, auth, guid, user_agent, *args, **kwargs): + self.session = [phone_number, auth, guid, user_agent] + + def information(self): + return self.session + + def save(self, file_name=None): + result = self.dump(self.session) + if result is None: + if isinstance(file_name, str): + if not file_name.endswith('.txt'): + file_name += '.txt' + with open(file_name, 'w+') as file: + file.write(result) + return result diff --git a/rubpy/structs/__init__.py b/rubpy/structs/__init__.py new file mode 100644 index 0000000..2c8fff6 --- /dev/null +++ b/rubpy/structs/__init__.py @@ -0,0 +1,5 @@ +from . import models +from . import results +from . import handlers +from .struct import Struct + diff --git a/rubpy/structs/handlers.py b/rubpy/structs/handlers.py new file mode 100644 index 0000000..4dd7fce --- /dev/null +++ b/rubpy/structs/handlers.py @@ -0,0 +1,72 @@ +import sys +import asyncio +from .struct import Struct +from ..gadgets import Classino + +__handlers__ = [ + 'ChatUpdates', + 'MessageUpdates', + 'ShowActivities', + 'ShowNotifications', + 'RemoveNotifications' +] + + +class BaseHandlers(Struct): + __name__ = 'CustomHandlers' + + def __init__(self, *models, __any: bool = False, **kwargs) -> None: + self.__models = models + self.__any = __any + + def is_async(self, value, *args, **kwargs): + result = False + if asyncio.iscoroutinefunction(value): + result = True + + elif asyncio.iscoroutinefunction(value.__call__): + result = True + + return result + + async def __call__(self, update: dict, *args, **kwargs) -> bool: + self.original_update = update + if self.__models: + for filter in self.__models: + if callable(filter): + # if BaseModels is not called + if isinstance(filter, type): + filter = filter(func=None) + + if self.is_async(filter): + status = await filter(self, result=None) + + else: + status = filter(self, result=None) + + if status and self.__any: + return True + + elif not status: + return False + + return True + + +class Handlers(Classino): + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseHandlers in value.__bases__ + + def __dir__(self): + return sorted(__handlers__) + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name)(*args, **kwargs) + + def __getattr__(self, name): + return self.create(name, (BaseHandlers,), __handlers__) + +sys.modules[__name__] = Handlers(__name__) diff --git a/rubpy/structs/models.py b/rubpy/structs/models.py new file mode 100644 index 0000000..d0ac0f5 --- /dev/null +++ b/rubpy/structs/models.py @@ -0,0 +1,155 @@ +import re +import sys +from ..gadgets import Classino + +__all__ = ['Operator', 'BaseModels', 'RegexModel'] +__models__ = [ + 'is_pinned', 'is_mute', 'count_unseen', 'message_id', + 'is_group', 'is_private', 'is_channel', 'is_in_contact', + 'raw_text', 'original_update', 'object_guid', 'author_guid', 'time', 'reply_message_id'] + +class Operator: + Or = 'OR' + And = 'AND' + Less = 'Less' + Lesse = 'Lesse' + Equal = 'Equal' + Greater = 'Greater' + Greatere = 'Greatere' + Inequality = 'Inequality' + + def __init__(self, value, operator, *args, **kwargs): + self.value = value + self.operator = operator + + def __eq__(self, value) -> bool: + return self.operator == value + + +class BaseModels: + __name__ = 'CustomModels' + + def __init__(self, + func=None, filters=[], *args, **kwargs) -> None: + self.func = func + if not isinstance(filters, list): + filters = [filters] + self.filters = filters + + def insert(self, filter): + self.filters.append(filter) + return self + + def __or__(self, value): + return self.insert(Operator(value, Operator.Or)) + + def __and__(self, value): + return self.insert(Operator(value, Operator.And)) + + def __eq__(self, value): + return self.insert(Operator(value, Operator.Equal)) + + def __ne__(self, value): + return self.insert(Operator(value, Operator.Inequality)) + + def __lt__(self, value): + return self.insert(Operator(value, Operator.Less)) + + def __le__(self, value): + return self.insert(Operator(value, Operator.Lesse)) + + def __gt__(self, value): + return self.insert(Operator(value, Operator.Greater)) + + def __ge__(self, value): + return self.insert(Operator(value, Operator.Greatere)) + + async def build(self, update): + # get key + result = getattr(update, self.__name__, None) + if callable(self.func): + if update.is_async(self.func): + result = await self.func(result) + else: + result = self.func(result) + + for filter in self.filters: + value = filter.value + + # if the comparison was with a function + if callable(value): + if update.is_async(value): + value = await value(update, result) + else: + value = value(update, result) + + if self.func: + if update.is_async(self.func): + value = await self.func(value) + else: + value = self.func(value) + + if filter == Operator.Or: + result = result or value + + elif filter == Operator.And: + result = result and value + + elif filter == Operator.Less: + result = result < value + + elif filter == Operator.Lesse: + result = result <= value + + elif filter == Operator.Equal: + result = result == value + + elif filter == Operator.Greater: + result = result > value + + elif filter == Operator.Greatere: + result = result >= value + + elif filter == Operator.Inequality: + result = result != value + + return bool(result) + + async def __call__(self, update, *args, **kwargs): + return await self.build(update) + + +class RegexModel(BaseModels): + def __init__(self, pattern, *args, **kwargs) -> None: + self.pattern = re.compile(pattern) + super().__init__(*args, **kwargs) + + def __call__(self, update, *args, **kwargs) -> bool: + if update.raw_text is None: + return False + + update.pattern_match = self.pattern.match(update.raw_text) + return bool(update.pattern_match) + + +class Models(Classino): + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseModels in value.__bases__ + + def __dir__(self): + return sorted(__models__) + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name) + + def __getattr__(self, name): + if name in __all__: + return globals()[name] + return self.create(name, (BaseModels, ), + authorise=__models__, exception=False) + + +sys.modules[__name__] = Models(__name__) diff --git a/rubpy/structs/results.py b/rubpy/structs/results.py new file mode 100644 index 0000000..24706d1 --- /dev/null +++ b/rubpy/structs/results.py @@ -0,0 +1,27 @@ +import sys +from .struct import Struct +from ..gadgets import Classino + + +class BaseResults(Struct): + __type__ = 'CustomResult' + + def __init__(self, update, *args, **kwargs) -> None: + self.original_update = update + + +class Results(Classino): + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseResults in value.__bases__ + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name)(*args, **kwargs) + + def __getattr__(self, name): + return self.create(name, (BaseResults, ), exception=False) + + +sys.modules[__name__] = Results(__name__) diff --git a/rubpy/structs/struct.py b/rubpy/structs/struct.py new file mode 100644 index 0000000..55037de --- /dev/null +++ b/rubpy/structs/struct.py @@ -0,0 +1,443 @@ +import json +import base64 +from ..gadgets import methods, thumbnail + + +class Struct: + def __str__(self) -> str: + return self.jsonify(indent=2) + + def __getattr__(self, name): + return self.find_keys(keys=name) + + def __setitem__(self, key, value): + self.original_update[key] = value + + def __getitem__(self, key): + return self.original_update[key] + + def __lts__(self, update: list, *args, **kwargs): + for index, element in enumerate(update): + if isinstance(element, list): + update[index] = self.__lts__(update=element) + + elif isinstance(element, dict): + update[index] = Struct(update=element) + + else: + update[index] = element + return update + + def __init__(self, update: dict, *args, **kwargs) -> None: + self.original_update = update + + def to_dict(self): + return self.original_update + + def jsonify(self, indent=None, *args, **kwargs) -> str: + result = self.original_update + result['original_update'] = 'dict{...}' + return json.dumps(result, indent=indent, + ensure_ascii=False, + default=lambda value: str(value)) + + def find_keys(self, keys, original_update=None, *args, **kwargs): + + if original_update is None: + original_update = self.original_update + + if not isinstance(keys, list): + keys = [keys] + + if isinstance(original_update, dict): + for key in keys: + try: + update = original_update[key] + if isinstance(update, dict): + update = Struct(update=update) + + elif isinstance(update, list): + update = self.__lts__(update=update) + + return update + + except KeyError: + pass + original_update = original_update.values() + + for value in original_update: + if isinstance(value, (dict, list)): + try: + return self.find_keys(keys=keys, original_update=value) + + except AttributeError: + pass + + return None + + def guid_type(self, guid: str, *args, **kwargs) -> str: + if isinstance(guid, str): + if guid.startswith('u'): + return 'User' + + elif guid.startswith('g'): + return 'Group' + + elif guid.startswith('c'): + return 'Channel' + + # property functions + + @property + def type(self): + try: + return self.find_keys(keys=['type', 'author_type']) + + except AttributeError: + pass + + @property + def raw_text(self): + try: + return self.find_keys(keys='text') + + except AttributeError: + pass + + @property + def message_id(self): + try: + return self.find_keys(keys=['message_id', + 'pinned_message_id']) + except AttributeError: + pass + + @property + def reply_message_id(self): + try: + return self.find_keys(keys='reply_to_message_id') + + except AttributeError: + pass + + @property + def is_group(self): + return self.type == 'Group' + + @property + def is_channel(self): + return self.type == 'Channel' + + @property + def is_private(self): + return self.type == 'User' + + @property + def object_guid(self): + try: + return self.find_keys(keys=['group_guid', + 'object_guid', 'channel_guid']) + except AttributeError: + pass + + @property + def author_guid(self): + try: + return self.author_object_guid + + except AttributeError: + pass + + # async methods + + async def pin(self, + object_guid: str = None, + message_id: str = None, + action: str = methods.messages.Pin, *args, **kwargs): + """_pin_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + action (bool, optional): + _pin or unpin_. Defaults to methods.messages.Pin. ( + methods.messages.Pin, + methods.messages.Unpin + ) + Returns: + BaseResults: result + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_id is None: + message_id = self.message_id + + return await self._client( + methods.messages.SetPinMessage( + object_guid=object_guid, + message_id=message_id, + action=action)) + + async def edit(self, + text: str, + object_guid: str = None, + message_id: str = None, *args, **kwargs): + """_edit_ + Args: + text (str): + _message text_ + object_guid (str, optional): + _custom objec guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_id is None: + message_id = self.message_id + + return await self._client( + methods.messages.EditMessage( + object_guid=object_guid, + message_id=message_id, + text=text)) + + async def copy(self, + to_object_guid: str, + from_object_guid: str = None, message_ids=None, *args, **kwargs): + """_copy_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + result = await self.get_messages(from_object_guid, message_ids) + messages = [] + if result.messages: + for message in result.messages: + + try: + file_inline = message.file_inline + kwargs.update(file_inline.to_dict()) + + except AttributeError: + file_inline = None + + try: + thumb = thumbnail.Thumbnail( + base64.b64decode(message.thumb_inline), *args, **kwargs) + + except AttributeError: + thumb = kwargs.get('thumb', True) + + try: + message = message.sticker + + except AttributeError: + message = message.raw_text + + if file_inline is not None: + if file_inline.type not in [methods.messages.Gif, + methods.messages.Sticker]: + file_inline = await self.download(file_inline) + messages.append(await self._client.send_message( + thumb=thumb, + message=message, + file_inline=file_inline, + object_guid=to_object_guid, *args, **kwargs)) + continue + + messages.append(await self._client.send_message( + message=message, + object_guid=to_object_guid, + file_inline=file_inline, *args, **kwargs)) + + return {'status': 'OK', 'messages': messages} + + async def seen(self, seen_list: dict = None, *args, **kwargs): + """_seen_ + Args: + seen_list (dict, optional): + _description_. Defaults t + {update.object_guid: update.message_id} + """ + + if seen_list is None: + seen_list = {self.object_guid: self.message_id} + return await self._client(methods.chats.SeenChats(seen_list=seen_list)) + + async def reply(self, + message=None, + object_guid: str = None, + reply_to_message_id: str = None, + file_inline: str = None, *args, **kwargs): + """_reply_ + Args: + message (Any, optional): + _message or cation or sticker_ . Defaults to None. + object_guid (str): + _object guid_ . Defaults to update.object_guid + reply_to_message_id (str, optional): + _reply to message id_. Defaults to None. + file_inline (typing.Union[pathlib.Path, bytes], optional): + _file_. Defaults to None. + is_gif (bool, optional): + _is it a gif file or not_. Defaults to None. + is_image (bool, optional): + _is it a image file or not_. Defaults to None. + is_voice (bool, optional): + _is it a voice file or not_. Defaults to None. + is_music (bool, optional): + _is it a music file or not_. Defaults to None. + is_video (bool, optional): + _is it a video file or not_. Defaults to None. + thumb (bool, optional): + if value is "True", + the lib will try to build the thumb ( require cv2 ) + if value is thumbnail.Thumbnail, to set custom + Defaults to True. + """ + + if object_guid is None: + object_guid = self.object_guid + + if reply_to_message_id is None: + reply_to_message_id = self.message_id + + return await self._client.send_message( + message=message, + object_guid=object_guid, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id, *args, **kwargs) + + async def forwards(self, + to_object_guid: str, + from_object_guid: str = None, + message_ids=None, *args, **kwargs): + """_forwards_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + return await self._client( + methods.messages.ForwardMessages( + from_object_guid=from_object_guid, + to_object_guid=to_object_guid, + message_ids=message_ids)) + + async def download(self, file_inline=None, file=None, *args, **kwargs): + return await self._client.download_file_inline( + file_inline or self.file_inline, + file=file, *args, **kwargs) + + async def get_author(self, author_guid: str = None, *args, **kwargs): + """_get user or author information_ + Args: + author_guid (str, optional): + _custom author guid_. Defaults to update.author_guid + """ + + if author_guid is None: + author_guid = self.author_guid + + return await self.get_object(object_guid=author_guid, *args, **kwargs) + + async def get_object(self, object_guid: str = None, *args, **kwargs): + """_get object information_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + """ + + if object_guid is None: + object_guid = self.object_guid + + if self.guid_type(object_guid) == 'User': + return await self._client( + methods.users.GetUserInfo( + user_guid=object_guid)) + + elif self.guid_type(object_guid) == 'Group': + return await self._client( + methods.groups.GetGroupInfo( + object_guid=object_guid)) + + elif self.guid_type(object_guid) == 'Channel': + return await self._client( + methods.channels.GetChannelInfo( + object_guid=object_guid)) + + async def get_messages(self, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_get messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + return await self._client( + methods.messages.GetMessagesByID( + object_guid=object_guid, message_ids=message_ids)) + + async def delete_messages(self, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_delete messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, list, optional): + _custom message ids_. Defaults to update.message_id. + type(str, optional): + the message should be deleted for everyone or not. + Defaults to methods.messages.Global ( + methods.messages.Local, + methods.messages.Global + ) + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + return await self._client( + methods.messages.DeleteMessages( + object_guid=object_guid, + message_ids=message_ids, *args, **kwargs)) \ No newline at end of file diff --git a/rubpy/sync/__init__.py b/rubpy/sync/__init__.py new file mode 100644 index 0000000..09a6f6e --- /dev/null +++ b/rubpy/sync/__init__.py @@ -0,0 +1,9 @@ +from .client import Client +from .network import Proxies +from .structs import handlers, models +from .structs.struct import Struct as Message +from .gadgets import exceptions, methods + + +__version__ = '6.4.6' +__author__ = 'Shayan Heidari' \ No newline at end of file diff --git a/rubpy/sync/client.py b/rubpy/sync/client.py new file mode 100644 index 0000000..e673e24 --- /dev/null +++ b/rubpy/sync/client.py @@ -0,0 +1,1061 @@ +from .gadgets import exceptions, methods, thumbnail +from .sessions import StringSession, SQLiteSession +from .network import Connection, Proxies +from . import __name__ as logger_name +from mutagen.mp3 import MP3 +from .structs import Struct +from .crypto import Crypto +from io import BytesIO +from re import compile +import logging +import os + + +class Client: + configuire = { + 'package': 'web.rubika.ir', + 'platform': 'Web', + 'app_name': 'Main', + 'user_agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko)' + 'Chrome/102.0.0.0 Safari/537.36'), + 'api_version': '6', + 'app_version': '4.4.5' + } + + def __init__(self, + session, + proxy=None, + logger=None, + timeout=20, + lang_code='fa', + user_agent=None, + request_retries=5, *args, **kwargs): + + if isinstance(session, str): + session = SQLiteSession(session) + + elif not isinstance(session, StringSession): + raise TypeError('The given session must be a ' + 'str or [rubpy.sessions.StringSession]') + + if not isinstance(logger, logging.Logger): + logger = logging.getLogger(logger_name) + + if proxy and not isinstance(proxy, Proxies): + raise TypeError( + 'The given proxy must be a [rubpy.network.Proxies]') + + self._dcs = None + self._key = None + self._auth = None + self._guid = None + self._proxy = proxy + self._logger = logger + self._timeout = timeout + self._session = session + self._handlers = {} + self._request_retries = request_retries + self._user_agent = user_agent or self.configuire['user_agent'] + self._platform = { + 'package': kwargs.get('package', self.configuire['package']), + 'platform': kwargs.get('platform', self.configuire['platform']), + 'app_name': kwargs.get('app_name', self.configuire['app_name']), + 'app_version': kwargs.get('app_version', + self.configuire['app_version']), + 'lang_code': lang_code} + + def __call__(self, request: object): + try: + result = self._connection.execute(request) + + if result.__name__ == 'signIn' and result.status == 'OK': + result.auth = Crypto.decrypt_RSA_OAEP(self._private_key, result.auth) + self._key = Crypto.passphrase(result.auth) + self._auth = result.auth + self._session.insert( + auth=self._auth, + guid=result.user.user_guid, + user_agent=self._user_agent, + phone_number=result.user.phone, + private_key=self._private_key) + + self( + methods.authorisations.RegisterDevice( + self._user_agent, + lang_code=self._platform['lang_code'], + app_version=self._platform['app_version']) + ) + + return result + + except AttributeError: + raise exceptions.NoConnection( + 'You must first connect the Client' + ' with the *.connect() method') + + def __enter__(self): + return self.start(phone_number=None) + + def __exit__(self, *args, **kwargs): + return self.disconnect() + + def start(self, phone_number: str, *args, **kwargs): + if not hasattr(self, '_connection'): + self.connect() + + try: + self._logger.info('user info', extra={'data': self.get_me()}) + + except (exceptions.NotRegistrred, exceptions.InvalidInput): + self._logger.debug('user not registered!') + if phone_number is None: + phone_number = input('Phone Number: ') + is_phone_number_true = True + while is_phone_number_true: + if input(f'Is the {phone_number} correct[y or n] > ').lower() == 'y': + is_phone_number_true = False + else: + phone_number = input('Phone Number: ') + + if phone_number.startswith('0'): + phone_number = '98{}'.format(phone_number[1:]) + elif phone_number.startswith('+98'): + phone_number = phone_number[1:] + elif phone_number.startswith('0098'): + phone_number = phone_number[2:] + + result = self( + methods.authorisations.SendCode( + phone_number=phone_number, *args, **kwargs)) + + if result.status == 'SendPassKey': + while True: + pass_key = input(f'Password [{result.hint_pass_key}] > ') + result = self( + methods.authorisations.SendCode( + phone_number=phone_number, + pass_key=pass_key, *args, **kwargs)) + + if result.status == 'OK': + break + + public_key, self._private_key = Crypto.create_keys() + while True: + phone_code = input('Code: ') + result = self( + methods.authorisations.SignIn( + phone_code=phone_code, + phone_number=phone_number, + phone_code_hash=result.phone_code_hash, + public_key=public_key, + *args, **kwargs)) + + if result.status == 'OK': + break + + return self + + def connect(self): + self._connection = Connection(client=self) + information = self._session.information() + self._logger.info(f'the session information was read {information}') + if information: + self._auth = information[1] + self._guid = information[2] + self._private_key = information[4] + if isinstance(information[3], str): + self._user_agent = information[3] + + return self + + def disconnect(self): + try: + self._connection.close() + self._logger.info(f'the client was disconnected') + + except AttributeError: + raise exceptions.NoConnection( + 'You must first connect the Client' + ' with the *.connect() method') + + def run_until_disconnected(self): + return self._connection.receive_updates() + + def on(self, handler): + def MetaHandler(func): + self.add_handler(func, handler) + return func + return MetaHandler + + def add_handler(self, func, handler): + self._handlers[func] = handler + + def remove_handler(self, func): + try: + self._handlers.pop(func) + except KeyError: + pass + + def get_me(self, *args, **kwargs): + return self(methods.users.GetUserInfo(self._guid)) + + def upload(self, file, *args, **kwargs): + return self._connection.upload_file(file=file, *args, **kwargs) + + def download_file_inline(self, file_inline, save_as: str = None, chunk_size: int = 131072, callback=None, *args, **kwargs): + result = self._connection.download( + file_inline.dc_id, + file_inline.file_id, + file_inline.access_hash_rec, + file_inline.size, + chunk=chunk_size, + callback=callback) + + if isinstance(save_as, str): + with open(save_as, 'wb+') as _file: + _file.write(result) + return save_as + + return result + + +# ---------------- Users Methods ---------------- + + def get_user_info(self, user_guid: str): + return self(methods.users.GetUserInfo(user_guid)) + + def block_user(self, user_guid: str): + return self(methods.users.SetBlockUser(user_guid, 'Block')) + + def unblock_user(self, user_guid: str): + return self(methods.users.SetBlockUser(user_guid, 'Unblock')) + + def delete_user_chat(self, user_guid: str, last_deleted_message_id: str): + return self(methods.users.DeleteUserChat(user_guid, last_deleted_message_id)) + + def check_user_username(self, username: str): + return self(methods.users.CheckUserUsername(username.replace('@', ''))) + +# ---------------- Chats Methods ---------------- + + def upload_avatar(self, object_guid: str, main_file_id: str, thumbnail_file_id: str): + return self(methods.chats.UploadAvatar(object_guid, main_file_id, thumbnail_file_id)) + + def delete_avatar(self, object_guid: str, avatar_id: str): + return self(methods.chats.DeleteAvatar(object_guid, avatar_id)) + + def get_avatars(self, object_guid: str): + return self(methods.chats.GetAvatars(object_guid)) + + def get_chats(self, start_id: int = None): + return self(methods.chats.GetChats(start_id)) + + def seen_chats(self, seen_list: dict): + return self(methods.chats.SeenChats(seen_list)) + + def get_chat_ads(self, state: int): + return self(methods.chats.GetChatAds(state)) + + def set_action_chat(self, object_guid: str, action: str): + ''' + alloweds: ["Mute", "Unmute"] + result = client.set_action_chat('object_guid', 'Mute') + print(result) + ''' + return self(methods.chats.SetActionChat(object_guid, action)) + + def get_chats_updates(self, state: int = None): + return self(methods.chats.GetChatsUpdates(state)) + + def send_chat_activity(self, object_guid: str, activity: str = None): + return self(methods.chats.SendChatActivity(object_guid, activity)) + + def delete_chat_history(self, object_guid: str): + return self(methods.chats.DeleteChatHistory(object_guid)) + + def search_chat_messages(self, object_guid: str, search_text: str, type: str = 'Hashtag'): + return self(methods.chats.SearchChatMessages(object_guid, search_text, type)) + +# ---------------- Extras Methods ---------------- + + def search_global_objects(self, search_text: str): + return self(methods.extras.SearchGlobalObjects(search_text)) + + def get_abs_objects(self, objects_guids: list): + return self(methods.extras.GetAbsObjects(objects_guids)) + + def get_object_by_username(self, username: str): + return self(methods.extras.GetObjectByUsername(username.replace('@', ''))) + + def get_link_from_app_url(self, app_url: str): + return self(methods.extras.GetLinkFromAppUrl(app_url)) + + def create_voice_call(self, object_guid: str): + if object_guid.startswith('c'): + return self(methods.channels.CreateChannelVoiceChat(object_guid)) + elif object_guid.startswith('g'): + return self(methods.groups.CreateGroupVoiceChat(object_guid)) + else: + print('Invalid Object Guid') + return False + + def set_voice_chat_setting(self, object_guid: str, voice_chat_id: str, title: str = None): + if object_guid.startswith('c'): + return self(methods.channels.SetChannelVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) + elif object_guid.startswith('g'): + return self(methods.groups.SetGroupVoiceChatSetting(object_guid, voice_chat_id, title, ['title'])) + else: + print('Invalid Object Guid') + return False + +# ---------------- Groups Methods ---------------- + + def add_group(self, title: str, member_guids: list): + return self(methods.groups.AddGroup(title, member_guids)) + + def join_group(self, link: str): + return self(methods.groups.JoinGroup(link)) + + def leave_group(self, group_guid: str): + return self(methods.groups.LeaveGroup(group_guid)) + + def remove_group(self, group_guid: str): + return self(methods.groups.RemoveGroup(group_guid)) + + def get_group_info(self, group_guid: str): + return self(methods.groups.GetGroupInfo(group_guid)) + + def get_group_link(self, group_guid: str): + return self(methods.groups.GetGroupLink(group_guid)) + + def set_group_link(self, group_guid: str): + return self(methods.groups.SetGroupLink(group_guid)) + + def edit_group_info(self, + group_guid: str, + title: str = None, + description: str = None, + chat_history_for_new_members: str = None, + ): + updated_parameters = [] + + if title: + updated_parameters.append('title') + if description: + updated_parameters.append('description') + if chat_history_for_new_members: + updated_parameters.append('chat_history_for_new_members') + + return self(methods.groups.EditGroupInfo( + group_guid, updated_parameters, title, description, chat_history_for_new_members)) + + def set_group_admin(self, + group_guid: str, + member_guid: str, + access_list: list, + action: str = 'SetAdmin', + ): + return self(methods.groups.SetGroupAdmin(group_guid, member_guid, access_list, action)) + + def ban_group_member(self, group_guid: str, member_guid: str): + return self(methods.groups.BanGroupMember(group_guid, member_guid, 'Set')) + + def unban_group_member(self, group_guid: str, member_guid: str): + return self(methods.groups.BanGroupMember(group_guid, member_guid, 'Unset')) + + def add_group_members(self, group_guid: str, member_guids: list): + return self(methods.groups.AddGroupMembers(group_guid, member_guids)) + + def get_group_all_members(self, group_guid: str, search_text: str = None, start_id: int = None): + return self(methods.groups.GetGroupAllMembers(group_guid, search_text, start_id)) + + def get_group_admin_members(self, group_guid: str, start_id: int = None): + return self(methods.groups.GetGroupAdminMembers(group_guid, start_id)) + + def get_group_mention_list(self, group_guid: str, search_mention: str = None): + return self(methods.groups.GetGroupMentionList(group_guid, search_mention)) + + def get_group_default_access(self, group_guid: str): + return self(methods.groups.GetGroupDefaultAccess(group_guid)) + + def set_group_default_access(self, group_guid: str, access_list: list): + return self(methods.groups.SetGroupDefaultAccess(group_guid, access_list)) + + def group_preview_by_join_link(self, group_link: str): + return self(methods.groups.GroupPreviewByJoinLink(group_link)) + + def delete_no_access_group_chat(self, group_guid: str): + return self(methods.groups.DeleteNoAccessGroupChat(group_guid)) + + def get_group_admin_access_list(self, group_guid: str, member_guid: str): + return self(methods.groups.GetGroupAdminAccessList(group_guid, member_guid)) + + def set_group_timer(self, group_guid: str, time: int): + return self(methods.groups.EditGroupInfo(group_guid, slow_mode=time, updated_parameters=['slow_mode'])) + +# ---------------- Messages Methods ---------------- + + def custom_send_message(self, + object_guid: str, + message=None, + reply_to_message_id: str = None, + file: bytes = None, + file_inline: dict = None, + *args, **kwargs + ) -> dict: + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if file: + file = self.upload(file, *args, **kwargs) + for key, value in file_inline.items(): + file[key] = value + + return self( + methods.messages.SendMessage( + object_guid, + message=message, + file_inline=file, + reply_to_message_id=reply_to_message_id)) + + def send_message(self, + object_guid: str, + message=None, + reply_to_message_id: str = None, + file_inline=None, + type: str = methods.messages.File, + thumb: bool = True, *args, **kwargs + ): + """_send message_ + + Args: + object_guid (str): + _object guid_ + + message (Any, optional): + _message or cation or sticker_ . Defaults to None. + + reply_to_message_id (str, optional): + _reply to message id_. Defaults to None. + + file_inline (typing.Union[pathlib.Path, bytes], optional): + _file_. Defaults to None. + + type (str, optional): + _file type_. Defaults to methods.messages.File.( + methods.messages.Gif, + methods.messages.Image, + methods.messages.Voice, + methods.messages.Music, + methods.messages.Video + ) + + thumb (bool, optional): + if value is "True", + the lib will try to build the thumb ( require cv2 ) + if value is thumbnail.Thumbnail, to set custom + Defaults to True. + """ + + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if file_inline is not None: + if not isinstance(file_inline, Struct): + if isinstance(file_inline, str): + with open(file_inline, 'rb') as file: + kwargs['file_name'] = kwargs.get( + 'file_name', os.path.basename(file_inline)) + file_inline = file.read() + + if thumb is True: + if type == methods.messages.Image: + thumb = thumbnail.MakeThumbnail(file_inline) + + elif type in [methods.messages.Gif, methods.messages.Video]: + thumb = thumbnail.MakeThumbnail.from_video(file_inline) + + if thumb.image is None: + type = methods.messages.File + thumb = None + + # the problem will be fixed in the next version #debug + # to avoid getting InputError + # values are not checked in Rubika (optional) + file_inline = self.upload(file_inline, *args, **kwargs) + file_inline['type'] = type + file_inline['time'] = kwargs.get('time', 1) + file_inline['width'] = kwargs.get('width', 200) + file_inline['height'] = kwargs.get('height', 200) + file_inline['music_performer'] = kwargs.get('performer', '') + + if isinstance(thumb, thumbnail.Thumbnail): + file_inline['time'] = thumb.seconds + file_inline['width'] = thumb.width + file_inline['height'] = thumb.height + file_inline['thumb_inline'] = thumb.to_base64() + + return self( + methods.messages.SendMessage( + object_guid, + message=message, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def send_photo(self, + object_guid: str, + photo: bytes, + caption: str = None, + file_name: str = None, + width: int = None, + height: int = None, + thumb_inline: str = None, + reply_to_message_id: str = None, + *args, + **kwargs, + ): + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if isinstance(photo, str): + with open(photo, 'rb') as file: + file_name = os.path.basename(photo) + kwargs['file_name'] = kwargs.get('file_name', file_name) + photo = file.read() + + else: + kwargs['file_name'] = kwargs.get('file_name', file_name) + + file_inline = self.upload(photo, *args, **kwargs) + file_inline['type'] = 'Image' + thumb = thumbnail.MakeThumbnail(photo) + + if isinstance(thumb, thumbnail.Thumbnail): + file_inline['width'] = thumb.width + file_inline['height'] = thumb.height + file_inline['thumb_inline'] = thumb.to_base64() + else: + file_inline['width'] = width + file_inline['height'] = height + file_inline['thumb_inline'] = thumb_inline + + return self( + methods.messages.SendMessage( + object_guid, + message=caption, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def send_file(self, + object_guid: str, + file: bytes, + caption: str = None, + file_name: str = None, + reply_to_message_id: str = None, + *args, + **kwargs, + ): + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if isinstance(file, str): + with open(file, 'rb') as ofile: + file_name = os.path.basename(file) + kwargs['file_name'] = kwargs.get('file_name', file_name) + file = ofile.read() + else: + kwargs['file_name'] = kwargs.get('file_name', file_name) + + file_inline = self.upload(file, *args, **kwargs) + file_inline['type'] = 'File' + + return self( + methods.messages.SendMessage( + object_guid, + message=caption, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def send_gif(self, + object_guid: str, + gif: bytes, + caption: str = None, + file_name: str = None, + thumb_inline: str = None, + time: str = None, + width: int = None, + height: int = None, + reply_to_message_id: str = None, + *args, + **kwargs, + ): + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if isinstance(gif, str): + with open(gif, 'rb') as file: + file_name = os.path.basename(gif) + kwargs['file_name'] = kwargs.get('file_name', file_name) + file = file.read() + else: + kwargs['file_name'] = kwargs.get('file_name', file_name) + + file_inline = self.upload(gif, *args, **kwargs) + file_inline['type'] = 'Gif' + thumb = thumbnail.MakeThumbnail.from_video(gif) + + if isinstance(thumb, thumbnail.Thumbnail): + file_inline['time'] = thumb.seconds + file_inline['width'] = thumb.width + file_inline['height'] = thumb.height + file_inline['thumb_inline'] = thumb.to_base64() + else: + file_inline['time'] = time + file_inline['width'] = width + file_inline['height'] = height + file_inline['thumb_inline'] = thumb_inline + + return self( + methods.messages.SendMessage( + object_guid, + message=caption, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def send_video(self, + object_guid: str, + video: bytes, + caption: str = None, + file_name: str = None, + thumb_inline: str = None, + time: str = None, + width: int = None, + height: int = None, + reply_to_message_id: str = None, + *args, + **kwargs, + ): + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if isinstance(video, str): + with open(video, 'rb') as file: + file_name = os.path.basename(video) + kwargs['file_name'] = kwargs.get('file_name', file_name) + video = file.read() + else: + kwargs['file_name'] = kwargs.get('file_name', file_name) + + file_inline = self.upload(video, *args, **kwargs) + file_inline['type'] = 'Video' + thumb = thumbnail.MakeThumbnail.from_video(video) + + if isinstance(thumb, thumbnail.Thumbnail): + file_inline['time'] = thumb.seconds + file_inline['width'] = thumb.width + file_inline['height'] = thumb.height + file_inline['thumb_inline'] = thumb.to_base64() + else: + file_inline['time'] = time + file_inline['width'] = width + file_inline['height'] = height + file_inline['thumb_inline'] = thumb_inline + + return self( + methods.messages.SendMessage( + object_guid, + message=caption, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def send_music(self, + object_guid: str, + music: bytes, + caption: str = None, + file_name: str = None, + time: str = None, + performer: str = None, + reply_to_message_id: str = None, + *args, + **kwargs, + ): + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if isinstance(music, bytes): + kwargs['file_name'] = kwargs.get('file_name', file_name) + else: + with open(music, 'rb') as file: + kwargs['file_name'] = kwargs.get('file_name', os.path.basename(music)) + music = file.read() + + file_inline = self.upload(music, *args, **kwargs) + file_inline['type'] = 'Music' + file_inline['auto_play'] = False + file_inline['music_performer'] = kwargs.get('performer', performer or '') + file = BytesIO() + file.write(music) + file.seek(0) + file_inline['time'] = time or MP3(file).info.length * 1000 + + return self( + methods.messages.SendMessage( + object_guid, + message=caption, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def send_voice(self, + object_guid: str, + voice: bytes, + caption: str = None, + file_name: str = None, + time: str = None, + reply_to_message_id: str = None, + *args, + **kwargs, + ): + if compile(r'(?i)^(me|self|cloud)$').match(object_guid): + object_guid = self._guid + + if isinstance(voice, str): + with open(voice, 'rb') as file: + file_name = os.path.basename(voice) + kwargs['file_name'] = kwargs.get('file_name', file_name) + file = file.read() + else: + kwargs['file_name'] = kwargs.get('file_name', file_name) + + file_inline = self.upload(voice, *args, **kwargs) + file_inline['type'] = 'Voice' + file_inline['mime'] = 'ogg' + file_inline['auto_play'] = False + file = BytesIO() + file.write(voice) + file.seek(0) + file_inline['time'] = str(time or MP3(file).info.length * 1000) + + return self( + methods.messages.SendMessage( + object_guid, + message=caption, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id)) + + def edit_message(self, object_guid: str, message_id: str, text: str): + return self(methods.messages.EditMessage(object_guid, message_id, text)) + + def delete_messages(self, object_guid: str, message_ids: list, type: str = 'Global'): + return self(methods.messages.DeleteMessages(object_guid, message_ids, type)) + + def request_send_file(self, file_name: str, size: int, mime: str): + return self(methods.messages.RequestSendFile(file_name, size, mime)) + + def forward_messages(self, from_object_guid: str, to_object_guid: str, message_ids: list): + return self(methods.messages.ForwardMessages(from_object_guid, to_object_guid, message_ids)) + + def create_poll(self, + object_guid: str, + question: str, + options: list, + type: str = 'Regular', + is_anonymous: bool = True, + allows_multiple_answers: bool = False, + correct_option_index: int = 0, + explanation: str = None, + reply_to_message_id: int = 0, + ): + if type == 'Regular': + return self(methods.messages.CreatePoll( + object_guid=object_guid, + question=question, + options=options, + allows_multiple_answers=allows_multiple_answers, + is_anonymous=is_anonymous, + reply_to_message_id=reply_to_message_id, + type=type, + )) + else: + return self(methods.messages.CreatePoll( + object_guid=object_guid, + question=question, + options=options, + allows_multiple_answers=allows_multiple_answers, + is_anonymous=is_anonymous, + reply_to_message_id=reply_to_message_id, + correct_option_index=correct_option_index, + explanation=explanation, + type=type, + )) + + def vote_poll(self, poll_id: str, selection_index: int): + return self(methods.messages.VotePoll(poll_id, selection_index)) + + def get_poll_status(self, poll_id: str): + return self(methods.messages.GetPollStatus(poll_id)) + + def get_poll_option_voters(self, poll_id: str, selection_index: int, start_id: int = None): + return self(methods.messages.GetPollOptionVoters(poll_id, selection_index, start_id)) + + def set_pin_message(self, object_guid: str, message_id: str, action: str = 'Pin'): + return self(methods.messages.SetPinMessage(object_guid, message_id, action)) + + def unset_pin_message(self, object_guid: str, message_id: str, action: str = 'Unpin'): + return self(methods.messages.SetPinMessage(object_guid, message_id, action)) + + def get_messages_updates(self, object_guid: str, state: int = None): + return self(methods.messages.GetMessagesUpdates(object_guid, state)) + + def search_global_messages(self, search_text: str, type: str = 'Text'): + return self(methods.messages.SearchGlobalMessages(search_text, type)) + + def click_message_url(self, object_guid: str, message_id: str, link_url: str): + return self(methods.messages.ClickMessageUrl(object_guid, message_id, link_url)) + + def get_messages_by_ID(self, object_guid: str, message_ids: list): + return self(methods.messages.GetMessagesByID(object_guid, message_ids)) + + def get_messages(self, object_guid: str, min_id: int, max_id: int, sort: str = 'FromMin', limit: int = 10): + return self(methods.messages.GetMessages(object_guid, min_id, max_id, sort, limit)) + + def get_messages_interval(self, object_guid: str, middle_message_id: str): + return self(methods.messages.GetMessagesInterval(object_guid, middle_message_id)) + + def get_message_url(self, object_guid: str, message_id: int): + if type(message_id) == str: + message_id = int(message_id) + return self(methods.messages.GetMessageShareUrl(object_guid, message_id)) + +# ---------------- Channels Methods ---------------- + + def add_channel(self, title: str, description: str = None): + return self(methods.channels.AddChannel(title, description)) + + def remove_channel(self, channel_guid: str): + return self(methods.channels.RemoveChannel(channel_guid)) + + def get_channel_info(self, channel_guid: str): + return self(methods.channels.GetChannelInfo(channel_guid)) + + def edit_channel_info(self, + channel_guid: str, + title: str = None, + description: str = None, + channel_type: str = None, + sign_messages: str = None, + ): + updated_parameters = [] + + if title: + updated_parameters.append('title') + if description: + updated_parameters.append('description') + if channel_type: + updated_parameters.append('channel_type') + if sign_messages: + updated_parameters.append('sign_messages') + + return self(methods.channels.EditChannelInfo( + channel_guid, updated_parameters, title, description, channel_type, sign_messages)) + + def join_channel(self, channel_guid: str): + return self(methods.channels.JoinChannelAction(channel_guid, 'Join')) + + def leave_channel(self, channel_guid: str): + return self(methods.channels.JoinChannelAction(channel_guid, 'Remove')) + + def archive_channel(self, channel_guid: str): + return self(methods.channels.JoinChannelAction(channel_guid, 'Archive')) + + def join_channel_by_link(self, link: str): + return self(methods.channels.JoinChannelByLink(link)) + + def add_channel_members(self, channel_guid: str, member_guids: list): + return self(methods.channels.AddChannelMembers(channel_guid, member_guids)) + + def ban_channel_member(self, channel_guid: str, member_guid: str): + return self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Set')) + + def unban_channel_member(self, channel_guid: str, member_guid: str): + return self(methods.channels.BanChannelMember(channel_guid, member_guid, 'Unset')) + + def check_channel_username(self, username: str): + return self(methods.channels.CheckChannelUsername(username)) + + def channel_preview_by_join_link(self, link: str): + return self(methods.channels.ChannelPreviewByJoinLink(link)) + + def get_channel_all_members(self, channel_guid: str, search_text: str = None, start_id: int = None): + return self(methods.channels.GetChannelAllMembers(channel_guid, search_text, start_id)) + + def check_join(self, channel_guid: str, username: str) -> bool: + result = self.get_channel_all_members(channel_guid, username.replace('@', '')) + in_chat_members: dict = result['in_chat_members'] + for member in in_chat_members: + if username in member.values(): + return True + else: + continue + else: + return False + + def get_channel_admin_members(self, channel_guid: str, start_id: int = None): + return self(methods.channels.GetChannelAdminMembers(channel_guid, start_id)) + + def update_channel_username(self, channel_guid: str, username: str): + return self(methods.channels.UpdateChannelUsername(channel_guid, username)) + + def get_channel_link(self, channel_guid: str): + return self(methods.channels.GetChannelLink(channel_guid)) + + def set_channel_link(self, channel_guid: str): + return self(methods.channels.SetChannelLink(channel_guid)) + + def get_channel_admin_access_list(self, channel_guid: str, member_guid: str): + return self(methods.channels.GetChannelAdminAccessList(channel_guid, member_guid)) + +# ---------------- Contacts Methods ---------------- + + def delete_contact(self, user_guid: str): + return self(methods.contacts.DeleteContact(user_guid)) + + def add_address_book(self, phone: str, first_name: str, last_name: str = ''): + return self(methods.contacts.AddAddressBook(phone, first_name, last_name)) + + def get_contacts_updates(self, state: int = None): + return self(methods.contacts.GetContactsUpdates(state)) + + def get_contacts(self, start_id: int = None): + return self(methods.contacts.GetContacts(start_id)) + +# ---------------- Settings Methods ---------------- + + def set_setting(self, + show_my_last_online: str = None, + show_my_phone_number: str = None, + show_my_profile_photo: str = None, + link_forward_message: str = None, + can_join_chat_by: str = None + ): + updated_parameters = [] + + if show_my_last_online: + updated_parameters.append('show_my_last_online') + if show_my_phone_number: + updated_parameters.append('show_my_phone_number') + if show_my_profile_photo: + updated_parameters.append('show_my_profile_photo') + if link_forward_message: + updated_parameters.append('link_forward_message') + if can_join_chat_by: + updated_parameters.append('can_join_chat_by') + + return self(methods.settings.SetSetting( + updated_parameters, + show_my_last_online, + show_my_phone_number, + show_my_profile_photo, + link_forward_message, + can_join_chat_by)) + + def add_folder(self, + include_chat_types: list = None, + exclude_chat_types: list = None, + include_object_guids: list = None, + exclude_object_guids: list = None + ): + return self(methods.settings.AddFolder( + include_chat_types, + exclude_chat_types, + include_object_guids, + exclude_object_guids)) + + def get_folders(self, last_state: int): + return self(methods.settings.GetFolders(last_state)) + + def edit_folder(self, + include_chat_types: list = None, + exclude_chat_types: list = None, + include_object_guids: list = None, + exclude_object_guids: list = None + ): + updated_parameters = [] + + if include_chat_types: + updated_parameters.append('include_chat_types') + if exclude_chat_types: + updated_parameters.append('exclude_chat_types') + if include_object_guids: + updated_parameters.append('include_object_guids') + if exclude_object_guids: + updated_parameters.append('exclude_object_guids') + + return self(methods.settings.EditFolder( + updated_parameters, + include_chat_types, + exclude_chat_types, + include_object_guids, + exclude_object_guids)) + + def delete_folder(self, folder_id: str): + return self(methods.settings.DeleteFolder(folder_id)) + + def update_profile(self, first_name: str = None, last_name: str = None, bio: str = None): + updated_parameters = [] + + if first_name: + updated_parameters.append('first_name') + if last_name: + updated_parameters.append('last_name') + if bio: + updated_parameters.append('bio') + + return self(methods.settings.UpdateProfile(updated_parameters, first_name, last_name, bio)) + + def update_username(self, username: str): + return self(methods.settings.UpdateUsername(username)) + + def get_two_passcode_status(self): + return self(methods.settings.GetTwoPasscodeStatus()) + + def get_suggested_folders(self): + return self(methods.settings.GetSuggestedFolders()) + + def get_privacy_setting(self): + return self(methods.settings.GetPrivacySetting()) + + def get_blocked_users(self): + return self(methods.settings.GetBlockedUsers()) + + def get_my_sessions(self): + return self(methods.settings.GetMySessions()) + + def terminate_session(self, session_key: str): + return self(methods.settings.TerminateSession(session_key)) + + def setup_two_step_verification(self, password: str, hint: str, recovery_email: str): + return self(methods.settings.SetupTwoStepVerification(password, hint, recovery_email)) + +# ---------------- Stickers Methods ---------------- + + def get_my_sticker_sets(self): + return self(methods.stickers.GetMyStickerSets()) + + def search_stickers(self, search_text: str = '', start_id: int = None): + return self(methods.stickers.SearchStickers(search_text, start_id)) + + def get_sticker_set_by_ID(self, sticker_set_id: str): + return self(methods.stickers.GetStickerSetByID(sticker_set_id)) + + def action_on_sticker_set(self, sticker_set_id: str, action: str = 'Add'): + return self(methods.stickers.ActionOnStickerSet(sticker_set_id, action)) + + def get_stickers_by_emoji(self, emoji: str, suggest_by: str = 'Add'): + return self(methods.stickers.GetStickersByEmoji(emoji, suggest_by)) + + def get_stickers_by_set_IDs(self, sticker_set_ids: list): + return self(methods.stickers.GetStickersBySetIDs(sticker_set_ids)) + + def get_trend_sticker_sets(self, start_id: int = None): + return self(methods.stickers.GetTrendStickerSets(start_id)) \ No newline at end of file diff --git a/rubpy/sync/crypto/__init__.py b/rubpy/sync/crypto/__init__.py new file mode 100644 index 0000000..9aeadc9 --- /dev/null +++ b/rubpy/sync/crypto/__init__.py @@ -0,0 +1 @@ +from .crypto import Crypto \ No newline at end of file diff --git a/rubpy/sync/crypto/crypto.py b/rubpy/sync/crypto/crypto.py new file mode 100644 index 0000000..e3e7d5f --- /dev/null +++ b/rubpy/sync/crypto/crypto.py @@ -0,0 +1,84 @@ +import re +import json +import base64 +import string +import secrets +from json import JSONDecoder +from Crypto.Cipher import AES +from Crypto.Hash import SHA256 +from Crypto.Signature import pkcs1_15 +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP +from string import ascii_lowercase, ascii_uppercase + +class Crypto(object): + AES_IV = b'\x00' * 16 + + def decode_auth(auth: str) -> str: + result_list, digits = [], '0123456789' + translation_table_lower = str.maketrans(ascii_lowercase, ''.join([chr(((32 - (ord(c) - 97)) % 26) + 97) for c in ascii_lowercase])) + translation_table_upper = str.maketrans(ascii_uppercase, ''.join([chr(((29 - (ord(c) - 65)) % 26) + 65) for c in ascii_uppercase])) + + for char in auth: + if char in ascii_lowercase: + result_list.append(char.translate(translation_table_lower)) + elif char in ascii_uppercase: + result_list.append(char.translate(translation_table_upper)) + elif char in digits: + result_list.append(chr(((13 - (ord(char) - 48)) % 10) + 48)) + else: + result_list.append(char) + + return ''.join(result_list) + + @classmethod + def passphrase(cls, auth): + if len(auth) != 32: + raise ValueError('auth length should be 32 digits') + + result_list = [] + chunks = re.findall(r'\S{8}', auth) + for character in (chunks[2] + chunks[0] + chunks[3] + chunks[1]): + result_list.append(chr(((ord(character) - 97 + 9) % 26) + 97)) + return ''.join(result_list) + + @classmethod + def secret(cls, length): + return ''.join(secrets.choice(string.ascii_lowercase) + for _ in range(length)) + + @classmethod + def decrypt(cls, data, key): + decoder = JSONDecoder() + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, cls.AES_IV) + decoded_data = base64.b64decode(data) + result, _ = decoder.raw_decode(cipher.decrypt(decoded_data).decode('utf-8')) + return result + + @classmethod + def encrypt(cls, data, key): + if not isinstance(data, str): + data = json.dumps(data, default=lambda x: str(x)) + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, cls.AES_IV) + length = 16 - (len(data) % 16) + data += chr(length) * length + return ( + base64.b64encode(cipher.encrypt(data.encode('utf-8'))) + .decode('utf-8') + ) + + def sign(private_key: str, data: str) -> str: + key = RSA.import_key(private_key.encode('utf-8')) + signature = pkcs1_15.new(key).sign( + SHA256.new(data.encode('utf-8'))) + return base64.b64encode(signature).decode('utf-8') + + def create_keys() -> tuple: + keys = RSA.generate(1024) + public_key = Crypto.decode_auth(base64.b64encode(keys.publickey().export_key()).decode('utf-8')) + private_key = keys.export_key().decode('utf-8') + return public_key, private_key + + def decrypt_RSA_OAEP(private_key: str, data: str): + key = RSA.import_key(private_key.encode('utf-8')) + return PKCS1_OAEP.new(key).decrypt(base64.b64decode(data)).decode('utf-8') \ No newline at end of file diff --git a/rubpy/sync/gadgets/__init__.py b/rubpy/sync/gadgets/__init__.py new file mode 100644 index 0000000..edf6610 --- /dev/null +++ b/rubpy/sync/gadgets/__init__.py @@ -0,0 +1,4 @@ +from . import methods +from . import thumbnail +from . import exceptions +from .classino import Classino diff --git a/rubpy/sync/gadgets/classino.py b/rubpy/sync/gadgets/classino.py new file mode 100644 index 0000000..4aefb1f --- /dev/null +++ b/rubpy/sync/gadgets/classino.py @@ -0,0 +1,27 @@ +import difflib +import inspect +import warnings + +class Classino: + + @classmethod + def create(cls, name, __base, authorise: list = [], exception: bool = True, *args, **kwargs): + result = None + if name in authorise: + result = name + + else: + proposal = difflib.get_close_matches(name, authorise, n=1) + if proposal: + result = proposal[0] + caller = inspect.getframeinfo(inspect.stack()[2][0]) + warnings.warn( + f'{caller.filename}:{caller.lineno}: do you mean' + f' "{name}", "{result}"? correct it') + + if result is not None or not exception: + if result is None: + result = name + return type(result, __base, {'__name__': result, **kwargs}) + + print(f'module has no attribute ({name})') \ No newline at end of file diff --git a/rubpy/sync/gadgets/exceptions.py b/rubpy/sync/gadgets/exceptions.py new file mode 100644 index 0000000..3e8654c --- /dev/null +++ b/rubpy/sync/gadgets/exceptions.py @@ -0,0 +1,141 @@ +import sys + +class ClientError(Exception): + pass + +class SocksError(ClientError): + pass + +class StopHandler(ClientError): + pass + +class CancelledError(ClientError): + pass + + +class UnknownAuthMethod(SocksError): + pass + + +class InvalidServerReply(SocksError): + pass + + +class SocksConnectionError(SocksError): + pass + + +class InvalidServerVersion(SocksError): + pass + + +class NoAcceptableAuthMethods(SocksError): + pass + + +class LoginAuthenticationFailed(SocksError): + pass + + +class RequestError(ClientError): + def __init__(self, message, request=None): + self.message = str(message) + self.request = request + + +class CodeIsUsed(RequestError): + pass + + +class TooRequests(RequestError): + pass + + +class InvalidAuth(RequestError): + pass + + +class ServerError(RequestError): + pass + + +class UrlNotFound(RequestError): + pass + + +class ErrorAction(RequestError): + pass + + +class ErrorIgnore(RequestError): + pass + + +class ErrorGeneric(RequestError): + pass + + +class NoConnection(RequestError): + pass + + +class InvalidInput(RequestError): + pass + + +class Undeliverable(RequestError): + pass + + +class NotRegistered(RequestError): + pass + + +class CodeIsExpired(RequestError): + pass + + +class InvalidMethod(RequestError): + pass + + +class UsernameExist(RequestError): + pass + + +class NotRegistrred(RequestError): + pass + + +class ErrorTryAgain(RequestError): + pass + + +class ErrorMessageTry(RequestError): + pass + + +class InternalProblem(RequestError): + pass + + +class ErrorMessageIgn(RequestError): + pass + + +class NotSupportedApiVersion(RequestError): + pass + + +class ExcetionsHandler: + def __init__(self, name) -> None: + self.name = name + + def __getattr__(self, name): + name = ''.join([chunk.title() for chunk in name.split('_')]) + return globals().get(name, ClientError) + + def __call__(self, name, *args, **kwargs): + return getattr(self, name) + +sys.modules[__name__] = ExcetionsHandler(__name__) \ No newline at end of file diff --git a/rubpy/sync/gadgets/grouping.py b/rubpy/sync/gadgets/grouping.py new file mode 100644 index 0000000..edbeb72 --- /dev/null +++ b/rubpy/sync/gadgets/grouping.py @@ -0,0 +1,689 @@ +grouping = { + "users": { + "Values": ["Block", "Unblock"], + "GetUserInfo": { + "params": { + "user_guid": {"types": "str"} + } + }, + "SetBlockUser": { + "params": { + "user_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Block", "Unblock"], "default": "Block"} + } + }, + "DeleteUserChat": { + "params": { + "user_guid": {"types": "str"}, + "last_deleted_message_id": {"types": ["str", "int"], "func": "to_string"} + } + }, + "CheckUserUsername": { + "params": { + "username": {"types": "str"} + } + } + }, + "chats": { + "Values": ["Mute", "Unmute", "Typing", "Uploading", "Recording", "Text", "Hashtag"], + "UploadAvatar": { + "params": { + "object_guid": {"types": "str"}, + "main_file_id": {"types": "str"}, + "thumbnail_file_id": {"types": "str"} + } + }, + "DeleteAvatar": { + "params": { + "object_guid": {"types": "str"}, + "avatar_id": {"types": "str"} + } + }, + "GetAvatars": { + "params": { + "object_guid": {"types": "str"} + } + }, + "GetChats": { + "params": { + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "SeenChats": { + "params": { + "seen_list": {"types": "dict", "func": "to_array"} + } + }, + "GetChatAds": { + "params": { + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "SetActionChat": { + "params": { + "object_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Mute", "Unmute"], "default": "Mute"} + } + }, + "GetChatsUpdates": { + "params": { + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "SendChatActivity": { + "params": { + "object_guid": {"types": "str"}, + "activity": {"types": ["str", "optional"], "alloweds": ["Typing", "Uploading", "Recording"], "default": "Typing"} + } + }, + "DeleteChatHistory": { + "params": { + "object_guid": {"types": "str"} + } + }, + "SearchChatMessages": { + "params": { + "object_guid": {"types": "str"}, + "search_text": {"types": "str"}, + "type": {"types": ["str", "optional"], "alloweds": ["Text", "Hashtag"], "default": "Hashtag"} + } + } + + }, + "extras": { + "Values": [], + "SearchGlobalObjects": { + "params": { + "search_text": {"types": "str"} + } + }, + "GetAbsObjects": { + "params": { + "objects_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "GetObjectByUsername": { + "params": { + "username": {"types": "str"} + } + }, + "GetLinkFromAppUrl": { + "params": { + "app_url": {"types": "str"} + } + } + }, + "groups": { + "Values": ["Set", "Unset", "SetAdmin", "UnsetAdmin", "Hidden", "Visible", "AddMember", "ViewAdmins", "ViewMembers", "SendMessages", "SetAdmin", "BanMember", "ChangeInfo", "PinMessages", "SetJoinLink", "SetMemberAccess", "DeleteGlobalAllMessages"], + "AddGroup": { + "params": { + "title": {"types": "str"}, + "member_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "JoinGroup": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "LeaveGroup": { + "params": { + "group_guid": {"types": "str"} + } + }, + "RemoveGroup": { + "params": { + "group_guid": {"types": "str"} + } + }, + "GetGroupInfo": { + "params": { + "group_guid": {"types": "str"} + } + }, + "GetGroupLink": { + "params": { + "group_guid": {"types": "str"} + } + }, + "SetGroupLink": { + "params": { + "group_guid": {"types": "str"} + } + }, + "EditGroupInfo": { + "updated_parameters": True, + "params": { + "group_guid": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title", "description", "slow_mode", "chat_history_for_new_members"]}, + "title": {"types": ["str", "optional"]}, + "description": {"types": ["str", "optional"]}, + "slow_mode": {"types": ["int", "str", "optional"]}, + "chat_history_for_new_members": {"types": ["str", "optional"], "alloweds": ["Hidden", "Visible"]}, + } + }, + "SetGroupAdmin": { + "params": { + "group_guid": {"types": "str"}, + "member_guid": {"types": "str"}, + "access_list": {"types": ["str", "list"], "alloweds": ["SetAdmin", "BanMember", "ChangeInfo", "PinMessages", "SetJoinLink", "SetMemberAccess", "DeleteGlobalAllMessages"], "func": "to_array"}, + "action": {"types": ["str", "optional"], "alloweds": ["SetAdmin", "UnsetAdmin"], "default": "SetAdmin"} + } + }, + "BanGroupMember": { + "params": { + "group_guid": {"types": "str"}, + "member_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Set", "Unset"], "default": "Set"} + } + }, + "AddGroupMembers": { + "params": { + "group_guid": {"types": "str"}, + "member_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "GetGroupAllMembers": { + "params": { + "group_guid": {"types": "str"}, + "search_text": {"types": ["str", "optional"], "default": ""}, + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "GetGroupAdminMembers": { + "params": { + "group_guid": {"types": "str"}, + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "GetGroupMentionList": { + "params": { + "group_guid": {"types": "str"}, + "search_mention": {"types": ["str", "optional"]} + } + }, + "GetGroupDefaultAccess": { + "params": { + "group_guid": {"types": "str"} + + } + }, + "SetGroupDefaultAccess": { + "params": { + "group_guid": {"types": "str"}, + "access_list": {"types": ["str", "list"], "alloweds": ["AddMember", "ViewAdmins", "ViewMembers", "SendMessages"]} + } + }, + "GroupPreviewByJoinLink": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "DeleteNoAccessGroupChat": { + "params": { + "group_guid": {"types": "str"} + } + }, + "GetGroupAdminAccessList": { + "params": { + "group_guid": {"types": "str"}, + "member_guid": {"types": "str"} + } + }, + "CreateGroupVoiceChat": { + "params": { + "group_guid": {"types": "str"}, + } + }, + "SetGroupVoiceChatSetting": { + "updated_parameters": True, + "params": { + "group_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "title": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title"]} + } + } + }, + "messages": { + "Values": [ + "Pin","Unpin", "Text", "Gif", "File", "Image", "Voice", "Music", "Video", "FileInline", "Quiz", "Regular", "FromMin", "FromMax", "Local", "Global"], + "SendMessage": { + "params": { + "object_guid": {"types": "str"}, + "message": { + "types": ["dict", "Struct", "str", "optional"], + "ifs": { + "str": {"func": "to_metadata", "unpack": True}, + "otherwise": {"cname": "sticker", "func": "to_array"} + } + }, + "reply_to_message_id": { + "types": ["str", "int", "optional"], + "func": "to_string" + }, + "file_inline": { + "types": ["Struct", "dict", "optional"], + "func": "to_array" + }, + "type": {"types": ["str", "optional"], "alloweds": ["FileInlineCaption", "FileInline"], "default": "FileInline"}, + "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} + } + }, + "EditMessage": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "text": {"types": "str", "func": "to_metadata", "unpack": True} + } + }, + "DeleteMessages": { + "params": { + "object_guid": {"types": "str"}, + "message_ids": {"types": ["int", "str", "list"], "func": "to_array"}, + "type": {"types": ["str", "optional"], "alloweds": ["Local", "Global"], "default": "Global"} + } + }, + "RequestSendFile": { + "params": { + "file_name": {"types": "str"}, + "size": {"types": ["str", "int", "float"], "func": "to_number"}, + "mime": {"types": ["str", "optional"], "heirship": ["file_name"], "func": "get_format"} + } + }, + "ForwardMessages": { + "params": { + "from_object_guid": {"types": "str"}, + "to_object_guid": {"types": "str"}, + "message_ids": {"types": ["int", "str", "list"], "func": "to_array"}, + "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} + } + }, + "CreatePoll": { + "params": { + "object_guid": {"types": "str"}, + "question": {"types": "str", }, + "options": {"types": "list", "minimum": 2}, + "type": {"types": ["str", "optional"], "alloweds": ["Quiz", "Regular"], "default": "Regular"}, + "is_anonymous": {"types": ["bool", "optional"]}, + "allows_multiple_answers": {"types": ["bool", "optional"]}, + "correct_option_index": {"types": ["str", "int", "optional"], "func": "to_number"}, + "explanation": {"types": ["str", "optional"]}, + "reply_to_message_id": {"types": ["int", "optional"], "default": 0}, + "rnd": {"types": ["str", "int", "optional"], "default": {"func": "random_number"}, "func": "to_string"} + } + }, + "VotePoll": { + "params": { + "poll_id": {"types": "str"}, + "selection_index": {"types": ["int", "str"], "func": "to_number"} + } + }, + "GetPollStatus": { + "params": { + "poll_id": {"types": "str"} + } + }, + "GetPollOptionVoters": { + "params": { + "poll_id": {"types": "str"}, + "selection_index": {"types": ["int", "str"], "func": "to_number"}, + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "SetPinMessage": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "action": {"types": ["str", "optional"], "alloweds": ["Pin", "Unpin"], "default": "Pin"} + } + }, + "GetMessagesUpdates": { + "params": { + "object_guid": {"types": "str"}, + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "SearchGlobalMessages": { + "params": { + "search_text": {"types": "str"}, + "type": {"types": ["str", "optional"], "alloweds": ["Text"], "default": "Text"} + } + }, + "ClickMessageUrl": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"], "func": "to_string"}, + "link_url": {"types": "str"} + } + }, + "GetMessagesByID": { + "params": { + "object_guid": {"types": "str"}, + "message_ids": {"types": ["int", "str", "list"], "func": "to_array"} + } + }, + "GetMessages": { + "params": { + "object_guid": {"types": "str"}, + "min_id": {"types": ["str", "int"], "func": "to_number"}, + "max_id": {"types": ["str", "int"], "func": "to_number"}, + "sort": {"types": ["str", "optional"], "alloweds": ["FromMin", "FromMax"], "default": "FromMin"}, + "limit": {"types": ["str", "int"], "func": "to_number", "default": 10}, + } + }, + "GetMessagesInterval": { + "params": { + "object_guid": {"types": "str"}, + "middle_message_id": {"types": ["str", "int"], "func": "to_string"}, + } + }, + "GetMessageShareUrl": { + "params": { + "object_guid": {"types": "str"}, + "message_id": {"types": ["str", "int"]}, + } + } + }, + "channels": { + "Values": ["Join", "Remove", "Archive", "Set", "Unset"], + "AddChannel": { + "params": { + "title": {"types": "str"}, + "description": {"types": ["str", "optional"]} + } + }, + "RemoveChannel": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "GetChannelInfo": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "EditChannelInfo": { + "updated_parameters": True, + "params": { + "channel_guid": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title", "description", "channel_type", "sign_messages"]}, + "title": {"types": "str"}, + "description": {"types": ["str", "optional"]}, + "channel_type": {"types": ["str", "optional"], "alloweds": ["Public", "Private"], "default": "Public" }, + "sign_messages": {"types": ["str", "optional"]} + } + }, + "JoinChannelAction": { + "params": { + "channel_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Join", "Remove", "Archive"], "default": "Join"} + } + }, + "JoinChannelByLink": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "AddChannelMembers": { + "params": { + "channel_guid": {"types": "str"}, + "member_guids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "BanChannelMember": { + "params": { + "channel_guid": {"types": "str"}, + "member_guid": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Set", "Unset"], "default": "Set"} + } + }, + "CheckChannelUsername": { + "params": { + "username": {"types": "str"} + } + }, + "ChannelPreviewByJoinLink": { + "params": { + "link": {"types": "str", "cname": "hash_link", "func": "get_hash_link"} + } + }, + "GetChannelAllMembers": { + "params": { + "channel_guid": {"types": "str"}, + "search_text": {"types": ["str", "optional"]}, + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "GetChannelAdminMembers": { + "params": { + "channel_guid": {"types": "str"}, + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "UpdateChannelUsername": { + "params": { + "channel_guid": {"types": "str"}, + "username": {"types": "str"} + } + }, + "GetChannelLink": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "SetChannelLink": { + "params": { + "channel_guid": {"types": "str"} + } + }, + "GetChannelAdminAccessList": { + "params": { + "channel_guid": {"types": "str"}, + "member_guid": {"types": "str"} + } + }, + "CreateChannelVoiceChat": { + "params": { + "object_guid": {"types": "str"}, + } + }, + "SetChannelVoiceChatSetting": { + "updated_parameters": True, + "params": { + "channel_guid": {"types": "str"}, + "voice_chat_id": {"types": "str"}, + "title": {"types": "str"}, + "updated_parameters": {"types": ["list"], "alloweds": ["title"]} + } + } + }, + "contacts": { + "Values": [], + "DeleteContact": { + "params": { + "user_guid": {"types": "str"} + } + }, + "AddAddressBook": { + "params": { + "phone": {"types": "str", "func": "get_phone"}, + "first_name": {"types": "str"}, + "last_name": {"types": ["str", "optional"], "default": ""} + } + }, + "GetContactsUpdates": { + "params": { + "state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "GetContacts": { + "params": { + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + } + }, + "settings": { + "Values": ["Nobody", "Everybody", "MyContacts", "Bots", "Groups", "Contacts", "Channels", "NonConatcts"], + "SetSetting": { + "updated_parameters": True, + "params": { + "updated_parameters": {"types": ["list"], "alloweds": ["show_my_last_online", "show_my_phone_number", "show_my_profile_photo", "link_forward_message", "can_join_chat_by"]}, + "show_my_last_online": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, + "show_my_phone_number": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, + "show_my_profile_photo": {"types": ["str", "optional"], "alloweds": ["Everybody", "MyContacts"]}, + "link_forward_message": {"types": ["str", "optional"], "alloweds": ["Nobody", "Everybody", "MyContacts"]}, + "can_join_chat_by": {"types": ["str", "optional"], "alloweds": ["Everybody", "MyContacts"]} + } + }, + "AddFolder": { + "params": { + "cname": {"types": "str"}, + "include_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "exclude_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "include_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []}, + "exclude_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []} + } + }, + "GetFolders": { + "params": { + "last_state": {"types": ["int", "str"], "defualt": {"func": "timestamp"}, "func": "to_number"} + } + }, + "EditFolder": { + "updated_parameters": True, + "params": { + "updated_parameters": {"types": ["list"], "alloweds": ["include_chat_types", "exclude_chat_types", "include_object_guids", "exclude_object_guids"]}, + "cname": {"types": "str"}, + "include_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "exclude_chat_types": { + "types": ["str", "list", "optional"], + "alloweds": ["Bots", "Groups", "Contacts", "Channels", "NonConatcts"], "func": "to_array", "default": []}, + "include_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []}, + "exclude_object_guids": {"types": ["str", "list", "optional"], "func": "to_array", "default": []} + } + }, + "DeleteFolder": { + "params": { + "folder_id": {"types": "str"} + } + }, + "UpdateProfile": { + "updated_parameters": True, + "params": { + "updated_parameters": {"types": ["list"], "alloweds": ["first_name", "last_name", "bio"]}, + "first_name": {"types": ["str", "optional"]}, + "last_name": {"types": ["str", "optional"]}, + "bio": {"types": ["str", "optional"]} + } + }, + "UpdateUsername": { + "params": { + "username": {"types": "str"} + } + }, + "GetTwoPasscodeStatus": None, + "GetSuggestedFolders": None, + "GetPrivacySetting": None, + "GetBlockedUsers": None, + "GetMySessions": None, + "TerminateSession": { + "params": { + "session_key": {"types": "str"} + } + }, + "SetupTwoStepVerification": { + "params": { + "password": {"types": ["str", "int"], "func": "to_string"}, + "hint": {"types": ["str", "int"], "func": "to_string"}, + "recovery_email": {"types": "str"} + } + } + }, + "stickers": { + "Values": ["All", "Add", "Remove"], + "GetMyStickerSets": None, + "SearchStickers": { + "params": { + "search_text": {"types": ["str", "optional"], "default": ""}, + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + }, + "GetStickerSetByID": { + "params": { + "sticker_set_id": {"types": "str"} + } + }, + "ActionOnStickerSet": { + "params": { + "sticker_set_id": {"types": "str"}, + "action": {"types": ["str", "optional"], "alloweds": ["Add", "Remove"], "default": "Add"} + } + }, + "GetStickersByEmoji": { + "params": { + "emoji": {"types": "str", "cname": "emoji_character"}, + "suggest_by": {"types": ["str", "optional"], "default": "Add"} + } + }, + "GetStickersBySetIDs": { + "params": { + "sticker_set_ids": {"types": ["str", "list"], "func": "to_array"} + } + }, + "GetTrendStickerSets": { + "params": { + "start_id": {"types": ["int", "str", "optional"], "func": "to_number"} + } + } + + }, + "authorisations": { + "Values": ["SMS", "Internal"], + "GetDCs": { + "urls": ["https://getdcmess.iranlms.ir/", "https://getdcmess1.iranlms.ir/", "https://getdcmess2.iranlms.ir/"], + "encrypt": False, + "params": { + "api_version": {"types": ["int", "str"], "func": "to_string", "default": "4"} + } + }, + "SignIn": { + "tmp_session": True, + "params": { + "phone_code": {"types": "str"}, + "phone_number": {"types": "str", "func": "get_phone"}, + "phone_code_hash": {"types": "str"}, + "public_key": {"types": "str"}, + } + }, + "SendCode": { + "tmp_session": True, + "params": { + "phone_number": {"types": "str", "func": "get_phone"}, + "pass_key": {"types": ["str", "optional"], "default": None}, + "send_type": {"types": ["str", "optional"], "alloweds": ["SMS", "Internal"], "default": "SMS"} + } + }, + "RegisterDevice": { + "params": { + "uaer_agent": {"types": "str", "func": "get_browser", "unpack": True}, + "app_version": {"types": "str"}, + "lang_code": {"types": ["str", "optional"], "default": "fa"} + } + }, + "LoginDisableTwoStep": { + "tmp_session": True, + "params": { + "phone_number": {"types": "str", "func": "get_phone"}, + "email_code": {"types": ["str", "int"], "func": "to_string"}, + "forget_password_code_hash": {"types": "str"} + } + } + } +} \ No newline at end of file diff --git a/rubpy/sync/gadgets/methods.py b/rubpy/sync/gadgets/methods.py new file mode 100644 index 0000000..2f40839 --- /dev/null +++ b/rubpy/sync/gadgets/methods.py @@ -0,0 +1,410 @@ +from .classino import Classino +from .grouping import grouping +import re +import sys +import time +import random +import warnings + +class Functions: + system_versions = { + 'Windows NT 10.0': 'Windows 10', + 'Windows NT 6.2': 'Windows 8', + 'Windows NT 6.1': 'Windows 7', + 'Windows NT 6.0': 'Windows Vista', + 'Windows NT 5.1': 'windows XP', + 'Windows NT 5.0': 'Windows 2000', + 'Mac': 'Mac/iOS', + 'X11': 'UNIX', + 'Linux': 'Linux' + } + + @classmethod + def get_phone(cls, value, *args, **kwargs): + phone_number = ''.join(re.findall(r'\d+', value)) + if not phone_number.startswith('98'): + phone_number = '98' + phone_number + return phone_number + + @classmethod + def get_browser(cls, user_agent, lang_code, app_version, *args, **kwargs): + device_model = re.search(r'(opera|chrome|safari|firefox|msie' + r'|trident)\/(\d+)', user_agent.lower()) + if not device_model: + device_model = 'Unknown' + warnings.warn(f'can not parse user-agent ({user_agent})') + + else: + device_model = device_model.group(1) + ' ' + device_model.group(2) + + system_version = 'Unknown' + for key, value in cls.system_versions.items(): + if key in user_agent: + system_version = value + break + + # window.navigator.mimeTypes.length (outdated . Defaults to '2') + device_hash = '2' + return { + 'token': '', + 'lang_code': lang_code, + 'token_type': 'Web', + 'app_version': f'WB_{app_version}', + 'system_version': system_version, + 'device_model': device_model.title(), + 'device_hash': device_hash + ''.join(re.findall(r'\d+', user_agent))} + + @classmethod + def random_number(cls, *args, **kwargs): + return int(random.random() * 1e6 + 1) + + @classmethod + def timestamp(cls, *args, **kwargs): + return int(time.time()) + + @classmethod + def get_format(cls, value, *args, **kwargs): + return value.split('.')[-1] + + @classmethod + def get_hash_link(cls, value, *args, **kwargs): + return value.split('/')[-1] + + @classmethod + def to_float(cls, value, *args, **kwargs): + return float(value) + + @classmethod + def to_number(cls, value, *args, **kwargs): + return int(value) + + @classmethod + def to_string(cls, value, *args, **kwargs): + return str(value) + + @classmethod + def to_array(cls, value, *args, **kwargs): + if isinstance(value, list): + return value + + elif isinstance(value, str): + return [value] + + try: + return value.to_dict() + + except AttributeError: + try: + return dict(value) + + except Exception: + return value + + @classmethod + def to_metadata(cls, value, *args, **kwargs): + pattern = r'`(.*)`|\*\*(.*)\*\*|__(.*)__|~~(.*)~~|--(.*)--|\[(.*)\]\((\S+)\)' + conflict = 0 + meta_data_parts = [] + for markdown in re.finditer(pattern, value): + span = markdown.span() + if markdown.group(0).startswith('`'): + value = re.sub(pattern, r'\1', value, count=1) + meta_data_parts.append( + { + 'type': 'Mono', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 2 + } + ) + conflict += 2 + + elif markdown.group(0).startswith('**'): + value = re.sub(pattern, r'\2', value, count=1) + meta_data_parts.append( + { + 'type': 'Bold', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('__'): + value = re.sub(pattern, r'\3', value, count=1) + meta_data_parts.append( + { + 'type': 'Italic', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('~~'): + value = re.sub(pattern, r'\4', value, count=1) + meta_data_parts.append( + { + 'type': 'Strike', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + elif markdown.group(0).startswith('--'): + value = re.sub(pattern, r'\5', value, count=1) + meta_data_parts.append( + { + 'type': 'Underline', + 'from_index': span[0] - conflict, + 'length': span[1] - span[0] - 4 + } + ) + conflict += 4 + + else: + value = re.sub(pattern, r'\6', value, count=1) + mention_text_object_guid = markdown.group(7) + mention_type = 'MentionText' + + if mention_text_object_guid.startswith('g'): + mention_text_object_type = 'Group' + + elif mention_text_object_guid.startswith('c'): + mention_text_object_type = 'Channel' + + elif mention_text_object_guid.startswith('u'): + mention_text_object_type = 'User' + + else: + mention_text_object_type = 'hyperlink' + mention_type = 'Link' + + if mention_type == 'MentionText': + meta_data_parts.append({ + 'type': 'MentionText', + 'from_index': span[0] - conflict, + 'length': len(markdown.group(6)), + 'mention_text_object_guid': mention_text_object_guid, + 'mention_text_object_type': mention_text_object_type + }) + conflict += 4 + len(mention_text_object_guid) + + else: + meta_data_parts.append({ + "from_index": span[0] - conflict, + "length": len(markdown.group(6)), + "link": { + "hyperlink_data": { + "url": mention_text_object_guid + }, + "type": mention_text_object_type, + }, + "type": mention_type, + }) + conflict += 4 + len(mention_text_object_guid) + + result = {'text': value} + if meta_data_parts: + result['metadata'] = { + 'meta_data_parts': meta_data_parts + } + + return result + + +class BaseMethod: + __name__ = 'CustomMethod' + + def __init__(self, method: dict, *args, **kwargs): + self.method = method + + def __str__(self): + result = f'{self.method_name}(*, *args, **kwargs)' + + if self.method_param: + result += '\nArgs:\n' + for name, param in self.method_param.items(): + types = param.get('types') + default = param.get('default') + alloweds = param.get('alloweds') + heirship = param.get('heirship') + + if not isinstance(types, list): + types = [types] + + types = ', '.join(types) + result += f'\t{name} ({types})\n' + if alloweds is not None: + result += '\t\tthe allowed values are: ' + result += str([alloweds]) + + if default is not None: + result += f'\n\t\tthe default value is {default}' + + if heirship is not None: + result += ('\n\t\tif it is not set, it takes the' + f' value from the ({[alloweds]}) argument\'s') + result += '\n' + return result + + @property + def method_name(self): + return self.__name__[0].lower() + self.__name__[1:] + + @property + def method_param(self): + if self.method: + if isinstance(self.method['params'], dict): + return self.method['params'] + + def build(self, argument, param, *args, **kwargs): + ifs = param.get('ifs') + func = param.get('func') + types = param.get('types') + alloweds = param.get('alloweds') + + # set defualt value + try: + value = self.request[argument] + + except KeyError: + value = param['default'] + if isinstance(value, dict): + default_func = value.get('func') + if isinstance(default_func, str): + value = getattr(Functions, default_func)( + **value, **self.request) + + # get value heirship + for heirship in param.get('heirship', []): + try: + value = self.request[heirship] + except KeyError: + pass + + # clall func method + if isinstance(func, str) and value is not None: + value = getattr(Functions, func)(value, **self.request) + argument = param.get('cname', argument) + + # check value types + if types and not type(value).__name__ in types: + if value is not None and 'optional' in types: + raise TypeError( + f'The given {argument} must be' + f' {types} not {type(value).__name__}') + + if alloweds is not None: + if isinstance(value, list): + for _value in value: + if _value not in alloweds: + raise ValueError( + f'the {argument}({_value}) value is' + f' not in the allowed list {alloweds}') + + elif value not in alloweds: + raise ValueError( + f'the {argument}({value}) value is' + f' not in the allowed list {alloweds}') + + # get ifs + if isinstance(ifs, dict): + + # move to the last key + if 'otherwise' in ifs: + ifs['otherwise'] = ifs.pop('otherwise') + + for operator, work in ifs.items(): + if type(value).__name__ == operator or operator == 'otherwise': + func = work.get('func') + param = work + if isinstance(func, str): + value = getattr(Functions, func)(value, **self.request) + break + + # to avoid adding an extra value if there is "cname" + if argument in self.request: + self.request.pop(argument) + + if value is not None: + if param.get('unpack'): + self.request.update(value) + + else: + self.request[param.get('cname', argument)] = value + + def __call__(self, *args, **kwargs): + if self.method_param: + self.request = {} + params = list(self.method['params'].keys()) + for index, value in enumerate(args): + try: + self.request[params[index]] = value + + except IndexError: + pass + + for argument, value in kwargs.items(): + if self.method['params'].get(argument): + self.request[argument] = value + + for argument, param in self.method['params'].items(): + try: + self.build(argument, param) + except KeyError: + if 'optional' not in param['types']: + raise TypeError( + f'{self.__name__}() ' + f'required argument ({argument})') + + if self.method.get('urls') is not None: + self.request['method'] = self.method_name + + else: + self.request = { + 'method': self.method_name, 'input': self.request} + + self.request['urls'] = self.method.get('urls') + self.request['encrypt'] = self.method.get('encrypt', True) + self.request['tmp_session'] = bool(self.method.get('tmp_session')) + + return self.request + else: + return { + 'urls': None, + 'input': {}, + 'method': self.method_name, + 'encrypt': True, + 'tmp_session': False} + + +class BaseGrouping(Classino): + def __init__(self, methods: dict, *args, **kwargs): + self.methods = methods + + def __dir__(self): + methods = list(self.methods.keys()) + methods.remove('Values') + return self.methods['Values'] + methods + + def __getattr__(self, name) -> BaseMethod: + if name in self.methods['Values']: + return name + + method = self.create(name, (BaseMethod,), dir(self)) + return method(self.methods[method.__name__]) + + +class Methods(Classino): + def __init__(self, name, *args, **kwargs): + self.__name__ = name + + def __dir__(self): + return grouping.keys() + + def __getattr__(self, name) -> BaseGrouping: + group = self.create(name, (BaseGrouping,), dir(self)) + return group(methods=grouping[group.__name__]) + +sys.modules[__name__] = Methods(__name__) \ No newline at end of file diff --git a/rubpy/sync/gadgets/thumbnail.py b/rubpy/sync/gadgets/thumbnail.py new file mode 100644 index 0000000..b33aac3 --- /dev/null +++ b/rubpy/sync/gadgets/thumbnail.py @@ -0,0 +1,83 @@ +import io +import base64 +import tempfile + + +try: + import cv2 + import numpy +except ImportError: + cv2 = None + numpy = None + + +class Thumbnail: + def __init__(self, + image: bytes, + width: int = 200, + height: int = 200, + seconds: int = 1, *args, **kwargs) -> None: + + self.image = image + self.width = width + self.height = height + self.seconds = seconds + + if isinstance(self.image, str): + with open(image, 'rb') as file: + self.image = file.read() + + def to_base64(self, *args, **kwargs) -> str: + if self.image is not None: + return base64.b64encode(self.image).decode('utf-8') + + +class MakeThumbnail(Thumbnail): + def __init__(self, + image, + width: int = 200, + height: int = 200, + seconds: int = 1, *args, **kwargs) -> None: + self.image = None + self.width = width + self.height = height + self.seconds = seconds + if hasattr(cv2, 'imdecode'): + if not isinstance(image, numpy.ndarray): + image = numpy.frombuffer(image, dtype=numpy.uint8) + image = cv2.imdecode(image, flags=1) + + self.image = self.ndarray_to_bytes(image) + + def ndarray_to_bytes(self, image, *args, **kwargs) -> str: + if hasattr(cv2, 'resize'): + self.width = image.shape[1] + self.height = image.shape[0] + image = cv2.resize(image, + (round(self.width / 10), round(self.height / 10)), + interpolation=cv2.INTER_CUBIC) + + status, buffer = cv2.imencode('.png', image) + if status is True: + return io.BytesIO(buffer).read() + + @classmethod + def from_video(cls, video: bytes, *args, **kwargs): + if hasattr(cv2, 'VideoCapture'): + with tempfile.TemporaryFile(mode='wb+') as file: + file.write(video) + capture = cv2.VideoCapture(file.name) + status, image = capture.read() + if status is True: + fps = capture.get(cv2.CAP_PROP_FPS) + frames = capture.get(cv2.CAP_PROP_FRAME_COUNT) + return MakeThumbnail( + image=image, + seconds=int(frames / fps), *args, **kwargs) + + + +thumb: str = 'iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAMC0lEQVRoBd3Bf3Cb9X0H8PfnkfQ8kmwhKXYsK8Q2QU6MpCSkSWywu9sCSeNe7YL5oXJXH2itbzsI58SUba0pvd1wmceVHWY1Ky3ntYB7S3Fz52ZzSd0YwqAx0DkJBMWExJA4P5Q4QZYdY/3+flfkE5WQvo8SYu8Pv16ERYqwSBEWKcIiRVikCIsUYZEi/H+57wHTxU+kKK7Z+8IpLDzCwttzgJA0GrAFwiZ8ijoaP8BCIiwYSaI9IwbGQ0gZDdgCYRP+TPnhbT7OOBYAYWG8ergsEjuNTKMBWyBsQiaL/tqHt+zDfCPMt9GLa0+OH0YuowFbIGxCFs4R9i998m/3Y/4Q5tWeAwSx0YAtEDZBgAM/bDyGeUKYJ3sOEPIZDdgCYRPy0HY0juKqEa7a5D8UKxusR0sunjcHoWo0YAuETRDjHO/zu8Zjq66rKntp9VdwFQhXIf7vN1/68DgApdoKgBF/a8XJmCYOgdGALRA2QeAc33gosgUAETmcFQA2lLn/zlSKL4Twhez7jutGaQIpSrUVKQnih8rOfqKEkGU0YAuETcgyiaq3wncghYgczgqk7Ln7/skjx3CFCFdIInz8nSIQIY1SbUWmWTl2oOwUkxjSjAZsgbAJaWK8YG9kO4EjDRE5nBVIw4Gdrs24EoQrEfxeKY/HkUWptiKXGTl6oGIcKaMBWyBsQhIHDYb/nkNCFiJyOCuQxVSy9GfFa3F5CJeH//zLQd9RCCjVVogdLw6ctQYAjAZsgbCJcz4c+/Y0s0GAiBzOCgi8+8Szvuf7kA/hMkx+txSJOMSUaivy8dnPvREteGv2rrHoeqgiIoezAmIcfKdrC1QR8pl8uAhEUKVUW5EPL7wutuW3d+zZOROLQhUROZwVUMXAf+XaAjGCqn9au+6BdQZdyTGoUqqtUMGZ9raRsTNT8Xhi9Q0rTk5e9LyyG2JE5HBWQIyDL/tn31O/bOOcQ4Cgyv+120EEQFc6prH6IaBUW5Eb1/xVr2y7qbDqzsnDvwJR0dp7pkd3AXjOd+i59w8gFyJyOCsgYP7NaeveswBMRvMj/7UNAgRV/oYm/BkplW+TLoIsSrUVWajsDn3tE0iyrvZMvtcHwLraM/leH5I4S9y5t//MpSlkIiKHswKZOOfa06GyJ94D4TOdQ+0QIKjyNzTh87j+hv0gjjRKtRXpdBb97cMkaZCiVN1xZn9POBZdecuDodFdSPNxJNw4sDPBGVKIyOGsQBo+Hb3uB+8Q48jUOdQOAYIqf0MTciFlVnGMgBOSlGor5vCYVL9HsVQhy5GjJ/z+i5s3bUQu+8/7d7z+WyICQEQOZwWSGKG8/ZD2UhS5dA61Q4Cgyt/QBDHJPCEv+wCAUm3Fn9z4qKHqPlyFR99+bfDUGBE5nBUAljx9/JrjAYh1DrVDgKDK39CEfOQVBw23rDPc2Y95wdmtAzvLP9Qu2/0h8ukcaocAQZW/oQmq4qCfhjdse/FbxUVLdLIegNl198x0iMUZ5kiARCDCZxhHgiNFo2iNBcr0kV8jKRqNhGdD3q92Ows0IIKqzqF2CBBU+RuaIEDArmjVaWYB0NrrZQnGOV/xF9tiJ3bjyhkqmy6NvjQbCuNPGGuu7wawVJFKZQlinUPtECCo8jc0IZd3WfG+qAMprb1elmAA3Fvbjvz+6dJSmyRpcNkYY0vW3DM+3IM5jDXXdyOlslBrIOTUOdQOAYIqf0MTMl3ghv+MrAEIaVp7vSzBALi3tvkGuzjneoNStKSYiKCKcz4zc4lzlNe2jA/3YA5jzfXdSEMEV6FWwud1DrVDgKDK39CEFAn82fCGMHTI0trrBefOLTsA+Aa7kHTrXf848cn05OGXkIt1zTcMpHv/jZ8gqby2hQin3/x5gjEw1lzfjSxGDTmMGqTpHGqHAEGVv6EJSa/Fy9+J2yHQ2ut1bt4e9PVZ3B7fYBcAInJ9ZQeS/P/7gsFgQBqr28PxqVNv/gfnHEB5bUvQ12dxe8aHe8BYc303BJYrklWWkNQ51A4Bgip/Q9NRvuR3kZVQ1drrdW7eHvT1Wdwe32AXktxb2wDEYokPXv0x59xmK9Hp5Hg8Njsbsq33KooWwPhwD5LKa1uCvj6L2zM+3APGmuu7ocpZqNESdQ61Q4Cg6gebH49DQj6tvV7n5u1BX5/F7fENdiHl0kzIVGhAiqKXZZ2CpJmZkMlk5Jwjqby2Jejrs7g948M9YKy5vhv5SBLtfvNRCBBUNdZ0lOgkm16CqtZer3Pz9qCvz+L2+Aa7IKAosiwryKW8tiXo67O4PePDPWCsub4bqr5uunib/qR9oB8CBFWNNR0AOFBllBSNBIHWXq9GkqpubeWc+wa7kMW9tW3qxEVoJHPZkvHhHmQpr20honMjz0ciMTDWXN8NAYfuk3bL+xyfsg/0Q4CgqrGmAykawGnSEnJo7fWyBAPg3trmG+yCgKLIsqwgl/LalvHhHsxhrLm+G1kk8GeWHtSCI8U+0A8BgqrGmg5k0kt8ZYEOmVp7vSzBALi3tvkGuyCgKLIsK8ilvLZlfLgHcxhrru9GGgn88aIjxVIYmewD/RAgqGqs6UAuVi1fbtAhpbXXyxIMQOVfPnj8f56BgKLoZFmPXEq+dN/EwRcwh7Hm+m6kPFJ0bIU0jVzsA/0QIKhqrOmA2AoDFWo1AFp7vSzBkOSufygaT5x649lwJIokjYZcmx8C5xpJQkqCcyTNhMJGozy+vwefYay5vhvAA0tOr9ech5h9oB8CBFWNNR3Ig7sLdTt+6WUJhjTLV917+oMXAVxft818jfHgnicBKHpZ1inIi7Gf3vX4Nw1jGnCosg/0Q4CgqrGmA/n8Yd3ym+5xP7nSZpE0SFm+6t7gmb6KL9/vG+xCit6g12l1UDd1RPeHe0OvI3pyNfKxD/RDgKCqsaYDYmOO4rNmA4A6jwsAB39mlV0hCYB7a5sco4OvPoU0RqNRo9FAIBEK6vdtAmnBeGQkCCB2rioxuRRi9oF+CBBUNdZ0IJfg0sLDZRaAkFTncSHlhmvkh+3FyMVoNGo0GmRJsITyyi0Un8UcxiMjQczhUmRsA48pyMU+0A8BgqrGmg5kiuh1B6pK4hoJaeo8LmSqL1XuNhchk8Fg0Gq1yMC1b++gwH6kYzwyEkQaHjVGxtYji32gHwIEVY01HUjzjts+rWiRpc7jQhYOPFhe/CWDjBRFr8g6GSnaj35BR38MED6H8chIEFkSU8tiZ69HGvtAPwQIqho2PkYSAThXYjq23AKBOo8LAgT8W6VNr9EAUBRZlhUA2tgJ7L2bCLkxHhkJIhcORI7eDKYFUFJQqHmpFwKEfBpv6nh9XRkIKuo8Lqgq1EhPV5bq9IoWM/LvG0iKQQXjkZEgxHhCGzlae+3L/YxzCBDyWbrjxWgkAVV1HhfyIR73Wv7662MceTEeGQlCFRkLLI+dhBjhMizd3huNxiFW53FBVUmopxyvVFombMZpl39Z8YwRKhiPjAQhJi2xmB85DlWEy2Pf5Jm9oQECdR4XBAojr1Wx5wgcQKVlwmacBiBxqeajCjmhQU6MR0aCEKh+Yfb4xCzyIVyJiu/vCn58CVnqPC5k0SHiDm3TIoyUSsuEzTiNFDmuvfmj65CN8chIEFksDic98DouD+HKWR98niU40tR5XMjAXaHvGnEGmSotEzbjNDKVTplXTSxFOsYjI0GkYYqx6PFxXAnCF2W+/xdIqfO4kFISf6089jPkUmmesBVMI5c1Z5ZbZ/WYw3hkJIg5BOuPLuLKEa7Chof+9XioCECdxwWgEBerQm0EDgGHeaK0YBoCurh248lyHZPAeGQkCOCJdzX/MngeXwjhqm3q3G2qWVI5+71COg9VDvOF0oIpqLLMFqw5VWqMlpH3VVwFwjzpeHltNBGCquvNF+wFU1Cl11RsuvEErhph/pCE7++uJBAErjdfsBdMQYh/dT3mC2G+Xbuy8NtPlQKELA7zhdKCKeTAv7YRjGEeERbGrpPfPHT4j8jksEyUGqeR6W/ulE+diGC+ERbST/bdfnbmCFIqzRO2gmmk3Li6wS7/NxYGYeE99vLaWCIEoNIyYTNOA7CYl9/sOIWFRFikCIsUYZEiLFKERYqwSBEWqf8DvC1ahbLUnEsAAAAASUVORK5CYII=' +width: int = 200 +height: int = 200 +time: str = '1' \ No newline at end of file diff --git a/rubpy/sync/network/__init__.py b/rubpy/sync/network/__init__.py new file mode 100644 index 0000000..2f85043 --- /dev/null +++ b/rubpy/sync/network/__init__.py @@ -0,0 +1,2 @@ +from .proxies import Proxies +from .connection import Connection \ No newline at end of file diff --git a/rubpy/sync/network/connection.py b/rubpy/sync/network/connection.py new file mode 100644 index 0000000..48c5f2e --- /dev/null +++ b/rubpy/sync/network/connection.py @@ -0,0 +1,316 @@ +import os +import requests +import threading +import time +import json +import websocket +from ..crypto import Crypto +from ..structs import results +from ..gadgets import exceptions, methods + + +def capitalize(text): + return ''.join([ + c.title() for c in text.split('_')]) + + +class Connection: + """Internal class""" + + def __init__(self, client): + self._client = client + self._session = requests.Session() + self._headers = { + 'user-agent': self._client._user_agent, + 'origin': 'https://web.rubika.ir', + 'referer': 'https://web.rubika.ir/' + } + + def close(self): + self._session.close() + + def execute(self, request: dict): + if not isinstance(request, dict): + request = request() + + self._client._logger.info('execute method', extra={'data': request}) + method_urls = request.pop('urls') + if method_urls is None: + method_urls = (self._dcs()).default_api_urls + + if not method_urls: + raise exceptions.UrlNotFound( + 'It seems that the client could not' + ' get the list of Rubika api\'s.' + ' Please wait and try again.', + request=request) + + method = request['method'] + tmp_session = request.pop('tmp_session') + if self._client._auth is None: + self._client._auth = Crypto.secret(length=32) + self._client._logger.info( + 'create auth secret', extra={'data': self._client._auth}) + + if self._client._key is None: + self._client._key = Crypto.passphrase(self._client._auth) + self._client._logger.info( + 'create key passphrase', extra={'data': self._client._key}) + + request['client'] = self._client._platform + if request.get('encrypt') is True: + request = {'data_enc': Crypto.encrypt(request, key=self._client._key)} + + request['tmp_session' if tmp_session else 'auth'] = self._client._auth + + if 'api_version' not in request: + request['api_version'] = self._client.configuire['api_version'] + + if request['api_version'] == '6' and tmp_session == False: + request['auth'] = Crypto.decode_auth(request['auth']) + request['sign'] = Crypto.sign(self._client._private_key, request['data_enc']) + + if not method_urls[0].startswith('https://getdcmess'): + method_urls = [method_urls[0]] * 3 + + for _ in range(self._client._request_retries): + for url in method_urls: + try: + response = self._session.post(url, json=request, headers=self._headers) + if response.status_code != 200: + continue + + result: dict = response.json() + if result.get('data_enc'): + result = Crypto.decrypt(result['data_enc'], + key=self._client._key) + status = result['status'] + status_det = result['status_det'] + if status == 'OK' and status_det == 'OK': + result['data']['_client'] = self._client + return results(method, update=result['data']) + + self._client._logger.warning( + 'request status ' + + capitalize(status_det), extra={'data': request}) + + raise exceptions(status_det)(result, request=request) + + except requests.Timeout: + pass + + raise exceptions.InternalProblem( + 'rubika server has an internal problem', request=request) + + def handel_update(self, name, update): + handlers = self._client._handlers.copy() + for func, handler in handlers.items(): + try: + # if handler is empty filters + if isinstance(handler, type): + handler = handler() + + if handler.__name__ != capitalize(name): + continue + + # analyze handlers + if not handler(update=update): + continue + + func(handler) + + except exceptions.StopHandler: + break + + except Exception: + self._client._logger.error( + 'handler raised an exception', + extra={'data': update}, exc_info=True) + + def receive_updates(self): + default_sockets = (self._dcs()).default_sockets + threading.Thread(target=self.to_keep_alive) + for url in default_sockets: + try: + ws = websocket.create_connection(url) + self.send_data_to_ws(ws) + threading.Thread(target=self.keep_socket, args=(ws,)) + self._client._logger.info( + 'start receiving updates', extra={'data': url}) + while True: + message = ws.recv() + if not message: + continue + + try: + result = json.loads(message) + if not result.get('data_enc'): + self._client._logger.debug( + 'the data_enc key was not found', + extra={'data': result}) + continue + + result = Crypto.decrypt(result['data_enc'], + key=self._client._key) + user_guid = result.pop('user_guid') + for name, package in result.items(): + if not isinstance(package, list): + continue + + for update in package: + update['_client'] = self._client + update['user_guid'] = user_guid + threading.Thread(target=self.handel_update, args=(name, update,)).start() + + except Exception: + self._client._logger.error( + 'websocket raised an exception', + extra={'data': url}, exc_info=True) + except websocket._exceptions.WebSocketTimeoutException: + pass + + def send_data_to_ws(self, wss: websocket.create_connection, data='handSnake'): + if data == 'handSnake': + wss.send(json.dumps({ + 'method': 'handShake', + 'data': '', + 'auth': self._client._auth, + 'api_version': '5', # self._client.configuire['api_version'] + })) + elif data == 'keep': + wss.send('{}') + + def to_keep_alive(self): + while True: + time.sleep(5) + try: + self._client.get_chats_updates(round(time.time()) - 200) + except: + continue + + def keep_socket(self, wss): + while True: + time.sleep(5) + try: + self.send_data_to_ws(wss, data='keep') + except: + continue + + def upload_file(self, file, mime: str = None, file_name: str = None, + chunk: int = 1048576 * 2, callback=None, *args, **kwargs): + if isinstance(file, str): + if not os.path.exists(file): + raise ValueError('file not found in the given path') + if file_name is None: + file_name = os.path.basename(file) + + with open(file, 'rb') as file: + file = file.read() + + elif not isinstance(file, bytes): + raise TypeError('file arg value must be file path or bytes') + + if file_name is None: + raise ValueError('the file_name is not set') + + if mime is None: + mime = file_name.split('.')[-1] + + result = self.execute( + methods.messages.RequestSendFile( + mime=mime, size=len(file), file_name=file_name)) + + id = result.id + index = 0 + dc_id = result.dc_id + total = int(len(file) / chunk + 1) + upload_url = result.upload_url + access_hash_send = result.access_hash_send + + while index < total: + data = file[index * chunk: index * chunk + chunk] + try: + response = self._session.post( + upload_url, + headers={ + 'auth': self._client._auth, + 'file-id': id, + 'total-part': str(total), + 'part-number': str(index + 1), + 'chunk-size': str(len(data)), + 'access-hash-send': access_hash_send + }, + data=data + ) + result = response.json() + if callable(callback): + try: + callback(len(file), index * chunk) + + except exceptions.CancelledError: + return None + + except Exception: + pass + + index += 1 + except Exception: + pass + + status = result['status'] + status_det = result['status_det'] + if status == 'OK' and status_det == 'OK': + result = { + 'mime': mime, + 'size': len(file), + 'dc_id': dc_id, + 'file_id': id, + 'file_name': file_name, + 'access_hash_rec': result['data']['access_hash_rec'] + } + + return results('UploadFile', result) + + self._client._logger.debug('upload failed', extra={'data': result}) + raise exceptions(status_det)(result, request=result) + + def download(self, dc_id: int, file_id: int, access_hash: str, size: int, chunk=131072, callback=None): + url = f'https://messenger{dc_id}.iranlms.ir/GetFile.ashx' + start_index = 0 + result = b'' + + headers = { + 'auth': self._client._auth, + 'access-hash-rec': access_hash, + 'file-id': str(file_id), + 'user-agent': self._client._user_agent + } + + while True: + last_index = start_index + chunk - 1 if start_index + chunk < size else size - 1 + + headers['start-index'] = str(start_index) + headers['last-index'] = str(last_index) + + response = self._session.post(url, headers=headers) + if response.status_code == 200: + data = response.content + if data: + result += data + if callback: + callback(size, len(result)) + + # بررسی پایان فایل + if len(result) >= size: + break + + # بروزرسانی مقدار start_index برای دریافت بخش بعدی فایل + start_index = last_index + 1 + + return result + + def _dcs(self): + if not self._client._dcs: + self._client._dcs = self.execute(methods.authorisations.GetDCs()) + + return self._client._dcs \ No newline at end of file diff --git a/rubpy/sync/network/proxies.py b/rubpy/sync/network/proxies.py new file mode 100644 index 0000000..d499c4a --- /dev/null +++ b/rubpy/sync/network/proxies.py @@ -0,0 +1,59 @@ +import requests +from urllib.parse import urlparse + + +class Proxies: + def __init__(self, + host: str, + port: int, + type: str = 'http', + resolver: bool = False, + username: str = None, + password: str = None): + if isinstance(type, str): + if type.lower() not in ['http', 'https', 'socks5', 'socks4']: + raise ValueError('Proxy type must be `socks5`, `socks4`, `http`, or `https`') + + self._resolver = resolver + self._proxy_host = host + self._proxy_port = port + self._proxy_type = type + self._proxy_username = username + self._proxy_password = password + + @property + def proxy_url(self): + pattern = '{scheme}://{host}:{port}' + if self._proxy_username: + pattern = '{scheme}://{username}:{password}@{host}:{port}' + return urlparse(pattern.format(scheme=self._proxy_type, + username=self._proxy_username, + password=self._proxy_password, + host=self._proxy_host, + port=self._proxy_port)) + + @classmethod + def from_url(cls, url, **kwargs): + parse = urlparse(url) + return cls( + type=parse.scheme, port=int(parse.port), host=parse.hostname, + username=parse.username, password=parse.password, **kwargs) + + def connect(self, req, timeout): + if self.proxy_url.scheme in ['http', 'https']: + req.proxies = { + 'http': self.proxy_url.geturl(), + 'https': self.proxy_url.geturl() + } + req.auth = (self._proxy_username, self._proxy_password) + return requests.request(req.method, req.url, headers=req.headers, data=req.body, timeout=timeout) + + def _wrap_create_connection(self, protocol_factory, host=None, port=None, *args, **kwargs): + if self.proxy_url.scheme not in ['http', 'https']: + raise NotImplementedError("Socks proxy connection is not supported in synchronous mode.") + else: + return requests.Session() + + def send(self, req, **kwargs): + timeout = kwargs.get('timeout') + return self.connect(req, timeout) \ No newline at end of file diff --git a/rubpy/sync/sessions/__init__.py b/rubpy/sync/sessions/__init__.py new file mode 100644 index 0000000..710ef4b --- /dev/null +++ b/rubpy/sync/sessions/__init__.py @@ -0,0 +1,2 @@ +from .sqliteSession import SQLiteSession +from .stringSession import StringSession \ No newline at end of file diff --git a/rubpy/sync/sessions/sqliteSession.py b/rubpy/sync/sessions/sqliteSession.py new file mode 100644 index 0000000..d9da93d --- /dev/null +++ b/rubpy/sync/sessions/sqliteSession.py @@ -0,0 +1,67 @@ +# import os +import sqlite3 + +suffix = '.rbs' +rbs_version = 1 + + +class SQLiteSession(object): + + def __init__(self, session: str) -> None: + self.filename = session + if not session.endswith(suffix): + self.filename += suffix + + self._connection = sqlite3.connect(self.filename, + check_same_thread=False) + cursor = self._connection.cursor() + cursor.execute('select name from sqlite_master ' + 'where type=? and name=?', ('table', 'version')) + if cursor.fetchone(): + cursor.execute('select version from version') + version = cursor.fetchone()[0] + if rbs_version != version: + self.upgrade_database(version) + + else: + cursor.execute( + 'create table version (version integer primary key)') + cursor.execute('insert into version values (?)', (rbs_version,)) + cursor.execute('create table session (phone text primary key' + ', auth text, guid text, agent text, private_key text)') + self._connection.commit() + cursor.close() + + def upgrade_database(self, version): + pass + + def information(self): + cursor = self._connection.cursor() + cursor.execute('select * from session') + result = cursor.fetchone() + cursor.close() + return result + + def insert(self, phone_number, auth, guid, user_agent, private_key, *args, **kwargs): + cursor = self._connection.cursor() + cursor.execute( + 'insert or replace into session (phone, auth, guid, agent, private_key)' + ' values (?, ?, ?, ?, ?)', + (phone_number, auth, guid, user_agent, private_key) + ) + self._connection.commit() + cursor.close() + + @classmethod + def from_string(cls, session, file_name=None): + info = session.information() + if file_name is None: + if info is None: + raise ValueError('file_name arg is not set') + file_name = info[0] + + session = SQLiteSession(file_name) + if info is not None: + session.insert(*info) + + return session diff --git a/rubpy/sync/sessions/stringSession.py b/rubpy/sync/sessions/stringSession.py new file mode 100644 index 0000000..60138cb --- /dev/null +++ b/rubpy/sync/sessions/stringSession.py @@ -0,0 +1,39 @@ +import json +import base64 + + +class StringSession(object): + def __init__(self, session: str = None) -> None: + self.session = self.load(session) + + @classmethod + def load(cls, session): + if isinstance(session, str): + return json.loads(base64.b64decode(session)) + + @classmethod + def dump(cls, session): + if isinstance(session, list): + session = json.dumps(session).encode('utf-8') + return base64.b64encode(session).decode('utf-8') + + @classmethod + def from_sqlite(cls, session): + session = cls.dump(session.information()) + return StringSession(session) + + def insert(self, phone_number, auth, guid, user_agent, *args, **kwargs): + self.session = [phone_number, auth, guid, user_agent] + + def information(self): + return self.session + + def save(self, file_name=None): + result = self.dump(self.session) + if result is None: + if isinstance(file_name, str): + if not file_name.endswith('.txt'): + file_name += '.txt' + with open(file_name, 'w+') as file: + file.write(result) + return result diff --git a/rubpy/sync/structs/__init__.py b/rubpy/sync/structs/__init__.py new file mode 100644 index 0000000..2c8fff6 --- /dev/null +++ b/rubpy/sync/structs/__init__.py @@ -0,0 +1,5 @@ +from . import models +from . import results +from . import handlers +from .struct import Struct + diff --git a/rubpy/sync/structs/handlers.py b/rubpy/sync/structs/handlers.py new file mode 100644 index 0000000..12c462d --- /dev/null +++ b/rubpy/sync/structs/handlers.py @@ -0,0 +1,53 @@ +import sys +from .struct import Struct +from ..gadgets import Classino + +__handlers__ = [ + 'ChatUpdates', + 'MessageUpdates', + 'ShowActivities', + 'ShowNotifications', + 'RemoveNotifications' +] + +class BaseHandlers(Struct): + __name__ = 'CustomHandlers' + + def __init__(self, *models, __any: bool = False, **kwargs) -> None: + self.__models = models + self.__any = __any + + def __call__(self, update: dict, *args, **kwargs) -> bool: + self.original_update = update + if self.__models: + for filter in self.__models: + if callable(filter): + if isinstance(filter, type): + filter = filter(func=None) + + status = filter(self, result=None) + + if status and self.__any: + return True + elif not status: + return False + + return True + +class Handlers(Classino): + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseHandlers in value.__bases__ + + def __dir__(self): + return sorted(__handlers__) + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name)(*args, **kwargs) + + def __getattr__(self, name): + return self.create(name, (BaseHandlers,), __handlers__) + +sys.modules[__name__] = Handlers(__name__) diff --git a/rubpy/sync/structs/models.py b/rubpy/sync/structs/models.py new file mode 100644 index 0000000..141e3cf --- /dev/null +++ b/rubpy/sync/structs/models.py @@ -0,0 +1,144 @@ +import re +import sys +from ..gadgets import Classino + +__all__ = ['Operator', 'BaseModels', 'RegexModel'] +__models__ = [ + 'is_pinned', 'is_mute', 'count_unseen', 'message_id', + 'is_group', 'is_private', 'is_channel', 'is_in_contact', + 'raw_text', 'original_update', 'object_guid', 'author_guid', 'time', 'reply_message_id'] + +class Operator: + Or = 'OR' + And = 'AND' + Less = 'Less' + Lesse = 'Lesse' + Equal = 'Equal' + Greater = 'Greater' + Greatere = 'Greatere' + Inequality = 'Inequality' + + def __init__(self, value, operator, *args, **kwargs): + self.value = value + self.operator = operator + + def __eq__(self, value) -> bool: + return self.operator == value + + +class BaseModels: + __name__ = 'CustomModels' + + def __init__(self, + func=None, filters=[], *args, **kwargs) -> None: + self.func = func + if not isinstance(filters, list): + filters = [filters] + self.filters = filters + + def insert(self, filter): + self.filters.append(filter) + return self + + def __or__(self, value): + return self.insert(Operator(value, Operator.Or)) + + def __and__(self, value): + return self.insert(Operator(value, Operator.And)) + + def __eq__(self, value): + return self.insert(Operator(value, Operator.Equal)) + + def __ne__(self, value): + return self.insert(Operator(value, Operator.Inequality)) + + def __lt__(self, value): + return self.insert(Operator(value, Operator.Less)) + + def __le__(self, value): + return self.insert(Operator(value, Operator.Lesse)) + + def __gt__(self, value): + return self.insert(Operator(value, Operator.Greater)) + + def __ge__(self, value): + return self.insert(Operator(value, Operator.Greatere)) + + def build(self, update): + result = getattr(update, self.__name__, None) + if callable(self.func): + result = self.func(result) + + for filter in self.filters: + value = filter.value + + if callable(value): + value = value(update, result) + + if self.func: + value = self.func(value) + + if filter == Operator.Or: + result = result or value + + elif filter == Operator.And: + result = result and value + + elif filter == Operator.Less: + result = result < value + + elif filter == Operator.Lesse: + result = result <= value + + elif filter == Operator.Equal: + result = result == value + + elif filter == Operator.Greater: + result = result > value + + elif filter == Operator.Greatere: + result = result >= value + + elif filter == Operator.Inequality: + result = result != value + + return bool(result) + + def __call__(self, update, *args, **kwargs): + return self.build(update) + + +class RegexModel(BaseModels): + def __init__(self, pattern, *args, **kwargs) -> None: + self.pattern = re.compile(pattern) + super().__init__(*args, **kwargs) + + def __call__(self, update, *args, **kwargs) -> bool: + if update.raw_text is None: + return False + + update.pattern_match = self.pattern.match(update.raw_text) + return bool(update.pattern_match) + + +class Models(Classino): + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseModels in value.__bases__ + + def __dir__(self): + return sorted(__models__) + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name) + + def __getattr__(self, name): + if name in __all__: + return globals()[name] + return self.create(name, (BaseModels, ), + authorise=__models__, exception=False) + + +sys.modules[__name__] = Models(__name__) \ No newline at end of file diff --git a/rubpy/sync/structs/results.py b/rubpy/sync/structs/results.py new file mode 100644 index 0000000..cc54e4a --- /dev/null +++ b/rubpy/sync/structs/results.py @@ -0,0 +1,27 @@ +import sys +from .struct import Struct +from ..gadgets import Classino + + +class BaseResults(Struct): + __type__ = 'CustomResult' + + def __init__(self, update, *args, **kwargs) -> None: + self.original_update = update + + +class Results(Classino): + def __init__(self, name, *args, **kwargs) -> None: + self.__name__ = name + + def __eq__(self, value: object) -> bool: + return BaseResults in value.__bases__ + + def __call__(self, name, *args, **kwargs): + return self.__getattr__(name)(*args, **kwargs) + + def __getattr__(self, name): + return self.create(name, (BaseResults,), exception=False) + + +sys.modules[__name__] = Results(__name__) diff --git a/rubpy/sync/structs/struct.py b/rubpy/sync/structs/struct.py new file mode 100644 index 0000000..657464c --- /dev/null +++ b/rubpy/sync/structs/struct.py @@ -0,0 +1,441 @@ +import json +import base64 +from ..gadgets import methods, thumbnail + + +class Struct: + def __str__(self) -> str: + return self.jsonify(indent=2) + + def __getattr__(self, name): + return self.find_keys(keys=name) + + def __setitem__(self, key, value): + self.original_update[key] = value + + def __getitem__(self, key): + return self.original_update[key] + + def __lts__(self, update: list, *args, **kwargs): + for index, element in enumerate(update): + if isinstance(element, list): + update[index] = self.__lts__(update=element) + + elif isinstance(element, dict): + update[index] = Struct(update=element) + + else: + update[index] = element + return update + + def __init__(self, update: dict, *args, **kwargs) -> None: + self.original_update = update + + def to_dict(self): + return self.original_update + + def jsonify(self, indent=None, *args, **kwargs) -> str: + result = self.original_update + result['original_update'] = 'dict{...}' + return json.dumps(result, indent=indent, + ensure_ascii=False, + default=lambda value: str(value)) + + def find_keys(self, keys, original_update=None, *args, **kwargs): + + if original_update is None: + original_update = self.original_update + + if not isinstance(keys, list): + keys = [keys] + + if isinstance(original_update, dict): + for key in keys: + try: + update = original_update[key] + if isinstance(update, dict): + update = Struct(update=update) + + elif isinstance(update, list): + update = self.__lts__(update=update) + + return update + + except KeyError: + pass + original_update = original_update.values() + + for value in original_update: + if isinstance(value, (dict, list)): + try: + return self.find_keys(keys=keys, original_update=value) + + except AttributeError: + pass + + raise AttributeError(f'Struct object has no attribute {keys}') + + def guid_type(self, guid: str, *args, **kwargs) -> str: + if isinstance(guid, str): + if guid.startswith('u'): + return 'User' + + elif guid.startswith('g'): + return 'Group' + + elif guid.startswith('c'): + return 'Channel' + + # property functions + + @property + def type(self): + try: + return self.find_keys(keys=['type', 'author_type']) + + except AttributeError: + pass + + @property + def raw_text(self): + try: + return self.find_keys(keys='text') + + except AttributeError: + pass + + @property + def message_id(self): + try: + return self.find_keys(keys=['message_id', + 'pinned_message_id']) + except AttributeError: + pass + + @property + def reply_message_id(self): + try: + return self.find_keys(keys='reply_to_message_id') + + except AttributeError: + pass + + @property + def is_group(self): + return self.type == 'Group' + + @property + def is_channel(self): + return self.type == 'Channel' + + @property + def is_private(self): + return self.type == 'User' + + @property + def object_guid(self): + try: + return self.find_keys(keys=['group_guid', + 'object_guid', 'channel_guid']) + except AttributeError: + pass + + @property + def author_guid(self): + try: + return self.author_object_guid + + except AttributeError: + pass + + def pin(self, + object_guid: str = None, + message_id: str = None, + action: str = methods.messages.Pin, *args, **kwargs): + """_pin_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + action (bool, optional): + _pin or unpin_. Defaults to methods.messages.Pin. ( + methods.messages.Pin, + methods.messages.Unpin + ) + Returns: + BaseResults: result + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_id is None: + message_id = self.message_id + + return self._client( + methods.messages.SetPinMessage( + object_guid=object_guid, + message_id=message_id, + action=action)) + + def edit(self, + text: str, + object_guid: str = None, + message_id: str = None, *args, **kwargs): + """_edit_ + Args: + text (str): + _message text_ + object_guid (str, optional): + _custom objec guid_. Defaults to update.object_guid. + message_id (str, optional): + _custom message id_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_id is None: + message_id = self.message_id + + return self._client( + methods.messages.EditMessage( + object_guid=object_guid, + message_id=message_id, + text=text)) + + def copy(self, + to_object_guid: str, + from_object_guid: str = None, message_ids=None, *args, **kwargs): + """_copy_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + result = self.get_messages(from_object_guid, message_ids) + messages = [] + if result.messages: + for message in result.messages: + + try: + file_inline = message.file_inline + kwargs.update(file_inline.to_dict()) + + except AttributeError: + file_inline = None + + try: + thumb = thumbnail.Thumbnail( + base64.b64decode(message.thumb_inline), *args, **kwargs) + + except AttributeError: + thumb = kwargs.get('thumb', True) + + try: + message = message.sticker + + except AttributeError: + message = message.raw_text + + if file_inline is not None: + if file_inline.type not in [methods.messages.Gif, + methods.messages.Sticker]: + file_inline = self.download(file_inline) + messages.append(self._client.send_message( + thumb=thumb, + message=message, + file_inline=file_inline, + object_guid=to_object_guid, *args, **kwargs)) + continue + + messages.append(self._client.send_message( + message=message, + object_guid=to_object_guid, + file_inline=file_inline, *args, **kwargs)) + + return Struct({'status': 'OK', 'messages': messages}) + + def seen(self, seen_list: dict = None, *args, **kwargs): + """_seen_ + Args: + seen_list (dict, optional): + _description_. Defaults t + {update.object_guid: update.message_id} + """ + + if seen_list is None: + seen_list = {self.object_guid: self.message_id} + return self._client(methods.chats.SeenChats(seen_list=seen_list)) + + def reply(self, + message=None, + object_guid: str = None, + reply_to_message_id: str = None, + file_inline: str = None, *args, **kwargs): + """_reply_ + Args: + message (Any, optional): + _message or cation or sticker_ . Defaults to None. + object_guid (str): + _object guid_ . Defaults to update.object_guid + reply_to_message_id (str, optional): + _reply to message id_. Defaults to None. + file_inline (typing.Union[pathlib.Path, bytes], optional): + _file_. Defaults to None. + is_gif (bool, optional): + _is it a gif file or not_. Defaults to None. + is_image (bool, optional): + _is it a image file or not_. Defaults to None. + is_voice (bool, optional): + _is it a voice file or not_. Defaults to None. + is_music (bool, optional): + _is it a music file or not_. Defaults to None. + is_video (bool, optional): + _is it a video file or not_. Defaults to None. + thumb (bool, optional): + if value is "True", + the lib will try to build the thumb ( require cv2 ) + if value is thumbnail.Thumbnail, to set custom + Defaults to True. + """ + + if object_guid is None: + object_guid = self.object_guid + + if reply_to_message_id is None: + reply_to_message_id = self.message_id + + return self._client.send_message( + message=message, + object_guid=object_guid, + file_inline=file_inline, + reply_to_message_id=reply_to_message_id, *args, **kwargs) + + def forwards(self, + to_object_guid: str, + from_object_guid: str = None, + message_ids=None, *args, **kwargs): + """_forwards_ + Args: + to_object_guid (str): + _to object guid_. + from_object_guid (str, optional): + _from object guid_. Defaults to update.object_guid. + message_ids (typing.Union[str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if from_object_guid is None: + from_object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + return self._client( + methods.messages.ForwardMessages( + from_object_guid=from_object_guid, + to_object_guid=to_object_guid, + message_ids=message_ids)) + + def download(self, file_inline=None, file=None, *args, **kwargs): + return self._client.download_file_inline( + file_inline or self.file_inline, + file=file, *args, **kwargs) + + def get_author(self, author_guid: str = None, *args, **kwargs): + """_get user or author information_ + Args: + author_guid (str, optional): + _custom author guid_. Defaults to update.author_guid + """ + + if author_guid is None: + author_guid = self.author_guid + + return self.get_object(object_guid=author_guid, *args, **kwargs) + + def get_object(self, object_guid: str = None, *args, **kwargs): + """_get object information_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + """ + + if object_guid is None: + object_guid = self.object_guid + + if self.guid_type(object_guid) == 'User': + return self._client( + methods.users.GetUserInfo( + user_guid=object_guid)) + + elif self.guid_type(object_guid) == 'Group': + return self._client( + methods.groups.GetGroupInfo( + object_guid=object_guid)) + + elif self.guid_type(object_guid) == 'Channel': + return self._client( + methods.channels.GetChannelInfo( + object_guid=object_guid)) + + def get_messages(self, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_get messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, int, typing.List[str]], optional): + _message ids_. Defaults to update.message_id. + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + return self._client( + methods.messages.GetMessagesByID( + object_guid=object_guid, message_ids=message_ids)) + + def delete_messages(self, + object_guid: str = None, + message_ids=None, *args, **kwargs): + """_delete messages_ + Args: + object_guid (str, optional): + _custom object guid_. Defaults to update.object_guid. + message_ids (str, list, optional): + _custom message ids_. Defaults to update.message_id. + type(str, optional): + the message should be deleted for everyone or not. + Defaults to methods.messages.Global ( + methods.messages.Local, + methods.messages.Global + ) + """ + + if object_guid is None: + object_guid = self.object_guid + + if message_ids is None: + message_ids = self.message_id + + return self._client( + methods.messages.DeleteMessages( + object_guid=object_guid, + message_ids=message_ids, *args, **kwargs)) \ No newline at end of file