Gleam CLI client for Tangled (clone of aly.codes/tg)
0

Configure Feed

Select the types of activity you want to include in your feed.

gtg / src / gtg_ffi.erl
4.8 kB 147 lines
1-module(gtg_ffi). 2-export([ 3 halt/1, 4 unix_seconds/0, 5 unix_micros/0, 6 gzip/1, 7 gunzip/1, 8 get_cwd/0, 9 format_rfc3339/1, 10 ed25519_generate/0, 11 ed25519_public/1, 12 ed25519_sign/2, 13 lookup_txt/1, 14 wait_http_callback/2 15]). 16 17halt(Code) -> 18 erlang:halt(Code). 19 20unix_seconds() -> 21 erlang:system_time(second). 22 23unix_micros() -> 24 erlang:system_time(microsecond). 25 26gzip(Data) when is_binary(Data) -> 27 zlib:gzip(Data). 28 29gunzip(Data) when is_binary(Data) -> 30 try 31 {ok, zlib:gunzip(Data)} 32 catch 33 _:_ -> {error, <<"gunzip failed">>} 34 end. 35 36get_cwd() -> 37 case file:get_cwd() of 38 {ok, Cwd} -> {ok, unicode:characters_to_binary(Cwd)}; 39 {error, Reason} -> {error, unicode:characters_to_binary(io_lib:format("~p", [Reason]))} 40 end. 41 42format_rfc3339(Secs) when is_integer(Secs) -> 43 {{Y, Mo, D}, {H, Mi, S}} = calendar:system_time_to_universal_time(Secs, second), 44 list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ", [Y, Mo, D, H, Mi, S])). 45 46%% {PublicKey :: binary(), PrivateSeed :: binary()} — both 32 bytes. 47ed25519_generate() -> 48 {Pub, Priv} = crypto:generate_key(eddsa, ed25519), 49 Seed = 50 case byte_size(Priv) of 51 32 -> Priv; 52 64 -> binary:part(Priv, 0, 32); 53 _ -> Priv 54 end, 55 {Pub, Seed}. 56 57ed25519_public(Seed) when is_binary(Seed), byte_size(Seed) =:= 32 -> 58 {Pub, _} = crypto:generate_key(eddsa, ed25519, Seed), 59 Pub; 60ed25519_public(Priv) when is_binary(Priv), byte_size(Priv) =:= 64 -> 61 Seed = binary:part(Priv, 0, 32), 62 {Pub, _} = crypto:generate_key(eddsa, ed25519, Seed), 63 Pub. 64 65ed25519_sign(Msg, Seed) when is_binary(Msg), is_binary(Seed) -> 66 crypto:sign(eddsa, none, Msg, [Seed, ed25519]). 67 68lookup_txt(NameBin) when is_binary(NameBin) -> 69 Name = unicode:characters_to_list(NameBin), 70 case inet_res:lookup(Name, in, txt) of 71 Records when is_list(Records) -> 72 lists:flatmap(fun txt_strings/1, Records); 73 _ -> 74 [] 75 end. 76 77txt_strings([H | _] = L) when is_list(H) -> 78 [list_to_binary(lists:flatten(L))]; 79txt_strings(Txt) when is_list(Txt) -> 80 [list_to_binary(Txt)]; 81txt_strings(Bin) when is_binary(Bin) -> 82 [Bin]; 83txt_strings(_) -> 84 []. 85 86%% Accept one HTTP request on 127.0.0.1:Port and return the raw query string 87%% from the request target (everything after '?'), or {error, ReasonBin}. 88wait_http_callback(Port, TimeoutMs) when is_integer(Port), is_integer(TimeoutMs) -> 89 Opts = [ 90 binary, 91 {packet, raw}, 92 {active, false}, 93 {reuseaddr, true}, 94 {ip, {127, 0, 0, 1}} 95 ], 96 case gen_tcp:listen(Port, Opts) of 97 {ok, Listen} -> 98 case gen_tcp:accept(Listen, TimeoutMs) of 99 {ok, Sock} -> 100 case gen_tcp:recv(Sock, 0, TimeoutMs) of 101 {ok, Data} -> 102 Reply = 103 <<"HTTP/1.1 200 OK\r\n", 104 "Content-Type: text/html; charset=utf-8\r\n", 105 "Connection: close\r\n", 106 "Content-Length: 62\r\n", 107 "\r\n", 108 "<html><body>Authenticated. You can close this tab.</body></html>">>, 109 _ = gen_tcp:send(Sock, Reply), 110 gen_tcp:close(Sock), 111 gen_tcp:close(Listen), 112 case extract_query(Data) of 113 {ok, Qs} -> {ok, Qs}; 114 error -> {error, <<"could not parse callback request">>} 115 end; 116 {error, Reason} -> 117 gen_tcp:close(Sock), 118 gen_tcp:close(Listen), 119 {error, reason_bin(Reason)} 120 end; 121 {error, Reason} -> 122 gen_tcp:close(Listen), 123 {error, reason_bin(Reason)} 124 end; 125 {error, Reason} -> 126 {error, reason_bin(Reason)} 127 end. 128 129extract_query(Data) when is_binary(Data) -> 130 %% First line: METHOD SP target SP HTTP/… 131 case binary:split(Data, <<"\r\n">>) of 132 [Line | _] -> 133 case binary:split(Line, <<" ">>, [global]) of 134 [_, Target | _] -> 135 case binary:split(Target, <<"?">>) of 136 [_, Qs] -> {ok, Qs}; 137 _ -> {ok, <<>>} 138 end; 139 _ -> 140 error 141 end; 142 _ -> 143 error 144 end. 145 146reason_bin(Reason) -> 147 unicode:characters_to_binary(io_lib:format("~p", [Reason])).