kiku-agent 1.0.11 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -495
- package/package.json +12 -11
- package/public/fonts/GoogleSans-Bold.ttf +0 -0
- package/public/fonts/GoogleSans-Bold.woff2 +0 -0
- package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
- package/public/fonts/GoogleSans-Italic.ttf +0 -0
- package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
- package/public/fonts/GoogleSans-Medium.ttf +0 -0
- package/public/fonts/GoogleSans-Medium.woff2 +0 -0
- package/public/fonts/GoogleSans-Regular.ttf +0 -0
- package/public/fonts/GoogleSans-Regular.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
- package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
- package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
- package/public/fonts/google-sans-official.css +900 -0
- package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
- package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/general/jujingyi_portrait.jpg +0 -0
- package/public/images/jujingyi_portrait.jpg +0 -0
- package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
- package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
- package/public/images/series/detective-samoyeds/poster.jpg +0 -0
- package/public/images/series/everyday/poster.jpg +0 -0
- package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
- package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
- package/public/images/series/in-blossom/poster.jpg +0 -0
- package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
- package/public/images/series/lets-fight/poster-2.jpg +0 -0
- package/public/images/series/lets-fight/poster.jpg +0 -0
- package/public/images/series/love-emergency/poster.jpg +0 -0
- package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
- package/public/images/series/money-bastion/poster.jpg +0 -0
- package/public/images/series/mr-swimmer/poster.jpg +0 -0
- package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
- package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
- package/public/images/series/rebirth-for-you/poster.jpg +0 -0
- package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
- package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
- package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
- package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
- package/public/images/series/veil-of-shadows/poster.jpg +0 -0
- package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
- package/public/images/series/wanhua-sin/poster.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
- package/public/manifest.json +25 -0
- package/public/mcp-logo-128.png +0 -0
- package/public/mcp-logo-64.png +0 -0
- package/public/mcp-logo.svg +16 -0
- package/server/agent/agentCore.js +542 -74
- package/server/agent/appPaths.js +1 -0
- package/server/agent/audioPlayer.js +4 -2
- package/server/agent/citations.js +294 -0
- package/server/agent/clarificationAnalytics.js +477 -0
- package/server/agent/clarifyingQuestions.js +1089 -0
- package/server/agent/cli.js +549 -125
- package/server/agent/commandFeatures.js +1 -0
- package/server/agent/commandRegistry.js +946 -879
- package/server/agent/commands.js +546 -272
- package/server/agent/config.js +581 -412
- package/server/agent/contextCompactor.js +2 -2
- package/server/agent/debate.js +235 -0
- package/server/agent/eventBus.js +3 -0
- package/server/agent/feedbackLearning.js +217 -0
- package/server/agent/llmClient.js +512 -394
- package/server/agent/mcpClient.js +182 -32
- package/server/agent/memory.js +283 -224
- package/server/agent/modelManager.js +79 -39
- package/server/agent/profiles.js +178 -0
- package/server/agent/publicTools.js +22 -0
- package/server/agent/queryDetails.js +68 -8
- package/server/agent/quizData.js +398 -384
- package/server/agent/research.js +121 -79
- package/server/agent/sanitizer.js +104 -13
- package/server/agent/session.js +319 -11
- package/server/agent/subagent.js +8 -3
- package/server/agent/suggestionTui.js +294 -0
- package/server/agent/suggestions.js +69 -3
- package/server/agent/telemetry.js +119 -0
- package/server/agent/terminalImage.js +146 -27
- package/server/agent/theme.js +17 -0
- package/server/agent/toolRouter.js +16 -1
- package/server/agent/tools.js +424 -225
- package/server/agent/ui.js +486 -64
- package/server/agent/version.js +2 -4
- package/server/agent/webSearch.js +111 -11
- package/server/agent/youtubeAuth.js +24 -6
- package/server/apiRouter.js +4222 -0
- package/server/mcp/data/discography.js +25 -16
- package/server/mcp/data/gallery.js +308 -20
- package/server/mcp/data/profile.js +74 -44
- package/server/mcp/data/timeline.js +1 -1
- package/server/mcp/data/works.js +51 -17
- package/server/mcp/dataAdapter.js +5 -3
- package/server/mcp/handler.js +5 -11
- package/server/mcp/prompts.js +86 -0
- package/server/mcp/security.js +18 -15
- package/server/mcp/server.js +7 -2
- package/server/mcp/tools.js +66 -2
- package/server/web-chat/core/browserLauncher.js +18 -0
- package/server/web-chat/core/connection.js +194 -0
- package/server/web-chat/core/conversationContext.js +82 -0
- package/server/web-chat/core/directCatalogEngine.js +196 -0
- package/server/web-chat/core/dramaKnowledge.js +142 -0
- package/server/web-chat/core/errorHandler.js +51 -0
- package/server/web-chat/core/followUpEngine.js +182 -0
- package/server/web-chat/core/futureEventGuard.js +80 -0
- package/server/web-chat/core/memorySnapshot.js +12 -0
- package/server/web-chat/core/security.js +146 -0
- package/server/web-chat/core/toolInspector.js +52 -0
- package/server/web-chat/router.js +120 -0
- package/server/web-chat/routes/chatRoute.js +397 -0
- package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
- package/server/web-chat/routes/clarifyRoute.js +83 -0
- package/server/web-chat/routes/compareRoute.js +79 -0
- package/server/web-chat/routes/exportRoute.js +59 -0
- package/server/web-chat/routes/feedbackRoute.js +57 -0
- package/server/web-chat/routes/newsRoute.js +33 -0
- package/server/web-chat/routes/quizRoute.js +11 -0
- package/server/web-chat/routes/staticRoute.js +103 -0
- package/server/web-chat/routes/statusRoute.js +52 -0
- package/server/web-chat/routes/suggestRoute.js +77 -0
- package/server/web-chat/routes/timelineRoute.js +34 -0
- package/server/web-chat/routes/verifyRoute.js +57 -0
- package/server/web-chat/server.js +116 -0
- package/server/web-chat/ui/htmlBuilder.js +91 -0
- package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
- package/server/web-chat/ui/scripts/client.js +8397 -0
- package/server/web-chat/ui/scripts/kikuDb.js +177 -0
- package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
- package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
- package/server/web-chat/ui/styles/main.css +8568 -0
- package/server/web-chat/ui/templates/body.html +796 -0
- package/server/webChatHtml.js +1 -0
- package/server/webChatServer.js +6 -0
- package/src/data/discographyData.js +892 -866
- package/src/data/galleryData.js +1805 -561
- package/src/data/profileData.js +79 -2
- package/src/data/socialUpdatesData.js +106 -47
- package/src/data/timelineData.js +1 -1
- package/src/data/worksData.js +25 -4
- package/tokens.css +97 -0
- package/server/agent/updateManager.js +0 -456
package/server/agent/version.js
CHANGED
|
@@ -4,7 +4,5 @@ const packageManifest = JSON.parse(
|
|
|
4
4
|
readFileSync(new URL('../../package.json', import.meta.url), 'utf8')
|
|
5
5
|
);
|
|
6
6
|
|
|
7
|
-
export const PACKAGE_NAME = packageManifest.name;
|
|
8
|
-
export const PACKAGE_VERSION = packageManifest.version;
|
|
9
|
-
export const PACKAGE_ENGINES = Object.freeze({ ...(packageManifest.engines || {}) });
|
|
10
|
-
export const PACKAGE_UPDATE_METADATA = Object.freeze({ ...(packageManifest.kikuUpdate || {}) });
|
|
7
|
+
export const PACKAGE_NAME = packageManifest.name;
|
|
8
|
+
export const PACKAGE_VERSION = packageManifest.version;
|
|
@@ -3,6 +3,7 @@ import { execFile } from 'node:child_process';
|
|
|
3
3
|
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
+
import { assertSafeUrlHostname, sanitizeUrl } from './sanitizer.js';
|
|
6
7
|
|
|
7
8
|
let _cheerioLoad = null;
|
|
8
9
|
async function loadCheerio() {
|
|
@@ -41,6 +42,73 @@ function cleanHtml(html) {
|
|
|
41
42
|
.trim();
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
46
|
+
|
|
47
|
+
export async function safeFetch(inputUrl, options = {}, maxRedirects = 5) {
|
|
48
|
+
let currentUrl = String(inputUrl || '');
|
|
49
|
+
let currentOptions = { ...options, redirect: 'manual' };
|
|
50
|
+
|
|
51
|
+
for (let redirects = 0; redirects <= maxRedirects; redirects++) {
|
|
52
|
+
const sanitized = sanitizeUrl(currentUrl);
|
|
53
|
+
if (!sanitized.safe) throw new Error(`URL rejected by SSRF guard: ${sanitized.reason}`);
|
|
54
|
+
const dnsCheck = await assertSafeUrlHostname(sanitized.hostname);
|
|
55
|
+
if (!dnsCheck.safe) throw new Error(`URL rejected by SSRF guard: ${dnsCheck.reason}`);
|
|
56
|
+
|
|
57
|
+
const response = await fetch(sanitized.url.href, currentOptions);
|
|
58
|
+
if (!REDIRECT_STATUSES.has(response.status)) return response;
|
|
59
|
+
|
|
60
|
+
const location = response.headers.get('location');
|
|
61
|
+
if (!location) return response;
|
|
62
|
+
if (redirects === maxRedirects) throw new Error('Too many redirects');
|
|
63
|
+
|
|
64
|
+
const nextUrl = new URL(location, sanitized.url).href;
|
|
65
|
+
const nextOrigin = new URL(nextUrl).origin;
|
|
66
|
+
const headers = new Headers(currentOptions.headers || {});
|
|
67
|
+
if (nextOrigin !== sanitized.url.origin) {
|
|
68
|
+
headers.delete('authorization');
|
|
69
|
+
headers.delete('cookie');
|
|
70
|
+
headers.delete('proxy-authorization');
|
|
71
|
+
}
|
|
72
|
+
const method = String(currentOptions.method || 'GET').toUpperCase();
|
|
73
|
+
if (response.status === 303 || ((response.status === 301 || response.status === 302) && method === 'POST')) {
|
|
74
|
+
headers.delete('content-type');
|
|
75
|
+
currentOptions = { ...currentOptions, method: 'GET', body: undefined, headers };
|
|
76
|
+
} else {
|
|
77
|
+
currentOptions = { ...currentOptions, headers };
|
|
78
|
+
}
|
|
79
|
+
currentUrl = nextUrl;
|
|
80
|
+
}
|
|
81
|
+
throw new Error('Too many redirects');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function readResponseTextLimited(response, maxBytes) {
|
|
85
|
+
const limit = Math.max(1024, Math.min(Number(maxBytes) || 1024, 2_000_000));
|
|
86
|
+
const declared = Number(response.headers.get('content-length') || 0);
|
|
87
|
+
if (declared > limit) throw new Error(`Response exceeds ${limit} byte limit`);
|
|
88
|
+
if (!response.body?.getReader) {
|
|
89
|
+
const text = await response.text();
|
|
90
|
+
if (Buffer.byteLength(text, 'utf8') > limit) throw new Error(`Response exceeds ${limit} byte limit`);
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
const reader = response.body.getReader();
|
|
94
|
+
const decoder = new TextDecoder();
|
|
95
|
+
let bytes = 0;
|
|
96
|
+
let text = '';
|
|
97
|
+
try {
|
|
98
|
+
while (true) {
|
|
99
|
+
const { done, value } = await reader.read();
|
|
100
|
+
if (done) break;
|
|
101
|
+
bytes += value.byteLength;
|
|
102
|
+
if (bytes > limit) throw new Error(`Response exceeds ${limit} byte limit`);
|
|
103
|
+
text += decoder.decode(value, { stream: true });
|
|
104
|
+
}
|
|
105
|
+
text += decoder.decode();
|
|
106
|
+
return text;
|
|
107
|
+
} finally {
|
|
108
|
+
if (bytes > limit) await reader.cancel().catch(() => {});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
44
112
|
function detectLanguage(query) {
|
|
45
113
|
if (/[\u0E00-\u0E7F]/.test(query)) return 'th';
|
|
46
114
|
if (/[\u4E00-\u9FA5]/.test(query)) return 'zh';
|
|
@@ -276,18 +344,28 @@ export async function fetchWebpage(url, maxLength = 8000) {
|
|
|
276
344
|
return { status: 'error', message: 'Valid HTTP/HTTPS URL is required.' };
|
|
277
345
|
}
|
|
278
346
|
|
|
347
|
+
let parsedUrl;
|
|
348
|
+
try {
|
|
349
|
+
parsedUrl = new URL(url);
|
|
350
|
+
} catch {
|
|
351
|
+
return { status: 'error', message: 'Invalid URL format.' };
|
|
352
|
+
}
|
|
353
|
+
const ssrf = await assertSafeUrlHostname(parsedUrl.hostname);
|
|
354
|
+
if (!ssrf.safe) {
|
|
355
|
+
return { status: 'error', message: `URL rejected by SSRF guard: ${ssrf.reason}` };
|
|
356
|
+
}
|
|
357
|
+
|
|
279
358
|
try {
|
|
280
359
|
const controller = new AbortController();
|
|
281
360
|
const timeoutId = setTimeout(() => controller.abort(), 15000);
|
|
282
361
|
|
|
283
|
-
const res = await
|
|
362
|
+
const res = await safeFetch(url, {
|
|
284
363
|
headers: {
|
|
285
364
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
|
286
365
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
287
366
|
'Accept-Language': 'en-US,en;q=0.9,th;q=0.8,zh-CN;q=0.7'
|
|
288
367
|
},
|
|
289
|
-
signal: controller.signal
|
|
290
|
-
redirect: 'follow'
|
|
368
|
+
signal: controller.signal
|
|
291
369
|
});
|
|
292
370
|
|
|
293
371
|
clearTimeout(timeoutId);
|
|
@@ -300,7 +378,8 @@ export async function fetchWebpage(url, maxLength = 8000) {
|
|
|
300
378
|
const finalUrl = res.url || url;
|
|
301
379
|
|
|
302
380
|
if (contentType.includes('application/json')) {
|
|
303
|
-
const
|
|
381
|
+
const raw = await readResponseTextLimited(res, Math.max(64_000, maxLength * 8));
|
|
382
|
+
const json = JSON.parse(raw);
|
|
304
383
|
const text = JSON.stringify(json, null, 2);
|
|
305
384
|
return {
|
|
306
385
|
status: 'success',
|
|
@@ -311,7 +390,7 @@ export async function fetchWebpage(url, maxLength = 8000) {
|
|
|
311
390
|
};
|
|
312
391
|
}
|
|
313
392
|
|
|
314
|
-
const html = await res.
|
|
393
|
+
const html = await readResponseTextLimited(res, Math.max(256_000, maxLength * 16));
|
|
315
394
|
const cheerioLoad = await loadCheerio();
|
|
316
395
|
|
|
317
396
|
if (cheerioLoad) {
|
|
@@ -429,6 +508,17 @@ export async function fetchJson(url, options = {}) {
|
|
|
429
508
|
return { status: 'error', message: 'Valid HTTP/HTTPS URL is required.' };
|
|
430
509
|
}
|
|
431
510
|
|
|
511
|
+
let parsedUrl;
|
|
512
|
+
try {
|
|
513
|
+
parsedUrl = new URL(url);
|
|
514
|
+
} catch {
|
|
515
|
+
return { status: 'error', message: 'Invalid URL format.' };
|
|
516
|
+
}
|
|
517
|
+
const ssrf = await assertSafeUrlHostname(parsedUrl.hostname);
|
|
518
|
+
if (!ssrf.safe) {
|
|
519
|
+
return { status: 'error', message: `URL rejected by SSRF guard: ${ssrf.reason}` };
|
|
520
|
+
}
|
|
521
|
+
|
|
432
522
|
const {
|
|
433
523
|
method = 'GET',
|
|
434
524
|
headers = {},
|
|
@@ -456,19 +546,19 @@ export async function fetchJson(url, options = {}) {
|
|
|
456
546
|
fetchOptions.headers['Content-Type'] = 'application/json';
|
|
457
547
|
}
|
|
458
548
|
|
|
459
|
-
const res = await
|
|
549
|
+
const res = await safeFetch(url, fetchOptions);
|
|
460
550
|
clearTimeout(timeoutId);
|
|
461
551
|
|
|
462
552
|
const responseInfo = {
|
|
463
|
-
|
|
464
|
-
|
|
553
|
+
httpStatus: res.status,
|
|
554
|
+
httpStatusText: res.statusText,
|
|
465
555
|
contentType: res.headers.get('content-type') || '',
|
|
466
556
|
finalUrl: res.url || url
|
|
467
557
|
};
|
|
468
558
|
|
|
469
559
|
if (!res.ok) {
|
|
470
560
|
let errorBody = '';
|
|
471
|
-
try { errorBody = (await res
|
|
561
|
+
try { errorBody = (await readResponseTextLimited(res, 1000)).slice(0, 1000); } catch {}
|
|
472
562
|
return {
|
|
473
563
|
status: 'error',
|
|
474
564
|
...responseInfo,
|
|
@@ -477,7 +567,7 @@ export async function fetchJson(url, options = {}) {
|
|
|
477
567
|
};
|
|
478
568
|
}
|
|
479
569
|
|
|
480
|
-
const rawText = await res.
|
|
570
|
+
const rawText = await readResponseTextLimited(res, Math.min(1_000_000, maxResponseLength + 1));
|
|
481
571
|
let data;
|
|
482
572
|
try {
|
|
483
573
|
data = JSON.parse(rawText);
|
|
@@ -542,6 +632,17 @@ export async function screenshotWebpage(url, options = {}) {
|
|
|
542
632
|
return { status: 'error', message: 'Valid HTTP/HTTPS URL is required.' };
|
|
543
633
|
}
|
|
544
634
|
|
|
635
|
+
let parsedUrl;
|
|
636
|
+
try {
|
|
637
|
+
parsedUrl = new URL(url);
|
|
638
|
+
} catch {
|
|
639
|
+
return { status: 'error', message: 'Invalid URL format.' };
|
|
640
|
+
}
|
|
641
|
+
const ssrf = await assertSafeUrlHostname(parsedUrl.hostname);
|
|
642
|
+
if (!ssrf.safe) {
|
|
643
|
+
return { status: 'error', message: `URL rejected by SSRF guard: ${ssrf.reason}` };
|
|
644
|
+
}
|
|
645
|
+
|
|
545
646
|
const { width = 1280, height = 800, fullPage = false, timeoutMs = 20000 } = options;
|
|
546
647
|
const chromePath = findChromePath();
|
|
547
648
|
|
|
@@ -557,7 +658,6 @@ export async function screenshotWebpage(url, options = {}) {
|
|
|
557
658
|
const screenshotPath = join(tmpDir, screenshotFile);
|
|
558
659
|
const args = [
|
|
559
660
|
'--headless=new',
|
|
560
|
-
'--no-sandbox',
|
|
561
661
|
'--disable-gpu',
|
|
562
662
|
'--disable-dev-shm-usage',
|
|
563
663
|
'--disable-software-rasterizer',
|
|
@@ -122,8 +122,8 @@ function loadEnv() {
|
|
|
122
122
|
for (const line of text.split('\n')) {
|
|
123
123
|
const m = line.match(/^\s*([\w.-]+)\s*=\s*(.*)\s*$/);
|
|
124
124
|
if (!m) continue;
|
|
125
|
-
const key = m[1];
|
|
126
|
-
const val = m[2].replace(/^["']|["']$/g, '');
|
|
125
|
+
const key = m[1].trim();
|
|
126
|
+
const val = m[2].trim().replace(/^["']|["']$/g, '');
|
|
127
127
|
if (process.env[key] === undefined) process.env[key] = val;
|
|
128
128
|
}
|
|
129
129
|
} catch {}
|
|
@@ -233,12 +233,27 @@ export async function ensureYouTubeCookies(opts = {}) {
|
|
|
233
233
|
return _inFlightPromise;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
export function isCookieFileValid(filePath) {
|
|
237
|
+
try {
|
|
238
|
+
if (!filePath || typeof filePath !== 'string' || !fs.existsSync(filePath)) return false;
|
|
239
|
+
const stat = fs.statSync(filePath);
|
|
240
|
+
if (stat.size < 10) return false;
|
|
241
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
242
|
+
const lines = content.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'));
|
|
243
|
+
if (lines.length === 0) return false;
|
|
244
|
+
return lines.some(l => l.includes('\t') && l.split('\t').length >= 4);
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
236
250
|
async function _ensureYouTubeCookiesImpl(opts = {}) {
|
|
237
251
|
loadEnv();
|
|
238
252
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
253
|
+
const cookiesCandidate = (process.env.KIKU_YT_COOKIES && isCookieFileValid(process.env.KIKU_YT_COOKIES))
|
|
254
|
+
? process.env.KIKU_YT_COOKIES
|
|
255
|
+
: (isCookieFileValid(SECURE_COOKIE_PATH) ? SECURE_COOKIE_PATH : null);
|
|
256
|
+
if (cookiesCandidate && isCookieFileValid(cookiesCandidate)) {
|
|
242
257
|
_ensured = { ok: true, source: 'file', cookiesPath: cookiesCandidate };
|
|
243
258
|
return _ensured;
|
|
244
259
|
}
|
|
@@ -367,10 +382,13 @@ export function resetCookieCheck() {
|
|
|
367
382
|
}
|
|
368
383
|
|
|
369
384
|
export function getCookieStatus() {
|
|
385
|
+
const envCookies = (process.env.KIKU_YT_COOKIES && isCookieFileValid(process.env.KIKU_YT_COOKIES))
|
|
386
|
+
? process.env.KIKU_YT_COOKIES
|
|
387
|
+
: (isCookieFileValid(SECURE_COOKIE_PATH) ? SECURE_COOKIE_PATH : null);
|
|
370
388
|
return {
|
|
371
389
|
ensured: _ensured,
|
|
372
390
|
envBrowser: process.env.KIKU_YT_BROWSER || null,
|
|
373
|
-
envCookies
|
|
391
|
+
envCookies,
|
|
374
392
|
envPath: ENV_PATH,
|
|
375
393
|
authConfigPath: AUTH_CONFIG_PATH
|
|
376
394
|
};
|