From 38b8a2fdf6dd117a440f47d8b2a0d8dbd3d29d07 Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Mon, 14 Jul 2025 12:13:13 +0200 Subject: [PATCH] Include external plugins, minor cleanup - Consolidate plugins from other repositories to make their installation easier - Make minor changes to improve formatting --- README.md | 13 + src/antispam.py | 549 ++++++++++ src/ascii.py | 1 + src/autocorrector.py | 1 + src/browser.py | 2 +- src/c/moj/.gitignore | 7 + src/c/moj/COPYING | 15 + src/c/moj/Makefile | 68 ++ src/c/moj/README.md | 28 + src/c/moj/gen/emoji.gperf | 1810 +++++++++++++++++++++++++++++++ src/c/moj/gen/emoji_list.h | 1646 ++++++++++++++++++++++++++++ src/c/moj/src/gen_gperf.sh | 15 + src/c/moj/src/gen_list.sh | 26 + src/c/moj/src/main.c | 124 +++ src/c/moj/src/moj.c | 98 ++ src/c/moj/src/moj.h | 5 + src/c/moj/src/process.awk | 93 ++ src/c/moj/src/profanity_emoji.c | 10 + src/c/pid/pid.c | 2 +- src/history.py | 167 +++ src/htmlstrip.py | 4 +- src/imgur.py | 1 - src/notifycmd.py | 137 +++ src/sanitize.py | 144 +++ src/shortcuts.py | 128 +++ src/sounds.py | 5 +- 26 files changed, 5092 insertions(+), 7 deletions(-) create mode 100644 src/antispam.py create mode 100644 src/c/moj/.gitignore create mode 100644 src/c/moj/COPYING create mode 100644 src/c/moj/Makefile create mode 100644 src/c/moj/README.md create mode 100644 src/c/moj/gen/emoji.gperf create mode 100644 src/c/moj/gen/emoji_list.h create mode 100755 src/c/moj/src/gen_gperf.sh create mode 100755 src/c/moj/src/gen_list.sh create mode 100644 src/c/moj/src/main.c create mode 100644 src/c/moj/src/moj.c create mode 100644 src/c/moj/src/moj.h create mode 100644 src/c/moj/src/process.awk create mode 100644 src/c/moj/src/profanity_emoji.c create mode 100644 src/history.py create mode 100644 src/notifycmd.py create mode 100644 src/sanitize.py create mode 100644 src/shortcuts.py diff --git a/README.md b/README.md index af8dba7..bf518d3 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Alternatively, you can use URLs to raw files to install Python plugins. For exam See the `/help plugins` command for further plugin management options. +## Plugins description + +To be described. + ## Building CProof with plugin support Dependencies required: @@ -41,3 +45,12 @@ API Documentation is available on our website, [jabber.space](https://jabber.spa - [Python hooks](https://jabber.space/api-reference/python/plugin/) - [C API](https://jabber.space/api-reference/c/profapi/) - [C hooks](https://jabber.space/api-reference/c/profhooks/) + +## External plugins (C) + +- [profanity-plugins by Reimar](https://github.com/ReimarPB/profanity-plugins) - Collection of plugins + + +## External plugins (Python) + +- [profanity-copy-message-plugin](https://github.com/lonski/profanity-copy-message-plugin) - Copy messages to clipboard. diff --git a/src/antispam.py b/src/antispam.py new file mode 100644 index 0000000..90457a7 --- /dev/null +++ b/src/antispam.py @@ -0,0 +1,549 @@ +""" +Antispam plugin for CProof. + +Asks a question from new contacts to verify their humanity. +""" + +import math +import prof +import re +import datetime +from uuid import uuid4 + +# Constants +_antispam_defaults = { + 'msg': 'Please, solve the captcha', + 'question': 'Where a bullet must be lodged in order for rifle to shoot?', + 'answer': 'Chamber', + 'donemsg': 'You passed antispam. Congratulations!', + 'blockmsg': 'You\'ve been blocked. Try again {} minutes later', + 'errormsg': 'Wrong answer. Please, try again. The question is', + 'otrmsg': 'Please, turn off the OTR and then send the answer.', + 'unbanmsg': 'Your ban has been lifted. You can try again.', + 'maxtries': 5, + 'blocktime': 5, + 'debug': 'off', + 'dry': 'off' +} + +_time_format = "%d.%m.%Y %H:%M:%S" + +_roster = [] + +_page_limit = 10 + +_egg_counter = 0 + +_as_settings_strings = ['msg', 'question', 'answer', 'donemsg', 'blockmsg', 'otrmsg', 'errormsg', 'unbanmsg', 'debug', 'dry'] +_as_settings_ints = {'maxtries': {'min': 2, 'max': 1000}, 'blocktime': {'min': -1, 'max': 100000}} +_as_settings = [*_as_settings_strings, *_as_settings_ints] + + +# Handlers + +def prof_init(version, status, account_name, fulljid): + synopsis = [ + "/antispam on|off", + "/antispam settings", + *[f"/antispam set {x} " for x in _as_settings_strings], + "/antispam set maxtries ", + "/antispam set blocktime ", + "/antispam counter []", + "/antispam history []", + "/antispam blocklist [|add|remove |clear]", + "/antispam whitelist [|add|remove |clear]", + ] + description = "Prevents spam messages by asking new contacts questions." + args = [ + [ "set msg ", "Set captcha message" ], + [ "set question ", "Set custom captcha question" ], + [ "set answer ", "Set answer to captcha question" ], + [ "set donemsg ", "Set congratulation message for passing antispam" ], + [ "set blockmsg ", "Set message that user is blocked. Use \"{}\" to show amounts of minutes before unlock. Example: You are banned. Unban in {} minutes." ], + [ "set otrmsg ", "Set answer in case if unauthorized user tries to initiate OTR session." ], + [ "set errormsg ", "Set prefix to answer in case if wrong answer given." ], + [ "set unbanmsg ", "Set message given when temporary ban is finished. Set to \"None\" to prevent sending message." ], + [ "set maxtries ", "Set maximum amount of tries before ban. Set -1 to make unlimited." ], + [ "set blocktime ", "Set time to block user for. Set -1 to make ban unlimited." ], + [ "set debug ", "Set debug mode on|off." ], + [ "set dry ", "Set dry mode on|off. In dry mode antispam doesn't block messages, mode is needed for debug use." ], + [ "counter [|clear]", "Show or clear wrong answer counter" ], + [ "history [|clear]", "Show or clear history" ], + [ "blocklist [add|remove |clear]", "Show banned users, or add|remove user from ban" ], + [ "whitelist [add|remove |clear]", "Whitelist users. Whitelist have priority over bans." ], + ] + examples = [ + "/antispam on", + "/antispam set msg Please, solve the captcha.", + "/antispam set question 2 + 2? (1984)", + "/antispam set answer 5", + "/antispam set donemsg You passed antispam. Now your messages are delivered.", + "/antispam set blockmsg You've been blocked by antispam. Please, try again later in {} minute(s).", + "/antispam set maxtries 5", + "/antispam set blocktime 69", + "/antispam blocklist clear", + "/antispam history 42", + ] + + prof.register_command("/antispam", 1, 40, synopsis, description, args, examples, _cmd_antispam) + + prof.register_command("/as", 1, 40, _prep_alias(synopsis), description, args, _prep_alias(examples), _cmd_antispam) + + prof.completer_add("/antispam", + [ "set", "history", "blocklist", "whitelist" ] + ) + + prof.completer_add("/antispam set", + _as_settings + ) + prof.completer_add("/antispam history", + [ 'clear' ] + ) + + prof.completer_add("/antispam blocklist", + [ 'add', 'remove', 'clear' ] + ) + + prof.completer_add("/antispam whitelist", + [ 'add', 'remove', 'clear' ] + ) + + prof.register_timed(_update_banned_list, 60) + + _force_update_roster((fulljid.split('/')[0] if fulljid else '')) + + prof.cons_show("[AntiSpam] plugin started.") + if _is_dry_mode(): + prof.cons_show("[AntiSpam] WARNING: dry-mode is on, no messages are going to be blocked.") + +def prof_on_message_stanza_send(stanza): + body = _parse_tag_content(stanza, 'body') + if body and (to := _parse_attr(stanza, 'to').split('/', 1)[0]): + if not _is_authorized(to): + prof.settings_string_list_add("antispam", "authorized", to) + return stanza + +def prof_on_presence_stanza_send(stanza): + if _parse_attr(stanza, 'type') == 'subscribe' and (to := _parse_attr(stanza, 'to').split('/', 1)[0]): + if not _is_authorized(to): + prof.settings_string_list_add("antispam", "authorized", to) + return stanza + + +def prof_on_message_stanza_receive(stanza) -> bool: + return _antispam_check_wrapper(stanza, 'msg') + +def prof_on_presence_stanza_receive(stanza) -> bool: + return _antispam_check_wrapper(stanza, 'presence') + +def prof_on_iq_stanza_receive(stanza) -> bool: + if 'jabber:iq:roster' in stanza and 'type="result"' in stanza: + _update_roster(stanza) + return _antispam_check_wrapper(stanza, 'iq') + +# Custom functions + +def _cmd_antispam(*args): + if not len(args): + _show_cmd_error() + if args[0] == "on": + prof.settings_boolean_set("antispam", "enabled", True) + prof.cons_show("Antispam is enabled.") + return + if args[0] == "off": + prof.settings_boolean_set("antispam", "enabled", False) + prof.cons_show("Antispam is disabled.") + return + if args[0] == "set": + if len(args) < 3: + _show_cmd_error('More arguments required') + return + key = args[1] + if key not in _as_settings: + _show_cmd_error(f'Wrong setting. You can use only {_as_settings}') + return + if args[2] == "default": + _set_default_values(key) + return + if _set_values(key, args[2:]): + prof.cons_show(f'Antispam setting {key} was successfully changed to "{" ".join(args[2:])}"') + else: + _show_cmd_error("Something went wrong, setting wasn't changed") + return + if args[0] == "settings": + prof.cons_show("===== Antispam settings =====") + for key in _as_settings: + value = _get_string_setting(key) if key in _as_settings_strings else _get_int_setting(key) + fvalue = f'"{value}"' if key in _as_settings_strings else value + prof.cons_show(f'{key} is {fvalue}') + return + if args[0] in ["counter", "history", "authorized"]: + return _cmd_show_or_clear(args) + if args[0] == "blocklist": + return _cmd_as_blocklist(args) + if args[0] == "whitelist": + return _cmd_as_whitelist(args) + if args[0] == "egg": + global _egg_counter + if _egg_counter == 10: + prof.cons_show(f"Do you know that it's an antispam plugin and not an egg farm? No more eggs for you!") + return + _egg_counter += 1 + prof.cons_show(f"Egg number {_egg_counter} has been planted.") + if _egg_counter == 5: + prof.cons_show(f"What do you need so many eggs for?") + return + if args[0] == "roster": + prof.cons_show("\n".join(_roster)) + return + prof.cons_bad_cmd_usage('/antispam') + +def _cmd_show_or_clear(args): + if len(args) == 1: + return _show_page(args[0]) + if len(args) == 2: + if args[1] == "clear": + if prof.settings_string_list_clear("antispam", args[0]): + prof.cons_show(f'{args[0].capitalize()} has been cleared.') + else: + prof.cons_show(f'{args[0].capitalize()} is already empty.') + return + return _show_page(args[0], args[1]) + prof.cons_bad_cmd_usage('/antispam') + +def _cmd_as_blocklist(args): + if len(args) < 3: + return _cmd_show_or_clear(args) + if args[1] == "add": + for jid in args[2:]: + ban_time = _ban_user(jid, full_jid=False, time_limited=False) + prof.cons_show(f'JID "{jid}" has been banned {ban_time}.') + elif args[1] == "remove": + for jid in args[2:]: + if _unban_user(jid): + prof.cons_show(f'JID "{jid}" has been unbanned.') + else: + prof.cons_show(f'JID "{jid}" has not been found in the blocklist.') + +def _cmd_as_whitelist(args): + if len(args) < 3: + return _cmd_show_or_clear(args) + if args[1] == "add": + for jid in args[2:]: + prof.settings_string_list_add("antispam", "whitelist", jid) + prof.cons_show(f'JID "{jid}" has been whitelisted.') + elif args[1] == "remove": + for jid in args[2:]: + if prof.settings_string_list_remove("antispam", "whitelist", jid): + prof.cons_show(f'JID "{jid}" has been removed from the whitelist.') + else: + prof.cons_show(f'JID "{jid}" has not been found in the whitelist.') + +def _show_cmd_error(error="", user_mistake=True) -> None: + """Used to shows str:error when user typed wrong input""" + if user_mistake: + prof.cons_bad_cmd_usage('/antispam') + if error: + prof.cons_show(error) + + +def _set_default_values(key) -> bool: + default_value = _antispam_defaults[key] + if (key in _as_settings_strings): + prof.settings_string_set("antispam", key, default_value) + elif (key in _as_settings_ints): + prof.settings_int_set("antispam", key, default_value) + prof.cons_show(f'Antispam setting {key} was successfully set to its default ({default_value}).') + +def _set_values(key, value) -> bool: + """Returns Bool True if values has been set, False is value is invalid""" + value = ' '.join(value) + if key in _as_settings_strings: + prof.settings_string_set("antispam", key, value) + return True + if key in _as_settings_ints: + min_val, max_val = _as_settings_ints[key]['min'], _as_settings_ints[key]['max'] + if not _represents_int_in_range(value, min_val, max_val): + prof.cons_show(f'Value of {key} must be an integer between {min_val} and {max_val}!') + return False + prof.settings_int_set("antispam", key, int(value)) + return True + return False + +def _parse_tag_content(stanza, tag): + tmp = re.search(f"<{tag}.*?>([\S\s]*?)", stanza) # don't blame me, it works :D + try: + return tmp.group(1) if tmp else "" + except IndexError as e: + return "" + +def _parse_attr(stanza, attribute) -> str: + """Parse attribute from the first tag""" + tmp = re.search(f'^\s*<[^>]+{attribute}="([^"]+)"', stanza) + try: + return tmp.group(1) if tmp else "" + except IndexError as e: + return "" + +def _parse_sender(stanza) -> str: + """Returns sender from stanza or empty string if not found""" + if " str: + """Returns id from stanza or empty string if not found""" + return _parse_attr(stanza, "id") + +def _is_dry_mode() -> bool: + return _get_string_setting("dry") == "on" + +def _antispam_check_wrapper(stanza, stanza_type) -> bool: + result = _antispam_check(stanza, stanza_type) + dry_mode = _is_dry_mode() + if not result: + if _get_string_setting("debug") == "on" or dry_mode: + prof.cons_show(f"[AntiSpam{' (DryMode)' if dry_mode else ''}] Blocked stanza: {stanza}") + else: + prof.log_debug(f"[AntiSpam] Blocked stanza: {stanza}") + return result or _is_dry_mode() + +def _antispam_check(stanza, stanza_type) -> bool: + """Returns Bool True if message is not spam, False if message should be blocked (spam/unverified sender)""" + sender = _parse_sender(stanza) + stanza_id = _parse_id(stanza) + if not sender and "from=" in stanza: + prof.log_warning(f"In the following stanza From is present, but not catched: \n{stanza}") + if not _is_activated(): + return True + if _is_whitelisted(sender) or _is_in_roster(sender): + return True + if _is_banned(sender): + return False + if _is_authorized(sender): + return True + if not _roster: + prof.log_debug(f"[AntiSpam] Passed message because roster was empty. Stanza: {stanza}") + return True + if stanza_type == "presence": + if re.search('^\s*', stanza): + _send_stanza(f'') + _send_stanza(f'{_get_string_setting("msg")}{_get_string_setting("question")}') + _counter_inc(sender) + elif stanza_type == "iq": + if re.search('^\s*]+type="set"', stanza): + to = f'to="{sender}" ' if sender else '' + _send_stanza(f'') + return False + return True + elif stanza_type == "msg": + if _parse_attr(stanza, "type") == "error": + prof.settings_string_list_add("antispam", "history", f"{_get_time()}|{sender}|{_parse_tag_content(stanza, 'body')}") + return False + if not _is_correct_answer(stanza): + if (is_otr_requested := "OTRv2" in stanza): + _send_stanza(f'{_get_string_setting("otrmsg")}') + prof.settings_string_list_add("antispam", "history", f"{_get_time()}|{sender}|{_parse_tag_content(stanza, 'body')}") + wrong_tries = _get_counter(sender) + subject = f'{_get_string_setting("msg")}' if wrong_tries == 0 else '' + wrong_answer = _get_string_setting("errormsg")+'\n' if wrong_tries > 0 and not is_otr_requested else '' + _send_stanza(f'{subject}{wrong_answer}{_get_string_setting("question")}') + _counter_inc(sender) + return False + prof.settings_string_list_add("antispam", "authorized", sender) + _send_stanza(f'{_get_string_setting("donemsg")}') + return False + +def _force_update_roster(myjid=None) -> None: + global _roster + if not _roster and myjid: + _roster = [myjid] + prof.send_stanza(f'\') + return + +def _update_roster(stanza) -> None: + global _roster + _roster = [ + *re.findall(']+to="([^"/]+)["/]', stanza), + *[x for x,y in re.findall(']+jid="([^"]+?)"[^>]+subscription="([^"]+?)"', stanza) if y == "both" or y == "to"], + *[x for y,x in re.findall(']+subscription="([^"]+?)"[^>]+jid="([^"]+?)"', stanza) if y == "both" or y == "to"], + ] + + +def _is_activated() -> bool: + return prof.settings_boolean_get("antispam", "enabled", True) + +def _update_banned_list() -> None: + ban_list = prof.settings_string_list_get("antispam", "blocklist") or [] + for banned_user in ban_list: + if not (tmp := _unpack_banned_user(banned_user)): + continue + ban_time, ban_type, jid = tmp + if ban_time != "Forever" and not _is_future_time(ban_time): + unbanmsg = _get_string_setting("unbanmsg") + if unbanmsg != "None": + _send_stanza(f'{unbanmsg}') + prof.settings_string_list_remove("antispam", "blocklist", banned_user) + return + +def _unpack_banned_user(banned_user): + """Returns None or Tuple of banned user info in (time_banned, ban_type, jid) format""" + tmp = banned_user.split("|", 2) + if len(tmp) != 3: + prof.cons_show(f"Problem on banned user unpacking: \"{banned_user}\". Deleting...") + prof.settings_string_list_remove("antispam", "blocklist", banned_user) + return + return tmp + +def _is_in_roster(jid) -> bool: + return jid in _roster + +def _is_authorized(jid) -> bool: + return jid in (prof.settings_string_list_get("antispam", "authorized") or []) + +def _is_banned(jid) -> bool: + ban_list = prof.settings_string_list_get("antispam", "blocklist") or [] + for banned_user in ban_list: + if not (tmp := _unpack_banned_user(banned_user)): + continue + ban_time, ban_type, bjid = tmp + if ban_type == "F": # On full JID ban (e.g. "user@example.com") + if jid == bjid and (ban_time == "Forever" or _is_future_time(ban_time)): + return True + else: # On partial JID ban (e.g. "@example.com", "spammer") + if bjid in jid and (ban_time == "Forever" or _is_future_time(ban_time)): + return True + return False + +def _is_whitelisted(jid) -> bool: + wl_users = (prof.settings_string_list_get("antispam", "whitelist") or []) + return any(wj in jid for wj in wl_users) + +def _get_validate_count(c): + tmp = c.split('|', 1) + if len(tmp) != 2: + prof.log_warning(f'Invalid counter entry: "{c}". Removing...') + prof.settings_string_list_remove("antispam", "counter", c) + return + return int(tmp[0]), tmp[1] + +def _counter_inc(jid) -> None: + counter = prof.settings_string_list_get("antispam", "counter") or [] + for c in counter: + if not (tmp := _get_validate_count(c)): + continue + count, cjid = tmp + if jid == cjid: + prof.settings_string_list_remove("antispam", "counter", c) + prof.settings_string_list_add("antispam", "counter", f"{count+1}|{jid}") + if count % _get_int_setting("maxtries") == 0: + ban_time = _ban_user(jid) + blockmsg = _get_string_setting("blockmsg").replace('{}', str(ban_time)) + _send_stanza(f'{blockmsg}') + return + prof.settings_string_list_add("antispam", "counter", f"1|{jid}") + + +def _get_counter(jid) -> int: + counter = prof.settings_string_list_get("antispam", "counter") or [] + for c in counter: + if not (tmp := _get_validate_count(c)): + continue + count, cjid = tmp + if jid == cjid: + return count + return 0 + +def _show_page(table, page_num=1) -> None: + """Shows a page from table N (table is fetched from string_list setting with name) to plugin user through cons_show.""" + if not _represents_int_in_range(page_num, 1): + prof.cons_show("Use correct page number. It must be integer from 1 to 10000.") + return + page_num = int(page_num) + page = page_num-1 + table_content = prof.settings_string_list_get("antispam", table) or [] + table_size = len(table_content) + total_pages = math.ceil(table_size/_page_limit) + if not table_content: + return prof.cons_show(f"Nothing to display, {table} is empty") + if page_num > total_pages: + return prof.cons_show(f"Page {page_num} does not exist. There are only {total_pages} pages") + page_content = table_content[page*_page_limit:page_num*_page_limit] + if not page_content: + return prof.cons_show(f"Nothing to display, {table} on Page {page_num} is empty") + for x in page_content: + formatting = (lambda x: x) + prof.cons_show(formatting(x)) + prof.cons_show(f"Page {page_num}/{total_pages}. Currently {table_size} entries.") + +def _ban_user(jid, full_jid=True, time_limited=True, custom_time_limit=None): + """Adds user to ban list""" + ban_type = "F" if full_jid else "P" + if time_limited and (ban_time := custom_time_limit if custom_time_limit else _get_int_setting("blocktime")) != -1: + ban_time_formatted = (datetime.datetime.now() + datetime.timedelta(minutes=ban_time)).strftime("%d.%m.%Y %H:%M:%S") + _unban_user(jid) + prof.settings_string_list_add("antispam", "blocklist", f"{ban_time_formatted}|{ban_type}|{jid}") + return ban_time + prof.settings_string_list_add("antispam", "blocklist", f"Forever|{ban_type}|{jid}") + return "forever" + +def _unban_user(jid): + """Removes user from ban list. True on success, otherwise false.""" + ban_list = prof.settings_string_list_get("antispam", "blocklist") or [] + for banned_user in ban_list: + if not (tmp := _unpack_banned_user(banned_user)): + continue + ban_time, ban_type, bjid = tmp + if bjid == jid: + return prof.settings_string_list_remove("antispam", "blocklist", banned_user) + return False + + +# Settings utils + +def _get_string_setting(setting) -> str: + return prof.settings_string_get("antispam", setting, _antispam_defaults[setting]) + +def _get_int_setting(setting) -> int: + return prof.settings_int_get("antispam", setting, _antispam_defaults[setting]) + +# Utils + +def _prep_alias(replace_list): + return [x.replace("/antispam", "/as") for x in replace_list] + +def _is_correct_answer(stanza): + correct_answer = _get_string_setting('answer') + answer = _parse_tag_content(stanza, "body") + if not answer: + prof.log_warning(f'Invalid mesage stanza. Ignoring user input. Stanza: {stanza}') + return False + return answer.strip().lower() == correct_answer.strip().lower() + +def _is_future_time(time) -> bool: + """Checks formatted time and returns True if time from parameter is in the future""" + try: + ptime = datetime.datetime.strptime(time, _time_format) + except ValueError as e: + prof.log_error(f"Exception raised when time was compared (input time: {time})\n{e}") + return True + return ptime > datetime.datetime.now() + +def _get_time(): + """Returns current time formatted""" + return datetime.datetime.now().strftime(_time_format) + +def _represents_int_in_range(s, num_min=-1, num_max=10000) -> bool: + """Returns True if passed number is integer and it is in provided range (from -1 to 10000 by default)""" + try: + num = int(s) + return num >= num_min and num <= num_max + except ValueError: + return False + else: + return True + +def _send_stanza(stanza): + if not _is_dry_mode(): + return prof.send_stanza(stanza) + diff --git a/src/ascii.py b/src/ascii.py index da62eed..f49124a 100644 --- a/src/ascii.py +++ b/src/ascii.py @@ -1,5 +1,6 @@ """ Print something in ASCII text. + Running from the console will print the text to the console. Running in a chat or room window will send the ASCII text diff --git a/src/autocorrector.py b/src/autocorrector.py index 87b8063..dedadd3 100644 --- a/src/autocorrector.py +++ b/src/autocorrector.py @@ -1,5 +1,6 @@ """ autocorrector - Replace common typos + My typing style is such a mess. But I invest more time in correcting it programmatically than training myself to type better. Maybe thats the problem? Anyway, here is a plugin for all keyboard messies like me. diff --git a/src/browser.py b/src/browser.py index a2c325f..df9720e 100644 --- a/src/browser.py +++ b/src/browser.py @@ -72,7 +72,7 @@ def prof_init(version, status, account_name, fulljid): [ "", "URL to open in the browser" ] ] examples = [ - "/browser http://www.profanity.im" + "/browser http://jabber.space/" ] prof.register_command("/browser", 0, 1, synopsis, description, args, examples, _cmd_browser) diff --git a/src/c/moj/.gitignore b/src/c/moj/.gitignore new file mode 100644 index 0000000..2054f5b --- /dev/null +++ b/src/c/moj/.gitignore @@ -0,0 +1,7 @@ +moj +moj.exe +*.o +*.so +gen/lookup.c +gen/emoji.json +gen/emoji.csv diff --git a/src/c/moj/COPYING b/src/c/moj/COPYING new file mode 100644 index 0000000..226bac9 --- /dev/null +++ b/src/c/moj/COPYING @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2018, Adrian "vifino" Pistol + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/src/c/moj/Makefile b/src/c/moj/Makefile new file mode 100644 index 0000000..f2eda6c --- /dev/null +++ b/src/c/moj/Makefile @@ -0,0 +1,68 @@ +# moj makefile +CC ?= cc + +MOJILIST=https://raw.githubusercontent.com/milesj/emojibase/master/packages/data/en/raw.json + +# Do not touch. +all: moj libmoj.so + +# Generate lookup lists +gen/emoji.json: + mkdir -p gen + wget -O $@ ${MOJILIST} + +gen/emoji.csv: gen/emoji.json + jq -r '.[] | select(.emoji) | [.emoji] + .shortcodes | @csv' -- \ + gen/emoji.json | \ + sed 's/^"//;s/"$$//' | awk -f src/process.awk > $@ + +gen/emoji.gperf: src/gen_gperf.sh gen/emoji.csv + ./src/gen_gperf.sh > ./gen/emoji.gperf + +gen/emoji_list.h: src/gen_list.sh gen/emoji.csv + ./src/gen_list.sh > $@ + +gen/lookup.c: gen/emoji.gperf + gperf --global-table --readonly-tables \ + --struct-type --includes \ + --compare-lengths --compare-strncmp \ + --ignore-case -Q emoji_spool \ + -H emoji_name_hash -N emoji_lookup_ref -m 3 \ + --output-file=$@ $^ + +# Compile our code. +src/moj.o: gen/lookup.c gen/emoji_list.h src/moj.c + $(CC) -c ${CFLAGS} ${CPPFLAGS} -o $@ src/moj.c + +libmoj.so: src/moj.o + $(CC) -shared -fPIC ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $< + +moj: src/main.c src/moj.o + $(CC) ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $^ + +# Profanity plugin. +profanity_emoji.so: src/profanity_emoji.c src/moj.o + $(CC) -shared -fPIC ${CFLAGS} ${CPPFLAGS} -o $@ ${LDFLAGS} $^ + +profanity: profanity_emoji.so + +# Install +install: moj + install -d $(DESTDIR)/$(PREFIX)/bin + install moj $(DESTDIR)/$(PREFIX)/bin + +install-devel: libmoj.so + install -d $(DESTDIR)/$(PREFIX)/lib + install libmoj.so $(DESTDIR)/$(PREFIX)/lib + install -d $(DESTDIR)/$(PREFIX)/include + cp src/moj.h $(DESTDIR)/$(PREFIX)/include/libmoj.h + +uninstall: + rm -f $(DESTDIR)/$(PREFIX)/bin/moj +uninstall-devel: + rm -f $(DESTDIR)/$(PREFIX)/lib/libmoj.so $(DESTDIR)/$(PREFIX)/include/libmoj.h + + +# Cleanup +clean: + rm -f moj gen/lookup.c src/*.o profanity_emoji.so diff --git a/src/c/moj/README.md b/src/c/moj/README.md new file mode 100644 index 0000000..9ac6b5e --- /dev/null +++ b/src/c/moj/README.md @@ -0,0 +1,28 @@ +# moj +'moj is for those of us that want something more minimal than emojify. + +Reasons to use moj instead of emojify: +- only 3 letters(!), huge reduction compared to 7 of emojify. +- written in C, compile time hash table generation, no interpreted language. +- VERY FAST. How does 0.002s to generate the average commit messages of this project sound? This is on a ThinkPad T61. A potato. +- data not mixed with code, only generated data/glue and handwritten code. (seperate files) + +# Requirements +- Make (compile time) +- C99 compiler(?). (compile time) +- gperf (compile time) + +No runtime deps. + +To regen the emoji list you also need: +- wget +- jq +- awk + +# Credits +[milesj/emojibase](https://github.com/milesj/emojibase) for the JSON Emoji list. Many thanks + +vifino for doing everything else. + +# License +ISC. diff --git a/src/c/moj/gen/emoji.gperf b/src/c/moj/gen/emoji.gperf new file mode 100644 index 0000000..9b43119 --- /dev/null +++ b/src/c/moj/gen/emoji.gperf @@ -0,0 +1,1810 @@ +%{ +// THIS FILE IS GENERATED BY src/gen_list.sh. +// DO NOT TOUCH HERE. +%} +struct emoji_ref { char* name; unsigned int ref; }; +%% +gleeful, 1 +blissful, 2 +grin, 2 +joyful, 3 +haha, 3 +entertained, 4 +rofl, 4 +glad, 5 +smile, 5 +happy, 6 +embarassed, 7 +amused, 8 +laugh, 8 +lol, 8 +coy, 9 +wink, 9 +anxious, 10 +blush, 10 +yum, 11 +savour, 11 +confident, 12 +lovestruck, 13 +flirty, 14 +love, 15 +kiss, 16 +happy_kiss, 17 +loving_kiss, 18 +relaxed, 19 +pleased, 20 +hugging, 21 +hug, 21 +starstruck, 22 +curious, 23 +thinking, 23 +think, 23 +contempt, 24 +indifferent, 25 +neutral, 25 +apathetic, 26 +expressionless, 26 +vacant, 27 +no_mouth, 27 +disbelief, 28 +cocky, 29 +smirk, 29 +persevered, 30 +hopeful, 31 +surprised, 32 +silenced, 33 +zipper_mouth, 33 +hushed, 34 +sleepy, 35 +tired, 36 +exhausted, 37 +sleeping, 37 +sleep, 37 +relieved, 38 +playful, 39 +tongue_out, 39 +mischievous, 40 +facetious, 41 +lmao, 41 +drooling, 42 +drool, 42 +unamused, 43 +shamed, 44 +pensive, 45 +confused, 46 +ecstatic, 47 +upside_down, 47 +pretentious, 48 +money_mouth, 48 +astonished, 49 +sad, 50 +frowning, 50 +frown, 50 +cheerless, 51 +confounded, 52 +disappointed, 53 +worried, 54 +annoyed, 55 +hrmph, 55 +upset, 56 +cry, 56 +distressed, 57 +sob, 57 +bored, 58 +anguished, 59 +wtf, 59 +fearful, 60 +weary, 61 +shocked, 62 +exploding_head, 62 +grimaced, 63 +frustrated, 64 +frightened, 65 +scream, 65 +overheating, 66 +overheat, 66 +freezing, 67 +freez, 67 +flushed, 68 +crazy, 69 +dizzy, 70 +enraged, 71 +pout, 71 +angry, 72 +censored, 73 +ill, 74 +mask, 74 +sick, 75 +injured, 76 +nauseated, 77 +vomiting, 78 +vomit, 78 +sneezing, 79 +sneez, 79 +innocent, 80 +halo, 80 +cowboy, 81 +partying, 82 +party, 82 +celebrating, 82 +celebrat, 82 +woozy, 83 +pleading, 84 +plead, 84 +lying, 85 +shushing, 86 +shush, 86 +gasp, 87 +monocle, 88 +nerd, 89 +imp, 90 +angry_imp, 91 +clown, 92 +ogre, 93 +goblin, 94 +skull, 95 +crossbones, 96 +ghost, 97 +alien, 98 +alien_monster, 99 +space_invader, 99 +robot, 100 +poop, 101 +smiling_cat, 102 +grinning_cat, 103 +joyful_cat, 104 +lovestruck_cat, 105 +smirking_cat, 106 +kissing_cat, 107 +weary_cat, 108 +crying_cat, 109 +pouting_cat, 110 +see_no_evil, 111 +hear_no_evil, 112 +speak_no_evil, 113 +baby, 114 +child, 115 +boy, 116 +girl, 117 +adult, 118 +man, 119 +woman, 120 +older_adult, 121 +older_man, 122 +older_woman, 123 +man_health_worker, 124 +woman_health_worker, 125 +man_student, 126 +woman_student, 127 +man_teacher, 128 +woman_teacher, 129 +man_judge, 130 +woman_judge, 131 +man_farmer, 132 +woman_farmer, 133 +man_cook, 134 +woman_cook, 135 +man_mechanic, 136 +woman_mechanic, 137 +man_factory_worker, 138 +woman_factory_worker, 139 +man_office_worker, 140 +woman_office_worker, 141 +man_scientist, 142 +woman_scientist, 143 +man_technologist, 144 +woman_technologist, 145 +man_singer, 146 +woman_singer, 147 +man_artist, 148 +woman_artist, 149 +man_pilot, 150 +woman_pilot, 151 +man_astronaut, 152 +woman_astronaut, 153 +man_firefighter, 154 +woman_firefighter, 155 +police_officer, 156 +man_police_officer, 157 +woman_police_officer, 158 +detective, 159 +man_detective, 160 +woman_detective, 161 +guard, 162 +man_guard, 163 +woman_guard, 164 +construction_worker, 165 +man_construction_worker, 166 +woman_construction_worker, 167 +prince, 168 +princess, 169 +person_turban, 170 +turban, 170 +man_turban, 171 +woman_turban, 172 +man_chinese_cap, 173 +woman_headscarf, 174 +bearded_person, 175 +blond_person, 176 +blond_man, 177 +blond_woman, 178 +red_haired_man, 179 +red_haired_woman, 180 +curly_haired_man, 181 +curly_haired_woman, 182 +bald_man, 183 +bald_woman, 184 +white_haired_man, 185 +white_haired_woman, 186 +man_tuxedo, 187 +bride_veil, 188 +pregnant_woman, 189 +breast_feeding, 190 +breast_feed, 190 +baby_angel, 191 +santa, 192 +mrs_claus, 193 +hero, 194 +superhero, 194 +woman_hero, 195 +man_hero, 196 +villain, 197 +supervillain, 197 +woman_villain, 198 +man_villain, 199 +mage, 200 +woman_mage, 201 +man_mage, 202 +fairy, 203 +woman_fairy, 204 +man_fairy, 205 +vampire, 206 +woman_vampire, 207 +man_vampire, 208 +merperson, 209 +mermaid, 210 +merman, 211 +elf, 212 +woman_elf, 213 +man_elf, 214 +genie, 215 +woman_genie, 216 +man_genie, 217 +zombie, 218 +woman_zombie, 219 +man_zombie, 220 +person_frowning, 221 +man_frowning, 222 +woman_frowning, 223 +person_pouting, 224 +pouting, 224 +man_pouting, 225 +woman_pouting, 226 +person_gesturing_no, 227 +gesturing_no, 227 +man_gesturing_no, 228 +woman_gesturing_no, 229 +person_gesturing_ok, 230 +gesturing_ok, 230 +man_gesturing_ok, 231 +woman_gesturing_ok, 232 +person_tipping_hand, 233 +tipping_hand, 233 +man_tipping_hand, 234 +woman_tipping_hand, 235 +person_raising_hand, 236 +raising_hand, 236 +man_raising_hand, 237 +woman_raising_hand, 238 +person_bowing, 239 +bowing, 239 +man_bowing, 240 +woman_bowing, 241 +person_facepalming, 242 +facepalming, 242 +facepalm, 242 +man_facepalming, 243 +woman_facepalming, 244 +person_shrugging, 245 +shrugging, 245 +shrug, 245 +man_shrugging, 246 +woman_shrugging, 247 +person_getting_massage, 248 +getting_massage, 248 +man_getting_face_massage, 249 +woman_getting_face_massage, 250 +person_getting_haircut, 251 +getting_haircut, 251 +man_getting_haircut, 252 +woman_getting_haircut, 253 +person_walking, 254 +walking, 254 +walk, 254 +man_walking, 255 +woman_walking, 256 +person_running, 257 +running, 257 +run, 257 +man_running, 258 +woman_running, 259 +dancer, 260 +woman_dancing, 260 +man_dancing, 261 +people_bunny_ears_partying, 262 +people_bunny_ears_party, 262 +men_bunny_ears_partying, 263 +women_bunny_ears_partying, 264 +person_steamy_room, 265 +steamy_room, 265 +woman_steamy_room, 266 +man_steamy_room, 267 +person_climbing, 268 +climbing, 268 +climb, 268 +woman_climbing, 269 +man_climbing, 270 +person_lotus_position, 271 +lotus_position, 271 +woman_lotus_position, 272 +man_lotus_position, 273 +bath, 274 +in_bed, 275 +levitate, 276 +speaking_head, 277 +bust_silhouette, 278 +busts_silhouette, 279 +person_fencing, 280 +fencing, 280 +fenc, 280 +horse_racing, 281 +horse_rac, 281 +skier, 282 +snowboarder, 283 +person_golfing, 284 +golfing, 284 +man_golfing, 285 +woman_golfing, 286 +person_surfing, 287 +surfing, 287 +surf, 287 +man_surfing, 288 +woman_surfing, 289 +person_rowing_boat, 290 +rowing_boat, 290 +man_rowing_boat, 291 +woman_rowing_boat, 292 +person_swimming, 293 +swimming, 293 +swim, 293 +man_swimming, 294 +woman_swimming, 295 +person_bouncing_ball, 296 +bouncing_ball, 296 +man_bouncing_ball, 297 +woman_bouncing_ball, 298 +person_lifting_weights, 299 +lifting_weights, 299 +man_lifting_weights, 300 +woman_lifting_weights, 301 +person_biking, 302 +biking, 302 +bik, 302 +man_biking, 303 +woman_biking, 304 +person_mountain_biking, 305 +mountain_biking, 305 +mountain_bik, 305 +man_mountain_biking, 306 +woman_mountain_biking, 307 +race_car, 308 +motorcycle, 309 +person_cartwheel, 310 +cartwheel, 310 +man_cartwheeling, 311 +woman_cartwheeling, 312 +people_wrestling, 313 +people_wrestl, 313 +men_wrestling, 314 +women_wrestling, 315 +person_water_polo, 316 +water_polo, 316 +man_water_polo, 317 +woman_water_polo, 318 +person_handball, 319 +handball, 319 +man_handball, 320 +woman_handball, 321 +person_juggling, 322 +juggling, 322 +juggl, 322 +man_juggling, 323 +woman_juggling, 324 +holding_hands_mw, 325 +holding_hands_wm, 325 +holding_hands_mm, 326 +holding_hands_ww, 327 +couple, 328 +kiss_mw, 329 +kiss_wm, 329 +kiss_mm, 330 +kiss_ww, 331 +couple_heart, 332 +couple_mw, 333 +couple_wm, 333 +couple_mm, 334 +couple_ww, 335 +family, 336 +family_mwb, 337 +family_mwg, 338 +family_mwgb, 339 +family_mwbb, 340 +family_mwgg, 341 +family_mmb, 342 +family_mmg, 343 +family_mmgb, 344 +family_mmbb, 345 +family_mmgg, 346 +family_wwb, 347 +family_wwg, 348 +family_wwgb, 349 +family_wwbb, 350 +family_wwgg, 351 +family_mb, 352 +family_mbb, 353 +family_mg, 354 +family_mgb, 355 +family_mgg, 356 +family_wb, 357 +family_wbb, 358 +family_wg, 359 +family_wgb, 360 +family_wgg, 361 +selfie, 362 +muscle, 363 +right_bicep, 363 +leg, 364 +foot, 365 +point_left, 366 +point_right, 367 +point_up, 368 +backhand_point_up, 369 +middle_finger, 370 +point_down, 371 +victory, 372 +fingers_crossed, 373 +vulcan, 374 +metal, 375 +call_me, 376 +splayed_hand, 377 +raised_hand, 378 +ok_hand, 379 +thumbsup, 380 ++1, 380 +yes, 380 +thumbsdown, 381 +-1, 381 +no, 381 +fist, 382 +punch, 383 +left_facing_fist, 384 +right_facing_fist, 385 +raised_backhand, 386 +wave, 387 +love_you_gesture, 388 +writing_hand, 389 +clap, 390 +open_hands, 391 +raised_hands, 392 +palms_up, 393 +pray, 394 +handshake, 395 +nail_care, 396 +ear, 397 +nose, 398 +footprints, 399 +eyes, 400 +eye, 401 +eye_bubble, 402 +brain, 403 +bone, 404 +tooth, 405 +tongue, 406 +lips, 407 +kiss_lips, 408 +cupid, 409 +heart, 410 +heartbeat, 411 +broken_heart, 412 +two_hearts, 413 +sparkling_heart, 414 +heartpulse, 415 +blue_heart, 416 +green_heart, 417 +yellow_heart, 418 +orange_heart, 419 +purple_heart, 420 +black_heart, 421 +heart_ribbon, 422 +revolving_hearts, 423 +heart_decoration, 424 +heart_exclamation, 425 +love_letter, 426 +zzz, 427 +anger, 428 +bomb, 429 +boom, 430 +collision, 430 +sweat_drops, 431 +dash, 432 +dizzy_star, 433 +speech, 434 +left_speech, 435 +right_anger_speech, 436 +thought, 437 +hole, 438 +glasses, 439 +sunglasses, 440 +goggles, 441 +lab_coat, 442 +necktie, 443 +tie, 443 +shirt, 444 +jeans, 445 +scarf, 446 +gloves, 447 +coat, 448 +socks, 449 +dress, 450 +kimono, 451 +bikini, 452 +blouse, 453 +womans_clothes, 453 +purse, 454 +handbag, 455 +pouch, 456 +clutch_bag, 456 +shopping_bags, 457 +backpack, 458 +dress_shoe, 459 +mans_shoe, 459 +sneaker, 460 +athletic_shoe, 460 +hiking_boot, 461 +flat_shoe, 462 +high_heel, 463 +womans_sandal, 464 +womans_boot, 465 +crown, 466 +womans_hat, 467 +top_hat, 468 +graduation_cap, 469 +billed_cap, 470 +helmet_cross, 471 +prayer_beads, 472 +lipstick, 473 +ring, 474 +gem, 475 +monkey_face, 476 +monkey, 477 +gorilla, 478 +dog_face, 479 +dog, 480 +poodle, 481 +wolf_face, 482 +fox_face, 483 +raccoon, 484 +cat_face, 485 +cat, 486 +lion_face, 487 +tiger_face, 488 +tiger, 489 +leopard, 490 +horse_face, 491 +horse, 492 +unicorn_face, 493 +zebra, 494 +deer, 495 +cow_face, 496 +ox, 497 +water_buffalo, 498 +cow, 499 +pig_face, 500 +pig, 501 +boar, 502 +pig_nose, 503 +ram, 504 +sheep, 505 +goat, 506 +camel, 507 +two_hump_camel, 508 +llama, 509 +giraffe, 510 +elephant, 511 +rhino, 512 +hippo, 513 +mouse_face, 514 +mouse, 515 +rat, 516 +hamster_face, 517 +rabbit_face, 518 +rabbit, 519 +chipmunk, 520 +hedgehog, 521 +bat, 522 +bear_face, 523 +koala_face, 524 +panda_face, 525 +kangaroo, 526 +badger, 527 +feet, 528 +turkey, 529 +chicken, 530 +rooster, 531 +hatching_chick, 532 +baby_chick, 533 +hatched_chick, 534 +bird, 535 +penguin, 536 +dove, 537 +eagle, 538 +duck, 539 +swan, 540 +owl, 541 +peacock, 542 +parrot, 543 +frog_face, 544 +crocodile, 545 +turtle, 546 +lizard, 547 +snake, 548 +dragon_face, 549 +dragon, 550 +sauropod, 551 +trex, 552 +spouting_whale, 553 +whale, 554 +dolphin, 555 +fish, 556 +tropical_fish, 557 +blowfish, 558 +shark, 559 +octopus, 560 +shell, 561 +crab, 562 +lobster, 563 +shrimp, 564 +squid, 565 +snail, 566 +butterfly, 567 +bug, 568 +ant, 569 +bee, 570 +beetle, 571 +cricket, 572 +spider, 573 +spider_web, 574 +scorpion, 575 +mosquito, 576 +microbe, 577 +germ, 577 +bouquet, 578 +cherry_blossom, 579 +white_flower, 580 +rosette, 581 +rose, 582 +wilted_rose, 583 +hibiscus, 584 +sunflower, 585 +blossom, 586 +tulip, 587 +seedling, 588 +seedl, 588 +evergreen_tree, 589 +deciduous_tree, 590 +palm_tree, 591 +cactus, 592 +ear_of_rice, 593 +herb, 594 +shamrock, 595 +four_leaf_clover, 596 +maple_leaf, 597 +fallen_leaf, 598 +leaves, 599 +grapes, 600 +melon, 601 +watermelon, 602 +tangerine, 603 +lemon, 604 +banana, 605 +pineapple, 606 +mango, 607 +apple, 608 +green_apple, 609 +pear, 610 +peach, 611 +cherries, 612 +strawberry, 613 +kiwi, 614 +tomato, 615 +coconut, 616 +avocado, 617 +eggplant, 618 +potato, 619 +carrot, 620 +corn, 621 +hot_pepper, 622 +cucumber, 623 +leafy_green, 624 +broccoli, 625 +mushroom, 626 +peanuts, 627 +chestnut, 628 +bread, 629 +croissant, 630 +french_bread, 631 +pretzel, 632 +bagel, 633 +pancakes, 634 +cheese, 635 +meat_on_bone, 636 +poultry_leg, 637 +cut_of_meat, 638 +bacon, 639 +hamburger, 640 +fries, 641 +pizza, 642 +hotdog, 643 +sandwich, 644 +taco, 645 +burrito, 646 +stuffed_flatbread, 647 +egg, 648 +cooking, 649 +cook, 649 +shallow_pan_of_food, 650 +stew, 651 +bowl_spoon, 652 +salad, 653 +popcorn, 654 +salt, 655 +canned_food, 656 +bento, 657 +rice_cracker, 658 +rice_ball, 659 +rice, 660 +curry, 661 +ramen, 662 +spaghetti, 663 +sweet_potato, 664 +oden, 665 +sushi, 666 +fried_shrimp, 667 +fish_cake, 668 +moon_cake, 669 +dango, 670 +dumpling, 671 +dumpl, 671 +fortune_cookie, 672 +takeout_box, 673 +icecream, 674 +shaved_ice, 675 +ice_cream, 676 +doughnut, 677 +cookie, 678 +birthday, 679 +cake, 680 +cupcake, 681 +pie, 682 +chocolate_bar, 683 +candy, 684 +lollipop, 685 +custard, 686 +honey_pot, 687 +baby_bottle, 688 +milk, 689 +coffee, 690 +tea, 691 +sake, 692 +champagne, 693 +wine_glass, 694 +cocktail, 695 +tropical_drink, 696 +beer, 697 +beers, 698 +champagne_glass, 699 +tumbler_glass, 700 +cup_straw, 701 +chopsticks, 702 +fork_knife_plate, 703 +utensils, 704 +spoon, 705 +knife, 706 +amphora, 707 +earth_africa, 708 +earth_americas, 709 +earth_asia, 710 +globe, 711 +map, 712 +japan, 713 +compass, 714 +snowy_mountain, 715 +mountain, 716 +volcano, 717 +mount_fuji, 718 +camping, 719 +camp, 719 +beach, 720 +desert, 721 +island, 722 +park, 723 +stadium, 724 +classical_building, 725 +classical_build, 725 +construction_site, 726 +brick, 727 +homes, 728 +house_abandoned, 729 +house, 730 +house_garden, 731 +office, 732 +ja_post_office, 733 +post_office, 734 +hospital, 735 +bank, 736 +hotel, 737 +love_hotel, 738 +convenience_store, 739 +school, 740 +department_store, 741 +factory, 742 +japanese_castle, 743 +castle, 744 +european_castle, 744 +wedding, 745 +wed, 745 +tokyo_tower, 746 +statue_of_liberty, 747 +church, 748 +mosque, 749 +synagogue, 750 +shinto_shrine, 751 +kaaba, 752 +fountain, 753 +tent, 754 +foggy, 755 +night_stars, 756 +cityscape, 757 +sunrise_over_mountains, 758 +sunrise, 759 +dusk, 760 +sunset, 761 +bridge_at_night, 762 +hotsprings, 763 +milky_way, 764 +carousel_horse, 765 +ferris_wheel, 766 +roller_coaster, 767 +barber, 768 +circus_tent, 769 +steam_locomotive, 770 +railway_car, 771 +bullettrain_side, 772 +bullettrain, 773 +train, 774 +metro, 775 +light_rail, 776 +station, 777 +tram, 778 +monorail, 779 +mountain_railway, 780 +tram_car, 781 +bus, 782 +oncoming_bus, 783 +trolleybus, 784 +minibus, 785 +ambulance, 786 +fire_engine, 787 +police_car, 788 +oncoming_police_car, 789 +taxi, 790 +oncoming_taxi, 791 +red_car, 792 +oncoming_automobile, 793 +blue_car, 794 +truck, 795 +lorry, 796 +tractor, 797 +bike, 798 +scooter, 799 +skateboard, 800 +motor_scooter, 801 +bus_stop, 802 +motorway, 803 +railway_track, 804 +oil_drum, 805 +fuel_pump, 806 +rotating_light, 807 +police_light, 807 +traffic_light, 808 +vertical_traffic_light, 809 +stop_sign, 810 +octagonal_sign, 810 +construction, 811 +anchor, 812 +sailboat, 813 +canoe, 814 +speedboat, 815 +cruise_ship, 816 +ferry, 817 +motorboat, 818 +ship, 819 +airplane, 820 +small_airplane, 821 +airplane_departure, 822 +airplane_arriving, 823 +airplane_arriv, 823 +seat, 824 +helicopter, 825 +suspension_railway, 826 +mountain_cableway, 827 +aerial_tramway, 828 +satellite, 829 +rocket, 830 +flying_saucer, 831 +bellhop, 832 +luggage, 833 +hourglass, 834 +hourglass_flowing, 835 +hourglass_flow, 835 +watch, 836 +alarm_clock, 837 +stopwatch, 838 +timer, 839 +clock, 840 +clock12, 841 +clock1230, 842 +clock1, 843 +clock130, 844 +clock2, 845 +clock230, 846 +clock3, 847 +clock330, 848 +clock4, 849 +clock430, 850 +clock5, 851 +clock530, 852 +clock6, 853 +clock630, 854 +clock7, 855 +clock730, 856 +clock8, 857 +clock830, 858 +clock9, 859 +clock930, 860 +clock10, 861 +clock1030, 862 +clock11, 863 +clock1130, 864 +new_moon, 865 +waxing_crescent_moon, 866 +first_quarter_moon, 867 +waxing_gibbous_moon, 868 +full_moon, 869 +waning_gibbous_moon, 870 +last_quarter_moon, 871 +waning_crescent_moon, 872 +crescent_moon, 873 +new_moon_face, 874 +first_quarter_moon_face, 875 +last_quarter_moon_face, 876 +thermometer, 877 +sun, 878 +full_moon_face, 879 +sun_face, 880 +star, 881 +star2, 882 +glowing_star, 882 +star3, 883 +shooting_star, 883 +cloud, 884 +partly_sunny, 885 +storm, 886 +overcast, 887 +cloudy, 888 +sunshower, 889 +rain, 890 +snow, 891 +lightning, 892 +lightn, 892 +tornado, 893 +fog, 894 +wind_face, 895 +cyclone, 896 +rainbow, 897 +closed_umbrella, 898 +umbrella, 899 +umbrella_rain, 900 +beach_umbrella, 901 +zap, 902 +high_voltage, 902 +snowflake, 903 +snowy_snowman, 904 +snowman, 905 +comet, 906 +fire, 907 +droplet, 908 +ocean, 909 +jack_o_lantern, 910 +christmas_tree, 911 +xmas_tree, 911 +fireworks, 912 +sparkler, 913 +firecracker, 914 +sparkles, 915 +balloon, 916 +tada, 917 +confetti_ball, 918 +tanabata_tree, 919 +bamboo, 920 +pine_decor, 920 +dolls, 921 +carp_streamer, 922 +wind_chime, 923 +moon_ceremony, 924 +rice_scene, 924 +red_envelope, 925 +ribbon, 926 +gift, 927 +reminder_ribbon, 928 +tickets, 929 +admission, 929 +ticket, 930 +military_medal, 931 +trophy, 932 +medal, 933 +first_place, 934 +second_place, 935 +third_place, 936 +soccer, 937 +baseball, 938 +softball, 939 +basketball, 940 +volleyball, 941 +football, 942 +rugby, 943 +tennis, 944 +flying_disc, 945 +bowling, 946 +bowl, 946 +cricket_game, 947 +field_hockey, 948 +hockey, 949 +lacrosse, 950 +ping_pong, 951 +badminton, 952 +boxing_glove, 953 +gi, 954 +martial_arts_uniform, 954 +goal, 955 +golf, 956 +ice_skate, 957 +fishing_pole, 958 +running_shirt, 959 +ski, 960 +sled, 961 +curling_stone, 962 +dart, 963 +8ball, 964 +crystal_ball, 965 +nazar_amulet, 966 +video_game, 967 +joystick, 968 +slot_machine, 969 +game_die, 970 +jigsaw, 971 +puzzle_piece, 971 +teddy_bear, 972 +spades, 973 +hearts, 974 +diamonds, 975 +clubs, 976 +chess_pawn, 977 +black_joker, 978 +mahjong, 979 +flower_cards, 980 +performing_arts, 981 +frame_photo, 982 +art, 983 +palette, 983 +spool, 984 +yarn, 985 +mute, 986 +no_sound, 986 +speaker, 987 +low_sound, 987 +sound, 988 +loud_sound, 989 +loudspeaker, 990 +megaphone, 991 +postal_horn, 992 +bell, 993 +no_bell, 994 +musical_score, 995 +musical_note, 996 +musical_notes, 997 +studio_microphone, 998 +level_slider, 999 +control_knobs, 1000 +microphone, 1001 +headphones, 1002 +radio, 1003 +saxophone, 1004 +guitar, 1005 +musical_keyboard, 1006 +trumpet, 1007 +violin, 1008 +drum, 1009 +mobile, 1010 +iphone, 1010 +android, 1010 +mobile_calling, 1011 +mobile_cal, 1011 +telephone, 1012 +telephone_receiver, 1013 +pager, 1014 +fax, 1015 +battery, 1016 +electric_plug, 1017 +laptop, 1018 +desktop, 1019 +computer, 1019 +printer, 1020 +keyboard, 1021 +computer_mouse, 1022 +trackball, 1023 +minidisc, 1024 +floppy_disk, 1025 +cd, 1026 +disk, 1026 +dvd, 1027 +abacus, 1028 +movie_camera, 1029 +film_frames, 1030 +projector, 1031 +clapper, 1032 +tv, 1033 +camera, 1034 +camera_flash, 1035 +video_camera, 1036 +vhs, 1037 +mag, 1038 +mag_right, 1039 +candle, 1040 +bulb, 1041 +light_bulb, 1041 +flashlight, 1042 +red_lantern, 1043 +decorative_notebook, 1044 +closed_book, 1045 +book, 1046 +green_book, 1047 +blue_book, 1048 +orange_book, 1049 +books, 1050 +notebook, 1051 +ledger, 1052 +page_curl, 1053 +scroll, 1054 +page_facing_up, 1055 +newspaper, 1056 +rolled_newspaper, 1057 +bookmark_tabs, 1058 +bookmark, 1059 +label, 1060 +moneybag, 1061 +yen, 1062 +dollar, 1063 +euro, 1064 +pound, 1065 +money_wings, 1066 +credit_card, 1067 +receipt, 1068 +ja_chart, 1069 +currency_exchange, 1070 +dollar_sign, 1071 +envelope, 1072 +email, 1073 +incoming_envelope, 1074 +envelope_arrow, 1075 +outbox_tray, 1076 +inbox_tray, 1077 +package, 1078 +mailbox, 1079 +mailbox_closed, 1080 +mailbox_mail, 1081 +mailbox_no_mail, 1082 +postbox, 1083 +ballot_box, 1084 +pencil, 1085 +black_nib, 1086 +fountain_pen, 1087 +pen, 1088 +paintbrush, 1089 +crayon, 1090 +memo, 1091 +briefcase, 1092 +file_folder, 1093 +open_file_folder, 1094 +dividers, 1095 +date, 1096 +calendar, 1096 +torn_calendar, 1097 +notepad_spiral, 1098 +calendar_spiral, 1099 +card_index, 1100 +chart_up, 1101 +chart_down, 1102 +bar_chart, 1103 +clipboard, 1104 +pushpin, 1105 +round_pushpin, 1106 +paperclip, 1107 +paperclips, 1108 +straight_ruler, 1109 +triangular_ruler, 1110 +scissors, 1111 +card_box, 1112 +file_cabinet, 1113 +trashcan, 1114 +wastebasket, 1114 +lock, 1115 +unlock, 1116 +locked_pen, 1117 +locked_key, 1118 +key, 1119 +old_key, 1120 +hammer, 1121 +pick, 1122 +hammer_pick, 1123 +tools, 1124 +hammer_wrench, 1124 +dagger, 1125 +crossed_swords, 1126 +gun, 1127 +pistol, 1127 +bow, 1128 +shield, 1129 +wrench, 1130 +nut_and_bolt, 1131 +gear, 1132 +clamp, 1133 +compression, 1133 +scales, 1134 +link, 1135 +chains, 1136 +toolbox, 1137 +magnet, 1138 +alembic, 1139 +test_tube, 1140 +petri_dish, 1141 +dna, 1142 +double_helix, 1142 +microscope, 1143 +telescope, 1144 +satellite_antenna, 1145 +syringe, 1146 +pill, 1147 +door, 1148 +bed, 1149 +couch, 1150 +toilet, 1151 +shower, 1152 +bathtub, 1153 +lotion, 1154 +safety_pin, 1155 +broom, 1156 +basket, 1157 +toilet_paper, 1158 +soap, 1159 +sponge, 1160 +fire_extinguisher, 1161 +shopping_cart, 1162 +cigarette, 1163 +smoking, 1163 +smok, 1163 +coffin, 1164 +urn, 1165 +moai, 1166 +atm, 1167 +litter_bin, 1168 +potable_water, 1169 +wheelchair, 1170 +mens, 1171 +womens, 1172 +restroom, 1173 +bathroom, 1173 +baby_symbol, 1174 +wc, 1175 +passport_control, 1176 +customs, 1177 +baggage_claim, 1178 +left_luggage, 1179 +warning, 1180 +warn, 1180 +children_crossing, 1181 +children_cros, 1181 +no_entry, 1182 +no_entry_sign, 1183 +no_bicycles, 1184 +no_smoking, 1185 +no_smok, 1185 +do_not_litter, 1186 +non_potable_water, 1187 +no_pedestrians, 1188 +no_mobile_phones, 1189 +underage, 1190 +radioactive, 1191 +biohazard, 1192 +arrow_up, 1193 +arrow_upper_right, 1194 +arrow_right, 1195 +arrow_lower_right, 1196 +arrow_down, 1197 +arrow_lower_left, 1198 +arrow_left, 1199 +arrow_upper_left, 1200 +arrow_up_down, 1201 +arrow_left_right, 1202 +arrow_left_hook, 1203 +arrow_right_hook, 1204 +arrow_heading_up, 1205 +arrow_heading_down, 1206 +clockwise, 1207 +counter_clockwise, 1208 +back, 1209 +end, 1210 +on, 1211 +soon, 1212 +top, 1213 +place_of_worship, 1214 +atom, 1215 +om_symbol, 1216 +star_of_david, 1217 +wheel_of_dharma, 1218 +yin_yang, 1219 +cross, 1220 +orthodox_cross, 1221 +star_and_crescent, 1222 +peace, 1223 +menorah, 1224 +six_pointed_star, 1225 +aries, 1226 +taurus, 1227 +gemini, 1228 +cancer, 1229 +leo, 1230 +virgo, 1231 +libra, 1232 +scorpius, 1233 +sagittarius, 1234 +capricorn, 1235 +aquarius, 1236 +pisces, 1237 +ophiuchus, 1238 +shuffle, 1239 +repeat, 1240 +repeat_single, 1241 +play, 1242 +fast_forward, 1243 +next_track, 1244 +play_pause, 1245 +reverse, 1246 +rewind, 1247 +previous_track, 1248 +up_button, 1249 +fast_up_button, 1250 +down_button, 1251 +fast_down_button, 1252 +pause, 1253 +stop, 1254 +record, 1255 +eject, 1256 +cinema, 1257 +dim, 1258 +low_brightness, 1258 +bright, 1259 +high_brightness, 1259 +signal_strength, 1260 +antenna_bars, 1260 +vibration_mode, 1261 +mobile_phone_off, 1262 +female, 1263 +female_sign, 1263 +male, 1264 +male_sign, 1264 +medical, 1265 +infinity, 1266 +recycle, 1267 +fleur-de-lis, 1268 +trident, 1269 +name_badge, 1270 +ja_beginner, 1271 +o, 1272 +white_check_mark, 1273 +checked_ballot, 1274 +check_mark, 1275 +multiplication, 1276 +x, 1277 +cross_mark, 1277 +cross_mark_button, 1278 +plus, 1279 +minus, 1280 +division, 1281 +curly_loop, 1282 +double_curly_loop, 1283 +part_alternation_mark, 1284 +eight_spoked_asterisk, 1285 +eight_pointed_star, 1286 +sparkle, 1287 +bangbang, 1288 +double_exclamation, 1288 +interrobang, 1289 +exclamation_question, 1289 +question, 1290 +white_question, 1291 +white_exclamation, 1292 +exclamation, 1293 +wavy_dash, 1294 +copyright, 1295 +registered, 1296 +tm, 1297 +hash, 1298 +asterisk, 1299 +zero, 1300 +one, 1301 +two, 1302 +three, 1303 +four, 1304 +five, 1305 +six, 1306 +seven, 1307 +eight, 1308 +nine, 1309 +ten, 1310 +100, 1311 +upper_abcd, 1312 +abcd, 1313 +1234, 1314 +symbols, 1315 +abc, 1316 +a_blood, 1317 +ab_blood, 1318 +b_blood, 1319 +cl, 1320 +cool, 1321 +free, 1322 +info, 1323 +id, 1324 +m, 1325 +new, 1326 +ng, 1327 +o_blood, 1328 +ok, 1329 +p, 1330 +sos, 1331 +up, 1332 +vs, 1333 +ja_here, 1334 +koko, 1334 +ja_service_charge, 1335 +ja_monthly_amount, 1336 +ja_not_free_of_carge, 1337 +ja_reserved, 1338 +ja_bargain, 1339 +ja_discount, 1340 +ja_free_of_charge, 1341 +ja_prohibited, 1342 +ja_acceptable, 1343 +ja_application, 1344 +ja_passing_grade, 1345 +ja_vacancy, 1346 +ja_congratulations, 1347 +ja_secret, 1348 +ja_open_for_business, 1349 +ja_no_vacancy, 1350 +small_black_square, 1351 +small_white_square, 1352 +medium_white_square, 1353 +medium_black_square, 1354 +medium_small_white_square, 1355 +medium_small_black_square, 1356 +large_black_square, 1357 +large_white_square, 1358 +large_orange_diamond, 1359 +large_blue_diamond, 1360 +small_orange_diamond, 1361 +small_blue_diamond, 1362 +up_red_triangle, 1363 +down_red_triangle, 1364 +diamond_dot, 1365 +radio_button, 1366 +black_square_button, 1367 +white_square_button, 1368 +white_circle, 1369 +black_circle, 1370 +red_circle, 1371 +blue_circle, 1372 +checkered_flag, 1373 +triangle_flag, 1374 +crossed_flags, 1375 +black_flag, 1376 +white_flag, 1377 +rainbow_flag, 1378 +pirate_flag, 1379 +jolly_roger, 1379 +flag_ac, 1380 +flag_ad, 1381 +flag_ae, 1382 +flag_af, 1383 +flag_ag, 1384 +flag_ai, 1385 +flag_al, 1386 +flag_am, 1387 +flag_ao, 1388 +flag_aq, 1389 +flag_ar, 1390 +flag_as, 1391 +flag_at, 1392 +flag_au, 1393 +flag_aw, 1394 +flag_ax, 1395 +flag_az, 1396 +flag_ba, 1397 +flag_bb, 1398 +flag_bd, 1399 +flag_be, 1400 +flag_bf, 1401 +flag_bg, 1402 +flag_bh, 1403 +flag_bi, 1404 +flag_bj, 1405 +flag_bl, 1406 +flag_bm, 1407 +flag_bn, 1408 +flag_bo, 1409 +flag_bq, 1410 +flag_br, 1411 +flag_bs, 1412 +flag_bt, 1413 +flag_bv, 1414 +flag_bw, 1415 +flag_by, 1416 +flag_bz, 1417 +flag_ca, 1418 +flag_cc, 1419 +flag_cd, 1420 +flag_cf, 1421 +flag_cg, 1422 +flag_ch, 1423 +flag_ci, 1424 +flag_ck, 1425 +flag_cl, 1426 +flag_cm, 1427 +flag_cn, 1428 +flag_co, 1429 +flag_cp, 1430 +flag_cr, 1431 +flag_cu, 1432 +flag_cv, 1433 +flag_cw, 1434 +flag_cx, 1435 +flag_cy, 1436 +flag_cz, 1437 +flag_de, 1438 +flag_dg, 1439 +flag_dj, 1440 +flag_dk, 1441 +flag_dm, 1442 +flag_do, 1443 +flag_dz, 1444 +flag_ea, 1445 +flag_ec, 1446 +flag_ee, 1447 +flag_eg, 1448 +flag_eh, 1449 +flag_er, 1450 +flag_es, 1451 +flag_et, 1452 +flag_eu, 1453 +flag_fi, 1454 +flag_fj, 1455 +flag_fk, 1456 +flag_fm, 1457 +flag_fo, 1458 +flag_fr, 1459 +flag_ga, 1460 +flag_gb, 1461 +flag_gd, 1462 +flag_ge, 1463 +flag_gf, 1464 +flag_gg, 1465 +flag_gh, 1466 +flag_gi, 1467 +flag_gl, 1468 +flag_gm, 1469 +flag_gn, 1470 +flag_gp, 1471 +flag_gq, 1472 +flag_gr, 1473 +flag_gs, 1474 +flag_gt, 1475 +flag_gu, 1476 +flag_gw, 1477 +flag_gy, 1478 +flag_hk, 1479 +flag_hm, 1480 +flag_hn, 1481 +flag_hr, 1482 +flag_ht, 1483 +flag_hu, 1484 +flag_ic, 1485 +flag_id, 1486 +flag_ie, 1487 +flag_il, 1488 +flag_im, 1489 +flag_in, 1490 +flag_io, 1491 +flag_iq, 1492 +flag_ir, 1493 +flag_is, 1494 +flag_it, 1495 +flag_je, 1496 +flag_jm, 1497 +flag_jo, 1498 +flag_jp, 1499 +flag_ke, 1500 +flag_kg, 1501 +flag_kh, 1502 +flag_ki, 1503 +flag_km, 1504 +flag_kn, 1505 +flag_kp, 1506 +flag_kr, 1507 +flag_kw, 1508 +flag_ky, 1509 +flag_kz, 1510 +flag_la, 1511 +flag_lb, 1512 +flag_lc, 1513 +flag_li, 1514 +flag_lk, 1515 +flag_lr, 1516 +flag_ls, 1517 +flag_lt, 1518 +flag_lu, 1519 +flag_lv, 1520 +flag_ly, 1521 +flag_ma, 1522 +flag_mc, 1523 +flag_md, 1524 +flag_me, 1525 +flag_mf, 1526 +flag_mg, 1527 +flag_mh, 1528 +flag_mk, 1529 +flag_ml, 1530 +flag_mm, 1531 +flag_mn, 1532 +flag_mo, 1533 +flag_mp, 1534 +flag_mq, 1535 +flag_mr, 1536 +flag_ms, 1537 +flag_mt, 1538 +flag_mu, 1539 +flag_mv, 1540 +flag_mw, 1541 +flag_mx, 1542 +flag_my, 1543 +flag_mz, 1544 +flag_na, 1545 +flag_nc, 1546 +flag_ne, 1547 +flag_nf, 1548 +flag_ng, 1549 +flag_ni, 1550 +flag_nl, 1551 +flag_no, 1552 +flag_np, 1553 +flag_nr, 1554 +flag_nu, 1555 +flag_nz, 1556 +flag_om, 1557 +flag_pa, 1558 +flag_pe, 1559 +flag_pf, 1560 +flag_pg, 1561 +flag_ph, 1562 +flag_pk, 1563 +flag_pl, 1564 +flag_pm, 1565 +flag_pn, 1566 +flag_pr, 1567 +flag_ps, 1568 +flag_pt, 1569 +flag_pw, 1570 +flag_py, 1571 +flag_qa, 1572 +flag_re, 1573 +flag_ro, 1574 +flag_rs, 1575 +flag_ru, 1576 +flag_rw, 1577 +flag_sa, 1578 +flag_sb, 1579 +flag_sc, 1580 +flag_sd, 1581 +flag_se, 1582 +flag_sg, 1583 +flag_sh, 1584 +flag_si, 1585 +flag_sj, 1586 +flag_sk, 1587 +flag_sl, 1588 +flag_sm, 1589 +flag_sn, 1590 +flag_so, 1591 +flag_sr, 1592 +flag_ss, 1593 +flag_st, 1594 +flag_sv, 1595 +flag_sx, 1596 +flag_sy, 1597 +flag_sz, 1598 +flag_ta, 1599 +flag_tc, 1600 +flag_td, 1601 +flag_tf, 1602 +flag_tg, 1603 +flag_th, 1604 +flag_tj, 1605 +flag_tk, 1606 +flag_tl, 1607 +flag_tm, 1608 +flag_tn, 1609 +flag_to, 1610 +flag_tr, 1611 +flag_tt, 1612 +flag_tv, 1613 +flag_tw, 1614 +flag_tz, 1615 +flag_ua, 1616 +flag_ug, 1617 +flag_um, 1618 +flag_un, 1619 +flag_us, 1620 +usa, 1620 +flag_uy, 1621 +flag_uz, 1622 +flag_va, 1623 +flag_vc, 1624 +flag_ve, 1625 +flag_vg, 1626 +flag_vi, 1627 +flag_vn, 1628 +flag_vu, 1629 +flag_wf, 1630 +flag_ws, 1631 +flag_xk, 1632 +flag_ye, 1633 +flag_yt, 1634 +flag_za, 1635 +flag_zm, 1636 +flag_zw, 1637 +flag_gbeng, 1638 +england, 1638 +flag_gbsct, 1639 +scotland, 1639 +flag_gbwls, 1640 +wales, 1640 diff --git a/src/c/moj/gen/emoji_list.h b/src/c/moj/gen/emoji_list.h new file mode 100644 index 0000000..ba85c7b --- /dev/null +++ b/src/c/moj/gen/emoji_list.h @@ -0,0 +1,1646 @@ +// THIS FILE IS GENERATED BY src/gen_list.sh. +// DO NOT TOUCH HERE. + +#define NUM_EMOJI 1640 +static const char* emoji_list[1640] = { + "๐Ÿ˜€", + "๐Ÿ˜", + "๐Ÿ˜‚", + "๐Ÿคฃ", + "๐Ÿ˜ƒ", + "๐Ÿ˜„", + "๐Ÿ˜…", + "๐Ÿ˜†", + "๐Ÿ˜‰", + "๐Ÿ˜Š", + "๐Ÿ˜‹", + "๐Ÿ˜Ž", + "๐Ÿ˜", + "๐Ÿ˜˜", + "๐Ÿฅฐ", + "๐Ÿ˜—", + "๐Ÿ˜™", + "๐Ÿ˜š", + "โ˜บ๏ธ", + "๐Ÿ™‚", + "๐Ÿค—", + "๐Ÿคฉ", + "๐Ÿค”", + "๐Ÿคจ", + "๐Ÿ˜๏ธ", + "๐Ÿ˜‘", + "๐Ÿ˜ถ", + "๐Ÿ™„", + "๐Ÿ˜", + "๐Ÿ˜ฃ", + "๐Ÿ˜ฅ", + "๐Ÿ˜ฎ", + "๐Ÿค", + "๐Ÿ˜ฏ", + "๐Ÿ˜ช", + "๐Ÿ˜ซ", + "๐Ÿ˜ด", + "๐Ÿ˜Œ", + "๐Ÿ˜›", + "๐Ÿ˜œ", + "๐Ÿ˜", + "๐Ÿคค", + "๐Ÿ˜’", + "๐Ÿ˜“", + "๐Ÿ˜”", + "๐Ÿ˜•", + "๐Ÿ™ƒ", + "๐Ÿค‘", + "๐Ÿ˜ฒ", + "โ˜น๏ธ", + "๐Ÿ™", + "๐Ÿ˜–", + "๐Ÿ˜ž", + "๐Ÿ˜Ÿ", + "๐Ÿ˜ค", + "๐Ÿ˜ข", + "๐Ÿ˜ญ", + "๐Ÿ˜ฆ", + "๐Ÿ˜ง", + "๐Ÿ˜จ", + "๐Ÿ˜ฉ", + "๐Ÿคฏ", + "๐Ÿ˜ฌ", + "๐Ÿ˜ฐ", + "๐Ÿ˜ฑ", + "๐Ÿฅต", + "๐Ÿฅถ", + "๐Ÿ˜ณ", + "๐Ÿคช", + "๐Ÿ˜ต", + "๐Ÿ˜ก", + "๐Ÿ˜ ", + "๐Ÿคฌ", + "๐Ÿ˜ท", + "๐Ÿค’", + "๐Ÿค•", + "๐Ÿคข", + "๐Ÿคฎ", + "๐Ÿคง", + "๐Ÿ˜‡", + "๐Ÿค ", + "๐Ÿฅณ", + "๐Ÿฅด", + "๐Ÿฅบ", + "๐Ÿคฅ", + "๐Ÿคซ", + "๐Ÿคญ", + "๐Ÿง", + "๐Ÿค“", + "๐Ÿ˜ˆ", + "๐Ÿ‘ฟ", + "๐Ÿคก", + "๐Ÿ‘น", + "๐Ÿ‘บ", + "๐Ÿ’€", + "โ˜ ๏ธ", + "๐Ÿ‘ป", + "๐Ÿ‘ฝ๏ธ", + "๐Ÿ‘พ", + "๐Ÿค–", + "๐Ÿ’ฉ", + "๐Ÿ˜บ", + "๐Ÿ˜ธ", + "๐Ÿ˜น", + "๐Ÿ˜ป", + "๐Ÿ˜ผ", + "๐Ÿ˜ฝ", + "๐Ÿ™€", + "๐Ÿ˜ฟ", + "๐Ÿ˜พ", + "๐Ÿ™ˆ", + "๐Ÿ™‰", + "๐Ÿ™Š", + "๐Ÿ‘ถ", + "๐Ÿง’", + "๐Ÿ‘ฆ", + "๐Ÿ‘ง", + "๐Ÿง‘", + "๐Ÿ‘จ", + "๐Ÿ‘ฉ", + "๐Ÿง“", + "๐Ÿ‘ด", + "๐Ÿ‘ต", + "๐Ÿ‘จโ€โš•๏ธ", + "๐Ÿ‘ฉโ€โš•๏ธ", + "๐Ÿ‘จโ€๐ŸŽ“", + "๐Ÿ‘ฉโ€๐ŸŽ“", + "๐Ÿ‘จโ€๐Ÿซ", + "๐Ÿ‘ฉโ€๐Ÿซ", + "๐Ÿ‘จโ€โš–๏ธ", + "๐Ÿ‘ฉโ€โš–๏ธ", + "๐Ÿ‘จโ€๐ŸŒพ", + "๐Ÿ‘ฉโ€๐ŸŒพ", + "๐Ÿ‘จโ€๐Ÿณ", + "๐Ÿ‘ฉโ€๐Ÿณ", + "๐Ÿ‘จโ€๐Ÿ”ง", + "๐Ÿ‘ฉโ€๐Ÿ”ง", + "๐Ÿ‘จโ€๐Ÿญ", + "๐Ÿ‘ฉโ€๐Ÿญ", + "๐Ÿ‘จโ€๐Ÿ’ผ", + "๐Ÿ‘ฉโ€๐Ÿ’ผ", + "๐Ÿ‘จโ€๐Ÿ”ฌ", + "๐Ÿ‘ฉโ€๐Ÿ”ฌ", + "๐Ÿ‘จโ€๐Ÿ’ป", + "๐Ÿ‘ฉโ€๐Ÿ’ป", + "๐Ÿ‘จโ€๐ŸŽค", + "๐Ÿ‘ฉโ€๐ŸŽค", + "๐Ÿ‘จโ€๐ŸŽจ", + "๐Ÿ‘ฉโ€๐ŸŽจ", + "๐Ÿ‘จโ€โœˆ๏ธ", + "๐Ÿ‘ฉโ€โœˆ๏ธ", + "๐Ÿ‘จโ€๐Ÿš€", + "๐Ÿ‘ฉโ€๐Ÿš€", + "๐Ÿ‘จโ€๐Ÿš’", + "๐Ÿ‘ฉโ€๐Ÿš’", + "๐Ÿ‘ฎ", + "๐Ÿ‘ฎโ€โ™‚๏ธ", + "๐Ÿ‘ฎโ€โ™€๏ธ", + "๐Ÿ•ต๏ธ", + "๐Ÿ•ต๏ธโ€โ™‚๏ธ", + "๐Ÿ•ต๏ธโ€โ™€๏ธ", + "๐Ÿ’‚", + "๐Ÿ’‚โ€โ™‚๏ธ", + "๐Ÿ’‚โ€โ™€๏ธ", + "๐Ÿ‘ท", + "๐Ÿ‘ทโ€โ™‚๏ธ", + "๐Ÿ‘ทโ€โ™€๏ธ", + "๐Ÿคด", + "๐Ÿ‘ธ", + "๐Ÿ‘ณ", + "๐Ÿ‘ณโ€โ™‚๏ธ", + "๐Ÿ‘ณโ€โ™€๏ธ", + "๐Ÿ‘ฒ", + "๐Ÿง•", + "๐Ÿง”", + "๐Ÿ‘ฑ", + "๐Ÿ‘ฑโ€โ™‚๏ธ", + "๐Ÿ‘ฑโ€โ™€๏ธ", + "๐Ÿ‘จโ€๐Ÿฆฐ", + "๐Ÿ‘ฉโ€๐Ÿฆฐ", + "๐Ÿ‘จโ€๐Ÿฆฑ", + "๐Ÿ‘ฉโ€๐Ÿฆฑ", + "๐Ÿ‘จโ€๐Ÿฆฒ", + "๐Ÿ‘ฉโ€๐Ÿฆฒ", + "๐Ÿ‘จโ€๐Ÿฆณ", + "๐Ÿ‘ฉโ€๐Ÿฆณ", + "๐Ÿคต", + "๐Ÿ‘ฐ", + "๐Ÿคฐ", + "๐Ÿคฑ", + "๐Ÿ‘ผ", + "๐ŸŽ…", + "๐Ÿคถ", + "๐Ÿฆธ", + "๐Ÿฆธโ€โ™€๏ธ", + "๐Ÿฆธโ€โ™‚๏ธ", + "๐Ÿฆน", + "๐Ÿฆนโ€โ™€๏ธ", + "๐Ÿฆนโ€โ™‚๏ธ", + "๐Ÿง™", + "๐Ÿง™โ€โ™€๏ธ", + "๐Ÿง™โ€โ™‚๏ธ", + "๐Ÿงš", + "๐Ÿงšโ€โ™€๏ธ", + "๐Ÿงšโ€โ™‚๏ธ", + "๐Ÿง›", + "๐Ÿง›โ€โ™€๏ธ", + "๐Ÿง›โ€โ™‚๏ธ", + "๐Ÿงœ", + "๐Ÿงœโ€โ™€๏ธ", + "๐Ÿงœโ€โ™‚๏ธ", + "๐Ÿง", + "๐Ÿงโ€โ™€๏ธ", + "๐Ÿงโ€โ™‚๏ธ", + "๐Ÿงž", + "๐Ÿงžโ€โ™€๏ธ", + "๐Ÿงžโ€โ™‚๏ธ", + "๐ŸงŸ", + "๐ŸงŸโ€โ™€๏ธ", + "๐ŸงŸโ€โ™‚๏ธ", + "๐Ÿ™", + "๐Ÿ™โ€โ™‚๏ธ", + "๐Ÿ™โ€โ™€๏ธ", + "๐Ÿ™Ž", + "๐Ÿ™Žโ€โ™‚๏ธ", + "๐Ÿ™Žโ€โ™€๏ธ", + "๐Ÿ™…", + "๐Ÿ™…โ€โ™‚๏ธ", + "๐Ÿ™…โ€โ™€๏ธ", + "๐Ÿ™†", + "๐Ÿ™†โ€โ™‚๏ธ", + "๐Ÿ™†โ€โ™€๏ธ", + "๐Ÿ’", + "๐Ÿ’โ€โ™‚๏ธ", + "๐Ÿ’โ€โ™€๏ธ", + "๐Ÿ™‹", + "๐Ÿ™‹โ€โ™‚๏ธ", + "๐Ÿ™‹โ€โ™€๏ธ", + "๐Ÿ™‡", + "๐Ÿ™‡โ€โ™‚๏ธ", + "๐Ÿ™‡โ€โ™€๏ธ", + "๐Ÿคฆ", + "๐Ÿคฆโ€โ™‚๏ธ", + "๐Ÿคฆโ€โ™€๏ธ", + "๐Ÿคท", + "๐Ÿคทโ€โ™‚๏ธ", + "๐Ÿคทโ€โ™€๏ธ", + "๐Ÿ’†", + "๐Ÿ’†โ€โ™‚๏ธ", + "๐Ÿ’†โ€โ™€๏ธ", + "๐Ÿ’‡", + "๐Ÿ’‡โ€โ™‚๏ธ", + "๐Ÿ’‡โ€โ™€๏ธ", + "๐Ÿšถ", + "๐Ÿšถโ€โ™‚๏ธ", + "๐Ÿšถโ€โ™€๏ธ", + "๐Ÿƒ", + "๐Ÿƒโ€โ™‚๏ธ", + "๐Ÿƒโ€โ™€๏ธ", + "๐Ÿ’ƒ", + "๐Ÿ•บ", + "๐Ÿ‘ฏ", + "๐Ÿ‘ฏโ€โ™‚๏ธ", + "๐Ÿ‘ฏโ€โ™€๏ธ", + "๐Ÿง–", + "๐Ÿง–โ€โ™€๏ธ", + "๐Ÿง–โ€โ™‚๏ธ", + "๐Ÿง—", + "๐Ÿง—โ€โ™€๏ธ", + "๐Ÿง—โ€โ™‚๏ธ", + "๐Ÿง˜", + "๐Ÿง˜โ€โ™€๏ธ", + "๐Ÿง˜โ€โ™‚๏ธ", + "๐Ÿ›€", + "๐Ÿ›Œ", + "๐Ÿ•ด๏ธ", + "๐Ÿ—ฃ๏ธ", + "๐Ÿ‘ค", + "๐Ÿ‘ฅ", + "๐Ÿคบ", + "๐Ÿ‡", + "โ›ท๏ธ", + "๐Ÿ‚๏ธ", + "๐ŸŒ๏ธ", + "๐ŸŒ๏ธโ€โ™‚๏ธ", + "๐ŸŒ๏ธโ€โ™€๏ธ", + "๐Ÿ„๏ธ", + "๐Ÿ„โ€โ™‚๏ธ", + "๐Ÿ„โ€โ™€๏ธ", + "๐Ÿšฃ", + "๐Ÿšฃโ€โ™‚๏ธ", + "๐Ÿšฃโ€โ™€๏ธ", + "๐ŸŠ๏ธ", + "๐ŸŠโ€โ™‚๏ธ", + "๐ŸŠโ€โ™€๏ธ", + "โ›น๏ธ", + "โ›น๏ธโ€โ™‚๏ธ", + "โ›น๏ธโ€โ™€๏ธ", + "๐Ÿ‹๏ธ", + "๐Ÿ‹๏ธโ€โ™‚๏ธ", + "๐Ÿ‹๏ธโ€โ™€๏ธ", + "๐Ÿšด", + "๐Ÿšดโ€โ™‚๏ธ", + "๐Ÿšดโ€โ™€๏ธ", + "๐Ÿšต", + "๐Ÿšตโ€โ™‚๏ธ", + "๐Ÿšตโ€โ™€๏ธ", + "๐ŸŽ๏ธ", + "๐Ÿ๏ธ", + "๐Ÿคธ", + "๐Ÿคธโ€โ™‚๏ธ", + "๐Ÿคธโ€โ™€๏ธ", + "๐Ÿคผ", + "๐Ÿคผโ€โ™‚๏ธ", + "๐Ÿคผโ€โ™€๏ธ", + "๐Ÿคฝ", + "๐Ÿคฝโ€โ™‚๏ธ", + "๐Ÿคฝโ€โ™€๏ธ", + "๐Ÿคพ", + "๐Ÿคพโ€โ™‚๏ธ", + "๐Ÿคพโ€โ™€๏ธ", + "๐Ÿคน", + "๐Ÿคนโ€โ™‚๏ธ", + "๐Ÿคนโ€โ™€๏ธ", + "๐Ÿ‘ซ", + "๐Ÿ‘ฌ", + "๐Ÿ‘ญ", + "๐Ÿ’", + "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", + "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", + "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ", + "๐Ÿ’‘", + "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ", + "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ", + "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ", + "๐Ÿ‘ช๏ธ", + "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", + "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง", + "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง", + "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "๐Ÿ‘จโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘ง", + "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "๐Ÿ‘ฉโ€๐Ÿ‘ฆ", + "๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ", + "๐Ÿ‘ฉโ€๐Ÿ‘ง", + "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", + "๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง", + "๐Ÿคณ", + "๐Ÿ’ช", + "๐Ÿฆต", + "๐Ÿฆถ", + "๐Ÿ‘ˆ๏ธ", + "๐Ÿ‘‰๏ธ", + "โ˜๏ธ", + "๐Ÿ‘†๏ธ", + "๐Ÿ–•", + "๐Ÿ‘‡๏ธ", + "โœŒ๏ธ", + "๐Ÿคž", + "๐Ÿ––", + "๐Ÿค˜", + "๐Ÿค™", + "๐Ÿ–๏ธ", + "โœ‹", + "๐Ÿ‘Œ", + "๐Ÿ‘๏ธ", + "๐Ÿ‘Ž๏ธ", + "โœŠ", + "๐Ÿ‘Š", + "๐Ÿค›", + "๐Ÿคœ", + "๐Ÿคš", + "๐Ÿ‘‹", + "๐ŸคŸ", + "โœ๏ธ", + "๐Ÿ‘", + "๐Ÿ‘", + "๐Ÿ™Œ", + "๐Ÿคฒ", + "๐Ÿ™", + "๐Ÿค", + "๐Ÿ’…", + "๐Ÿ‘‚๏ธ", + "๐Ÿ‘ƒ", + "๐Ÿ‘ฃ", + "๐Ÿ‘€", + "๐Ÿ‘๏ธ", + "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", + "๐Ÿง ", + "๐Ÿฆด", + "๐Ÿฆท", + "๐Ÿ‘…", + "๐Ÿ‘„", + "๐Ÿ’‹", + "๐Ÿ’˜", + "โค๏ธ", + "๐Ÿ’“", + "๐Ÿ’”", + "๐Ÿ’•", + "๐Ÿ’–", + "๐Ÿ’—", + "๐Ÿ’™", + "๐Ÿ’š", + "๐Ÿ’›", + "๐Ÿงก", + "๐Ÿ’œ", + "๐Ÿ–ค", + "๐Ÿ’", + "๐Ÿ’ž", + "๐Ÿ’Ÿ", + "โฃ๏ธ", + "๐Ÿ’Œ", + "๐Ÿ’ค", + "๐Ÿ’ข", + "๐Ÿ’ฃ๏ธ", + "๐Ÿ’ฅ", + "๐Ÿ’ฆ", + "๐Ÿ’จ", + "๐Ÿ’ซ", + "๐Ÿ’ฌ", + "๐Ÿ—จ๏ธ", + "๐Ÿ—ฏ๏ธ", + "๐Ÿ’ญ", + "๐Ÿ•ณ๏ธ", + "๐Ÿ‘“๏ธ", + "๐Ÿ•ถ๏ธ", + "๐Ÿฅฝ", + "๐Ÿฅผ", + "๐Ÿ‘”", + "๐Ÿ‘•", + "๐Ÿ‘–", + "๐Ÿงฃ", + "๐Ÿงค", + "๐Ÿงฅ", + "๐Ÿงฆ", + "๐Ÿ‘—", + "๐Ÿ‘˜", + "๐Ÿ‘™", + "๐Ÿ‘š", + "๐Ÿ‘›", + "๐Ÿ‘œ", + "๐Ÿ‘", + "๐Ÿ›๏ธ", + "๐ŸŽ’", + "๐Ÿ‘ž", + "๐Ÿ‘Ÿ", + "๐Ÿฅพ", + "๐Ÿฅฟ", + "๐Ÿ‘ ", + "๐Ÿ‘ก", + "๐Ÿ‘ข", + "๐Ÿ‘‘", + "๐Ÿ‘’", + "๐ŸŽฉ", + "๐ŸŽ“๏ธ", + "๐Ÿงข", + "โ›‘๏ธ", + "๐Ÿ“ฟ", + "๐Ÿ’„", + "๐Ÿ’", + "๐Ÿ’Ž", + "๐Ÿต", + "๐Ÿ’", + "๐Ÿฆ", + "๐Ÿถ", + "๐Ÿ•๏ธ", + "๐Ÿฉ", + "๐Ÿบ", + "๐ŸฆŠ", + "๐Ÿฆ", + "๐Ÿฑ", + "๐Ÿˆ๏ธ", + "๐Ÿฆ", + "๐Ÿฏ", + "๐Ÿ…", + "๐Ÿ†", + "๐Ÿด", + "๐ŸŽ", + "๐Ÿฆ„", + "๐Ÿฆ“", + "๐ŸฆŒ", + "๐Ÿฎ", + "๐Ÿ‚", + "๐Ÿƒ", + "๐Ÿ„", + "๐Ÿท", + "๐Ÿ–", + "๐Ÿ—", + "๐Ÿฝ", + "๐Ÿ", + "๐Ÿ‘", + "๐Ÿ", + "๐Ÿช", + "๐Ÿซ", + "๐Ÿฆ™", + "๐Ÿฆ’", + "๐Ÿ˜", + "๐Ÿฆ", + "๐Ÿฆ›", + "๐Ÿญ", + "๐Ÿ", + "๐Ÿ€", + "๐Ÿน", + "๐Ÿฐ", + "๐Ÿ‡", + "๐Ÿฟ๏ธ", + "๐Ÿฆ”", + "๐Ÿฆ‡", + "๐Ÿป", + "๐Ÿจ", + "๐Ÿผ", + "๐Ÿฆ˜", + "๐Ÿฆก", + "๐Ÿพ", + "๐Ÿฆƒ", + "๐Ÿ”", + "๐Ÿ“", + "๐Ÿฃ", + "๐Ÿค", + "๐Ÿฅ", + "๐Ÿฆ๏ธ", + "๐Ÿง", + "๐Ÿ•Š๏ธ", + "๐Ÿฆ…", + "๐Ÿฆ†", + "๐Ÿฆข", + "๐Ÿฆ‰", + "๐Ÿฆš", + "๐Ÿฆœ", + "๐Ÿธ", + "๐ŸŠ", + "๐Ÿข", + "๐ŸฆŽ", + "๐Ÿ", + "๐Ÿฒ", + "๐Ÿ‰", + "๐Ÿฆ•", + "๐Ÿฆ–", + "๐Ÿณ", + "๐Ÿ‹", + "๐Ÿฌ", + "๐ŸŸ๏ธ", + "๐Ÿ ", + "๐Ÿก", + "๐Ÿฆˆ", + "๐Ÿ™", + "๐Ÿš", + "๐Ÿฆ€", + "๐Ÿฆž", + "๐Ÿฆ", + "๐Ÿฆ‘", + "๐ŸŒ", + "๐Ÿฆ‹", + "๐Ÿ›", + "๐Ÿœ", + "๐Ÿ", + "๐Ÿž", + "๐Ÿฆ—", + "๐Ÿ•ท๏ธ", + "๐Ÿ•ธ๏ธ", + "๐Ÿฆ‚", + "๐ŸฆŸ", + "๐Ÿฆ ", + "๐Ÿ’", + "๐ŸŒธ", + "๐Ÿ’ฎ", + "๐Ÿต๏ธ", + "๐ŸŒน", + "๐Ÿฅ€", + "๐ŸŒบ", + "๐ŸŒป", + "๐ŸŒผ", + "๐ŸŒท", + "๐ŸŒฑ", + "๐ŸŒฒ", + "๐ŸŒณ", + "๐ŸŒด", + "๐ŸŒต", + "๐ŸŒพ", + "๐ŸŒฟ", + "โ˜˜๏ธ", + "๐Ÿ€", + "๐Ÿ", + "๐Ÿ‚", + "๐Ÿƒ", + "๐Ÿ‡", + "๐Ÿˆ", + "๐Ÿ‰", + "๐ŸŠ", + "๐Ÿ‹", + "๐ŸŒ", + "๐Ÿ", + "๐Ÿฅญ", + "๐ŸŽ", + "๐Ÿ", + "๐Ÿ", + "๐Ÿ‘", + "๐Ÿ’", + "๐Ÿ“", + "๐Ÿฅ", + "๐Ÿ…", + "๐Ÿฅฅ", + "๐Ÿฅ‘", + "๐Ÿ†", + "๐Ÿฅ”", + "๐Ÿฅ•", + "๐ŸŒฝ", + "๐ŸŒถ๏ธ", + "๐Ÿฅ’", + "๐Ÿฅฌ", + "๐Ÿฅฆ", + "๐Ÿ„", + "๐Ÿฅœ", + "๐ŸŒฐ", + "๐Ÿž", + "๐Ÿฅ", + "๐Ÿฅ–", + "๐Ÿฅจ", + "๐Ÿฅฏ", + "๐Ÿฅž", + "๐Ÿง€", + "๐Ÿ–", + "๐Ÿ—", + "๐Ÿฅฉ", + "๐Ÿฅ“", + "๐Ÿ”", + "๐ŸŸ", + "๐Ÿ•", + "๐ŸŒญ", + "๐Ÿฅช", + "๐ŸŒฎ", + "๐ŸŒฏ", + "๐Ÿฅ™", + "๐Ÿฅš", + "๐Ÿณ", + "๐Ÿฅ˜", + "๐Ÿฒ", + "๐Ÿฅฃ", + "๐Ÿฅ—", + "๐Ÿฟ", + "๐Ÿง‚", + "๐Ÿฅซ", + "๐Ÿฑ", + "๐Ÿ˜", + "๐Ÿ™", + "๐Ÿš", + "๐Ÿ›", + "๐Ÿœ", + "๐Ÿ", + "๐Ÿ ", + "๐Ÿข", + "๐Ÿฃ", + "๐Ÿค", + "๐Ÿฅ", + "๐Ÿฅฎ", + "๐Ÿก", + "๐ŸฅŸ", + "๐Ÿฅ ", + "๐Ÿฅก", + "๐Ÿฆ", + "๐Ÿง", + "๐Ÿจ", + "๐Ÿฉ", + "๐Ÿช", + "๐ŸŽ‚", + "๐Ÿฐ", + "๐Ÿง", + "๐Ÿฅง", + "๐Ÿซ", + "๐Ÿฌ", + "๐Ÿญ", + "๐Ÿฎ", + "๐Ÿฏ", + "๐Ÿผ", + "๐Ÿฅ›", + "โ˜•๏ธ", + "๐Ÿต", + "๐Ÿถ", + "๐Ÿพ", + "๐Ÿท", + "๐Ÿธ๏ธ", + "๐Ÿน", + "๐Ÿบ", + "๐Ÿป", + "๐Ÿฅ‚", + "๐Ÿฅƒ", + "๐Ÿฅค", + "๐Ÿฅข", + "๐Ÿฝ๏ธ", + "๐Ÿด", + "๐Ÿฅ„", + "๐Ÿ”ช", + "๐Ÿบ", + "๐ŸŒ๏ธ", + "๐ŸŒŽ๏ธ", + "๐ŸŒ๏ธ", + "๐ŸŒ", + "๐Ÿ—บ๏ธ", + "๐Ÿ—พ", + "๐Ÿงญ", + "๐Ÿ”๏ธ", + "โ›ฐ๏ธ", + "๐ŸŒ‹", + "๐Ÿ—ป", + "๐Ÿ•๏ธ", + "๐Ÿ–๏ธ", + "๐Ÿœ๏ธ", + "๐Ÿ๏ธ", + "๐Ÿž๏ธ", + "๐ŸŸ๏ธ", + "๐Ÿ›๏ธ", + "๐Ÿ—๏ธ", + "๐Ÿงฑ", + "๐Ÿ˜๏ธ", + "๐Ÿš๏ธ", + "๐Ÿ ๏ธ", + "๐Ÿก", + "๐Ÿข", + "๐Ÿฃ", + "๐Ÿค", + "๐Ÿฅ", + "๐Ÿฆ", + "๐Ÿจ", + "๐Ÿฉ", + "๐Ÿช", + "๐Ÿซ", + "๐Ÿฌ", + "๐Ÿญ๏ธ", + "๐Ÿฏ", + "๐Ÿฐ", + "๐Ÿ’’", + "๐Ÿ—ผ", + "๐Ÿ—ฝ", + "โ›ช๏ธ", + "๐Ÿ•Œ", + "๐Ÿ•", + "โ›ฉ๏ธ", + "๐Ÿ•‹", + "โ›ฒ๏ธ", + "โ›บ๏ธ", + "๐ŸŒ", + "๐ŸŒƒ", + "๐Ÿ™๏ธ", + "๐ŸŒ„", + "๐ŸŒ…", + "๐ŸŒ†", + "๐ŸŒ‡", + "๐ŸŒ‰", + "โ™จ๏ธ", + "๐ŸŒŒ", + "๐ŸŽ ", + "๐ŸŽก", + "๐ŸŽข", + "๐Ÿ’ˆ", + "๐ŸŽช", + "๐Ÿš‚", + "๐Ÿšƒ", + "๐Ÿš„", + "๐Ÿš…", + "๐Ÿš†", + "๐Ÿš‡๏ธ", + "๐Ÿšˆ", + "๐Ÿš‰", + "๐ŸšŠ", + "๐Ÿš", + "๐Ÿšž", + "๐Ÿš‹", + "๐ŸšŒ", + "๐Ÿš๏ธ", + "๐ŸšŽ", + "๐Ÿš", + "๐Ÿš‘๏ธ", + "๐Ÿš’", + "๐Ÿš“", + "๐Ÿš”๏ธ", + "๐Ÿš•", + "๐Ÿš–", + "๐Ÿš—", + "๐Ÿš˜๏ธ", + "๐Ÿš™", + "๐Ÿšš", + "๐Ÿš›", + "๐Ÿšœ", + "๐Ÿšฒ๏ธ", + "๐Ÿ›ด", + "๐Ÿ›น", + "๐Ÿ›ต", + "๐Ÿš", + "๐Ÿ›ฃ๏ธ", + "๐Ÿ›ค๏ธ", + "๐Ÿ›ข๏ธ", + "โ›ฝ๏ธ", + "๐Ÿšจ", + "๐Ÿšฅ", + "๐Ÿšฆ", + "๐Ÿ›‘", + "๐Ÿšง", + "โš“๏ธ", + "โ›ต๏ธ", + "๐Ÿ›ถ", + "๐Ÿšค", + "๐Ÿ›ณ๏ธ", + "โ›ด๏ธ", + "๐Ÿ›ฅ๏ธ", + "๐Ÿšข", + "โœˆ๏ธ", + "๐Ÿ›ฉ๏ธ", + "๐Ÿ›ซ", + "๐Ÿ›ฌ", + "๐Ÿ’บ", + "๐Ÿš", + "๐ŸšŸ", + "๐Ÿš ", + "๐Ÿšก", + "๐Ÿ›ฐ๏ธ", + "๐Ÿš€", + "๐Ÿ›ธ", + "๐Ÿ›Ž๏ธ", + "๐Ÿงณ", + "โŒ›๏ธ", + "โณ๏ธ", + "โŒš๏ธ", + "โฐ", + "โฑ๏ธ", + "โฒ๏ธ", + "๐Ÿ•ฐ๏ธ", + "๐Ÿ•›๏ธ", + "๐Ÿ•ง๏ธ", + "๐Ÿ•๏ธ", + "๐Ÿ•œ๏ธ", + "๐Ÿ•‘๏ธ", + "๐Ÿ•๏ธ", + "๐Ÿ•’๏ธ", + "๐Ÿ•ž๏ธ", + "๐Ÿ•“๏ธ", + "๐Ÿ•Ÿ๏ธ", + "๐Ÿ•”๏ธ", + "๐Ÿ• ๏ธ", + "๐Ÿ••๏ธ", + "๐Ÿ•ก๏ธ", + "๐Ÿ•–๏ธ", + "๐Ÿ•ข๏ธ", + "๐Ÿ•—๏ธ", + "๐Ÿ•ฃ๏ธ", + "๐Ÿ•˜๏ธ", + "๐Ÿ•ค๏ธ", + "๐Ÿ•™๏ธ", + "๐Ÿ•ฅ๏ธ", + "๐Ÿ•š๏ธ", + "๐Ÿ•ฆ๏ธ", + "๐ŸŒ‘", + "๐ŸŒ’", + "๐ŸŒ“", + "๐ŸŒ”", + "๐ŸŒ•๏ธ", + "๐ŸŒ–", + "๐ŸŒ—", + "๐ŸŒ˜", + "๐ŸŒ™", + "๐ŸŒš", + "๐ŸŒ›", + "๐ŸŒœ๏ธ", + "๐ŸŒก๏ธ", + "โ˜€๏ธ", + "๐ŸŒ", + "๐ŸŒž", + "โญ๏ธ", + "๐ŸŒŸ", + "๐ŸŒ ", + "โ˜๏ธ", + "โ›…๏ธ", + "โ›ˆ๏ธ", + "๐ŸŒค๏ธ", + "๐ŸŒฅ๏ธ", + "๐ŸŒฆ๏ธ", + "๐ŸŒง๏ธ", + "๐ŸŒจ๏ธ", + "๐ŸŒฉ๏ธ", + "๐ŸŒช๏ธ", + "๐ŸŒซ๏ธ", + "๐ŸŒฌ๏ธ", + "๐ŸŒ€", + "๐ŸŒˆ", + "๐ŸŒ‚", + "โ˜‚๏ธ", + "โ˜”๏ธ", + "โ›ฑ๏ธ", + "โšก๏ธ", + "โ„๏ธ", + "โ˜ƒ๏ธ", + "โ›„๏ธ", + "โ˜„๏ธ", + "๐Ÿ”ฅ", + "๐Ÿ’ง", + "๐ŸŒŠ", + "๐ŸŽƒ", + "๐ŸŽ„", + "๐ŸŽ†", + "๐ŸŽ‡", + "๐Ÿงจ", + "โœจ", + "๐ŸŽˆ", + "๐ŸŽ‰", + "๐ŸŽŠ", + "๐ŸŽ‹", + "๐ŸŽ", + "๐ŸŽŽ", + "๐ŸŽ", + "๐ŸŽ", + "๐ŸŽ‘", + "๐Ÿงง", + "๐ŸŽ€", + "๐ŸŽ", + "๐ŸŽ—๏ธ", + "๐ŸŽŸ๏ธ", + "๐ŸŽซ", + "๐ŸŽ–๏ธ", + "๐Ÿ†๏ธ", + "๐Ÿ…", + "๐Ÿฅ‡", + "๐Ÿฅˆ", + "๐Ÿฅ‰", + "โšฝ๏ธ", + "โšพ๏ธ", + "๐ŸฅŽ", + "๐Ÿ€", + "๐Ÿ", + "๐Ÿˆ", + "๐Ÿ‰", + "๐ŸŽพ", + "๐Ÿฅ", + "๐ŸŽณ", + "๐Ÿ", + "๐Ÿ‘", + "๐Ÿ’", + "๐Ÿฅ", + "๐Ÿ“", + "๐Ÿธ", + "๐ŸฅŠ", + "๐Ÿฅ‹", + "๐Ÿฅ…", + "โ›ณ๏ธ", + "โ›ธ๏ธ", + "๐ŸŽฃ", + "๐ŸŽฝ", + "๐ŸŽฟ", + "๐Ÿ›ท", + "๐ŸฅŒ", + "๐ŸŽฏ", + "๐ŸŽฑ", + "๐Ÿ”ฎ", + "๐Ÿงฟ", + "๐ŸŽฎ๏ธ", + "๐Ÿ•น๏ธ", + "๐ŸŽฐ", + "๐ŸŽฒ", + "๐Ÿงฉ", + "๐Ÿงธ", + "โ™ ๏ธ", + "โ™ฅ๏ธ", + "โ™ฆ๏ธ", + "โ™ฃ๏ธ", + "โ™Ÿ๏ธ", + "๐Ÿƒ", + "๐Ÿ€„๏ธ", + "๐ŸŽด", + "๐ŸŽญ๏ธ", + "๐Ÿ–ผ๏ธ", + "๐ŸŽจ", + "๐Ÿงต", + "๐Ÿงถ", + "๐Ÿ”‡", + "๐Ÿ”ˆ๏ธ", + "๐Ÿ”‰", + "๐Ÿ”Š", + "๐Ÿ“ข", + "๐Ÿ“ฃ", + "๐Ÿ“ฏ", + "๐Ÿ””", + "๐Ÿ”•", + "๐ŸŽผ", + "๐ŸŽต", + "๐ŸŽถ", + "๐ŸŽ™๏ธ", + "๐ŸŽš๏ธ", + "๐ŸŽ›๏ธ", + "๐ŸŽค", + "๐ŸŽง๏ธ", + "๐Ÿ“ป๏ธ", + "๐ŸŽท", + "๐ŸŽธ", + "๐ŸŽน", + "๐ŸŽบ", + "๐ŸŽป", + "๐Ÿฅ", + "๐Ÿ“ฑ", + "๐Ÿ“ฒ", + "โ˜Ž๏ธ", + "๐Ÿ“ž", + "๐Ÿ“Ÿ๏ธ", + "๐Ÿ“ ", + "๐Ÿ”‹", + "๐Ÿ”Œ", + "๐Ÿ’ป๏ธ", + "๐Ÿ–ฅ๏ธ", + "๐Ÿ–จ๏ธ", + "โŒจ๏ธ", + "๐Ÿ–ฑ๏ธ", + "๐Ÿ–ฒ๏ธ", + "๐Ÿ’ฝ", + "๐Ÿ’พ", + "๐Ÿ’ฟ๏ธ", + "๐Ÿ“€", + "๐Ÿงฎ", + "๐ŸŽฅ", + "๐ŸŽž๏ธ", + "๐Ÿ“ฝ๏ธ", + "๐ŸŽฌ๏ธ", + "๐Ÿ“บ๏ธ", + "๐Ÿ“ท๏ธ", + "๐Ÿ“ธ", + "๐Ÿ“น๏ธ", + "๐Ÿ“ผ", + "๐Ÿ”๏ธ", + "๐Ÿ”Ž", + "๐Ÿ•ฏ๏ธ", + "๐Ÿ’ก", + "๐Ÿ”ฆ", + "๐Ÿฎ", + "๐Ÿ“”", + "๐Ÿ“•", + "๐Ÿ“–", + "๐Ÿ“—", + "๐Ÿ“˜", + "๐Ÿ“™", + "๐Ÿ“š๏ธ", + "๐Ÿ““", + "๐Ÿ“’", + "๐Ÿ“ƒ", + "๐Ÿ“œ", + "๐Ÿ“„", + "๐Ÿ“ฐ", + "๐Ÿ—ž๏ธ", + "๐Ÿ“‘", + "๐Ÿ”–", + "๐Ÿท๏ธ", + "๐Ÿ’ฐ๏ธ", + "๐Ÿ’ด", + "๐Ÿ’ต", + "๐Ÿ’ถ", + "๐Ÿ’ท", + "๐Ÿ’ธ", + "๐Ÿ’ณ๏ธ", + "๐Ÿงพ", + "๐Ÿ’น", + "๐Ÿ’ฑ", + "๐Ÿ’ฒ", + "โœ‰๏ธ", + "๐Ÿ“ง", + "๐Ÿ“จ", + "๐Ÿ“ฉ", + "๐Ÿ“ค๏ธ", + "๐Ÿ“ฅ๏ธ", + "๐Ÿ“ฆ๏ธ", + "๐Ÿ“ซ๏ธ", + "๐Ÿ“ช๏ธ", + "๐Ÿ“ฌ๏ธ", + "๐Ÿ“ญ๏ธ", + "๐Ÿ“ฎ", + "๐Ÿ—ณ๏ธ", + "โœ๏ธ", + "โœ’๏ธ", + "๐Ÿ–‹๏ธ", + "๐Ÿ–Š๏ธ", + "๐Ÿ–Œ๏ธ", + "๐Ÿ–๏ธ", + "๐Ÿ“", + "๐Ÿ’ผ", + "๐Ÿ“", + "๐Ÿ“‚", + "๐Ÿ—‚๏ธ", + "๐Ÿ“…", + "๐Ÿ“†", + "๐Ÿ—’๏ธ", + "๐Ÿ—“๏ธ", + "๐Ÿ“‡", + "๐Ÿ“ˆ", + "๐Ÿ“‰", + "๐Ÿ“Š", + "๐Ÿ“‹๏ธ", + "๐Ÿ“Œ", + "๐Ÿ“", + "๐Ÿ“Ž", + "๐Ÿ–‡๏ธ", + "๐Ÿ“", + "๐Ÿ“", + "โœ‚๏ธ", + "๐Ÿ—ƒ๏ธ", + "๐Ÿ—„๏ธ", + "๐Ÿ—‘๏ธ", + "๐Ÿ”’๏ธ", + "๐Ÿ”“๏ธ", + "๐Ÿ”", + "๐Ÿ”", + "๐Ÿ”‘", + "๐Ÿ—๏ธ", + "๐Ÿ”จ", + "โ›๏ธ", + "โš’๏ธ", + "๐Ÿ› ๏ธ", + "๐Ÿ—ก๏ธ", + "โš”๏ธ", + "๐Ÿ”ซ", + "๐Ÿน", + "๐Ÿ›ก๏ธ", + "๐Ÿ”ง", + "๐Ÿ”ฉ", + "โš™๏ธ", + "๐Ÿ—œ๏ธ", + "โš–๏ธ", + "๐Ÿ”—", + "โ›“๏ธ", + "๐Ÿงฐ", + "๐Ÿงฒ", + "โš—๏ธ", + "๐Ÿงช", + "๐Ÿงซ", + "๐Ÿงฌ", + "๐Ÿ”ฌ", + "๐Ÿ”ญ", + "๐Ÿ“ก", + "๐Ÿ’‰", + "๐Ÿ’Š", + "๐Ÿšช", + "๐Ÿ›๏ธ", + "๐Ÿ›‹๏ธ", + "๐Ÿšฝ", + "๐Ÿšฟ", + "๐Ÿ›", + "๐Ÿงด", + "๐Ÿงท", + "๐Ÿงน", + "๐Ÿงบ", + "๐Ÿงป", + "๐Ÿงผ", + "๐Ÿงฝ", + "๐Ÿงฏ", + "๐Ÿ›’", + "๐Ÿšฌ", + "โšฐ๏ธ", + "โšฑ๏ธ", + "๐Ÿ—ฟ", + "๐Ÿง", + "๐Ÿšฎ", + "๐Ÿšฐ", + "โ™ฟ๏ธ", + "๐Ÿšน๏ธ", + "๐Ÿšบ๏ธ", + "๐Ÿšป", + "๐Ÿšผ๏ธ", + "๐Ÿšพ", + "๐Ÿ›‚", + "๐Ÿ›ƒ", + "๐Ÿ›„", + "๐Ÿ›…", + "โš ๏ธ", + "๐Ÿšธ", + "โ›”๏ธ", + "๐Ÿšซ", + "๐Ÿšณ", + "๐Ÿšญ๏ธ", + "๐Ÿšฏ", + "๐Ÿšฑ", + "๐Ÿšท", + "๐Ÿ“ต", + "๐Ÿ”ž", + "โ˜ข๏ธ", + "โ˜ฃ๏ธ", + "โฌ†๏ธ", + "โ†—๏ธ", + "โžก๏ธ", + "โ†˜๏ธ", + "โฌ‡๏ธ", + "โ†™๏ธ", + "โฌ…๏ธ", + "โ†–๏ธ", + "โ†•๏ธ", + "โ†”๏ธ", + "โ†ฉ๏ธ", + "โ†ช๏ธ", + "โคด๏ธ", + "โคต๏ธ", + "๐Ÿ”ƒ", + "๐Ÿ”„", + "๐Ÿ”™", + "๐Ÿ”š", + "๐Ÿ”›", + "๐Ÿ”œ", + "๐Ÿ”", + "๐Ÿ›", + "โš›๏ธ", + "๐Ÿ•‰๏ธ", + "โœก๏ธ", + "โ˜ธ๏ธ", + "โ˜ฏ๏ธ", + "โœ๏ธ", + "โ˜ฆ๏ธ", + "โ˜ช๏ธ", + "โ˜ฎ๏ธ", + "๐Ÿ•Ž", + "๐Ÿ”ฏ", + "โ™ˆ๏ธ", + "โ™‰๏ธ", + "โ™Š๏ธ", + "โ™‹๏ธ", + "โ™Œ๏ธ", + "โ™๏ธ", + "โ™Ž๏ธ", + "โ™๏ธ", + "โ™๏ธ", + "โ™‘๏ธ", + "โ™’๏ธ", + "โ™“๏ธ", + "โ›Ž", + "๐Ÿ”€", + "๐Ÿ”", + "๐Ÿ”‚", + "โ–ถ๏ธ", + "โฉ๏ธ", + "โญ๏ธ", + "โฏ๏ธ", + "โ—€๏ธ", + "โช๏ธ", + "โฎ๏ธ", + "๐Ÿ”ผ", + "โซ", + "๐Ÿ”ฝ", + "โฌ", + "โธ๏ธ", + "โน๏ธ", + "โบ๏ธ", + "โ๏ธ", + "๐ŸŽฆ", + "๐Ÿ”…", + "๐Ÿ”†", + "๐Ÿ“ถ", + "๐Ÿ“ณ", + "๐Ÿ“ด", + "โ™€๏ธ", + "โ™‚๏ธ", + "โš•๏ธ", + "โ™พ๏ธ", + "โ™ป๏ธ", + "โšœ๏ธ", + "๐Ÿ”ฑ", + "๐Ÿ“›", + "๐Ÿ”ฐ", + "โญ•๏ธ", + "โœ…", + "โ˜‘๏ธ", + "โœ”๏ธ", + "โœ–๏ธ", + "โŒ", + "โŽ", + "โž•", + "โž–", + "โž—", + "โžฐ", + "โžฟ", + "ใ€ฝ๏ธ", + "โœณ๏ธ", + "โœด๏ธ", + "โ‡๏ธ", + "โ€ผ๏ธ", + "โ‰๏ธ", + "โ“๏ธ", + "โ”", + "โ•", + "โ—๏ธ", + "ใ€ฐ๏ธ", + "ยฉ๏ธ", + "ยฎ๏ธ", + "โ„ข๏ธ", + "", + "*๏ธโƒฃ", + "0๏ธโƒฃ", + "1๏ธโƒฃ", + "2๏ธโƒฃ", + "3๏ธโƒฃ", + "4๏ธโƒฃ", + "5๏ธโƒฃ", + "6๏ธโƒฃ", + "7๏ธโƒฃ", + "8๏ธโƒฃ", + "9๏ธโƒฃ", + "๐Ÿ”Ÿ", + "๐Ÿ’ฏ", + "๐Ÿ” ", + "๐Ÿ”ก", + "๐Ÿ”ข", + "๐Ÿ”ฃ", + "๐Ÿ”ค", + "๐Ÿ…ฐ๏ธ", + "๐Ÿ†Ž", + "๐Ÿ…ฑ๏ธ", + "๐Ÿ†‘", + "๐Ÿ†’", + "๐Ÿ†“", + "โ„น๏ธ", + "๐Ÿ†”", + "โ“‚๏ธ", + "๐Ÿ†•", + "๐Ÿ†–", + "๐Ÿ…พ๏ธ", + "๐Ÿ†—", + "๐Ÿ…ฟ๏ธ", + "๐Ÿ†˜", + "๐Ÿ†™", + "๐Ÿ†š", + "๐Ÿˆ", + "๐Ÿˆ‚๏ธ", + "๐Ÿˆท๏ธ", + "๐Ÿˆถ", + "๐Ÿˆฏ๏ธ", + "๐Ÿ‰", + "๐Ÿˆน", + "๐Ÿˆš๏ธ", + "๐Ÿˆฒ", + "๐Ÿ‰‘", + "๐Ÿˆธ", + "๐Ÿˆด", + "๐Ÿˆณ", + "ใŠ—๏ธ", + "ใŠ™๏ธ", + "๐Ÿˆบ", + "๐Ÿˆต", + "โ–ช๏ธ", + "โ–ซ๏ธ", + "โ—ป๏ธ", + "โ—ผ๏ธ", + "โ—ฝ๏ธ", + "โ—พ๏ธ", + "โฌ›๏ธ", + "โฌœ๏ธ", + "๐Ÿ”ถ", + "๐Ÿ”ท", + "๐Ÿ”ธ", + "๐Ÿ”น", + "๐Ÿ”บ", + "๐Ÿ”ป", + "๐Ÿ’ ", + "๐Ÿ”˜", + "๐Ÿ”ฒ", + "๐Ÿ”ณ", + "โšช๏ธ", + "โšซ๏ธ", + "๐Ÿ”ด", + "๐Ÿ”ต", + "๐Ÿ", + "๐Ÿšฉ", + "๐ŸŽŒ", + "๐Ÿด", + "๐Ÿณ๏ธ", + "๐Ÿณ๏ธโ€๐ŸŒˆ", + "๐Ÿดโ€โ˜ ๏ธ", + "๐Ÿ‡ฆ๐Ÿ‡จ", + "๐Ÿ‡ฆ๐Ÿ‡ฉ", + "๐Ÿ‡ฆ๐Ÿ‡ช", + "๐Ÿ‡ฆ๐Ÿ‡ซ", + "๐Ÿ‡ฆ๐Ÿ‡ฌ", + "๐Ÿ‡ฆ๐Ÿ‡ฎ", + "๐Ÿ‡ฆ๐Ÿ‡ฑ", + "๐Ÿ‡ฆ๐Ÿ‡ฒ", + "๐Ÿ‡ฆ๐Ÿ‡ด", + "๐Ÿ‡ฆ๐Ÿ‡ถ", + "๐Ÿ‡ฆ๐Ÿ‡ท", + "๐Ÿ‡ฆ๐Ÿ‡ธ", + "๐Ÿ‡ฆ๐Ÿ‡น", + "๐Ÿ‡ฆ๐Ÿ‡บ", + "๐Ÿ‡ฆ๐Ÿ‡ผ", + "๐Ÿ‡ฆ๐Ÿ‡ฝ", + "๐Ÿ‡ฆ๐Ÿ‡ฟ", + "๐Ÿ‡ง๐Ÿ‡ฆ", + "๐Ÿ‡ง๐Ÿ‡ง", + "๐Ÿ‡ง๐Ÿ‡ฉ", + "๐Ÿ‡ง๐Ÿ‡ช", + "๐Ÿ‡ง๐Ÿ‡ซ", + "๐Ÿ‡ง๐Ÿ‡ฌ", + "๐Ÿ‡ง๐Ÿ‡ญ", + "๐Ÿ‡ง๐Ÿ‡ฎ", + "๐Ÿ‡ง๐Ÿ‡ฏ", + "๐Ÿ‡ง๐Ÿ‡ฑ", + "๐Ÿ‡ง๐Ÿ‡ฒ", + "๐Ÿ‡ง๐Ÿ‡ณ", + "๐Ÿ‡ง๐Ÿ‡ด", + "๐Ÿ‡ง๐Ÿ‡ถ", + "๐Ÿ‡ง๐Ÿ‡ท", + "๐Ÿ‡ง๐Ÿ‡ธ", + "๐Ÿ‡ง๐Ÿ‡น", + "๐Ÿ‡ง๐Ÿ‡ป", + "๐Ÿ‡ง๐Ÿ‡ผ", + "๐Ÿ‡ง๐Ÿ‡พ", + "๐Ÿ‡ง๐Ÿ‡ฟ", + "๐Ÿ‡จ๐Ÿ‡ฆ", + "๐Ÿ‡จ๐Ÿ‡จ", + "๐Ÿ‡จ๐Ÿ‡ฉ", + "๐Ÿ‡จ๐Ÿ‡ซ", + "๐Ÿ‡จ๐Ÿ‡ฌ", + "๐Ÿ‡จ๐Ÿ‡ญ", + "๐Ÿ‡จ๐Ÿ‡ฎ", + "๐Ÿ‡จ๐Ÿ‡ฐ", + "๐Ÿ‡จ๐Ÿ‡ฑ", + "๐Ÿ‡จ๐Ÿ‡ฒ", + "๐Ÿ‡จ๐Ÿ‡ณ", + "๐Ÿ‡จ๐Ÿ‡ด", + "๐Ÿ‡จ๐Ÿ‡ต", + "๐Ÿ‡จ๐Ÿ‡ท", + "๐Ÿ‡จ๐Ÿ‡บ", + "๐Ÿ‡จ๐Ÿ‡ป", + "๐Ÿ‡จ๐Ÿ‡ผ", + "๐Ÿ‡จ๐Ÿ‡ฝ", + "๐Ÿ‡จ๐Ÿ‡พ", + "๐Ÿ‡จ๐Ÿ‡ฟ", + "๐Ÿ‡ฉ๐Ÿ‡ช", + "๐Ÿ‡ฉ๐Ÿ‡ฌ", + "๐Ÿ‡ฉ๐Ÿ‡ฏ", + "๐Ÿ‡ฉ๐Ÿ‡ฐ", + "๐Ÿ‡ฉ๐Ÿ‡ฒ", + "๐Ÿ‡ฉ๐Ÿ‡ด", + "๐Ÿ‡ฉ๐Ÿ‡ฟ", + "๐Ÿ‡ช๐Ÿ‡ฆ", + "๐Ÿ‡ช๐Ÿ‡จ", + "๐Ÿ‡ช๐Ÿ‡ช", + "๐Ÿ‡ช๐Ÿ‡ฌ", + "๐Ÿ‡ช๐Ÿ‡ญ", + "๐Ÿ‡ช๐Ÿ‡ท", + "๐Ÿ‡ช๐Ÿ‡ธ", + "๐Ÿ‡ช๐Ÿ‡น", + "๐Ÿ‡ช๐Ÿ‡บ", + "๐Ÿ‡ซ๐Ÿ‡ฎ", + "๐Ÿ‡ซ๐Ÿ‡ฏ", + "๐Ÿ‡ซ๐Ÿ‡ฐ", + "๐Ÿ‡ซ๐Ÿ‡ฒ", + "๐Ÿ‡ซ๐Ÿ‡ด", + "๐Ÿ‡ซ๐Ÿ‡ท", + "๐Ÿ‡ฌ๐Ÿ‡ฆ", + "๐Ÿ‡ฌ๐Ÿ‡ง", + "๐Ÿ‡ฌ๐Ÿ‡ฉ", + "๐Ÿ‡ฌ๐Ÿ‡ช", + "๐Ÿ‡ฌ๐Ÿ‡ซ", + "๐Ÿ‡ฌ๐Ÿ‡ฌ", + "๐Ÿ‡ฌ๐Ÿ‡ญ", + "๐Ÿ‡ฌ๐Ÿ‡ฎ", + "๐Ÿ‡ฌ๐Ÿ‡ฑ", + "๐Ÿ‡ฌ๐Ÿ‡ฒ", + "๐Ÿ‡ฌ๐Ÿ‡ณ", + "๐Ÿ‡ฌ๐Ÿ‡ต", + "๐Ÿ‡ฌ๐Ÿ‡ถ", + "๐Ÿ‡ฌ๐Ÿ‡ท", + "๐Ÿ‡ฌ๐Ÿ‡ธ", + "๐Ÿ‡ฌ๐Ÿ‡น", + "๐Ÿ‡ฌ๐Ÿ‡บ", + "๐Ÿ‡ฌ๐Ÿ‡ผ", + "๐Ÿ‡ฌ๐Ÿ‡พ", + "๐Ÿ‡ญ๐Ÿ‡ฐ", + "๐Ÿ‡ญ๐Ÿ‡ฒ", + "๐Ÿ‡ญ๐Ÿ‡ณ", + "๐Ÿ‡ญ๐Ÿ‡ท", + "๐Ÿ‡ญ๐Ÿ‡น", + "๐Ÿ‡ญ๐Ÿ‡บ", + "๐Ÿ‡ฎ๐Ÿ‡จ", + "๐Ÿ‡ฎ๐Ÿ‡ฉ", + "๐Ÿ‡ฎ๐Ÿ‡ช", + "๐Ÿ‡ฎ๐Ÿ‡ฑ", + "๐Ÿ‡ฎ๐Ÿ‡ฒ", + "๐Ÿ‡ฎ๐Ÿ‡ณ", + "๐Ÿ‡ฎ๐Ÿ‡ด", + "๐Ÿ‡ฎ๐Ÿ‡ถ", + "๐Ÿ‡ฎ๐Ÿ‡ท", + "๐Ÿ‡ฎ๐Ÿ‡ธ", + "๐Ÿ‡ฎ๐Ÿ‡น", + "๐Ÿ‡ฏ๐Ÿ‡ช", + "๐Ÿ‡ฏ๐Ÿ‡ฒ", + "๐Ÿ‡ฏ๐Ÿ‡ด", + "๐Ÿ‡ฏ๐Ÿ‡ต", + "๐Ÿ‡ฐ๐Ÿ‡ช", + "๐Ÿ‡ฐ๐Ÿ‡ฌ", + "๐Ÿ‡ฐ๐Ÿ‡ญ", + "๐Ÿ‡ฐ๐Ÿ‡ฎ", + "๐Ÿ‡ฐ๐Ÿ‡ฒ", + "๐Ÿ‡ฐ๐Ÿ‡ณ", + "๐Ÿ‡ฐ๐Ÿ‡ต", + "๐Ÿ‡ฐ๐Ÿ‡ท", + "๐Ÿ‡ฐ๐Ÿ‡ผ", + "๐Ÿ‡ฐ๐Ÿ‡พ", + "๐Ÿ‡ฐ๐Ÿ‡ฟ", + "๐Ÿ‡ฑ๐Ÿ‡ฆ", + "๐Ÿ‡ฑ๐Ÿ‡ง", + "๐Ÿ‡ฑ๐Ÿ‡จ", + "๐Ÿ‡ฑ๐Ÿ‡ฎ", + "๐Ÿ‡ฑ๐Ÿ‡ฐ", + "๐Ÿ‡ฑ๐Ÿ‡ท", + "๐Ÿ‡ฑ๐Ÿ‡ธ", + "๐Ÿ‡ฑ๐Ÿ‡น", + "๐Ÿ‡ฑ๐Ÿ‡บ", + "๐Ÿ‡ฑ๐Ÿ‡ป", + "๐Ÿ‡ฑ๐Ÿ‡พ", + "๐Ÿ‡ฒ๐Ÿ‡ฆ", + "๐Ÿ‡ฒ๐Ÿ‡จ", + "๐Ÿ‡ฒ๐Ÿ‡ฉ", + "๐Ÿ‡ฒ๐Ÿ‡ช", + "๐Ÿ‡ฒ๐Ÿ‡ซ", + "๐Ÿ‡ฒ๐Ÿ‡ฌ", + "๐Ÿ‡ฒ๐Ÿ‡ญ", + "๐Ÿ‡ฒ๐Ÿ‡ฐ", + "๐Ÿ‡ฒ๐Ÿ‡ฑ", + "๐Ÿ‡ฒ๐Ÿ‡ฒ", + "๐Ÿ‡ฒ๐Ÿ‡ณ", + "๐Ÿ‡ฒ๐Ÿ‡ด", + "๐Ÿ‡ฒ๐Ÿ‡ต", + "๐Ÿ‡ฒ๐Ÿ‡ถ", + "๐Ÿ‡ฒ๐Ÿ‡ท", + "๐Ÿ‡ฒ๐Ÿ‡ธ", + "๐Ÿ‡ฒ๐Ÿ‡น", + "๐Ÿ‡ฒ๐Ÿ‡บ", + "๐Ÿ‡ฒ๐Ÿ‡ป", + "๐Ÿ‡ฒ๐Ÿ‡ผ", + "๐Ÿ‡ฒ๐Ÿ‡ฝ", + "๐Ÿ‡ฒ๐Ÿ‡พ", + "๐Ÿ‡ฒ๐Ÿ‡ฟ", + "๐Ÿ‡ณ๐Ÿ‡ฆ", + "๐Ÿ‡ณ๐Ÿ‡จ", + "๐Ÿ‡ณ๐Ÿ‡ช", + "๐Ÿ‡ณ๐Ÿ‡ซ", + "๐Ÿ‡ณ๐Ÿ‡ฌ", + "๐Ÿ‡ณ๐Ÿ‡ฎ", + "๐Ÿ‡ณ๐Ÿ‡ฑ", + "๐Ÿ‡ณ๐Ÿ‡ด", + "๐Ÿ‡ณ๐Ÿ‡ต", + "๐Ÿ‡ณ๐Ÿ‡ท", + "๐Ÿ‡ณ๐Ÿ‡บ", + "๐Ÿ‡ณ๐Ÿ‡ฟ", + "๐Ÿ‡ด๐Ÿ‡ฒ", + "๐Ÿ‡ต๐Ÿ‡ฆ", + "๐Ÿ‡ต๐Ÿ‡ช", + "๐Ÿ‡ต๐Ÿ‡ซ", + "๐Ÿ‡ต๐Ÿ‡ฌ", + "๐Ÿ‡ต๐Ÿ‡ญ", + "๐Ÿ‡ต๐Ÿ‡ฐ", + "๐Ÿ‡ต๐Ÿ‡ฑ", + "๐Ÿ‡ต๐Ÿ‡ฒ", + "๐Ÿ‡ต๐Ÿ‡ณ", + "๐Ÿ‡ต๐Ÿ‡ท", + "๐Ÿ‡ต๐Ÿ‡ธ", + "๐Ÿ‡ต๐Ÿ‡น", + "๐Ÿ‡ต๐Ÿ‡ผ", + "๐Ÿ‡ต๐Ÿ‡พ", + "๐Ÿ‡ถ๐Ÿ‡ฆ", + "๐Ÿ‡ท๐Ÿ‡ช", + "๐Ÿ‡ท๐Ÿ‡ด", + "๐Ÿ‡ท๐Ÿ‡ธ", + "๐Ÿ‡ท๐Ÿ‡บ", + "๐Ÿ‡ท๐Ÿ‡ผ", + "๐Ÿ‡ธ๐Ÿ‡ฆ", + "๐Ÿ‡ธ๐Ÿ‡ง", + "๐Ÿ‡ธ๐Ÿ‡จ", + "๐Ÿ‡ธ๐Ÿ‡ฉ", + "๐Ÿ‡ธ๐Ÿ‡ช", + "๐Ÿ‡ธ๐Ÿ‡ฌ", + "๐Ÿ‡ธ๐Ÿ‡ญ", + "๐Ÿ‡ธ๐Ÿ‡ฎ", + "๐Ÿ‡ธ๐Ÿ‡ฏ", + "๐Ÿ‡ธ๐Ÿ‡ฐ", + "๐Ÿ‡ธ๐Ÿ‡ฑ", + "๐Ÿ‡ธ๐Ÿ‡ฒ", + "๐Ÿ‡ธ๐Ÿ‡ณ", + "๐Ÿ‡ธ๐Ÿ‡ด", + "๐Ÿ‡ธ๐Ÿ‡ท", + "๐Ÿ‡ธ๐Ÿ‡ธ", + "๐Ÿ‡ธ๐Ÿ‡น", + "๐Ÿ‡ธ๐Ÿ‡ป", + "๐Ÿ‡ธ๐Ÿ‡ฝ", + "๐Ÿ‡ธ๐Ÿ‡พ", + "๐Ÿ‡ธ๐Ÿ‡ฟ", + "๐Ÿ‡น๐Ÿ‡ฆ", + "๐Ÿ‡น๐Ÿ‡จ", + "๐Ÿ‡น๐Ÿ‡ฉ", + "๐Ÿ‡น๐Ÿ‡ซ", + "๐Ÿ‡น๐Ÿ‡ฌ", + "๐Ÿ‡น๐Ÿ‡ญ", + "๐Ÿ‡น๐Ÿ‡ฏ", + "๐Ÿ‡น๐Ÿ‡ฐ", + "๐Ÿ‡น๐Ÿ‡ฑ", + "๐Ÿ‡น๐Ÿ‡ฒ", + "๐Ÿ‡น๐Ÿ‡ณ", + "๐Ÿ‡น๐Ÿ‡ด", + "๐Ÿ‡น๐Ÿ‡ท", + "๐Ÿ‡น๐Ÿ‡น", + "๐Ÿ‡น๐Ÿ‡ป", + "๐Ÿ‡น๐Ÿ‡ผ", + "๐Ÿ‡น๐Ÿ‡ฟ", + "๐Ÿ‡บ๐Ÿ‡ฆ", + "๐Ÿ‡บ๐Ÿ‡ฌ", + "๐Ÿ‡บ๐Ÿ‡ฒ", + "๐Ÿ‡บ๐Ÿ‡ณ", + "๐Ÿ‡บ๐Ÿ‡ธ", + "๐Ÿ‡บ๐Ÿ‡พ", + "๐Ÿ‡บ๐Ÿ‡ฟ", + "๐Ÿ‡ป๐Ÿ‡ฆ", + "๐Ÿ‡ป๐Ÿ‡จ", + "๐Ÿ‡ป๐Ÿ‡ช", + "๐Ÿ‡ป๐Ÿ‡ฌ", + "๐Ÿ‡ป๐Ÿ‡ฎ", + "๐Ÿ‡ป๐Ÿ‡ณ", + "๐Ÿ‡ป๐Ÿ‡บ", + "๐Ÿ‡ผ๐Ÿ‡ซ", + "๐Ÿ‡ผ๐Ÿ‡ธ", + "๐Ÿ‡ฝ๐Ÿ‡ฐ", + "๐Ÿ‡พ๐Ÿ‡ช", + "๐Ÿ‡พ๐Ÿ‡น", + "๐Ÿ‡ฟ๐Ÿ‡ฆ", + "๐Ÿ‡ฟ๐Ÿ‡ฒ", + "๐Ÿ‡ฟ๐Ÿ‡ผ", + "๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ", + "๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ", + "๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ", +}; diff --git a/src/c/moj/src/gen_gperf.sh b/src/c/moj/src/gen_gperf.sh new file mode 100755 index 0000000..f905f63 --- /dev/null +++ b/src/c/moj/src/gen_gperf.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Generator for the hash list. +# This is unquestionably magic. +set -e +cat <
+#include +#include +#include +#include "moj.h" + +#define MODE_NORMAL 0 +#define MODE_LOOKUP 1 +#define MODE_USELIBRARY 2 + +void usage(char* name, int fail) { + FILE* f = stdout; + if (fail) f = stderr; + fprintf(f, "Usage: %s [-s sep] [-e] <:emoji: text>\n" + " -e: lookup mode - interpret arguments as emoji names\n" + " -s : use custom seperator\n", name); + exit(fail); +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + usage(argv[0], 1); + } + + // parse opts + int opt; + int mode = MODE_NORMAL; + char* sep = NULL; + while ((opt = getopt(argc, argv, "hels:")) != -1) { + switch (opt) { + case 'h': + usage(argv[0], 0); + break; + default: + case '?': + usage(argv[0], 1); + break; + case 'e': + mode = MODE_LOOKUP; + break; + case 's': + sep = optarg; + break; + case 'l': + mode = MODE_USELIBRARY; + break; + } + } + + if (optind >= argc) + usage(argv[0], 1); + + int argi; + switch (mode) { + case MODE_LOOKUP: + for (argi = optind; argi < argc; argi++) { + char* found = moj_lookup(argv[argi], strlen(argv[argi])); + if (found) { + printf("%s%s", found, sep ? sep : "\n"); + } else { + printf("Couldn't find %s. :(\n", argv[argi]); + return 1; + } + } + if (sep) printf("\n"); + break; + case MODE_NORMAL: + for (argi = optind; argi < argc; argi++) { + char* s = argv[argi]; + char c; + int ci; + int len = strlen(s); + int start = 0; + int attention = 0; + for (ci = 0; ci <= len; ci++) { + c = s[ci]; + + if (attention) { // in : + if (c == ' ') { + attention = 0; + s[ci] = 0; + fprintf(stdout, "%s ", s + start); + } else if (c == ':') { + // check if start to ci - start -1 is a valid emoji. + s[ci] = 0; + char* found = moj_lookup(s + start + 1, ci - start - 1); + if (found) { + fprintf(stdout, "%s", found); + } else { + // not an emoji? oops. + fprintf(stdout, "%s:", s + start); + start = ci; + // the next might be one, though.. + } + attention = 0; + } + } else if (c == ':' && ci < len) { + attention = 1; + start = ci; + } else { + putc(c, stdout); + } + } + if (attention && start < len) + fprintf(stdout, "%s", s + start + 1); + + if (argi < (argc - 1)) + fputs(sep ? sep : " ", stdout); + } + fprintf(stdout, "\n"); + break; + case MODE_USELIBRARY: + for (argi = optind; argi < argc; argi++) { + char* rep = moj_replace(argv[argi], strlen(argv[argi])); + printf("%s\n", rep); + free(rep); + } + break;; + } + + return 0; +} diff --git a/src/c/moj/src/moj.c b/src/c/moj/src/moj.c new file mode 100644 index 0000000..35f1065 --- /dev/null +++ b/src/c/moj/src/moj.c @@ -0,0 +1,98 @@ +// moj: Small Emoji library +// Based on our generated things. + +#include +#include +#include +#include "../gen/lookup.c" +#include "../gen/emoji_list.h" + +const char* moj_lookup(const char* name, size_t len) { + const struct emoji_ref *found = emoji_lookup_ref(name, len); + if (found) { + return emoji_list[found->ref - 1]; + } + return NULL; +} + +char* moj_replace(const char* text, size_t len) { + if (!len) + return strdup(text); + + char c; + // count size delta to alloc a string just the right size. + int size_delta = 0; + + size_t ci; + size_t start = 0; + size_t strend; + int attention = 0; + for (ci = 0; ci <= len; ci++) { + c = text[ci]; + + if (attention) { // in : + if (c == ' ') { + attention = 0; + } else if (c == ':') { + // check if start to ci - start -1 is a valid emoji. + size_t elen = ci - start - 1; + const char* found = moj_lookup(text + start + 1, elen); + if (found) { + size_delta += elen - strlen(found); + } else { + start = ci; + } + attention = 0; + } + } else if (c == ':' && ci < len) { + attention = 1; + start = ci; + } + } + + // allocate buffer the right size. + size_t realsz = len + size_delta; + char* buf = malloc(realsz + 1); + if (!buf) + return NULL; + + start = 0; + attention = 0; + size_t offset = 0; + for (ci = 0; ci < len; ci++) { + c = text[ci]; + + if (attention) { // in : + if (c == ' ') { + attention = 0; + memcpy(buf + offset, text + start, ci - start + 1); + offset += ci - start + 1; + } else if (c == ':') { + // check if start to ci - start -1 is a valid emoji. + const char* found = moj_lookup(text + start + 1, ci - start - 1); + if (found) { + offset += sprintf(buf + offset, "%s", found); + } else { + // not an emoji? oops. + memcpy(buf + offset, text + start, ci - start + 1); + offset += ci - start + 1; + start = ci; + // the next might be one, though.. + } + attention = 0; + } + } else if (c == ':' && ci < len) { + attention = 1; + start = ci; + } else { + buf[offset] = c; + offset++; + } + } + if (attention) { + memcpy(buf + offset, text + start, ci - start); + offset += ci - start; + } + buf[realsz] = 0; + return buf; +} diff --git a/src/c/moj/src/moj.h b/src/c/moj/src/moj.h new file mode 100644 index 0000000..2c0d4e9 --- /dev/null +++ b/src/c/moj/src/moj.h @@ -0,0 +1,5 @@ +// moj: a small emoji library and cli. +#include + +char* moj_lookup(const char* name, size_t len); +char* moj_replace(const char* name, size_t len); diff --git a/src/c/moj/src/process.awk b/src/c/moj/src/process.awk new file mode 100644 index 0000000..b36e027 --- /dev/null +++ b/src/c/moj/src/process.awk @@ -0,0 +1,93 @@ +#!/usr/bin/awk -f +# This file takes in our CSV, emits simpler stuff. +# It also fixes up some things. + +# Bunch of convenience functions. +function didsee(name) { + if (name in seen) + return 1; + return 0; +} + +function hasnew() { + for (i = 2; i <= NF; i++) + if (!didsee($i)) + return 1; + return 0; +} + +function newname(name) { + if (didsee(name)) + return 0; + + seen[name] = 1; + return 1; +} + +function char(str, idx) { + return substr(str, idx, 1); +} + +function prefixed(name, prefixes) { + for (prefix in prefixes) + if (index(name, prefixes[prefix]) == 1) + return 1; + return 0; +} + +function inarray(name, array) { + for (i in array) + if (array[i] == name) + return 1; + return 0; +} + +function noing(name) { + # yep thats the name. + len = length(name); + if (len > 5 && substr(name, len - 2, 3) == "ing" && !prefixed(name, persons) && !inarray(name, exemptions)) { + if (char(name, len - 4) == char(name, len - 3)) { + # double letter combo, gg, nn, etc.. + short = substr(name, 0, len - 4); + } else { + short = substr(name, 0, len - 3); + } + if (newname(short)) + printf ",%s", short; + } +} + +BEGIN { + FS="\",\"" + + # I hate this. + persons[1] = "person_"; + persons[2] = "women_"; + persons[3] = "woman_" + persons[4] = "men_"; + persons[5] = "man_"; + + # I hate this even more. + exemptions[1] = "bowing"; + exemptions[2] = "golfing"; +} + +{ + if (hasnew()) + printf "%s", $1; + + for (i = 2; i <= NF; i++) { + if (newname($i)) { + printf ",%s", $i; + noing($i); + if (index($i, "person_") == 1) { + replaced = substr($i, 8); + if (newname(replaced)) { + printf ",%s", replaced; + } + noing(replaced); + } + } + } + printf "\n"; +} diff --git a/src/c/moj/src/profanity_emoji.c b/src/c/moj/src/profanity_emoji.c new file mode 100644 index 0000000..b50d575 --- /dev/null +++ b/src/c/moj/src/profanity_emoji.c @@ -0,0 +1,10 @@ +// moj: CProof plugin +// Intercepts text and replaces :emoji: + +#include +#include +#include "moj.h" + +char* prof_pre_chat_message_send(const char* const barejid, const char* message) { + return moj_replace(message, strlen(message)); // yep that is it. +} diff --git a/src/c/pid/pid.c b/src/c/pid/pid.c index 4ba02d0..53dde23 100644 --- a/src/c/pid/pid.c +++ b/src/c/pid/pid.c @@ -1,5 +1,5 @@ /* -Simple plugin to display the PID of the Profanity process and its parent +Simple plugin to display the PID of the CProof process and its parent Theme example in ~/.local/share/profanity/plugin_themes diff --git a/src/history.py b/src/history.py new file mode 100644 index 0000000..21a08ea --- /dev/null +++ b/src/history.py @@ -0,0 +1,167 @@ +""" +CProof plugin to read full chat history with custom editor using /hh command. +""" + +import os +import prof +import re +import sqlite3 + +from pathlib import Path + +_editor = "" +_current_user = "" +_logs_dir = "" +_db_file = "" +_win = "History" +_plugin_name = __file__.split('/')[-1] if __file__ else "history.py" + + +def _handle_win_input(): + pass + + +def _create_win(win): + if not prof.win_exists(win): + prof.win_create(win, _handle_win_input) + + +def _show_error(error): + error_msg = f"[History Reader] Error happened: {error}" + prof.cons_alert() + prof.cons_show(error_msg) + prof.log_error(error_msg) + return + + +def _str_sanitize(text): + return re.sub('[^a-zA-Z0-9\._]', '', text.replace("@", "_at_")) + + +def _cmd_editor(*args): + global _editor + _db_connection = sqlite3.connect(f"{_db_file}") + _db_connection.text_factory = bytes + _cur = _db_connection.cursor() + msg_buffer = [] + if args and args[0] == "set": + if len(args) < 2: + prof.cons_show("Please, use this format: \"/hh set \"") + return + _editor = args[1] + prof.settings_string_set("history", "editor", _editor) + prof.cons_show( + f"New editor set up successfully. New editor: {_editor}") + return + if args and args[0] == "--no-repeat": + prof.cons_show("[History Reader] Trying operation again...") + if not _current_user: + if args and args[0] == "--no-repeat": + _show_error("Can't fetch current user.") + return + prof.send_line(f"/plugins reload {_plugin_name}") + prof.send_line("/hh --no-repeat") + return + if not _editor: + prof.cons_show( + "Please, set up editor using /hh set. E.g. /hh set /usr/bin/vim") + return + if not (recipient := prof.get_current_recipient()): + prof.cons_show("Please, use this command in a chat window") + return + + tmpfpath = _logs_dir / f"{_str_sanitize(recipient)}.log" + res = _cur.execute("""SELECT timestamp, from_jid, message FROM `chatlogs` + WHERE ((`from_jid` = :jid AND `to_jid` = :myjid) + OR (`from_jid` = :myjid AND `to_jid` = :jid)) + ORDER BY id""", {"jid": recipient, "myjid": _current_user}) + + for bmsg in res.fetchall(): + try: + msg = [x.decode("UTF-8", errors="backslashreplace") for x in bmsg] + except Exception as e: + msg = [x.decode("UTF-8", errors="replace") for x in bmsg] + sender = "me" if msg[1] == _current_user else msg[1] + msg_buffer.append(f"{msg[0]} - {sender}: {msg[2]}") + + tmpfpath.write_text('\n'.join(msg_buffer), + encoding="UTF-8", errors="replace") + + pid = os.fork() + if pid == 0: + os.execlp(_editor, _editor, str(tmpfpath)) + else: + if pid == -1: + return + os.waitpid(pid, 0) + prof.send_line("/statusbar show name") + try: + tmpfpath.unlink() + except Exception as e: + prof.log_error( + f"[History Reader] Error on file deletion (path: {tmpfpath}): {e}") + return + + +def prof_on_connect(account_name, fulljid): + prof.log_debug( + f"[History Reader] prof_on_connect called with this JID: {fulljid}") + _init(fulljid) + + +def prof_on_disconnect(account_name, fulljid): + prof.log_debug( + f"[History Reader] prof_on_disconnect called with this JID: {fulljid}") + global _current_user + _current_user = "" + + +def prof_init(version, status, account_name, fulljid): + prof.log_debug( + f"[History Reader] prof_init called with this JID: {fulljid}") + _init(fulljid) + synopsis = ["/hh"] + description = "Open an editor and check user's history." + args = [ + ["set", "Set custom editor."] + ] + examples = [ + "/hh", + "/hh set /usr/bin/vim", + "/hh set gtk-open" + ] + prof.register_command("/hh", 0, 2, synopsis, + description, args, examples, _cmd_editor) + prof.completer_add("/hh", ["set"]) + prof.filepath_completer_add("/hh set") + + +def _init(fulljid): + global _current_user, _editor, _logs_dir, _db_file + prof.log_debug( + f"[History Reader] Initialization started with this JID: {fulljid}") + _current_user = fulljid and fulljid.split('/')[0] + if not _current_user: + prof.log_debug( + "[History Reader] Can't fetch current user, aborting initialization...") + return + _cproof_home = Path(os.getenv('XDG_DATA_HOME') + or "~/.local/share").expanduser() / "profanity" + _logs_dir = _cproof_home / "chatlogs" / _str_sanitize(_current_user) + db_dir = _cproof_home / "database" / _str_sanitize(_current_user) + if not _logs_dir.is_dir(): + _show_error(f"Can't open logs directory. Path: {_logs_dir}") + return + if not db_dir.is_dir(): + _show_error(f"Can't open DB directory. Path: {db_dir}") + return + _db_file = db_dir / "chatlog.db" + if not _db_file.is_file(): + _show_error(f"Log db is not present. Path: {_db_file}") + return + _editor = prof.settings_string_get("history", "editor", "") + if not _editor: + prof.cons_show("Please, set up editor using /hh set.") + else: + prof.cons_show( + f"[History Reader] Successfully started. Editor: {_editor}") diff --git a/src/htmlstrip.py b/src/htmlstrip.py index d303a2d..01354f1 100644 --- a/src/htmlstrip.py +++ b/src/htmlstrip.py @@ -1,10 +1,10 @@ # -*- coding:utf-8 -*- # vim: set ts=2 sw=2 sts=2 et: """ -Author: Diego Blanco - Removes "unwanted" HTML tags from incoming messages and unescapes html. +Author: Diego Blanco + Some XMPP clients like Adium or Pidgin send HTML tags like , , , etc to format text, specially when using OTR. This plugin removes them and also unescapes HTML like <, >, etc that these clients also tend to escape. diff --git a/src/imgur.py b/src/imgur.py index 48d572f..3ef12e7 100644 --- a/src/imgur.py +++ b/src/imgur.py @@ -5,7 +5,6 @@ Requires scrot installed for screenshot (on linux) sudo apt-get install scrot Uses built in screencapture on OSX - """ import prof diff --git a/src/notifycmd.py b/src/notifycmd.py new file mode 100644 index 0000000..3d0f37e --- /dev/null +++ b/src/notifycmd.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Executes a command when a message is received + +MIT License + +Copyright (c) 2016 Oliver Schmidhauser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import prof +from subprocess import Popen +import time +from sys import platform + +def secure(string): + string=string.replace('\\','\\\\') + string=string.replace("\"","\\\"") + string=string.replace("$","\$") + string=string.replace("`","\`") + return string + +def notifycmd(sender,message): + command = prof.settings_string_get("notifycmd", "command", "") + # replace markers with complex strings first to avoid "%%mittens"=>"(%%)mittens"=>"(%m)ittens"=>"ittens" but rather do "(%%)mittens"=>"%mittens" + command = command.replace("%%","{percentreplace}") + command = command.replace("%s","${senderreplace}") + command = command.replace("%m","${messagereplace}") + + + command = command.replace("{percentreplace}","%") + command = "set -f;senderreplace=\"{}\";messagereplace=\"{}\";{}".format(secure(sender),secure(message),command) + p = Popen(['sh', '-c', command]) + +def prof_post_chat_message_display(barejid, resource, message): + enabled = prof.settings_string_get("notifycmd", "enabled", "on") + current_recipient = prof.get_current_recipient() + if enabled == "on" or (enabled == "active" and current_recipient == barejid): + notifycmd(barejid, message) + return message + + +def prof_post_room_message_display(barejid, nick, message): + enabled = prof.settings_string_get("notifycmd", "enabled", "on") + rooms = prof.settings_string_get("notifycmd", "rooms", "mention") + current_muc = prof.get_current_muc() + mynick = prof.get_room_nick(barejid) + # Don't notify for ones own messages + if nick != mynick: + if rooms == "on": + if enabled == "on": + notifycmd(nick + " in " + barejid, message) + elif enabled == "active" and current_muc == barejid: + notifycmd(nick, message) + elif rooms == "mention": + if mynick in message: + if enabled == "on": + notifycmd(nick, message) + elif enabled == "active" and current_muc == barejid: + notifycmd(nick, message) + return message + + +def prof_post_priv_message_display(barejid, nick, message): + if enabled: + notifycmd(nick,message) + + return message + + +def _cmd_notifycmd(arg1=None, arg2=None): + if arg1 == "on": + prof.settings_string_set("notifycmd", "enabled", "on") + prof.cons_show("Notifycmd plugin enabled") + elif arg1 == "off": + prof.settings_string_set("notifycmd", "enabled", "off") + prof.cons_show("Notifycmd plugin disabled") + elif arg1 == "active": + prof.settings_string_set("notifycmd", "enabled", "active") + prof.cons_show("Notifycmd plugin enabled for active window only") + elif arg1 == "command": + if arg2 == None: + prof.cons_bad_cmd_usage("/notifycmd") + else: + prof.settings_string_set("notifycmd", "command", arg2) + prof.cons_show("notifycmd plugin command set to: " + arg2) + elif arg1 == "rooms": + if arg2 == None: + prof.cons_bad_cmd_usage("/notifycmd") + else: + prof.settings_string_set("notifycmd", "rooms", arg2) + prof.cons_show("notifycmd plugin notifications for rooms set to: " + arg2) + else: + enabled = prof.settings_string_get("notifycmd", "enabled", "on") + rooms = prof.settings_string_get("notifycmd", "rooms", "mention") + command = prof.settings_string_get("notifycmd", "command", "") + prof.cons_show("Notifycmd plugin settings:") + prof.cons_show("enabled : " + enabled) + prof.cons_show("command : " + command) + prof.cons_show("rooms : " + rooms) + +def prof_init(version, status, account_name, fulljid): + synopsis = [ + "/notifycmd on|off|active", + "/notifycmd command ", + "/notifycmd rooms on|off|mention" + ] + description = "Executes a command when a message is received" + args = [ + [ "on|off", "Enable/disable notifycmd for all windows" ], + [ "active", "Enable notifycmd for active window only" ], + [ "command ", "Set command to execute. Replaces %s with sender, %m with message and %% with literal %." ], + [ "rooms ", "Setting for multi-user rooms. Set to 'on', 'off' or 'mention'. If set to mention it will only run it your nick was mentioned." ] + ] + examples = [] + + prof.register_command("/notifycmd", 0, 2, synopsis, description, args, examples, _cmd_notifycmd) + prof.completer_add("/notifycmd", [ "on", "off","active","command","rooms" ]) + prof.completer_add("/notifycmd rooms", [ "on", "off", "mention" ]) diff --git a/src/sanitize.py b/src/sanitize.py new file mode 100644 index 0000000..0c6872d --- /dev/null +++ b/src/sanitize.py @@ -0,0 +1,144 @@ +""" +Sanitize incoming messages from Adium clients. + +MIT License + +Copyright (c) 2016 Nd2(SO4)3 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from io import StringIO +from lxml import etree +from lxml.etree import _ElementUnicodeResult, _ElementStringResult +import prof + +# ROADMAP +# +# Version 2: +# "/sanitise on|off": global auto detect setting +# "/sanitise on|off|": setting per JID: can be autodetect, off, +# or a special "mode" (if there are any). + +iq_count = 1 + +# Stores client information. JID/str~>Client/str. +clients = {} + + +def prof_on_presence_stanza_receive(stanza): + """Send a software version request when a contact comes online.""" + full_jid = etree.fromstring(stanza).get("from") + if full_jid: + send_version_request(full_jid) + return True + + +def send_version_request(full_jid): + global iq_count + + iq = etree.Element( + "iq", {"type": "get", "to": full_jid, "id": "sanitiser_" + str(iq_count)} + ) + etree.SubElement(iq, "query", {"xmlns": "jabber:iq:version"}) + + iq_count = iq_count + 1 + prof.send_stanza(etree.tostring(iq)) + + +def prof_on_iq_stanza_receive(stanza): + """Listen for software version responses and save results in the + 'clients' store. + """ + iq = etree.fromstring(stanza) + id = iq.get("id") + if not id: + return True + if not id.startswith("sanitiser_"): + return True + full_jid = iq.get("from") + if not full_jid: + return False + + query = iq.find("{jabber:iq:version}query") + if query is None: + return False + client = query.find("{jabber:iq:version}name") + if client is not None: + clients[full_jid] = client.text + return False + + +def prof_pre_chat_message_display(jid, resource, message): + """Sanitises chat room messages if necessary.""" + full_jid = "%s/%s" % (jid, resource) + if full_jid not in clients: + return message + return get_sanitiser(clients[full_jid])(message) + + +# Sanitiser functions: + + +def get_sanitiser(client): + """Returns appropriate sanitiser function for the given + Jabber client (string). + """ + if client.startswith("Adium"): + return sanitise + else: + return lambda msg: msg + + +def sanitise(message): + """Cleans message from HTML. Raises exceptions on problems.""" + parser = etree.HTMLParser() + tree = etree.parse(StringIO(unicode(message)), parser) + elems = tree.xpath("(//br|//a|//*[not(self::a)]/text())") + return "".join(map(substitude, elems)) + + +def try_sanitise(message): + """Same as 'sanitise' but returns unmodified message if it encounters + a problem, instead of throwing an exception. + """ + try: + return sanitise(message) + except: + return message + + +def substitude(elem): + """Substitudes an lxml element with a string representation.""" + if type(elem) in (_ElementUnicodeResult, _ElementStringResult): + return str(elem) + else: + if elem.tag == "br": + return "\n" + elif elem.tag == "a": + text = elem.text or "" + href = elem.get("href", "") + if not text and not href: + return "" + elif text == href: + return href + else: + return "%s (%s)" % (text, href) + else: + raise Exception("unhandled tag <%s>" % elem.tag) diff --git a/src/shortcuts.py b/src/shortcuts.py new file mode 100644 index 0000000..b19ef20 --- /dev/null +++ b/src/shortcuts.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +""" +Manage Message Shortcuts + +The MIT License (MIT) + +Copyright (c) 2016 Renรฉ Calles + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import json +import re +import prof + +ENABLED = True +SHORTCUTS_DICTIONARY = {u'shrug': u'ยฏ\_(ใƒ„)_/ยฏ', + u'yay': u'\o/'} + +plugin_win = "Shortcuts" + + +def _handle_win_input(win, line): + prof.win_show(win, line) + + +def create_win(): + if prof.win_exists(plugin_win) == False: + prof.win_create(plugin_win, _handle_win_input) + + +def _substitute(message): + if ENABLED: + shortcuts = re.findall(r'(?:^|\s)(:\w+:)(?=\s|$)', message) + for s in shortcuts: + shortcut_text = SHORTCUTS_DICTIONARY.get(s.strip()[1:-1]) + if shortcut_text: + message = message.replace(s.strip(), shortcut_text) + + return message + + +def save(key, value): + global SHORTCUTS_DICTIONARY + SHORTCUTS_DICTIONARY[key] = value + + +def _list_shortcuts(): + global SHORTCUTS_DICTIONARY + # ordered_dict = sorted(DICTIONARY.items(), key=lambda t: t[0]) + prof.cons_show(u'Shortcuts: ') + for key, value in SHORTCUTS_DICTIONARY.iteritems(): + prof.cons_show(u':{0}: => {1}'.format(key, value)) + + +def _cmd_shortcuts(arg1=None, arg2=None, arg3=None): + global ENABLED + + if arg1 == None: + prof.cons_show("Shortcuts Plugin is {}.".format("ON" if ENABLED else "OFF")) + elif arg1 == "on": + ENABLED = True + elif arg1 == "off": + ENABLED = False + elif arg1 == "list": + _list_shortcuts() + elif arg1 == "set": + if arg2 == None: + prof.cons_bad_cmd_usage("/shortcuts") + else: + save(arg2, arg3) + else: + prof.cons_bad_cmd_usage("/shortcuts") + + +def prof_pre_chat_message_send(jid, message): + return _substitute(message) + + +def prof_pre_room_message_send(room, message): + return _substitute(message) + + +def prof_pre_priv_message_send(room, nick, message): + return _substitute(message) + + +def prof_init(version, status): + synopsis = [ + "/shortcuts", + "/shortcuts on|off", + "/shortcuts set " + ] + description = "Manage shortcuts for emoticons or other stuff" + args = [ + [ "on|off", "Enable/disable shortcuts" ], + [ "set ", "Save shortcut" ], + [ "list", "List Shortcuts Dictionary"] + ] + examples = [ + "/shortcuts", + "/shortcuts on", + "/shortcuts set lol \"Laughing out loud\"" + ] + + prof.register_command("/shortcuts", 0, 3, synopsis, description, args, examples, _cmd_shortcuts) + prof.register_ac("/shortcuts", [ "on", "off", "set" ]) + + shortcut_keys = [":{}:".format(k) for k in SHORTCUTS_DICTIONARY.keys()] + # prof.cons_show(", ".join(shortcut_keys)) + prof.register_ac(" ", shortcut_keys) \ No newline at end of file diff --git a/src/sounds.py b/src/sounds.py index 8e007bb..7971ef3 100644 --- a/src/sounds.py +++ b/src/sounds.py @@ -1,5 +1,6 @@ """ -Play sounds on various events. +Play sounds on receiving messages. + Default settings require mpg123 to be installed on the host system: sudo apt-get install mpg123 brew install mpg123 @@ -209,7 +210,7 @@ def prof_init(version, status, account_name, fulljid): "/sounds clear private", "/sounds rooms add ", ] - description = "Play sounds on various Profanity events. Calling with no args shows current sound files." + description = "Play sounds on receiving messages. Executing the command without arguments displays current settings." args = [ ["on|off", "Enable or disable playing sounds."], [