├── .gitignore ├── rebar.config ├── src ├── pe4kin.app.src ├── pe4kin_app.erl ├── pe4kin_sup.erl ├── pe4kin_util.erl ├── pe4kin_http.erl ├── pe4kin_types.erl ├── pe4kin.erl ├── pe4kin_receiver.erl └── pe4kin_emoji.erl ├── rebar.lock ├── .github └── workflows │ └── ci.yml ├── mk_emoji.py ├── README.md ├── test ├── mock_longpoll_server.erl └── pe4kin_receiver_SUITE.erl ├── LICENSE └── emoji.json /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | doc/ 13 | erl_crash.dump 14 | .rebar 15 | logs 16 | _build 17 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | 3 | {erl_opts, [debug_info]}. 4 | 5 | {deps, [{gun, "2.2.0"}, 6 | {jsx, "3.1.0"}]}. 7 | 8 | %% https://github.com/erlang/rebar3/issues/2364#issuecomment-695819058 9 | {overrides, [ 10 | {override, gun, [{deps, [{cowlib, "2.15.0"}]}]} 11 | ]}. 12 | 13 | {profiles, 14 | [{test, 15 | [ 16 | {deps, [{cowboy, "2.12.0"}]} 17 | ]}] 18 | }. 19 | 20 | {xref_checks, [ 21 | undefined_function_calls, 22 | undefined_functions, 23 | deprecated_functions_calls, 24 | deprecated_functions 25 | ]}. 26 | 27 | {dialyzer, 28 | [ 29 | %% {warnings, [unknown]}, 30 | {plt_apps, all_deps} 31 | ]}. 32 | -------------------------------------------------------------------------------- /src/pe4kin.app.src: -------------------------------------------------------------------------------- 1 | {application, pe4kin, 2 | [{description, "Telegram messenger bot API wrapper"}, 3 | {vsn, "0.2.5"}, 4 | {registered, []}, 5 | {mod, { pe4kin_app, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib, 9 | gun 10 | ]}, 11 | {env, 12 | [ 13 | {api_server_endpoint, <<"https://api.telegram.org">>}, 14 | {http_tcp_opts, []}, 15 | {http_tls_opts, [{verify, verify_none}]}, 16 | {keepalive_pool, 17 | #{max_count => 10, 18 | checkout_retry => [500, 1000, 1500]}}, 19 | {tokens, #{}} 20 | ]}, 21 | {modules, []}, 22 | 23 | {licenses, ["Apache 2.0"]}, 24 | {links, [{"Github", "https://github.com/seriyps/pe4kin"}]} 25 | ]}. 26 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.15.0">>},1}, 3 | {<<"gun">>,{pkg,<<"gun">>,<<"2.2.0">>},0}, 4 | {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}]}. 5 | [ 6 | {pkg_hash,[ 7 | {<<"cowlib">>, <<"3C97A318A933962D1C12B96AB7C1D728267D2C523C25A5B57B0F93392B6E9E25">>}, 8 | {<<"gun">>, <<"B8F6B7D417E277D4C2B0DC3C07DFDF892447B087F1CC1CAFF9C0F556B884E33D">>}, 9 | {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}]}, 10 | {pkg_hash_ext,[ 11 | {<<"cowlib">>, <<"4F00C879A64B4FE7C8FCB42A4281925E9FFDB928820B03C3AD325A617E857532">>}, 12 | {<<"gun">>, <<"76022700C64287FEB4DF93A1795CFF6741B83FB37415C40C34C38D2A4645261A">>}, 13 | {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}]} 14 | ]. 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | ci: 11 | name: Run checks and tests over ${{matrix.otp}} and ${{matrix.os}} 12 | runs-on: ${{matrix.os}} 13 | container: 14 | image: erlang:${{ matrix.otp }} 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | otp: ["28.0", "27.3", "26.2", "25.3"] 20 | os: ["ubuntu-22.04"] 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Compile 26 | run: rebar3 compile 27 | 28 | - name: xref 29 | run: rebar3 xref 30 | 31 | - name: Eunit test 32 | run: rebar3 eunit 33 | 34 | - name: Common test 35 | run: rebar3 ct 36 | 37 | - name: Generate docs 38 | run: rebar3 edoc 39 | 40 | - name: Dialyze 41 | run: rebar3 dialyzer 42 | -------------------------------------------------------------------------------- /src/pe4kin_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc pe4kin application public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(pe4kin_app). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([start/2, stop/1]). 12 | 13 | %%==================================================================== 14 | %% API 15 | %%==================================================================== 16 | 17 | start(_StartType, _StartArgs) -> 18 | pe4kin_http:start_pool(), 19 | pe4kin_sup:start_link(). 20 | 21 | %%-------------------------------------------------------------------- 22 | stop(_State) -> 23 | pe4kin_http:stop_pool(), 24 | ok. 25 | 26 | %%==================================================================== 27 | %% Internal functions 28 | %%==================================================================== 29 | -------------------------------------------------------------------------------- /src/pe4kin_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc pe4kin top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(pe4kin_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | 13 | %% Supervisor callbacks 14 | -export([init/1]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | %%==================================================================== 19 | %% API functions 20 | %%==================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 24 | 25 | %%==================================================================== 26 | %% Supervisor callbacks 27 | %%==================================================================== 28 | 29 | %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} 30 | init([]) -> 31 | {ok, { {one_for_all, 0, 1}, []} }. 32 | 33 | %%==================================================================== 34 | %% Internal functions 35 | %%==================================================================== 36 | -------------------------------------------------------------------------------- /mk_emoji.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | # 3 | # This script generates src/pe4kin_emoji.erl from emoji.json 4 | # emoji.json extracted from https://web.telegram.org by smth like 5 | # ``` 6 | # var nodes = document.querySelectorAll("a.composer_emoji_btn"); 7 | # var pairs = Array.prototype.slice.call(nodes.map(function(a) { return [a.dataset.code, a.getAttribute("title")] })) 8 | # JSON.stringify(pairs) 9 | # ``` 10 | # and minor postprocessing. 11 | # FIXME: telegram specificaly handles some emojes. It sends 2 unicode chars: 12 | # ``` 13 | # :hash: 35,8419 14 | # :zero: 48,8419 15 | # :one: 49,8419 16 | # :two: 50,8419 17 | # :three: 51,8419 18 | # :four: 52,8419 19 | # :five: 53,8419 20 | # :six: 54,8419 21 | # :seven: 55,8419 22 | # :eight: 56,8419 23 | # :nine: 57,8419 24 | # ``` 25 | # Generated code now handles them not correctly. 26 | 27 | import json 28 | import datetime 29 | import sys 30 | 31 | 32 | TEMPLATE = """%% 33 | %% This file is auto-generated at {when} by {script} from {json}. 34 | %% DO NOT EDIT! 35 | 36 | -module(pe4kin_emoji). 37 | -export([name_to_char/1, char_to_name/1, is_emoji/1, names/0]). 38 | 39 | -spec name_to_char(atom()) -> char(). 40 | {name2char}; 41 | name_to_char(_) -> throw({{pe4kin, invalid_emoji_name}}). 42 | 43 | -spec char_to_name(char()) -> atom(). 44 | {char2name}; 45 | char_to_name(_) -> throw({{pe4kin, unknown_emoji}}). 46 | 47 | -spec names() -> ordsets:ordset(atom()). 48 | names() -> 49 | [{names}]. 50 | 51 | -spec is_emoji(char()) -> boolean(). 52 | is_emoji(Char) -> 53 | try char_to_name(Char) of 54 | _ -> true 55 | catch throw:{{pe4kin, unknown_emoji}} -> 56 | false 57 | end. 58 | """ 59 | 60 | 61 | def main(): 62 | src_file = "emoji.json" 63 | dst_file = "src/pe4kin_emoji.erl" 64 | when = datetime.datetime.utcnow() 65 | script = sys.argv[0] 66 | with open(src_file) as src: 67 | emoji_map = json.load(src) 68 | 69 | name2char = ";\n".join( 70 | "name_to_char('{}') -> 16#{}".format(name, char) 71 | for name, char in sorted(emoji_map.items(), key=lambda kv: kv[0]) 72 | ) 73 | char2name = ";\n".join( 74 | "char_to_name(16#{}) -> '{}'".format(char, name) 75 | for name, char in sorted(emoji_map.items(), key=lambda kv: int(kv[1], 16)) 76 | ) 77 | names = ",\n".join( 78 | "'{}'".format(name) 79 | for name in sorted(emoji_map.keys()) 80 | ) 81 | erl_module = TEMPLATE.format( 82 | when=when, 83 | script=script, 84 | json=src_file, 85 | name2char=name2char, 86 | char2name=char2name, 87 | names=names 88 | ) 89 | with open(dst_file, "w") as dst: 90 | dst.write(erl_module) 91 | print "See", dst_file 92 | 93 | 94 | if __name__ == '__main__': 95 | main() 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pe4kin 2 | ===== 3 | 4 | Erlang Telegram bot library. 5 | Erlang wrapper for Telegram messenger bot APIs. 6 | 7 | With pe4kin you can receive, send, reply and forward text, as well as media 8 | messages using [telegram bot API](https://core.telegram.org/bots/api). 9 | 10 | Pe4kin is the name of the [multiplicational postman](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%87%D1%82%D0%B0%D0%BB%D1%8C%D0%BE%D0%BD_%D0%9F%D0%B5%D1%87%D0%BA%D0%B8%D0%BD). 11 | 12 | 13 | Example 14 | ------- 15 | 16 | ```erlang 17 | % $ rebar3 compile 18 | % $ rebar3 shell 19 | 20 | > application:ensure_all_started(pe4kin). 21 | 22 | % First of all, you need to get bot's credentials. 23 | % Consult telegram docs https://core.telegram.org/bots#6-botfather 24 | > BotName = <<"Pe4kin_Test_Bot">>. 25 | > BotToken = <<"186***:******">>. 26 | 27 | 28 | % 29 | % Receive messages from users 30 | % 31 | 32 | % Launch incoming messages receiver worker. 33 | % XXX: In real applications you'll want to launch pe4kin_receiver gen_server 34 | % under supervisor! 35 | > pe4kin:launch_bot(BotName, BotToken, #{receiver => true}). 36 | 37 | % Subscribe self to incoming messages 38 | > pe4kin_receiver:subscribe(BotName, self()). 39 | 40 | % Start HTTP-polling of telegram server for incoming messages 41 | > pe4kin_receiver:start_http_poll(BotName, #{limit=>100, timeout=>60}). 42 | 43 | % Wait for new messages. 44 | % ...here you should send "/start" to this bot via telegram client... 45 | > Update = receive {pe4kin_update, BotName, Upd} -> Upd end. 46 | 47 | % Guess incoming update payload type. 48 | > message = pe4kin_types:update_type(Update). 49 | 50 | % Note `ChatId` - it uniquely identifies this particular chat and will be used 51 | % in replies. 52 | > #{<<"message">> := #{<<"chat">> := #{<<"id">> := ChatId}} = Message} = Update. 53 | 54 | % Guess message payload type 55 | > text = pe4kin_types:message_type(Message). 56 | 57 | % Decode "/start" command (will raise exception if no commands in message) 58 | > {<<"/start">>, BotName, true, _} = pe4kin_types:message_command(BotName, Message). 59 | 60 | 61 | % 62 | % Send messages 63 | % 64 | 65 | % Check that bot configured properly 66 | > {ok, #{<<"first_name">> := _, <<"id">> := _, <<"username">> := _}} = pe4kin:get_me(BotName). 67 | 68 | % Send reply to previously received `Update` message (note `ChatId`) 69 | > From = maps:get(<<"first_name">>, maps:get(<<"from">>, Message, #{}), <<"Anonymous">>). 70 | > HeartEmoji = pe4kin_emoji:name_to_char('heart'). 71 | > ResponseText = unicode:characters_to_binary([<<"Hello, ">>, From, HeartEmoji]). 72 | > {ok, _} = pe4kin:send_message(BotName, #{chat_id => ChatId, text => ResponseText}). 73 | 74 | % Send image to the same chat 75 | > CatImgFileName = "funny_cat.jpg". 76 | > {ok, CatImgBin} = file:read_file(CatImgFileName). 77 | > Photo = {file, CatImgFileName, <<"image/jpeg">>, CatImgBin}. 78 | > {ok, _} = pe4kin:send_photo(BotName, #{chat_id => ChatId, photo => Photo}). 79 | ``` 80 | -------------------------------------------------------------------------------- /src/pe4kin_util.erl: -------------------------------------------------------------------------------- 1 | %%% @author Sergey Prokhorov 2 | %%% @copyright (C) 2016, Sergey Prokhorov 3 | %%% @doc 4 | %%% Utility functions 5 | %%% @end 6 | %%% Created : 25 May 2016 by Sergey Prokhorov 7 | 8 | -module(pe4kin_util). 9 | -export([slice/2, slice/3, slice_pos/2, slice_pos/3]). 10 | -export([strlen/1]). 11 | -export([strip/1, strip/2]). 12 | -export([to_lower/1]). 13 | -export([to_binary/1]). 14 | 15 | %% @doc like binary:part/3 but Offset and Size in UTF-8 codepoints, not bytes 16 | slice(Utf8, Offset, Size) -> 17 | {BOffset, BSize} = slice_pos(Utf8, Offset, Size), 18 | binary:part(Utf8, BOffset, BSize). 19 | 20 | slice(Utf8, Size) -> 21 | BSize = slice_pos(Utf8, Size), 22 | binary:part(Utf8, 0, BSize). 23 | 24 | %% @doc convert utf-8 Offset and Size to byte offset and size 25 | -spec slice_pos(<<_:8>>, non_neg_integer(), non_neg_integer()) -> 26 | {ByteOffset :: non_neg_integer(), ByteSize :: non_neg_integer()}. 27 | slice_pos(Utf8, Offset, Size) -> 28 | slice_pos_(Utf8, Offset, Size, 0). 29 | slice_pos_(Utf8, 0, Size, BO) -> {BO, slice_pos(Utf8, Size)}; 30 | slice_pos_(<<_C/utf8, Utf8/binary>> = B, Offset, Size, BO) -> 31 | SizeOfC = size(B) - size(Utf8), 32 | slice_pos_(Utf8, Offset -1, Size, BO + SizeOfC). 33 | 34 | -spec slice_pos(<<_:8>>, non_neg_integer()) -> non_neg_integer(). 35 | slice_pos(Utf8, Size) -> 36 | slice_pos_(Utf8, Size, 0). 37 | slice_pos_(_, 0, BS) -> BS; 38 | slice_pos_(<<_C/utf8, Utf8/binary>> = B, Size, BS) -> 39 | SizeOfC = size(B) - size(Utf8), 40 | slice_pos_(Utf8, Size - 1, BS + SizeOfC). 41 | 42 | %% slice(Utf8, 0, Size) -> 43 | %% slice(Utf8, Size); 44 | %% slice(<<_/utf8, Utf8/binary>>, Offset, Size) -> 45 | %% slice(Utf8, Offset -1, Size). 46 | 47 | %% slice(_, 0) -> <<>>; 48 | %% slice(<>, Size) -> 49 | %% <>. 50 | 51 | 52 | strlen(Utf8) -> 53 | strlen(Utf8, 0). 54 | 55 | strlen(<<_/utf8, Rest/binary>>, Len) -> 56 | strlen(Rest, Len + 1); 57 | strlen(<<>>, Len) -> Len. 58 | 59 | 60 | 61 | -define(IS_WSP(C), (C == $\s) orelse (C == $\n) orelse (C == $\t) orelse (C == $\r)). 62 | strip(Bin) -> 63 | strip(Bin, both). 64 | 65 | strip(Bin, both) -> 66 | strip(strip(Bin, left), right); 67 | 68 | strip(<>, left) when ?IS_WSP(C) -> 69 | strip(Rest, left); 70 | strip(Bin, right) when size(Bin) > 0 -> 71 | SizeMinus1 = size(Bin) - 1, 72 | case Bin of 73 | <> when ?IS_WSP(C) -> 74 | strip(Rest, right); 75 | Stripped -> 76 | Stripped 77 | end; 78 | strip(Bin, _) -> Bin. 79 | 80 | 81 | 82 | %% @doc lowercase ASCII binary 83 | to_lower(Bin) -> 84 | list_to_binary(string:to_lower(binary_to_list(Bin))). 85 | 86 | to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8); 87 | to_binary(String) when is_list(String) -> list_to_binary(String); 88 | to_binary(Int) when is_integer(Int) -> integer_to_binary(Int); 89 | to_binary(Bin) when is_binary(Bin) -> Bin. 90 | -------------------------------------------------------------------------------- /test/mock_longpoll_server.erl: -------------------------------------------------------------------------------- 1 | %% @doc mock server for telegram long-poll API 2 | -module(mock_longpoll_server). 3 | 4 | -export([start/1, stop/1]). 5 | -export([add_updates/2]). 6 | 7 | -behaviour(cowboy_handler). 8 | -export([init/2]). 9 | 10 | -type update() :: map(). 11 | 12 | -record(st, 13 | {cowboy, 14 | state :: pid()}). 15 | 16 | start(Opts0) -> 17 | application:ensure_all_started(cowboy), 18 | StatePid = proc_lib:spawn_link(fun state_enter/0), 19 | Defaults = #{port => 1080, 20 | token => <<"1234:my-token">>, 21 | state_pid => StatePid, 22 | api_handler => fun default_api_handler/4}, 23 | Opts = maps:merge(Defaults, Opts0), 24 | Routes = 25 | [{'_', 26 | [{"/:token/:method", ?MODULE, Opts} 27 | ]}], 28 | Dispatch = cowboy_router:compile(Routes), 29 | {ok, _} = cowboy:start_clear( 30 | ?MODULE, 31 | #{max_connections => 64, 32 | socket_opts => [{port, maps:get(port, Opts)}]}, 33 | #{env => #{dispatch => Dispatch}}), 34 | Host = iolist_to_binary(io_lib:format("http://localhost:~w", 35 | [maps:get(port, Opts)])), 36 | ok = application:set_env(pe4kin, api_server_endpoint, Host), 37 | #st{cowboy = ?MODULE, 38 | state = StatePid}. 39 | 40 | stop(#st{cowboy = ?MODULE, 41 | state = StatePid}) -> 42 | unlink(StatePid), 43 | exit(StatePid, shutdown), 44 | cowboy:stop_listener(?MODULE). 45 | 46 | -spec add_updates([update()], #st{}) -> ok. 47 | add_updates(Updates, #st{state = StatePid}) -> 48 | StatePid ! {add, Updates}, 49 | ok. 50 | 51 | subscribe(Pid, Offset, Timeout) -> 52 | %% There is a race-condition here when Timeout is too short 53 | Pid ! {subscribe, self(), Offset}, 54 | receive 55 | {updates, Updates} -> 56 | Updates 57 | after Timeout -> 58 | Pid ! {unsubscribe, self()}, 59 | [] 60 | end. 61 | 62 | state_enter() -> 63 | Updates = [], 64 | Subscribers = [], 65 | state_loop(Updates, 0, Subscribers). 66 | 67 | state_loop(Updates, Offset, Subscribers) -> 68 | ct:pal("~p(~p, ~p, ~p)", [?FUNCTION_NAME, Updates, Offset, Subscribers]), 69 | receive 70 | {add, NewUpdates} -> 71 | NUpdates = length(NewUpdates), 72 | Offsets = lists:seq(Offset, Offset + NUpdates - 1), 73 | UpdatesWithOffsets = lists:zip(Offsets, NewUpdates), 74 | Updates1 = lists:sort(UpdatesWithOffsets ++ Updates), 75 | state_loop(Updates1, Offset + NUpdates, 76 | state_invariant(Updates1, Subscribers)); 77 | {subscribe, Pid, After} -> 78 | Subscribers1 = [{Pid, After} | Subscribers], 79 | state_loop(Updates, Offset, state_invariant(Updates, Subscribers1)); 80 | {unsubscribe, Pid} -> 81 | state_loop(Updates, Offset, [Sub || {SubPid, _} = Sub <- Subscribers, 82 | SubPid =/= Pid]) 83 | end. 84 | 85 | state_invariant(Updates, [{Pid, After} = Sub | Subscribers]) -> 86 | ToNotify = [Upd || {At, _} = Upd <- Updates, At >= After], 87 | case ToNotify of 88 | [] -> 89 | [Sub | state_invariant(Updates, Subscribers)]; 90 | _ -> 91 | Pid ! {updates, ToNotify}, 92 | state_invariant(Updates, Subscribers) 93 | end; 94 | state_invariant(_, []) -> []. 95 | 96 | 97 | init(Req0, #{token := Token} = Opts) -> 98 | %% Method = cowboy_req:method(Req0), 99 | %% Path = cowboy_req:path(Req0), 100 | QS = maps:from_list(cowboy_req:parse_qs(Req0)), 101 | <<"bot", ReqToken/binary>> = cowboy_req:binding(token, Req0), 102 | ApiMethod = cowboy_req:binding(method, Req0), 103 | case Token == ReqToken of 104 | true -> 105 | {ok, Body, Req1} = cowboy_req:read_body(Req0), 106 | {Code, Response} = handle_api_call(ApiMethod, QS, Body, Opts), 107 | reply(Code, Response, Req1); 108 | false -> 109 | reply(401, <<"Unauthorized">>, Req0) 110 | end. 111 | 112 | reply(Code, Response, Req) when Code < 300, Code >= 200 -> 113 | Body = pe4kin_http:json_encode(#{ok => true, 114 | result => Response}), 115 | Req1 = cowboy_req:reply(Code, #{<<"content-type">> => <<"application/json">>}, Body, Req), 116 | {ok, Req1, []}; 117 | reply(Code, Response, Req) -> 118 | Body = pe4kin_http:json_encode(#{ok => false, 119 | error_code => Code, 120 | description => Response}), 121 | Req1 = cowboy_req:reply(Code, #{<<"content-type">> => <<"application/json">>}, Body, Req), 122 | {ok, Req1, []}. 123 | 124 | handle_api_call(<<"getUpdates">>, QS, <<>>, #{state_pid := Pid}) -> 125 | Defaults = #{<<"offset">> => <<"0">>, 126 | <<"timeout">> => <<"10">>}, 127 | #{<<"offset">> := OffsetBin, 128 | <<"timeout">> := TimeoutBin} = maps:merge(Defaults, QS), 129 | Offset = binary_to_integer(OffsetBin), 130 | Timeout = binary_to_integer(TimeoutBin) * 1000, 131 | Changes = subscribe(Pid, Offset, Timeout), %Will block 132 | {200, lists:map(fun({UpdOffset, Upd}) -> 133 | Upd#{update_id => UpdOffset} 134 | end, Changes)}; 135 | handle_api_call(Method, QS, Body, #{api_handler := Handler} = Opts) -> 136 | Handler(Method, QS, Body, Opts). 137 | 138 | default_api_handler(Method, QS, Body, _) -> 139 | {200, #{method => Method, 140 | query => QS, 141 | body => Body}}. 142 | -------------------------------------------------------------------------------- /test/pe4kin_receiver_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% @doc Basic tests with long pooling 2 | -module(pe4kin_receiver_SUITE). 3 | 4 | -export([all/0, 5 | groups/0, 6 | init_per_suite/1, 7 | end_per_suite/1, 8 | init_per_testcase/2, 9 | end_per_testcase/2 10 | ]). 11 | 12 | -export([basic_update_case/1, 13 | basic_2_updates_case/1, 14 | basic_3_separate_updates_case/1, 15 | make_api_call_case/1 16 | ]). 17 | 18 | -include_lib("common_test/include/ct.hrl"). 19 | -include_lib("stdlib/include/assert.hrl"). 20 | -define(APP, temp_email). 21 | 22 | 23 | all() -> 24 | %% All exported functions of arity 1 whose name ends with "_case" 25 | [{group, all}]. 26 | 27 | groups() -> 28 | Exports = ?MODULE:module_info(exports), 29 | Cases = [F 30 | || {F, A} <- Exports, 31 | A == 1, 32 | case lists:reverse(atom_to_list(F)) of 33 | "esac_" ++ _ -> true; 34 | _ -> false 35 | end], 36 | [{all, Cases}]. 37 | 38 | 39 | init_per_suite(Cfg) -> 40 | application:load(pe4kin), 41 | Cfg. 42 | 43 | end_per_suite(Cfg) -> 44 | Cfg. 45 | 46 | 47 | init_per_testcase(Name, Cfg) -> 48 | BotName = <<"my-bot">>, 49 | Tok = <<"my-token">>, 50 | Srv = mock_longpoll_server:start(#{token => Tok}), 51 | Env = application:get_all_env(pe4kin), 52 | application:set_env(pe4kin, tokens, #{BotName => Tok}), 53 | {ok, _} = application:ensure_all_started(pe4kin), 54 | [{pre_env, Env} | ?MODULE:Name({pre, [{server, Srv}, {name, BotName}, {token, Tok} | Cfg]})]. 55 | 56 | end_per_testcase(Name, Cfg) -> 57 | %% Env = ?config(pre_env, Cfg), 58 | Srv = ?config(server, Cfg), 59 | ?MODULE:Name({post, Cfg}), 60 | ok = mock_longpoll_server:stop(Srv), 61 | ok = application:stop(pe4kin), 62 | Cfg. 63 | 64 | 65 | %% @doc Test single update 66 | basic_update_case({pre, Cfg}) -> 67 | Name = ?config(name, Cfg), 68 | Tok = ?config(token, Cfg), 69 | {ok, Pid} = pe4kin_receiver:start_link(Name, Tok, #{}), 70 | [{recv_pid, Pid} | Cfg]; 71 | basic_update_case({post, Cfg}) -> 72 | RecvPid = ?config(recv_pid, Cfg), 73 | unlink(RecvPid), 74 | exit(RecvPid, shutdown), 75 | Cfg; 76 | basic_update_case(Cfg) when is_list(Cfg) -> 77 | Srv = ?config(server, Cfg), 78 | Name = ?config(name, Cfg), 79 | ok = pe4kin_receiver:subscribe(Name, self()), 80 | ok = pe4kin_receiver:start_http_poll(Name, #{}), 81 | ok = mock_longpoll_server:add_updates([#{<<"message">> => #{}}], Srv), 82 | ?assertEqual([#{<<"message">> => #{}, 83 | <<"update_id">> => 0}], recv(Name, 1)), 84 | ?assertEqual([], flush(Name)). 85 | 86 | %% @doc Test 2 updates at once 87 | basic_2_updates_case({pre, Cfg}) -> 88 | Name = ?config(name, Cfg), 89 | Tok = ?config(token, Cfg), 90 | {ok, Pid} = pe4kin_receiver:start_link(Name, Tok, #{}), 91 | [{recv_pid, Pid} | Cfg]; 92 | basic_2_updates_case({post, Cfg}) -> 93 | RecvPid = ?config(recv_pid, Cfg), 94 | unlink(RecvPid), 95 | exit(RecvPid, shutdown), 96 | Cfg; 97 | basic_2_updates_case(Cfg) when is_list(Cfg) -> 98 | Srv = ?config(server, Cfg), 99 | Name = ?config(name, Cfg), 100 | ok = pe4kin_receiver:subscribe(Name, self()), 101 | ok = pe4kin_receiver:start_http_poll(Name, #{}), 102 | ok = mock_longpoll_server:add_updates( 103 | [#{<<"message">> => #{<<"text">> => <<"msg1">>}}, 104 | #{<<"message">> => #{<<"text">> => <<"msg2">>}}], Srv), 105 | ?assertEqual([#{<<"message">> => #{<<"text">> => <<"msg1">>}, 106 | <<"update_id">> => 0}, 107 | #{<<"message">> => #{<<"text">> => <<"msg2">>}, 108 | <<"update_id">> => 1}], recv(Name, 2)), 109 | ?assertEqual([], flush(Name)). 110 | 111 | %% @doc Test 3 updates at once 112 | basic_3_separate_updates_case({pre, Cfg}) -> 113 | Name = ?config(name, Cfg), 114 | Tok = ?config(token, Cfg), 115 | {ok, Pid} = pe4kin_receiver:start_link(Name, Tok, #{}), 116 | [{recv_pid, Pid} | Cfg]; 117 | basic_3_separate_updates_case({post, Cfg}) -> 118 | RecvPid = ?config(recv_pid, Cfg), 119 | unlink(RecvPid), 120 | exit(RecvPid, shutdown), 121 | Cfg; 122 | basic_3_separate_updates_case(Cfg) when is_list(Cfg) -> 123 | Srv = ?config(server, Cfg), 124 | Name = ?config(name, Cfg), 125 | ok = pe4kin_receiver:subscribe(Name, self()), 126 | ok = pe4kin_receiver:start_http_poll(Name, #{}), 127 | lists:foreach( 128 | fun(I) -> 129 | Text = <<"msg", (integer_to_binary(I))/binary>>, 130 | ok = mock_longpoll_server:add_updates( 131 | [#{<<"message">> => #{<<"text">> => Text}}], Srv), 132 | ?assertEqual([#{<<"message">> => #{<<"text">> => Text}, 133 | <<"update_id">> => I}], recv(Name, 1)), 134 | ?assertEqual([], flush(Name)) 135 | end, lists:seq(0, 2)). 136 | 137 | make_api_call_case({pre, Cfg}) -> 138 | Cfg; 139 | make_api_call_case({post, Cfg}) -> 140 | Cfg; 141 | make_api_call_case(Cfg) when is_list(Cfg) -> 142 | Name = ?config(name, Cfg), 143 | {ok, MeRes} = pe4kin:get_me(Name), 144 | ?assertEqual(#{<<"body">> => <<>>, 145 | <<"method">> => <<"getMe">>, 146 | <<"query">> => #{}}, MeRes), 147 | {ok, MsgRes} = pe4kin:send_message(Name, #{chat_id => 1, text => <<"text">>}), 148 | ?assertMatch(#{<<"body">> := _, 149 | <<"method">> := <<"sendMessage">>, 150 | <<"query">> := #{}}, 151 | MsgRes), 152 | ?assertEqual(#{<<"text">> => <<"text">>, 153 | <<"chat_id">> => 1}, 154 | pe4kin_http:json_decode(maps:get(<<"body">>, MsgRes))), 155 | File = {file, <<"test.txt">>, <<"text/plain">>, <<"test">>}, 156 | {ok, DocRes} = pe4kin:send_document(Name, #{chat_id => 1, document => File}), 157 | ?assertMatch(#{<<"body">> := <<"\r\n--", _/binary>>, 158 | <<"method">> := <<"sendDocument">>, 159 | <<"query">> := #{}}, DocRes), 160 | MPBody = maps:get(<<"body">>, DocRes), 161 | ?assertMatch({_, _}, binary:match(MPBody, <<"filename=\"test.txt\"">>)), 162 | ?assertMatch({_, _}, binary:match(MPBody, <<"Content-Type: text/plain">>)), 163 | ?assertMatch({_, _}, binary:match(MPBody, <<"\r\n\r\ntest\r\n">>)). 164 | 165 | flush(Bot) -> 166 | recv(Bot, 5, 0). 167 | 168 | recv(Bot, N) -> 169 | recv(Bot, N, 5000). 170 | 171 | recv(_, 0, _) -> 172 | []; 173 | recv(Bot, N, Timeout) -> 174 | receive 175 | {pe4kin_update, Bot, Update} -> 176 | [Update | recv(Bot, N - 1, Timeout)] 177 | after Timeout -> 178 | [] 179 | end. 180 | -------------------------------------------------------------------------------- /src/pe4kin_http.erl: -------------------------------------------------------------------------------- 1 | %% @doc abstract API for HTTP client 2 | 3 | -module(pe4kin_http). 4 | 5 | -export([start_pool/0, stop_pool/0]). 6 | -export([open/0, open/3, get/1, post/3]). 7 | -export([json_encode/1, json_decode/1]). 8 | 9 | -export_type([req_body/0]). 10 | 11 | -type response() :: {non_neg_integer(), [{binary(), binary()}], iodata()}. 12 | -type path() :: iodata(). 13 | -type req_headers() :: [{binary(), iodata()}]. 14 | -type disposition() :: 15 | {Disposition :: binary(), Params :: [{binary(), iodata()}]}. 16 | -type multipart() :: 17 | [{file, file:name_all(), disposition(), req_headers()} | 18 | {Name :: binary(), Payload :: binary(), disposition(), req_headers()} | 19 | {Name :: binary(), Payload :: binary()}]. 20 | -type req_body() :: binary() | 21 | iodata() | 22 | {form, #{binary() => binary()}} | 23 | {json, pe4kin:json_value()} | 24 | {multipart, multipart()}. 25 | 26 | open() -> 27 | {ok, Endpoint} = pe4kin:get_env(api_server_endpoint), 28 | {Transport, Host, Port} = parse_endpoint(Endpoint), 29 | open(Transport, Host, Port). 30 | 31 | open(Transport, Host, Port) -> 32 | {ok, Pid} = gun:open(Host, Port, #{transport => Transport}), 33 | _Protocol = gun:await_up(Pid), 34 | {ok, Pid}. 35 | 36 | -spec get(iodata()) -> response() | {error, any()}. 37 | get(Path) -> 38 | {Opts, Host} = http_req_opts(), 39 | await(gun_pool:get(Path, #{<<"host">> => Host}, Opts)). 40 | 41 | -spec post(path(), req_headers(), req_body()) -> response() | {error, any()}. 42 | post(Path, Headers, Body) when is_binary(Body); 43 | is_list(Body) -> 44 | {Opts, Host} = http_req_opts(), 45 | await(gun_pool:post(Path, [{<<"host">>, Host} | Headers], Body, Opts)); 46 | post(Path, Headers, {form, KV}) -> 47 | post(Path, Headers, cow_qs:qs(maps:to_list(KV))); 48 | post(Path, Headers, {json, Struct}) -> 49 | post(Path, Headers, json_encode(Struct)); 50 | post(Path, Headers0, {multipart, Multipart}) -> 51 | Boundary = cow_multipart:boundary(), 52 | {value, {_, <<"multipart/form-data">>}, Headers1} = 53 | lists:keytake(<<"content-type">>, 1, Headers0), 54 | Headers = [{<<"content-type">>, 55 | [<<"multipart/form-data;boundary=">>, Boundary]} 56 | | Headers1], 57 | {Opts, Host} = http_req_opts(), 58 | {async, Ref} = gun_pool:post(Path, [{<<"host">>, Host} | Headers], Opts), 59 | multipart_stream(Ref, Boundary, Multipart), 60 | await(Ref). 61 | 62 | http_req_opts() -> 63 | {ok, PoolOpts} = pe4kin:get_env(keepalive_pool), 64 | Opts = #{reply_to => self(), 65 | scope => ?MODULE, 66 | checkout_retry => maps:get(checkout_retry, PoolOpts, [])}, 67 | {ok, Endpoint} = pe4kin:get_env(api_server_endpoint), 68 | {Transport, Host, Port} = parse_endpoint(Endpoint), 69 | {Opts, gun_http:host_header(Transport, Host, Port)}. 70 | 71 | await({async, Ref}) -> 72 | await(Ref); 73 | await(Ref) -> 74 | case gun_pool:await(Ref, 30_000) of 75 | {response, fin, Status, Headers} -> 76 | {Status, Headers, []}; 77 | {response, nofin, Status, Headers} -> 78 | {ok, Body} = gun_pool:await_body(Ref), 79 | {Status, Headers, Body}; 80 | {error, _} = Err -> 81 | Err 82 | end. 83 | 84 | multipart_stream(Ref, Boundary, Multipart) -> 85 | ok = lists:foreach( 86 | fun({file, Path, Disposition, Hdrs0}) -> 87 | {ok, Bin} = file:read_file(Path), 88 | Hdrs = [{<<"content-disposition">>, encode_disposition(Disposition)} 89 | | Hdrs0], 90 | Chunk = cow_multipart:part(Boundary, Hdrs), 91 | ok = gun_pool:data(Ref, nofin, [Chunk, Bin]); 92 | ({_Name, Payload, Disposition, Hdrs0}) -> 93 | Hdrs = [{<<"content-disposition">>, encode_disposition(Disposition)} 94 | | Hdrs0], 95 | Chunk = cow_multipart:part(Boundary, Hdrs), 96 | ok = gun_pool:data(Ref, nofin, [Chunk, Payload]); 97 | ({Name, Value}) -> 98 | Hdrs = [{<<"content-disposition">>, 99 | encode_disposition({<<"form-data">>, 100 | [{<<"name">>, Name}]})}], 101 | Chunk = cow_multipart:part(Boundary, Hdrs), 102 | ok = gun_pool:data(Ref, nofin, [Chunk, Value]) 103 | end, Multipart), 104 | Closing = cow_multipart:close(Boundary), 105 | ok = gun_pool:data(Ref, fin, Closing). 106 | 107 | encode_disposition({Disposition, Params}) -> 108 | [Disposition 109 | | [[";", K, "=\"", V, "\""] || {K, V} <- Params]]. 110 | 111 | %% Pool 112 | 113 | start_pool() -> 114 | {ok, Opts} = pe4kin:get_env(keepalive_pool), 115 | {ok, Endpoint} = pe4kin:get_env(api_server_endpoint), 116 | {Transport, Host, Port} = parse_endpoint(Endpoint), 117 | ConnOpts0 = case Transport of 118 | tls -> 119 | case pe4kin:get_env(http_tls_opts, []) of 120 | [] -> #{}; 121 | TlsOpts -> #{tls_opts => TlsOpts} 122 | end; 123 | tcp -> 124 | case pe4kin:get_env(http_tcp_opts, []) of 125 | [] -> #{}; 126 | TcpOpts -> #{tcp_opts => TcpOpts} 127 | end 128 | end, 129 | {ok, ManagerPid} = gun_pool:start_pool(Host, Port, #{ 130 | conn_opts => ConnOpts0#{protocols => [http2], 131 | transport => Transport}, 132 | scope => ?MODULE, 133 | size => maps:get(max_count, Opts, 10) 134 | }), 135 | gun_pool:await_up(ManagerPid). 136 | 137 | stop_pool() -> 138 | {ok, Endpoint} = pe4kin:get_env(api_server_endpoint), 139 | {Transport, Host, Port} = parse_endpoint(Endpoint), 140 | ok = gun_pool:stop_pool(Host, Port, #{transport => Transport, 141 | scope => ?MODULE}). 142 | 143 | parse_endpoint(Uri) -> 144 | Parts = case Uri of 145 | <<"https://", Rest/binary>> -> 146 | [tls | binary:split(Rest, <<":">>)]; 147 | <<"http://", Rest/binary>> -> 148 | [tcp | binary:split(Rest, <<":">>)] 149 | end, 150 | case Parts of 151 | [tls, Host] -> 152 | {tls, binary_to_list(Host), 443}; 153 | [tcp, Host] -> 154 | {tcp, binary_to_list(Host), 80}; 155 | [Transport, Host, Port] -> 156 | {Transport, binary_to_list(Host), binary_to_integer(Port)} 157 | end. 158 | 159 | -dialyzer({nowarn_function, json_decode/1}). 160 | -spec json_decode(binary()) -> pe4kin:json_value(). 161 | json_decode(Body) -> 162 | jsx:decode(Body, [return_maps]). 163 | 164 | -dialyzer({nowarn_function, json_encode/1}). 165 | -spec json_encode(pe4kin:json_value()) -> binary(). 166 | json_encode(Struct) -> 167 | jsx:encode(Struct). 168 | -------------------------------------------------------------------------------- /src/pe4kin_types.erl: -------------------------------------------------------------------------------- 1 | %%% @author Sergey Prokhorov 2 | %%% @copyright (C) 2016, Sergey Prokhorov 3 | %%% @doc 4 | %%% Helpers to work with telegram data types. 5 | %%% @end 6 | %%% Created : 24 May 2016 by Sergey Prokhorov 7 | 8 | -module(pe4kin_types). 9 | 10 | -export([update_type/1, message_type/1, message_command/2, 11 | chat_id/2, object/2, user/2]). 12 | 13 | -export([command_get_args/3]). 14 | 15 | -type update_type() :: message 16 | | edited_message 17 | | channel_post 18 | | edited_channel_post 19 | | inline_query 20 | | chosen_inline_result 21 | | callback_query 22 | | shipping_query 23 | | pre_checkout_query. 24 | 25 | %% @doc Detect incoming update type 26 | -spec update_type(pe4kin:json_object()) -> update_type() | undefined. 27 | update_type(#{<<"message">> := _}) -> message; 28 | update_type(#{<<"edited_message">> := _}) -> edited_message; 29 | update_type(#{<<"channel_post">> := _}) -> channel_post; 30 | update_type(#{<<"edited_channel_post">> := _}) -> edited_channel_post; 31 | update_type(#{<<"inline_query">> := _}) -> inline_query; 32 | update_type(#{<<"chosen_inline_result">> := _}) -> chosen_inline_result; 33 | update_type(#{<<"callback_query">> := _}) -> callback_query; 34 | update_type(#{<<"shipping_query">> := _}) -> shipping_query; 35 | update_type(#{<<"pre_checkout_query">> := _}) -> pre_checkout_query; 36 | update_type(#{}) -> undefined. 37 | 38 | -spec chat_id(update_type(), pe4kin:update()) -> {ok, pe4kin:chat_id()} | undefined. 39 | chat_id(UpdType, Update) -> 40 | case lists:member(UpdType, [message, edited_message, channel_post, 41 | edited_channel_post]) of 42 | true -> 43 | {ok, #{<<"chat">> := #{<<"id">> := ChatId}}} = object(UpdType, Update), 44 | {ok, ChatId}; 45 | false when UpdType == callback_query -> 46 | #{<<"callback_query">> := 47 | #{<<"message">> := 48 | #{<<"chat">> := 49 | #{<<"id">> := ChatId}}}} = Update, 50 | {ok, ChatId}; 51 | _ -> undefined 52 | end. 53 | 54 | -spec object(update_type(), pe4kin:update()) -> {ok, pe4kin:json_object()} | error. 55 | object(undefined, _) -> error; 56 | object(Type, Update) -> 57 | Key = atom_to_binary(Type, utf8), 58 | maps:find(Key, Update). 59 | 60 | %% @doc gets `User' structure from an update 61 | -spec user(update_type(), pe4kin:update()) -> {ok, pe4kin:json_object()} | error. 62 | user(Type, Update) -> 63 | {ok, Object} = object(Type, Update), 64 | maps:find(<<"from">>, Object). 65 | 66 | 67 | %% @doc Returns only 1st command 68 | -spec message_command(pe4kin:bot_name(), pe4kin:json_object()) -> 69 | {Cmd :: binary(), 70 | BotName :: binary(), 71 | SentToMe :: boolean(), 72 | Command :: pe4kin:json_object()}. 73 | message_command(BotName, #{<<"text">> := Text, 74 | <<"entities">> := Entities}) -> 75 | #{<<"offset">> := Offset, 76 | <<"length">> := Length} = Command = hd(entities_filter_type(<<"bot_command">>, Entities)), 77 | BinBotName = pe4kin_util:to_binary(BotName), 78 | Cmd = pe4kin_util:to_lower(pe4kin_util:slice(Text, Offset, Length)), 79 | case binary:split(Cmd, <<"@">>) of 80 | [Cmd1, BinBotName] -> {Cmd1, BinBotName, true, Command}; % /cmd@this_bot 81 | [Cmd1, OtherBotName] -> {Cmd1, OtherBotName, false, Command}; % /cmd@other_bot 82 | [Cmd] -> {Cmd, BinBotName, true, Command} % /cmd 83 | end. 84 | 85 | %% @doc Detect message type 86 | message_type(#{<<"message_id">> := _, <<"text">> := _}) -> text; 87 | message_type(#{<<"message_id">> := _, <<"audio">> := _}) -> audio; 88 | message_type(#{<<"message_id">> := _, <<"document">> := _}) -> document; 89 | message_type(#{<<"message_id">> := _, <<"game">> := _}) -> game; 90 | message_type(#{<<"message_id">> := _, <<"photo">> := _}) -> photo; 91 | message_type(#{<<"message_id">> := _, <<"sticker">> := _}) -> sticker; 92 | message_type(#{<<"message_id">> := _, <<"video">> := _}) -> video; 93 | message_type(#{<<"message_id">> := _, <<"voice">> := _}) -> voice; 94 | message_type(#{<<"message_id">> := _, <<"video_note">> := _}) -> video_note; 95 | message_type(#{<<"message_id">> := _, <<"caption">> := _}) -> caption; 96 | message_type(#{<<"message_id">> := _, <<"contact">> := _}) -> contact; 97 | message_type(#{<<"message_id">> := _, <<"location">> := _}) -> location; 98 | message_type(#{<<"message_id">> := _, <<"venue">> := _}) -> venue; 99 | message_type(#{<<"message_id">> := _, <<"new_chat_member">> := _}) -> new_chat_member; 100 | message_type(#{<<"message_id">> := _, <<"left_chat_member">> := _}) -> left_chat_member; 101 | message_type(#{<<"message_id">> := _, <<"new_chat_title">> := _}) -> new_chat_title; 102 | message_type(#{<<"message_id">> := _, <<"new_chat_photo">> := _}) -> new_chat_photo; 103 | message_type(#{<<"message_id">> := _, <<"delete_chat_photo">> := _}) -> delete_chat_photo; 104 | message_type(#{<<"message_id">> := _, <<"group_chat_created">> := _}) -> group_chat_created; 105 | message_type(#{<<"message_id">> := _, <<"supergroup_chat_created">> := _}) -> supergroup_chat_created; 106 | message_type(#{<<"message_id">> := _, <<"channel_chat_created">> := _}) -> channel_chat_created; 107 | message_type(#{<<"message_id">> := _, <<"migrate_to_chat_id">> := _}) -> migrate_to_chat_id; 108 | message_type(#{<<"message_id">> := _, <<"migrate_from_chat_id">> := _}) -> migrate_from_chat_id; 109 | message_type(#{<<"message_id">> := _, <<"pinned_message">> := _}) -> pinned_message; 110 | message_type(#{<<"message_id">> := _, <<"invoice">> := _}) -> invoice; 111 | message_type(#{<<"message_id">> := _, <<"successfull_payment">> := _}) -> successfull_payment; 112 | message_type(#{<<"message_id">> := _, <<"connected_website">> := _}) -> connected_website; 113 | message_type(#{<<"message_id">> := _}) -> undefined. 114 | 115 | 116 | entities_filter_type(Type, Entities) -> 117 | lists:filter(fun(#{<<"type">> := T}) -> T == Type end, Entities). 118 | 119 | 120 | %% @doc Parses not more than `NArgs' command arguments. 121 | %% If `NArgs' is '*', parses all arguments (until meet the end of the message or new line). 122 | -spec command_get_args(non_neg_integer() | '*', Command :: pe4kin:json_object(), Message :: pe4kin:json_object()) -> 123 | [binary()]. 124 | command_get_args(NArgs, #{<<"offset">> := CmdOffset, <<"length">> := CmdLength}, #{<<"text">> := Text}) -> 125 | ArgsUtfOffset = CmdOffset + CmdLength + 1, 126 | ArgsByteOffset = pe4kin_util:slice_pos(Text, ArgsUtfOffset), 127 | read_args(NArgs, Text, ArgsByteOffset). 128 | 129 | read_args('*', Text, O) -> 130 | Text1 = binary:part(Text, O, size(Text) - O), 131 | CmdLine = case binary:split(Text1, <<$\n>>) of 132 | [CmdLine1, _] -> CmdLine1; 133 | [CmdLine1] -> CmdLine1 134 | end, 135 | binary:split(CmdLine, [<<$\s>>, <<$\t>>], [global, trim_all]); 136 | read_args(N, Text, O) -> 137 | Text1 = hd(binary:split(binary:part(Text, O, size(Text) - O), <<$\n>>)), 138 | read_args1(N, Text1). 139 | 140 | read_args1(0, _) -> []; 141 | read_args1(N, Text) -> 142 | case binary:split(pe4kin_util:strip(Text, left), [<<$\s>>, <<$\t>>], [trim]) of 143 | [Arg, Rest] -> 144 | [Arg | read_args1(N - 1, Rest)]; 145 | [] -> []; 146 | [Arg] -> [Arg] 147 | end. 148 | 149 | 150 | 151 | -ifdef(TEST). 152 | 153 | -include_lib("eunit/include/eunit.hrl"). 154 | 155 | message_command_test() -> 156 | CommandObj = #{<<"type">> => <<"bot_command">>, 157 | <<"offset">> => 10, 158 | <<"length">> => 7}, 159 | Message = #{<<"text">> => <<"asd \n fgh /CmD@ME qwe rty \t uio \n jkl">>, 160 | <<"entities">> => [#{<<"type">> => <<"mention">>}, CommandObj]}, 161 | ?assertEqual({<<"/cmd">>, <<"me">>, true, CommandObj}, 162 | message_command(<<"me">>, Message)), 163 | ?assertEqual({<<"/cmd">>, <<"me">>, false, CommandObj}, 164 | message_command(<<"not_me">>, Message)). 165 | 166 | command_get_args_test() -> 167 | CommandObj = #{<<"type">> => <<"bot_command">>, 168 | <<"offset">> => 10, 169 | <<"length">> => 7}, 170 | Message = #{<<"text">> => <<"asd \n fgh /CmD@ME qwe rty \t uio \n jkl">>, 171 | <<"entities">> => [#{<<"type">> => <<"mention">>}, CommandObj]}, 172 | ?assertEqual([<<"qwe">>, <<"rty">>, <<"uio">>], command_get_args('*', CommandObj, Message)), 173 | ?assertEqual([<<"qwe">>, <<"rty">>, <<"uio">>], command_get_args(3, CommandObj, Message)), 174 | ?assertEqual([<<"qwe">>, <<"rty">>, <<"uio">>], command_get_args(10, CommandObj, Message)), 175 | ?assertEqual([<<"qwe">>], command_get_args(1, CommandObj, Message)). 176 | 177 | -endif. 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/pe4kin.erl: -------------------------------------------------------------------------------- 1 | %%% @author Sergey Prokhorov 2 | %%% @copyright (C) 2016, Sergey Prokhorov 3 | %%% @doc 4 | %%% Main API module. 5 | %%% @end 6 | %%% Created : 18 May 2016 by Sergey Prokhorov 7 | 8 | -module(pe4kin). 9 | -export([api_call/2, api_call/3, download_file/2, send_big_text/2]). 10 | -export([launch_bot/3]). 11 | -export([get_me/1, send_message/2, forward_message/2, send_photo/2, send_audio/2, 12 | send_document/2, send_sticker/2, send_video/2, send_voice/2, send_location/2, 13 | send_venue/2, send_contact/2, send_chat_action/2, get_user_profile_photos/2, 14 | get_file/2, kick_chat_member/2, unban_chat_member/2, answer_callback_query/2, 15 | get_updates_sync/2]). 16 | -export([get_env/1, get_env/2]). 17 | 18 | -export_type([bot_name/0, chat_id/0, update/0]). 19 | -export_type([json_object/0, json_value/0]). 20 | 21 | -type bot_name() :: binary(). % bot name without leading "@" 22 | -type chat_id() :: integer(). % =< 52 bit 23 | -type update() :: json_object(). 24 | -type input_file() :: {file, Name :: binary(), ContentType :: binary(), Payload :: iodata()} 25 | | {file_path, file:name()} 26 | | integer(). 27 | 28 | 29 | -type json_literal() :: null 30 | | true 31 | | false 32 | | json_string() 33 | | json_number(). 34 | -type json_value() :: json_literal() 35 | | json_object() 36 | | json_array(). 37 | 38 | -type json_array() :: [json_value()]. 39 | -type json_string() :: atom() | binary(). 40 | -type json_number() :: integer() | float(). 41 | 42 | -type json_object() :: #{json_string() => json_value()}. 43 | 44 | -type api_body() :: undefined 45 | | #{json_string() => json_literal() | input_file()} 46 | | [{json_string(), json_literal() | input_file()}]. 47 | 48 | -spec get_token(bot_name()) -> binary(). 49 | get_token(Bot) -> 50 | Tokens = get_env(tokens, #{}), 51 | maps:get(Bot, Tokens). 52 | 53 | launch_bot(Bot, Token, Opts) -> 54 | Tokens = get_env(tokens, #{}), 55 | set_env(tokens, Tokens#{Bot => Token}), 56 | case Opts of 57 | #{receiver := true} -> 58 | %% pe4kin_receiver_sup:start_receiver( 59 | pe4kin_receiver:start_link( 60 | Bot, 61 | Token, 62 | maps:remove(receiver, Opts)); 63 | _ -> ok 64 | end. 65 | 66 | %% Api methods 67 | 68 | get_me(Bot) -> 69 | api_call(Bot, <<"getMe">>). 70 | 71 | send_message(Bot, #{chat_id := _, text := _} = Message) -> 72 | api_call(Bot, <<"sendMessage">>, {json, Message}). 73 | 74 | forward_message(Bot, #{chat_id := _, from_chat_id := _, message_id := _} = Req) -> 75 | api_call(Bot, <<"forwardMessage">>, {json, Req}). 76 | 77 | send_photo(Bot, #{chat_id := _, photo := _} = Req) -> 78 | api_call(Bot, <<"sendPhoto">>, body_with_file([photo], Req)). 79 | 80 | send_audio(Bot, #{chat_id := _, audio := _} = Req) -> 81 | api_call(Bot, <<"sendAudio">>, body_with_file([audio], Req)). 82 | 83 | send_document(Bot, #{chat_id := _, document := _} = Req) -> 84 | api_call(Bot, <<"sendDocument">>, body_with_file([document], Req)). 85 | 86 | send_sticker(Bot, #{chat_id := _, sticker := _} = Req) -> 87 | api_call(Bot, <<"sendSticker">>, body_with_file([sticker], Req)). 88 | 89 | send_video(Bot, #{chat_id := _, video := _} = Req) -> 90 | api_call(Bot, <<"sendVideo">>, body_with_file([video], Req)). 91 | 92 | send_voice(Bot, #{chat_id := _, voice := _} = Req) -> 93 | api_call(Bot, <<"sendVoice">>, body_with_file([voice], Req)). 94 | 95 | send_location(Bot, #{chat_id := _, latitude := _, longitude := _} = Req) -> 96 | api_call(Bot, <<"sendLocation">>, {json, Req}). 97 | 98 | send_venue(Bot, #{chat_id := _, latitude := _, longitude := _, title := _, address := _} = Req) -> 99 | api_call(Bot, <<"sendVenue">>, {json, Req}). 100 | 101 | send_contact(Bot, #{chat_id := _, phone_number := _, first_name := _} = Req) -> 102 | api_call(Bot, <<"sendContact">>, {json, Req}). 103 | 104 | send_chat_action(Bot, #{chat_id := _, action := _} = Req) -> 105 | api_call(Bot, <<"sendChatAction">>, {json, Req}). 106 | 107 | get_user_profile_photos(Bot, #{user_id := _} = Req) -> 108 | api_call(Bot, <<"getUserProfilePhotos">>, {json, Req}). 109 | 110 | get_file(Bot, #{file_id := _} = Req) -> 111 | api_call(Bot, <<"getFile">>, {json, Req}). 112 | 113 | kick_chat_member(Bot, #{chat_id := _, user_id := _} = Req) -> 114 | api_call(Bot, <<"kickChatMember">>, {json, Req}). 115 | 116 | unban_chat_member(Bot, #{chat_id := _, user_id := _} = Req) -> 117 | api_call(Bot, <<"unbanChatMember">>, {json, Req}). 118 | 119 | answer_callback_query(Bot, #{callback_query_id := _} = Req) -> 120 | api_call(Bot, <<"answerCallbackQuery">>, {json, Req}). 121 | 122 | 123 | %% @doc Sends text message and if it's too big for a single message, splits it to many. 124 | %% It's not a good idea to add extra fields like online keyboards, because they will be 125 | %% attached to every message 126 | send_big_text(Bot, #{chat_id := _, text := Text} = Message) -> 127 | case pe4kin_util:strlen(Text) of 128 | L when L > 4095 -> 129 | {split, send_text_by_parts(Bot, Text, Message)}; 130 | _ -> 131 | send_message(Bot, Message) 132 | end. 133 | 134 | send_text_by_parts(Bot, Utf8Str, Extra) -> 135 | try pe4kin_util:slice_pos(Utf8Str, 4095) of 136 | SliceBSize -> 137 | <> = Utf8Str, 138 | Res = send_message(Bot, Extra#{text => Slice}), 139 | [Res | send_text_by_parts(Bot, Rest, Extra)] 140 | catch error:_ -> 141 | %% too short 142 | [send_message(Bot, Extra#{text => Utf8Str})] 143 | end. 144 | 145 | 146 | %% @doc 147 | %% This API is for testing purposes 148 | get_updates_sync(Bot, Opts) -> 149 | Opts1 = Opts#{timeout => 0}, 150 | QS = cow_qs:qs([{atom_to_binary(Key, utf8), integer_to_binary(Val)} 151 | || {Key, Val} <- maps:to_list(Opts1)]), 152 | api_call(Bot, <<"getUpdates?", QS/binary>>). 153 | 154 | %% Generic API methods 155 | 156 | -spec download_file(bot_name(), json_object()) -> {ok, Headers :: [{binary(), binary()}], Body :: binary()}. 157 | download_file(Bot, #{<<"file_id">> := _, 158 | <<"file_path">> := FilePath}) -> 159 | Endpoint = get_env(api_server_endpoint, <<"https://api.telegram.org">>), 160 | Token = get_token(Bot), 161 | Url = <>, 162 | {200, Headers, Body} = do_api_call(Url, undefined), 163 | {ok, Headers, Body}. 164 | 165 | 166 | -spec api_call(bot_name(), binary()) -> {ok, json_value()} | {error, Type :: atom(), term()}. 167 | api_call(Bot, Method) -> 168 | api_call(Bot, Method, undefined). 169 | 170 | -spec api_call(bot_name(), binary(), pe4kin_http:req_body() | undefined) -> 171 | {ok, json_value()} | {error, Type :: atom(), term()}. 172 | api_call(Bot, Method, Payload) -> 173 | Endpoint = get_env(api_server_endpoint, <<"https://api.telegram.org">>), 174 | Token = get_token(Bot), 175 | api_call({Endpoint, Token}, Bot, Method, Payload). 176 | 177 | api_call({_ApiServerEndpoint, Token}, _Bot, Method, Payload) -> 178 | Url = <<"/bot", Token/binary, "/", Method/binary>>, 179 | case do_api_call(Url, Payload) of 180 | {Code, Hdrs, Body} -> 181 | ContentType = cow_http_hd:parse_content_type( 182 | proplists:get_value(<<"content-type">>, Hdrs)), 183 | case {Body, ContentType, Code} of 184 | {<<>>, _, 200} -> ok; 185 | {Body, {<<"application">>, <<"json">>, _}, _} -> 186 | case pe4kin_http:json_decode(Body) of 187 | #{<<"ok">> := true, <<"result">> := Result} when Code == 200 -> 188 | {ok, Result}; 189 | #{<<"ok">> := false, <<"description">> := ErrDescription, 190 | <<"error_code">> := ErrCode} when Code =/= 200 -> 191 | {error, telegram, {ErrCode, Code, ErrDescription}} 192 | end 193 | end; 194 | {error, ErrReason} -> {error, http, ErrReason} 195 | end. 196 | 197 | 198 | do_api_call(Url, undefined) -> 199 | pe4kin_http:get(Url); 200 | do_api_call(Url, {json, _} = Json) -> 201 | Headers = [{<<"content-type">>, <<"application/json">>}, 202 | {<<"accept">>, <<"application/json">>}], 203 | pe4kin_http:post(Url, Headers, Json); 204 | do_api_call(Url, {form, Payload}) when is_map(Payload) -> 205 | Headers = [{<<"content-type">>, <<"application/x-www-form-urlencoded; encoding=utf-8">>}, 206 | {<<"accept">>, <<"application/json">>}], 207 | pe4kin_http:post(Url, Headers, {form, Payload}); 208 | do_api_call(Url, {multipart, Payload}) -> 209 | Headers = [{<<"content-type">>, <<"multipart/form-data">>}, 210 | {<<"accept">>, <<"application/json">>}], 211 | pe4kin_http:post(Url, Headers, {multipart, Payload}). 212 | 213 | body_with_file(FileFields, Payload) -> 214 | body_with_file_(FileFields, json, Payload). 215 | 216 | -spec body_with_file_([atom()], json | multipart | form, api_body()) -> pe4kin_http:req_body(). 217 | body_with_file_([Key | Keys] = AllKeys, json, Map) -> 218 | case maps:get(Key, Map) of 219 | Bin when is_binary(Bin) -> body_with_file_(Keys, json, Map); 220 | File when (element(1, File) == file) orelse (element(1, File) == file_path) -> 221 | body_with_file_(AllKeys, multipart, maps:to_list(Map)) 222 | end; 223 | body_with_file_([Key | Keys], multipart, List) -> 224 | case lists:keyfind(Key, 1, List) of 225 | {Key, Bin} when is_binary(Bin) -> 226 | %% file ID 227 | body_with_file_(Keys, multipart, List); 228 | {Key, File} when is_tuple(File) -> 229 | %% `file' or `file_name' tuple 230 | body_with_file_(Keys, multipart, lists:keyreplace(Key, 1, List, file2multipart(Key, File))) 231 | end; 232 | body_with_file_([], multipart, Load) -> 233 | ToBin = fun(V) when is_integer(V) -> integer_to_binary(V); 234 | (V) when is_atom(V) -> atom_to_binary(V, utf8); 235 | (V) -> V 236 | end, 237 | BinLoad = lists:map(fun({K, V}) -> 238 | {ToBin(K), ToBin(V)}; 239 | (File) -> File 240 | end, Load), 241 | {multipart, BinLoad}; 242 | body_with_file_([], Enctype, Load) when Enctype =:= json; 243 | Enctype =:= form -> 244 | {Enctype, Load}. 245 | 246 | file2multipart(Key, {file, FileName, ContentType, Payload}) -> 247 | {atom_to_binary(Key, utf8), 248 | Payload, 249 | {<<"form-data">>, [{<<"name">>, atom_to_binary(Key, utf8)}, 250 | {<<"filename">>, FileName}]}, 251 | [{<<"Content-Type">>, ContentType}]}; 252 | file2multipart(Key, {file_path, Path}) -> 253 | {file, 254 | Path, 255 | {<<"form-data">>, [{<<"name">>, atom_to_binary(Key, utf8)}, 256 | {<<"filename">>, filename:basename(Path)}]}, 257 | []}. 258 | 259 | -spec get_env(any(), any()) -> any(). 260 | get_env(Key, Default) -> 261 | application:get_env(?MODULE, Key, Default). 262 | 263 | -spec get_env(any()) -> {ok, any()}. 264 | get_env(Key) -> 265 | application:get_env(?MODULE, Key). 266 | 267 | -spec set_env(any(), any()) -> ok. 268 | set_env(Key, Value) -> 269 | application:set_env(?MODULE, Key, Value). 270 | -------------------------------------------------------------------------------- /src/pe4kin_receiver.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Sergey Prokhorov 3 | %%% @copyright (C) 2016, Sergey Prokhorov 4 | %%% @doc 5 | %%% Telegram bot update pooler. 6 | %%% Receive incoming messages (updates) via webhook or http longpolling. 7 | %%% @end 8 | %%% Created : 18 May 2016 by Sergey Prokhorov 9 | %%%------------------------------------------------------------------- 10 | -module(pe4kin_receiver). 11 | 12 | -behaviour(gen_server). 13 | 14 | %% API 15 | -export([start_link/3]). 16 | -export([start_http_poll/2, stop_http_poll/1, 17 | start_set_webhook/3, stop_set_webhook/1]). 18 | -export([webhook_callback/3]). 19 | -export([subscribe/2, unsubscribe/2, get_updates/2]). 20 | 21 | %% gen_server callbacks 22 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 23 | terminate/2, code_change/3]). 24 | 25 | -include_lib("kernel/include/logger.hrl"). 26 | 27 | -type longpoll_state() :: #{pid => pid(), 28 | ref => reference(), 29 | state => start | status | headers | body | undefined, 30 | status => pos_integer() | undefined, 31 | headers => [{binary(), binary()}] | undefined, 32 | body => iodata() | undefined}. 33 | -type longpoll_opts() :: #{limit => 1..100, 34 | timeout => non_neg_integer()}. 35 | 36 | -record(state, 37 | { 38 | name :: pe4kin:bot_name(), 39 | token :: binary(), 40 | buffer_edge_size :: non_neg_integer(), 41 | method :: webhook | longpoll | undefined, 42 | method_opts :: longpoll_opts() | undefined, 43 | method_state :: longpoll_state() | undefined, 44 | active :: boolean(), 45 | last_update_id :: integer() | undefined, 46 | subscribers :: #{pid() => reference()}, 47 | monitors :: #{reference() => pid()}, 48 | ulen :: non_neg_integer(), 49 | updates :: queue:queue() 50 | }). 51 | 52 | 53 | -spec start_http_poll(pe4kin:bot_name(), 54 | #{offset => integer(), 55 | limit => 1..100, 56 | timeout => non_neg_integer()}) -> ok. 57 | start_http_poll(Bot, Opts) -> 58 | gen_server:call(?MODULE, {start_http_poll, Bot, Opts}). 59 | 60 | -spec stop_http_poll(pe4kin:bot_name()) -> ok. 61 | stop_http_poll(Bot) -> 62 | gen_server:call(?MODULE, {stop_http_poll, Bot}). 63 | 64 | -spec start_set_webhook(pe4kin:bot_name(), 65 | binary(), 66 | #{certfile_id => integer()}) -> ok. 67 | start_set_webhook(Bot, UrlPrefix, Opts) -> 68 | gen_server:call(?MODULE, {start_set_webhook, Bot, UrlPrefix, Opts}). 69 | 70 | -spec stop_set_webhook(pe4kin:bot_name()) -> ok. 71 | stop_set_webhook(Bot) -> 72 | gen_server:call(?MODULE, {stop_set_webhook, Bot}). 73 | 74 | -spec webhook_callback(binary(), #{binary() => binary()}, binary()) -> ok. 75 | webhook_callback(Path, Query, Body) -> 76 | gen_server:call(?MODULE, {webhook_callback, Path, Query, Body}). 77 | 78 | 79 | -spec subscribe(pe4kin:bot_name(), pid()) -> ok | {error, process_already_subscribed}. 80 | subscribe(Bot, Pid) -> 81 | gen_server:call(?MODULE, {subscribe, Bot, Pid}). 82 | 83 | -spec unsubscribe(pe4kin:bot_name(), pid()) -> ok | not_found. 84 | unsubscribe(Bot, Pid) -> 85 | gen_server:call(?MODULE, {unsubscribe, Bot, Pid}). 86 | 87 | %% @doc Return not more than 'Limit' updates. May return empty list. 88 | -spec get_updates(pe4kin:bot_name(), pos_integer()) -> [pe4kin:update()]. 89 | get_updates(Bot, Limit) -> 90 | gen_server:call(?MODULE, {get_updates, Bot, Limit}). 91 | 92 | start_link(Bot, Token, Opts) -> 93 | gen_server:start_link({local, ?MODULE}, ?MODULE, [Bot, Token, Opts], []). 94 | 95 | 96 | init([Bot, Token, Opts]) -> 97 | {ok, #state{name = Bot, 98 | token = Token, 99 | active = false, 100 | subscribers = #{}, 101 | monitors = #{}, 102 | ulen = 0, 103 | updates = queue:new(), 104 | buffer_edge_size = maps:get(buffer_edge_size, Opts, 1000)}}. 105 | 106 | handle_call({start_http_poll, _, Opts}, _From, #state{method = undefined, active = false} = State) -> 107 | State1 = do_start_http_poll(Opts, State), 108 | MOpts = maps:remove(offset, Opts), 109 | {reply, ok, State1#state{method_opts = MOpts, method = longpoll}}; 110 | handle_call({stop_http_poll, _}, _From, #state{method = longpoll, active = Active} = State) -> 111 | State1 = case Active of 112 | true -> do_stop_http_poll(State); 113 | false -> State 114 | end, 115 | {reply, ok, State1#state{method = undefined}}; 116 | handle_call(webhook___TODO, _From, State) -> 117 | Reply = ok, 118 | {reply, Reply, State}; 119 | handle_call({subscribe, _, Pid}, _From, #state{subscribers=Subs, monitors = Mons} = State) -> 120 | case maps:is_key(Pid, Subs) of 121 | false -> 122 | Ref = erlang:monitor(process, Pid), 123 | Subs1 = Subs#{Pid => Ref}, 124 | Mons1 = Mons#{Ref => Pid}, 125 | {reply, ok, invariant(State#state{subscribers=Subs1, monitors = Mons1})}; 126 | true -> 127 | {reply, {error, process_already_subscribed}, State} 128 | end; 129 | handle_call({unsubscribe, _, Pid}, _From, #state{subscribers=Subs, monitors = Mons} = State) -> 130 | {Mon, Subs1} = maps:take(Pid, Subs), 131 | erlang:demonitor(Mon, [flush]), 132 | {reply, ok, 133 | State#state{subscribers = Subs1, 134 | monitors = maps:remove(Mon, Mons)}}; 135 | handle_call({get_updates, _, Limit}, _From, #state{buffer_edge_size=BESize, subscribers=Subs} = State) 136 | when map_size(Subs) == 0 -> 137 | (BESize >= Limit) orelse 138 | ?LOG_WARNING("get_updates limit ~p is greater than buffer_edge_size ~p", 139 | [Limit, BESize], 140 | #{domain => [pe4kin]}), 141 | {Reply, State1} = pull_updates(Limit, State), 142 | {reply, Reply, invariant(State1)}; 143 | handle_call(_Request, _From, #state{method=Method, subscribers=Subs, ulen=ULen, active=Active}=State) -> 144 | {reply, {error, bad_request, #{method => Method, 145 | n_subscribers => map_size(Subs), 146 | ulen => ULen, 147 | active => Active}}, State}. 148 | handle_cast(Msg, State) -> 149 | ?LOG_WARNING("Unexpected cast ~p; state ~p", [Msg, State], #{domain => [pe4kin]}), 150 | {noreply, State}. 151 | 152 | handle_info({gun_response, Pid, Ref, IsFin, Status, Headers}, #state{method_state=#{ref := Ref}} = State) -> 153 | WithBody = 154 | case IsFin of 155 | fin -> 156 | {Status, Headers, <<>>}; 157 | nofin -> 158 | {ok, Body} = gun:await_body(Pid, Ref), 159 | {Status, Headers, Body}; 160 | {error, _} = Err -> 161 | Err 162 | end, 163 | State1 = handle_http_poll_msg(WithBody, State), 164 | {noreply, invariant(State1)}; 165 | handle_info({gun_response, Ref, Msg}, #state{method_state=MState, name = Name} = State) -> 166 | ?LOG_WARNING("Unexpected http msg ~p, ~p; state ~p", [Ref, Msg, MState], 167 | #{domain => [pe4kin], bot => Name}), 168 | {noreply, State}; 169 | handle_info({gun_error, Pid, Ref, Reason}, #state{method_state=#{ref := Ref, pid := Pid}} = State) -> 170 | State1 = handle_http_poll_msg({error, Reason}, State), 171 | {noreply, invariant(State1)}; 172 | handle_info({gun_error, Pid, Reason}, #state{method_state=#{pid := Pid}} = State) -> 173 | State1 = handle_http_poll_msg({error, Reason}, State), 174 | {noreply, invariant(State1)}; 175 | handle_info({'DOWN', Ref, process, Pid, _Reason}, #state{subscribers=Subs, monitors = Mons} = State) -> 176 | {noreply, 177 | State#state{subscribers = maps:remove(Pid, Subs), 178 | monitors = maps:remove(Ref, Mons)}}; 179 | handle_info(Info, #state{name = Name} = State) -> 180 | ?LOG_WARNING("Unexpected info msg ~p; state ~p", [Info, State], 181 | #{domain => [pe4kin], 182 | bot => Name}), 183 | {noreply, State}. 184 | 185 | terminate(_Reason, _State) -> 186 | ok. 187 | code_change(_OldVsn, State, _Extra) -> 188 | {ok, State}. 189 | 190 | 191 | %%% 192 | %%% Internal functions 193 | %%% 194 | activate_get_updates(#state{method=webhook, active=false} = State) -> 195 | State#state{active=true}; 196 | activate_get_updates(#state{method=longpoll, active=false, 197 | method_opts=MOpts, last_update_id=LastUpdId} = State) -> 198 | MOpts1 = case LastUpdId of 199 | undefined -> MOpts; 200 | _ -> MOpts#{offset => LastUpdId + 1} 201 | end, 202 | do_start_http_poll(MOpts1, State). 203 | 204 | pause_get_updates(#state{method=longpoll, active=true} = State) -> 205 | do_stop_http_poll(State); 206 | pause_get_updates(#state{method=webhook, active=true} = State) -> 207 | State#state{active=false}. 208 | 209 | 210 | do_start_http_poll(Opts, #state{token=Token, active=false, method_state = #{pid := Pid}} = State) -> 211 | Opts1 = maps:merge(#{timeout => 30}, Opts), 212 | QS = cow_qs:qs([{atom_to_binary(Key, utf8), integer_to_binary(Val)} 213 | || {Key, Val} <- maps:to_list(Opts1)]), 214 | Url = <<"/bot", Token/binary, "/getUpdates?", QS/binary>>, 215 | Ref = gun:get(Pid, Url), 216 | ?LOG_DEBUG("Long poll ~s", [Url], #{domain => [pe4kin]}), 217 | State#state{%% method = longpoll, 218 | active = true, 219 | method_state = #{pid => Pid, 220 | ref => Ref, 221 | state => start}}; 222 | do_start_http_poll(Opts, #state{active=false, method_state = undefined} = State) -> 223 | {ok, Pid} = pe4kin_http:open(), 224 | do_start_http_poll(Opts, State#state{method_state = #{pid => Pid}}). 225 | 226 | do_stop_http_poll(#state{active=true, method=longpoll, 227 | method_state=#{ref := Ref, pid := Pid}} = State) -> 228 | ok = gun:cancel(Pid, Ref), 229 | ok = gun:close(Pid), 230 | State#state{active=false, method_state=undefined}. 231 | 232 | 233 | handle_http_poll_msg({200, _Headers, Body}, 234 | #state{method_state = #{pid := Pid}} = State) -> 235 | push_updates(Body, State#state{method_state=#{pid => Pid}, active=false}); 236 | handle_http_poll_msg({Status, _, _}, 237 | #state{method_state = #{pid := Pid} = MState, name = Name} = State) -> 238 | gun:close(Pid), 239 | ?LOG_WARNING("Bot ~p: longpool bad status ~p when state ~p", [Name, Status, MState], 240 | #{domain => [pe4kin], 241 | bot => Name}), 242 | State#state{method_state = undefined, active=false}; 243 | handle_http_poll_msg({error, Reason}, #state{method_state = #{pid := Pid} = MState, name=Name} = State) -> 244 | gun:close(Pid), 245 | ?LOG_WARNING("Bot ~p: http longpoll error ~p when state ~p", [Name, Reason, MState], 246 | #{domain => [pe4kin], 247 | bot => Name}), 248 | State#state{method_state=undefined, active=false}. 249 | 250 | 251 | push_updates(<<>>, State) -> State; 252 | push_updates(UpdatesBin, #state{last_update_id = LastID, updates = UpdatesQ, ulen = ULen} = State) -> 253 | case pe4kin_http:json_decode(UpdatesBin) of 254 | [] -> State; 255 | #{<<"ok">> := true, <<"result">> := []} -> State; 256 | #{<<"ok">> := true, <<"result">> := NewUpdates} -> 257 | #{<<"update_id">> := NewLastID} = lists:last(NewUpdates), 258 | ((LastID == undefined) or (NewLastID > LastID)) 259 | orelse error({assertion_failed, "NewLastID>LastID", NewLastID, LastID}), 260 | NewUpdatesQ = queue:from_list(NewUpdates), 261 | UpdatesQ1 = queue:join(UpdatesQ, NewUpdatesQ), 262 | State#state{last_update_id = NewLastID, updates = UpdatesQ1, 263 | ulen = ULen + length(NewUpdates)} 264 | end. 265 | 266 | pull_updates(_, #state{ulen = 0} = State) -> {[], State}; 267 | pull_updates(1, #state{updates = UpdatesQ, ulen = ULen} = State) -> 268 | {{value, Update}, UpdatesQ1} = queue:out(UpdatesQ), 269 | {[Update], State#state{updates = UpdatesQ1, ulen = ULen - 1}}; 270 | pull_updates(N, #state{updates = UpdatesQ, ulen = ULen} = State) -> 271 | PopN = erlang:min(N, ULen), 272 | {RetQ, UpdatesQ1} = queue:split(PopN, UpdatesQ), 273 | {queue:to_list(RetQ), State#state{updates = UpdatesQ1, ulen = ULen - PopN}}. 274 | 275 | 276 | invariant( 277 | #state{method = Method, 278 | active = false, 279 | ulen = ULen, 280 | buffer_edge_size = BEdge} = State) when (ULen < BEdge) 281 | and (Method =/= undefined)-> 282 | invariant(activate_get_updates(State)); 283 | invariant( 284 | #state{subscribers = Subscribers, 285 | ulen = ULen, 286 | updates = Updates, 287 | name = Name} = State) when ULen > 0, map_size(Subscribers) > 0 -> 288 | [Subscriber ! {pe4kin_update, Name, Update} 289 | || Subscriber <- maps:keys(Subscribers), 290 | Update <- queue:to_list(Updates)], 291 | invariant(State#state{ulen = 0, updates = queue:new()}); 292 | invariant( 293 | #state{method = Method, 294 | active = true, 295 | ulen = ULen, 296 | buffer_edge_size = BEdge} = State) when (ULen > BEdge) 297 | and (Method =/= undefined) -> 298 | invariant(pause_get_updates(State)); 299 | invariant(State) -> State. 300 | -------------------------------------------------------------------------------- /emoji.json: -------------------------------------------------------------------------------- 1 | {"wine_glass": "1f377", "clock830": "1f563", "rabbit": "1f430", "european_post_office": "1f3e4", "dollar": "1f4b5", "four": "0034", "crocodile": "1f40a", "smoking": "1f6ac", "white_square_button": "1f533", "maple_leaf": "1f341", "no_bicycles": "1f6b3", "man_with_gua_pi_mao": "1f472", "bride_with_veil": "1f470", "e-mail": "1f4e7", "tv": "1f4fa", "open_hands": "1f450", "rotating_light": "1f6a8", "part_alternation_mark": "303d", "tm": "2122", "mountain_cableway": "1f6a0", "melon": "1f348", "smile": "1f604", "large_blue_circle": "1f535", "persevere": "1f623", "sound": "1f509", "fax": "1f4e0", "woman": "1f469", "raising_hand": "1f64b", "eight_pointed_black_star": "2734", "department_store": "1f3ec", "trident": "1f531", "oncoming_automobile": "1f698", "wave": "1f44b", "u7a7a": "1f233", "arrow_right": "27a1", "ticket": "1f3ab", "ramen": "1f35c", "twisted_rightwards_arrows": "1f500", "cool": "1f192", "alien": "1f47d", "school": "1f3eb", "high_brightness": "1f506", "railway_car": "1f683", "notes": "1f3b6", "white_flower": "1f4ae", "gun": "1f52b", "video_game": "1f3ae", "-1": "1f44e", "notebook_with_decorative_cover": "1f4d4", "triumph": "1f624", "tea": "1f375", "scorpius": "264f", "suspension_railway": "1f69f", "arrow_left": "2b05", "zero": "0030", "small_orange_diamond": "1f538", "recycle": "267b", "hankey": "1f4a9", "cactus": "1f335", "spaghetti": "1f35d", "white_small_square": "25ab", "ribbon": "1f380", "closed_umbrella": "1f302", "abc": "1f524", "hocho": "1f52a", "purple_heart": "1f49c", "love_letter": "1f48c", "file_folder": "1f4c1", "clipboard": "1f4cb", "baby_bottle": "1f37c", "new": "1f195", "bird": "1f426", "1234": "1f522", "smiling_imp": "1f608", "relaxed": "263a", "hash": "0023", "card_index": "1f4c7", "pouting_cat": "1f63e", "vertical_traffic_light": "1f6a6", "surfer": "1f3c4", "house_with_garden": "1f3e1", "baseball": "26be", "busstop": "1f68f", "new_moon": "1f311", "100": "1f4af", "bangbang": "203c", "boy": "1f466", "raised_hands": "1f64c", "no_entry": "26d4", "see_no_evil": "1f648", "metro": "1f687", "leaves": "1f343", "heavy_plus_sign": "2795", "roller_coaster": "1f3a2", "thought_balloon": "1f4ad", "anchor": "2693", "dragon_face": "1f432", "light_rail": "1f688", "bullettrain_side": "1f684", "massage": "1f486", "children_crossing": "1f6b8", "apple": "1f34e", "family": "1f46a", "arrow_up_down": "2195", "mount_fuji": "1f5fb", "keycap_ten": "1f51f", "clock230": "1f55d", "tired_face": "1f62b", "dango": "1f361", "honey_pot": "1f36f", "baby": "1f476", "sake": "1f376", "confounded": "1f616", "hospital": "1f3e5", "poodle": "1f429", "red_circle": "1f534", "princess": "1f478", "camera": "1f4f7", "iphone": "1f4f1", "sweat_smile": "1f605", "aries": "2648", "ear_of_rice": "1f33e", "video_camera": "1f4f9", "mouse2": "1f401", "chestnut": "1f330", "heavy_dollar_sign": "1f4b2", "door": "1f6aa", "steam_locomotive": "1f682", "tangerine": "1f34a", "blue_heart": "1f499", "phone": "260e", "train": "1f68b", "beers": "1f37b", "black_small_square": "25aa", "water_buffalo": "1f403", "first_quarter_moon_with_face": "1f31b", "mailbox_closed": "1f4ea", "curly_loop": "27b0", "night_with_stars": "1f303", "pouch": "1f45d", "jack_o_lantern": "1f383", "izakaya_lantern": "1f3ee", "palm_tree": "1f334", "car": "1f697", "cat": "1f431", "dizzy": "1f4ab", "nine": "0039", "chocolate_bar": "1f36b", "v": "270c", "running_shirt_with_sash": "1f3bd", "clock330": "1f55e", "arrow_lower_left": "2199", "put_litter_in_its_place": "1f6ae", "game_die": "1f3b2", "u6e80": "1f235", "abcd": "1f521", "heart": "2764", "chart_with_upwards_trend": "1f4c8", "green_heart": "1f49a", "hamburger": "1f354", "pushpin": "1f4cc", "lock": "1f512", "dolphin": "1f42c", "confused": "1f615", "accept": "1f251", "name_badge": "1f4db", "pig2": "1f416", "white_medium_small_square": "25fd", "sunglasses": "1f60e", "airplane": "2708", "dress": "1f457", "bow": "1f647", "kissing_closed_eyes": "1f61a", "earth_americas": "1f30e", "end": "1f51a", "trumpet": "1f3ba", "speedboat": "1f6a4", "six": "0036", "ledger": "1f4d2", "tomato": "1f345", "leftwards_arrow_with_hook": "21a9", "rewind": "23ea", "underage": "1f51e", "earth_asia": "1f30f", "goat": "1f410", "pizza": "1f355", "heavy_check_mark": "2714", "briefcase": "1f4bc", "disappointed_relieved": "1f625", "sunny": "2600", "customs": "1f6c3", "sun_with_face": "1f31e", "notebook": "1f4d3", "fast_forward": "23e9", "heartpulse": "1f497", "mag": "1f50d", "sparkler": "1f387", "cow2": "1f404", "man": "1f468", "a": "1f170", "baby_symbol": "1f6bc", "wheelchair": "267f", "dragon": "1f409", "wink": "1f609", "truck": "1f69a", "wrench": "1f527", "ambulance": "1f691", "sa": "1f202", "point_up_2": "1f446", "egg": "1f373", "small_red_triangle": "1f53a", "office": "1f3e2", "mute": "1f507", "clapper": "1f3ac", "haircut": "1f487", "soon": "1f51c", "symbols": "1f523", "black_square_button": "1f532", "japan": "1f5fe", "post_office": "1f3e3", "last_quarter_moon_with_face": "1f31c", "ok_woman": "1f646", "pray": "1f64f", "flushed": "1f633", "dizzy_face": "1f635", "rugby_football": "1f3c9", "currency_exchange": "1f4b1", "paperclip": "1f4ce", "moneybag": "1f4b0", "mailbox_with_no_mail": "1f4ed", "no_bell": "1f515", "dolls": "1f38e", "eight_spoked_asterisk": "2733", "coffee": "2615", "tiger2": "1f405", "neutral_face": "1f610", "elephant": "1f418", "open_mouth": "1f62e", "bar_chart": "1f4ca", "european_castle": "1f3f0", "page_with_curl": "1f4c3", "snake": "1f40d", "kiss": "1f48b", "blue_car": "1f699", "confetti_ball": "1f38a", "bank": "1f3e6", "bread": "1f35e", "rice_ball": "1f359", "oncoming_police_car": "1f694", "capricorn": "2651", "trolleybus": "1f68e", "tokyo_tower": "1f5fc", "clock930": "1f564", "fishing_pole_and_fish": "1f3a3", "repeat_one": "1f502", "bowling": "1f3b3", "volcano": "1f30b", "older_woman": "1f475", "smiley_cat": "1f63a", "turtle": "1f422", "no_smoking": "1f6ad", "telescope": "1f52d", "beginner": "1f530", "earth_africa": "1f30d", "postal_horn": "1f4ef", "house": "1f3e0", "fish": "1f41f", "construction_worker": "1f477", "u7121": "1f21a", "mobile_phone_off": "1f4f4", "unlock": "1f513", "books": "1f4da", "sweet_potato": "1f360", "beetle": "1f41e", "lock_with_ink_pen": "1f50f", "8ball": "1f3b1", "calling": "1f4f2", "sunrise": "1f305", "exclamation": "2757", "no_good": "1f645", "bridge_at_night": "1f309", "lipstick": "1f484", "hushed": "1f62f", "factory": "1f3ed", "baggage_claim": "1f6c4", "cherry_blossom": "1f338", "sparkle": "2747", "rooster": "1f413", "point_right": "1f449", "speak_no_evil": "1f64a", "cyclone": "1f300", "saxophone": "1f3b7", "blue_book": "1f4d8", "dancers": "1f46f", "flower_playing_cards": "1f3b4", "umbrella": "2614", "octopus": "1f419", "hatching_chick": "1f423", "free": "1f193", "traffic_light": "1f6a5", "grimacing": "1f62c", "cookie": "1f36a", "poultry_leg": "1f357", "grapes": "1f347", "smirk_cat": "1f63c", "hand": "270b", "lollipop": "1f36d", "high_heel": "1f460", "black_medium_small_square": "25fe", "green_book": "1f4d7", "headphones": "1f3a7", "no_mobile_phones": "1f4f5", "fork_and_knife": "1f374", "mailbox": "1f4eb", "passport_control": "1f6c2", "blossom": "1f33c", "+1": "1f44d", "small_blue_diamond": "1f539", "yum": "1f60b", "closed_lock_with_key": "1f510", "heartbeat": "1f493", "blush": "1f60a", "ophiuchus": "26ce", "bouquet": "1f490", "fire_engine": "1f692", "one": "0031", "feet": "1f43e", "date": "1f4c5", "inbox_tray": "1f4e5", "sparkles": "2728", "scissors": "2702", "ring": "1f48d", "slot_machine": "1f3b0", "zap": "26a1", "sheep": "1f411", "horse": "1f434", "basketball": "1f3c0", "atm": "1f3e7", "monkey": "1f412", "information_source": "2139", "bookmark": "1f516", "gift_heart": "1f49d", "top": "1f51d", "clock630": "1f561", "station": "1f689", "clock730": "1f562", "banana": "1f34c", "shaved_ice": "1f367", "eyes": "1f440", "shell": "1f41a", "relieved": "1f60c", "memo": "1f4dd", "hotel": "1f3e8", "small_red_triangle_down": "1f53b", "crossed_flags": "1f38c", "nut_and_bolt": "1f529", "aerial_tramway": "1f6a1", "sweat_drops": "1f4a6", "panda_face": "1f43c", "minibus": "1f690", "b": "1f171", "unamused": "1f612", "evergreen_tree": "1f332", "bee": "1f41d", "shower": "1f6bf", "smile_cat": "1f638", "hourglass_flowing_sand": "23f3", "round_pushpin": "1f4cd", "tophat": "1f3a9", "six_pointed_star": "1f52f", "dog2": "1f415", "tractor": "1f69c", "u6709": "1f236", "u6708": "1f237", "crying_cat_face": "1f63f", "loud_sound": "1f50a", "nail_care": "1f485", "runner": "1f3c3", "ram": "1f40f", "dash": "1f4a8", "rat": "1f400", "information_desk_person": "1f481", "rice_scene": "1f391", "mailbox_with_mail": "1f4ec", "milky_way": "1f30c", "pencil2": "270f", "microphone": "1f3a4", "koala": "1f428", "necktie": "1f454", "tulip": "1f337", "monorail": "1f69d", "kissing_cat": "1f63d", "u55b6": "1f23a", "globe_with_meridians": "1f310", "snowflake": "2744", "hibiscus": "1f33a", "crystal_ball": "1f52e", "koko": "1f201", "chart": "1f4b9", "credit_card": "1f4b3", "checkered_flag": "1f3c1", "eight": "0038", "hearts": "2665", "handbag": "1f45c", "pager": "1f4df", "arrows_clockwise": "1f503", "ballot_box_with_check": "2611", "fried_shrimp": "1f364", "mans_shoe": "1f45e", "m": "24c2", "dog": "1f436", "bookmark_tabs": "1f4d1", "new_moon_with_face": "1f31a", "ideograph_advantage": "1f250", "pineapple": "1f34d", "link": "1f517", "scream": "1f631", "bell": "1f514", "snowman": "26c4", "walking": "1f6b6", "bomb": "1f4a3", "minidisc": "1f4bd", "horse_racing": "1f3c7", "radio": "1f4fb", "point_down": "1f447", "chicken": "1f414", "copyright": "00a9", "arrow_lower_right": "2198", "city_sunset": "1f306", "camel": "1f42b", "waning_crescent_moon": "1f318", "cupid": "1f498", "mens": "1f6b9", "virgo": "264d", "libra": "264e", "busts_in_silhouette": "1f465", "rice": "1f35a", "lips": "1f444", "alarm_clock": "23f0", "couplekiss": "1f48f", "sagittarius": "2650", "arrow_heading_down": "2935", "circus_tent": "1f3aa", "watch": "231a", "arrow_up": "2b06", "bear": "1f43b", "frowning": "1f626", "incoming_envelope": "1f4e8", "watermelon": "1f349", "wedding": "1f492", "yellow_heart": "1f49b", "gem": "1f48e", "herb": "1f33f", "negative_squared_cross_mark": "274e", "cry": "1f622", "worried": "1f61f", "microscope": "1f52c", "x": "274c", "interrobang": "2049", "japanese_ogre": "1f479", "fuelpump": "26fd", "oncoming_taxi": "1f696", "man_with_turban": "1f473", "arrow_up_small": "1f53c", "art": "1f3a8", "cocktail": "1f378", "hear_no_evil": "1f649", "convenience_store": "1f3ea", "seat": "1f4ba", "computer": "1f4bb", "arrow_down": "2b07", "arrow_upper_left": "2196", "parking": "1f17f", "pisces": "2653", "calendar": "1f4c6", "hammer": "1f528", "hourglass": "231b", "loudspeaker": "1f4e2", "scream_cat": "1f640", "black_joker": "1f0cf", "ferris_wheel": "1f3a1", "bicyclist": "1f6b4", "no_mouth": "1f636", "postbox": "1f4ee", "large_blue_diamond": "1f537", "non-potable_water": "1f6b1", "icecream": "1f366", "diamonds": "2666", "email": "2709", "older_man": "1f474", "tent": "26fa", "deciduous_tree": "1f333", "wc": "1f6be", "joy": "1f602", "moyai": "1f5ff", "aquarius": "2652", "couple_with_heart": "1f491", "guitar": "1f3b8", "imp": "1f47f", "key": "1f511", "weary": "1f629", "statue_of_liberty": "1f5fd", "cop": "1f46e", "clock1230": "1f567", "tropical_drink": "1f379", "mega": "1f4e3", "restroom": "1f6bb", "white_large_square": "2b1c", "eggplant": "1f346", "low_brightness": "1f505", "four_leaf_clover": "1f340", "space_invader": "1f47e", "pig_nose": "1f43d", "cancer": "264b", "cl": "1f191", "battery": "1f50b", "jeans": "1f456", "leo": "264c", "cd": "1f4bf", "dancer": "1f483", "page_facing_up": "1f4c4", "church": "26ea", "boar": "1f417", "boat": "26f5", "person_with_blond_hair": "1f471", "swimmer": "1f3ca", "wavy_dash": "3030", "three": "0033", "oden": "1f362", "secret": "3299", "clock430": "1f55f", "stuck_out_tongue_closed_eyes": "1f61d", "helicopter": "1f681", "heavy_division_sign": "2797", "musical_note": "1f3b5", "u7981": "1f232", "mushroom": "1f344", "fire": "1f525", "two_hearts": "1f495", "revolving_hearts": "1f49e", "cow": "1f42e", "tiger": "1f42f", "cold_sweat": "1f630", "bike": "1f6b2", "bulb": "1f4a1", "golf": "26f3", "heart_eyes": "1f60d", "open_file_folder": "1f4c2", "ant": "1f41c", "blowfish": "1f421", "soccer": "26bd", "speech_balloon": "1f4ac", "wind_chime": "1f390", "arrow_right_hook": "21aa", "seedling": "1f331", "fearful": "1f628", "envelope_with_arrow": "1f4e9", "grinning": "1f600", "bikini": "1f459", "warning": "26a0", "u5408": "1f234", "newspaper": "1f4f0", "kissing": "1f617", "bathtub": "1f6c1", "grey_question": "2754", "loop": "27bf", "potable_water": "1f6b0", "seven": "0037", "pound": "1f4b7", "two_women_holding_hands": "1f46d", "sushi": "1f363", "purse": "1f45b", "monkey_face": "1f435", "u5272": "1f239", "sleeping": "1f634", "anger": "1f4a2", "vs": "1f19a", "arrow_forward": "25b6", "violin": "1f3bb", "bullettrain_front": "1f685", "mouse": "1f42d", "id": "1f194", "u6307": "1f22f", "shirt": "1f455", "white_circle": "26aa", "balloon": "1f388", "heart_decoration": "1f49f", "joy_cat": "1f639", "kimono": "1f458", "speaker": "1f508", "train2": "1f686", "first_quarter_moon": "1f313", "left_luggage": "1f6c5", "meat_on_bone": "1f356", "point_left": "1f448", "hamster": "1f439", "satellite": "1f4e1", "arrow_heading_up": "2934", "snail": "1f40c", "rainbow": "1f308", "arrow_down_small": "1f53d", "leopard": "1f406", "diamond_shape_with_a_dot_inside": "1f4a0", "barber": "1f488", "christmas_tree": "1f384", "arrow_double_down": "23ec", "whale": "1f433", "ice_cream": "1f368", "foggy": "1f301", "euro": "1f4b6", "broken_heart": "1f494", "musical_score": "1f3bc", "triangular_ruler": "1f4d0", "ocean": "1f30a", "signal_strength": "1f4f6", "flags": "1f38f", "five": "0035", "muscle": "1f4aa", "love_hotel": "1f3e9", "taxi": "1f695", "eyeglasses": "1f453", "rocket": "1f680", "yen": "1f4b4", "straight_ruler": "1f4cf", "u7533": "1f238", "racehorse": "1f40e", "sleepy": "1f62a", "birthday": "1f382", "green_apple": "1f34f", "dromedary_camel": "1f42a", "doughnut": "1f369", "flashlight": "1f526", "womans_hat": "1f452", "sandal": "1f461", "white_medium_square": "25fb", "snowboarder": "1f3c2", "sunflower": "1f33b", "grey_exclamation": "2755", "rose": "1f339", "guardsman": "1f482", "boot": "1f462", "tropical_fish": "1f420", "cherries": "1f352", "innocent": "1f607", "rice_cracker": "1f358", "ski": "1f3bf", "pill": "1f48a", "musical_keyboard": "1f3b9", "boom": "1f4a5", "full_moon": "1f315", "orange_book": "1f4d9", "couple": "1f46b", "japanese_goblin": "1f47a", "dart": "1f3af", "clock1": "1f550", "clock2": "1f551", "clock3": "1f552", "clock4": "1f553", "clock5": "1f554", "clock6": "1f555", "clock7": "1f556", "clock8": "1f557", "clock9": "1f558", "clock130": "1f55c", "disappointed": "1f61e", "grin": "1f601", "womans_clothes": "1f45a", "gift": "1f381", "stuck_out_tongue": "1f61b", "stuck_out_tongue_winking_eye": "1f61c", "candy": "1f36c", "arrows_counterclockwise": "1f504", "tram": "1f68a", "hotsprings": "2668", "laughing": "1f606", "bamboo": "1f38d", "clap": "1f44f", "outbox_tray": "1f4e4", "left_right_arrow": "2194", "japanese_castle": "1f3ef", "waning_gibbous_moon": "1f316", "crown": "1f451", "back": "1f519", "sparkling_heart": "1f496", "clubs": "2663", "bust_in_silhouette": "1f464", "person_with_pouting_face": "1f64e", "two_men_holding_hands": "1f46c", "bento": "1f371", "moon": "1f314", "anguished": "1f627", "tanabata_tree": "1f38b", "fountain": "26f2", "carousel_horse": "1f3a0", "large_orange_diamond": "1f536", "heavy_minus_sign": "2796", "o2": "1f17e", "nose": "1f443", "point_up": "261d", "smiley": "1f603", "facepunch": "1f44a", "zzz": "1f4a4", "corn": "1f33d", "kissing_smiling_eyes": "1f619", "fish_cake": "1f365", "stew": "1f372", "santa": "1f385", "kissing_heart": "1f618", "no_pedestrians": "1f6b7", "crescent_moon": "1f319", "trophy": "1f3c6", "on": "1f51b", "ok": "1f197", "city_sunrise": "1f307", "package": "1f4e6", "arrow_backward": "25c0", "school_satchel": "1f392", "o": "2b55", "clock12": "1f55b", "chart_with_downwards_trend": "1f4c9", "clock10": "1f559", "clock11": "1f55a", "wolf": "1f43a", "sweat": "1f613", "ox": "1f402", "mountain_railway": "1f69e", "tongue": "1f445", "womens": "1f6ba", "curry": "1f35b", "angry": "1f620", "baby_chick": "1f424", "two": "0032", "sob": "1f62d", "do_not_litter": "1f6af", "waxing_crescent_moon": "1f312", "full_moon_with_face": "1f31d", "bath": "1f6c0", "sos": "1f198", "frog": "1f438", "angel": "1f47c", "syringe": "1f489", "last_quarter_moon": "1f317", "tada": "1f389", "ok_hand": "1f44c", "custard": "1f36e", "rowboat": "1f6a3", "clock530": "1f560", "heavy_multiplication_x": "2716", "white_check_mark": "2705", "tennis": "1f3be", "question": "2753", "beer": "1f37a", "stars": "1f320", "capital_abcd": "1f520", "mahjong": "1f004", "bus": "1f68c", "registered": "00ae", "fireworks": "1f386", "cloud": "2601", "construction": "1f6a7", "black_circle": "26ab", "fallen_leaf": "1f342", "astonished": "1f632", "ear": "1f442", "radio_button": "1f518", "bug": "1f41b", "penguin": "1f427", "electric_plug": "1f50c", "congratulations": "3297", "skull": "1f480", "rage": "1f621", "whale2": "1f40b", "fries": "1f35f", "up": "1f199", "vhs": "1f4fc", "strawberry": "1f353", "cat2": "1f408", "athletic_shoe": "1f45f", "star2": "1f31f", "cake": "1f370", "gemini": "264a", "arrow_double_up": "23eb", "toilet": "1f6bd", "ab": "1f18e", "fist": "270a", "mortar_board": "1f393", "cinema": "1f3a6", "hatched_chick": "1f425", "triangular_flag_on_post": "1f6a9", "black_nib": "2712", "pig": "1f437", "floppy_disk": "1f4be", "black_large_square": "2b1b", "ship": "1f6a2", "girl": "1f467", "telephone_receiver": "1f4de", "performing_arts": "1f3ad", "person_frowning": "1f64d", "movie_camera": "1f3a5", "lemon": "1f34b", "pensive": "1f614", "peach": "1f351", "arrow_upper_right": "2197", "ng": "1f196", "mountain_bicyclist": "1f6b5", "book": "1f4d6", "clock1130": "1f566", "oncoming_bus": "1f68d", "clock1030": "1f565", "heart_eyes_cat": "1f63b", "repeat": "1f501", "star": "2b50", "rabbit2": "1f407", "footprints": "1f463", "football": "1f3c8", "pear": "1f350", "taurus": "2649", "articulated_lorry": "1f69b", "no_entry_sign": "1f6ab", "police_car": "1f693", "money_with_wings": "1f4b8", "black_medium_square": "25fc", "closed_book": "1f4d5", "ghost": "1f47b", "droplet": "1f4a7", "spades": "2660", "vibration_mode": "1f4f3", "expressionless": "1f611", "dvd": "1f4c0", "mask": "1f637", "mag_right": "1f50e", "smirk": "1f60f", "sunrise_over_mountains": "1f304", "partly_sunny": "26c5", "scroll": "1f4dc"} -------------------------------------------------------------------------------- /src/pe4kin_emoji.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% This file is auto-generated at 2016-06-06 23:21:01.821410 by mk_emoji.py from emoji.json. 3 | %% DO NOT EDIT! 4 | 5 | -module(pe4kin_emoji). 6 | -export([name_to_char/1, char_to_name/1, is_emoji/1, names/0]). 7 | 8 | -spec name_to_char(atom()) -> char(). 9 | name_to_char('+1') -> 16#1f44d; 10 | name_to_char('-1') -> 16#1f44e; 11 | name_to_char('100') -> 16#1f4af; 12 | name_to_char('1234') -> 16#1f522; 13 | name_to_char('8ball') -> 16#1f3b1; 14 | name_to_char('a') -> 16#1f170; 15 | name_to_char('ab') -> 16#1f18e; 16 | name_to_char('abc') -> 16#1f524; 17 | name_to_char('abcd') -> 16#1f521; 18 | name_to_char('accept') -> 16#1f251; 19 | name_to_char('aerial_tramway') -> 16#1f6a1; 20 | name_to_char('airplane') -> 16#2708; 21 | name_to_char('alarm_clock') -> 16#23f0; 22 | name_to_char('alien') -> 16#1f47d; 23 | name_to_char('ambulance') -> 16#1f691; 24 | name_to_char('anchor') -> 16#2693; 25 | name_to_char('angel') -> 16#1f47c; 26 | name_to_char('anger') -> 16#1f4a2; 27 | name_to_char('angry') -> 16#1f620; 28 | name_to_char('anguished') -> 16#1f627; 29 | name_to_char('ant') -> 16#1f41c; 30 | name_to_char('apple') -> 16#1f34e; 31 | name_to_char('aquarius') -> 16#2652; 32 | name_to_char('aries') -> 16#2648; 33 | name_to_char('arrow_backward') -> 16#25c0; 34 | name_to_char('arrow_double_down') -> 16#23ec; 35 | name_to_char('arrow_double_up') -> 16#23eb; 36 | name_to_char('arrow_down') -> 16#2b07; 37 | name_to_char('arrow_down_small') -> 16#1f53d; 38 | name_to_char('arrow_forward') -> 16#25b6; 39 | name_to_char('arrow_heading_down') -> 16#2935; 40 | name_to_char('arrow_heading_up') -> 16#2934; 41 | name_to_char('arrow_left') -> 16#2b05; 42 | name_to_char('arrow_lower_left') -> 16#2199; 43 | name_to_char('arrow_lower_right') -> 16#2198; 44 | name_to_char('arrow_right') -> 16#27a1; 45 | name_to_char('arrow_right_hook') -> 16#21aa; 46 | name_to_char('arrow_up') -> 16#2b06; 47 | name_to_char('arrow_up_down') -> 16#2195; 48 | name_to_char('arrow_up_small') -> 16#1f53c; 49 | name_to_char('arrow_upper_left') -> 16#2196; 50 | name_to_char('arrow_upper_right') -> 16#2197; 51 | name_to_char('arrows_clockwise') -> 16#1f503; 52 | name_to_char('arrows_counterclockwise') -> 16#1f504; 53 | name_to_char('art') -> 16#1f3a8; 54 | name_to_char('articulated_lorry') -> 16#1f69b; 55 | name_to_char('astonished') -> 16#1f632; 56 | name_to_char('athletic_shoe') -> 16#1f45f; 57 | name_to_char('atm') -> 16#1f3e7; 58 | name_to_char('b') -> 16#1f171; 59 | name_to_char('baby') -> 16#1f476; 60 | name_to_char('baby_bottle') -> 16#1f37c; 61 | name_to_char('baby_chick') -> 16#1f424; 62 | name_to_char('baby_symbol') -> 16#1f6bc; 63 | name_to_char('back') -> 16#1f519; 64 | name_to_char('baggage_claim') -> 16#1f6c4; 65 | name_to_char('balloon') -> 16#1f388; 66 | name_to_char('ballot_box_with_check') -> 16#2611; 67 | name_to_char('bamboo') -> 16#1f38d; 68 | name_to_char('banana') -> 16#1f34c; 69 | name_to_char('bangbang') -> 16#203c; 70 | name_to_char('bank') -> 16#1f3e6; 71 | name_to_char('bar_chart') -> 16#1f4ca; 72 | name_to_char('barber') -> 16#1f488; 73 | name_to_char('baseball') -> 16#26be; 74 | name_to_char('basketball') -> 16#1f3c0; 75 | name_to_char('bath') -> 16#1f6c0; 76 | name_to_char('bathtub') -> 16#1f6c1; 77 | name_to_char('battery') -> 16#1f50b; 78 | name_to_char('bear') -> 16#1f43b; 79 | name_to_char('bee') -> 16#1f41d; 80 | name_to_char('beer') -> 16#1f37a; 81 | name_to_char('beers') -> 16#1f37b; 82 | name_to_char('beetle') -> 16#1f41e; 83 | name_to_char('beginner') -> 16#1f530; 84 | name_to_char('bell') -> 16#1f514; 85 | name_to_char('bento') -> 16#1f371; 86 | name_to_char('bicyclist') -> 16#1f6b4; 87 | name_to_char('bike') -> 16#1f6b2; 88 | name_to_char('bikini') -> 16#1f459; 89 | name_to_char('bird') -> 16#1f426; 90 | name_to_char('birthday') -> 16#1f382; 91 | name_to_char('black_circle') -> 16#26ab; 92 | name_to_char('black_joker') -> 16#1f0cf; 93 | name_to_char('black_large_square') -> 16#2b1b; 94 | name_to_char('black_medium_small_square') -> 16#25fe; 95 | name_to_char('black_medium_square') -> 16#25fc; 96 | name_to_char('black_nib') -> 16#2712; 97 | name_to_char('black_small_square') -> 16#25aa; 98 | name_to_char('black_square_button') -> 16#1f532; 99 | name_to_char('blossom') -> 16#1f33c; 100 | name_to_char('blowfish') -> 16#1f421; 101 | name_to_char('blue_book') -> 16#1f4d8; 102 | name_to_char('blue_car') -> 16#1f699; 103 | name_to_char('blue_heart') -> 16#1f499; 104 | name_to_char('blush') -> 16#1f60a; 105 | name_to_char('boar') -> 16#1f417; 106 | name_to_char('boat') -> 16#26f5; 107 | name_to_char('bomb') -> 16#1f4a3; 108 | name_to_char('book') -> 16#1f4d6; 109 | name_to_char('bookmark') -> 16#1f516; 110 | name_to_char('bookmark_tabs') -> 16#1f4d1; 111 | name_to_char('books') -> 16#1f4da; 112 | name_to_char('boom') -> 16#1f4a5; 113 | name_to_char('boot') -> 16#1f462; 114 | name_to_char('bouquet') -> 16#1f490; 115 | name_to_char('bow') -> 16#1f647; 116 | name_to_char('bowling') -> 16#1f3b3; 117 | name_to_char('boy') -> 16#1f466; 118 | name_to_char('bread') -> 16#1f35e; 119 | name_to_char('bride_with_veil') -> 16#1f470; 120 | name_to_char('bridge_at_night') -> 16#1f309; 121 | name_to_char('briefcase') -> 16#1f4bc; 122 | name_to_char('broken_heart') -> 16#1f494; 123 | name_to_char('bug') -> 16#1f41b; 124 | name_to_char('bulb') -> 16#1f4a1; 125 | name_to_char('bullettrain_front') -> 16#1f685; 126 | name_to_char('bullettrain_side') -> 16#1f684; 127 | name_to_char('bus') -> 16#1f68c; 128 | name_to_char('busstop') -> 16#1f68f; 129 | name_to_char('bust_in_silhouette') -> 16#1f464; 130 | name_to_char('busts_in_silhouette') -> 16#1f465; 131 | name_to_char('cactus') -> 16#1f335; 132 | name_to_char('cake') -> 16#1f370; 133 | name_to_char('calendar') -> 16#1f4c6; 134 | name_to_char('calling') -> 16#1f4f2; 135 | name_to_char('camel') -> 16#1f42b; 136 | name_to_char('camera') -> 16#1f4f7; 137 | name_to_char('cancer') -> 16#264b; 138 | name_to_char('candy') -> 16#1f36c; 139 | name_to_char('capital_abcd') -> 16#1f520; 140 | name_to_char('capricorn') -> 16#2651; 141 | name_to_char('car') -> 16#1f697; 142 | name_to_char('card_index') -> 16#1f4c7; 143 | name_to_char('carousel_horse') -> 16#1f3a0; 144 | name_to_char('cat') -> 16#1f431; 145 | name_to_char('cat2') -> 16#1f408; 146 | name_to_char('cd') -> 16#1f4bf; 147 | name_to_char('chart') -> 16#1f4b9; 148 | name_to_char('chart_with_downwards_trend') -> 16#1f4c9; 149 | name_to_char('chart_with_upwards_trend') -> 16#1f4c8; 150 | name_to_char('checkered_flag') -> 16#1f3c1; 151 | name_to_char('cherries') -> 16#1f352; 152 | name_to_char('cherry_blossom') -> 16#1f338; 153 | name_to_char('chestnut') -> 16#1f330; 154 | name_to_char('chicken') -> 16#1f414; 155 | name_to_char('children_crossing') -> 16#1f6b8; 156 | name_to_char('chocolate_bar') -> 16#1f36b; 157 | name_to_char('christmas_tree') -> 16#1f384; 158 | name_to_char('church') -> 16#26ea; 159 | name_to_char('cinema') -> 16#1f3a6; 160 | name_to_char('circus_tent') -> 16#1f3aa; 161 | name_to_char('city_sunrise') -> 16#1f307; 162 | name_to_char('city_sunset') -> 16#1f306; 163 | name_to_char('cl') -> 16#1f191; 164 | name_to_char('clap') -> 16#1f44f; 165 | name_to_char('clapper') -> 16#1f3ac; 166 | name_to_char('clipboard') -> 16#1f4cb; 167 | name_to_char('clock1') -> 16#1f550; 168 | name_to_char('clock10') -> 16#1f559; 169 | name_to_char('clock1030') -> 16#1f565; 170 | name_to_char('clock11') -> 16#1f55a; 171 | name_to_char('clock1130') -> 16#1f566; 172 | name_to_char('clock12') -> 16#1f55b; 173 | name_to_char('clock1230') -> 16#1f567; 174 | name_to_char('clock130') -> 16#1f55c; 175 | name_to_char('clock2') -> 16#1f551; 176 | name_to_char('clock230') -> 16#1f55d; 177 | name_to_char('clock3') -> 16#1f552; 178 | name_to_char('clock330') -> 16#1f55e; 179 | name_to_char('clock4') -> 16#1f553; 180 | name_to_char('clock430') -> 16#1f55f; 181 | name_to_char('clock5') -> 16#1f554; 182 | name_to_char('clock530') -> 16#1f560; 183 | name_to_char('clock6') -> 16#1f555; 184 | name_to_char('clock630') -> 16#1f561; 185 | name_to_char('clock7') -> 16#1f556; 186 | name_to_char('clock730') -> 16#1f562; 187 | name_to_char('clock8') -> 16#1f557; 188 | name_to_char('clock830') -> 16#1f563; 189 | name_to_char('clock9') -> 16#1f558; 190 | name_to_char('clock930') -> 16#1f564; 191 | name_to_char('closed_book') -> 16#1f4d5; 192 | name_to_char('closed_lock_with_key') -> 16#1f510; 193 | name_to_char('closed_umbrella') -> 16#1f302; 194 | name_to_char('cloud') -> 16#2601; 195 | name_to_char('clubs') -> 16#2663; 196 | name_to_char('cocktail') -> 16#1f378; 197 | name_to_char('coffee') -> 16#2615; 198 | name_to_char('cold_sweat') -> 16#1f630; 199 | name_to_char('computer') -> 16#1f4bb; 200 | name_to_char('confetti_ball') -> 16#1f38a; 201 | name_to_char('confounded') -> 16#1f616; 202 | name_to_char('confused') -> 16#1f615; 203 | name_to_char('congratulations') -> 16#3297; 204 | name_to_char('construction') -> 16#1f6a7; 205 | name_to_char('construction_worker') -> 16#1f477; 206 | name_to_char('convenience_store') -> 16#1f3ea; 207 | name_to_char('cookie') -> 16#1f36a; 208 | name_to_char('cool') -> 16#1f192; 209 | name_to_char('cop') -> 16#1f46e; 210 | name_to_char('copyright') -> 16#00a9; 211 | name_to_char('corn') -> 16#1f33d; 212 | name_to_char('couple') -> 16#1f46b; 213 | name_to_char('couple_with_heart') -> 16#1f491; 214 | name_to_char('couplekiss') -> 16#1f48f; 215 | name_to_char('cow') -> 16#1f42e; 216 | name_to_char('cow2') -> 16#1f404; 217 | name_to_char('credit_card') -> 16#1f4b3; 218 | name_to_char('crescent_moon') -> 16#1f319; 219 | name_to_char('crocodile') -> 16#1f40a; 220 | name_to_char('crossed_flags') -> 16#1f38c; 221 | name_to_char('crown') -> 16#1f451; 222 | name_to_char('cry') -> 16#1f622; 223 | name_to_char('crying_cat_face') -> 16#1f63f; 224 | name_to_char('crystal_ball') -> 16#1f52e; 225 | name_to_char('cupid') -> 16#1f498; 226 | name_to_char('curly_loop') -> 16#27b0; 227 | name_to_char('currency_exchange') -> 16#1f4b1; 228 | name_to_char('curry') -> 16#1f35b; 229 | name_to_char('custard') -> 16#1f36e; 230 | name_to_char('customs') -> 16#1f6c3; 231 | name_to_char('cyclone') -> 16#1f300; 232 | name_to_char('dancer') -> 16#1f483; 233 | name_to_char('dancers') -> 16#1f46f; 234 | name_to_char('dango') -> 16#1f361; 235 | name_to_char('dart') -> 16#1f3af; 236 | name_to_char('dash') -> 16#1f4a8; 237 | name_to_char('date') -> 16#1f4c5; 238 | name_to_char('deciduous_tree') -> 16#1f333; 239 | name_to_char('department_store') -> 16#1f3ec; 240 | name_to_char('diamond_shape_with_a_dot_inside') -> 16#1f4a0; 241 | name_to_char('diamonds') -> 16#2666; 242 | name_to_char('disappointed') -> 16#1f61e; 243 | name_to_char('disappointed_relieved') -> 16#1f625; 244 | name_to_char('dizzy') -> 16#1f4ab; 245 | name_to_char('dizzy_face') -> 16#1f635; 246 | name_to_char('do_not_litter') -> 16#1f6af; 247 | name_to_char('dog') -> 16#1f436; 248 | name_to_char('dog2') -> 16#1f415; 249 | name_to_char('dollar') -> 16#1f4b5; 250 | name_to_char('dolls') -> 16#1f38e; 251 | name_to_char('dolphin') -> 16#1f42c; 252 | name_to_char('door') -> 16#1f6aa; 253 | name_to_char('doughnut') -> 16#1f369; 254 | name_to_char('dragon') -> 16#1f409; 255 | name_to_char('dragon_face') -> 16#1f432; 256 | name_to_char('dress') -> 16#1f457; 257 | name_to_char('dromedary_camel') -> 16#1f42a; 258 | name_to_char('droplet') -> 16#1f4a7; 259 | name_to_char('dvd') -> 16#1f4c0; 260 | name_to_char('e-mail') -> 16#1f4e7; 261 | name_to_char('ear') -> 16#1f442; 262 | name_to_char('ear_of_rice') -> 16#1f33e; 263 | name_to_char('earth_africa') -> 16#1f30d; 264 | name_to_char('earth_americas') -> 16#1f30e; 265 | name_to_char('earth_asia') -> 16#1f30f; 266 | name_to_char('egg') -> 16#1f373; 267 | name_to_char('eggplant') -> 16#1f346; 268 | name_to_char('eight') -> 16#0038; 269 | name_to_char('eight_pointed_black_star') -> 16#2734; 270 | name_to_char('eight_spoked_asterisk') -> 16#2733; 271 | name_to_char('electric_plug') -> 16#1f50c; 272 | name_to_char('elephant') -> 16#1f418; 273 | name_to_char('email') -> 16#2709; 274 | name_to_char('end') -> 16#1f51a; 275 | name_to_char('envelope_with_arrow') -> 16#1f4e9; 276 | name_to_char('euro') -> 16#1f4b6; 277 | name_to_char('european_castle') -> 16#1f3f0; 278 | name_to_char('european_post_office') -> 16#1f3e4; 279 | name_to_char('evergreen_tree') -> 16#1f332; 280 | name_to_char('exclamation') -> 16#2757; 281 | name_to_char('expressionless') -> 16#1f611; 282 | name_to_char('eyeglasses') -> 16#1f453; 283 | name_to_char('eyes') -> 16#1f440; 284 | name_to_char('facepunch') -> 16#1f44a; 285 | name_to_char('factory') -> 16#1f3ed; 286 | name_to_char('fallen_leaf') -> 16#1f342; 287 | name_to_char('family') -> 16#1f46a; 288 | name_to_char('fast_forward') -> 16#23e9; 289 | name_to_char('fax') -> 16#1f4e0; 290 | name_to_char('fearful') -> 16#1f628; 291 | name_to_char('feet') -> 16#1f43e; 292 | name_to_char('ferris_wheel') -> 16#1f3a1; 293 | name_to_char('file_folder') -> 16#1f4c1; 294 | name_to_char('fire') -> 16#1f525; 295 | name_to_char('fire_engine') -> 16#1f692; 296 | name_to_char('fireworks') -> 16#1f386; 297 | name_to_char('first_quarter_moon') -> 16#1f313; 298 | name_to_char('first_quarter_moon_with_face') -> 16#1f31b; 299 | name_to_char('fish') -> 16#1f41f; 300 | name_to_char('fish_cake') -> 16#1f365; 301 | name_to_char('fishing_pole_and_fish') -> 16#1f3a3; 302 | name_to_char('fist') -> 16#270a; 303 | name_to_char('five') -> 16#0035; 304 | name_to_char('flags') -> 16#1f38f; 305 | name_to_char('flashlight') -> 16#1f526; 306 | name_to_char('floppy_disk') -> 16#1f4be; 307 | name_to_char('flower_playing_cards') -> 16#1f3b4; 308 | name_to_char('flushed') -> 16#1f633; 309 | name_to_char('foggy') -> 16#1f301; 310 | name_to_char('football') -> 16#1f3c8; 311 | name_to_char('footprints') -> 16#1f463; 312 | name_to_char('fork_and_knife') -> 16#1f374; 313 | name_to_char('fountain') -> 16#26f2; 314 | name_to_char('four') -> 16#0034; 315 | name_to_char('four_leaf_clover') -> 16#1f340; 316 | name_to_char('free') -> 16#1f193; 317 | name_to_char('fried_shrimp') -> 16#1f364; 318 | name_to_char('fries') -> 16#1f35f; 319 | name_to_char('frog') -> 16#1f438; 320 | name_to_char('frowning') -> 16#1f626; 321 | name_to_char('fuelpump') -> 16#26fd; 322 | name_to_char('full_moon') -> 16#1f315; 323 | name_to_char('full_moon_with_face') -> 16#1f31d; 324 | name_to_char('game_die') -> 16#1f3b2; 325 | name_to_char('gem') -> 16#1f48e; 326 | name_to_char('gemini') -> 16#264a; 327 | name_to_char('ghost') -> 16#1f47b; 328 | name_to_char('gift') -> 16#1f381; 329 | name_to_char('gift_heart') -> 16#1f49d; 330 | name_to_char('girl') -> 16#1f467; 331 | name_to_char('globe_with_meridians') -> 16#1f310; 332 | name_to_char('goat') -> 16#1f410; 333 | name_to_char('golf') -> 16#26f3; 334 | name_to_char('grapes') -> 16#1f347; 335 | name_to_char('green_apple') -> 16#1f34f; 336 | name_to_char('green_book') -> 16#1f4d7; 337 | name_to_char('green_heart') -> 16#1f49a; 338 | name_to_char('grey_exclamation') -> 16#2755; 339 | name_to_char('grey_question') -> 16#2754; 340 | name_to_char('grimacing') -> 16#1f62c; 341 | name_to_char('grin') -> 16#1f601; 342 | name_to_char('grinning') -> 16#1f600; 343 | name_to_char('guardsman') -> 16#1f482; 344 | name_to_char('guitar') -> 16#1f3b8; 345 | name_to_char('gun') -> 16#1f52b; 346 | name_to_char('haircut') -> 16#1f487; 347 | name_to_char('hamburger') -> 16#1f354; 348 | name_to_char('hammer') -> 16#1f528; 349 | name_to_char('hamster') -> 16#1f439; 350 | name_to_char('hand') -> 16#270b; 351 | name_to_char('handbag') -> 16#1f45c; 352 | name_to_char('hankey') -> 16#1f4a9; 353 | name_to_char('hash') -> 16#0023; 354 | name_to_char('hatched_chick') -> 16#1f425; 355 | name_to_char('hatching_chick') -> 16#1f423; 356 | name_to_char('headphones') -> 16#1f3a7; 357 | name_to_char('hear_no_evil') -> 16#1f649; 358 | name_to_char('heart') -> 16#2764; 359 | name_to_char('heart_decoration') -> 16#1f49f; 360 | name_to_char('heart_eyes') -> 16#1f60d; 361 | name_to_char('heart_eyes_cat') -> 16#1f63b; 362 | name_to_char('heartbeat') -> 16#1f493; 363 | name_to_char('heartpulse') -> 16#1f497; 364 | name_to_char('hearts') -> 16#2665; 365 | name_to_char('heavy_check_mark') -> 16#2714; 366 | name_to_char('heavy_division_sign') -> 16#2797; 367 | name_to_char('heavy_dollar_sign') -> 16#1f4b2; 368 | name_to_char('heavy_minus_sign') -> 16#2796; 369 | name_to_char('heavy_multiplication_x') -> 16#2716; 370 | name_to_char('heavy_plus_sign') -> 16#2795; 371 | name_to_char('helicopter') -> 16#1f681; 372 | name_to_char('herb') -> 16#1f33f; 373 | name_to_char('hibiscus') -> 16#1f33a; 374 | name_to_char('high_brightness') -> 16#1f506; 375 | name_to_char('high_heel') -> 16#1f460; 376 | name_to_char('hocho') -> 16#1f52a; 377 | name_to_char('honey_pot') -> 16#1f36f; 378 | name_to_char('horse') -> 16#1f434; 379 | name_to_char('horse_racing') -> 16#1f3c7; 380 | name_to_char('hospital') -> 16#1f3e5; 381 | name_to_char('hotel') -> 16#1f3e8; 382 | name_to_char('hotsprings') -> 16#2668; 383 | name_to_char('hourglass') -> 16#231b; 384 | name_to_char('hourglass_flowing_sand') -> 16#23f3; 385 | name_to_char('house') -> 16#1f3e0; 386 | name_to_char('house_with_garden') -> 16#1f3e1; 387 | name_to_char('hushed') -> 16#1f62f; 388 | name_to_char('ice_cream') -> 16#1f368; 389 | name_to_char('icecream') -> 16#1f366; 390 | name_to_char('id') -> 16#1f194; 391 | name_to_char('ideograph_advantage') -> 16#1f250; 392 | name_to_char('imp') -> 16#1f47f; 393 | name_to_char('inbox_tray') -> 16#1f4e5; 394 | name_to_char('incoming_envelope') -> 16#1f4e8; 395 | name_to_char('information_desk_person') -> 16#1f481; 396 | name_to_char('information_source') -> 16#2139; 397 | name_to_char('innocent') -> 16#1f607; 398 | name_to_char('interrobang') -> 16#2049; 399 | name_to_char('iphone') -> 16#1f4f1; 400 | name_to_char('izakaya_lantern') -> 16#1f3ee; 401 | name_to_char('jack_o_lantern') -> 16#1f383; 402 | name_to_char('japan') -> 16#1f5fe; 403 | name_to_char('japanese_castle') -> 16#1f3ef; 404 | name_to_char('japanese_goblin') -> 16#1f47a; 405 | name_to_char('japanese_ogre') -> 16#1f479; 406 | name_to_char('jeans') -> 16#1f456; 407 | name_to_char('joy') -> 16#1f602; 408 | name_to_char('joy_cat') -> 16#1f639; 409 | name_to_char('key') -> 16#1f511; 410 | name_to_char('keycap_ten') -> 16#1f51f; 411 | name_to_char('kimono') -> 16#1f458; 412 | name_to_char('kiss') -> 16#1f48b; 413 | name_to_char('kissing') -> 16#1f617; 414 | name_to_char('kissing_cat') -> 16#1f63d; 415 | name_to_char('kissing_closed_eyes') -> 16#1f61a; 416 | name_to_char('kissing_heart') -> 16#1f618; 417 | name_to_char('kissing_smiling_eyes') -> 16#1f619; 418 | name_to_char('koala') -> 16#1f428; 419 | name_to_char('koko') -> 16#1f201; 420 | name_to_char('large_blue_circle') -> 16#1f535; 421 | name_to_char('large_blue_diamond') -> 16#1f537; 422 | name_to_char('large_orange_diamond') -> 16#1f536; 423 | name_to_char('last_quarter_moon') -> 16#1f317; 424 | name_to_char('last_quarter_moon_with_face') -> 16#1f31c; 425 | name_to_char('laughing') -> 16#1f606; 426 | name_to_char('leaves') -> 16#1f343; 427 | name_to_char('ledger') -> 16#1f4d2; 428 | name_to_char('left_luggage') -> 16#1f6c5; 429 | name_to_char('left_right_arrow') -> 16#2194; 430 | name_to_char('leftwards_arrow_with_hook') -> 16#21a9; 431 | name_to_char('lemon') -> 16#1f34b; 432 | name_to_char('leo') -> 16#264c; 433 | name_to_char('leopard') -> 16#1f406; 434 | name_to_char('libra') -> 16#264e; 435 | name_to_char('light_rail') -> 16#1f688; 436 | name_to_char('link') -> 16#1f517; 437 | name_to_char('lips') -> 16#1f444; 438 | name_to_char('lipstick') -> 16#1f484; 439 | name_to_char('lock') -> 16#1f512; 440 | name_to_char('lock_with_ink_pen') -> 16#1f50f; 441 | name_to_char('lollipop') -> 16#1f36d; 442 | name_to_char('loop') -> 16#27bf; 443 | name_to_char('loud_sound') -> 16#1f50a; 444 | name_to_char('loudspeaker') -> 16#1f4e2; 445 | name_to_char('love_hotel') -> 16#1f3e9; 446 | name_to_char('love_letter') -> 16#1f48c; 447 | name_to_char('low_brightness') -> 16#1f505; 448 | name_to_char('m') -> 16#24c2; 449 | name_to_char('mag') -> 16#1f50d; 450 | name_to_char('mag_right') -> 16#1f50e; 451 | name_to_char('mahjong') -> 16#1f004; 452 | name_to_char('mailbox') -> 16#1f4eb; 453 | name_to_char('mailbox_closed') -> 16#1f4ea; 454 | name_to_char('mailbox_with_mail') -> 16#1f4ec; 455 | name_to_char('mailbox_with_no_mail') -> 16#1f4ed; 456 | name_to_char('man') -> 16#1f468; 457 | name_to_char('man_with_gua_pi_mao') -> 16#1f472; 458 | name_to_char('man_with_turban') -> 16#1f473; 459 | name_to_char('mans_shoe') -> 16#1f45e; 460 | name_to_char('maple_leaf') -> 16#1f341; 461 | name_to_char('mask') -> 16#1f637; 462 | name_to_char('massage') -> 16#1f486; 463 | name_to_char('meat_on_bone') -> 16#1f356; 464 | name_to_char('mega') -> 16#1f4e3; 465 | name_to_char('melon') -> 16#1f348; 466 | name_to_char('memo') -> 16#1f4dd; 467 | name_to_char('mens') -> 16#1f6b9; 468 | name_to_char('metro') -> 16#1f687; 469 | name_to_char('microphone') -> 16#1f3a4; 470 | name_to_char('microscope') -> 16#1f52c; 471 | name_to_char('milky_way') -> 16#1f30c; 472 | name_to_char('minibus') -> 16#1f690; 473 | name_to_char('minidisc') -> 16#1f4bd; 474 | name_to_char('mobile_phone_off') -> 16#1f4f4; 475 | name_to_char('money_with_wings') -> 16#1f4b8; 476 | name_to_char('moneybag') -> 16#1f4b0; 477 | name_to_char('monkey') -> 16#1f412; 478 | name_to_char('monkey_face') -> 16#1f435; 479 | name_to_char('monorail') -> 16#1f69d; 480 | name_to_char('moon') -> 16#1f314; 481 | name_to_char('mortar_board') -> 16#1f393; 482 | name_to_char('mount_fuji') -> 16#1f5fb; 483 | name_to_char('mountain_bicyclist') -> 16#1f6b5; 484 | name_to_char('mountain_cableway') -> 16#1f6a0; 485 | name_to_char('mountain_railway') -> 16#1f69e; 486 | name_to_char('mouse') -> 16#1f42d; 487 | name_to_char('mouse2') -> 16#1f401; 488 | name_to_char('movie_camera') -> 16#1f3a5; 489 | name_to_char('moyai') -> 16#1f5ff; 490 | name_to_char('muscle') -> 16#1f4aa; 491 | name_to_char('mushroom') -> 16#1f344; 492 | name_to_char('musical_keyboard') -> 16#1f3b9; 493 | name_to_char('musical_note') -> 16#1f3b5; 494 | name_to_char('musical_score') -> 16#1f3bc; 495 | name_to_char('mute') -> 16#1f507; 496 | name_to_char('nail_care') -> 16#1f485; 497 | name_to_char('name_badge') -> 16#1f4db; 498 | name_to_char('necktie') -> 16#1f454; 499 | name_to_char('negative_squared_cross_mark') -> 16#274e; 500 | name_to_char('neutral_face') -> 16#1f610; 501 | name_to_char('new') -> 16#1f195; 502 | name_to_char('new_moon') -> 16#1f311; 503 | name_to_char('new_moon_with_face') -> 16#1f31a; 504 | name_to_char('newspaper') -> 16#1f4f0; 505 | name_to_char('ng') -> 16#1f196; 506 | name_to_char('night_with_stars') -> 16#1f303; 507 | name_to_char('nine') -> 16#0039; 508 | name_to_char('no_bell') -> 16#1f515; 509 | name_to_char('no_bicycles') -> 16#1f6b3; 510 | name_to_char('no_entry') -> 16#26d4; 511 | name_to_char('no_entry_sign') -> 16#1f6ab; 512 | name_to_char('no_good') -> 16#1f645; 513 | name_to_char('no_mobile_phones') -> 16#1f4f5; 514 | name_to_char('no_mouth') -> 16#1f636; 515 | name_to_char('no_pedestrians') -> 16#1f6b7; 516 | name_to_char('no_smoking') -> 16#1f6ad; 517 | name_to_char('non-potable_water') -> 16#1f6b1; 518 | name_to_char('nose') -> 16#1f443; 519 | name_to_char('notebook') -> 16#1f4d3; 520 | name_to_char('notebook_with_decorative_cover') -> 16#1f4d4; 521 | name_to_char('notes') -> 16#1f3b6; 522 | name_to_char('nut_and_bolt') -> 16#1f529; 523 | name_to_char('o') -> 16#2b55; 524 | name_to_char('o2') -> 16#1f17e; 525 | name_to_char('ocean') -> 16#1f30a; 526 | name_to_char('octopus') -> 16#1f419; 527 | name_to_char('oden') -> 16#1f362; 528 | name_to_char('office') -> 16#1f3e2; 529 | name_to_char('ok') -> 16#1f197; 530 | name_to_char('ok_hand') -> 16#1f44c; 531 | name_to_char('ok_woman') -> 16#1f646; 532 | name_to_char('older_man') -> 16#1f474; 533 | name_to_char('older_woman') -> 16#1f475; 534 | name_to_char('on') -> 16#1f51b; 535 | name_to_char('oncoming_automobile') -> 16#1f698; 536 | name_to_char('oncoming_bus') -> 16#1f68d; 537 | name_to_char('oncoming_police_car') -> 16#1f694; 538 | name_to_char('oncoming_taxi') -> 16#1f696; 539 | name_to_char('one') -> 16#0031; 540 | name_to_char('open_file_folder') -> 16#1f4c2; 541 | name_to_char('open_hands') -> 16#1f450; 542 | name_to_char('open_mouth') -> 16#1f62e; 543 | name_to_char('ophiuchus') -> 16#26ce; 544 | name_to_char('orange_book') -> 16#1f4d9; 545 | name_to_char('outbox_tray') -> 16#1f4e4; 546 | name_to_char('ox') -> 16#1f402; 547 | name_to_char('package') -> 16#1f4e6; 548 | name_to_char('page_facing_up') -> 16#1f4c4; 549 | name_to_char('page_with_curl') -> 16#1f4c3; 550 | name_to_char('pager') -> 16#1f4df; 551 | name_to_char('palm_tree') -> 16#1f334; 552 | name_to_char('panda_face') -> 16#1f43c; 553 | name_to_char('paperclip') -> 16#1f4ce; 554 | name_to_char('parking') -> 16#1f17f; 555 | name_to_char('part_alternation_mark') -> 16#303d; 556 | name_to_char('partly_sunny') -> 16#26c5; 557 | name_to_char('passport_control') -> 16#1f6c2; 558 | name_to_char('peach') -> 16#1f351; 559 | name_to_char('pear') -> 16#1f350; 560 | name_to_char('pencil2') -> 16#270f; 561 | name_to_char('penguin') -> 16#1f427; 562 | name_to_char('pensive') -> 16#1f614; 563 | name_to_char('performing_arts') -> 16#1f3ad; 564 | name_to_char('persevere') -> 16#1f623; 565 | name_to_char('person_frowning') -> 16#1f64d; 566 | name_to_char('person_with_blond_hair') -> 16#1f471; 567 | name_to_char('person_with_pouting_face') -> 16#1f64e; 568 | name_to_char('phone') -> 16#260e; 569 | name_to_char('pig') -> 16#1f437; 570 | name_to_char('pig2') -> 16#1f416; 571 | name_to_char('pig_nose') -> 16#1f43d; 572 | name_to_char('pill') -> 16#1f48a; 573 | name_to_char('pineapple') -> 16#1f34d; 574 | name_to_char('pisces') -> 16#2653; 575 | name_to_char('pizza') -> 16#1f355; 576 | name_to_char('point_down') -> 16#1f447; 577 | name_to_char('point_left') -> 16#1f448; 578 | name_to_char('point_right') -> 16#1f449; 579 | name_to_char('point_up') -> 16#261d; 580 | name_to_char('point_up_2') -> 16#1f446; 581 | name_to_char('police_car') -> 16#1f693; 582 | name_to_char('poodle') -> 16#1f429; 583 | name_to_char('post_office') -> 16#1f3e3; 584 | name_to_char('postal_horn') -> 16#1f4ef; 585 | name_to_char('postbox') -> 16#1f4ee; 586 | name_to_char('potable_water') -> 16#1f6b0; 587 | name_to_char('pouch') -> 16#1f45d; 588 | name_to_char('poultry_leg') -> 16#1f357; 589 | name_to_char('pound') -> 16#1f4b7; 590 | name_to_char('pouting_cat') -> 16#1f63e; 591 | name_to_char('pray') -> 16#1f64f; 592 | name_to_char('princess') -> 16#1f478; 593 | name_to_char('purple_heart') -> 16#1f49c; 594 | name_to_char('purse') -> 16#1f45b; 595 | name_to_char('pushpin') -> 16#1f4cc; 596 | name_to_char('put_litter_in_its_place') -> 16#1f6ae; 597 | name_to_char('question') -> 16#2753; 598 | name_to_char('rabbit') -> 16#1f430; 599 | name_to_char('rabbit2') -> 16#1f407; 600 | name_to_char('racehorse') -> 16#1f40e; 601 | name_to_char('radio') -> 16#1f4fb; 602 | name_to_char('radio_button') -> 16#1f518; 603 | name_to_char('rage') -> 16#1f621; 604 | name_to_char('railway_car') -> 16#1f683; 605 | name_to_char('rainbow') -> 16#1f308; 606 | name_to_char('raised_hands') -> 16#1f64c; 607 | name_to_char('raising_hand') -> 16#1f64b; 608 | name_to_char('ram') -> 16#1f40f; 609 | name_to_char('ramen') -> 16#1f35c; 610 | name_to_char('rat') -> 16#1f400; 611 | name_to_char('recycle') -> 16#267b; 612 | name_to_char('red_circle') -> 16#1f534; 613 | name_to_char('registered') -> 16#00ae; 614 | name_to_char('relaxed') -> 16#263a; 615 | name_to_char('relieved') -> 16#1f60c; 616 | name_to_char('repeat') -> 16#1f501; 617 | name_to_char('repeat_one') -> 16#1f502; 618 | name_to_char('restroom') -> 16#1f6bb; 619 | name_to_char('revolving_hearts') -> 16#1f49e; 620 | name_to_char('rewind') -> 16#23ea; 621 | name_to_char('ribbon') -> 16#1f380; 622 | name_to_char('rice') -> 16#1f35a; 623 | name_to_char('rice_ball') -> 16#1f359; 624 | name_to_char('rice_cracker') -> 16#1f358; 625 | name_to_char('rice_scene') -> 16#1f391; 626 | name_to_char('ring') -> 16#1f48d; 627 | name_to_char('rocket') -> 16#1f680; 628 | name_to_char('roller_coaster') -> 16#1f3a2; 629 | name_to_char('rooster') -> 16#1f413; 630 | name_to_char('rose') -> 16#1f339; 631 | name_to_char('rotating_light') -> 16#1f6a8; 632 | name_to_char('round_pushpin') -> 16#1f4cd; 633 | name_to_char('rowboat') -> 16#1f6a3; 634 | name_to_char('rugby_football') -> 16#1f3c9; 635 | name_to_char('runner') -> 16#1f3c3; 636 | name_to_char('running_shirt_with_sash') -> 16#1f3bd; 637 | name_to_char('sa') -> 16#1f202; 638 | name_to_char('sagittarius') -> 16#2650; 639 | name_to_char('sake') -> 16#1f376; 640 | name_to_char('sandal') -> 16#1f461; 641 | name_to_char('santa') -> 16#1f385; 642 | name_to_char('satellite') -> 16#1f4e1; 643 | name_to_char('saxophone') -> 16#1f3b7; 644 | name_to_char('school') -> 16#1f3eb; 645 | name_to_char('school_satchel') -> 16#1f392; 646 | name_to_char('scissors') -> 16#2702; 647 | name_to_char('scorpius') -> 16#264f; 648 | name_to_char('scream') -> 16#1f631; 649 | name_to_char('scream_cat') -> 16#1f640; 650 | name_to_char('scroll') -> 16#1f4dc; 651 | name_to_char('seat') -> 16#1f4ba; 652 | name_to_char('secret') -> 16#3299; 653 | name_to_char('see_no_evil') -> 16#1f648; 654 | name_to_char('seedling') -> 16#1f331; 655 | name_to_char('seven') -> 16#0037; 656 | name_to_char('shaved_ice') -> 16#1f367; 657 | name_to_char('sheep') -> 16#1f411; 658 | name_to_char('shell') -> 16#1f41a; 659 | name_to_char('ship') -> 16#1f6a2; 660 | name_to_char('shirt') -> 16#1f455; 661 | name_to_char('shower') -> 16#1f6bf; 662 | name_to_char('signal_strength') -> 16#1f4f6; 663 | name_to_char('six') -> 16#0036; 664 | name_to_char('six_pointed_star') -> 16#1f52f; 665 | name_to_char('ski') -> 16#1f3bf; 666 | name_to_char('skull') -> 16#1f480; 667 | name_to_char('sleeping') -> 16#1f634; 668 | name_to_char('sleepy') -> 16#1f62a; 669 | name_to_char('slot_machine') -> 16#1f3b0; 670 | name_to_char('small_blue_diamond') -> 16#1f539; 671 | name_to_char('small_orange_diamond') -> 16#1f538; 672 | name_to_char('small_red_triangle') -> 16#1f53a; 673 | name_to_char('small_red_triangle_down') -> 16#1f53b; 674 | name_to_char('smile') -> 16#1f604; 675 | name_to_char('smile_cat') -> 16#1f638; 676 | name_to_char('smiley') -> 16#1f603; 677 | name_to_char('smiley_cat') -> 16#1f63a; 678 | name_to_char('smiling_imp') -> 16#1f608; 679 | name_to_char('smirk') -> 16#1f60f; 680 | name_to_char('smirk_cat') -> 16#1f63c; 681 | name_to_char('smoking') -> 16#1f6ac; 682 | name_to_char('snail') -> 16#1f40c; 683 | name_to_char('snake') -> 16#1f40d; 684 | name_to_char('snowboarder') -> 16#1f3c2; 685 | name_to_char('snowflake') -> 16#2744; 686 | name_to_char('snowman') -> 16#26c4; 687 | name_to_char('sob') -> 16#1f62d; 688 | name_to_char('soccer') -> 16#26bd; 689 | name_to_char('soon') -> 16#1f51c; 690 | name_to_char('sos') -> 16#1f198; 691 | name_to_char('sound') -> 16#1f509; 692 | name_to_char('space_invader') -> 16#1f47e; 693 | name_to_char('spades') -> 16#2660; 694 | name_to_char('spaghetti') -> 16#1f35d; 695 | name_to_char('sparkle') -> 16#2747; 696 | name_to_char('sparkler') -> 16#1f387; 697 | name_to_char('sparkles') -> 16#2728; 698 | name_to_char('sparkling_heart') -> 16#1f496; 699 | name_to_char('speak_no_evil') -> 16#1f64a; 700 | name_to_char('speaker') -> 16#1f508; 701 | name_to_char('speech_balloon') -> 16#1f4ac; 702 | name_to_char('speedboat') -> 16#1f6a4; 703 | name_to_char('star') -> 16#2b50; 704 | name_to_char('star2') -> 16#1f31f; 705 | name_to_char('stars') -> 16#1f320; 706 | name_to_char('station') -> 16#1f689; 707 | name_to_char('statue_of_liberty') -> 16#1f5fd; 708 | name_to_char('steam_locomotive') -> 16#1f682; 709 | name_to_char('stew') -> 16#1f372; 710 | name_to_char('straight_ruler') -> 16#1f4cf; 711 | name_to_char('strawberry') -> 16#1f353; 712 | name_to_char('stuck_out_tongue') -> 16#1f61b; 713 | name_to_char('stuck_out_tongue_closed_eyes') -> 16#1f61d; 714 | name_to_char('stuck_out_tongue_winking_eye') -> 16#1f61c; 715 | name_to_char('sun_with_face') -> 16#1f31e; 716 | name_to_char('sunflower') -> 16#1f33b; 717 | name_to_char('sunglasses') -> 16#1f60e; 718 | name_to_char('sunny') -> 16#2600; 719 | name_to_char('sunrise') -> 16#1f305; 720 | name_to_char('sunrise_over_mountains') -> 16#1f304; 721 | name_to_char('surfer') -> 16#1f3c4; 722 | name_to_char('sushi') -> 16#1f363; 723 | name_to_char('suspension_railway') -> 16#1f69f; 724 | name_to_char('sweat') -> 16#1f613; 725 | name_to_char('sweat_drops') -> 16#1f4a6; 726 | name_to_char('sweat_smile') -> 16#1f605; 727 | name_to_char('sweet_potato') -> 16#1f360; 728 | name_to_char('swimmer') -> 16#1f3ca; 729 | name_to_char('symbols') -> 16#1f523; 730 | name_to_char('syringe') -> 16#1f489; 731 | name_to_char('tada') -> 16#1f389; 732 | name_to_char('tanabata_tree') -> 16#1f38b; 733 | name_to_char('tangerine') -> 16#1f34a; 734 | name_to_char('taurus') -> 16#2649; 735 | name_to_char('taxi') -> 16#1f695; 736 | name_to_char('tea') -> 16#1f375; 737 | name_to_char('telephone_receiver') -> 16#1f4de; 738 | name_to_char('telescope') -> 16#1f52d; 739 | name_to_char('tennis') -> 16#1f3be; 740 | name_to_char('tent') -> 16#26fa; 741 | name_to_char('thought_balloon') -> 16#1f4ad; 742 | name_to_char('three') -> 16#0033; 743 | name_to_char('ticket') -> 16#1f3ab; 744 | name_to_char('tiger') -> 16#1f42f; 745 | name_to_char('tiger2') -> 16#1f405; 746 | name_to_char('tired_face') -> 16#1f62b; 747 | name_to_char('tm') -> 16#2122; 748 | name_to_char('toilet') -> 16#1f6bd; 749 | name_to_char('tokyo_tower') -> 16#1f5fc; 750 | name_to_char('tomato') -> 16#1f345; 751 | name_to_char('tongue') -> 16#1f445; 752 | name_to_char('top') -> 16#1f51d; 753 | name_to_char('tophat') -> 16#1f3a9; 754 | name_to_char('tractor') -> 16#1f69c; 755 | name_to_char('traffic_light') -> 16#1f6a5; 756 | name_to_char('train') -> 16#1f68b; 757 | name_to_char('train2') -> 16#1f686; 758 | name_to_char('tram') -> 16#1f68a; 759 | name_to_char('triangular_flag_on_post') -> 16#1f6a9; 760 | name_to_char('triangular_ruler') -> 16#1f4d0; 761 | name_to_char('trident') -> 16#1f531; 762 | name_to_char('triumph') -> 16#1f624; 763 | name_to_char('trolleybus') -> 16#1f68e; 764 | name_to_char('trophy') -> 16#1f3c6; 765 | name_to_char('tropical_drink') -> 16#1f379; 766 | name_to_char('tropical_fish') -> 16#1f420; 767 | name_to_char('truck') -> 16#1f69a; 768 | name_to_char('trumpet') -> 16#1f3ba; 769 | name_to_char('tulip') -> 16#1f337; 770 | name_to_char('turtle') -> 16#1f422; 771 | name_to_char('tv') -> 16#1f4fa; 772 | name_to_char('twisted_rightwards_arrows') -> 16#1f500; 773 | name_to_char('two') -> 16#0032; 774 | name_to_char('two_hearts') -> 16#1f495; 775 | name_to_char('two_men_holding_hands') -> 16#1f46c; 776 | name_to_char('two_women_holding_hands') -> 16#1f46d; 777 | name_to_char('u5272') -> 16#1f239; 778 | name_to_char('u5408') -> 16#1f234; 779 | name_to_char('u55b6') -> 16#1f23a; 780 | name_to_char('u6307') -> 16#1f22f; 781 | name_to_char('u6708') -> 16#1f237; 782 | name_to_char('u6709') -> 16#1f236; 783 | name_to_char('u6e80') -> 16#1f235; 784 | name_to_char('u7121') -> 16#1f21a; 785 | name_to_char('u7533') -> 16#1f238; 786 | name_to_char('u7981') -> 16#1f232; 787 | name_to_char('u7a7a') -> 16#1f233; 788 | name_to_char('umbrella') -> 16#2614; 789 | name_to_char('unamused') -> 16#1f612; 790 | name_to_char('underage') -> 16#1f51e; 791 | name_to_char('unlock') -> 16#1f513; 792 | name_to_char('up') -> 16#1f199; 793 | name_to_char('v') -> 16#270c; 794 | name_to_char('vertical_traffic_light') -> 16#1f6a6; 795 | name_to_char('vhs') -> 16#1f4fc; 796 | name_to_char('vibration_mode') -> 16#1f4f3; 797 | name_to_char('video_camera') -> 16#1f4f9; 798 | name_to_char('video_game') -> 16#1f3ae; 799 | name_to_char('violin') -> 16#1f3bb; 800 | name_to_char('virgo') -> 16#264d; 801 | name_to_char('volcano') -> 16#1f30b; 802 | name_to_char('vs') -> 16#1f19a; 803 | name_to_char('walking') -> 16#1f6b6; 804 | name_to_char('waning_crescent_moon') -> 16#1f318; 805 | name_to_char('waning_gibbous_moon') -> 16#1f316; 806 | name_to_char('warning') -> 16#26a0; 807 | name_to_char('watch') -> 16#231a; 808 | name_to_char('water_buffalo') -> 16#1f403; 809 | name_to_char('watermelon') -> 16#1f349; 810 | name_to_char('wave') -> 16#1f44b; 811 | name_to_char('wavy_dash') -> 16#3030; 812 | name_to_char('waxing_crescent_moon') -> 16#1f312; 813 | name_to_char('wc') -> 16#1f6be; 814 | name_to_char('weary') -> 16#1f629; 815 | name_to_char('wedding') -> 16#1f492; 816 | name_to_char('whale') -> 16#1f433; 817 | name_to_char('whale2') -> 16#1f40b; 818 | name_to_char('wheelchair') -> 16#267f; 819 | name_to_char('white_check_mark') -> 16#2705; 820 | name_to_char('white_circle') -> 16#26aa; 821 | name_to_char('white_flower') -> 16#1f4ae; 822 | name_to_char('white_large_square') -> 16#2b1c; 823 | name_to_char('white_medium_small_square') -> 16#25fd; 824 | name_to_char('white_medium_square') -> 16#25fb; 825 | name_to_char('white_small_square') -> 16#25ab; 826 | name_to_char('white_square_button') -> 16#1f533; 827 | name_to_char('wind_chime') -> 16#1f390; 828 | name_to_char('wine_glass') -> 16#1f377; 829 | name_to_char('wink') -> 16#1f609; 830 | name_to_char('wolf') -> 16#1f43a; 831 | name_to_char('woman') -> 16#1f469; 832 | name_to_char('womans_clothes') -> 16#1f45a; 833 | name_to_char('womans_hat') -> 16#1f452; 834 | name_to_char('womens') -> 16#1f6ba; 835 | name_to_char('worried') -> 16#1f61f; 836 | name_to_char('wrench') -> 16#1f527; 837 | name_to_char('x') -> 16#274c; 838 | name_to_char('yellow_heart') -> 16#1f49b; 839 | name_to_char('yen') -> 16#1f4b4; 840 | name_to_char('yum') -> 16#1f60b; 841 | name_to_char('zap') -> 16#26a1; 842 | name_to_char('zero') -> 16#0030; 843 | name_to_char('zzz') -> 16#1f4a4; 844 | name_to_char(_) -> throw({pe4kin, invalid_emoji_name}). 845 | 846 | -spec char_to_name(char()) -> atom(). 847 | char_to_name(16#0023) -> 'hash'; 848 | char_to_name(16#0030) -> 'zero'; 849 | char_to_name(16#0031) -> 'one'; 850 | char_to_name(16#0032) -> 'two'; 851 | char_to_name(16#0033) -> 'three'; 852 | char_to_name(16#0034) -> 'four'; 853 | char_to_name(16#0035) -> 'five'; 854 | char_to_name(16#0036) -> 'six'; 855 | char_to_name(16#0037) -> 'seven'; 856 | char_to_name(16#0038) -> 'eight'; 857 | char_to_name(16#0039) -> 'nine'; 858 | char_to_name(16#00a9) -> 'copyright'; 859 | char_to_name(16#00ae) -> 'registered'; 860 | char_to_name(16#203c) -> 'bangbang'; 861 | char_to_name(16#2049) -> 'interrobang'; 862 | char_to_name(16#2122) -> 'tm'; 863 | char_to_name(16#2139) -> 'information_source'; 864 | char_to_name(16#2194) -> 'left_right_arrow'; 865 | char_to_name(16#2195) -> 'arrow_up_down'; 866 | char_to_name(16#2196) -> 'arrow_upper_left'; 867 | char_to_name(16#2197) -> 'arrow_upper_right'; 868 | char_to_name(16#2198) -> 'arrow_lower_right'; 869 | char_to_name(16#2199) -> 'arrow_lower_left'; 870 | char_to_name(16#21a9) -> 'leftwards_arrow_with_hook'; 871 | char_to_name(16#21aa) -> 'arrow_right_hook'; 872 | char_to_name(16#231a) -> 'watch'; 873 | char_to_name(16#231b) -> 'hourglass'; 874 | char_to_name(16#23e9) -> 'fast_forward'; 875 | char_to_name(16#23ea) -> 'rewind'; 876 | char_to_name(16#23eb) -> 'arrow_double_up'; 877 | char_to_name(16#23ec) -> 'arrow_double_down'; 878 | char_to_name(16#23f0) -> 'alarm_clock'; 879 | char_to_name(16#23f3) -> 'hourglass_flowing_sand'; 880 | char_to_name(16#24c2) -> 'm'; 881 | char_to_name(16#25aa) -> 'black_small_square'; 882 | char_to_name(16#25ab) -> 'white_small_square'; 883 | char_to_name(16#25b6) -> 'arrow_forward'; 884 | char_to_name(16#25c0) -> 'arrow_backward'; 885 | char_to_name(16#25fb) -> 'white_medium_square'; 886 | char_to_name(16#25fc) -> 'black_medium_square'; 887 | char_to_name(16#25fd) -> 'white_medium_small_square'; 888 | char_to_name(16#25fe) -> 'black_medium_small_square'; 889 | char_to_name(16#2600) -> 'sunny'; 890 | char_to_name(16#2601) -> 'cloud'; 891 | char_to_name(16#260e) -> 'phone'; 892 | char_to_name(16#2611) -> 'ballot_box_with_check'; 893 | char_to_name(16#2614) -> 'umbrella'; 894 | char_to_name(16#2615) -> 'coffee'; 895 | char_to_name(16#261d) -> 'point_up'; 896 | char_to_name(16#263a) -> 'relaxed'; 897 | char_to_name(16#2648) -> 'aries'; 898 | char_to_name(16#2649) -> 'taurus'; 899 | char_to_name(16#264a) -> 'gemini'; 900 | char_to_name(16#264b) -> 'cancer'; 901 | char_to_name(16#264c) -> 'leo'; 902 | char_to_name(16#264d) -> 'virgo'; 903 | char_to_name(16#264e) -> 'libra'; 904 | char_to_name(16#264f) -> 'scorpius'; 905 | char_to_name(16#2650) -> 'sagittarius'; 906 | char_to_name(16#2651) -> 'capricorn'; 907 | char_to_name(16#2652) -> 'aquarius'; 908 | char_to_name(16#2653) -> 'pisces'; 909 | char_to_name(16#2660) -> 'spades'; 910 | char_to_name(16#2663) -> 'clubs'; 911 | char_to_name(16#2665) -> 'hearts'; 912 | char_to_name(16#2666) -> 'diamonds'; 913 | char_to_name(16#2668) -> 'hotsprings'; 914 | char_to_name(16#267b) -> 'recycle'; 915 | char_to_name(16#267f) -> 'wheelchair'; 916 | char_to_name(16#2693) -> 'anchor'; 917 | char_to_name(16#26a0) -> 'warning'; 918 | char_to_name(16#26a1) -> 'zap'; 919 | char_to_name(16#26aa) -> 'white_circle'; 920 | char_to_name(16#26ab) -> 'black_circle'; 921 | char_to_name(16#26bd) -> 'soccer'; 922 | char_to_name(16#26be) -> 'baseball'; 923 | char_to_name(16#26c4) -> 'snowman'; 924 | char_to_name(16#26c5) -> 'partly_sunny'; 925 | char_to_name(16#26ce) -> 'ophiuchus'; 926 | char_to_name(16#26d4) -> 'no_entry'; 927 | char_to_name(16#26ea) -> 'church'; 928 | char_to_name(16#26f2) -> 'fountain'; 929 | char_to_name(16#26f3) -> 'golf'; 930 | char_to_name(16#26f5) -> 'boat'; 931 | char_to_name(16#26fa) -> 'tent'; 932 | char_to_name(16#26fd) -> 'fuelpump'; 933 | char_to_name(16#2702) -> 'scissors'; 934 | char_to_name(16#2705) -> 'white_check_mark'; 935 | char_to_name(16#2708) -> 'airplane'; 936 | char_to_name(16#2709) -> 'email'; 937 | char_to_name(16#270a) -> 'fist'; 938 | char_to_name(16#270b) -> 'hand'; 939 | char_to_name(16#270c) -> 'v'; 940 | char_to_name(16#270f) -> 'pencil2'; 941 | char_to_name(16#2712) -> 'black_nib'; 942 | char_to_name(16#2714) -> 'heavy_check_mark'; 943 | char_to_name(16#2716) -> 'heavy_multiplication_x'; 944 | char_to_name(16#2728) -> 'sparkles'; 945 | char_to_name(16#2733) -> 'eight_spoked_asterisk'; 946 | char_to_name(16#2734) -> 'eight_pointed_black_star'; 947 | char_to_name(16#2744) -> 'snowflake'; 948 | char_to_name(16#2747) -> 'sparkle'; 949 | char_to_name(16#274c) -> 'x'; 950 | char_to_name(16#274e) -> 'negative_squared_cross_mark'; 951 | char_to_name(16#2753) -> 'question'; 952 | char_to_name(16#2754) -> 'grey_question'; 953 | char_to_name(16#2755) -> 'grey_exclamation'; 954 | char_to_name(16#2757) -> 'exclamation'; 955 | char_to_name(16#2764) -> 'heart'; 956 | char_to_name(16#2795) -> 'heavy_plus_sign'; 957 | char_to_name(16#2796) -> 'heavy_minus_sign'; 958 | char_to_name(16#2797) -> 'heavy_division_sign'; 959 | char_to_name(16#27a1) -> 'arrow_right'; 960 | char_to_name(16#27b0) -> 'curly_loop'; 961 | char_to_name(16#27bf) -> 'loop'; 962 | char_to_name(16#2934) -> 'arrow_heading_up'; 963 | char_to_name(16#2935) -> 'arrow_heading_down'; 964 | char_to_name(16#2b05) -> 'arrow_left'; 965 | char_to_name(16#2b06) -> 'arrow_up'; 966 | char_to_name(16#2b07) -> 'arrow_down'; 967 | char_to_name(16#2b1b) -> 'black_large_square'; 968 | char_to_name(16#2b1c) -> 'white_large_square'; 969 | char_to_name(16#2b50) -> 'star'; 970 | char_to_name(16#2b55) -> 'o'; 971 | char_to_name(16#3030) -> 'wavy_dash'; 972 | char_to_name(16#303d) -> 'part_alternation_mark'; 973 | char_to_name(16#3297) -> 'congratulations'; 974 | char_to_name(16#3299) -> 'secret'; 975 | char_to_name(16#1f004) -> 'mahjong'; 976 | char_to_name(16#1f0cf) -> 'black_joker'; 977 | char_to_name(16#1f170) -> 'a'; 978 | char_to_name(16#1f171) -> 'b'; 979 | char_to_name(16#1f17e) -> 'o2'; 980 | char_to_name(16#1f17f) -> 'parking'; 981 | char_to_name(16#1f18e) -> 'ab'; 982 | char_to_name(16#1f191) -> 'cl'; 983 | char_to_name(16#1f192) -> 'cool'; 984 | char_to_name(16#1f193) -> 'free'; 985 | char_to_name(16#1f194) -> 'id'; 986 | char_to_name(16#1f195) -> 'new'; 987 | char_to_name(16#1f196) -> 'ng'; 988 | char_to_name(16#1f197) -> 'ok'; 989 | char_to_name(16#1f198) -> 'sos'; 990 | char_to_name(16#1f199) -> 'up'; 991 | char_to_name(16#1f19a) -> 'vs'; 992 | char_to_name(16#1f201) -> 'koko'; 993 | char_to_name(16#1f202) -> 'sa'; 994 | char_to_name(16#1f21a) -> 'u7121'; 995 | char_to_name(16#1f22f) -> 'u6307'; 996 | char_to_name(16#1f232) -> 'u7981'; 997 | char_to_name(16#1f233) -> 'u7a7a'; 998 | char_to_name(16#1f234) -> 'u5408'; 999 | char_to_name(16#1f235) -> 'u6e80'; 1000 | char_to_name(16#1f236) -> 'u6709'; 1001 | char_to_name(16#1f237) -> 'u6708'; 1002 | char_to_name(16#1f238) -> 'u7533'; 1003 | char_to_name(16#1f239) -> 'u5272'; 1004 | char_to_name(16#1f23a) -> 'u55b6'; 1005 | char_to_name(16#1f250) -> 'ideograph_advantage'; 1006 | char_to_name(16#1f251) -> 'accept'; 1007 | char_to_name(16#1f300) -> 'cyclone'; 1008 | char_to_name(16#1f301) -> 'foggy'; 1009 | char_to_name(16#1f302) -> 'closed_umbrella'; 1010 | char_to_name(16#1f303) -> 'night_with_stars'; 1011 | char_to_name(16#1f304) -> 'sunrise_over_mountains'; 1012 | char_to_name(16#1f305) -> 'sunrise'; 1013 | char_to_name(16#1f306) -> 'city_sunset'; 1014 | char_to_name(16#1f307) -> 'city_sunrise'; 1015 | char_to_name(16#1f308) -> 'rainbow'; 1016 | char_to_name(16#1f309) -> 'bridge_at_night'; 1017 | char_to_name(16#1f30a) -> 'ocean'; 1018 | char_to_name(16#1f30b) -> 'volcano'; 1019 | char_to_name(16#1f30c) -> 'milky_way'; 1020 | char_to_name(16#1f30d) -> 'earth_africa'; 1021 | char_to_name(16#1f30e) -> 'earth_americas'; 1022 | char_to_name(16#1f30f) -> 'earth_asia'; 1023 | char_to_name(16#1f310) -> 'globe_with_meridians'; 1024 | char_to_name(16#1f311) -> 'new_moon'; 1025 | char_to_name(16#1f312) -> 'waxing_crescent_moon'; 1026 | char_to_name(16#1f313) -> 'first_quarter_moon'; 1027 | char_to_name(16#1f314) -> 'moon'; 1028 | char_to_name(16#1f315) -> 'full_moon'; 1029 | char_to_name(16#1f316) -> 'waning_gibbous_moon'; 1030 | char_to_name(16#1f317) -> 'last_quarter_moon'; 1031 | char_to_name(16#1f318) -> 'waning_crescent_moon'; 1032 | char_to_name(16#1f319) -> 'crescent_moon'; 1033 | char_to_name(16#1f31a) -> 'new_moon_with_face'; 1034 | char_to_name(16#1f31b) -> 'first_quarter_moon_with_face'; 1035 | char_to_name(16#1f31c) -> 'last_quarter_moon_with_face'; 1036 | char_to_name(16#1f31d) -> 'full_moon_with_face'; 1037 | char_to_name(16#1f31e) -> 'sun_with_face'; 1038 | char_to_name(16#1f31f) -> 'star2'; 1039 | char_to_name(16#1f320) -> 'stars'; 1040 | char_to_name(16#1f330) -> 'chestnut'; 1041 | char_to_name(16#1f331) -> 'seedling'; 1042 | char_to_name(16#1f332) -> 'evergreen_tree'; 1043 | char_to_name(16#1f333) -> 'deciduous_tree'; 1044 | char_to_name(16#1f334) -> 'palm_tree'; 1045 | char_to_name(16#1f335) -> 'cactus'; 1046 | char_to_name(16#1f337) -> 'tulip'; 1047 | char_to_name(16#1f338) -> 'cherry_blossom'; 1048 | char_to_name(16#1f339) -> 'rose'; 1049 | char_to_name(16#1f33a) -> 'hibiscus'; 1050 | char_to_name(16#1f33b) -> 'sunflower'; 1051 | char_to_name(16#1f33c) -> 'blossom'; 1052 | char_to_name(16#1f33d) -> 'corn'; 1053 | char_to_name(16#1f33e) -> 'ear_of_rice'; 1054 | char_to_name(16#1f33f) -> 'herb'; 1055 | char_to_name(16#1f340) -> 'four_leaf_clover'; 1056 | char_to_name(16#1f341) -> 'maple_leaf'; 1057 | char_to_name(16#1f342) -> 'fallen_leaf'; 1058 | char_to_name(16#1f343) -> 'leaves'; 1059 | char_to_name(16#1f344) -> 'mushroom'; 1060 | char_to_name(16#1f345) -> 'tomato'; 1061 | char_to_name(16#1f346) -> 'eggplant'; 1062 | char_to_name(16#1f347) -> 'grapes'; 1063 | char_to_name(16#1f348) -> 'melon'; 1064 | char_to_name(16#1f349) -> 'watermelon'; 1065 | char_to_name(16#1f34a) -> 'tangerine'; 1066 | char_to_name(16#1f34b) -> 'lemon'; 1067 | char_to_name(16#1f34c) -> 'banana'; 1068 | char_to_name(16#1f34d) -> 'pineapple'; 1069 | char_to_name(16#1f34e) -> 'apple'; 1070 | char_to_name(16#1f34f) -> 'green_apple'; 1071 | char_to_name(16#1f350) -> 'pear'; 1072 | char_to_name(16#1f351) -> 'peach'; 1073 | char_to_name(16#1f352) -> 'cherries'; 1074 | char_to_name(16#1f353) -> 'strawberry'; 1075 | char_to_name(16#1f354) -> 'hamburger'; 1076 | char_to_name(16#1f355) -> 'pizza'; 1077 | char_to_name(16#1f356) -> 'meat_on_bone'; 1078 | char_to_name(16#1f357) -> 'poultry_leg'; 1079 | char_to_name(16#1f358) -> 'rice_cracker'; 1080 | char_to_name(16#1f359) -> 'rice_ball'; 1081 | char_to_name(16#1f35a) -> 'rice'; 1082 | char_to_name(16#1f35b) -> 'curry'; 1083 | char_to_name(16#1f35c) -> 'ramen'; 1084 | char_to_name(16#1f35d) -> 'spaghetti'; 1085 | char_to_name(16#1f35e) -> 'bread'; 1086 | char_to_name(16#1f35f) -> 'fries'; 1087 | char_to_name(16#1f360) -> 'sweet_potato'; 1088 | char_to_name(16#1f361) -> 'dango'; 1089 | char_to_name(16#1f362) -> 'oden'; 1090 | char_to_name(16#1f363) -> 'sushi'; 1091 | char_to_name(16#1f364) -> 'fried_shrimp'; 1092 | char_to_name(16#1f365) -> 'fish_cake'; 1093 | char_to_name(16#1f366) -> 'icecream'; 1094 | char_to_name(16#1f367) -> 'shaved_ice'; 1095 | char_to_name(16#1f368) -> 'ice_cream'; 1096 | char_to_name(16#1f369) -> 'doughnut'; 1097 | char_to_name(16#1f36a) -> 'cookie'; 1098 | char_to_name(16#1f36b) -> 'chocolate_bar'; 1099 | char_to_name(16#1f36c) -> 'candy'; 1100 | char_to_name(16#1f36d) -> 'lollipop'; 1101 | char_to_name(16#1f36e) -> 'custard'; 1102 | char_to_name(16#1f36f) -> 'honey_pot'; 1103 | char_to_name(16#1f370) -> 'cake'; 1104 | char_to_name(16#1f371) -> 'bento'; 1105 | char_to_name(16#1f372) -> 'stew'; 1106 | char_to_name(16#1f373) -> 'egg'; 1107 | char_to_name(16#1f374) -> 'fork_and_knife'; 1108 | char_to_name(16#1f375) -> 'tea'; 1109 | char_to_name(16#1f376) -> 'sake'; 1110 | char_to_name(16#1f377) -> 'wine_glass'; 1111 | char_to_name(16#1f378) -> 'cocktail'; 1112 | char_to_name(16#1f379) -> 'tropical_drink'; 1113 | char_to_name(16#1f37a) -> 'beer'; 1114 | char_to_name(16#1f37b) -> 'beers'; 1115 | char_to_name(16#1f37c) -> 'baby_bottle'; 1116 | char_to_name(16#1f380) -> 'ribbon'; 1117 | char_to_name(16#1f381) -> 'gift'; 1118 | char_to_name(16#1f382) -> 'birthday'; 1119 | char_to_name(16#1f383) -> 'jack_o_lantern'; 1120 | char_to_name(16#1f384) -> 'christmas_tree'; 1121 | char_to_name(16#1f385) -> 'santa'; 1122 | char_to_name(16#1f386) -> 'fireworks'; 1123 | char_to_name(16#1f387) -> 'sparkler'; 1124 | char_to_name(16#1f388) -> 'balloon'; 1125 | char_to_name(16#1f389) -> 'tada'; 1126 | char_to_name(16#1f38a) -> 'confetti_ball'; 1127 | char_to_name(16#1f38b) -> 'tanabata_tree'; 1128 | char_to_name(16#1f38c) -> 'crossed_flags'; 1129 | char_to_name(16#1f38d) -> 'bamboo'; 1130 | char_to_name(16#1f38e) -> 'dolls'; 1131 | char_to_name(16#1f38f) -> 'flags'; 1132 | char_to_name(16#1f390) -> 'wind_chime'; 1133 | char_to_name(16#1f391) -> 'rice_scene'; 1134 | char_to_name(16#1f392) -> 'school_satchel'; 1135 | char_to_name(16#1f393) -> 'mortar_board'; 1136 | char_to_name(16#1f3a0) -> 'carousel_horse'; 1137 | char_to_name(16#1f3a1) -> 'ferris_wheel'; 1138 | char_to_name(16#1f3a2) -> 'roller_coaster'; 1139 | char_to_name(16#1f3a3) -> 'fishing_pole_and_fish'; 1140 | char_to_name(16#1f3a4) -> 'microphone'; 1141 | char_to_name(16#1f3a5) -> 'movie_camera'; 1142 | char_to_name(16#1f3a6) -> 'cinema'; 1143 | char_to_name(16#1f3a7) -> 'headphones'; 1144 | char_to_name(16#1f3a8) -> 'art'; 1145 | char_to_name(16#1f3a9) -> 'tophat'; 1146 | char_to_name(16#1f3aa) -> 'circus_tent'; 1147 | char_to_name(16#1f3ab) -> 'ticket'; 1148 | char_to_name(16#1f3ac) -> 'clapper'; 1149 | char_to_name(16#1f3ad) -> 'performing_arts'; 1150 | char_to_name(16#1f3ae) -> 'video_game'; 1151 | char_to_name(16#1f3af) -> 'dart'; 1152 | char_to_name(16#1f3b0) -> 'slot_machine'; 1153 | char_to_name(16#1f3b1) -> '8ball'; 1154 | char_to_name(16#1f3b2) -> 'game_die'; 1155 | char_to_name(16#1f3b3) -> 'bowling'; 1156 | char_to_name(16#1f3b4) -> 'flower_playing_cards'; 1157 | char_to_name(16#1f3b5) -> 'musical_note'; 1158 | char_to_name(16#1f3b6) -> 'notes'; 1159 | char_to_name(16#1f3b7) -> 'saxophone'; 1160 | char_to_name(16#1f3b8) -> 'guitar'; 1161 | char_to_name(16#1f3b9) -> 'musical_keyboard'; 1162 | char_to_name(16#1f3ba) -> 'trumpet'; 1163 | char_to_name(16#1f3bb) -> 'violin'; 1164 | char_to_name(16#1f3bc) -> 'musical_score'; 1165 | char_to_name(16#1f3bd) -> 'running_shirt_with_sash'; 1166 | char_to_name(16#1f3be) -> 'tennis'; 1167 | char_to_name(16#1f3bf) -> 'ski'; 1168 | char_to_name(16#1f3c0) -> 'basketball'; 1169 | char_to_name(16#1f3c1) -> 'checkered_flag'; 1170 | char_to_name(16#1f3c2) -> 'snowboarder'; 1171 | char_to_name(16#1f3c3) -> 'runner'; 1172 | char_to_name(16#1f3c4) -> 'surfer'; 1173 | char_to_name(16#1f3c6) -> 'trophy'; 1174 | char_to_name(16#1f3c7) -> 'horse_racing'; 1175 | char_to_name(16#1f3c8) -> 'football'; 1176 | char_to_name(16#1f3c9) -> 'rugby_football'; 1177 | char_to_name(16#1f3ca) -> 'swimmer'; 1178 | char_to_name(16#1f3e0) -> 'house'; 1179 | char_to_name(16#1f3e1) -> 'house_with_garden'; 1180 | char_to_name(16#1f3e2) -> 'office'; 1181 | char_to_name(16#1f3e3) -> 'post_office'; 1182 | char_to_name(16#1f3e4) -> 'european_post_office'; 1183 | char_to_name(16#1f3e5) -> 'hospital'; 1184 | char_to_name(16#1f3e6) -> 'bank'; 1185 | char_to_name(16#1f3e7) -> 'atm'; 1186 | char_to_name(16#1f3e8) -> 'hotel'; 1187 | char_to_name(16#1f3e9) -> 'love_hotel'; 1188 | char_to_name(16#1f3ea) -> 'convenience_store'; 1189 | char_to_name(16#1f3eb) -> 'school'; 1190 | char_to_name(16#1f3ec) -> 'department_store'; 1191 | char_to_name(16#1f3ed) -> 'factory'; 1192 | char_to_name(16#1f3ee) -> 'izakaya_lantern'; 1193 | char_to_name(16#1f3ef) -> 'japanese_castle'; 1194 | char_to_name(16#1f3f0) -> 'european_castle'; 1195 | char_to_name(16#1f400) -> 'rat'; 1196 | char_to_name(16#1f401) -> 'mouse2'; 1197 | char_to_name(16#1f402) -> 'ox'; 1198 | char_to_name(16#1f403) -> 'water_buffalo'; 1199 | char_to_name(16#1f404) -> 'cow2'; 1200 | char_to_name(16#1f405) -> 'tiger2'; 1201 | char_to_name(16#1f406) -> 'leopard'; 1202 | char_to_name(16#1f407) -> 'rabbit2'; 1203 | char_to_name(16#1f408) -> 'cat2'; 1204 | char_to_name(16#1f409) -> 'dragon'; 1205 | char_to_name(16#1f40a) -> 'crocodile'; 1206 | char_to_name(16#1f40b) -> 'whale2'; 1207 | char_to_name(16#1f40c) -> 'snail'; 1208 | char_to_name(16#1f40d) -> 'snake'; 1209 | char_to_name(16#1f40e) -> 'racehorse'; 1210 | char_to_name(16#1f40f) -> 'ram'; 1211 | char_to_name(16#1f410) -> 'goat'; 1212 | char_to_name(16#1f411) -> 'sheep'; 1213 | char_to_name(16#1f412) -> 'monkey'; 1214 | char_to_name(16#1f413) -> 'rooster'; 1215 | char_to_name(16#1f414) -> 'chicken'; 1216 | char_to_name(16#1f415) -> 'dog2'; 1217 | char_to_name(16#1f416) -> 'pig2'; 1218 | char_to_name(16#1f417) -> 'boar'; 1219 | char_to_name(16#1f418) -> 'elephant'; 1220 | char_to_name(16#1f419) -> 'octopus'; 1221 | char_to_name(16#1f41a) -> 'shell'; 1222 | char_to_name(16#1f41b) -> 'bug'; 1223 | char_to_name(16#1f41c) -> 'ant'; 1224 | char_to_name(16#1f41d) -> 'bee'; 1225 | char_to_name(16#1f41e) -> 'beetle'; 1226 | char_to_name(16#1f41f) -> 'fish'; 1227 | char_to_name(16#1f420) -> 'tropical_fish'; 1228 | char_to_name(16#1f421) -> 'blowfish'; 1229 | char_to_name(16#1f422) -> 'turtle'; 1230 | char_to_name(16#1f423) -> 'hatching_chick'; 1231 | char_to_name(16#1f424) -> 'baby_chick'; 1232 | char_to_name(16#1f425) -> 'hatched_chick'; 1233 | char_to_name(16#1f426) -> 'bird'; 1234 | char_to_name(16#1f427) -> 'penguin'; 1235 | char_to_name(16#1f428) -> 'koala'; 1236 | char_to_name(16#1f429) -> 'poodle'; 1237 | char_to_name(16#1f42a) -> 'dromedary_camel'; 1238 | char_to_name(16#1f42b) -> 'camel'; 1239 | char_to_name(16#1f42c) -> 'dolphin'; 1240 | char_to_name(16#1f42d) -> 'mouse'; 1241 | char_to_name(16#1f42e) -> 'cow'; 1242 | char_to_name(16#1f42f) -> 'tiger'; 1243 | char_to_name(16#1f430) -> 'rabbit'; 1244 | char_to_name(16#1f431) -> 'cat'; 1245 | char_to_name(16#1f432) -> 'dragon_face'; 1246 | char_to_name(16#1f433) -> 'whale'; 1247 | char_to_name(16#1f434) -> 'horse'; 1248 | char_to_name(16#1f435) -> 'monkey_face'; 1249 | char_to_name(16#1f436) -> 'dog'; 1250 | char_to_name(16#1f437) -> 'pig'; 1251 | char_to_name(16#1f438) -> 'frog'; 1252 | char_to_name(16#1f439) -> 'hamster'; 1253 | char_to_name(16#1f43a) -> 'wolf'; 1254 | char_to_name(16#1f43b) -> 'bear'; 1255 | char_to_name(16#1f43c) -> 'panda_face'; 1256 | char_to_name(16#1f43d) -> 'pig_nose'; 1257 | char_to_name(16#1f43e) -> 'feet'; 1258 | char_to_name(16#1f440) -> 'eyes'; 1259 | char_to_name(16#1f442) -> 'ear'; 1260 | char_to_name(16#1f443) -> 'nose'; 1261 | char_to_name(16#1f444) -> 'lips'; 1262 | char_to_name(16#1f445) -> 'tongue'; 1263 | char_to_name(16#1f446) -> 'point_up_2'; 1264 | char_to_name(16#1f447) -> 'point_down'; 1265 | char_to_name(16#1f448) -> 'point_left'; 1266 | char_to_name(16#1f449) -> 'point_right'; 1267 | char_to_name(16#1f44a) -> 'facepunch'; 1268 | char_to_name(16#1f44b) -> 'wave'; 1269 | char_to_name(16#1f44c) -> 'ok_hand'; 1270 | char_to_name(16#1f44d) -> '+1'; 1271 | char_to_name(16#1f44e) -> '-1'; 1272 | char_to_name(16#1f44f) -> 'clap'; 1273 | char_to_name(16#1f450) -> 'open_hands'; 1274 | char_to_name(16#1f451) -> 'crown'; 1275 | char_to_name(16#1f452) -> 'womans_hat'; 1276 | char_to_name(16#1f453) -> 'eyeglasses'; 1277 | char_to_name(16#1f454) -> 'necktie'; 1278 | char_to_name(16#1f455) -> 'shirt'; 1279 | char_to_name(16#1f456) -> 'jeans'; 1280 | char_to_name(16#1f457) -> 'dress'; 1281 | char_to_name(16#1f458) -> 'kimono'; 1282 | char_to_name(16#1f459) -> 'bikini'; 1283 | char_to_name(16#1f45a) -> 'womans_clothes'; 1284 | char_to_name(16#1f45b) -> 'purse'; 1285 | char_to_name(16#1f45c) -> 'handbag'; 1286 | char_to_name(16#1f45d) -> 'pouch'; 1287 | char_to_name(16#1f45e) -> 'mans_shoe'; 1288 | char_to_name(16#1f45f) -> 'athletic_shoe'; 1289 | char_to_name(16#1f460) -> 'high_heel'; 1290 | char_to_name(16#1f461) -> 'sandal'; 1291 | char_to_name(16#1f462) -> 'boot'; 1292 | char_to_name(16#1f463) -> 'footprints'; 1293 | char_to_name(16#1f464) -> 'bust_in_silhouette'; 1294 | char_to_name(16#1f465) -> 'busts_in_silhouette'; 1295 | char_to_name(16#1f466) -> 'boy'; 1296 | char_to_name(16#1f467) -> 'girl'; 1297 | char_to_name(16#1f468) -> 'man'; 1298 | char_to_name(16#1f469) -> 'woman'; 1299 | char_to_name(16#1f46a) -> 'family'; 1300 | char_to_name(16#1f46b) -> 'couple'; 1301 | char_to_name(16#1f46c) -> 'two_men_holding_hands'; 1302 | char_to_name(16#1f46d) -> 'two_women_holding_hands'; 1303 | char_to_name(16#1f46e) -> 'cop'; 1304 | char_to_name(16#1f46f) -> 'dancers'; 1305 | char_to_name(16#1f470) -> 'bride_with_veil'; 1306 | char_to_name(16#1f471) -> 'person_with_blond_hair'; 1307 | char_to_name(16#1f472) -> 'man_with_gua_pi_mao'; 1308 | char_to_name(16#1f473) -> 'man_with_turban'; 1309 | char_to_name(16#1f474) -> 'older_man'; 1310 | char_to_name(16#1f475) -> 'older_woman'; 1311 | char_to_name(16#1f476) -> 'baby'; 1312 | char_to_name(16#1f477) -> 'construction_worker'; 1313 | char_to_name(16#1f478) -> 'princess'; 1314 | char_to_name(16#1f479) -> 'japanese_ogre'; 1315 | char_to_name(16#1f47a) -> 'japanese_goblin'; 1316 | char_to_name(16#1f47b) -> 'ghost'; 1317 | char_to_name(16#1f47c) -> 'angel'; 1318 | char_to_name(16#1f47d) -> 'alien'; 1319 | char_to_name(16#1f47e) -> 'space_invader'; 1320 | char_to_name(16#1f47f) -> 'imp'; 1321 | char_to_name(16#1f480) -> 'skull'; 1322 | char_to_name(16#1f481) -> 'information_desk_person'; 1323 | char_to_name(16#1f482) -> 'guardsman'; 1324 | char_to_name(16#1f483) -> 'dancer'; 1325 | char_to_name(16#1f484) -> 'lipstick'; 1326 | char_to_name(16#1f485) -> 'nail_care'; 1327 | char_to_name(16#1f486) -> 'massage'; 1328 | char_to_name(16#1f487) -> 'haircut'; 1329 | char_to_name(16#1f488) -> 'barber'; 1330 | char_to_name(16#1f489) -> 'syringe'; 1331 | char_to_name(16#1f48a) -> 'pill'; 1332 | char_to_name(16#1f48b) -> 'kiss'; 1333 | char_to_name(16#1f48c) -> 'love_letter'; 1334 | char_to_name(16#1f48d) -> 'ring'; 1335 | char_to_name(16#1f48e) -> 'gem'; 1336 | char_to_name(16#1f48f) -> 'couplekiss'; 1337 | char_to_name(16#1f490) -> 'bouquet'; 1338 | char_to_name(16#1f491) -> 'couple_with_heart'; 1339 | char_to_name(16#1f492) -> 'wedding'; 1340 | char_to_name(16#1f493) -> 'heartbeat'; 1341 | char_to_name(16#1f494) -> 'broken_heart'; 1342 | char_to_name(16#1f495) -> 'two_hearts'; 1343 | char_to_name(16#1f496) -> 'sparkling_heart'; 1344 | char_to_name(16#1f497) -> 'heartpulse'; 1345 | char_to_name(16#1f498) -> 'cupid'; 1346 | char_to_name(16#1f499) -> 'blue_heart'; 1347 | char_to_name(16#1f49a) -> 'green_heart'; 1348 | char_to_name(16#1f49b) -> 'yellow_heart'; 1349 | char_to_name(16#1f49c) -> 'purple_heart'; 1350 | char_to_name(16#1f49d) -> 'gift_heart'; 1351 | char_to_name(16#1f49e) -> 'revolving_hearts'; 1352 | char_to_name(16#1f49f) -> 'heart_decoration'; 1353 | char_to_name(16#1f4a0) -> 'diamond_shape_with_a_dot_inside'; 1354 | char_to_name(16#1f4a1) -> 'bulb'; 1355 | char_to_name(16#1f4a2) -> 'anger'; 1356 | char_to_name(16#1f4a3) -> 'bomb'; 1357 | char_to_name(16#1f4a4) -> 'zzz'; 1358 | char_to_name(16#1f4a5) -> 'boom'; 1359 | char_to_name(16#1f4a6) -> 'sweat_drops'; 1360 | char_to_name(16#1f4a7) -> 'droplet'; 1361 | char_to_name(16#1f4a8) -> 'dash'; 1362 | char_to_name(16#1f4a9) -> 'hankey'; 1363 | char_to_name(16#1f4aa) -> 'muscle'; 1364 | char_to_name(16#1f4ab) -> 'dizzy'; 1365 | char_to_name(16#1f4ac) -> 'speech_balloon'; 1366 | char_to_name(16#1f4ad) -> 'thought_balloon'; 1367 | char_to_name(16#1f4ae) -> 'white_flower'; 1368 | char_to_name(16#1f4af) -> '100'; 1369 | char_to_name(16#1f4b0) -> 'moneybag'; 1370 | char_to_name(16#1f4b1) -> 'currency_exchange'; 1371 | char_to_name(16#1f4b2) -> 'heavy_dollar_sign'; 1372 | char_to_name(16#1f4b3) -> 'credit_card'; 1373 | char_to_name(16#1f4b4) -> 'yen'; 1374 | char_to_name(16#1f4b5) -> 'dollar'; 1375 | char_to_name(16#1f4b6) -> 'euro'; 1376 | char_to_name(16#1f4b7) -> 'pound'; 1377 | char_to_name(16#1f4b8) -> 'money_with_wings'; 1378 | char_to_name(16#1f4b9) -> 'chart'; 1379 | char_to_name(16#1f4ba) -> 'seat'; 1380 | char_to_name(16#1f4bb) -> 'computer'; 1381 | char_to_name(16#1f4bc) -> 'briefcase'; 1382 | char_to_name(16#1f4bd) -> 'minidisc'; 1383 | char_to_name(16#1f4be) -> 'floppy_disk'; 1384 | char_to_name(16#1f4bf) -> 'cd'; 1385 | char_to_name(16#1f4c0) -> 'dvd'; 1386 | char_to_name(16#1f4c1) -> 'file_folder'; 1387 | char_to_name(16#1f4c2) -> 'open_file_folder'; 1388 | char_to_name(16#1f4c3) -> 'page_with_curl'; 1389 | char_to_name(16#1f4c4) -> 'page_facing_up'; 1390 | char_to_name(16#1f4c5) -> 'date'; 1391 | char_to_name(16#1f4c6) -> 'calendar'; 1392 | char_to_name(16#1f4c7) -> 'card_index'; 1393 | char_to_name(16#1f4c8) -> 'chart_with_upwards_trend'; 1394 | char_to_name(16#1f4c9) -> 'chart_with_downwards_trend'; 1395 | char_to_name(16#1f4ca) -> 'bar_chart'; 1396 | char_to_name(16#1f4cb) -> 'clipboard'; 1397 | char_to_name(16#1f4cc) -> 'pushpin'; 1398 | char_to_name(16#1f4cd) -> 'round_pushpin'; 1399 | char_to_name(16#1f4ce) -> 'paperclip'; 1400 | char_to_name(16#1f4cf) -> 'straight_ruler'; 1401 | char_to_name(16#1f4d0) -> 'triangular_ruler'; 1402 | char_to_name(16#1f4d1) -> 'bookmark_tabs'; 1403 | char_to_name(16#1f4d2) -> 'ledger'; 1404 | char_to_name(16#1f4d3) -> 'notebook'; 1405 | char_to_name(16#1f4d4) -> 'notebook_with_decorative_cover'; 1406 | char_to_name(16#1f4d5) -> 'closed_book'; 1407 | char_to_name(16#1f4d6) -> 'book'; 1408 | char_to_name(16#1f4d7) -> 'green_book'; 1409 | char_to_name(16#1f4d8) -> 'blue_book'; 1410 | char_to_name(16#1f4d9) -> 'orange_book'; 1411 | char_to_name(16#1f4da) -> 'books'; 1412 | char_to_name(16#1f4db) -> 'name_badge'; 1413 | char_to_name(16#1f4dc) -> 'scroll'; 1414 | char_to_name(16#1f4dd) -> 'memo'; 1415 | char_to_name(16#1f4de) -> 'telephone_receiver'; 1416 | char_to_name(16#1f4df) -> 'pager'; 1417 | char_to_name(16#1f4e0) -> 'fax'; 1418 | char_to_name(16#1f4e1) -> 'satellite'; 1419 | char_to_name(16#1f4e2) -> 'loudspeaker'; 1420 | char_to_name(16#1f4e3) -> 'mega'; 1421 | char_to_name(16#1f4e4) -> 'outbox_tray'; 1422 | char_to_name(16#1f4e5) -> 'inbox_tray'; 1423 | char_to_name(16#1f4e6) -> 'package'; 1424 | char_to_name(16#1f4e7) -> 'e-mail'; 1425 | char_to_name(16#1f4e8) -> 'incoming_envelope'; 1426 | char_to_name(16#1f4e9) -> 'envelope_with_arrow'; 1427 | char_to_name(16#1f4ea) -> 'mailbox_closed'; 1428 | char_to_name(16#1f4eb) -> 'mailbox'; 1429 | char_to_name(16#1f4ec) -> 'mailbox_with_mail'; 1430 | char_to_name(16#1f4ed) -> 'mailbox_with_no_mail'; 1431 | char_to_name(16#1f4ee) -> 'postbox'; 1432 | char_to_name(16#1f4ef) -> 'postal_horn'; 1433 | char_to_name(16#1f4f0) -> 'newspaper'; 1434 | char_to_name(16#1f4f1) -> 'iphone'; 1435 | char_to_name(16#1f4f2) -> 'calling'; 1436 | char_to_name(16#1f4f3) -> 'vibration_mode'; 1437 | char_to_name(16#1f4f4) -> 'mobile_phone_off'; 1438 | char_to_name(16#1f4f5) -> 'no_mobile_phones'; 1439 | char_to_name(16#1f4f6) -> 'signal_strength'; 1440 | char_to_name(16#1f4f7) -> 'camera'; 1441 | char_to_name(16#1f4f9) -> 'video_camera'; 1442 | char_to_name(16#1f4fa) -> 'tv'; 1443 | char_to_name(16#1f4fb) -> 'radio'; 1444 | char_to_name(16#1f4fc) -> 'vhs'; 1445 | char_to_name(16#1f500) -> 'twisted_rightwards_arrows'; 1446 | char_to_name(16#1f501) -> 'repeat'; 1447 | char_to_name(16#1f502) -> 'repeat_one'; 1448 | char_to_name(16#1f503) -> 'arrows_clockwise'; 1449 | char_to_name(16#1f504) -> 'arrows_counterclockwise'; 1450 | char_to_name(16#1f505) -> 'low_brightness'; 1451 | char_to_name(16#1f506) -> 'high_brightness'; 1452 | char_to_name(16#1f507) -> 'mute'; 1453 | char_to_name(16#1f508) -> 'speaker'; 1454 | char_to_name(16#1f509) -> 'sound'; 1455 | char_to_name(16#1f50a) -> 'loud_sound'; 1456 | char_to_name(16#1f50b) -> 'battery'; 1457 | char_to_name(16#1f50c) -> 'electric_plug'; 1458 | char_to_name(16#1f50d) -> 'mag'; 1459 | char_to_name(16#1f50e) -> 'mag_right'; 1460 | char_to_name(16#1f50f) -> 'lock_with_ink_pen'; 1461 | char_to_name(16#1f510) -> 'closed_lock_with_key'; 1462 | char_to_name(16#1f511) -> 'key'; 1463 | char_to_name(16#1f512) -> 'lock'; 1464 | char_to_name(16#1f513) -> 'unlock'; 1465 | char_to_name(16#1f514) -> 'bell'; 1466 | char_to_name(16#1f515) -> 'no_bell'; 1467 | char_to_name(16#1f516) -> 'bookmark'; 1468 | char_to_name(16#1f517) -> 'link'; 1469 | char_to_name(16#1f518) -> 'radio_button'; 1470 | char_to_name(16#1f519) -> 'back'; 1471 | char_to_name(16#1f51a) -> 'end'; 1472 | char_to_name(16#1f51b) -> 'on'; 1473 | char_to_name(16#1f51c) -> 'soon'; 1474 | char_to_name(16#1f51d) -> 'top'; 1475 | char_to_name(16#1f51e) -> 'underage'; 1476 | char_to_name(16#1f51f) -> 'keycap_ten'; 1477 | char_to_name(16#1f520) -> 'capital_abcd'; 1478 | char_to_name(16#1f521) -> 'abcd'; 1479 | char_to_name(16#1f522) -> '1234'; 1480 | char_to_name(16#1f523) -> 'symbols'; 1481 | char_to_name(16#1f524) -> 'abc'; 1482 | char_to_name(16#1f525) -> 'fire'; 1483 | char_to_name(16#1f526) -> 'flashlight'; 1484 | char_to_name(16#1f527) -> 'wrench'; 1485 | char_to_name(16#1f528) -> 'hammer'; 1486 | char_to_name(16#1f529) -> 'nut_and_bolt'; 1487 | char_to_name(16#1f52a) -> 'hocho'; 1488 | char_to_name(16#1f52b) -> 'gun'; 1489 | char_to_name(16#1f52c) -> 'microscope'; 1490 | char_to_name(16#1f52d) -> 'telescope'; 1491 | char_to_name(16#1f52e) -> 'crystal_ball'; 1492 | char_to_name(16#1f52f) -> 'six_pointed_star'; 1493 | char_to_name(16#1f530) -> 'beginner'; 1494 | char_to_name(16#1f531) -> 'trident'; 1495 | char_to_name(16#1f532) -> 'black_square_button'; 1496 | char_to_name(16#1f533) -> 'white_square_button'; 1497 | char_to_name(16#1f534) -> 'red_circle'; 1498 | char_to_name(16#1f535) -> 'large_blue_circle'; 1499 | char_to_name(16#1f536) -> 'large_orange_diamond'; 1500 | char_to_name(16#1f537) -> 'large_blue_diamond'; 1501 | char_to_name(16#1f538) -> 'small_orange_diamond'; 1502 | char_to_name(16#1f539) -> 'small_blue_diamond'; 1503 | char_to_name(16#1f53a) -> 'small_red_triangle'; 1504 | char_to_name(16#1f53b) -> 'small_red_triangle_down'; 1505 | char_to_name(16#1f53c) -> 'arrow_up_small'; 1506 | char_to_name(16#1f53d) -> 'arrow_down_small'; 1507 | char_to_name(16#1f550) -> 'clock1'; 1508 | char_to_name(16#1f551) -> 'clock2'; 1509 | char_to_name(16#1f552) -> 'clock3'; 1510 | char_to_name(16#1f553) -> 'clock4'; 1511 | char_to_name(16#1f554) -> 'clock5'; 1512 | char_to_name(16#1f555) -> 'clock6'; 1513 | char_to_name(16#1f556) -> 'clock7'; 1514 | char_to_name(16#1f557) -> 'clock8'; 1515 | char_to_name(16#1f558) -> 'clock9'; 1516 | char_to_name(16#1f559) -> 'clock10'; 1517 | char_to_name(16#1f55a) -> 'clock11'; 1518 | char_to_name(16#1f55b) -> 'clock12'; 1519 | char_to_name(16#1f55c) -> 'clock130'; 1520 | char_to_name(16#1f55d) -> 'clock230'; 1521 | char_to_name(16#1f55e) -> 'clock330'; 1522 | char_to_name(16#1f55f) -> 'clock430'; 1523 | char_to_name(16#1f560) -> 'clock530'; 1524 | char_to_name(16#1f561) -> 'clock630'; 1525 | char_to_name(16#1f562) -> 'clock730'; 1526 | char_to_name(16#1f563) -> 'clock830'; 1527 | char_to_name(16#1f564) -> 'clock930'; 1528 | char_to_name(16#1f565) -> 'clock1030'; 1529 | char_to_name(16#1f566) -> 'clock1130'; 1530 | char_to_name(16#1f567) -> 'clock1230'; 1531 | char_to_name(16#1f5fb) -> 'mount_fuji'; 1532 | char_to_name(16#1f5fc) -> 'tokyo_tower'; 1533 | char_to_name(16#1f5fd) -> 'statue_of_liberty'; 1534 | char_to_name(16#1f5fe) -> 'japan'; 1535 | char_to_name(16#1f5ff) -> 'moyai'; 1536 | char_to_name(16#1f600) -> 'grinning'; 1537 | char_to_name(16#1f601) -> 'grin'; 1538 | char_to_name(16#1f602) -> 'joy'; 1539 | char_to_name(16#1f603) -> 'smiley'; 1540 | char_to_name(16#1f604) -> 'smile'; 1541 | char_to_name(16#1f605) -> 'sweat_smile'; 1542 | char_to_name(16#1f606) -> 'laughing'; 1543 | char_to_name(16#1f607) -> 'innocent'; 1544 | char_to_name(16#1f608) -> 'smiling_imp'; 1545 | char_to_name(16#1f609) -> 'wink'; 1546 | char_to_name(16#1f60a) -> 'blush'; 1547 | char_to_name(16#1f60b) -> 'yum'; 1548 | char_to_name(16#1f60c) -> 'relieved'; 1549 | char_to_name(16#1f60d) -> 'heart_eyes'; 1550 | char_to_name(16#1f60e) -> 'sunglasses'; 1551 | char_to_name(16#1f60f) -> 'smirk'; 1552 | char_to_name(16#1f610) -> 'neutral_face'; 1553 | char_to_name(16#1f611) -> 'expressionless'; 1554 | char_to_name(16#1f612) -> 'unamused'; 1555 | char_to_name(16#1f613) -> 'sweat'; 1556 | char_to_name(16#1f614) -> 'pensive'; 1557 | char_to_name(16#1f615) -> 'confused'; 1558 | char_to_name(16#1f616) -> 'confounded'; 1559 | char_to_name(16#1f617) -> 'kissing'; 1560 | char_to_name(16#1f618) -> 'kissing_heart'; 1561 | char_to_name(16#1f619) -> 'kissing_smiling_eyes'; 1562 | char_to_name(16#1f61a) -> 'kissing_closed_eyes'; 1563 | char_to_name(16#1f61b) -> 'stuck_out_tongue'; 1564 | char_to_name(16#1f61c) -> 'stuck_out_tongue_winking_eye'; 1565 | char_to_name(16#1f61d) -> 'stuck_out_tongue_closed_eyes'; 1566 | char_to_name(16#1f61e) -> 'disappointed'; 1567 | char_to_name(16#1f61f) -> 'worried'; 1568 | char_to_name(16#1f620) -> 'angry'; 1569 | char_to_name(16#1f621) -> 'rage'; 1570 | char_to_name(16#1f622) -> 'cry'; 1571 | char_to_name(16#1f623) -> 'persevere'; 1572 | char_to_name(16#1f624) -> 'triumph'; 1573 | char_to_name(16#1f625) -> 'disappointed_relieved'; 1574 | char_to_name(16#1f626) -> 'frowning'; 1575 | char_to_name(16#1f627) -> 'anguished'; 1576 | char_to_name(16#1f628) -> 'fearful'; 1577 | char_to_name(16#1f629) -> 'weary'; 1578 | char_to_name(16#1f62a) -> 'sleepy'; 1579 | char_to_name(16#1f62b) -> 'tired_face'; 1580 | char_to_name(16#1f62c) -> 'grimacing'; 1581 | char_to_name(16#1f62d) -> 'sob'; 1582 | char_to_name(16#1f62e) -> 'open_mouth'; 1583 | char_to_name(16#1f62f) -> 'hushed'; 1584 | char_to_name(16#1f630) -> 'cold_sweat'; 1585 | char_to_name(16#1f631) -> 'scream'; 1586 | char_to_name(16#1f632) -> 'astonished'; 1587 | char_to_name(16#1f633) -> 'flushed'; 1588 | char_to_name(16#1f634) -> 'sleeping'; 1589 | char_to_name(16#1f635) -> 'dizzy_face'; 1590 | char_to_name(16#1f636) -> 'no_mouth'; 1591 | char_to_name(16#1f637) -> 'mask'; 1592 | char_to_name(16#1f638) -> 'smile_cat'; 1593 | char_to_name(16#1f639) -> 'joy_cat'; 1594 | char_to_name(16#1f63a) -> 'smiley_cat'; 1595 | char_to_name(16#1f63b) -> 'heart_eyes_cat'; 1596 | char_to_name(16#1f63c) -> 'smirk_cat'; 1597 | char_to_name(16#1f63d) -> 'kissing_cat'; 1598 | char_to_name(16#1f63e) -> 'pouting_cat'; 1599 | char_to_name(16#1f63f) -> 'crying_cat_face'; 1600 | char_to_name(16#1f640) -> 'scream_cat'; 1601 | char_to_name(16#1f645) -> 'no_good'; 1602 | char_to_name(16#1f646) -> 'ok_woman'; 1603 | char_to_name(16#1f647) -> 'bow'; 1604 | char_to_name(16#1f648) -> 'see_no_evil'; 1605 | char_to_name(16#1f649) -> 'hear_no_evil'; 1606 | char_to_name(16#1f64a) -> 'speak_no_evil'; 1607 | char_to_name(16#1f64b) -> 'raising_hand'; 1608 | char_to_name(16#1f64c) -> 'raised_hands'; 1609 | char_to_name(16#1f64d) -> 'person_frowning'; 1610 | char_to_name(16#1f64e) -> 'person_with_pouting_face'; 1611 | char_to_name(16#1f64f) -> 'pray'; 1612 | char_to_name(16#1f680) -> 'rocket'; 1613 | char_to_name(16#1f681) -> 'helicopter'; 1614 | char_to_name(16#1f682) -> 'steam_locomotive'; 1615 | char_to_name(16#1f683) -> 'railway_car'; 1616 | char_to_name(16#1f684) -> 'bullettrain_side'; 1617 | char_to_name(16#1f685) -> 'bullettrain_front'; 1618 | char_to_name(16#1f686) -> 'train2'; 1619 | char_to_name(16#1f687) -> 'metro'; 1620 | char_to_name(16#1f688) -> 'light_rail'; 1621 | char_to_name(16#1f689) -> 'station'; 1622 | char_to_name(16#1f68a) -> 'tram'; 1623 | char_to_name(16#1f68b) -> 'train'; 1624 | char_to_name(16#1f68c) -> 'bus'; 1625 | char_to_name(16#1f68d) -> 'oncoming_bus'; 1626 | char_to_name(16#1f68e) -> 'trolleybus'; 1627 | char_to_name(16#1f68f) -> 'busstop'; 1628 | char_to_name(16#1f690) -> 'minibus'; 1629 | char_to_name(16#1f691) -> 'ambulance'; 1630 | char_to_name(16#1f692) -> 'fire_engine'; 1631 | char_to_name(16#1f693) -> 'police_car'; 1632 | char_to_name(16#1f694) -> 'oncoming_police_car'; 1633 | char_to_name(16#1f695) -> 'taxi'; 1634 | char_to_name(16#1f696) -> 'oncoming_taxi'; 1635 | char_to_name(16#1f697) -> 'car'; 1636 | char_to_name(16#1f698) -> 'oncoming_automobile'; 1637 | char_to_name(16#1f699) -> 'blue_car'; 1638 | char_to_name(16#1f69a) -> 'truck'; 1639 | char_to_name(16#1f69b) -> 'articulated_lorry'; 1640 | char_to_name(16#1f69c) -> 'tractor'; 1641 | char_to_name(16#1f69d) -> 'monorail'; 1642 | char_to_name(16#1f69e) -> 'mountain_railway'; 1643 | char_to_name(16#1f69f) -> 'suspension_railway'; 1644 | char_to_name(16#1f6a0) -> 'mountain_cableway'; 1645 | char_to_name(16#1f6a1) -> 'aerial_tramway'; 1646 | char_to_name(16#1f6a2) -> 'ship'; 1647 | char_to_name(16#1f6a3) -> 'rowboat'; 1648 | char_to_name(16#1f6a4) -> 'speedboat'; 1649 | char_to_name(16#1f6a5) -> 'traffic_light'; 1650 | char_to_name(16#1f6a6) -> 'vertical_traffic_light'; 1651 | char_to_name(16#1f6a7) -> 'construction'; 1652 | char_to_name(16#1f6a8) -> 'rotating_light'; 1653 | char_to_name(16#1f6a9) -> 'triangular_flag_on_post'; 1654 | char_to_name(16#1f6aa) -> 'door'; 1655 | char_to_name(16#1f6ab) -> 'no_entry_sign'; 1656 | char_to_name(16#1f6ac) -> 'smoking'; 1657 | char_to_name(16#1f6ad) -> 'no_smoking'; 1658 | char_to_name(16#1f6ae) -> 'put_litter_in_its_place'; 1659 | char_to_name(16#1f6af) -> 'do_not_litter'; 1660 | char_to_name(16#1f6b0) -> 'potable_water'; 1661 | char_to_name(16#1f6b1) -> 'non-potable_water'; 1662 | char_to_name(16#1f6b2) -> 'bike'; 1663 | char_to_name(16#1f6b3) -> 'no_bicycles'; 1664 | char_to_name(16#1f6b4) -> 'bicyclist'; 1665 | char_to_name(16#1f6b5) -> 'mountain_bicyclist'; 1666 | char_to_name(16#1f6b6) -> 'walking'; 1667 | char_to_name(16#1f6b7) -> 'no_pedestrians'; 1668 | char_to_name(16#1f6b8) -> 'children_crossing'; 1669 | char_to_name(16#1f6b9) -> 'mens'; 1670 | char_to_name(16#1f6ba) -> 'womens'; 1671 | char_to_name(16#1f6bb) -> 'restroom'; 1672 | char_to_name(16#1f6bc) -> 'baby_symbol'; 1673 | char_to_name(16#1f6bd) -> 'toilet'; 1674 | char_to_name(16#1f6be) -> 'wc'; 1675 | char_to_name(16#1f6bf) -> 'shower'; 1676 | char_to_name(16#1f6c0) -> 'bath'; 1677 | char_to_name(16#1f6c1) -> 'bathtub'; 1678 | char_to_name(16#1f6c2) -> 'passport_control'; 1679 | char_to_name(16#1f6c3) -> 'customs'; 1680 | char_to_name(16#1f6c4) -> 'baggage_claim'; 1681 | char_to_name(16#1f6c5) -> 'left_luggage'; 1682 | char_to_name(_) -> throw({pe4kin, unknown_emoji}). 1683 | 1684 | -spec names() -> ordsets:ordset(atom()). 1685 | names() -> 1686 | ['+1', 1687 | '-1', 1688 | '100', 1689 | '1234', 1690 | '8ball', 1691 | 'a', 1692 | 'ab', 1693 | 'abc', 1694 | 'abcd', 1695 | 'accept', 1696 | 'aerial_tramway', 1697 | 'airplane', 1698 | 'alarm_clock', 1699 | 'alien', 1700 | 'ambulance', 1701 | 'anchor', 1702 | 'angel', 1703 | 'anger', 1704 | 'angry', 1705 | 'anguished', 1706 | 'ant', 1707 | 'apple', 1708 | 'aquarius', 1709 | 'aries', 1710 | 'arrow_backward', 1711 | 'arrow_double_down', 1712 | 'arrow_double_up', 1713 | 'arrow_down', 1714 | 'arrow_down_small', 1715 | 'arrow_forward', 1716 | 'arrow_heading_down', 1717 | 'arrow_heading_up', 1718 | 'arrow_left', 1719 | 'arrow_lower_left', 1720 | 'arrow_lower_right', 1721 | 'arrow_right', 1722 | 'arrow_right_hook', 1723 | 'arrow_up', 1724 | 'arrow_up_down', 1725 | 'arrow_up_small', 1726 | 'arrow_upper_left', 1727 | 'arrow_upper_right', 1728 | 'arrows_clockwise', 1729 | 'arrows_counterclockwise', 1730 | 'art', 1731 | 'articulated_lorry', 1732 | 'astonished', 1733 | 'athletic_shoe', 1734 | 'atm', 1735 | 'b', 1736 | 'baby', 1737 | 'baby_bottle', 1738 | 'baby_chick', 1739 | 'baby_symbol', 1740 | 'back', 1741 | 'baggage_claim', 1742 | 'balloon', 1743 | 'ballot_box_with_check', 1744 | 'bamboo', 1745 | 'banana', 1746 | 'bangbang', 1747 | 'bank', 1748 | 'bar_chart', 1749 | 'barber', 1750 | 'baseball', 1751 | 'basketball', 1752 | 'bath', 1753 | 'bathtub', 1754 | 'battery', 1755 | 'bear', 1756 | 'bee', 1757 | 'beer', 1758 | 'beers', 1759 | 'beetle', 1760 | 'beginner', 1761 | 'bell', 1762 | 'bento', 1763 | 'bicyclist', 1764 | 'bike', 1765 | 'bikini', 1766 | 'bird', 1767 | 'birthday', 1768 | 'black_circle', 1769 | 'black_joker', 1770 | 'black_large_square', 1771 | 'black_medium_small_square', 1772 | 'black_medium_square', 1773 | 'black_nib', 1774 | 'black_small_square', 1775 | 'black_square_button', 1776 | 'blossom', 1777 | 'blowfish', 1778 | 'blue_book', 1779 | 'blue_car', 1780 | 'blue_heart', 1781 | 'blush', 1782 | 'boar', 1783 | 'boat', 1784 | 'bomb', 1785 | 'book', 1786 | 'bookmark', 1787 | 'bookmark_tabs', 1788 | 'books', 1789 | 'boom', 1790 | 'boot', 1791 | 'bouquet', 1792 | 'bow', 1793 | 'bowling', 1794 | 'boy', 1795 | 'bread', 1796 | 'bride_with_veil', 1797 | 'bridge_at_night', 1798 | 'briefcase', 1799 | 'broken_heart', 1800 | 'bug', 1801 | 'bulb', 1802 | 'bullettrain_front', 1803 | 'bullettrain_side', 1804 | 'bus', 1805 | 'busstop', 1806 | 'bust_in_silhouette', 1807 | 'busts_in_silhouette', 1808 | 'cactus', 1809 | 'cake', 1810 | 'calendar', 1811 | 'calling', 1812 | 'camel', 1813 | 'camera', 1814 | 'cancer', 1815 | 'candy', 1816 | 'capital_abcd', 1817 | 'capricorn', 1818 | 'car', 1819 | 'card_index', 1820 | 'carousel_horse', 1821 | 'cat', 1822 | 'cat2', 1823 | 'cd', 1824 | 'chart', 1825 | 'chart_with_downwards_trend', 1826 | 'chart_with_upwards_trend', 1827 | 'checkered_flag', 1828 | 'cherries', 1829 | 'cherry_blossom', 1830 | 'chestnut', 1831 | 'chicken', 1832 | 'children_crossing', 1833 | 'chocolate_bar', 1834 | 'christmas_tree', 1835 | 'church', 1836 | 'cinema', 1837 | 'circus_tent', 1838 | 'city_sunrise', 1839 | 'city_sunset', 1840 | 'cl', 1841 | 'clap', 1842 | 'clapper', 1843 | 'clipboard', 1844 | 'clock1', 1845 | 'clock10', 1846 | 'clock1030', 1847 | 'clock11', 1848 | 'clock1130', 1849 | 'clock12', 1850 | 'clock1230', 1851 | 'clock130', 1852 | 'clock2', 1853 | 'clock230', 1854 | 'clock3', 1855 | 'clock330', 1856 | 'clock4', 1857 | 'clock430', 1858 | 'clock5', 1859 | 'clock530', 1860 | 'clock6', 1861 | 'clock630', 1862 | 'clock7', 1863 | 'clock730', 1864 | 'clock8', 1865 | 'clock830', 1866 | 'clock9', 1867 | 'clock930', 1868 | 'closed_book', 1869 | 'closed_lock_with_key', 1870 | 'closed_umbrella', 1871 | 'cloud', 1872 | 'clubs', 1873 | 'cocktail', 1874 | 'coffee', 1875 | 'cold_sweat', 1876 | 'computer', 1877 | 'confetti_ball', 1878 | 'confounded', 1879 | 'confused', 1880 | 'congratulations', 1881 | 'construction', 1882 | 'construction_worker', 1883 | 'convenience_store', 1884 | 'cookie', 1885 | 'cool', 1886 | 'cop', 1887 | 'copyright', 1888 | 'corn', 1889 | 'couple', 1890 | 'couple_with_heart', 1891 | 'couplekiss', 1892 | 'cow', 1893 | 'cow2', 1894 | 'credit_card', 1895 | 'crescent_moon', 1896 | 'crocodile', 1897 | 'crossed_flags', 1898 | 'crown', 1899 | 'cry', 1900 | 'crying_cat_face', 1901 | 'crystal_ball', 1902 | 'cupid', 1903 | 'curly_loop', 1904 | 'currency_exchange', 1905 | 'curry', 1906 | 'custard', 1907 | 'customs', 1908 | 'cyclone', 1909 | 'dancer', 1910 | 'dancers', 1911 | 'dango', 1912 | 'dart', 1913 | 'dash', 1914 | 'date', 1915 | 'deciduous_tree', 1916 | 'department_store', 1917 | 'diamond_shape_with_a_dot_inside', 1918 | 'diamonds', 1919 | 'disappointed', 1920 | 'disappointed_relieved', 1921 | 'dizzy', 1922 | 'dizzy_face', 1923 | 'do_not_litter', 1924 | 'dog', 1925 | 'dog2', 1926 | 'dollar', 1927 | 'dolls', 1928 | 'dolphin', 1929 | 'door', 1930 | 'doughnut', 1931 | 'dragon', 1932 | 'dragon_face', 1933 | 'dress', 1934 | 'dromedary_camel', 1935 | 'droplet', 1936 | 'dvd', 1937 | 'e-mail', 1938 | 'ear', 1939 | 'ear_of_rice', 1940 | 'earth_africa', 1941 | 'earth_americas', 1942 | 'earth_asia', 1943 | 'egg', 1944 | 'eggplant', 1945 | 'eight', 1946 | 'eight_pointed_black_star', 1947 | 'eight_spoked_asterisk', 1948 | 'electric_plug', 1949 | 'elephant', 1950 | 'email', 1951 | 'end', 1952 | 'envelope_with_arrow', 1953 | 'euro', 1954 | 'european_castle', 1955 | 'european_post_office', 1956 | 'evergreen_tree', 1957 | 'exclamation', 1958 | 'expressionless', 1959 | 'eyeglasses', 1960 | 'eyes', 1961 | 'facepunch', 1962 | 'factory', 1963 | 'fallen_leaf', 1964 | 'family', 1965 | 'fast_forward', 1966 | 'fax', 1967 | 'fearful', 1968 | 'feet', 1969 | 'ferris_wheel', 1970 | 'file_folder', 1971 | 'fire', 1972 | 'fire_engine', 1973 | 'fireworks', 1974 | 'first_quarter_moon', 1975 | 'first_quarter_moon_with_face', 1976 | 'fish', 1977 | 'fish_cake', 1978 | 'fishing_pole_and_fish', 1979 | 'fist', 1980 | 'five', 1981 | 'flags', 1982 | 'flashlight', 1983 | 'floppy_disk', 1984 | 'flower_playing_cards', 1985 | 'flushed', 1986 | 'foggy', 1987 | 'football', 1988 | 'footprints', 1989 | 'fork_and_knife', 1990 | 'fountain', 1991 | 'four', 1992 | 'four_leaf_clover', 1993 | 'free', 1994 | 'fried_shrimp', 1995 | 'fries', 1996 | 'frog', 1997 | 'frowning', 1998 | 'fuelpump', 1999 | 'full_moon', 2000 | 'full_moon_with_face', 2001 | 'game_die', 2002 | 'gem', 2003 | 'gemini', 2004 | 'ghost', 2005 | 'gift', 2006 | 'gift_heart', 2007 | 'girl', 2008 | 'globe_with_meridians', 2009 | 'goat', 2010 | 'golf', 2011 | 'grapes', 2012 | 'green_apple', 2013 | 'green_book', 2014 | 'green_heart', 2015 | 'grey_exclamation', 2016 | 'grey_question', 2017 | 'grimacing', 2018 | 'grin', 2019 | 'grinning', 2020 | 'guardsman', 2021 | 'guitar', 2022 | 'gun', 2023 | 'haircut', 2024 | 'hamburger', 2025 | 'hammer', 2026 | 'hamster', 2027 | 'hand', 2028 | 'handbag', 2029 | 'hankey', 2030 | 'hash', 2031 | 'hatched_chick', 2032 | 'hatching_chick', 2033 | 'headphones', 2034 | 'hear_no_evil', 2035 | 'heart', 2036 | 'heart_decoration', 2037 | 'heart_eyes', 2038 | 'heart_eyes_cat', 2039 | 'heartbeat', 2040 | 'heartpulse', 2041 | 'hearts', 2042 | 'heavy_check_mark', 2043 | 'heavy_division_sign', 2044 | 'heavy_dollar_sign', 2045 | 'heavy_minus_sign', 2046 | 'heavy_multiplication_x', 2047 | 'heavy_plus_sign', 2048 | 'helicopter', 2049 | 'herb', 2050 | 'hibiscus', 2051 | 'high_brightness', 2052 | 'high_heel', 2053 | 'hocho', 2054 | 'honey_pot', 2055 | 'horse', 2056 | 'horse_racing', 2057 | 'hospital', 2058 | 'hotel', 2059 | 'hotsprings', 2060 | 'hourglass', 2061 | 'hourglass_flowing_sand', 2062 | 'house', 2063 | 'house_with_garden', 2064 | 'hushed', 2065 | 'ice_cream', 2066 | 'icecream', 2067 | 'id', 2068 | 'ideograph_advantage', 2069 | 'imp', 2070 | 'inbox_tray', 2071 | 'incoming_envelope', 2072 | 'information_desk_person', 2073 | 'information_source', 2074 | 'innocent', 2075 | 'interrobang', 2076 | 'iphone', 2077 | 'izakaya_lantern', 2078 | 'jack_o_lantern', 2079 | 'japan', 2080 | 'japanese_castle', 2081 | 'japanese_goblin', 2082 | 'japanese_ogre', 2083 | 'jeans', 2084 | 'joy', 2085 | 'joy_cat', 2086 | 'key', 2087 | 'keycap_ten', 2088 | 'kimono', 2089 | 'kiss', 2090 | 'kissing', 2091 | 'kissing_cat', 2092 | 'kissing_closed_eyes', 2093 | 'kissing_heart', 2094 | 'kissing_smiling_eyes', 2095 | 'koala', 2096 | 'koko', 2097 | 'large_blue_circle', 2098 | 'large_blue_diamond', 2099 | 'large_orange_diamond', 2100 | 'last_quarter_moon', 2101 | 'last_quarter_moon_with_face', 2102 | 'laughing', 2103 | 'leaves', 2104 | 'ledger', 2105 | 'left_luggage', 2106 | 'left_right_arrow', 2107 | 'leftwards_arrow_with_hook', 2108 | 'lemon', 2109 | 'leo', 2110 | 'leopard', 2111 | 'libra', 2112 | 'light_rail', 2113 | 'link', 2114 | 'lips', 2115 | 'lipstick', 2116 | 'lock', 2117 | 'lock_with_ink_pen', 2118 | 'lollipop', 2119 | 'loop', 2120 | 'loud_sound', 2121 | 'loudspeaker', 2122 | 'love_hotel', 2123 | 'love_letter', 2124 | 'low_brightness', 2125 | 'm', 2126 | 'mag', 2127 | 'mag_right', 2128 | 'mahjong', 2129 | 'mailbox', 2130 | 'mailbox_closed', 2131 | 'mailbox_with_mail', 2132 | 'mailbox_with_no_mail', 2133 | 'man', 2134 | 'man_with_gua_pi_mao', 2135 | 'man_with_turban', 2136 | 'mans_shoe', 2137 | 'maple_leaf', 2138 | 'mask', 2139 | 'massage', 2140 | 'meat_on_bone', 2141 | 'mega', 2142 | 'melon', 2143 | 'memo', 2144 | 'mens', 2145 | 'metro', 2146 | 'microphone', 2147 | 'microscope', 2148 | 'milky_way', 2149 | 'minibus', 2150 | 'minidisc', 2151 | 'mobile_phone_off', 2152 | 'money_with_wings', 2153 | 'moneybag', 2154 | 'monkey', 2155 | 'monkey_face', 2156 | 'monorail', 2157 | 'moon', 2158 | 'mortar_board', 2159 | 'mount_fuji', 2160 | 'mountain_bicyclist', 2161 | 'mountain_cableway', 2162 | 'mountain_railway', 2163 | 'mouse', 2164 | 'mouse2', 2165 | 'movie_camera', 2166 | 'moyai', 2167 | 'muscle', 2168 | 'mushroom', 2169 | 'musical_keyboard', 2170 | 'musical_note', 2171 | 'musical_score', 2172 | 'mute', 2173 | 'nail_care', 2174 | 'name_badge', 2175 | 'necktie', 2176 | 'negative_squared_cross_mark', 2177 | 'neutral_face', 2178 | 'new', 2179 | 'new_moon', 2180 | 'new_moon_with_face', 2181 | 'newspaper', 2182 | 'ng', 2183 | 'night_with_stars', 2184 | 'nine', 2185 | 'no_bell', 2186 | 'no_bicycles', 2187 | 'no_entry', 2188 | 'no_entry_sign', 2189 | 'no_good', 2190 | 'no_mobile_phones', 2191 | 'no_mouth', 2192 | 'no_pedestrians', 2193 | 'no_smoking', 2194 | 'non-potable_water', 2195 | 'nose', 2196 | 'notebook', 2197 | 'notebook_with_decorative_cover', 2198 | 'notes', 2199 | 'nut_and_bolt', 2200 | 'o', 2201 | 'o2', 2202 | 'ocean', 2203 | 'octopus', 2204 | 'oden', 2205 | 'office', 2206 | 'ok', 2207 | 'ok_hand', 2208 | 'ok_woman', 2209 | 'older_man', 2210 | 'older_woman', 2211 | 'on', 2212 | 'oncoming_automobile', 2213 | 'oncoming_bus', 2214 | 'oncoming_police_car', 2215 | 'oncoming_taxi', 2216 | 'one', 2217 | 'open_file_folder', 2218 | 'open_hands', 2219 | 'open_mouth', 2220 | 'ophiuchus', 2221 | 'orange_book', 2222 | 'outbox_tray', 2223 | 'ox', 2224 | 'package', 2225 | 'page_facing_up', 2226 | 'page_with_curl', 2227 | 'pager', 2228 | 'palm_tree', 2229 | 'panda_face', 2230 | 'paperclip', 2231 | 'parking', 2232 | 'part_alternation_mark', 2233 | 'partly_sunny', 2234 | 'passport_control', 2235 | 'peach', 2236 | 'pear', 2237 | 'pencil2', 2238 | 'penguin', 2239 | 'pensive', 2240 | 'performing_arts', 2241 | 'persevere', 2242 | 'person_frowning', 2243 | 'person_with_blond_hair', 2244 | 'person_with_pouting_face', 2245 | 'phone', 2246 | 'pig', 2247 | 'pig2', 2248 | 'pig_nose', 2249 | 'pill', 2250 | 'pineapple', 2251 | 'pisces', 2252 | 'pizza', 2253 | 'point_down', 2254 | 'point_left', 2255 | 'point_right', 2256 | 'point_up', 2257 | 'point_up_2', 2258 | 'police_car', 2259 | 'poodle', 2260 | 'post_office', 2261 | 'postal_horn', 2262 | 'postbox', 2263 | 'potable_water', 2264 | 'pouch', 2265 | 'poultry_leg', 2266 | 'pound', 2267 | 'pouting_cat', 2268 | 'pray', 2269 | 'princess', 2270 | 'purple_heart', 2271 | 'purse', 2272 | 'pushpin', 2273 | 'put_litter_in_its_place', 2274 | 'question', 2275 | 'rabbit', 2276 | 'rabbit2', 2277 | 'racehorse', 2278 | 'radio', 2279 | 'radio_button', 2280 | 'rage', 2281 | 'railway_car', 2282 | 'rainbow', 2283 | 'raised_hands', 2284 | 'raising_hand', 2285 | 'ram', 2286 | 'ramen', 2287 | 'rat', 2288 | 'recycle', 2289 | 'red_circle', 2290 | 'registered', 2291 | 'relaxed', 2292 | 'relieved', 2293 | 'repeat', 2294 | 'repeat_one', 2295 | 'restroom', 2296 | 'revolving_hearts', 2297 | 'rewind', 2298 | 'ribbon', 2299 | 'rice', 2300 | 'rice_ball', 2301 | 'rice_cracker', 2302 | 'rice_scene', 2303 | 'ring', 2304 | 'rocket', 2305 | 'roller_coaster', 2306 | 'rooster', 2307 | 'rose', 2308 | 'rotating_light', 2309 | 'round_pushpin', 2310 | 'rowboat', 2311 | 'rugby_football', 2312 | 'runner', 2313 | 'running_shirt_with_sash', 2314 | 'sa', 2315 | 'sagittarius', 2316 | 'sake', 2317 | 'sandal', 2318 | 'santa', 2319 | 'satellite', 2320 | 'saxophone', 2321 | 'school', 2322 | 'school_satchel', 2323 | 'scissors', 2324 | 'scorpius', 2325 | 'scream', 2326 | 'scream_cat', 2327 | 'scroll', 2328 | 'seat', 2329 | 'secret', 2330 | 'see_no_evil', 2331 | 'seedling', 2332 | 'seven', 2333 | 'shaved_ice', 2334 | 'sheep', 2335 | 'shell', 2336 | 'ship', 2337 | 'shirt', 2338 | 'shower', 2339 | 'signal_strength', 2340 | 'six', 2341 | 'six_pointed_star', 2342 | 'ski', 2343 | 'skull', 2344 | 'sleeping', 2345 | 'sleepy', 2346 | 'slot_machine', 2347 | 'small_blue_diamond', 2348 | 'small_orange_diamond', 2349 | 'small_red_triangle', 2350 | 'small_red_triangle_down', 2351 | 'smile', 2352 | 'smile_cat', 2353 | 'smiley', 2354 | 'smiley_cat', 2355 | 'smiling_imp', 2356 | 'smirk', 2357 | 'smirk_cat', 2358 | 'smoking', 2359 | 'snail', 2360 | 'snake', 2361 | 'snowboarder', 2362 | 'snowflake', 2363 | 'snowman', 2364 | 'sob', 2365 | 'soccer', 2366 | 'soon', 2367 | 'sos', 2368 | 'sound', 2369 | 'space_invader', 2370 | 'spades', 2371 | 'spaghetti', 2372 | 'sparkle', 2373 | 'sparkler', 2374 | 'sparkles', 2375 | 'sparkling_heart', 2376 | 'speak_no_evil', 2377 | 'speaker', 2378 | 'speech_balloon', 2379 | 'speedboat', 2380 | 'star', 2381 | 'star2', 2382 | 'stars', 2383 | 'station', 2384 | 'statue_of_liberty', 2385 | 'steam_locomotive', 2386 | 'stew', 2387 | 'straight_ruler', 2388 | 'strawberry', 2389 | 'stuck_out_tongue', 2390 | 'stuck_out_tongue_closed_eyes', 2391 | 'stuck_out_tongue_winking_eye', 2392 | 'sun_with_face', 2393 | 'sunflower', 2394 | 'sunglasses', 2395 | 'sunny', 2396 | 'sunrise', 2397 | 'sunrise_over_mountains', 2398 | 'surfer', 2399 | 'sushi', 2400 | 'suspension_railway', 2401 | 'sweat', 2402 | 'sweat_drops', 2403 | 'sweat_smile', 2404 | 'sweet_potato', 2405 | 'swimmer', 2406 | 'symbols', 2407 | 'syringe', 2408 | 'tada', 2409 | 'tanabata_tree', 2410 | 'tangerine', 2411 | 'taurus', 2412 | 'taxi', 2413 | 'tea', 2414 | 'telephone_receiver', 2415 | 'telescope', 2416 | 'tennis', 2417 | 'tent', 2418 | 'thought_balloon', 2419 | 'three', 2420 | 'ticket', 2421 | 'tiger', 2422 | 'tiger2', 2423 | 'tired_face', 2424 | 'tm', 2425 | 'toilet', 2426 | 'tokyo_tower', 2427 | 'tomato', 2428 | 'tongue', 2429 | 'top', 2430 | 'tophat', 2431 | 'tractor', 2432 | 'traffic_light', 2433 | 'train', 2434 | 'train2', 2435 | 'tram', 2436 | 'triangular_flag_on_post', 2437 | 'triangular_ruler', 2438 | 'trident', 2439 | 'triumph', 2440 | 'trolleybus', 2441 | 'trophy', 2442 | 'tropical_drink', 2443 | 'tropical_fish', 2444 | 'truck', 2445 | 'trumpet', 2446 | 'tulip', 2447 | 'turtle', 2448 | 'tv', 2449 | 'twisted_rightwards_arrows', 2450 | 'two', 2451 | 'two_hearts', 2452 | 'two_men_holding_hands', 2453 | 'two_women_holding_hands', 2454 | 'u5272', 2455 | 'u5408', 2456 | 'u55b6', 2457 | 'u6307', 2458 | 'u6708', 2459 | 'u6709', 2460 | 'u6e80', 2461 | 'u7121', 2462 | 'u7533', 2463 | 'u7981', 2464 | 'u7a7a', 2465 | 'umbrella', 2466 | 'unamused', 2467 | 'underage', 2468 | 'unlock', 2469 | 'up', 2470 | 'v', 2471 | 'vertical_traffic_light', 2472 | 'vhs', 2473 | 'vibration_mode', 2474 | 'video_camera', 2475 | 'video_game', 2476 | 'violin', 2477 | 'virgo', 2478 | 'volcano', 2479 | 'vs', 2480 | 'walking', 2481 | 'waning_crescent_moon', 2482 | 'waning_gibbous_moon', 2483 | 'warning', 2484 | 'watch', 2485 | 'water_buffalo', 2486 | 'watermelon', 2487 | 'wave', 2488 | 'wavy_dash', 2489 | 'waxing_crescent_moon', 2490 | 'wc', 2491 | 'weary', 2492 | 'wedding', 2493 | 'whale', 2494 | 'whale2', 2495 | 'wheelchair', 2496 | 'white_check_mark', 2497 | 'white_circle', 2498 | 'white_flower', 2499 | 'white_large_square', 2500 | 'white_medium_small_square', 2501 | 'white_medium_square', 2502 | 'white_small_square', 2503 | 'white_square_button', 2504 | 'wind_chime', 2505 | 'wine_glass', 2506 | 'wink', 2507 | 'wolf', 2508 | 'woman', 2509 | 'womans_clothes', 2510 | 'womans_hat', 2511 | 'womens', 2512 | 'worried', 2513 | 'wrench', 2514 | 'x', 2515 | 'yellow_heart', 2516 | 'yen', 2517 | 'yum', 2518 | 'zap', 2519 | 'zero', 2520 | 'zzz']. 2521 | 2522 | -spec is_emoji(char()) -> boolean(). 2523 | is_emoji(Char) -> 2524 | try char_to_name(Char) of 2525 | _ -> true 2526 | catch throw:{pe4kin, unknown_emoji} -> 2527 | false 2528 | end. 2529 | --------------------------------------------------------------------------------