rsshub
Version:
Make RSS Great Again!
121 lines (108 loc) • 4.38 kB
text/typescript
import { Route, ViewType } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import crypto from 'node:crypto';
import path from 'node:path';
import { art } from '@/utils/render';
import { getAppreciatedQuery, getProfileProjectsAndSelectionsQuery, getProjectPageQuery } from './queries';
export const route: Route = {
path: '/:user/:type?',
categories: ['design'],
view: ViewType.Pictures,
example: '/behance/mishapetrick',
parameters: {
user: 'username',
type: {
description: 'type',
options: [
{ value: 'projects', label: 'projects' },
{ value: 'appreciated', label: 'appreciated' },
],
default: 'projects',
},
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: 'User Works',
maintainers: ['MisteryMonster'],
handler,
description: `Behance user's profile URL, like [https://www.behance.net/mishapetrick](https://www.behance.net/mishapetrick) the username will be \`mishapetrick\`。`,
};
const getUserProfile = async (nodes, user) =>
(await cache.tryGet(`behance:profile:${user}`, () => {
const profile = nodes.flatMap((item) => item.owners).find((owner) => owner.username === user);
return Promise.resolve({
displayName: profile.displayName,
id: profile.id,
link: profile.url,
image: profile.images.size_50.url.replace('/user/50/', '/user/source/'),
});
})) as { displayName: string; id: string; link: string; image: string };
async function handler(ctx) {
const { user, type = 'projects' } = ctx.req.param();
const uuid = crypto.randomUUID();
const headers = {
Cookie: `gk_suid=${Math.random().toString().slice(2, 10)}, gki=; originalReferrer=; bcp=${uuid}`,
'X-BCP': uuid,
'X-Requested-With': 'XMLHttpRequest',
};
const response = await ofetch('https://www.behance.net/v3/graphql', {
method: 'POST',
headers,
body: {
query: type === 'projects' ? getProfileProjectsAndSelectionsQuery : getAppreciatedQuery,
variables: {
username: user,
after: '',
},
},
});
const nodes = type === 'projects' ? response.data.user.profileProjects.nodes : response.data.user.appreciatedProjects.nodes;
const list = nodes.map((item) => ({
title: item.name,
link: item.url,
author: item.owners.map((owner) => owner.displayName).join(', '),
image: item.covers.size_202.url.replace('/202/', '/source/'),
pubDate: item.publishedOn ? parseDate(item.publishedOn, 'X') : undefined,
category: item.fields?.map((field) => field.label.toLowerCase()),
projectId: item.id,
}));
const profile = await getUserProfile(nodes, user);
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const response = await ofetch('https://www.behance.net/v3/graphql', {
method: 'POST',
headers,
body: {
query: getProjectPageQuery,
variables: {
projectId: item.projectId,
},
},
});
const project = response.data.project;
item.description = art(path.join(__dirname, 'templates/description.art'), {
description: project.description,
modules: project.allModules,
});
item.category = [...new Set([...(item.category || []), ...(project.tags?.map((tag) => tag.title.toLowerCase()) || [])])];
item.pubDate = item.pubDate || (project.publishedOn ? parseDate(project.publishedOn, 'X') : undefined);
return item;
})
)
);
return {
title: `${profile.displayName}'s ${type}`,
link: `https://www.behance.net/${user}/${type}`,
image: profile.image,
item: items,
};
}