diff --git a/lib/lsg/telegram.ex b/lib/lsg/telegram.ex index 02d7115..747f272 100644 --- a/lib/lsg/telegram.ex +++ b/lib/lsg/telegram.ex @@ -1,253 +1,221 @@ defmodule LSG.Telegram do require Logger use Telegram.Bot, token: Keyword.get(Application.get_env(:lsg, :telegram, []), :key), username: Keyword.get(Application.get_env(:lsg, :telegram, []), :nick, "beauttebot"), purge: false def my_path() do "https://t.me/beauttebot" end def init() do + Logger.info("Telegram starting") # Create users in track: IRC.UserTrack.connected(...) :ok end def handle_update(_, %{"message" => m = %{"chat" => %{"type" => "private"}, "text" => text = "/start"<>_}}) do text = "Hello to beautte! Query the bot on IRC and say \"enable-telegram\" to continue." send_message(m["chat"]["id"], text) end def handle_update(_, %{"message" => m = %{"chat" => %{"type" => "private"}, "text" => text = "/enable"<>_}}) do key = case String.split(text, " ") do ["/enable", key | _] -> key _ -> "nil" end #Handled message "1247435154:AAGnSSCnySn0RuVxy_SUcDEoOX_rbF6vdq0" %{"message" => # %{"chat" => %{"first_name" => "J", "id" => 2075406, "type" => "private", "username" => "ahref"}, # "date" => 1591027272, "entities" => # [%{"length" => 7, "offset" => 0, "type" => "bot_command"}], # "from" => %{"first_name" => "J", "id" => 2075406, "is_bot" => false, "language_code" => "en", "username" => "ahref"}, # "message_id" => 11, "text" => "/enable salope"}, "update_id" => 764148578} account = IRC.Account.find_meta_account("telegram-validation-code", String.downcase(key)) text = if account do net = IRC.Account.get_meta(account, "telegram-validation-target") IRC.Account.put_meta(account, "telegram-id", m["chat"]["id"]) IRC.Account.put_meta(account, "telegram-username", m["chat"]["username"]) IRC.Account.delete_meta(account, "telegram-validation-code") IRC.Account.delete_meta(account, "telegram-validation-target") IRC.Connection.broadcast_message(net, account, "Telegram #{m["chat"]["username"]} account added!") "Yay! Linked to account #{account.name}" else "Token invalid" end send_message(m["chat"]["id"], text) end #[debug] Unhandled update: %{"message" => # %{"chat" => %{"first_name" => "J", "id" => 2075406, "type" => "private", "username" => "ahref"}, # "date" => 1591096015, # "from" => %{"first_name" => "J", "id" => 2075406, "is_bot" => false, "language_code" => "en", "username" => "ahref"}, # "message_id" => 29, # "photo" => [ # %{"file_id" => "AgACAgQAAxkBAAMdXtYyz4RQqLcpOlR6xKK3w3NayHAAAnCzMRuL4bFSgl_cTXMl4m5G_T0kXQADAQADAgADbQADZVMBAAEaBA", # "file_size" => 9544, "file_unique_id" => "AQADRv09JF0AA2VTAQAB", "height" => 95, "width" => 320}, # %{"file_id" => "AgACAgQAAxkBAAMdXtYyz4RQqLcpOlR6xKK3w3NayHAAAnCzMRuL4bFSgl_cTXMl4m5G_T0kXQADAQADAgADeAADZFMBAAEaBA", # "file_size" => 21420, "file_unique_id" => "AQADRv09JF0AA2RTAQAB", "height" => 148, "width" => 501}]}, # "update_id" => 218161546} def handle_update(token, data = %{"message" => %{"photo" => _}}) do start_upload(token, "photo", data) end def handle_update(token, data = %{"message" => %{"voice" => _}}) do start_upload(token, "voice", data) end def handle_update(token, data = %{"message" => %{"video" => _}}) do start_upload(token, "video", data) end def handle_update(token, data = %{"message" => %{"document" => _}}) do start_upload(token, "document", data) end def handle_update(token, data = %{"message" => %{"animation" => _}}) do start_upload(token, "animation", data) end - def start_upload(token, _, %{"message" => m = %{"chat" => %{"id" => id, "type" => "private"}}}) do - account = IRC.Account.find_meta_account("telegram-id", id) - if account do - text = if(m["text"], do: m["text"], else: nil) - targets = IRC.Membership.of_account(account) - |> Enum.map(fn({net, chan}) -> "#{net}/#{chan}" end) - |> Enum.map(fn(i) -> %{"text" => i, "callback_data" => i} end) - kb = if Enum.count(targets) > 1 do - [%{"text" => "everywhere", "callback_data" => "everywhere"}] ++ targets - else - targets - end - |> Enum.chunk_every(2) - keyboard = %{"inline_keyboard" => kb} - Telegram.Api.request(token, "sendMessage", chat_id: id, text: "Where should I send this file?", reply_markup: keyboard, reply_to_message_id: m["message_id"]) - end - end - #[debug] Unhandled update: %{"callback_query" => # %{ # "chat_instance" => "-7948978714441865930", "data" => "evolu.net/#dmz", # "from" => %{"first_name" => "J", "id" => 2075406, "is_bot" => false, "language_code" => "en", "username" => "ahref"}, # "id" => "8913804780149600", # "message" => %{"chat" => %{"first_name" => "J", "id" => 2075406, "type" => "private", "username" => "ahref"}, # "date" => 1591098553, "from" => %{"first_name" => "devbeautte", "id" => 1293058838, "is_bot" => true, "username" => "devbeauttebot"}, # "message_id" => 62, # "reply_markup" => %{"inline_keyboard" => [[%{"callback_data" => "random/#", "text" => "random/#"}, # %{"callback_data" => "evolu.net/#dmz", "text" => "evolu.net/#dmz"}]]}, # "text" => "Where should I send the file?"} # } # , "update_id" => 218161568} - def handle_update(t, %{"callback_query" => cb = %{"data" => "resend", "id" => id, "message" => m = %{"message_id" => m_id, "chat" => %{"id" => chat_id}, "reply_to_message" => op}}}) do - end + #def handle_update(t, %{"callback_query" => cb = %{"data" => "resend", "id" => id, "message" => m = %{"message_id" => m_id, "chat" => %{"id" => chat_id}, "reply_to_message" => op}}}) do + #end def handle_update(t, %{"callback_query" => cb = %{"data" => target, "id" => id, "message" => m = %{"message_id" => m_id, "chat" => %{"id" => chat_id}, "reply_to_message" => op}}}) do account = IRC.Account.find_meta_account("telegram-id", chat_id) if account do target = case String.split(target, "/") do ["everywhere"] -> IRC.Membership.of_account(account) [net, chan] -> [{net, chan}] end Telegram.Api.request(t, "editMessageText", chat_id: chat_id, message_id: m_id, text: "Processing...", reply_markup: %{}) {content, type} = cond do op["photo"] -> {op["photo"], ""} op["voice"] -> {op["voice"], " a voice message"} op["video"] -> {op["video"], ""} op["document"] -> {op["document"], ""} op["animation"] -> {op["animation"], ""} end file = if is_list(content) && Enum.count(content) > 1 do Enum.sort_by(content, fn(p) -> p["file_size"] end, &>=/2) |> List.first() else content end file_id = file["file_id"] file_unique_id = file["file_unique_id"] text = if(op["caption"], do: ": "<> op["caption"] <> "", else: "") resend = %{"inline_keyboard" => [ [%{"text" => "re-share", "callback_data" => "resend"}] ]} spawn(fn() -> with \ {:ok, file} <- Telegram.Api.request(t, "getFile", file_id: file_id), path = "https://api.telegram.org/file/bot#{t}/#{file["file_path"]}", {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- HTTPoison.get(path), <> = body, {:ok, magic} <- GenMagic.Pool.perform(LSG.GenMagic, {:bytes, smol_body}), bucket = Application.get_env(:lsg, :s3, []) |> Keyword.get(:bucket), ext = Path.extname(file["file_path"]), s3path = "#{account.id}/#{file_unique_id}#{ext}", Telegram.Api.request(t, "editMessageText", chat_id: chat_id, message_id: m_id, text: "Uploading...", reply_markup: %{}), s3req = ExAws.S3.put_object(bucket, s3path, body, acl: :public_read, content_type: magic.mime_type), {:ok, _} <- ExAws.request(s3req) do path = LSGWeb.Router.Helpers.url(LSGWeb.Endpoint) <> "/files/#{s3path}" sent = for {net, chan} <- target do user = IRC.UserTrack.find_by_account(net, account) nick = if(user, do: user.nick, else: account.name) txt = "#{nick} sent#{type}#{text} #{path}" IRC.Connection.broadcast_message(net, chan, txt) "#{net}/#{chan}" end if caption = op["caption"], do: as_irc_message(chat_id, caption, account) text = "Sent on " <> Enum.join(sent, ", ") <> " !" Telegram.Api.request(t, "editMessageText", chat_id: chat_id, message_id: m_id, text: "Sent!", reply_markup: %{}) else error -> Telegram.Api.request(t, "editMessageText", chat_id: chat_id, message_id: m_id, text: "Something failed.", reply_markup: %{}) Logger.error("Failed upload from Telegram: #{inspect error}") end end) end end def handle_update(_, %{"message" => m = %{"chat" => %{"id" => id, "type" => "private"}, "text" => text}}) do account = IRC.Account.find_meta_account("telegram-id", id) if account do as_irc_message(id, text, account) end end def send_message(id, text) do token = Keyword.get(Application.get_env(:lsg, :telegram, []), :key) Telegram.Api.request(token, "sendMessage", chat_id: id, text: text) end def handle_update(token__, m) do Logger.debug("Unhandled update: #{inspect m}") end defp as_irc_message(id, text, account) do reply_fun = fn(text) -> send_message(id, text) end trigger_text = cond do String.starts_with?(text, "/") -> "/"<>text = text "!"<>text Enum.any?(IRC.Connection.triggers(), fn({trigger, _}) -> String.starts_with?(text, trigger) end) -> text true -> "!"<>text end message = %IRC.Message{ transport: :telegram, network: "telegram", channel: nil, text: text, account: account, sender: %ExIRC.SenderInfo{nick: account.name}, replyfun: reply_fun, trigger: IRC.Connection.extract_trigger(trigger_text), at: nil } - IO.puts("converted telegram to message: #{inspect message}") IRC.Connection.publish(message, ["message:private", "message:telegram"]) message end - command "start" do - text = "Hello to beautte! Query the bot on IRC and say \"enable-telegram\" to continue." - request("sendMessage", chat_id: update["chat"]["id"], - text: text) - end - - command "start", args do - IO.inspect(update) - key = "none" - account = IRC.Account.find_meta_account("telegram-validation-code", String.downcase(key)) - text = if account do - net = IRC.Account.get_meta(account, "telegram-validation-target") - IRC.Account.put_meta(account, "telegram-id", update["chat"]["id"]) - IRC.Account.delete_meta(account, "telegram-validation-code") - IRC.Account.delete_meta(account, "telegram-validation-target") - IRC.Connection.broadcast_message(net, account, "Telegram account added!") - "Yay! Linked to account #{account.name}\n\nThe bot doesn't work by sending telegram commands, just send what you would usually send on IRC." - else - "Token invalid" + defp start_upload(token, _, %{"message" => m = %{"chat" => %{"id" => id, "type" => "private"}}}) do + account = IRC.Account.find_meta_account("telegram-id", id) + if account do + text = if(m["text"], do: m["text"], else: nil) + targets = IRC.Membership.of_account(account) + |> Enum.map(fn({net, chan}) -> "#{net}/#{chan}" end) + |> Enum.map(fn(i) -> %{"text" => i, "callback_data" => i} end) + kb = if Enum.count(targets) > 1 do + [%{"text" => "everywhere", "callback_data" => "everywhere"}] ++ targets + else + targets + end + |> Enum.chunk_every(2) + keyboard = %{"inline_keyboard" => kb} + Telegram.Api.request(token, "sendMessage", chat_id: id, text: "Where should I send this file?", reply_markup: keyboard, reply_to_message_id: m["message_id"]) end - request("sendMessage", chat_id: update["chat"]["id"], text: text) end - command unknown do - request("sendMessage", chat_id: update["chat"]["id"], - text: "Hey! You sent me a command #{unknown} #{inspect update}") - end - - message do - request("sendMessage", chat_id: update["chat"]["id"], - text: "Hey! You sent me a message: #{inspect update}") - end end diff --git a/lib/lsg_irc/tell_plugin.ex b/lib/lsg_irc/tell_plugin.ex index a683b43..2c9e3c8 100644 --- a/lib/lsg_irc/tell_plugin.ex +++ b/lib/lsg_irc/tell_plugin.ex @@ -1,93 +1,106 @@ defmodule LSG.IRC.TellPlugin do use GenServer @moduledoc """ # Tell * **!tell `` ``**: tell `message` to `nick` when they reconnect. """ def irc_doc, do: @moduledoc def start_link() do GenServer.start_link(__MODULE__, [], name: __MODULE__) end def dets do (LSG.data_path() <> "/tell.dets") |> String.to_charlist() end + def tell(m, target, message) do + GenServer.cast(__MODULE__, {:tell, m, target, message}) + end + def init([]) do regopts = [plugin: __MODULE__] {:ok, _} = Registry.register(IRC.PubSub, "account", regopts) {:ok, _} = Registry.register(IRC.PubSub, "trigger:tell", regopts) {:ok, dets} = :dets.open_file(dets(), [type: :bag]) {:ok, %{dets: dets}} end - def handle_info({:irc, :trigger, "tell", m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: [nick_target | message]}}}, state) do - target = IRC.Account.find_always_by_nick(m.network, m.channel, nick_target) - message = Enum.join(message, " ") - with \ - {:target, %IRC.Account{} = target} <- {:target, target}, - {:same, false} <- {:same, target.id == m.account.id}, - target_user = IRC.UserTrack.find_by_account(m.network, target), - target_nick = if(target_user, do: target_user.nick, else: target.name), - present? = if(target_user, do: Map.has_key?(target_user.last_active, m.channel)), - {:absent, true, _} <- {:absent, !present?, target_nick}, - {:message, message} <- {:message, message} - do - obj = { {m.network, m.channel, target.id}, m.account.id, message, NaiveDateTime.utc_now()} - :dets.insert(state.dets, obj) - m.replyfun.("will tell to #{target_nick}") - else - {:same, _} -> m.replyfun.("are you so stupid that you need a bot to tell yourself things ?") - {:target, _} -> m.replyfun.("#{nick_target} unknown") - {:absent, _, nick} -> m.replyfun.("#{nick} is here, tell yourself!") - {:message, _} -> m.replyfun.("can't tell without a message") - end + def handle_cast({:tell, m, target, message}, state) do + do_tell(state, m, target, message) + {:noreply, state} + end + + def handle_info({:irc, :trigger, "tell", m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: [target | message]}}}, state) do + do_tell(state, m, target, message) {:noreply, state} end def handle_info({:account, network, channel, nick, account_id}, state) do messages = :dets.lookup(state.dets, {network, channel, account_id}) if messages != [] do strs = Enum.map(messages, fn({_, from, message, at}) -> account = IRC.Account.get(from) user = IRC.UserTrack.find_by_account(network, account) fromnick = if user, do: user.nick, else: account.name "#{nick}: <#{fromnick}> #{message}" end) Enum.each(strs, fn(s) -> IRC.Connection.broadcast_message(network, channel, s) end) :dets.delete(state.dets, {network, channel, account_id}) end {:noreply, state} end def handle_info({:account_change, old_id, new_id}, state) do #:ets.fun2ms(fn({ {_net, _chan, target_id}, from_id, _, _} = obj) when (target_id == old_id) or (from_id == old_id) -> obj end) spec = [{{{:"$1", :"$2", :"$3"}, :"$4", :_, :_}, [{:orelse, {:==, :"$3", {:const, old_id}}, {:==, :"$4", {:const, old_id}}}], [:"$_"]}] Util.Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj) -> case obj do { {net, chan, ^old_id}, from_id, message, at } = obj -> :dets.delete(obj) :dets.insert(table, {{net, chan, new_id}, from_id, message, at}) {key, ^old_id, message, at} = obj -> :dets.delete(table, obj) :dets.insert(table, {key, new_id, message, at}) _ -> :ok end end) {:noreply, state} end def handle_info(info, state) do {:noreply, state} end def terminate(_, state) do :dets.close(state.dets) :ok end + defp do_tell(state, m, nick_target, message) do + target = IRC.Account.find_always_by_nick(m.network, m.channel, nick_target) + message = Enum.join(message, " ") + with \ + {:target, %IRC.Account{} = target} <- {:target, target}, + {:same, false} <- {:same, target.id == m.account.id}, + target_user = IRC.UserTrack.find_by_account(m.network, target), + target_nick = if(target_user, do: target_user.nick, else: target.name), + present? = if(target_user, do: Map.has_key?(target_user.last_active, m.channel)), + {:absent, true, _} <- {:absent, !present?, target_nick}, + {:message, message} <- {:message, message} + do + obj = { {m.network, m.channel, target.id}, m.account.id, message, NaiveDateTime.utc_now()} + :dets.insert(state.dets, obj) + m.replyfun.("will tell to #{target_nick}") + else + {:same, _} -> m.replyfun.("are you so stupid that you need a bot to tell yourself things ?") + {:target, _} -> m.replyfun.("#{nick_target} unknown") + {:absent, _, nick} -> m.replyfun.("#{nick} is here, tell yourself!") + {:message, _} -> m.replyfun.("can't tell without a message") + end + end + end diff --git a/lib/lsg_irc/user_mention_plugin.ex b/lib/lsg_irc/user_mention_plugin.ex new file mode 100644 index 0000000..5f7b10a --- /dev/null +++ b/lib/lsg_irc/user_mention_plugin.ex @@ -0,0 +1,49 @@ +defmodule LSG.IRC.UserMentionPlugin do + @moduledoc """ + # mention + + * **@`` ``**: notifie si possible le nick immédiatement via Telegram, SMS, ou équivalent à `!tell`. + """ + + require Logger + + def short_irc_doc, do: false + def irc_doc, do: @moduledoc + + def start_link() do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def init(_) do + {:ok, _} = Registry.register(IRC.PubSub, "triggers", plugin: __MODULE__) + {:ok, nil} + end + + def handle_info({:irc, :trigger, nick, message = %IRC.Message{sender: sender, account: account, network: network, channel: channel, trigger: %IRC.Trigger{type: :at, args: content}}}, state) do + target = IRC.Account.find_always_by_nick(network, channel, nick) + if target do + telegram = IRC.Account.get_meta(target, "telegram-id") + sms = IRC.Account.get_meta(target, "sms-number") + text = "#{channel} <#{sender.nick}> #{Enum.join(content, " ")}" + + cond do + telegram -> + LSG.Telegram.send_message(telegram, "`#{channel}` <**#{sender.nick}**> #{Enum.join(content, " ")}") + sms -> + case LSG.IRC.SmsPlugin.send_sms(sms, text) do + {:error, code} -> message.replyfun("#{sender.nick}: erreur #{code} (sms)") + end + true -> + LSG.IRC.TellPlugin.tell(message, nick, content) + end + else + message.replyfun.("#{nick} m'est inconnu") + end + {:noreply, state} + end + + def handle_info(_, state) do + {:noreply, state} + end + +end