feat(route/juejin): add aicoding (#20044)
This commit is contained in:
parent
3a219bb308
commit
5665b1eca3
|
|
@ -0,0 +1,102 @@
|
|||
import { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { getTagList, parseList, ProcessFeed } from './utils';
|
||||
import { Tag } from './types';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/aicoding/:tag?/:sort?',
|
||||
categories: ['programming'],
|
||||
example: '/juejin/aicoding',
|
||||
parameters: {
|
||||
tag: {
|
||||
description: '标签,留空为全部',
|
||||
options: [
|
||||
{ value: 'AI编程', label: 'AI编程' },
|
||||
{ value: 'Claude', label: 'Claude' },
|
||||
{ value: 'Trae', label: 'Trae' },
|
||||
{ value: 'MCP', label: 'MCP' },
|
||||
{ value: 'Cursor', label: 'Cursor' },
|
||||
{ value: 'Cline', label: 'Cline' },
|
||||
{ value: 'Github Copilot', label: 'Github Copilot' },
|
||||
{ value: 'bolt', label: 'bolt' },
|
||||
{ value: 'V0', label: 'V0' },
|
||||
{ value: 'replit', label: 'replit' },
|
||||
{ value: 'Warp', label: 'Warp' },
|
||||
{ value: 'Visual Studio IntelliCode', label: 'Visual Studio IntelliCode' },
|
||||
{ value: 'WindSurf', label: 'WindSurf' },
|
||||
{ value: '豆包MarsCode', label: '豆包MarsCode' },
|
||||
{ value: '通义灵码', label: '通义灵码' },
|
||||
{ value: 'Devin', label: 'Devin' },
|
||||
{ value: '文心快码', label: '文心快码' },
|
||||
{ value: 'imgcook', label: 'imgcook' },
|
||||
{ value: 'CodeWhisperer', label: 'CodeWhisperer' },
|
||||
{ value: 'Lovable', label: 'Lovable' },
|
||||
{ value: 'FittenCode', label: 'FittenCode' },
|
||||
{ value: 'Solo', label: 'Solo' },
|
||||
{ value: 'CodeFuse', label: 'CodeFuse' },
|
||||
{ value: 'Tabnine', label: 'Tabnine' },
|
||||
],
|
||||
},
|
||||
sort: {
|
||||
description: '排序方式,默认为最新发布',
|
||||
default: 'hot',
|
||||
options: [
|
||||
{ value: 'hot', label: '热门' },
|
||||
{ value: 'latest', label: '最新' },
|
||||
],
|
||||
},
|
||||
},
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['juejin.cn/books'],
|
||||
},
|
||||
],
|
||||
name: 'AI 编程',
|
||||
maintainers: ['TonyRL'],
|
||||
handler,
|
||||
url: 'aicoding.juejin.cn',
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const { tag, sort } = ctx.req.param();
|
||||
const sortType = sort === 'hot' ? 1 : 2;
|
||||
let tagList: {
|
||||
tag_id: string;
|
||||
tag: Tag;
|
||||
}[],
|
||||
currentTag: Tag | undefined,
|
||||
tagId: string | undefined;
|
||||
if (tag) {
|
||||
tagList = await getTagList();
|
||||
currentTag = tagList.find((item) => item.tag.tag_name === tag)?.tag;
|
||||
tagId = currentTag?.tag_id;
|
||||
}
|
||||
|
||||
const response = await ofetch('https://api.juejin.cn/content_api/v1/aicoding/content', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
sort_type: sortType,
|
||||
cursor: '',
|
||||
limit: 10,
|
||||
tag_ids: tagId ? [tagId] : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const list = parseList(response.data.map((item) => item.article_pack || item.short_msg_pack));
|
||||
const items = await ProcessFeed(list);
|
||||
|
||||
return {
|
||||
title: `${currentTag ? `${currentTag.tag_name} - ` : ''}AI 编程`,
|
||||
link: `https://aicoding.juejin.cn/?contentType=${sortType}${tagId ? `&tagId=${tagId}` : ''}`,
|
||||
image: currentTag ? currentTag.icon : 'https://lf-web-assets.juejin.cn/obj/juejin-web/goofy_deploy_edenx/toutiao-fe/xitu_juejin_aicoding/favicon.ico',
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
import ofetch from '@/utils/ofetch';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
// import MarkdownIt from 'markdown-it';
|
||||
// const md = MarkdownIt({
|
||||
// html: true,
|
||||
// });
|
||||
import crypto from 'node:crypto';
|
||||
import cache from '@/utils/cache';
|
||||
import { Category, Collection, Tag } from './types';
|
||||
|
|
@ -18,22 +14,6 @@ const s256 = (s1: Uint8Array, s2: string) => {
|
|||
return sha.digest('hex');
|
||||
};
|
||||
|
||||
// 加载文章页
|
||||
// async function loadContent(id) {
|
||||
// const response = await ofetch('https://api.juejin.cn/content_api/v1/article/detail', {
|
||||
// method: 'post',
|
||||
// body: {
|
||||
// article_id: id,
|
||||
// },
|
||||
// });
|
||||
// let description;
|
||||
// if (response.data) {
|
||||
// description = md.render(response.data.article_info.mark_content) || response.data.article_info.content;
|
||||
// }
|
||||
|
||||
// return { description };
|
||||
// }
|
||||
|
||||
const solveWafChallenge = (cs) => {
|
||||
const c = JSON.parse(Buffer.from(cs, 'base64').toString());
|
||||
const prefix = b64tou8a(c.v.a);
|
||||
|
|
@ -49,6 +29,11 @@ const solveWafChallenge = (cs) => {
|
|||
return Buffer.from(JSON.stringify(c)).toString('base64');
|
||||
};
|
||||
|
||||
export const generateUuid = () => {
|
||||
const e = (t) => (t ? (t ^ ((16 * 0.5) >> (t / 4))).toString(10) : '10000000-1000-4000-8000-100000000000'.replaceAll(/[018]/g, e));
|
||||
return e().replaceAll('-', '').slice(0, 19);
|
||||
};
|
||||
|
||||
export const getArticle = async (link) => {
|
||||
let response = await ofetch(link);
|
||||
let $ = cheerio.load(response);
|
||||
|
|
@ -70,16 +55,23 @@ export const getArticle = async (link) => {
|
|||
return $('.article-viewer').html();
|
||||
};
|
||||
|
||||
// const loadNews = async (link) => {
|
||||
// const response = await ofetch(link);
|
||||
// const $ = cheerio.load(response);
|
||||
// $('h1.title, .main-box .message').remove();
|
||||
// return { description: $('.main-box .article').html() };
|
||||
// };
|
||||
|
||||
export const parseList = (data) =>
|
||||
data.map((item) => {
|
||||
const isArticle = !!item.article_info;
|
||||
const isShortMsg = !!item.msg_Info;
|
||||
if (isShortMsg) {
|
||||
const msg = item.msg_Info;
|
||||
const contentMatches = msg.content.match(/\[(.*?)\]\s+(.*)/);
|
||||
return {
|
||||
title: contentMatches?.[1],
|
||||
description: contentMatches?.[2]?.trim(),
|
||||
pubDate: parseDate(msg.ctime, 'X'),
|
||||
author: item.author_user_info.user_name,
|
||||
link: `https://juejin.cn/pin/7548882523352186915${item.msg_id}`,
|
||||
category: [item.topic.title],
|
||||
isShortMsg: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: isArticle ? item.article_info.title : item.content_info.title,
|
||||
|
|
@ -95,36 +87,13 @@ export const ProcessFeed = (list) =>
|
|||
Promise.all(
|
||||
list.map((item) =>
|
||||
cache.tryGet(item.link, async () => {
|
||||
item.description = (await getArticle(item.link)) || item.description;
|
||||
item.description = item.isShortMsg ? item.description : (await getArticle(item.link)) || item.description;
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// export const ProcessFeed = (list, caches) =>
|
||||
// Promise.all(
|
||||
// list.map(async (item) => {
|
||||
// const isArticle = !!item.article_info;
|
||||
// const pubDate = parseDate((isArticle ? item.article_info.ctime : item.content_info.ctime) * 1000);
|
||||
// const link = `https://juejin.cn${isArticle ? '/post/' + item.article_id : '/news/' + item.content_id}`;
|
||||
// // 列表上提取到的信息
|
||||
// const single = {
|
||||
// title: isArticle ? item.article_info.title : item.content_info.title,
|
||||
// description: ((isArticle ? item.article_info.brief_content : item.content_info.brief) || '无描述').replaceAll(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, ''),
|
||||
// pubDate,
|
||||
// author: item.author_user_info.user_name,
|
||||
// link,
|
||||
// };
|
||||
|
||||
// // 使用tryGet方法从缓存获取内容。
|
||||
// // 当缓存中无法获取到链接内容的时候,则使用load方法加载文章内容。
|
||||
// const other = await caches.tryGet(link, () => (isArticle ? loadContent(item.article_id) : loadNews(link)));
|
||||
// // 合并解析后的结果集作为该篇文章最终的输出结果
|
||||
// return { ...single, ...other };
|
||||
// })
|
||||
// );
|
||||
|
||||
export const getCategoryBrief = () =>
|
||||
cache.tryGet('juejin:categoryBriefs', async () => {
|
||||
const response = await ofetch('https://api.juejin.cn/tag_api/v1/query_category_briefs');
|
||||
|
|
@ -152,3 +121,19 @@ export const getTag = (tag) =>
|
|||
});
|
||||
return response.data;
|
||||
}) as Promise<{ tag_id: string; tag: Tag }>;
|
||||
|
||||
export const getTagList = () =>
|
||||
cache.tryGet('juejin:tagList', async () => {
|
||||
const response = await ofetch('https://api.juejin.cn/tag_api/v1/query_tag_list', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
key_word: '',
|
||||
status: [0],
|
||||
id_type: 1101,
|
||||
sort_type: 0,
|
||||
cursor: '0',
|
||||
limit: 100,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
}) as Promise<{ tag_id: string; tag: Tag }[]>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue