RSSHub/lib/routes/behance/user.tsx

181 lines
6.2 KiB
TypeScript

import crypto from 'node:crypto';
import { raw } from 'hono/html';
import { renderToString } from 'hono/jsx/dom/server';
import type { Route } from '@/types';
import { ViewType } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
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> 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 };
const renderDescription = (description, modules) =>
renderToString(
<>
{description?.length ? (
<>
{description}
<br />
</>
) : null}
{modules?.map((module) => {
if (module.__typename === 'ImageModule') {
return (
<>
<figure>
<img src={module.imageSizes?.allAvailable?.[0]?.url} alt={module.altText ?? undefined} />
{module.caption?.length ? <figcaption>{module.caption}</figcaption> : null}
</figure>
<br />
</>
);
}
if (module.__typename === 'TextModule') {
return (
<>
{module.text ? raw(module.text) : null}
<br />
</>
);
}
if (module.__typename === 'MediaCollectionModule') {
return (
<>
{module.components?.map((comp) => (
<img src={`https://mir-s3-cdn-cf.behance.net/project_modules/source/${comp.filename}`} alt="" />
))}
<br />
</>
);
}
if (module.__typename === 'EmbedModule') {
const embed = module.fluidEmbed || module.originalEmbed;
return (
<>
{embed ? raw(embed) : null}
<br />
</>
);
}
return (
<>
UNHANDLED MODULE: {module.__typename}
<br />
</>
);
})}
</>
);
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 = renderDescription(project.description, 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,
};
}