all repos — kokyo @ 7686a3b843cded2f31731b30131d4d7ff54f70f5

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      const inlineKeyboard = new InlineKeyboard().url(
 58        "Plus d'infos",
 59        "https://5ika.ch/posts/un-bot-pour-les-transports-en-commun/"
 60      );
 61      await sendMessage({
 62        chat_id: from.id,
 63        reply_markup: inlineKeyboard,
 64        text: `Hey ✋ !
 65Je suis un bot qui te permet d'obtenir rapidement des informations sur les transports en commun dans toute la Suisse 🇨🇭
 66Entre le nom d'un arrêt et laisse-toi guider !
 67
 68📍 Tu peux aussi envoyer ta position GPS et je liste les arrêts à proximité.
 69
 70🧑‍💻 Ce bot est en cours de développement actif. Il se peut qu'il y ait encore quelques instabilités.
 71
 72/aide - Affiche ce message
 73/favoris - Affiche vos favoris`,
 74      });
 75    } else if (text === "/favoris") {
 76      const userId = `telegram:${from.id}`;
 77      const favorites = kvdb.getUserFavorites(userId);
 78
 79      let inlineKeyboard = new InlineKeyboard();
 80      for await (const favorite of favorites)
 81        inlineKeyboard = inlineKeyboard
 82          .text(favorite.value.name, {
 83            cmd: "nextDepartures",
 84            stopRef: favorite.value.stopRef,
 85          })
 86          .row();
 87
 88      await sendMessage({
 89        chat_id: from.id,
 90        text: "Voici vos favoris",
 91        reply_markup: inlineKeyboard,
 92      });
 93    } else {
 94      telegram.api.sendChatAction({
 95        chat_id: from.id,
 96        action: "find_location",
 97      });
 98      const searchInput = location ? location : text;
 99      const stopLists = await findStops(searchInput);
100      if (!stopLists?.length) {
101        await sendMessage({
102          chat_id: from.id,
103          text: "Désolé, je n'ai pas réussi à récupérer une liste d'arrêts correspondants.",
104        });
105      } else {
106        kvdb.saveStops(stopLists);
107        let keyboard = new InlineKeyboard();
108        for (const stop of stopLists)
109          keyboard = keyboard
110            .text(stop.name, { cmd: "nextDepartures", stopRef: stop.stopRef })
111            .row();
112
113        await sendMessage({
114          chat_id: from.id,
115          text: "Quel arrêt correspond ?",
116          reply_markup: keyboard,
117        });
118      }
119    }
120  }
121
122  // On keyboard event (callback query)
123  else if (update.callback_query) {
124    const { from, data, message } = update.callback_query;
125    const userId = `telegram:${from.id}`;
126    const payload = JSON.parse(data);
127
128    logger.info(`New request from ${getUsername(from)}: ${data}`);
129
130    if (payload?.cmd === "nextDepartures") {
131      telegram.api.sendChatAction({
132        chat_id: from.id,
133        action: "find_location",
134      });
135      const nextDepartures = await getNextDepartures(payload.stopRef);
136      const stop = await kvdb.getStopByRef(payload.stopRef);
137      const text = formatContent(`Prochains départs depuis *${
138        stop.value?.name || "n.c."
139      }*:\n
140${nextDepartures
141  .map(
142    item =>
143      `*${item.serviceName}*  ${item.serviceTypeIcon}    *${item.departure}*    _${item.departureIn} min_     \n${item.to}\n`
144  )
145  .join("\n")}`);
146
147      let keyboard = new InlineKeyboard()
148        .text("Rafraîchir", {
149          cmd: "nextDepartures",
150          stopRef: payload.stopRef,
151          refresh: message.message_id,
152        })
153        .text("Localiser", {
154          cmd: "localizeStop",
155          stopRef: payload.stopRef,
156        });
157
158      const existingFavorite = await kvdb.getUserFavorite(
159        userId,
160        payload.stopRef
161      );
162      if (!existingFavorite?.value)
163        keyboard = keyboard.row().text("Enregistrer comme favoris", {
164          cmd: "saveFavorite",
165          stopRef: payload.stopRef,
166        });
167
168      await sendMessage({
169        chat_id: from.id,
170        parse_mode: "MarkdownV2",
171        text,
172        reply_markup: keyboard,
173      });
174    } else if (payload?.cmd === "localizeStop") {
175      const stop = await kvdb.getStopByRef(payload.stopRef);
176      if (stop.value?.geoPosition)
177        await telegram.api.sendLocation({
178          chat_id: from.id,
179          longitude: stop.value.geoPosition.longitude,
180          latitude: stop.value.geoPosition.latitude,
181        });
182      else
183        sendMessage({
184          chat_id: from.id,
185          text: "Pas de coordonnées associées à cet arrêt.",
186        });
187    } else if (payload?.cmd === "saveFavorite") {
188      const stop = await kvdb.getStopByRef(payload.stopRef);
189      if (stop?.value) await kvdb.saveUserFavorite(userId, stop.value);
190      await sendMessage({
191        chat_id: from.id,
192        parse_mode: "MarkdownV2",
193        text: `L'arrêt *${formatContent(
194          stop.value?.name
195        )}* a été enregistré dans vos /favoris`,
196      });
197    }
198  }
199}