rsshub
Version:
Make RSS Great Again!
109 lines (103 loc) • 3.83 kB
text/typescript
import { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import { load } from 'cheerio';
import markdownit from 'markdown-it';
import vm from 'node:vm';
interface Package {
name: string;
version: string;
entrypoint: string;
authors: Array<string>;
license: string;
description: string;
repository: string;
keywords: Array<string>;
compiler: string;
exclude: Array<string>;
size: number;
readme: string;
updatedAt: number;
releasedAt: number;
}
interface Context {
an: { exports: Array<Package> };
}
const GITHUBRAW_BASE = 'https://raw.githubusercontent.com';
const PKG_GITHUB_BASE = `${GITHUBRAW_BASE}/typst/packages/main/packages/preview`;
function fixImageSrc(src: string, env: Package) {
if (src.includes('://')) {
if (src.startsWith('https://typst.app/universe/package')) {
src = src.replaceAll('https://typst.app/universe/package', `${PKG_GITHUB_BASE}/${env.name}/${env.version}`);
} else if (src.startsWith('https://github.com/') && src.match(/\.(jpeg|jpg|gif|png|bmp|webp)$/gi)?.length) {
src = src.replace('https://github.com/', `${GITHUBRAW_BASE}/`);
}
} else {
const suffix = src.startsWith('/') ? '' : '/';
const package_base = `${PKG_GITHUB_BASE}/${env.name}/${env.version}${suffix}`;
const url = new URL(src, package_base);
src = url.toString();
}
return src;
}
export const route: Route = {
path: '/universe',
categories: ['program-update'],
example: '/typst/universe',
radar: [
{
source: ['typst.app/universe'],
target: '/universe',
},
],
name: 'Universe',
maintainers: ['HPDell'],
handler: async () => {
const targetUrl = 'https://typst.app/universe/search?kind=packages%2Ctemplates&packages=last-updated';
const page = await ofetch(targetUrl);
const $ = load(page);
const script = $('script')
.toArray()
.map((item) => item.attribs.src)
.find((item) => item && item.startsWith('/scripts/universe-search'));
const data: string = await ofetch(`https://typst.app${script}`, {
parseResponse: (txt) => txt,
});
let packages = data.match(/(an.exports=[\S\s]+);var ([$A-Z_a-z][\w$]*)=new Intl.Collator/)?.[1];
if (packages) {
packages = packages.slice(0, -2);
const context: Context = { an: { exports: [] } };
vm.createContext(context);
vm.runInContext(packages, context, {
displayErrors: true,
});
const md = markdownit('commonmark');
const items = context.an.exports.toSorted((a, b) => a.updatedAt - b.updatedAt);
const groups = new Map(items.map((it) => [it.name, it]));
const pkgs = [...groups.values()].map((item) => {
const $ = load(md.render(item.readme));
$('img').each((i, el) => {
const src = el.attribs.src;
el.attribs.src = fixImageSrc(src, item);
});
return {
title: `${item.name} (${item.version}) | ${item.description}`,
link: `https://typst.app/universe/package/${item.name}`,
description: $.html(),
pubDate: parseDate(item.updatedAt, 'X'),
};
});
return {
title: 'Typst universe',
link: targetUrl,
item: pkgs,
};
} else {
return {
title: 'Typst universe',
link: targetUrl,
item: [],
};
}
},
};