master
1import type { CommentData, CommentProvider } from './types';
2import { cleanCommentHtml } from './utils';
3import { asideConfig, commentConfig } from '@/config';
4import { t } from '@utils/i18n';
5
6export const ArtalkProvider: CommentProvider = {
7 async setup() {
8 const artalkConfig = commentConfig.artalk!;
9 const desiredCount = asideConfig.recentComment.count;
10
11 const apiBase = artalkConfig.serverURL;
12 const apiConfigUrl = new URL(`api/v2/conf`, apiBase).toString();
13
14 // fetch comment config first
15 const responseConfig = await fetch(apiConfigUrl, { method: 'GET' });
16 if (!responseConfig.ok) {
17 throw new Error('Failed to fetch comment config');
18 }
19 const configData: {
20 gravatar: {
21 mirror: string;
22 params: string;
23 };
24 } = (await responseConfig.json()).frontend_conf;
25
26 const getAvatarUrl = (email: string) => {
27 return `${configData.gravatar.mirror}${email}?${configData.gravatar.params}`;
28 };
29
30 // type for items returned by Artalk
31 type FetchItem = {
32 id: number;
33 nick: string;
34 content_marked: string;
35 date: string;
36 email_encrypted: string;
37 page_url: string;
38 is_collapsed: boolean;
39 visible: boolean;
40 }[];
41
42 const collected: FetchItem = [];
43 const seenIds = new Set<number>();
44 let attempt = 0;
45 let limit = desiredCount;
46 let prevCollectedCount = 0;
47
48 while (collected.length < desiredCount && attempt < 3) {
49 const apiCommentUrl = new URL(
50 `api/v2/stats/latest_comments?limit=${limit}`,
51 apiBase
52 ).toString();
53 const resp = await fetch(apiCommentUrl, { method: 'GET' });
54 if (!resp.ok) {
55 throw new Error('Failed to fetch recent comments');
56 }
57 const data: FetchItem = (await resp.json()).data || [];
58
59 let addedThisRound = 0;
60 for (const item of data) {
61 if (!item.visible) continue; // skip invisible comments
62 if (seenIds.has(item.id)) continue; // dedupe
63 collected.push(item);
64 seenIds.add(item.id);
65 addedThisRound++;
66 if (collected.length >= desiredCount) break;
67 }
68
69 // if increasing limit didn't yield any new visible comments, stop
70 if (addedThisRound === 0 && prevCollectedCount === collected.length) {
71 break;
72 }
73
74 prevCollectedCount = collected.length;
75 if (collected.length >= desiredCount) break;
76
77 // increase limit to try to fetch more next round
78 limit = limit + (desiredCount - collected.length);
79 attempt++;
80 }
81
82 // map to CommentData and return up to desiredCount
83 return collected.slice(0, desiredCount).map(
84 (item): CommentData => ({
85 avatarUrl: getAvatarUrl(item.email_encrypted),
86 commentContent: item.is_collapsed
87 ? t.info.commentAbbrs.collapsed()
88 : cleanCommentHtml(item.content_marked),
89 commentUrl: `${item.page_url}#atk-comment-${item.id}`,
90 author: item.nick,
91 time: new Date(item.date),
92 })
93 );
94 },
95};