Skip to content

Instantly share code, notes, and snippets.

@darvesh
Last active March 27, 2022 05:07
Show Gist options
  • Save darvesh/bc053a506f09e24c23b6eb45080252b8 to your computer and use it in GitHub Desktop.
Save darvesh/bc053a506f09e24c23b6eb45080252b8 to your computer and use it in GitHub Desktop.
A Telegram bot that sends notification when there is new blog post on https://devblogs.microsoft.com/typescript/feed/. You can also list blogs inline and send.
import { Bot } from "https://deno.land/x/[email protected]/mod.ts";
import { parseFeed, Feed } from "https://deno.land/x/[email protected]/mod.ts";
const INTERVAL = 60 * 10 * 1000;
const RECIPIENT = -1001335202320;
const TOKEN = Deno.args[0];
if (!TOKEN) throw new Error("Please provide a bot token");
const bot = new Bot(TOKEN);
type ArrayElement<T extends unknown[]> = T extends (infer E)[] ? E : never;
type FeedEntry = ArrayElement<Feed["entries"]>;
type Post = { title: string; published: Date; link: string };
type Maybe<T> = T | null;
const escapables = {
"<": "&lt;",
">": "&gt;",
"&": "&amp;",
"'": "&#39;",
'"': "&quot;",
};
const escape = (s: string) =>
s.replace(/<|>|&|"|'/g, (r) => escapables[r as keyof typeof escapables] || r);
async function getPosts(attempt = 0): Promise<FeedEntry[]> {
try {
if (attempt > 2) throw new Error("Retry attempt count exceeded");
const response = await fetch(
"https://devblogs.microsoft.com/typescript/feed/"
);
const xml = await response.text();
const feed = await parseFeed(xml);
return feed.entries;
} catch {
await new Promise((res) => setTimeout(res, 60 * 1000));
return getPosts(attempt + 1);
}
}
const parser = (feed: FeedEntry): Maybe<Post> => {
const published = feed.published;
const title = feed.title?.value;
const link = feed.links[0].href;
if (title && published && link) return { title, published, link };
return null;
};
const filterNewPosts = (post: Maybe<Post>) =>
post &&
post.published.getTime() > new Date("2022-02-09T18:45:19.000Z").getTime()
? post
: null;
const sendPost = (bot: Bot, post: Maybe<Post>) =>
post &&
bot.api.sendMessage(
RECIPIENT,
`<b>New TypeScript blog post published ๐Ÿ“</b>\n\n<a href="${
post.link
}">${escape(post.title)}</a>`,
{
parse_mode: "HTML",
}
);
setInterval(async () => {
try {
const posts = await getPosts();
await Promise.all(
posts
.map(parser)
.filter(filterNewPosts)
.map((post) => sendPost(bot, post))
);
} catch (error) {
console.trace(error);
}
}, INTERVAL);
bot.command("start", (ctx) => ctx.reply("Welcome! Up and running.\nDev: https://github.com/darvesh"));
bot.on("inline_query", async (ctx) => {
const posts = await getPosts();
await ctx.answerInlineQuery(
posts
.map((post) => {
const p = parser(post);
return p
? {
type: "article" as const,
title: p.title,
id: Math.random().toString(),
input_message_content: {
message_text: `<b>New TypeScript blog post published ๐Ÿ“</b>\n\n<a href="${
p.link
}">${escape(p.title)}</a>`,
parse_mode: "HTML" as const,
},
}
: null;
})
.filter(<T>(res: T): res is NonNullable<T> => Boolean(res))
);
});
bot.catch(console.trace);
bot.start({
onStart: () => console.log("Bot started"),
drop_pending_updates: true,
});
@darvesh
Copy link
Author

darvesh commented Mar 27, 2022

deno run --allow-net https://gist.githubusercontent.com/darvesh/bc053a506f09e24c23b6eb45080252b8/raw/816d56ae340177f03cdef4acd6e26a8be78f570c/tsrelease.ts "<bot-token>" 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment