fetch-telegram.mjs 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. // Fetches the public Telegram channel feed (t.me/s/...) and saves the latest
  2. // posts for the home-page Telegram widget. Run manually: npm run fetch-news
  3. import * as cheerio from "cheerio";
  4. import { mkdir, writeFile } from "node:fs/promises";
  5. import path from "node:path";
  6. const CHANNEL = "scientia_ru";
  7. const UA =
  8. "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
  9. const res = await fetch(`https://t.me/s/${CHANNEL}`, { headers: { "User-Agent": UA } });
  10. if (!res.ok) {
  11. console.error(`Failed to fetch channel: HTTP ${res.status}`);
  12. process.exit(1);
  13. }
  14. const $ = cheerio.load(await res.text());
  15. const posts = [];
  16. $(".tgme_widget_message").each((_, el) => {
  17. const $el = $(el);
  18. const textNode = $el.find(".tgme_widget_message_text").first();
  19. // Keep basic formatting tags, drop telegram-internal spans/attrs
  20. textNode.find("a, b, i, u, s, strong, em, code, br").each((__, keep) => {
  21. const attrs = $(keep).attr() || {};
  22. for (const name of Object.keys(attrs)) {
  23. if (name !== "href") $(keep).removeAttr(name);
  24. }
  25. });
  26. const text = textNode.html()?.trim() || "";
  27. const datetime = $el.find("time").first().attr("datetime") || "";
  28. const photoStyle = $el.find(".tgme_widget_message_photo_wrap").first().attr("style") || "";
  29. const photo = photoStyle.match(/url\('([^']+)'\)/)?.[1]?.split("?")[0] || "";
  30. const video = $el.find(".tgme_widget_message_video_wrap, .tgme_widget_message_video_player").length > 0;
  31. const link = $el.find(".tgme_widget_message_date").first().attr("href") || `https://t.me/${CHANNEL}`;
  32. if (text || photo || video) posts.push({ text, datetime, photo, video, link });
  33. });
  34. // t.me/s serves the first page oldest-first — flip so the newest post comes first
  35. const latest = posts.slice(0, 12).reverse();
  36. await mkdir(path.dirname("src/data/telegram-news.json"), { recursive: true });
  37. await writeFile("src/data/telegram-news.json", JSON.stringify(latest, null, 2));
  38. console.log(`saved ${latest.length} posts`);
  39. // Channel avatar for the widget header
  40. const avatar = await fetch(`https://t.me/i/userpic/320/${CHANNEL}.jpg`, { headers: { "User-Agent": UA } });
  41. if (avatar.ok) {
  42. const buf = Buffer.from(await avatar.arrayBuffer());
  43. if (buf.length > 500) {
  44. await writeFile("public/images/telegram-avatar.jpg", buf);
  45. console.log("avatar saved");
  46. } else {
  47. console.log("avatar looks empty, skipped");
  48. }
  49. } else {
  50. console.log(`avatar fetch failed: HTTP ${avatar.status}`);
  51. }