all repos — kokyo @ 55e9c6b23c3e1f92c5bc11756773c088bd263d12

Chatbot and CLI tool for Swiss public transports

bots/telegram.ts (view raw)

  1import { Telegram, getUpdates } from "@gramio/wrappergram";
  2import { findStops } from "@api/locationInformationRequest.ts";
  3import { InlineKeyboard } from "@gramio/keyboards";
  4import { getNextDepartures } from "@api/stopEvent.ts";
  5import logger from "@lib/logger.ts";
  6import * as kvdb from "../lib/kvdb.ts";
  7
  8const telegram = new Telegram(Deno.env.get("TELEGRAM_TOKEN") as string);
  9
 10const formatContent = (content?: string) =>
 11  content
 12    ?.replaceAll("-", "\\-")
 13    .replaceAll(".", "\\.")
 14    .replaceAll("(", "\\(")
 15    .replaceAll(")", "\\)");
 16
 17const sendMessage = async (message: any) => {
 18  await telegram.api.sendMessage(message);
 19};
 20
 21const getUsername = (from: From) => {
 22  let username = `id:${from.id}`;
 23  if (from.username) username = `user:${from.username}`;
 24  if (from.first_name && from.last_name)
 25    return `${from.first_name} ${from.last_name} (${username})`;
 26  else return username;
 27};
 28
 29for await (const update of getUpdates(telegram)) {
 30  logger.debug(JSON.stringify(update, null, 4));
 31  // On new message
 32  if (update.message) {
 33    if (!update.message?.from) {
 34      console.error("No 'from' in message");
 35      continue;
 36    }
 37
 38    const { from, text, location } = update.message;
 39
 40    logger.info(
 41      `New message from ${getUsername(from)}: ${
 42        text || JSON.stringify(location)
 43      }`
 44    );
 45
 46    telegram.api.setMyCommands({
 47      commands: [
 48        { command: "aide", description: "Explique comment utiliser le bot" },
 49        {
 50          command: "favoris",
 51          description: "Affiche la liste de vos arrêts favoris",
 52        },
 53      ],
 54    });
 55
 56    if (text === "/aide" || text === "/start") {
 57      sendMessage({
 58        chat_id: from.id,
 59        text: `Hey ! Je suis un bot qui te permet d'obtenir rapidement des informations sur les transports en commun dans toute la Suisse.
 60Entre le nom d'un arrêt et laisse-toi guider !
 61
 62Ce bot est en cours de développement actif. Les fonctionnalités sont pour le moment limitées.
 63
 64/aide - Affiche ce message
 65/favoris - Affiche vos favoris`,
 66      });
 67    } else if (text === "/favoris") {
 68      const userId = `telegram:${from.id}`;
 69      const favorites = kvdb.getUserFavorites(userId);
 70
 71      let inlineKeyboard = new InlineKeyboard();
 72      for await (const favorite of favorites)
 73        inlineKeyboard = inlineKeyboard
 74          .text(favorite.value.name, {
 75            cmd: "nextDepartures",
 76            stopRef: favorite.value.stopRef,
 77          })
 78          .row();
 79
 80      await sendMessage({
 81        chat_id: from.id,
 82        text: "Voici vos favoris",
 83        reply_markup: inlineKeyboard,
 84      });
 85    } else {
 86      const searchInput = location ? location : text;
 87      const stopLists = await findStops(searchInput);
 88      if (!stopLists?.length) {
 89        await sendMessage({
 90          chat_id: from.id,
 91          text: "Désolé, je n'ai pas réussi à récupérer une liste d'arrêts correspondants.",
 92        });
 93      } else {
 94        kvdb.saveStops(stopLists);
 95        let keyboard = new InlineKeyboard();
 96        for (const stop of stopLists)
 97          keyboard = keyboard
 98            .text(stop.name, { cmd: "nextDepartures", stopRef: stop.stopRef })
 99            .row();
100
101        await sendMessage({
102          chat_id: from.id,
103          text: "Quel arrêt correspond ?",
104          reply_markup: keyboard,
105        });
106      }
107    }
108  }
109
110  // On keyboard event (callback query)
111  else if (update.callback_query) {
112    const { from, data, message } = update.callback_query;
113    const userId = `telegram:${from.id}`;
114    const payload = JSON.parse(data);
115
116    logger.info(`New request from ${getUsername(from)}: ${data}`);
117
118    if (payload?.cmd === "nextDepartures") {
119      const nextDepartures = await getNextDepartures(payload.stopRef);
120      const stop = await kvdb.getStopByRef(payload.stopRef);
121      const text = formatContent(`Prochains départs depuis *${
122        stop.value?.name || "n.c."
123      }*:\n
124${nextDepartures
125  .map(
126    item =>
127      `*${item.serviceName}*  ${item.serviceTypeIcon}    *${item.departure}*    _${item.departureIn} min_     \n${item.to}\n`
128  )
129  .join("\n")}`);
130
131      let keyboard = new InlineKeyboard().text("Rafraîchir", {
132        cmd: "nextDepartures",
133        stopRef: payload.stopRef,
134        refresh: message.message_id,
135      });
136      const existingFavorite = await kvdb.getUserFavorite(
137        userId,
138        payload.stopRef
139      );
140      if (!existingFavorite?.value)
141        keyboard = keyboard.text("Enregistrer comme favoris", {
142          cmd: "saveFavorite",
143          stopRef: payload.stopRef,
144        });
145
146      await sendMessage({
147        chat_id: from.id,
148        parse_mode: "MarkdownV2",
149        text,
150        reply_markup: keyboard,
151      });
152    } else if (payload?.cmd === "saveFavorite") {
153      const stop = await kvdb.getStopByRef(payload.stopRef);
154      if (stop?.value) await kvdb.saveUserFavorite(userId, stop.value);
155      await sendMessage({
156        chat_id: from.id,
157        parse_mode: "MarkdownV2",
158        text: `L'arrêt *${formatContent(
159          stop.value?.name
160        )}* a été enregistré dans vos /favoris`,
161      });
162    }
163  }
164}