Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-09-29 11:47:47 +07:00
co-authored by Claude Opus 5.5
commit 0fbdba5ccf
622 changed files with 72105 additions and 0 deletions
+921
View File
@@ -0,0 +1,921 @@
/**
* Web SDK v1.2.0 接入层。
* 页面/点击/导航沿用 jha-1043 的口径,短剧使用最新版 dramaEvent/dramaStatusChange。
*/
(function (global) {
"use strict";
var state = {
initialized: false,
initializing: false,
queue: [],
page: { key: "internal_other", name: "站内其他页面" },
referrer: { key: "direct", name: "直接访问" },
userType: "normal",
pageStartedAt: Date.now(),
dramaViewSent: false,
adShown: {},
};
function stringValue(value, fallback) {
var result = value == null ? "" : String(value).trim();
return result || (fallback == null ? "" : String(fallback));
}
function integer(value, fallback) {
var result = parseInt(value, 10);
return isFinite(result) ? result : (fallback || 0);
}
function clamp(value, min, max) {
var result = Number(value);
if (!isFinite(result)) result = min;
return Math.max(min, Math.min(max, result));
}
function sdkAvailable() {
return !!(global.WebSDK && global.WebSDK.track);
}
function logError() {}
function invoke(method, payload) {
if (!state.initialized || !sdkAvailable()) {
return new Promise(function (resolve) {
state.queue.push([method, payload, resolve]);
});
}
try {
var fn = global.WebSDK.track[method];
if (typeof fn !== "function") {
var missing = new Error("SDK method does not exist: " + method);
logError(missing);
return Promise.resolve({ sent: false, ok: false, reason: "unknown_method" });
}
var result = fn(payload);
return Promise.resolve(result).then(function (response) {
if (response && response.ok === false) {
logError(method + " rejected: " + JSON.stringify(response));
}
return response;
}).catch(function (error) {
logError(error);
return { sent: false, ok: false, reason: "exception" };
});
} catch (error) {
logError(error);
return Promise.resolve({ sent: false, ok: false, reason: "exception" });
}
}
function flushQueue() {
var pending = state.queue.splice(0, state.queue.length);
pending.forEach(function (entry) {
Promise.resolve(invoke(entry[0], entry[1])).then(entry[2]);
});
}
function currentChannel(value) {
var channel = stringValue(value);
if (!channel && global.ChannelAttribution && typeof global.ChannelAttribution.get === "function") {
channel = stringValue(global.ChannelAttribution.get());
}
if (!channel) channel = stringValue(global.__CHANNEL_CODE__, "organic");
var normalized = channel.toLowerCase();
if (["self", "natural", "official", "channel", "channel://natural"].indexOf(normalized) >= 0) {
return "organic";
}
if (!channel || channel.length > 50 || !/^[A-Za-z0-9_.-]+$/.test(channel) ||
["{}", "0", "-", "unknown"].indexOf(normalized) >= 0) {
return "organic";
}
return channel;
}
function normalizeUid(value) {
var uid = stringValue(value);
if (uid === "0" || uid.length < 4 || uid.length > 32 || !/^[A-Za-z0-9_]+$/.test(uid) ||
["u_1001", "stress_user", "u_lab"].indexOf(uid.toLowerCase()) >= 0) return "";
return uid;
}
function inferPage(path) {
path = stringValue(path, "/").replace(/\/$/, "") || "/";
if (path === "/") return { key: "home", name: "首页" };
var rules = [
[/^\/series-play(?:\/|$)/, "series_play", "AI成人剧集播放页"],
[/^\/series(?:\/|$)/, "series", "AI成人剧集"],
[/^\/play(?:\/|$)/, "play", "短剧播放页"],
[/^\/detail(?:\/|$)/, "detail", "短剧详情"],
[/^\/list(?:\/|$)/, "list", "短剧列表"],
[/^\/rank(?:\/|$)/, "rank", "排行榜"],
[/^\/topic-detail(?:\/|$)/, "topic_detail", "专题详情"],
[/^\/topic(?:\/|$)/, "topic", "专题列表"],
[/^\/tags(?:\/|$)/, "tags", "标签详情"],
[/^\/search(?:-mobile)?(?:\/|$)/, "search", "搜索结果页"],
[/^\/history(?:\/|$)/, "history", "观看历史"],
[/^\/favorites(?:\/|$)/, "favorites", "我的收藏"],
[/^\/profile(?:\/|$)/, "profile", "个人中心"],
];
for (var i = 0; i < rules.length; i += 1) {
if (rules[i][0].test(path)) return { key: rules[i][1], name: rules[i][2] };
}
return { key: "internal_other", name: "站内其他页面" };
}
function readReferrer() {
try {
var saved = global.sessionStorage.getItem("__tracker_last_page");
if (saved) {
var parsed = JSON.parse(saved);
if (parsed && parsed.key) return parsed;
}
} catch (error) {}
try {
if (document.referrer) {
var url = new URL(document.referrer, global.location.href);
if (url.host === global.location.host) return inferPage(url.pathname);
return { key: "external", name: url.host || "外部站点" };
}
} catch (error) {}
return { key: "direct", name: "直接访问" };
}
function rememberPage() {
try { global.sessionStorage.setItem("__tracker_last_page", JSON.stringify(state.page)); } catch (error) {}
}
function init(options) {
if (state.initialized || state.initializing) return;
options = options || {};
var inferred = inferPage(global.location && global.location.pathname);
state.page = {
key: stringValue(options.pageKey, inferred.key),
name: stringValue(options.pageName, inferred.name),
};
state.userType = options.userType === "vip" ? "vip" : "normal";
state.referrer = readReferrer();
state.initializing = true;
var attempts = 0;
var timer = global.setInterval(function () {
attempts += 1;
if (!sdkAvailable() && attempts < 100) return;
global.clearInterval(timer);
if (!sdkAvailable()) {
state.initializing = false;
return;
}
try {
var result = global.WebSDK.init({
appId: stringValue(options.appId),
uid: normalizeUid(options.uid),
channel: currentChannel(options.channel),
encryptedConfig: stringValue(options.encryptedConfig),
appVersion: stringValue(options.appVersion, "1.0.0"),
disableAutoEvents: ["app_page_view", "page_click"],
});
Promise.resolve(result).then(function () {
state.initialized = true;
state.initializing = false;
__commonFieldsReady = null;
loadCommonFields();
flushQueue();
onReady();
}).catch(function (error) {
state.initializing = false;
logError(error);
});
} catch (error) {
state.initializing = false;
logError(error);
}
}, 50);
}
function whenDOMReady(callback) {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", callback, { once: true });
} else {
callback();
}
}
function trackPageView(extra) {
extra = extra || {};
invoke("appPageView", {
user_type: stringValue(extra.user_type, state.userType),
page_key: state.page.key,
page_name: state.page.name,
referrer_page_key: state.referrer.key,
referrer_page_name: state.referrer.name,
current_page_key: state.page.key,
current_page_name: state.page.name,
page_load_time: pageLoadDuration(),
recommend_trace_id: stringValue(extra.recommend_trace_id),
});
rememberPage();
}
function pageLoadDuration() {
try {
if (global.performance && typeof global.performance.getEntriesByType === "function") {
var entries = global.performance.getEntriesByType("navigation");
if (entries && entries[0]) {
var navigation = entries[0];
var end = navigation.loadEventEnd || navigation.domContentLoadedEventEnd || global.performance.now();
if (isFinite(end)) return Math.max(0, Math.round(end));
}
}
if (global.performance && typeof global.performance.now === "function") {
return Math.max(0, Math.round(global.performance.now()));
}
} catch (error) {}
return Math.max(0, Date.now() - state.pageStartedAt);
}
function trackPageClick(event) {
var doc = document.documentElement || {};
var viewportWidth = global.innerWidth || doc.clientWidth || 0;
var viewportHeight = global.innerHeight || doc.clientHeight || 0;
var x = integer(event.clientX, 0);
var y = integer(event.clientY, 0);
invoke("pageClick", {
page_key: state.page.key,
page_name: state.page.name,
click_page_x: x,
click_page_y: y,
click_x_percent: viewportWidth ? Math.round(clamp(x / viewportWidth * 100, 0, 100)) : 0,
click_y_percent: viewportHeight ? Math.round(clamp(y / viewportHeight * 100, 0, 100)) : 0,
screen_width: integer(global.screen && global.screen.width, 0),
screen_height: integer(global.screen && global.screen.height, 0),
recommend_trace_id: "",
});
}
function trackNavigation(anchor) {
if (!anchor || (!anchor.hasAttribute("data-navigation-key") && !anchor.hasAttribute("data-nav-key")) ||
!anchor.getAttribute("href")) return;
var href = anchor.getAttribute("href");
if (!href || href.charAt(0) === "#" || /^javascript:/i.test(href)) return;
var label = stringValue(anchor.getAttribute("aria-label") || anchor.textContent).replace(/\s+/g, " ");
var navigationKey = stringValue(
anchor.getAttribute("data-navigation-key") || anchor.getAttribute("data-nav-key")
);
if (!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(navigationKey)) {
navigationKey = "";
try {
var url = new URL(href, global.location.href);
if (url.origin !== global.location.origin) {
navigationKey = "nav_external";
} else {
var targetPage = inferPage(url.pathname);
if (targetPage.key !== "internal_other") {
navigationKey = "nav_" + targetPage.key;
} else {
var firstSegment = stringValue(url.pathname.split("/").filter(Boolean)[0])
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "")
.slice(0, 48);
navigationKey = firstSegment ? "nav_" + firstSegment : "nav_home";
}
}
} catch (error) {
navigationKey = "nav_other";
}
}
invoke("navigation", {
navigation_key: navigationKey.toLowerCase(),
navigation_name: stringValue(
anchor.getAttribute("data-navigation-name") || anchor.getAttribute("data-nav-name"),
label || navigationKey
).slice(0, 255),
});
}
function adPayload(node) {
var id = stringValue(node && node.getAttribute("data-ad-id"));
if (!id) return null;
var adType = stringValue(node.getAttribute("data-ad-type"), "banner");
var allSlots = document.querySelectorAll('[data-track-ad="external"][data-ad-id]');
var slotPosition = Array.prototype.indexOf.call(allSlots, node) + 1;
if (slotPosition <= 0) slotPosition = 1;
var fallbackSlotKey = state.page.key + "_content_banner_" + slotPosition;
var fallbackSlotName = state.page.name + "内容横幅第" + slotPosition + "位";
if (adType === "interstitial") {
fallbackSlotKey = state.page.key + "_popup_" + slotPosition;
fallbackSlotName = state.page.name + "弹窗广告位";
}
return {
page_key: state.page.key,
page_name: state.page.name,
ad_slot_key: stringValue(
node.getAttribute("data-ad-slot-key"),
fallbackSlotKey
),
ad_slot_name: stringValue(
node.getAttribute("data-ad-slot-name"),
fallbackSlotName
),
ad_id: id,
creative_id: stringValue(node.getAttribute("data-creative-id")),
ad_type: adType,
};
}
function bindAds() {
var selector = '[data-track-ad="external"][data-ad-id]';
var nodes = document.querySelectorAll(selector);
function show(node) {
trackExternalAd(node, "show");
}
if (global.IntersectionObserver) {
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
show(entry.target);
observer.unobserve(entry.target);
});
}, { threshold: 0.1 });
function observe(node) {
if (!node || node.__trackerAdObserved) return;
node.__trackerAdObserved = true;
observer.observe(node);
}
Array.prototype.forEach.call(nodes, observe);
if (global.MutationObserver) {
new MutationObserver(function (records) {
records.forEach(function (record) {
Array.prototype.forEach.call(record.addedNodes || [], function (node) {
if (!node || node.nodeType !== 1) return;
if (node.matches && node.matches(selector)) observe(node);
if (node.querySelectorAll) {
Array.prototype.forEach.call(node.querySelectorAll(selector), observe);
}
});
});
}).observe(document.documentElement, { childList: true, subtree: true });
}
} else {
Array.prototype.forEach.call(nodes, show);
}
}
function trackExternalAd(node, eventType) {
if (!node || (eventType !== "show" && eventType !== "click")) return;
var payload = adPayload(node);
if (!payload) return;
var key = payload.ad_slot_key + "|" + payload.ad_id;
if (!state.adShown[key]) {
state.adShown[key] = true;
invoke("adImpression", payload);
}
if (eventType === "click" && node.getAttribute("data-ad-clickable") === "1") {
return invoke("adClick", payload);
}
}
function bindExternalAdEvents() {
document.addEventListener("external-ad:track", function (event) {
var detail = event && event.detail ? event.detail : {};
trackExternalAd(detail.element, stringValue(detail.eventType));
});
}
function trackAdvertising(node, eventType) {
if (!node || ["show", "click", "close"].indexOf(eventType) < 0) return;
invoke("advertising", {
event_type: eventType,
advertising_key: stringValue(node.getAttribute("data-advertising-key"), "dialog_ad"),
advertising_name: stringValue(node.getAttribute("data-advertising-name"), "弹窗广告"),
advertising_id: stringValue(node.getAttribute("data-advertising-id") || node.getAttribute("data-dialog-ad-id")),
});
}
function bindAdvertisingEvents() {
document.addEventListener("dialog-ad:track", function (event) {
var detail = event && event.detail ? event.detail : {};
var node = detail.element;
if (!node || !node.getAttribute) return;
trackAdvertising(node, stringValue(detail.eventType));
});
}
function dramaRoot() {
return document.querySelector("[data-track-drama]");
}
function dramaContext(root) {
root = root || dramaRoot();
if (!root) return null;
var data = root.dataset || {};
var dramaID = stringValue(data.dramaId);
var videoID = stringValue(data.videoId);
if (!dramaID || !videoID) return null;
return {
media_id: stringValue(data.mediaId),
drama_id: dramaID,
drama_title: stringValue(data.dramaTitle, "未命名短剧"),
episode: Math.max(1, integer(data.episode, 1)),
total_episode: Math.max(1, integer(data.totalEpisode, 1)),
video_id: videoID,
drama_type_id: stringValue(data.dramaTypeId, "default"),
drama_type_name: stringValue(data.dramaTypeName, "默认分类"),
drama_content_type: "short_drama",
recommend_trace_id: stringValue(data.recommendTraceId),
drama_tag_key: stringValue(data.dramaTagKey, "default"),
drama_tag_name: stringValue(data.dramaTagName, "默认标签"),
drama_duration: Math.max(0, integer(data.dramaDuration, 0)),
};
}
var dramaBehaviorNames = {
ep_view: "展示",
ep_play: "播放",
ep_pause: "暂停",
ep_forward: "快进",
ep_rewind: "快退",
ep_complete: "播放完成",
ep_share: "分享",
};
function videoPlaybackState(video) {
if (!video) return null;
if (!video.__trackerPlaybackState) {
video.__trackerPlaybackState = {
startedAt: 0,
elapsedMs: 0,
seekFrom: null,
lastPosition: Number(video.currentTime) || 0,
};
}
return video.__trackerPlaybackState;
}
function beginPlayback(video) {
var playback = videoPlaybackState(video);
if (playback && !playback.startedAt) playback.startedAt = Date.now();
}
function settlePlayback(video) {
var playback = videoPlaybackState(video);
if (!playback || !playback.startedAt) return;
playback.elapsedMs += Math.max(0, Date.now() - playback.startedAt);
playback.startedAt = 0;
}
function playbackSeconds(video, keepDuration) {
var playback = videoPlaybackState(video);
if (!playback) return 0;
var now = Date.now();
var activeMs = playback.startedAt ? Math.max(0, now - playback.startedAt) : 0;
var seconds = Math.max(0, Math.round((playback.elapsedMs + activeMs) / 1000));
if (!keepDuration) {
playback.elapsedMs = 0;
if (playback.startedAt) playback.startedAt = now;
}
return seconds;
}
function videoMetrics(video, context, keepDuration) {
var duration = video && isFinite(video.duration) ? Math.round(video.duration) : context.drama_duration;
var current = video && isFinite(video.currentTime) ? Math.round(video.currentTime) : 0;
return {
duration: Math.max(0, duration),
current: Math.max(0, current),
played: playbackSeconds(video, keepDuration),
progress: duration > 0 ? Math.round(clamp(current / duration * 100, 0, 100)) : 0,
speed: Math.max(1, Math.round(((video && video.playbackRate) || 1) * 100)),
};
}
function trackDramaEvent(behaviorKey, video, options) {
var context = dramaContext();
if (!context) return;
options = options || {};
var metrics = videoMetrics(video, context, options.keepPlaybackDuration === true);
context.drama_duration = metrics.duration;
context.play_duration = metrics.played;
context.play_progress = options.forceProgress != null
? Math.round(clamp(options.forceProgress, 0, 100))
: behaviorKey === "ep_complete" ? 100 : metrics.progress;
context.play_speed = metrics.speed;
context.play_behavior_key = behaviorKey;
context.play_behavior_name = dramaBehaviorNames[behaviorKey] || behaviorKey;
return invoke("dramaEvent", context);
}
function trackDramaStatusChange(targetEpisode, targetVideoID, behaviorOverride) {
var context = dramaContext();
if (!context) return;
var nextEpisode = Math.max(1, integer(targetEpisode, context.episode));
var videoID = stringValue(targetVideoID, context.video_id);
var behavior = stringValue(behaviorOverride);
var names = {
ep_auto_play: "自动播放",
ep_previous: "上一集",
ep_next: "下一集",
ep_switch: "切换剧集",
ep_unlock: "解锁",
};
if (!behavior) {
if (nextEpisode === context.episode && videoID === context.video_id) return;
behavior = "ep_switch";
if (nextEpisode === context.episode + 1) behavior = "ep_next";
else if (nextEpisode === context.episode - 1) behavior = "ep_previous";
}
if (!names[behavior]) return;
return invoke("dramaStatusChange", {
media_id: context.media_id,
drama_id: context.drama_id,
drama_title: context.drama_title,
episode: nextEpisode,
video_id: videoID,
pre_episode: context.episode,
drama_behavior_key: behavior,
drama_behavior_name: names[behavior],
});
}
function trackDramaUnlock() {
var context = dramaContext();
if (!context) return Promise.resolve({ sent: false, ok: false, reason: "no_drama_context" });
return trackDramaStatusChange(context.episode, context.video_id, "ep_unlock");
}
function isMainDramaVideo(root, video) {
var mode = video && video.__trackerDramaMode;
if (!mode && root) {
mode = root.getAttribute("data-play-mode") === "preview" ? "preview" : "main";
}
return !!(
root &&
video &&
video.tagName === "VIDEO" &&
root.contains(video) &&
mode !== "preview"
);
}
function rememberDramaVideoMode(root, video) {
if (!root || !video || video.tagName !== "VIDEO" || !root.contains(video)) return;
video.__trackerDramaMode = root.getAttribute("data-play-mode") === "preview" ? "preview" : "main";
}
function prepareDramaModeChange(nextMode) {
var root = dramaRoot();
var video = root && root.querySelector("video");
if (!root || !video) return;
var previousMode = video.__trackerDramaMode ||
(root.getAttribute("data-play-mode") === "preview" ? "preview" : "main");
nextMode = nextMode === "preview" ? "preview" : "main";
if (previousMode === nextMode) return;
if (previousMode === "main") {
var playback = video.__trackerPlaybackState;
var wasPlaying = !!(playback && (playback.startedAt || playback.elapsedMs > 0));
settlePlayback(video);
if (wasPlaying) trackDramaEvent("ep_pause", video);
}
video.__trackerPlaybackState = null;
video.__trackerDramaMode = nextMode;
}
function bindDramaEvents() {
var root = dramaRoot();
if (!root) return;
if (!state.dramaViewSent) {
state.dramaViewSent = true;
trackDramaEvent("ep_view", null);
}
document.addEventListener("play", function (event) {
rememberDramaVideoMode(root, event.target);
if (isMainDramaVideo(root, event.target)) {
trackDramaEvent("ep_play", event.target);
}
}, true);
document.addEventListener("playing", function (event) {
rememberDramaVideoMode(root, event.target);
if (isMainDramaVideo(root, event.target)) beginPlayback(event.target);
}, true);
document.addEventListener("loadedmetadata", function (event) {
rememberDramaVideoMode(root, event.target);
}, true);
document.addEventListener("waiting", function (event) {
if (isMainDramaVideo(root, event.target)) settlePlayback(event.target);
}, true);
document.addEventListener("pause", function (event) {
if (isMainDramaVideo(root, event.target) && !event.target.ended) {
var playback = event.target.__trackerPlaybackState;
var wasPlaying = !!(playback && (playback.startedAt || playback.elapsedMs > 0));
settlePlayback(event.target);
if (event.target.seeking || !wasPlaying) return;
trackDramaEvent("ep_pause", event.target);
}
}, true);
document.addEventListener("timeupdate", function (event) {
if (!isMainDramaVideo(root, event.target) || event.target.seeking) return;
videoPlaybackState(event.target).lastPosition = Number(event.target.currentTime) || 0;
}, true);
document.addEventListener("seeking", function (event) {
if (!isMainDramaVideo(root, event.target)) return;
var playback = videoPlaybackState(event.target);
if (playback.seekFrom != null) return;
settlePlayback(event.target);
playback.seekFrom = playback.lastPosition;
}, true);
document.addEventListener("seeked", function (event) {
if (!isMainDramaVideo(root, event.target)) return;
var playback = videoPlaybackState(event.target);
if (playback.seekFrom == null) return;
var from = playback.seekFrom;
var target = Number(event.target.currentTime) || 0;
playback.seekFrom = null;
playback.lastPosition = target;
if (Math.abs(target - from) >= 0.5) {
trackDramaEvent(target > from ? "ep_forward" : "ep_rewind", event.target);
}
if (!event.target.paused && !event.target.ended) beginPlayback(event.target);
}, true);
document.addEventListener("ended", function (event) {
if (isMainDramaVideo(root, event.target)) {
settlePlayback(event.target);
trackDramaEvent("ep_complete", event.target, { keepPlaybackDuration: true });
trackDramaEvent("ep_play", event.target, { forceProgress: 100 });
}
}, true);
function reportPendingDramaProgress() {
var video = root.querySelector("video");
if (!isMainDramaVideo(root, video)) return false;
var playback = video.__trackerPlaybackState;
if (!playback || (!playback.startedAt && playback.elapsedMs <= 0)) return false;
var pendingMs = playback.elapsedMs +
(playback.startedAt ? Math.max(0, Date.now() - playback.startedAt) : 0);
if (pendingMs < 500) return false;
settlePlayback(video);
trackDramaEvent("ep_play", video);
return true;
}
function flushSDKAfterEnqueue() {
if (!global.WebSDK || typeof global.WebSDK.flush !== "function") return;
function flush() {
Promise.resolve(global.WebSDK.flush()).catch(logError);
}
Promise.resolve().then(function () {
return Promise.resolve();
}).then(flush);
global.setTimeout(flush, 50);
global.setTimeout(flush, 200);
}
document.addEventListener("visibilitychange", function () {
var video = root.querySelector("video");
if (document.visibilityState === "hidden") {
if (reportPendingDramaProgress()) flushSDKAfterEnqueue();
if (isMainDramaVideo(root, video) && !video.paused && !video.ended) beginPlayback(video);
return;
}
if (document.visibilityState === "visible" && isMainDramaVideo(root, video) &&
!video.paused && !video.ended) {
beginPlayback(video);
}
});
global.addEventListener("beforeunload", function () {
if (reportPendingDramaProgress()) flushSDKAfterEnqueue();
});
global.addEventListener("pagehide", function () {
if (reportPendingDramaProgress()) flushSDKAfterEnqueue();
});
}
function bindSearchEvents() {
var container = document.querySelector("[data-search-results]");
if (!container) return;
var keyword = stringValue(container.getAttribute("data-search-keyword"));
if (!keyword) return;
var grid = container.querySelector("[data-search-grid]");
var cards = grid ? Array.prototype.slice.call(grid.querySelectorAll("[data-search-item-id]")) : [];
var resultCount = Math.max(0, integer(container.getAttribute("data-search-result-count"), cards.length));
var pageNum = Math.max(1, integer(container.getAttribute("data-search-page"), 1));
var pageSize = Math.max(1, integer(container.getAttribute("data-search-page-size"), cards.length || 1));
if (pageNum === 1) {
invoke("keywordSearch", {
keyword: keyword,
search_result_count: resultCount,
search_content_type: "",
search_trace_id: "",
search_id: "",
});
}
if (!grid) return;
grid.addEventListener("click", function (event) {
var target = event.target && event.target.closest ? event.target : null;
var resultLink = target && target.closest("[data-search-result-click]");
var card = resultLink && resultLink.closest("[data-search-item-id]");
if (!card || !grid.contains(card)) return;
var position = cards.indexOf(card);
if (position < 0) return;
invoke("keywordClick", {
keyword: keyword,
click_item_id: stringValue(card.getAttribute("data-search-item-id")),
click_item_type_key: "short_drama",
click_item_type_name: "短剧",
click_position: (pageNum - 1) * pageSize + position + 1,
search_trace_id: "",
});
}, true);
}
function bindClicks() {
document.addEventListener("click", function (event) {
var target = event.target && event.target.closest ? event.target : null;
if (!target) return;
var popupClose = target.closest(
"[data-dialog-ad-close],[data-ad-close],[data-register-close]," +
"[data-download-close],[data-login-close],[data-recharge-close]," +
"[data-play-coin-close],[data-setting-password-close],[data-drawer-close]"
);
if (!popupClose) trackPageClick(event);
var switcher = target.closest("[data-drama-switch]");
if (switcher) {
trackDramaStatusChange(switcher.getAttribute("data-target-episode"), switcher.getAttribute("data-target-video-id"));
}
if (target.closest("[data-share-page]") && dramaRoot()) {
var root = dramaRoot();
var video = root.querySelector("video");
trackDramaEvent("ep_share", isMainDramaVideo(root, video) ? video : null);
}
var ad = target.closest('[data-track-ad="external"][data-ad-clickable="1"][data-ad-id]');
if (ad) {
trackExternalAd(ad, "click");
}
trackNavigation(target.closest("a[data-navigation-key],a[data-nav-key]"));
}, true);
}
function onReady() {
whenDOMReady(function () {
Promise.resolve(loadCommonFields()).then(function () {
trackPageView();
});
bindClicks();
bindAds();
bindExternalAdEvents();
bindAdvertisingEvents();
bindDramaEvents();
bindSearchEvents();
try {
global.dispatchEvent(new CustomEvent("tracker:ready", { detail: state.page }));
} catch (error) {}
});
}
var COMMON_FIELD_KEYS = [
"sid",
"device_id",
"device",
"device_fingerprint",
"fp_version",
];
var __commonFields = null;
var __commonFieldsReady = null;
function emptyCommonFields() {
var out = {};
for (var i = 0; i < COMMON_FIELD_KEYS.length; i++) {
out[COMMON_FIELD_KEYS[i]] = "";
}
return out;
}
function normalizeCommonFields(fields) {
var out = emptyCommonFields();
fields = fields || {};
for (var i = 0; i < COMMON_FIELD_KEYS.length; i++) {
var k = COMMON_FIELD_KEYS[i];
if (fields[k] != null) out[k] = String(fields[k]);
}
return out;
}
function publishCommonFields(fields) {
__commonFields = normalizeCommonFields(fields);
try {
global.__trackerCommonFields = __commonFields;
} catch (e) {}
try {
if (typeof CustomEvent === "function") {
global.dispatchEvent(
new CustomEvent("tracker:common-fields", { detail: __commonFields })
);
}
} catch (e) {}
return __commonFields;
}
function loadCommonFields() {
if (__commonFieldsReady) return __commonFieldsReady;
__commonFieldsReady = new Promise(function (resolve) {
function finish(fields) {
resolve(publishCommonFields(fields));
}
if (!global.WebSDK || typeof global.WebSDK.getCommonFields !== "function") {
finish(emptyCommonFields());
return;
}
try {
Promise.resolve(global.WebSDK.getCommonFields(COMMON_FIELD_KEYS.slice()))
.then(function (fields) {
finish(fields || emptyCommonFields());
})
.catch(function (err) {
logError(err);
finish(emptyCommonFields());
});
} catch (err) {
logError(err);
finish(emptyCommonFields());
}
});
return __commonFieldsReady;
}
function getCommonFieldsCached(keys) {
return loadCommonFields().then(function (all) {
if (!Array.isArray(keys) || !keys.length) return all;
var out = {};
for (var i = 0; i < keys.length; i++) {
var k = String(keys[i] || "").trim();
if (k) out[k] = Object.prototype.hasOwnProperty.call(all, k) ? all[k] : "";
}
return out;
});
}
function getCommonFieldCached(key) {
var k = String(key || "").trim();
if (!k) return Promise.resolve("");
return getCommonFieldsCached([k]).then(function (m) {
return m[k] || "";
});
}
function encodeSDKCommonHeader(fields) {
var payload = normalizeCommonFields(fields);
try {
return btoa(unescape(encodeURIComponent(JSON.stringify(payload))));
} catch (e) {
logError(e);
return "";
}
}
function getSDKCommonHeader() {
return loadCommonFields().then(function (fields) {
return encodeSDKCommonHeader(fields);
});
}
global.trackerInit = init;
global.tracker = {
init: init,
pageView: trackPageView,
dramaEvent: trackDramaEvent,
dramaStatusChange: trackDramaStatusChange,
dramaUnlock: trackDramaUnlock,
prepareDramaModeChange: prepareDramaModeChange,
advertising: function (payload) { return invoke("advertising", payload || {}); },
externalAd: trackExternalAd,
getDramaContext: function () {
var context = dramaContext();
return context ? Object.assign({}, context) : {};
},
keywordSearch: function (payload) { return invoke("keywordSearch", payload || {}); },
keywordClick: function (payload) { return invoke("keywordClick", payload || {}); },
getCommonFields: getCommonFieldsCached,
getCommonField: getCommonFieldCached,
getCommonFieldsSync: function () { return __commonFields; },
encodeSDKCommonHeader: encodeSDKCommonHeader,
getSDKCommonHeader: getSDKCommonHeader,
flush: function () {
if (!global.WebSDK || typeof global.WebSDK.flush !== "function") return Promise.resolve();
return Promise.resolve(global.WebSDK.flush()).catch(logError);
},
setUid: function (uid) {
if (global.WebSDK && global.WebSDK.setUid) global.WebSDK.setUid(normalizeUid(uid));
},
setChannel: function (channel) {
if (global.WebSDK && global.WebSDK.setChannel) global.WebSDK.setChannel(currentChannel(channel));
},
setContext: function (context) {
if (global.WebSDK && global.WebSDK.setContext) global.WebSDK.setContext(context || {});
},
};
init(global.__TRACKER_BOOT__ || {});
})(window);