yt-transcript-strapi-plugin 0.1.5 → 0.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/dist/_chunks/en-B4KWt_jN.js +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs +0 -1
- package/dist/admin/index.js +0 -1
- package/dist/admin/index.mjs +0 -1
- package/dist/server/index.js +299 -343
- package/dist/server/index.mjs +299 -343
- package/dist/server/src/index.d.ts +5 -0
- package/dist/server/src/mcp/tools/fetch-transcript.d.ts +6 -1
- package/dist/server/src/mcp/tools/find-transcripts.d.ts +6 -4
- package/dist/server/src/mcp/tools/get-transcript.d.ts +6 -3
- package/dist/server/src/mcp/tools/index.d.ts +0 -3
- package/dist/server/src/mcp/tools/list-transcripts.d.ts +6 -4
- package/dist/server/src/mcp/tools/search-transcript.d.ts +6 -2
- package/dist/server/src/services/ai-tools.d.ts +13 -0
- package/dist/server/src/services/index.d.ts +5 -0
- package/dist/server/src/tools/fetch-transcript.d.ts +2 -0
- package/dist/server/src/tools/find-transcripts.d.ts +2 -0
- package/dist/server/src/tools/get-transcript.d.ts +2 -0
- package/dist/server/src/tools/index.d.ts +19 -0
- package/dist/server/src/tools/list-transcripts.d.ts +2 -0
- package/dist/server/src/tools/search-transcript.d.ts +2 -0
- package/package.json +1 -1
- package/dist/_chunks/en-B4KWt_jN.js.map +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs.map +0 -1
- package/dist/admin/index.js.map +0 -1
- package/dist/admin/index.mjs.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/index.mjs.map +0 -1
|
@@ -95,6 +95,11 @@ declare const _default: {
|
|
|
95
95
|
saveTranscript(payload: Record<string, unknown>): Promise<import("@strapi/types/dist/modules/documents").AnyDocument>;
|
|
96
96
|
findTranscript(videoId: string): Promise<import("@strapi/types/dist/modules/documents").AnyDocument>;
|
|
97
97
|
};
|
|
98
|
+
'ai-tools': ({ strapi }: {
|
|
99
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
100
|
+
}) => {
|
|
101
|
+
getTools(): import("./tools").ToolDefinition[];
|
|
102
|
+
};
|
|
98
103
|
};
|
|
99
104
|
contentTypes: {
|
|
100
105
|
transcript: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Core } from '@strapi/strapi';
|
|
2
|
-
|
|
2
|
+
import { fetchTranscriptTool } from '../../tools';
|
|
3
|
+
export { fetchTranscriptTool };
|
|
4
|
+
export declare const fetchTranscriptToolMcp: {
|
|
3
5
|
name: string;
|
|
4
6
|
description: string;
|
|
5
7
|
inputSchema: {
|
|
@@ -13,6 +15,9 @@ export declare const fetchTranscriptTool: {
|
|
|
13
15
|
required: string[];
|
|
14
16
|
};
|
|
15
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* MCP handler -- delegates to canonical tool and wraps result in MCP envelope
|
|
20
|
+
*/
|
|
16
21
|
export declare function handleFetchTranscript(strapi: Core.Strapi, args: unknown): Promise<{
|
|
17
22
|
content: {
|
|
18
23
|
type: "text";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Core } from '@strapi/strapi';
|
|
2
|
-
|
|
2
|
+
import { findTranscriptsTool } from '../../tools';
|
|
3
|
+
export { findTranscriptsTool };
|
|
4
|
+
export declare const findTranscriptsToolMcp: {
|
|
3
5
|
name: string;
|
|
4
6
|
description: string;
|
|
5
7
|
inputSchema: {
|
|
@@ -24,22 +26,22 @@ export declare const findTranscriptsTool: {
|
|
|
24
26
|
page: {
|
|
25
27
|
type: string;
|
|
26
28
|
description: string;
|
|
27
|
-
default: number;
|
|
28
29
|
};
|
|
29
30
|
pageSize: {
|
|
30
31
|
type: string;
|
|
31
32
|
description: string;
|
|
32
|
-
default: number;
|
|
33
33
|
};
|
|
34
34
|
sort: {
|
|
35
35
|
type: string;
|
|
36
36
|
description: string;
|
|
37
|
-
default: string;
|
|
38
37
|
};
|
|
39
38
|
};
|
|
40
39
|
required: any[];
|
|
41
40
|
};
|
|
42
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* MCP handler -- delegates to canonical tool and wraps result in MCP envelope
|
|
44
|
+
*/
|
|
43
45
|
export declare function handleFindTranscripts(strapi: Core.Strapi, args: unknown): Promise<{
|
|
44
46
|
content: {
|
|
45
47
|
type: "text";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Core } from '@strapi/strapi';
|
|
2
|
-
|
|
2
|
+
import { getTranscriptTool } from '../../tools';
|
|
3
|
+
export { getTranscriptTool };
|
|
4
|
+
export declare const getTranscriptToolMcp: {
|
|
3
5
|
name: string;
|
|
4
6
|
description: string;
|
|
5
7
|
inputSchema: {
|
|
@@ -12,12 +14,10 @@ export declare const getTranscriptTool: {
|
|
|
12
14
|
includeFullTranscript: {
|
|
13
15
|
type: string;
|
|
14
16
|
description: string;
|
|
15
|
-
default: boolean;
|
|
16
17
|
};
|
|
17
18
|
includeTimecodes: {
|
|
18
19
|
type: string;
|
|
19
20
|
description: string;
|
|
20
|
-
default: boolean;
|
|
21
21
|
};
|
|
22
22
|
startTime: {
|
|
23
23
|
type: string;
|
|
@@ -39,6 +39,9 @@ export declare const getTranscriptTool: {
|
|
|
39
39
|
required: string[];
|
|
40
40
|
};
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* MCP handler -- delegates to canonical tool and wraps result in MCP envelope
|
|
44
|
+
*/
|
|
42
45
|
export declare function handleGetTranscript(strapi: Core.Strapi, args: unknown): Promise<{
|
|
43
46
|
content: {
|
|
44
47
|
type: "text";
|
|
@@ -21,17 +21,14 @@ export declare const tools: ({
|
|
|
21
21
|
page: {
|
|
22
22
|
type: string;
|
|
23
23
|
description: string;
|
|
24
|
-
default: number;
|
|
25
24
|
};
|
|
26
25
|
pageSize: {
|
|
27
26
|
type: string;
|
|
28
27
|
description: string;
|
|
29
|
-
default: number;
|
|
30
28
|
};
|
|
31
29
|
sort: {
|
|
32
30
|
type: string;
|
|
33
31
|
description: string;
|
|
34
|
-
default: string;
|
|
35
32
|
};
|
|
36
33
|
};
|
|
37
34
|
required: any[];
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Core } from '@strapi/strapi';
|
|
2
|
-
|
|
2
|
+
import { listTranscriptsTool } from '../../tools';
|
|
3
|
+
export { listTranscriptsTool };
|
|
4
|
+
export declare const listTranscriptsToolMcp: {
|
|
3
5
|
name: string;
|
|
4
6
|
description: string;
|
|
5
7
|
inputSchema: {
|
|
@@ -8,22 +10,22 @@ export declare const listTranscriptsTool: {
|
|
|
8
10
|
page: {
|
|
9
11
|
type: string;
|
|
10
12
|
description: string;
|
|
11
|
-
default: number;
|
|
12
13
|
};
|
|
13
14
|
pageSize: {
|
|
14
15
|
type: string;
|
|
15
16
|
description: string;
|
|
16
|
-
default: number;
|
|
17
17
|
};
|
|
18
18
|
sort: {
|
|
19
19
|
type: string;
|
|
20
20
|
description: string;
|
|
21
|
-
default: string;
|
|
22
21
|
};
|
|
23
22
|
};
|
|
24
23
|
required: any[];
|
|
25
24
|
};
|
|
26
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* MCP handler -- delegates to canonical tool and wraps result in MCP envelope
|
|
28
|
+
*/
|
|
27
29
|
export declare function handleListTranscripts(strapi: Core.Strapi, args: unknown): Promise<{
|
|
28
30
|
content: {
|
|
29
31
|
type: "text";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Core } from '@strapi/strapi';
|
|
2
|
-
|
|
2
|
+
import { searchTranscriptTool } from '../../tools';
|
|
3
|
+
export { searchTranscriptTool };
|
|
4
|
+
export declare const searchTranscriptToolMcp: {
|
|
3
5
|
name: string;
|
|
4
6
|
description: string;
|
|
5
7
|
inputSchema: {
|
|
@@ -16,12 +18,14 @@ export declare const searchTranscriptTool: {
|
|
|
16
18
|
maxResults: {
|
|
17
19
|
type: string;
|
|
18
20
|
description: string;
|
|
19
|
-
default: number;
|
|
20
21
|
};
|
|
21
22
|
};
|
|
22
23
|
required: string[];
|
|
23
24
|
};
|
|
24
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* MCP handler -- delegates to canonical tool and wraps result in MCP envelope
|
|
28
|
+
*/
|
|
25
29
|
export declare function handleSearchTranscript(strapi: Core.Strapi, args: unknown): Promise<{
|
|
26
30
|
content: {
|
|
27
31
|
type: "text";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Tools Service -- Adapter for AI SDK Discovery
|
|
3
|
+
*
|
|
4
|
+
* Imports canonical tool definitions from src/tools/ and exposes them
|
|
5
|
+
* via getTools() for the AI SDK discovery loop to register into its ToolRegistry.
|
|
6
|
+
*/
|
|
7
|
+
import type { Core } from '@strapi/strapi';
|
|
8
|
+
declare const _default: ({ strapi }: {
|
|
9
|
+
strapi: Core.Strapi;
|
|
10
|
+
}) => {
|
|
11
|
+
getTools(): import("../tools").ToolDefinition[];
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
|
@@ -18,5 +18,10 @@ declare const _default: {
|
|
|
18
18
|
saveTranscript(payload: Record<string, unknown>): Promise<import("@strapi/types/dist/modules/documents").AnyDocument>;
|
|
19
19
|
findTranscript(videoId: string): Promise<import("@strapi/types/dist/modules/documents").AnyDocument>;
|
|
20
20
|
};
|
|
21
|
+
'ai-tools': ({ strapi }: {
|
|
22
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
23
|
+
}) => {
|
|
24
|
+
getTools(): import("../tools").ToolDefinition[];
|
|
25
|
+
};
|
|
21
26
|
};
|
|
22
27
|
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Core } from '@strapi/strapi';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
export interface ToolDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
schema: z.ZodObject<any>;
|
|
7
|
+
execute: (args: unknown, strapi: Core.Strapi, context?: {
|
|
8
|
+
adminUserId?: number;
|
|
9
|
+
}) => Promise<unknown>;
|
|
10
|
+
internal?: boolean;
|
|
11
|
+
publicSafe?: boolean;
|
|
12
|
+
}
|
|
13
|
+
import { fetchTranscriptTool } from './fetch-transcript';
|
|
14
|
+
import { listTranscriptsTool } from './list-transcripts';
|
|
15
|
+
import { getTranscriptTool } from './get-transcript';
|
|
16
|
+
import { searchTranscriptTool } from './search-transcript';
|
|
17
|
+
import { findTranscriptsTool } from './find-transcripts';
|
|
18
|
+
export declare const tools: ToolDefinition[];
|
|
19
|
+
export { fetchTranscriptTool, listTranscriptsTool, getTranscriptTool, searchTranscriptTool, findTranscriptsTool, };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"en-B4KWt_jN.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"en-Byx4XI2L.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/dist/admin/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../admin/src/pluginId.ts","../../admin/src/utils/getTranslation.ts","../../admin/src/components/Initializer.tsx","../../admin/src/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'yt-transcript-strapi-plugin';\n","import { PLUGIN_ID } from '../pluginId';\n\nconst getTranslation = (id: string) => `${PLUGIN_ID}.${id}`;\n\nexport { getTranslation };\n","import { useEffect, useRef } from 'react';\n\nimport { PLUGIN_ID } from '../pluginId';\n\ntype InitializerProps = {\n setPlugin: (id: string) => void;\n};\n\nconst Initializer = ({ setPlugin }: InitializerProps) => {\n const ref = useRef(setPlugin);\n\n useEffect(() => {\n ref.current(PLUGIN_ID);\n }, []);\n\n return null;\n};\n\nexport { Initializer };\n","import { getTranslation } from './utils/getTranslation';\nimport { PLUGIN_ID } from './pluginId';\nimport { Initializer } from './components/Initializer';\n// import { PluginIcon } from './components/PluginIcon';\n\nexport default {\n register(app: any) {\n // app.addMenuLink({\n // to: `plugins/${PLUGIN_ID}`,\n // icon: PluginIcon,\n // intlLabel: {\n // id: `${PLUGIN_ID}.plugin.name`,\n // defaultMessage: PLUGIN_ID,\n // },\n // Component: async () => {\n // const { App } = await import('./pages/App');\n\n // return App;\n // },\n // });\n\n app.registerPlugin({\n id: PLUGIN_ID,\n initializer: Initializer,\n isReady: false,\n name: PLUGIN_ID,\n });\n },\n\n async registerTrads(app: any) {\n const { locales } = app;\n\n const importedTranslations = await Promise.all(\n (locales as string[]).map((locale) => {\n return import(`./translations/${locale}.json`)\n .then(({ default: data }) => {\n return {\n data: getTranslation(data),\n locale,\n };\n })\n .catch(() => {\n return {\n data: {},\n locale,\n };\n });\n })\n );\n\n return importedTranslations;\n },\n};\n"],"names":["useRef","useEffect"],"mappings":";;;;;;;;;;;;;;;;;;AAAO,MAAM,YAAY;ACEzB,MAAM,iBAAiB,CAAC,OAAe,GAAG,SAAS,IAAI,EAAE;ACMzD,MAAM,cAAc,CAAC,EAAE,gBAAkC;AACvD,QAAM,MAAMA,MAAAA,OAAO,SAAS;AAE5BC,QAAAA,UAAU,MAAM;AACd,QAAI,QAAQ,SAAS;AAAA,EACvB,GAAG,CAAA,CAAE;AAEL,SAAO;AACT;ACXA,MAAA,QAAe;AAAA,EACb,SAAS,KAAU;AAejB,QAAI,eAAe;AAAA,MACjB,IAAI;AAAA,MACJ,aAAa;AAAA,MACb,SAAS;AAAA,MACT,MAAM;AAAA,IAAA,CACP;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,KAAU;AAC5B,UAAM,EAAE,YAAY;AAEpB,UAAM,uBAAuB,MAAM,QAAQ;AAAA,MACxC,QAAqB,IAAI,CAAC,WAAW;AACpC,eAAO,qCAAA,uBAAA,OAAA,EAAA,0BAAA,MAAA,QAAA,QAAA,EAAA,KAAA,MAAA,QAAA,2BAAA,CAAA,EAAA,CAAA,GAAA,kBAAA,MAAA,SAAA,CAAA,EACJ,KAAK,CAAC,EAAE,SAAS,WAAW;AAC3B,iBAAO;AAAA,YACL,MAAM,eAAe,IAAI;AAAA,YACzB;AAAA,UAAA;AAAA,QAEJ,CAAC,EACA,MAAM,MAAM;AACX,iBAAO;AAAA,YACL,MAAM,CAAA;AAAA,YACN;AAAA,UAAA;AAAA,QAEJ,CAAC;AAAA,MACL,CAAC;AAAA,IAAA;AAGH,WAAO;AAAA,EACT;AACF;;"}
|
package/dist/admin/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../admin/src/pluginId.ts","../../admin/src/utils/getTranslation.ts","../../admin/src/components/Initializer.tsx","../../admin/src/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'yt-transcript-strapi-plugin';\n","import { PLUGIN_ID } from '../pluginId';\n\nconst getTranslation = (id: string) => `${PLUGIN_ID}.${id}`;\n\nexport { getTranslation };\n","import { useEffect, useRef } from 'react';\n\nimport { PLUGIN_ID } from '../pluginId';\n\ntype InitializerProps = {\n setPlugin: (id: string) => void;\n};\n\nconst Initializer = ({ setPlugin }: InitializerProps) => {\n const ref = useRef(setPlugin);\n\n useEffect(() => {\n ref.current(PLUGIN_ID);\n }, []);\n\n return null;\n};\n\nexport { Initializer };\n","import { getTranslation } from './utils/getTranslation';\nimport { PLUGIN_ID } from './pluginId';\nimport { Initializer } from './components/Initializer';\n// import { PluginIcon } from './components/PluginIcon';\n\nexport default {\n register(app: any) {\n // app.addMenuLink({\n // to: `plugins/${PLUGIN_ID}`,\n // icon: PluginIcon,\n // intlLabel: {\n // id: `${PLUGIN_ID}.plugin.name`,\n // defaultMessage: PLUGIN_ID,\n // },\n // Component: async () => {\n // const { App } = await import('./pages/App');\n\n // return App;\n // },\n // });\n\n app.registerPlugin({\n id: PLUGIN_ID,\n initializer: Initializer,\n isReady: false,\n name: PLUGIN_ID,\n });\n },\n\n async registerTrads(app: any) {\n const { locales } = app;\n\n const importedTranslations = await Promise.all(\n (locales as string[]).map((locale) => {\n return import(`./translations/${locale}.json`)\n .then(({ default: data }) => {\n return {\n data: getTranslation(data),\n locale,\n };\n })\n .catch(() => {\n return {\n data: {},\n locale,\n };\n });\n })\n );\n\n return importedTranslations;\n },\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAO,MAAM,YAAY;ACEzB,MAAM,iBAAiB,CAAC,OAAe,GAAG,SAAS,IAAI,EAAE;ACMzD,MAAM,cAAc,CAAC,EAAE,gBAAkC;AACvD,QAAM,MAAM,OAAO,SAAS;AAE5B,YAAU,MAAM;AACd,QAAI,QAAQ,SAAS;AAAA,EACvB,GAAG,CAAA,CAAE;AAEL,SAAO;AACT;ACXA,MAAA,QAAe;AAAA,EACb,SAAS,KAAU;AAejB,QAAI,eAAe;AAAA,MACjB,IAAI;AAAA,MACJ,aAAa;AAAA,MACb,SAAS;AAAA,MACT,MAAM;AAAA,IAAA,CACP;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,KAAU;AAC5B,UAAM,EAAE,YAAY;AAEpB,UAAM,uBAAuB,MAAM,QAAQ;AAAA,MACxC,QAAqB,IAAI,CAAC,WAAW;AACpC,eAAO,qCAAA,uBAAA,OAAA,EAAA,0BAAA,MAAA,OAAA,4BAAA,EAAA,CAAA,GAAA,kBAAA,MAAA,SAAA,CAAA,EACJ,KAAK,CAAC,EAAE,SAAS,WAAW;AAC3B,iBAAO;AAAA,YACL,MAAM,eAAe,IAAI;AAAA,YACzB;AAAA,UAAA;AAAA,QAEJ,CAAC,EACA,MAAM,MAAM;AACX,iBAAO;AAAA,YACL,MAAM,CAAA;AAAA,YACN;AAAA,UAAA;AAAA,QAEJ,CAAC;AAAA,MACL,CAAC;AAAA,IAAA;AAGH,WAAO;AAAA,EACT;AACF;"}
|
package/dist/server/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../server/src/mcp/schemas/index.ts","../../server/src/utils/extract-youtube-id.ts","../../server/src/mcp/tools/fetch-transcript.ts","../../server/src/mcp/tools/list-transcripts.ts","../../server/src/mcp/tools/get-transcript.ts","../../server/src/mcp/tools/search-transcript.ts","../../server/src/mcp/tools/find-transcripts.ts","../../server/src/mcp/tools/index.ts","../../server/src/mcp/server.ts","../../server/src/bootstrap.ts","../../server/src/destroy.ts","../../server/src/register.ts","../../server/src/config/index.ts","../../server/src/content-types/transcript/index.ts","../../server/src/content-types/index.ts","../../server/src/controllers/controller.ts","../../server/src/controllers/mcp.ts","../../server/src/controllers/index.ts","../../server/src/middlewares/index.ts","../../server/src/policies/index.ts","../../server/src/routes/content-api.ts","../../server/src/routes/admin.ts","../../server/src/routes/index.ts","../../server/src/utils/fetch-transcript.ts","../../server/src/services/service.ts","../../server/src/services/index.ts","../../server/src/index.ts"],"sourcesContent":["import { z } from 'zod';\n\n// Schema for fetch_transcript tool\nexport const FetchTranscriptSchema = z.object({\n videoId: z.string().min(1, 'Video ID or URL is required'),\n});\n\n// Schema for list_transcripts tool\nexport const ListTranscriptsSchema = z.object({\n page: z.number().int().min(1).optional().default(1),\n pageSize: z.number().int().min(1).max(100).optional().default(25),\n sort: z.string().optional().default('createdAt:desc'),\n});\n\n// Schema for get_transcript tool\nexport const GetTranscriptSchema = z.object({\n videoId: z.string().min(1, 'Video ID is required'),\n includeFullTranscript: z.boolean().optional().default(false),\n includeTimecodes: z.boolean().optional().default(false),\n startTime: z.number().min(0).optional(),\n endTime: z.number().min(0).optional(),\n chunkIndex: z.number().int().min(0).optional(),\n chunkSize: z.number().int().min(30).optional(),\n});\n\n// Schema for search_transcript tool\nexport const SearchTranscriptSchema = z.object({\n videoId: z.string().min(1, 'Video ID is required'),\n query: z.string().min(1, 'Search query is required'),\n maxResults: z.number().int().min(1).max(20).optional().default(5),\n});\n\n// Schema for find_transcripts tool\nexport const FindTranscriptsSchema = z.object({\n query: z.string().optional(),\n videoId: z.string().optional(),\n title: z.string().optional(),\n includeFullContent: z.boolean().optional().default(false),\n page: z.number().int().min(1).optional().default(1),\n pageSize: z.number().int().min(1).max(100).optional().default(25),\n sort: z.string().optional().default('createdAt:desc'),\n});\n\n// Type exports\nexport type FetchTranscriptInput = z.infer<typeof FetchTranscriptSchema>;\nexport type ListTranscriptsInput = z.infer<typeof ListTranscriptsSchema>;\nexport type GetTranscriptInput = z.infer<typeof GetTranscriptSchema>;\nexport type SearchTranscriptInput = z.infer<typeof SearchTranscriptSchema>;\nexport type FindTranscriptsInput = z.infer<typeof FindTranscriptsSchema>;\n\n// All schemas for easy lookup\nexport const ToolSchemas = {\n fetch_transcript: FetchTranscriptSchema,\n list_transcripts: ListTranscriptsSchema,\n get_transcript: GetTranscriptSchema,\n search_transcript: SearchTranscriptSchema,\n find_transcripts: FindTranscriptsSchema,\n} as const;\n\ntype ToolName = keyof typeof ToolSchemas;\n\n// Validation helper function\nexport function validateToolInput<T extends ToolName>(\n toolName: T,\n input: unknown\n): z.infer<(typeof ToolSchemas)[T]> {\n const schema = ToolSchemas[toolName];\n const result = schema.safeParse(input);\n\n if (!result.success) {\n const errorMessages = result.error.issues.map((err) => {\n const path = err.path.length > 0 ? `${err.path.join('.')}: ` : '';\n return `${path}${err.message}`;\n });\n throw new Error(`Validation failed for ${toolName}:\\n${errorMessages.join('\\n')}`);\n }\n\n return result.data as z.infer<(typeof ToolSchemas)[T]>;\n}\n","export function extractYouTubeID(urlOrID: string): string | null {\n // Regular expression for YouTube ID format\n const regExpID = /^[a-zA-Z0-9_-]{11}$/;\n\n // Check if the input is a YouTube ID\n if (regExpID.test(urlOrID)) {\n return urlOrID;\n }\n\n // Regular expression for standard YouTube links\n const regExpStandard = /youtube\\.com\\/watch\\?v=([a-zA-Z0-9_-]+)/;\n\n // Regular expression for YouTube Shorts links\n const regExpShorts = /youtube\\.com\\/shorts\\/([a-zA-Z0-9_-]+)/;\n\n // Check for standard YouTube link\n const matchStandard = urlOrID.match(regExpStandard);\n if (matchStandard) {\n return matchStandard[1];\n }\n\n // Check for YouTube Shorts link\n const matchShorts = urlOrID.match(regExpShorts);\n if (matchShorts) {\n return matchShorts[1];\n }\n\n // Return null if no match is found\n return null;\n}","import type { Core } from '@strapi/strapi';\nimport { validateToolInput } from '../schemas';\nimport { extractYouTubeID } from '../../utils/extract-youtube-id';\n\ninterface PluginConfig {\n previewLength?: number;\n}\n\ninterface TimecodeEntry {\n start: number;\n end: number;\n duration: number;\n}\n\nexport const fetchTranscriptTool = {\n name: 'fetch_transcript',\n description:\n 'Fetch a transcript from YouTube for a given video ID or URL. The transcript is saved to the database. Returns metadata and preview only to avoid context overflow. Use get_transcript to retrieve content.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n videoId: {\n type: 'string',\n description: 'YouTube video ID (e.g., \"dQw4w9WgXcQ\") or full YouTube URL',\n },\n },\n required: ['videoId'],\n },\n};\n\n/**\n * Calculate video duration from timecodes\n */\nfunction getVideoDurationMs(timecodes: TimecodeEntry[]): number {\n if (!timecodes || timecodes.length === 0) return 0;\n const lastEntry = timecodes[timecodes.length - 1];\n return lastEntry.end || lastEntry.start + (lastEntry.duration || 0);\n}\n\n/**\n * Format milliseconds as MM:SS or HH:MM:SS\n */\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n}\n\n/**\n * Build metadata response for a transcript\n */\nfunction buildMetadataResponse(\n transcript: Record<string, unknown>,\n previewLength: number,\n cached: boolean\n) {\n const fullText = (transcript.fullTranscript as string) || '';\n const timecodes = (transcript.transcriptWithTimeCodes as TimecodeEntry[]) || [];\n const durationMs = getVideoDurationMs(timecodes);\n const wordCount = fullText.split(/\\s+/).length;\n\n const preview = fullText.length > previewLength\n ? fullText.substring(0, previewLength) + '...'\n : fullText;\n\n return {\n message: cached ? 'Transcript already exists in database' : 'Transcript fetched and saved successfully',\n cached,\n videoId: transcript.videoId,\n title: transcript.title,\n metadata: {\n wordCount,\n characterCount: fullText.length,\n duration: formatTime(durationMs),\n durationSeconds: Math.floor(durationMs / 1000),\n },\n preview,\n usage: 'Use get_transcript with videoId to retrieve full content, specific time ranges, or paginated chunks.',\n };\n}\n\nexport async function handleFetchTranscript(strapi: Core.Strapi, args: unknown) {\n const validatedArgs = validateToolInput('fetch_transcript', args);\n const { videoId: videoIdOrUrl } = validatedArgs;\n\n // Get config\n const pluginConfig = await strapi.config.get('plugin::yt-transcript-strapi-plugin') as PluginConfig | undefined;\n const previewLength = pluginConfig?.previewLength || 500;\n\n // Extract the video ID from URL or use as-is\n const videoId = extractYouTubeID(videoIdOrUrl);\n if (!videoId) {\n throw new Error(`Invalid YouTube video ID or URL: \"${videoIdOrUrl}\". Please provide a valid 11-character video ID or YouTube URL.`);\n }\n\n const service = strapi.plugin('yt-transcript-strapi-plugin').service('service');\n\n // Check if transcript already exists in database\n const existingTranscript = await service.findTranscript(videoId);\n if (existingTranscript) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n buildMetadataResponse(existingTranscript, previewLength, true),\n null,\n 2\n ),\n },\n ],\n };\n }\n\n // Fetch transcript from YouTube\n const transcriptData = await service.getTranscript(videoId);\n\n if (!transcriptData || !transcriptData.fullTranscript) {\n throw new Error('No transcript data returned from YouTube');\n }\n\n // Prepare the payload\n const payload: Record<string, unknown> = {\n videoId,\n title: transcriptData.title || `YouTube Video ${videoId}`,\n fullTranscript: transcriptData.fullTranscript,\n transcriptWithTimeCodes: transcriptData.transcriptWithTimeCodes,\n };\n\n // Save to database\n const savedTranscript = await service.saveTranscript(payload);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n buildMetadataResponse(savedTranscript as Record<string, unknown>, previewLength, false),\n null,\n 2\n ),\n },\n ],\n };\n}\n","import type { Core } from '@strapi/strapi';\nimport { validateToolInput } from '../schemas';\n\nexport const listTranscriptsTool = {\n name: 'list_transcripts',\n description:\n 'List all saved YouTube transcripts from the database. Supports pagination and sorting.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n page: {\n type: 'number',\n description: 'Page number (starts at 1)',\n default: 1,\n },\n pageSize: {\n type: 'number',\n description: 'Number of items per page (max 100)',\n default: 25,\n },\n sort: {\n type: 'string',\n description: 'Sort order (e.g., \"createdAt:desc\", \"title:asc\")',\n default: 'createdAt:desc',\n },\n },\n required: [],\n },\n};\n\nexport async function handleListTranscripts(strapi: Core.Strapi, args: unknown) {\n const validatedArgs = validateToolInput('list_transcripts', args);\n const { page, pageSize, sort } = validatedArgs;\n\n const start = (page - 1) * pageSize;\n\n // Query transcripts from database\n const transcripts = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findMany({\n sort,\n limit: pageSize,\n start,\n fields: ['id', 'documentId', 'title', 'videoId', 'createdAt', 'updatedAt'],\n });\n\n // Get total count\n const allTranscripts = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findMany({});\n const total = allTranscripts.length;\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n data: transcripts,\n pagination: {\n page,\n pageSize,\n total,\n pageCount: Math.ceil(total / pageSize),\n },\n },\n null,\n 2\n ),\n },\n ],\n };\n}\n","import type { Core } from '@strapi/strapi';\nimport { validateToolInput } from '../schemas';\nimport { extractYouTubeID } from '../../utils/extract-youtube-id';\n\ninterface PluginConfig {\n chunkSizeSeconds?: number;\n previewLength?: number;\n maxFullTranscriptLength?: number;\n}\n\ninterface TimecodeEntry {\n start: number;\n end: number;\n text: string;\n duration: number;\n}\n\nexport const getTranscriptTool = {\n name: 'get_transcript',\n description:\n 'Get a saved transcript by YouTube video ID. Returns metadata and preview by default. Use parameters to get full content or specific time ranges to avoid context overflow.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n videoId: {\n type: 'string',\n description: 'YouTube video ID (e.g., \"dQw4w9WgXcQ\") or full YouTube URL',\n },\n includeFullTranscript: {\n type: 'boolean',\n description: 'Include the complete transcript text. Warning: may cause context overflow for long videos. Default: false',\n default: false,\n },\n includeTimecodes: {\n type: 'boolean',\n description: 'Include the transcript with timecodes array. Warning: significantly increases response size. Default: false',\n default: false,\n },\n startTime: {\n type: 'number',\n description: 'Start time in seconds for fetching a specific portion of the transcript',\n },\n endTime: {\n type: 'number',\n description: 'End time in seconds for fetching a specific portion of the transcript',\n },\n chunkIndex: {\n type: 'number',\n description: 'Chunk index (0-based) when paginating through transcript. Use with chunkSize to paginate through long videos.',\n },\n chunkSize: {\n type: 'number',\n description: 'Chunk size in seconds. Overrides config default. Use with chunkIndex for pagination.',\n },\n },\n required: ['videoId'],\n },\n};\n\n/**\n * Get transcript text for a specific time range from timecoded entries\n */\nfunction getTranscriptForTimeRange(\n timecodes: TimecodeEntry[],\n startTimeMs: number,\n endTimeMs: number\n): { text: string; entries: TimecodeEntry[] } {\n const entries = timecodes.filter(\n (entry) => entry.start >= startTimeMs && entry.start < endTimeMs\n );\n const text = entries.map((e) => e.text).join(' ');\n return { text, entries };\n}\n\n/**\n * Calculate video duration from timecodes\n */\nfunction getVideoDurationMs(timecodes: TimecodeEntry[]): number {\n if (!timecodes || timecodes.length === 0) return 0;\n const lastEntry = timecodes[timecodes.length - 1];\n return lastEntry.end || lastEntry.start + (lastEntry.duration || 0);\n}\n\n/**\n * Format milliseconds as MM:SS or HH:MM:SS\n */\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n}\n\nexport async function handleGetTranscript(strapi: Core.Strapi, args: unknown) {\n const validatedArgs = validateToolInput('get_transcript', args);\n const {\n videoId: videoIdOrUrl,\n includeFullTranscript,\n includeTimecodes,\n startTime,\n endTime,\n chunkIndex,\n chunkSize: chunkSizeOverride,\n } = validatedArgs;\n\n // Get config\n const pluginConfig = await strapi.config.get('plugin::yt-transcript-strapi-plugin') as PluginConfig | undefined;\n const defaultChunkSize = pluginConfig?.chunkSizeSeconds || 300;\n const previewLength = pluginConfig?.previewLength || 500;\n const maxFullTranscriptLength = pluginConfig?.maxFullTranscriptLength || 50000;\n const chunkSizeSeconds = chunkSizeOverride || defaultChunkSize;\n\n // Extract the video ID from URL or use as-is\n const videoId = extractYouTubeID(videoIdOrUrl);\n if (!videoId) {\n throw new Error(`Invalid YouTube video ID or URL: \"${videoIdOrUrl}\". Please provide a valid 11-character video ID or YouTube URL.`);\n }\n\n const service = strapi.plugin('yt-transcript-strapi-plugin').service('service');\n\n // Find transcript in database\n const transcript = await service.findTranscript(videoId);\n\n if (!transcript) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n error: true,\n message: `No transcript found for video ID: ${videoId}. Use fetch_transcript to fetch it from YouTube first.`,\n videoId,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n const timecodes: TimecodeEntry[] = transcript.transcriptWithTimeCodes || [];\n const fullText: string = transcript.fullTranscript || '';\n const durationMs = getVideoDurationMs(timecodes);\n const totalChunks = Math.ceil(durationMs / (chunkSizeSeconds * 1000));\n const wordCount = fullText.split(/\\s+/).length;\n\n // Build response based on requested parameters\n const response: Record<string, unknown> = {\n videoId: transcript.videoId,\n title: transcript.title,\n metadata: {\n wordCount,\n characterCount: fullText.length,\n duration: formatTime(durationMs),\n durationSeconds: Math.floor(durationMs / 1000),\n totalChunks,\n chunkSizeSeconds,\n },\n };\n\n // Handle time range request\n if (startTime !== undefined || endTime !== undefined) {\n const startMs = (startTime || 0) * 1000;\n const endMs = endTime !== undefined ? endTime * 1000 : durationMs;\n\n const { text, entries } = getTranscriptForTimeRange(timecodes, startMs, endMs);\n\n response.timeRange = {\n startTime: startTime || 0,\n endTime: endTime || Math.floor(durationMs / 1000),\n startFormatted: formatTime(startMs),\n endFormatted: formatTime(endMs),\n };\n response.transcript = text;\n\n if (includeTimecodes) {\n response.transcriptWithTimeCodes = entries;\n }\n }\n // Handle chunk request\n else if (chunkIndex !== undefined) {\n const chunkStartMs = chunkIndex * chunkSizeSeconds * 1000;\n const chunkEndMs = Math.min((chunkIndex + 1) * chunkSizeSeconds * 1000, durationMs);\n\n if (chunkStartMs >= durationMs) {\n response.error = `Chunk index ${chunkIndex} is out of range. Total chunks: ${totalChunks} (0-${totalChunks - 1})`;\n } else {\n const { text, entries } = getTranscriptForTimeRange(timecodes, chunkStartMs, chunkEndMs);\n\n response.chunk = {\n index: chunkIndex,\n totalChunks,\n startTime: Math.floor(chunkStartMs / 1000),\n endTime: Math.floor(chunkEndMs / 1000),\n startFormatted: formatTime(chunkStartMs),\n endFormatted: formatTime(chunkEndMs),\n };\n response.transcript = text;\n\n if (includeTimecodes) {\n response.transcriptWithTimeCodes = entries;\n }\n\n // Add navigation hints\n if (chunkIndex < totalChunks - 1) {\n response.nextChunk = `Use chunkIndex: ${chunkIndex + 1} to get the next portion`;\n }\n if (chunkIndex > 0) {\n response.previousChunk = `Use chunkIndex: ${chunkIndex - 1} to get the previous portion`;\n }\n }\n }\n // Handle full transcript request OR auto-load if small enough\n else if (includeFullTranscript || fullText.length <= maxFullTranscriptLength) {\n response.transcript = fullText;\n\n if (includeTimecodes) {\n response.transcriptWithTimeCodes = timecodes;\n }\n\n // Only show warning if explicitly requested full transcript for a large video\n if (includeFullTranscript && fullText.length > maxFullTranscriptLength) {\n response.warning = 'Full transcript included. For long videos, consider using chunkIndex, startTime/endTime, or search_transcript to reduce response size.';\n } else if (fullText.length <= maxFullTranscriptLength) {\n response.note = 'Full transcript auto-loaded (fits within context limit).';\n }\n }\n // Default for large transcripts: return preview only\n else {\n const preview = fullText.length > previewLength\n ? fullText.substring(0, previewLength) + '...'\n : fullText;\n\n response.preview = preview;\n response.isLargeTranscript = true;\n response.usage = {\n fullTranscript: 'Set includeFullTranscript: true to get complete text (warning: may exceed context)',\n search: 'Use search_transcript to find relevant portions by keyword (recommended for large transcripts)',\n timeRange: 'Use startTime and endTime (in seconds) to get a specific portion',\n pagination: `Use chunkIndex (0-${totalChunks - 1}) to paginate through ${chunkSizeSeconds}s chunks`,\n };\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(response, null, 2),\n },\n ],\n };\n}\n","import type { Core } from '@strapi/strapi';\nimport { validateToolInput } from '../schemas';\nimport { extractYouTubeID } from '../../utils/extract-youtube-id';\n\ninterface PluginConfig {\n searchSegmentSeconds?: number;\n}\n\ninterface TimecodeEntry {\n start: number;\n end: number;\n text: string;\n duration: number;\n}\n\ninterface SearchSegment {\n text: string;\n startTime: number;\n endTime: number;\n startFormatted: string;\n endFormatted: string;\n}\n\ninterface ScoredSegment extends SearchSegment {\n score: number;\n}\n\nexport const searchTranscriptTool = {\n name: 'search_transcript',\n description:\n 'Search within a saved transcript using BM25 scoring. Returns the most relevant segments matching your query with timestamps. Use this to find specific content in long videos without loading the entire transcript.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n videoId: {\n type: 'string',\n description: 'YouTube video ID (e.g., \"dQw4w9WgXcQ\") or full YouTube URL',\n },\n query: {\n type: 'string',\n description: 'Search query - keywords or phrases to find in the transcript',\n },\n maxResults: {\n type: 'number',\n description: 'Maximum number of results to return (default: 5, max: 20)',\n default: 5,\n },\n },\n required: ['videoId', 'query'],\n },\n};\n\n/**\n * Tokenize text into lowercase words\n */\nfunction tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter((word) => word.length > 1);\n}\n\n/**\n * Calculate IDF (Inverse Document Frequency) for each term\n */\nfunction calculateIDF(segments: SearchSegment[], vocabulary: Set<string>): Map<string, number> {\n const idf = new Map<string, number>();\n const N = segments.length;\n\n for (const term of vocabulary) {\n const docsWithTerm = segments.filter((seg) =>\n tokenize(seg.text).includes(term)\n ).length;\n // BM25 IDF formula: log((N - n + 0.5) / (n + 0.5) + 1)\n idf.set(term, Math.log((N - docsWithTerm + 0.5) / (docsWithTerm + 0.5) + 1));\n }\n\n return idf;\n}\n\n/**\n * Calculate BM25 score for a segment against a query\n */\nfunction bm25Score(\n segmentTokens: string[],\n queryTokens: string[],\n idf: Map<string, number>,\n avgDocLength: number,\n k1 = 1.5,\n b = 0.75\n): number {\n const docLength = segmentTokens.length;\n let score = 0;\n\n // Count term frequencies in segment\n const tf = new Map<string, number>();\n for (const token of segmentTokens) {\n tf.set(token, (tf.get(token) || 0) + 1);\n }\n\n for (const term of queryTokens) {\n const termFreq = tf.get(term) || 0;\n const termIdf = idf.get(term) || 0;\n\n if (termFreq > 0) {\n // BM25 scoring formula\n const numerator = termFreq * (k1 + 1);\n const denominator = termFreq + k1 * (1 - b + b * (docLength / avgDocLength));\n score += termIdf * (numerator / denominator);\n }\n }\n\n return score;\n}\n\n/**\n * Format milliseconds as MM:SS or HH:MM:SS\n */\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n}\n\n/**\n * Split transcript timecodes into segments of specified duration\n */\nfunction createSegments(\n timecodes: TimecodeEntry[],\n segmentDurationMs: number\n): SearchSegment[] {\n if (!timecodes || timecodes.length === 0) return [];\n\n const segments: SearchSegment[] = [];\n let currentSegment: TimecodeEntry[] = [];\n let segmentStartTime = timecodes[0].start;\n\n for (const entry of timecodes) {\n const segmentEndTime = segmentStartTime + segmentDurationMs;\n\n if (entry.start < segmentEndTime) {\n currentSegment.push(entry);\n } else {\n // Save current segment\n if (currentSegment.length > 0) {\n const endTime = currentSegment[currentSegment.length - 1].end ||\n currentSegment[currentSegment.length - 1].start + (currentSegment[currentSegment.length - 1].duration || 0);\n segments.push({\n text: currentSegment.map((e) => e.text).join(' '),\n startTime: Math.floor(segmentStartTime / 1000),\n endTime: Math.floor(endTime / 1000),\n startFormatted: formatTime(segmentStartTime),\n endFormatted: formatTime(endTime),\n });\n }\n\n // Start new segment\n segmentStartTime = entry.start;\n currentSegment = [entry];\n }\n }\n\n // Don't forget the last segment\n if (currentSegment.length > 0) {\n const endTime = currentSegment[currentSegment.length - 1].end ||\n currentSegment[currentSegment.length - 1].start + (currentSegment[currentSegment.length - 1].duration || 0);\n segments.push({\n text: currentSegment.map((e) => e.text).join(' '),\n startTime: Math.floor(segmentStartTime / 1000),\n endTime: Math.floor(endTime / 1000),\n startFormatted: formatTime(segmentStartTime),\n endFormatted: formatTime(endTime),\n });\n }\n\n return segments;\n}\n\nexport async function handleSearchTranscript(strapi: Core.Strapi, args: unknown) {\n const validatedArgs = validateToolInput('search_transcript', args);\n const { videoId: videoIdOrUrl, query, maxResults: maxResultsInput } = validatedArgs;\n\n // Get config\n const pluginConfig = await strapi.config.get('plugin::yt-transcript-strapi-plugin') as PluginConfig | undefined;\n const segmentSeconds = pluginConfig?.searchSegmentSeconds || 30;\n\n // Clamp maxResults\n const maxResults = Math.min(Math.max(maxResultsInput || 5, 1), 20);\n\n // Extract the video ID from URL or use as-is\n const videoId = extractYouTubeID(videoIdOrUrl);\n if (!videoId) {\n throw new Error(`Invalid YouTube video ID or URL: \"${videoIdOrUrl}\". Please provide a valid 11-character video ID or YouTube URL.`);\n }\n\n const service = strapi.plugin('yt-transcript-strapi-plugin').service('service');\n\n // Find transcript in database\n const transcript = await service.findTranscript(videoId);\n\n if (!transcript) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n error: true,\n message: `No transcript found for video ID: ${videoId}. Use fetch_transcript to fetch it from YouTube first.`,\n videoId,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n const timecodes: TimecodeEntry[] = transcript.transcriptWithTimeCodes || [];\n\n if (timecodes.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n error: true,\n message: 'Transcript has no timecode data for searching.',\n videoId,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n // Create segments from timecodes\n const segments = createSegments(timecodes, segmentSeconds * 1000);\n\n if (segments.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n error: true,\n message: 'Could not create searchable segments from transcript.',\n videoId,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n // Tokenize query\n const queryTokens = tokenize(query);\n\n if (queryTokens.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n error: true,\n message: 'Query is empty or contains only stop words.',\n query,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n // Build vocabulary from query terms\n const vocabulary = new Set(queryTokens);\n\n // Calculate IDF\n const idf = calculateIDF(segments, vocabulary);\n\n // Calculate average document length\n const avgDocLength = segments.reduce((sum, seg) => sum + tokenize(seg.text).length, 0) / segments.length;\n\n // Score all segments\n const scoredSegments: ScoredSegment[] = segments.map((segment) => ({\n ...segment,\n score: bm25Score(tokenize(segment.text), queryTokens, idf, avgDocLength),\n }));\n\n // Filter and sort by score\n const results = scoredSegments\n .filter((seg) => seg.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n videoId: transcript.videoId,\n title: transcript.title,\n query,\n totalSegments: segments.length,\n matchingResults: results.length,\n results: results.map((r) => ({\n text: r.text,\n startTime: r.startTime,\n endTime: r.endTime,\n timeRange: `${r.startFormatted} - ${r.endFormatted}`,\n score: Math.round(r.score * 100) / 100,\n })),\n usage: results.length > 0\n ? `Use get_transcript with startTime: ${results[0].startTime} and endTime: ${results[0].endTime} to get full context for the top result.`\n : 'No matches found. Try different keywords.',\n },\n null,\n 2\n ),\n },\n ],\n };\n}\n","import type { Core } from '@strapi/strapi';\nimport { validateToolInput } from '../schemas';\n\n// Maximum characters to show for transcript preview\nconst TRANSCRIPT_PREVIEW_LENGTH = 244;\n\nexport const findTranscriptsTool = {\n name: 'find_transcripts',\n description:\n 'Search and filter transcripts based on query criteria. Returns multiple matching transcripts with truncated previews (244 chars). Use get_transcript for full content. Supports filtering by title, videoId, and full-text search.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n query: {\n type: 'string',\n description: 'Search query to match against title or transcript content',\n },\n videoId: {\n type: 'string',\n description: 'Filter by specific video ID (partial match supported)',\n },\n title: {\n type: 'string',\n description: 'Filter by title (partial match, case-insensitive)',\n },\n includeFullContent: {\n type: 'boolean',\n description: 'Set to true to include full transcript content. Default: false. Warning: may cause context overflow with multiple results.',\n },\n page: {\n type: 'number',\n description: 'Page number (starts at 1)',\n default: 1,\n },\n pageSize: {\n type: 'number',\n description: 'Number of items per page (max 100)',\n default: 25,\n },\n sort: {\n type: 'string',\n description: 'Sort order (e.g., \"createdAt:desc\", \"title:asc\")',\n default: 'createdAt:desc',\n },\n },\n required: [],\n },\n};\n\n/**\n * Truncates a string to a maximum length with ellipsis\n */\nfunction truncateText(text: string | null | undefined, maxLength: number): string | null {\n if (!text) return null;\n if (text.length <= maxLength) return text;\n return text.substring(0, maxLength) + '...';\n}\n\n/**\n * Truncates transcript fields in an array of transcripts\n */\nfunction truncateTranscripts(transcripts: any[]): any[] {\n return transcripts.map((transcript) => ({\n ...transcript,\n fullTranscript: truncateText(transcript.fullTranscript, TRANSCRIPT_PREVIEW_LENGTH),\n }));\n}\n\nexport async function handleFindTranscripts(strapi: Core.Strapi, args: unknown) {\n const validatedArgs = validateToolInput('find_transcripts', args);\n const { query, videoId, title, includeFullContent, page, pageSize, sort } = validatedArgs;\n\n const start = (page - 1) * pageSize;\n\n // Build filters based on search criteria\n const filters: Record<string, any> = {};\n\n if (videoId) {\n filters.videoId = { $containsi: videoId };\n }\n\n if (title) {\n filters.title = { $containsi: title };\n }\n\n // If query is provided, search in multiple fields using $or\n if (query) {\n filters.$or = [\n { title: { $containsi: query } },\n { videoId: { $containsi: query } },\n { fullTranscript: { $containsi: query } },\n ];\n }\n\n // Query transcripts from database\n const transcripts = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findMany({\n filters,\n sort,\n limit: pageSize,\n start,\n });\n\n // Get total count for matching filters\n const allMatching = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findMany({\n filters,\n });\n const total = allMatching.length;\n\n // Truncate transcript content unless full content is requested\n const processedTranscripts = includeFullContent ? transcripts : truncateTranscripts(transcripts);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n data: processedTranscripts,\n pagination: {\n page,\n pageSize,\n total,\n pageCount: Math.ceil(total / pageSize),\n },\n filters: {\n query: query || null,\n videoId: videoId || null,\n title: title || null,\n },\n ...(!includeFullContent && { note: 'Transcript content truncated to 244 chars. Use get_transcript for full content or set includeFullContent=true.' }),\n },\n null,\n 2\n ),\n },\n ],\n };\n}\n","import type { Core } from '@strapi/strapi';\n\n// Import tool definitions and handlers\nimport { fetchTranscriptTool, handleFetchTranscript } from './fetch-transcript';\nimport { listTranscriptsTool, handleListTranscripts } from './list-transcripts';\nimport { getTranscriptTool, handleGetTranscript } from './get-transcript';\nimport { searchTranscriptTool, handleSearchTranscript } from './search-transcript';\nimport { findTranscriptsTool, handleFindTranscripts } from './find-transcripts';\n\n// Export all tool definitions\nexport const tools = [\n fetchTranscriptTool,\n listTranscriptsTool,\n getTranscriptTool,\n searchTranscriptTool,\n findTranscriptsTool,\n];\n\n// Tool handler registry\nconst toolHandlers: Record<string, (strapi: Core.Strapi, args: unknown) => Promise<any>> = {\n fetch_transcript: handleFetchTranscript,\n list_transcripts: handleListTranscripts,\n get_transcript: handleGetTranscript,\n search_transcript: handleSearchTranscript,\n find_transcripts: handleFindTranscripts,\n};\n\n/**\n * Handle a tool call by delegating to the appropriate handler\n */\nexport async function handleToolCall(\n strapi: Core.Strapi,\n request: { params: { name: string; arguments?: Record<string, unknown> } }\n) {\n const { name, arguments: args } = request.params;\n\n const handler = toolHandlers[name];\n if (!handler) {\n throw new Error(`Unknown tool: ${name}`);\n }\n\n const startTime = Date.now();\n try {\n const result = await handler(strapi, args || {});\n const duration = Date.now() - startTime;\n\n // Log successful execution\n strapi.log.debug(`[yt-transcript-mcp] Tool ${name} executed successfully in ${duration}ms`);\n\n return result;\n } catch (error) {\n const duration = Date.now() - startTime;\n\n // Log failed execution\n strapi.log.error(`[yt-transcript-mcp] Tool ${name} failed after ${duration}ms`, {\n error: error instanceof Error ? error.message : String(error),\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n {\n error: true,\n message: error instanceof Error ? error.message : String(error),\n tool: name,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n}\n","import type { Core } from '@strapi/strapi';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { tools, handleToolCall } from './tools';\n\n/**\n * Create an MCP server instance configured with YouTube transcript tools\n */\nexport function createMcpServer(strapi: Core.Strapi): Server {\n const server = new Server(\n {\n name: 'yt-transcript-mcp',\n version: '1.0.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Register handler for listing available tools\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n strapi.log.debug('[yt-transcript-mcp] Listing tools');\n return { tools };\n });\n\n // Register handler for tool calls\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n strapi.log.debug(`[yt-transcript-mcp] Tool call: ${request.params.name}`);\n return handleToolCall(strapi, request);\n });\n\n strapi.log.info('[yt-transcript-mcp] MCP server created with tools:', {\n tools: tools.map((t) => t.name),\n });\n\n return server;\n}\n","import type { Core } from '@strapi/strapi';\nimport { createMcpServer } from './mcp/server';\nimport { ProxyAgent, fetch as undiciFetch } from 'undici';\n\nconst PLUGIN_ID = 'yt-transcript-strapi-plugin';\nconst OAUTH_PLUGIN_ID = 'strapi-oauth-mcp-manager';\n\n/**\n * Test proxy connectivity by making a request to check IP\n */\nasync function testProxyConnection(proxyUrl: string): Promise<{ success: boolean; ip?: string; error?: string }> {\n try {\n const proxyAgent = new ProxyAgent(proxyUrl);\n\n // Use httpbin to check our outbound IP\n const response = await undiciFetch('https://httpbin.org/ip', {\n dispatcher: proxyAgent,\n signal: AbortSignal.timeout(10000), // 10 second timeout\n } as any);\n\n if (!response.ok) {\n return { success: false, error: `HTTP ${response.status}` };\n }\n\n const data = await response.json() as { origin?: string };\n return { success: true, ip: data.origin };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error)\n };\n }\n}\n\n/**\n * Fallback auth middleware for when OAuth manager plugin is not installed.\n * Requires Bearer token (Strapi API token) for MCP endpoints.\n * This allows standalone use with Claude Desktop without OAuth.\n */\nfunction createFallbackAuthMiddleware(strapi: Core.Strapi) {\n const mcpPath = `/api/${PLUGIN_ID}/mcp`;\n\n return async (ctx: any, next: () => Promise<void>) => {\n // Only apply to this plugin's MCP endpoint\n if (!ctx.path.startsWith(mcpPath)) {\n return next();\n }\n\n const authHeader = ctx.request.headers.authorization;\n\n if (!authHeader?.startsWith('Bearer ')) {\n ctx.status = 401;\n ctx.body = {\n error: 'Unauthorized',\n message: 'Bearer token required. Provide a Strapi API token.',\n };\n return;\n }\n\n // Extract token and set it for the controller\n const token = authHeader.slice(7);\n ctx.state.strapiToken = token;\n ctx.state.authMethod = 'api-token';\n\n return next();\n };\n}\n\ninterface PluginConfig {\n proxyUrl?: string;\n chunkSizeSeconds?: number;\n previewLength?: number;\n maxFullTranscriptLength?: number;\n searchSegmentSeconds?: number;\n}\n\nconst bootstrap = async ({ strapi }: { strapi: Core.Strapi }) => {\n // Store the server factory function - we'll create server+transport per session\n const plugin = strapi.plugin(PLUGIN_ID) as any;\n plugin.createMcpServer = () => createMcpServer(strapi);\n plugin.sessions = new Map(); // Track active sessions\n\n // Log proxy configuration status and test connectivity\n const pluginConfig = strapi.config.get('plugin::yt-transcript-strapi-plugin') as PluginConfig | undefined;\n if (pluginConfig?.proxyUrl) {\n // Mask the proxy URL for security (hide credentials)\n const maskedUrl = pluginConfig.proxyUrl.replace(/:([^@:]+)@/, ':****@');\n strapi.log.info(`[${PLUGIN_ID}] Proxy configured: ${maskedUrl}`);\n\n // Test proxy connectivity (non-blocking)\n testProxyConnection(pluginConfig.proxyUrl).then((result) => {\n if (result.success) {\n strapi.log.info(`[${PLUGIN_ID}] ✓ Proxy connection successful - Outbound IP: ${result.ip}`);\n } else {\n strapi.log.error(`[${PLUGIN_ID}] ✗ Proxy connection FAILED: ${result.error}`);\n strapi.log.error(`[${PLUGIN_ID}] YouTube requests will likely fail. Check your proxy credentials and URL.`);\n }\n });\n } else {\n strapi.log.warn(`[${PLUGIN_ID}] No proxy configured - YouTube may block requests. Set PROXY_URL in .env`);\n }\n\n // Check if OAuth manager is installed\n // If not, use fallback auth middleware (API token only)\n const oauthPlugin = strapi.plugin(OAUTH_PLUGIN_ID);\n\n if (oauthPlugin) {\n // OAuth manager handles auth via convention-based middleware\n // Any /api/*/mcp route is automatically protected\n strapi.log.info(`[${PLUGIN_ID}] OAuth manager detected - OAuth + API token auth enabled`);\n } else {\n // No OAuth manager - use fallback auth\n const fallbackMiddleware = createFallbackAuthMiddleware(strapi);\n strapi.server.use(fallbackMiddleware);\n strapi.log.info(`[${PLUGIN_ID}] Using API token authentication (OAuth manager not installed)`);\n }\n\n strapi.log.info(`[${PLUGIN_ID}] MCP endpoint available at: /api/${PLUGIN_ID}/mcp`);\n};\n\nexport default bootstrap;\n","import type { Core } from '@strapi/strapi';\n\nconst destroy = ({ strapi }: { strapi: Core.Strapi }) => {\n // destroy phase\n};\n\nexport default destroy;\n","import type { Core } from '@strapi/strapi';\n\nconst register = ({ strapi }: { strapi: Core.Strapi }) => {\n // register phase - middleware is registered in bootstrap\n};\n\nexport default register;\n","export default {\n default: {\n proxyUrl: '', // Optional: HTTP/HTTPS proxy for YouTube requests (e.g., 'http://user:pass@proxy.example.com:8080')\n chunkSizeSeconds: 300, // Default chunk size for transcript pagination (5 minutes)\n previewLength: 500, // Default preview length in characters\n maxFullTranscriptLength: 50000, // Auto-load full transcript if under this character count (~12K tokens)\n searchSegmentSeconds: 30, // Segment size for BM25 search scoring\n },\n validator(config: {\n proxyUrl?: string;\n chunkSizeSeconds?: number;\n previewLength?: number;\n maxFullTranscriptLength?: number;\n searchSegmentSeconds?: number;\n }) {\n if (config.proxyUrl && typeof config.proxyUrl !== 'string') {\n throw new Error('proxyUrl must be a string');\n }\n if (config.chunkSizeSeconds !== undefined && (typeof config.chunkSizeSeconds !== 'number' || config.chunkSizeSeconds < 30)) {\n throw new Error('chunkSizeSeconds must be a number >= 30');\n }\n if (config.previewLength !== undefined && (typeof config.previewLength !== 'number' || config.previewLength < 100)) {\n throw new Error('previewLength must be a number >= 100');\n }\n if (config.maxFullTranscriptLength !== undefined && (typeof config.maxFullTranscriptLength !== 'number' || config.maxFullTranscriptLength < 1000)) {\n throw new Error('maxFullTranscriptLength must be a number >= 1000');\n }\n if (config.searchSegmentSeconds !== undefined && (typeof config.searchSegmentSeconds !== 'number' || config.searchSegmentSeconds < 10)) {\n throw new Error('searchSegmentSeconds must be a number >= 10');\n }\n },\n};\n","import schema from './schema.json';\n\nexport default {\n schema,\n};","import transcript from './transcript';\n\nexport default {\n transcript,\n};\n","import type { Core } from '@strapi/strapi';\nimport { extractYouTubeID } from '../utils/extract-youtube-id';\n\nconst controller = ({ strapi }: { strapi: Core.Strapi }) => ({\n async getTranscript(ctx) {\n const videoId = extractYouTubeID(ctx.params.videoId);\n\n if (!videoId) {\n return (ctx.body = { error: 'Invalid YouTube URL or ID', data: null });\n }\n\n // Check if transcript exists in database\n const found = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .findTranscript(videoId);\n\n if (found) {\n return (ctx.body = { data: found });\n }\n\n // Fetch from YouTube\n const transcriptData = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .getTranscript(videoId);\n\n const payload = {\n videoId,\n title: transcriptData?.title || 'No title found',\n fullTranscript: transcriptData?.fullTranscript,\n transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes,\n };\n\n const transcript = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .saveTranscript(payload);\n\n ctx.body = { data: transcript };\n },\n});\n\nexport default controller;\n","import type { Core } from '@strapi/strapi';\nimport { randomUUID } from 'node:crypto';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { YtTranscriptPlugin } from '../types';\n\n// Session timeout: 4 hours (Strapi Cloud may restart, so keep reasonable)\nconst SESSION_TIMEOUT_MS = 4 * 60 * 60 * 1000;\n\n/**\n * Check if a session has expired\n */\nfunction isSessionExpired(session: { createdAt: number }): boolean {\n return Date.now() - session.createdAt > SESSION_TIMEOUT_MS;\n}\n\n/**\n * Clean up expired sessions\n */\nfunction cleanupExpiredSessions(plugin: YtTranscriptPlugin, strapi: Core.Strapi): void {\n let cleaned = 0;\n for (const [sessionId, session] of plugin.sessions.entries()) {\n if (isSessionExpired(session)) {\n try {\n session.server.close();\n } catch {\n // Ignore close errors\n }\n plugin.sessions.delete(sessionId);\n cleaned++;\n }\n }\n if (cleaned > 0) {\n strapi.log.debug(`[yt-transcript-mcp] Cleaned up ${cleaned} expired sessions`);\n }\n}\n\n/**\n * MCP Controller\n *\n * Handles MCP (Model Context Protocol) requests.\n * Authentication is handled by the oauth-auth policy which sets:\n * - ctx.state.strapiToken: The Strapi API token to use\n * - ctx.state.authMethod: 'oauth' or 'api-token'\n */\nconst mcpController = ({ strapi }: { strapi: Core.Strapi }) => ({\n /**\n * Handle MCP requests (POST, GET, DELETE)\n * Authentication is handled by the oauth-auth policy\n * Creates a new server+transport per session for proper isolation\n */\n async handle(ctx: any) {\n const plugin = strapi.plugin('yt-transcript-strapi-plugin') as unknown as YtTranscriptPlugin;\n\n if (!plugin.createMcpServer) {\n ctx.status = 503;\n ctx.body = {\n error: 'MCP plugin not initialized',\n message: 'The MCP plugin is not available. Check plugin configuration.',\n };\n return;\n }\n\n // Periodically clean up expired sessions (roughly every 100 requests)\n if (Math.random() < 0.01) {\n cleanupExpiredSessions(plugin, strapi);\n }\n\n try {\n // Get session ID from header\n const requestedSessionId = ctx.request.headers['mcp-session-id'];\n let session = requestedSessionId ? plugin.sessions.get(requestedSessionId) : null;\n\n // Check if session exists and is not expired\n if (session && isSessionExpired(session)) {\n strapi.log.debug(`[yt-transcript-mcp] Session expired, removing: ${requestedSessionId}`);\n try {\n session.server.close();\n } catch {\n // Ignore close errors\n }\n plugin.sessions.delete(requestedSessionId);\n session = null;\n }\n\n // If client sent a session ID but session doesn't exist, return error to force re-init\n if (requestedSessionId && !session) {\n ctx.status = 400;\n ctx.body = {\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Session expired or invalid. Please reinitialize the connection.',\n },\n id: null,\n };\n return;\n }\n\n // Create new session if none exists\n if (!session) {\n const sessionId = randomUUID();\n const server = plugin.createMcpServer();\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => sessionId,\n });\n\n await server.connect(transport);\n\n session = { server, transport, createdAt: Date.now(), strapiToken: ctx.state.strapiToken };\n plugin.sessions.set(sessionId, session);\n\n strapi.log.debug(`[yt-transcript-mcp] New session created: ${sessionId} (auth: ${ctx.state.authMethod})`);\n }\n\n // Handle the request - wrap in try/catch to handle transport errors\n try {\n await session.transport.handleRequest(ctx.req, ctx.res, ctx.request.body);\n } catch (transportError) {\n // Transport error likely means SSE stream was terminated by load balancer\n // Clean up the session so client can reinitialize\n strapi.log.warn(`[yt-transcript-mcp] Transport error, cleaning up session: ${requestedSessionId}`, {\n error: transportError instanceof Error ? transportError.message : String(transportError),\n });\n\n try {\n session.server.close();\n } catch {\n // Ignore close errors\n }\n plugin.sessions.delete(requestedSessionId!);\n\n // Return error to tell client to reinitialize\n if (!ctx.res.headersSent) {\n ctx.status = 400;\n ctx.body = {\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Session transport error. Please reinitialize the connection.',\n },\n id: null,\n };\n }\n return;\n }\n\n // Prevent Koa from handling response\n ctx.respond = false;\n } catch (error) {\n strapi.log.error('[yt-transcript-mcp] Error handling MCP request', {\n error: error instanceof Error ? error.message : String(error),\n method: ctx.method,\n path: ctx.path,\n });\n\n if (!ctx.res.headersSent) {\n ctx.status = 500;\n ctx.body = {\n error: 'MCP request failed',\n message: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n },\n});\n\nexport default mcpController;\n","import controller from './controller';\nimport mcp from './mcp';\n\nexport default {\n controller,\n mcp,\n};\n","export default {};\n","export default {};\n","export default [\n // MCP endpoints - auth: false bypasses Strapi auth, oauth-auth middleware handles authentication\n {\n method: 'POST',\n path: '/mcp',\n handler: 'mcp.handle',\n config: {\n auth: false,\n policies: [],\n },\n },\n {\n method: 'GET',\n path: '/mcp',\n handler: 'mcp.handle',\n config: {\n auth: false,\n policies: [],\n },\n },\n {\n method: 'DELETE',\n path: '/mcp',\n handler: 'mcp.handle',\n config: {\n auth: false,\n policies: [],\n },\n },\n // Other routes\n {\n method: 'GET',\n path: '/yt-transcript/:videoId',\n handler: 'controller.getTranscript',\n config: {\n policies: [],\n },\n },\n];","export default [\n {\n method: 'GET',\n path: '/yt-transcript/:videoId',\n handler: 'controller.getTranscript',\n config: { \n policies: [], \n }, \n },\n];","\"use strict\";\n\nimport contentApi from \"./content-api\";\nimport admin from \"./admin\";\n\nexport default {\n \"content-api\": {\n type: \"content-api\",\n routes: [...contentApi],\n },\n admin: {\n type: \"admin\",\n routes: [...admin],\n },\n};\n","import { Innertube } from 'youtubei.js';\nimport { ProxyAgent, fetch as undiciFetch } from 'undici';\n\nexport interface TranscriptSegment {\n text: string;\n start: number;\n end: number;\n duration: number;\n}\n\nexport interface TranscriptData {\n videoId: string;\n title?: string;\n fullTranscript: string;\n transcriptWithTimeCodes: TranscriptSegment[];\n}\n\nexport interface FetchOptions {\n proxyUrl?: string;\n}\n\n/**\n * Check if an object is Request-like (has url, method, headers properties)\n * We can't rely on instanceof Request since the Request class may differ across environments\n */\nfunction isRequestLike(input: unknown): input is Request {\n return (\n typeof input === 'object' &&\n input !== null &&\n 'url' in input &&\n typeof (input as Request).url === 'string' &&\n 'method' in input\n );\n}\n\n/**\n * Create a proxy-enabled fetch function using undici\n */\nfunction createProxyFetch(proxyUrl?: string): typeof fetch | undefined {\n if (!proxyUrl) {\n return undefined;\n }\n\n const proxyAgent = new ProxyAgent(proxyUrl);\n const maskedProxyUrl = proxyUrl.replace(/:([^@:]+)@/, ':****@');\n\n return (async (input: string | URL | Request, init?: RequestInit) => {\n let url: string;\n let method: string;\n let headers: Record<string, string> = {};\n let body: any;\n\n // Check for Request-like objects (can't rely on instanceof across environments)\n if (isRequestLike(input)) {\n const request = input;\n url = request.url;\n method = init?.method || request.method || 'GET';\n\n // Convert Request headers to plain object\n if (request.headers && typeof request.headers.forEach === 'function') {\n request.headers.forEach((value: string, key: string) => {\n headers[key] = value;\n });\n }\n\n // Merge with init headers (init takes precedence)\n if (init?.headers) {\n const initHeaders =\n init.headers instanceof Headers\n ? Object.fromEntries((init.headers as Headers).entries())\n : (init.headers as Record<string, string>);\n headers = { ...headers, ...initHeaders };\n }\n\n // Prefer body from init, otherwise use request body\n if (init?.body !== undefined) {\n body = init.body;\n } else if (method !== 'GET' && method !== 'HEAD' && request.body) {\n // Clone and read the request body if needed\n try {\n const cloned = request.clone();\n body = await cloned.text();\n } catch {\n // Body might not be available\n }\n }\n } else {\n // Handle string or URL with init options\n url = input instanceof URL ? input.toString() : input;\n method = init?.method || 'GET';\n if (init?.headers) {\n headers =\n init.headers instanceof Headers\n ? Object.fromEntries((init.headers as Headers).entries())\n : (init.headers as Record<string, string>);\n }\n body = init?.body;\n }\n\n const options: any = {\n method,\n headers,\n dispatcher: proxyAgent,\n };\n\n if (body && method !== 'GET' && method !== 'HEAD') {\n options.body = body;\n }\n\n // Log request details\n const urlPath = new URL(url).pathname;\n console.log(`[yt-transcript] Proxy ${method} ${urlPath} via ${maskedProxyUrl}`);\n\n const response = await undiciFetch(url, options);\n\n // Log response status\n if (!response.ok) {\n console.log(`[yt-transcript] Proxy response: ${response.status} ${response.statusText}`);\n }\n\n return response;\n }) as typeof fetch;\n}\n\n/**\n * Decode HTML entities in transcript text\n */\nfunction decodeHtmlEntities(text: string): string {\n return text\n .replace(/'/g, \"'\")\n .replace(/"/g, '\"')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/ /g, ' ')\n .replace(/'/g, \"'\")\n .replace(/&#(\\d+);/g, (_, num) => String.fromCharCode(parseInt(num, 10)))\n .replace(/<[^>]+>/g, '')\n .trim();\n}\n\n/**\n * Parse <p t=\"ms\" d=\"ms\">text</p> format (Android client)\n */\nfunction parsePTagFormat(xml: string): TranscriptSegment[] {\n const segments: TranscriptSegment[] = [];\n const pTagRegex = /<p\\s+t=\"(\\d+)\"\\s+d=\"(\\d+)\"[^>]*>([\\s\\S]*?)<\\/p>/g;\n\n let match = pTagRegex.exec(xml);\n while (match !== null) {\n const [, startMsStr, durationMsStr, rawText] = match;\n if (startMsStr && durationMsStr && rawText) {\n const text = decodeHtmlEntities(rawText);\n if (text) {\n const start = parseInt(startMsStr, 10);\n const duration = parseInt(durationMsStr, 10);\n segments.push({\n text,\n start,\n end: start + duration,\n duration,\n });\n }\n }\n match = pTagRegex.exec(xml);\n }\n return segments;\n}\n\n/**\n * Parse <text start=\"sec\" dur=\"sec\">text</text> format (alternative format)\n */\nfunction parseTextTagFormat(xml: string): TranscriptSegment[] {\n const segments: TranscriptSegment[] = [];\n const textTagRegex = /<text\\s+start=\"([\\d.]+)\"(?:\\s+dur=\"([\\d.]+)\")?[^>]*>([\\s\\S]*?)<\\/text>/g;\n\n let match = textTagRegex.exec(xml);\n while (match !== null) {\n const [, startStr, durStr, rawText] = match;\n if (startStr && rawText) {\n const text = decodeHtmlEntities(rawText);\n if (text) {\n const start = Math.round(parseFloat(startStr) * 1000);\n const duration = Math.round(parseFloat(durStr || '0') * 1000);\n segments.push({\n text,\n start,\n end: start + duration,\n duration,\n });\n }\n }\n match = textTagRegex.exec(xml);\n }\n return segments;\n}\n\n/**\n * Parse timedtext XML into transcript segments\n * Supports both <p> format (Android) and <text> format (alternative)\n */\nfunction parseTimedTextXml(xml: string): TranscriptSegment[] {\n // Try <p> tag format first (Android client format)\n const pSegments = parsePTagFormat(xml);\n if (pSegments.length > 0) {\n return pSegments;\n }\n // Fall back to <text> tag format\n return parseTextTagFormat(xml);\n}\n\n/**\n * Fetch timedtext XML from caption URL\n */\nasync function fetchTimedTextXml(\n captionUrl: string,\n proxyFetch?: typeof fetch\n): Promise<string> {\n const fetchFn = proxyFetch || fetch;\n const response = await fetchFn(captionUrl, {\n headers: {\n 'Accept-Language': 'en-US,en;q=0.9',\n 'User-Agent':\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch timedtext: ${response.status}`);\n }\n\n const xml = await response.text();\n if (!xml || xml.length === 0) {\n throw new Error('Empty timedtext response');\n }\n\n return xml;\n}\n\n/**\n * Fetch transcript using youtubei.js getBasicInfo to get caption URLs\n * This approach uses the Innertube client to get caption track URLs,\n * then fetches timedtext directly - avoiding BotGuard requirements.\n */\nasync function fetchTranscriptFromYouTube(\n videoId: string,\n options?: FetchOptions\n): Promise<TranscriptData> {\n const proxyFetch = createProxyFetch(options?.proxyUrl);\n\n // Log proxy status for debugging\n if (options?.proxyUrl) {\n const maskedUrl = options.proxyUrl.replace(/:([^@:]+)@/, ':****@');\n console.log(`[yt-transcript] Fetching video ${videoId} via proxy: ${maskedUrl}`);\n } else {\n console.log(`[yt-transcript] Fetching video ${videoId} without proxy`);\n }\n\n // 1. Create Innertube client with optional proxy\n const client = await Innertube.create({\n generate_session_locally: true,\n lang: 'en',\n location: 'US',\n retrieve_player: true, // Required to get caption tracks\n fetch: proxyFetch,\n });\n\n // 2. Get basic info (includes caption tracks)\n const info = await client.getBasicInfo(videoId);\n\n // Get title from basic info\n const title = info.basic_info?.title;\n\n // 3. Check for caption tracks\n const captionTracks = info.captions?.caption_tracks;\n const playabilityStatus = (info as any).playability_status;\n\n // Log detailed info for debugging\n console.log(`[yt-transcript] Video ${videoId} - Title: ${info.basic_info?.title || 'Unknown'}`);\n console.log(`[yt-transcript] Video ${videoId} - Playability: ${playabilityStatus?.status || 'Unknown'}`);\n console.log(`[yt-transcript] Video ${videoId} - Caption tracks found: ${captionTracks?.length || 0}`);\n\n if (!captionTracks || captionTracks.length === 0) {\n // Check playability status for more details\n const status = playabilityStatus?.status;\n const reason = playabilityStatus?.reason;\n const subreason = playabilityStatus?.messages?.[0] || playabilityStatus?.subreason;\n\n console.log(`[yt-transcript] Video ${videoId} - No captions found`);\n console.log(`[yt-transcript] Video ${videoId} - Playability status: ${status || 'Unknown'}`);\n if (reason) {\n console.log(`[yt-transcript] Video ${videoId} - Playability reason: ${reason}`);\n }\n if (subreason) {\n console.log(`[yt-transcript] Video ${videoId} - Playability subreason: ${subreason}`);\n }\n\n // Check for various error conditions\n if (reason && reason.includes('Sign in')) {\n throw new Error(\n 'YouTube requires sign-in. This usually means the IP is blocked. ' +\n 'Configure a residential proxy in the plugin settings.'\n );\n }\n\n if (status === 'ERROR' || status === 'UNPLAYABLE') {\n throw new Error(\n `Video is not playable (status: ${status}). ${reason || 'The video may be private, deleted, or unavailable in your region.'}`\n );\n }\n\n if (status === 'LOGIN_REQUIRED') {\n throw new Error(\n 'YouTube requires login to access this video. This usually indicates IP blocking. ' +\n 'Configure a residential proxy in the plugin settings.'\n );\n }\n\n // Check if captions object exists but is empty\n if (info.captions) {\n console.log(`[yt-transcript] Video ${videoId} - Captions object exists but no tracks available`);\n } else {\n console.log(`[yt-transcript] Video ${videoId} - No captions object in response`);\n }\n\n throw new Error(\n `No captions available for this video. ` +\n `Playability: ${status || 'Unknown'}. ` +\n `The video may not have captions enabled, or YouTube may be blocking the request. ` +\n (options?.proxyUrl ? '' : 'Try configuring a proxy in the plugin settings.')\n );\n }\n\n // Log available caption languages\n const availableLanguages = captionTracks.map((t) => `${t.language_code}${t.kind === 'asr' ? ' (auto)' : ''}`);\n console.log(`[yt-transcript] Video ${videoId} - Available languages: ${availableLanguages.join(', ')}`);\n\n // 4. Find English caption track (prefer non-ASR if available)\n const englishTrack =\n captionTracks.find((t) => t.language_code === 'en' && t.kind !== 'asr') ||\n captionTracks.find((t) => t.language_code?.startsWith('en')) ||\n captionTracks[0];\n\n if (!englishTrack?.base_url) {\n throw new Error('No valid caption track URL found');\n }\n\n // 5. Fetch timedtext XML\n console.log(`[yt-transcript] Video ${videoId} - Fetching caption track: ${englishTrack.language_code}`);\n const xml = await fetchTimedTextXml(englishTrack.base_url, proxyFetch);\n\n // 6. Parse XML to segments\n const segments = parseTimedTextXml(xml);\n\n if (segments.length === 0) {\n throw new Error('Failed to parse any transcript segments from XML');\n }\n\n const transcriptLength = segments.map((s) => s.text).join(' ').length;\n console.log(`[yt-transcript] Video ${videoId} - Success! ${segments.length} segments, ${transcriptLength} chars`);\n\n return {\n videoId,\n title,\n fullTranscript: segments.map((s) => s.text).join(' '),\n transcriptWithTimeCodes: segments,\n };\n}\n\n/**\n * Main entry point for fetching YouTube transcripts\n * @param videoId - The YouTube video ID\n * @param options - Optional configuration including proxy settings\n */\nconst fetchTranscript = async (videoId: string, options?: FetchOptions): Promise<TranscriptData> => {\n try {\n return await fetchTranscriptFromYouTube(videoId, options);\n } catch (error) {\n throw new Error(\n `Failed to fetch transcript for video ${videoId}. ` +\n `The video may not have captions enabled, or may be unavailable. ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n};\n\nexport default fetchTranscript;\n","import type { Core } from '@strapi/strapi';\nimport fetchTranscript from '../utils/fetch-transcript';\n\ninterface YTTranscriptConfig {\n proxyUrl?: string;\n}\n\nconst service = ({ strapi }: { strapi: Core.Strapi }) => ({\n async getTranscript(identifier: string) {\n const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;\n const isValid = youtubeIdRegex.test(identifier);\n if (!isValid) {\n return { error: 'Invalid video ID', data: null };\n }\n\n // Get proxy config - try multiple methods to find it\n const pluginConfigFromGet = strapi.config.get('plugin::yt-transcript-strapi-plugin') as any;\n const pluginInstance = strapi.plugin('yt-transcript-strapi-plugin');\n const configFromPlugin = pluginInstance?.config;\n\n // Debug: log what we're getting\n strapi.log.info(`[yt-transcript] Config from strapi.config.get: ${JSON.stringify(pluginConfigFromGet)}`);\n strapi.log.info(`[yt-transcript] Config from plugin.config: ${typeof configFromPlugin === 'function' ? 'function' : JSON.stringify(configFromPlugin)}`);\n\n // Try to get proxyUrl from various places\n let proxyUrl: string | undefined;\n\n // Method 1: Direct from plugin config function (Strapi v5 way)\n if (typeof configFromPlugin === 'function') {\n proxyUrl = configFromPlugin('proxyUrl');\n strapi.log.info(`[yt-transcript] proxyUrl from config function: ${proxyUrl ? 'SET' : 'NOT SET'}`);\n }\n\n // Method 2: From strapi.config.get (might be nested under .config)\n if (!proxyUrl && pluginConfigFromGet) {\n proxyUrl = pluginConfigFromGet.proxyUrl || pluginConfigFromGet.config?.proxyUrl;\n }\n\n // Log at service level using strapi logger\n if (proxyUrl) {\n const maskedUrl = proxyUrl.replace(/:([^@:]+)@/, ':****@');\n strapi.log.info(`[yt-transcript] Fetching transcript for ${identifier} via proxy: ${maskedUrl}`);\n } else {\n strapi.log.info(`[yt-transcript] Fetching transcript for ${identifier} (NO PROXY - check config)`);\n }\n\n const transcriptData = await fetchTranscript(identifier, {\n proxyUrl,\n });\n\n strapi.log.info(`[yt-transcript] Successfully fetched transcript for ${identifier}`);\n\n return {\n title: transcriptData.title,\n fullTranscript: transcriptData.fullTranscript,\n transcriptWithTimeCodes: transcriptData.transcriptWithTimeCodes,\n };\n },\n\n async saveTranscript(payload: Record<string, unknown>) {\n return await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').create({\n data: payload,\n });\n },\n\n async findTranscript(videoId: string) {\n const transcriptData = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findFirst({\n filters: { videoId },\n });\n\n if (!transcriptData) return null;\n return transcriptData;\n },\n});\n\nexport default service;\n","import service from './service';\n\nexport default {\n service,\n};\n","/**\n * Application methods\n */\nimport bootstrap from './bootstrap';\nimport destroy from './destroy';\nimport register from './register';\n\n/**\n * Plugin server methods\n */\nimport config from './config';\nimport contentTypes from './content-types';\nimport controllers from './controllers';\nimport middlewares from './middlewares';\nimport policies from './policies';\nimport routes from './routes';\nimport services from './services';\n\nexport default {\n register,\n bootstrap,\n destroy,\n config,\n controllers,\n routes,\n services,\n contentTypes,\n policies,\n middlewares,\n};\n"],"names":["z","schema","getVideoDurationMs","formatTime","transcript","service","Server","ListToolsRequestSchema","CallToolRequestSchema","ProxyAgent","undiciFetch","config","randomUUID","StreamableHTTPServerTransport","mcp","options","Innertube","info"],"mappings":"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"}
|