133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
/* eslint-disable no-await-in-loop */
|
|
import type { Context } from 'hono';
|
|
import { Api } from 'telegram';
|
|
|
|
import NotFoundError from '@/errors/types/not-found';
|
|
import { configureMiddlewares, handleMedia } from '@/routes/telegram/channel-media';
|
|
import type { Data, DataItem, Route } from '@/types';
|
|
|
|
import { getGeoLink, getMediaLink } from './tglib/channel';
|
|
import { getClient, getStory, unwrapMedia } from './tglib/client';
|
|
|
|
export const route: Route = {
|
|
path: '/stories/:username/:story?',
|
|
categories: ['social-media'],
|
|
example: '/telegram/stories/telegram',
|
|
parameters: { username: 'entity name', story: 'story' },
|
|
features: {
|
|
requireConfig: [
|
|
{
|
|
name: 'TELEGRAM_SESSION',
|
|
optional: false,
|
|
description: 'Telegram API Authentication',
|
|
},
|
|
{
|
|
name: 'TELEGRAM_API_ID',
|
|
optional: true,
|
|
description: 'Telegram API ID',
|
|
},
|
|
{
|
|
name: 'TELEGRAM_API_HASH',
|
|
optional: true,
|
|
description: 'Telegram API Hash',
|
|
},
|
|
{
|
|
name: 'TELEGRAM_MAX_CONCURRENT_DOWNLOADS',
|
|
optional: true,
|
|
description: 'Telegram Max Concurrent Downloads',
|
|
},
|
|
{
|
|
name: 'TELEGRAM_PROXY_HOST',
|
|
optional: true,
|
|
description: 'Telegram Proxy Host',
|
|
},
|
|
{
|
|
name: 'TELEGRAM_PROXY_PORT',
|
|
optional: true,
|
|
description: 'Telegram Proxy Port',
|
|
},
|
|
{
|
|
name: 'TELEGRAM_PROXY_SECRET',
|
|
optional: true,
|
|
description: 'Telegram Proxy Secret',
|
|
},
|
|
],
|
|
requirePuppeteer: false,
|
|
antiCrawler: false,
|
|
supportBT: false,
|
|
supportPodcast: false,
|
|
supportScihub: false,
|
|
},
|
|
radar: [],
|
|
name: 'Stories',
|
|
maintainers: ['synchrone'],
|
|
handler,
|
|
description: ``,
|
|
};
|
|
|
|
function getMediaAreas(mediaAreas?: Api.TypeMediaArea[]) {
|
|
let description = '';
|
|
for (const area of mediaAreas ?? []) {
|
|
if (area instanceof Api.MediaAreaChannelPost) {
|
|
// TODO: fetch area.msgId and display inline
|
|
} else if ((area instanceof Api.MediaAreaGeoPoint || area instanceof Api.MediaAreaVenue) && area.geo instanceof Api.GeoPoint) {
|
|
description += getGeoLink(area.geo);
|
|
} else if (area instanceof Api.MediaAreaSuggestedReaction) {
|
|
if (area.reaction instanceof Api.ReactionEmoji) {
|
|
description += area.reaction.emoticon;
|
|
} else if (area.reaction instanceof Api.ReactionCustomEmoji) {
|
|
// TODO: fetch area.reaction.documentId and display inline
|
|
}
|
|
}
|
|
}
|
|
return description;
|
|
}
|
|
|
|
export default async function handler(ctx: Context) {
|
|
const c = await getClient();
|
|
const { username, story } = ctx.req.param();
|
|
if (!username) {
|
|
throw new NotFoundError();
|
|
}
|
|
const peer = await c.getInputEntity(username);
|
|
if (story) {
|
|
const storyItem = await getStory(peer, Number(story));
|
|
await configureMiddlewares(ctx);
|
|
return await handleMedia(storyItem.media, c, ctx);
|
|
}
|
|
|
|
const storiesRes = await c.invoke(new Api.stories.GetPeerStories({ peer }));
|
|
|
|
const item: DataItem[] = [];
|
|
for (const story of storiesRes.stories.stories) {
|
|
if (!(story instanceof Api.StoryItem)) {
|
|
// story is deleted (archived) or skipped
|
|
continue;
|
|
}
|
|
const src = `${new URL(ctx.req.url).origin}/telegram/stories/${username}/${story.id}`;
|
|
const pubDate = new Date(story.date * 1000).toUTCString();
|
|
const media = await unwrapMedia(story.media);
|
|
if (!media) {
|
|
// cannot load the story
|
|
continue;
|
|
}
|
|
|
|
const description = getMediaLink(src, media) + getMediaAreas(story.mediaAreas);
|
|
item.push({
|
|
title: story.caption ?? pubDate,
|
|
description,
|
|
pubDate,
|
|
link: `https://t.me/${username}/s/${story.id}`,
|
|
author: username,
|
|
});
|
|
}
|
|
|
|
return {
|
|
title: `Stories of @${username}`,
|
|
link: `https://t.me/${username}`,
|
|
item,
|
|
allowEmpty: ctx.req.param('id') === 'allow_empty',
|
|
description: `Stories of @${username} on Telegram`,
|
|
} as Data;
|
|
}
|