@xiaohhhh1/canvas-agent 0.4.25 → 0.4.26
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.
|
@@ -30,6 +30,7 @@ type VisibleVideoItem = {
|
|
|
30
30
|
images: string[];
|
|
31
31
|
headers: string[];
|
|
32
32
|
cells: string[];
|
|
33
|
+
previewIndex?: number;
|
|
33
34
|
};
|
|
34
35
|
export declare class FastMossIntegration {
|
|
35
36
|
private context;
|
|
@@ -113,6 +114,12 @@ export declare class FastMossIntegration {
|
|
|
113
114
|
private openProductRanking;
|
|
114
115
|
private openVideoLearningPage;
|
|
115
116
|
private readVideoLearningItems;
|
|
117
|
+
/**
|
|
118
|
+
* AI ranking cards expose their TikTok video id only through the visible
|
|
119
|
+
* preview action. Open only the cards needed for this batch, capture the
|
|
120
|
+
* public FastMoss video-detail URL, and close the temporary tab again.
|
|
121
|
+
*/
|
|
122
|
+
private resolveVideoLearningCardLinks;
|
|
116
123
|
private clickVideoLearningTab;
|
|
117
124
|
private clickVideoLearningNextPage;
|
|
118
125
|
private waitForVideoLearningNavigation;
|
|
@@ -125,6 +132,8 @@ export declare function mergeRankingRecord(target: Map<string, Record<string, un
|
|
|
125
132
|
export declare function selectDetailCandidates(records: Array<Record<string, unknown>>, limit?: number): Record<string, unknown>[];
|
|
126
133
|
export declare function isTransientFastMossNavigation(error: unknown): boolean;
|
|
127
134
|
export declare function isAnalyzableVideoUrl(value: string): boolean;
|
|
135
|
+
export declare function isFastMossVideoDetailUrl(value: string): boolean;
|
|
136
|
+
export declare function publicTikTokVideoUrlFromFastMoss(value: string): string;
|
|
128
137
|
export declare function videoLearningSourcesFromItems(items: VisibleVideoItem[], context: {
|
|
129
138
|
market: string;
|
|
130
139
|
dimension: VideoLearningDimension;
|
|
@@ -285,7 +285,7 @@ export class FastMossIntegration {
|
|
|
285
285
|
return;
|
|
286
286
|
let previousSignature = "";
|
|
287
287
|
for (let pageNumber = 1; pageNumber <= 5 && sources.filter((item) => item.rankingDimension === dimension).length < limit; pageNumber += 1) {
|
|
288
|
-
const snapshot = await this.readVideoLearningItems();
|
|
288
|
+
const snapshot = await this.resolveVideoLearningCardLinks(await this.readVideoLearningItems(), limit);
|
|
289
289
|
const signature = JSON.stringify(snapshot.map((item) => [item.text, item.media, item.links]).slice(0, 20));
|
|
290
290
|
if (!snapshot.length || (pageNumber > 1 && signature === previousSignature))
|
|
291
291
|
break;
|
|
@@ -480,6 +480,47 @@ export class FastMossIntegration {
|
|
|
480
480
|
}
|
|
481
481
|
throw lastError instanceof Error ? lastError : new Error("FastMoss 视频榜页面跳转后没有恢复");
|
|
482
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* AI ranking cards expose their TikTok video id only through the visible
|
|
485
|
+
* preview action. Open only the cards needed for this batch, capture the
|
|
486
|
+
* public FastMoss video-detail URL, and close the temporary tab again.
|
|
487
|
+
*/
|
|
488
|
+
async resolveVideoLearningCardLinks(items, limit) {
|
|
489
|
+
if (!this.page || this.page.isClosed())
|
|
490
|
+
return items;
|
|
491
|
+
const candidates = items
|
|
492
|
+
.filter((item) => !item.links.some(isFastMossVideoDetailUrl) && Number.isInteger(item.previewIndex))
|
|
493
|
+
.slice(0, Math.max(0, limit));
|
|
494
|
+
if (!candidates.length)
|
|
495
|
+
return items;
|
|
496
|
+
const covers = this.page.locator('div.cursor-pointer > img[class*="aspect-"]');
|
|
497
|
+
const coverCount = await covers.count();
|
|
498
|
+
for (const item of candidates) {
|
|
499
|
+
const index = Number(item.previewIndex);
|
|
500
|
+
if (!Number.isInteger(index) || index < 0 || index >= coverCount)
|
|
501
|
+
continue;
|
|
502
|
+
let popup = null;
|
|
503
|
+
try {
|
|
504
|
+
[popup] = await Promise.all([
|
|
505
|
+
this.page.waitForEvent("popup", { timeout: 6_000 }),
|
|
506
|
+
covers.nth(index).click({ timeout: 6_000 }),
|
|
507
|
+
]);
|
|
508
|
+
await popup.waitForURL(/\/media-source\/video\/\d+/i, { timeout: 10_000 });
|
|
509
|
+
const detailUrl = popup.url();
|
|
510
|
+
if (isFastMossVideoDetailUrl(detailUrl))
|
|
511
|
+
item.links.unshift(detailUrl);
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
// Keep the item unresolved. The caller records a truthful rejection
|
|
515
|
+
// instead of counting a cover image or ranking page as a video.
|
|
516
|
+
}
|
|
517
|
+
finally {
|
|
518
|
+
if (popup && !popup.isClosed())
|
|
519
|
+
await popup.close().catch(() => undefined);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return items;
|
|
523
|
+
}
|
|
483
524
|
async clickVideoLearningTab(labels) {
|
|
484
525
|
let lastError;
|
|
485
526
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -692,13 +733,14 @@ async function readVisibleVideoLearningItems(page) {
|
|
|
692
733
|
]);
|
|
693
734
|
return [...new Set([...direct, ...data].filter(Boolean).map(absoluteUrl).filter(Boolean))];
|
|
694
735
|
};
|
|
695
|
-
const serialize = (root, headers = [], cells = []) => ({
|
|
736
|
+
const serialize = (root, headers = [], cells = [], previewIndex) => ({
|
|
696
737
|
text: root.innerText.replace(/\s+/g, " ").trim().slice(0, 8000),
|
|
697
738
|
links: [...new Set([...root.querySelectorAll("a[href]")].map((link) => link.href).filter(Boolean))],
|
|
698
739
|
media: mediaUrls(root),
|
|
699
740
|
images: [...new Set([...root.querySelectorAll("img")].map((image) => image.currentSrc || image.src || image.dataset.src || "").filter(Boolean).map(absoluteUrl).filter(Boolean))],
|
|
700
741
|
headers,
|
|
701
742
|
cells,
|
|
743
|
+
...(previewIndex == null ? {} : { previewIndex }),
|
|
702
744
|
});
|
|
703
745
|
const tableItems = [...document.querySelectorAll("table tbody tr")]
|
|
704
746
|
.filter(shown)
|
|
@@ -709,8 +751,15 @@ async function readVisibleVideoLearningItems(page) {
|
|
|
709
751
|
return serialize(row, headers, cells);
|
|
710
752
|
})
|
|
711
753
|
.filter((item) => item.text && (item.links.length || item.media.length || item.images.length));
|
|
754
|
+
const previewSelector = 'div.cursor-pointer > img[class*="aspect-"]';
|
|
755
|
+
const previewImages = [...document.querySelectorAll(previewSelector)];
|
|
756
|
+
const previewItems = previewImages.map((preview, previewIndex) => {
|
|
757
|
+
const root = preview.parentElement?.parentElement || preview.parentElement || preview;
|
|
758
|
+
return serialize(root, [], [], previewIndex);
|
|
759
|
+
}).filter((item) => item.text && item.images.length);
|
|
712
760
|
const cardCandidates = [...document.querySelectorAll("article,li,[class*='card'],[class*='item']")]
|
|
713
761
|
.filter(shown)
|
|
762
|
+
.filter((element) => !element.querySelector(previewSelector))
|
|
714
763
|
.filter((element) => {
|
|
715
764
|
const rect = element.getBoundingClientRect();
|
|
716
765
|
const text = element.innerText.replace(/\s+/g, " ").trim();
|
|
@@ -731,7 +780,7 @@ async function readVisibleVideoLearningItems(page) {
|
|
|
731
780
|
if (cards.length >= 80)
|
|
732
781
|
break;
|
|
733
782
|
}
|
|
734
|
-
const combined = [...tableItems, ...cards];
|
|
783
|
+
const combined = [...tableItems, ...previewItems, ...cards];
|
|
735
784
|
const seen = new Set();
|
|
736
785
|
return combined.filter((item) => {
|
|
737
786
|
const key = JSON.stringify([item.text.slice(0, 300), item.media[0], item.links[0], item.images[0]]);
|
|
@@ -808,6 +857,21 @@ export function isAnalyzableVideoUrl(value) {
|
|
|
808
857
|
return false;
|
|
809
858
|
}
|
|
810
859
|
}
|
|
860
|
+
export function isFastMossVideoDetailUrl(value) {
|
|
861
|
+
try {
|
|
862
|
+
const url = new URL(value);
|
|
863
|
+
return /(?:^|\.)fastmoss\.com$/i.test(url.hostname) && /\/media-source\/video\/\d+(?:\/|$)/i.test(url.pathname);
|
|
864
|
+
}
|
|
865
|
+
catch {
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
export function publicTikTokVideoUrlFromFastMoss(value) {
|
|
870
|
+
if (!isFastMossVideoDetailUrl(value))
|
|
871
|
+
return "";
|
|
872
|
+
const id = new URL(value).pathname.match(/\/media-source\/video\/(\d+)(?:\/|$)/i)?.[1];
|
|
873
|
+
return id ? `https://www.tiktok.com/@_/video/${id}` : "";
|
|
874
|
+
}
|
|
811
875
|
export function videoLearningSourcesFromItems(items, context) {
|
|
812
876
|
const output = [];
|
|
813
877
|
const seen = new Set();
|
|
@@ -815,7 +879,8 @@ export function videoLearningSourcesFromItems(items, context) {
|
|
|
815
879
|
const mediaUrl = item.media.find((value) => isAnalyzableVideoUrl(value)) || "";
|
|
816
880
|
const publicVideoUrl = firstHttp(item.links, /tiktok\.com\/.*(?:video|v)\//i);
|
|
817
881
|
const traceUrl = firstHttp(item.links, /video|aweme|creative|material/i);
|
|
818
|
-
const
|
|
882
|
+
const publicTraceUrl = publicTikTokVideoUrlFromFastMoss(traceUrl);
|
|
883
|
+
const sourceUrl = mediaUrl || publicVideoUrl || publicTraceUrl;
|
|
819
884
|
const platformVideoId = videoIdFromValues([sourceUrl, ...item.links, ...item.media]) || stableId(`${context.dimension}|${item.text}|${item.images[0] || ""}`);
|
|
820
885
|
if (seen.has(platformVideoId))
|
|
821
886
|
continue;
|