UNPKG

@caplin/streamlink-devtools

Version:

StreamLink wrapper for Caplin Developer Tools

1,611 lines (1,422 loc) 52.1 kB
/* eslint-disable */ // @ts-nocheck let __caplin_devtools_show_gui_in_browser_tab__ = false; let __caplin_devtools_webworker__ = undefined; export function withDevTools(streamlink, webWorkerUrl = undefined) { if (webWorkerUrl && !__caplin_devtools_webworker__) { __caplin_devtools_show_gui_in_browser_tab__ = true; // start content script in webworker if (window.Worker) { try { __caplin_devtools_webworker__ = new Worker(webWorkerUrl); console.log("Caplin dev tools script loaded in web worker"); } catch (e) { console.error("Error starting Caplin dev tools script in web worker", e); return undefined; } } else { console.error("Web workers not supported, unable to start Caplin dev tools script"); return undefined; } } const streamLinkInterceptor = new StreamLinkInterceptor(streamlink); const proxy = intercept(streamlink, streamLinkInterceptor); streamLinkInterceptor.proxyStreamLink = proxy; return proxy; } class BaseInterceptor { __other__(method) { this.__post__({ method: method.name, params: method.stringArgs, }); } } class StreamLinkInterceptor extends BaseInterceptor { constructor(streamlink) { super(); this.streamlink = streamlink; this.streamlinkId = randomUUID(); this.connectionListeners = new Map(); this.apiLineNumber = 1; this.logLineNumber = 1; this.toolsConfig = null; this.userSubscriptions = new Map(); this.proxyStreamLink = null; this.messageListener = (event) => { // console.log("window message:", event); if (__caplin_devtools_show_gui_in_browser_tab__ || (event.source instanceof Window && event.source === window)) { if (event.data && event.data.source === "__caplin_streamlink_devtools__") { // console.log("Message from content script:", event.data); switch (event.data.type) { case "setToolsConfig": this.toolsConfig = event.data.toolsConfig; localStorage.setItem("__caplin_streamlink_devtools__", JSON.stringify(this.toolsConfig)); this.applySubscriptions(); break; } } } }; const jsonConfig = localStorage.getItem("__caplin_streamlink_devtools__"); let savedToolsConfig = {}; if (jsonConfig) { try { savedToolsConfig = JSON.parse(jsonConfig); } catch (e) { console.log("Unable to parse saved Devtools JSON config: ", jsonConfig); } } if (__caplin_devtools_show_gui_in_browser_tab__) { __caplin_devtools_webworker__.onmessage = this.messageListener; } else { window.addEventListener('message', this.messageListener); } windowPost({type: "toolsConfig", toolsConfig: savedToolsConfig}); try { streamlink.getLogger().addListener( { onLog: (info) => { windowPost({ type: "log", lineNumber: this.logLineNumber++, level: info.getLevel().getValue(), line: info.toString(), }); }, }, {getValue: () => 9, getPaddedName: () => "FINEST"} ); } catch (e) { console.log(e); } } applySubscriptions() { const sentSubs = this.toolsConfig.subscriptions; const sentIds = new Map(); for (const sub of sentSubs) { sentIds.set(sub.id, null); const currentSub = this.userSubscriptions.get(sub.id); if (currentSub) { // existing sub if (currentSub.subject !== sub.subject || currentSub.enabled !== sub.enabled) { // it's changed if (currentSub.enabled && currentSub.subscribed) { currentSub.subscription.unsubscribe(); } if (sub.enabled) { currentSub.subject = sub.subject; currentSub.enabled = sub.enabled; currentSub.subscription = this.proxyStreamLink.subscribe(sub.subject, currentSub); currentSub.subscribed = true; } else { this.userSubscriptions.delete(sub.id); } } } else { // new sub if (sub.enabled) { const newSub = { id: sub.id, subject: sub.subject, enabled: sub.enabled, subscribed: false, subscription: null, onSubscriptionError: (subscription, evt) => { newSub.subscribed = false; }, onSubscriptionStatus: (subscription, evt) => { }, onStoryUpdate: function (subscription, event) { }, onJsonUpdate: function (subscription, event) { }, onRecordUpdate: function (subscription, event) { }, onRecordType2Update: function (subscription, event) { }, onRecordType3Update: function (subscription, event) { }, onPermissionUpdate: function (subscription, event) { }, onNewsUpdate: function (subscription, event) { }, onDirectoryUpdate: function (subscription, event) { }, onContainerUpdate: function (subscription, event) { }, onChatUpdate: function (subscription, event) { }, onPageUpdate: function (subscription, event) { } }; this.userSubscriptions.set(newSub.id, newSub); newSub.subscription = this.proxyStreamLink.subscribe(newSub.subject, newSub); newSub.subscribed = true; } } } // find entries to remove const toRemove = []; for (const [id, sub] of this.userSubscriptions) { if (!sentIds.has(id)) { toRemove.push(id); } } // remove and unsubscribe for (const id of toRemove) { const sub = this.userSubscriptions.get(id); if (sub.enabled && sub.subscribed) { sub.subscription.unsubscribe(); } this.userSubscriptions.delete(id); } } applyLogging(json) { if (json.method && json.params && json.params.subject && json.type) { if (this.toolsConfig && this.toolsConfig.logMessages && this.toolsConfig.logMessages.some((lm) => { return lm.enabled && lm.subject == json.params.subject })) { const prefix = json.timestamp + (json.type === "callback" ? " Incoming: " : " Outgoing: ") + json.params.subject; const image = json.params.image ? json.params.image : false; if (json.params.fields) { console.log(prefix, json.params.fields, "image: " + image); } else if (json.params.json) { console.log(prefix, json.params.json, "image: " + image); } else { console.log(prefix, json.method, json.params); } } } } __basePost__(json) { json.timestamp = formatDateStamp(new Date()); this.applyLogging(json); json.streamlinkId = this.streamlinkId; json.lineNumber = this.apiLineNumber++; windowPost(json); } __post__(json) { json.type = "call"; json.classType = "StreamLink"; json.caller = callingFunction(5); this.__basePost__(json); } connect(method) { this.__post__({method: method.name}); } disconnect(method) { this.__post__({method: method.name}); window.removeEventListener('message', this.messageListener); } addConnectionListener(method, connectionListener) { const subscriptionId = randomUUID(); const wrappedListener = intercept( connectionListener, new ConnectionListenerInterceptor(subscriptionId, this) ); this.connectionListeners.set(connectionListener, wrappedListener); this.__post__({ method: method.name, subscriptionId, }); method.args[0] = wrappedListener; method.apply(method.args); } removeConnectionListener(method, connectionListener) { const wrappedListener = this.connectionListeners.get(connectionListener); if (wrappedListener) { this.connectionListeners.delete(connectionListener); method.args[0] = wrappedListener; method.apply(method.args); } this.__post__({method: method.name}); } subscribe(method, subject, subscriptionListener, subscriptionParameters) { const subscriptionId = randomUUID(); method.args[1] = intercept( subscriptionListener, new SubscriptionListenerInterceptor(subscriptionId, this) ); const subscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, subscriptionParameters}, }); return intercept( subscription, new SubscriptionInterceptor(subscriptionId, subject, this) ); } snapshot(method, subject, subscriptionListener, subscriptionParameters) { const subscriptionId = randomUUID(); method.args[1] = intercept( subscriptionListener, new SubscriptionListenerInterceptor(subscriptionId, this) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, subscriptionParameters}, }); } createSubject( method, subject, subjectType, commandListener, commandParameters ) { const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: { subject, subjectType: subjectType.toString(), commandParameters, }, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } deleteSubject(method, subject, commandListener) { const subscriptionId = randomUUID(); method.args[1] = intercept( commandListener, new CommandListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({method: method.name, subscriptionId, params: {subject}}); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } publishToSubject( method, subject, fieldData, commandListener, commandParameters ) { if (!Array.isArray(fieldData)) { // not an array, just a map of fieldname:fieldvalue this.applyToolsIntercept("output", subject, fieldData); } else { // an array of maps this.applyToolsInterceptForArray("output", subject, fieldData); } const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, fieldData, commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } publishToJsonSubject( method, subject, jsonObject, commandListener, commandParameters ) { const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, jsonObject, commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } create(method, subject, commandParameters, commandListener) { const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandResultListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } delete(method, subject, commandParameters, commandListener) { const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandResultListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } publish(method, subject, commandParameters, commandListener) { const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandResultListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } throttleSubject( method, subject, throttleCommand, commandListener, commandParameters ) { const subscriptionId = randomUUID(); method.args[2] = intercept( commandListener, new CommandListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } throttleEverything( method, throttleCommand, commandListener, commandParameters ) { const subscriptionId = randomUUID(); method.args[1] = intercept( commandListener, new CommandListenerInterceptor(subscriptionId, this) ); const commandSubscription = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {commandParameters}, }); return intercept( commandSubscription, new CommandSubscriptionInterceptor(subscriptionId, this) ); } getLogger(method) { this.__post__({method: method.name}); } getVersion(method) { // this.__post__({method: "getVersion"}); } networkAvailable(method) { this.__post__({method: method.name}); } networkUnavailable(method) { this.__post__({method: method.name}); } pause(method) { this.__post__({method: method.name}); } resume(method) { this.__post__({method: method.name}); } createWebRequestParameters(method, moduleName, options) { this.__post__({ method: method.name, params: {moduleName, options}, }); } getLastLog(method) { this.__post__({method: method.name}); } createChannel(method, subject, channelListener, commandParameters) { const subscriptionId = randomUUID(); method.args[1] = intercept( channelListener, new ChannelListenerInterceptor(subscriptionId, this) ); const channel = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, commandParameters}, }); return intercept(channel, new ChannelInterceptor(subject, subscriptionId, this)); } createJsonChannel(method, subject, channelListener, commandParameters) { const subscriptionId = randomUUID(); method.args[1] = intercept( channelListener, new JsonChannelListenerInterceptor(subscriptionId, this) ); const channel = method.apply(method.args); this.__post__({ method: method.name, subscriptionId, params: {subject, commandParameters}, }); return intercept(channel, new JsonChannelInterceptor(subscriptionId, this)); } getConnectionCurrentState(method) { this.__post__({ method: method.name, }); } registerRecordProvider(method, pattern, provider, commandListener) { const providerId = randomUUID(); method.args[1] = intercept( provider, new RecordProviderInterceptor(providerId, this) ); if (commandListener) { method.args[2] = intercept( commandListener, new CommandListenerInterceptor(providerId, this) ); } const providerHandle = method.apply(method.args); this.__post__({ method: method.name, providerId, params: {pattern}, }); return providerHandle; } registerJsonProvider(method, pattern, provider, commandListener) { const providerId = randomUUID(); method.args[1] = intercept( provider, new JsonProviderInterceptor(providerId, this) ); if (commandListener) { method.args[2] = intercept( commandListener, new CommandListenerInterceptor(providerId, this) ); } const providerHandle = method.apply(method.args); this.__post__({ method: method.name, providerId, params: {pattern}, }); return providerHandle; } applyToolsIntercept(type, subject, fieldsMap) { let logMessage = ""; if (this.toolsConfig && this.toolsConfig.intercepts) { for (const intercept of this.toolsConfig.intercepts) { if (intercept.enabled && intercept.type === type && ((intercept.isRegEx === false && subject === intercept.subject) || (intercept.isRegEx === true && subject.match(new RegExp(intercept.subject))))) { let allowIntercept = true; if (intercept.where) { for (const field of intercept.where) { if (!fieldsMap[field.name]) { allowIntercept = false; break; } if (fieldsMap[field.name] !== field.value) { allowIntercept = false; break; } } } if (allowIntercept) { for (const field of intercept.fields) { logMessage += field.name + ": " + fieldsMap[field.name] + " -> " + field.value + ", "; fieldsMap[field.name] = field.value; } } } } } if (logMessage !== "") { this.__post__({ method: "--Message Intercept--", params: {subject: subject, changes: logMessage}, }) } } applyToolsInterceptForArray(type, subject, fieldsArray) { let logMessage = ""; if (this.toolsConfig && this.toolsConfig.intercepts) { for (const intercept of this.toolsConfig.intercepts) { if (intercept.enabled && intercept.type === type && ((intercept.isRegEx === false && subject === intercept.subject) || (intercept.isRegEx === true && subject.match(new RegExp(intercept.subject))))) { let allowIntercept = true; if (intercept.where) { for (const field of intercept.where) { const found = fieldsArray.find((m) => m[field.name] !== undefined); if (!found) { allowIntercept = false; break; } if (found[field.name] !== field.value) { allowIntercept = false; break; } } } if (allowIntercept) { for (const field of intercept.fields) { const found = fieldsArray.find((m) => m[field.name] !== undefined) logMessage += field.name + ": " + found[field.name] + " -> " + field.value + ", "; found[field.name] = field.value; } } } } } if (logMessage !== "") { this.__post__({ method: "--Message Intercept--", params: {subject: subject, changes: logMessage}, }) } } } // =================================================== class SubscriptionInterceptor extends BaseInterceptor { constructor(subscriptionId, subject, streamlinkInterceptor) { super(); this.subscriptionId = subscriptionId; this.subject = subject; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "call"; json.classType = "Subscription"; json.caller = callingFunction(5); this.streamlinkInterceptor.__basePost__(json); } getSubject(method) { } getSubscriptionListener(method) { } unsubscribe(method) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: {subject: this.subject}, }); } setContainerWindow(method, start, size) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: {start, size}, }); } } class SubscriptionListenerInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.streamlinkInterceptor = streamlinkInterceptor; this.subscriptionId = subscriptionId; } __post__(json) { json.type = "callback"; json.classType = "SubscriptionListener"; this.streamlinkInterceptor.__basePost__(json); } onStoryUpdate(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), image: evt.isImage(), }, }); } onDirectoryUpdate(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), image: evt.isImage(), }, }); } onNewsUpdate(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), image: evt.isImage(), }, }); } onPageUpdate(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), image: evt.isImage(), }, }); } onChatUpdate(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), image: evt.isImage(), }, }); } onRecordUpdate(method, subscription, evt) { this.streamlinkInterceptor.applyToolsIntercept("input", evt.getSubject(), evt.getFields()); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), fields: sortFields(evt.getFields()), image: evt.isImage(), }, }); } onRecordType2Update(method, subscription, evt) { this.streamlinkInterceptor.applyToolsIntercept("input", evt.getSubject(), evt.getFields()); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), fields: sortFields(evt.getFields()), image: evt.isImage(), indexField: evt.getType2IndexField(), deleteRow: evt.deleteRow(), deleteAllRows: evt.deleteAllRows(), }, }); } onRecordType3Update(method, subscription, evt) { this.streamlinkInterceptor.applyToolsIntercept("input", evt.getSubject(), evt.getFields()); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), fields: sortFields(evt.getFields()), image: evt.isImage(), deleteAllEntries: evt.deleteAllEntries(), }, }); } onPermissionUpdate(method, subscription, evt) { this.streamlinkInterceptor.applyToolsIntercept("input", evt.getSubject(), evt.getFields()); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), fields: sortFields(evt.getFields()), image: evt.isImage(), indexField: evt.getKey(), deleteRow: evt.deleteKey(), deleteAllRows: evt.deleteAllKeys(), }, }); } onJsonUpdate(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), json: evt.getJson(), jsonString: evt.getJsonString(), image: evt.isImage(), }, }); } onContainerUpdate(method, subscription, evt) { const ops = []; evt.updateModel({ clear: () => { ops.push({op: "clear"}); }, insert: (index, element) => { ops.push({op: "insert", index: index, subject: element.getSubject()}); }, remove: (index, element) => { ops.push({op: "remove", index: index}); }, move: (from, to, element) => { ops.push({op: "move", from, to}); }, }); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), size: evt.getSize(), windowStart: evt.getWindowStart(), windowEnd: evt.getWindowEnd(), ops: ops, image: evt.isImage(), }, }); } onSubscriptionStatus(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), status: evt.getStatus().toString(), message: evt.getStatusMessage(), }, }); } onSubscriptionError(method, subscription, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), error: evt.getError().toString(), errorReason: evt.getErrorReason(), }, }); } } class ChannelInterceptor extends BaseInterceptor { constructor(subject, subscriptionId, streamlinkInterceptor) { super(); this.subject = subject; this.subscriptionId = subscriptionId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "call"; json.classType = "Channel"; json.caller = callingFunction(5); this.streamlinkInterceptor.__basePost__(json); } getSubject(method) { } closeChannel(method) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: {}, }); } send(method, fields, commandListener) { this.streamlinkInterceptor.applyToolsIntercept("output", this.subject, fields); method.args[1] = intercept( commandListener, new CommandListenerInterceptor(this.subscriptionId, this.streamlinkInterceptor) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: {fields}, }); } sendWithFieldList(method, fieldlist, commandListener) { method.args[1] = intercept( commandListener, new CommandListenerInterceptor(this.subscriptionId, this.streamlinkInterceptor) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: {fieldlist}, }); } } class ChannelListenerInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.streamlinkInterceptor = streamlinkInterceptor; this.subscriptionId = subscriptionId; } __post__(json) { json.type = "callback"; json.classType = "ChannelListener"; this.streamlinkInterceptor.__basePost__(json); } onChannelData(method, channel, evt) { this.streamlinkInterceptor.applyToolsIntercept("input", evt.getSubject(), evt.getFields()); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), image: evt.isImage(), fields: sortFields(evt.getFields()) }, }); } onChannelStatus(method, channel, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), status: evt.getStatus().toString(), message: evt.getStatusMessage(), }, }); } onChannelError(method, channel, evt) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: evt.getSubject(), error: evt.getError().toString(), errorReason: evt.getErrorReason(), }, }); } } class ConnectionListenerInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.streamlinkInterceptor = streamlinkInterceptor; this.subscriptionId = subscriptionId; } __post__(json) { json.type = "callback"; json.classType = "ConnectionListener"; this.streamlinkInterceptor.__basePost__(json); } onConnectionStatusChange(method, connectionStatusEvent) { const params = { connectionState: connectionStatusEvent.getConnectionState().toString(), liberatorUrl: connectionStatusEvent.getLiberatorUrl(), }; if (connectionStatusEvent.getUsername() !== null) { params.userName = connectionStatusEvent.getUsername(); } if (connectionStatusEvent.getLoginFailReason() !== "UNDEFINED") { params.loginFailedReason = connectionStatusEvent.getLoginFailReason(); } this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params, }); } onSourceStatusChange(method, sourceStatusEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { sourceName: sourceStatusEvent.getSourceName(), sourceStatus: sourceStatusEvent.getSourceStatus().toString(), }, }); } onServiceStatusChange(method, serviceStatusEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { serviceName: serviceStatusEvent.getServiceName(), serviceStatus: serviceStatusEvent.getServiceStatus().toString(), }, }); } onStatisticsChange(method, statisticsEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { latency: statisticsEvent.getLatency(), averageLatency: statisticsEvent.getAverageLatency(), clockOffset: statisticsEvent.getClockOffset(), }, }); } } class JsonChannelInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.subscriptionId = subscriptionId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "call"; json.classType = "JsonChannel"; json.caller = callingFunction(5); this.streamlinkInterceptor.__basePost__(json); } getSubject(method) { } closeChannel(method) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, }); } send(method, json, commandListener) { method.args[1] = intercept( commandListener, new CommandListenerInterceptor(this.subscriptionId, this.streamlinkInterceptor) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { json: json, }, }); } } class JsonChannelListenerInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.subscriptionId = subscriptionId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "callback"; json.classType = "JsonChannelListener"; this.streamlinkInterceptor.__basePost__(json); } onChannelData(method, channel, jsonEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: jsonEvent.getSubject(), json: jsonEvent.getJson(), jsonString: jsonEvent.getJsonString(), }, }); } onChannelStatus(method, channel, subscriptionStatusEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: subscriptionStatusEvent.getSubject(), status: subscriptionStatusEvent.getStatus().toString(), message: subscriptionStatusEvent.getStatusMessage(), }, }); } onChannelError(method, channel, subscriptionErrorEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: subscriptionErrorEvent.getSubject(), error: subscriptionErrorEvent.getError().toString(), errorReason: subscriptionErrorEvent.getErrorReason(), }, }); } } class CommandSubscriptionInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.subscriptionId = subscriptionId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "call"; json.classType = "CommandSubscription"; json.caller = callingFunction(5); this.streamlinkInterceptor.__basePost__(json); } getSubject(method) { } getCommandListener(method) { } unPersist(method) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, }); } } class CommandListenerInterceptor extends BaseInterceptor { constructor(subscriptionId, streamlinkInterceptor) { super(); this.subscriptionId = subscriptionId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "callback"; json.classType = "CommandListener"; this.streamlinkInterceptor.__basePost__(json); } onCommandError(method, subject, commandErrorEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: subject, error: commandErrorEvent.getError().toString(), reason: commandErrorEvent.getErrorReason().toString(), }, }); } onCommandOk(method, subject) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: subject, }, }); } } class CommandResultListenerInterceptor extends CommandListenerInterceptor { __post__(json) { json.type = "callback"; json.classType = "CommandResultListener"; this.streamlinkInterceptor.__basePost__(json); } onCommandResult(method, subject, commandEvent) { this.__post__({ method: method.name, subscriptionId: this.subscriptionId, params: { subject: subject, payload: commandEvent.getPayload(), }, }); } } // =================================================== class RecordProviderInterceptor extends BaseInterceptor { constructor(providerId, streamlinkInterceptor) { super(); this.providerId = providerId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "callback"; json.classType = "RecordProvider"; this.streamlinkInterceptor.__basePost__(json); } onRequest(method, publisher) { method.args[0] = intercept( publisher, new RecordPublisherInterceptor(this.providerId, this.streamlinkInterceptor, publisher.getSubject()) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: publisher.getSubject() }, }); } } // =================================================== class RecordPublisherInterceptor extends BaseInterceptor { constructor(providerId, streamlinkInterceptor, subject) { super(); this.providerId = providerId; this.streamlinkInterceptor = streamlinkInterceptor; this.subject = subject; } __post__(json) { json.type = "call"; json.classType = "RecordPublisher"; this.streamlinkInterceptor.__basePost__(json); } send(method, fields, image, commandListener) { this.streamlinkInterceptor.applyToolsIntercept("output", this.subject, fields); if (commandListener) { method.args[2] = intercept( commandListener, new CommandListenerInterceptor(this.providerId, this.streamlinkInterceptor) ); } method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: this.subject, fields: fields, image: image }, }); } setDiscardHandler(method, discardHandler) { method.args[0] = intercept( discardHandler, new RecordDiscardHandlerInterceptor(this.providerId, this.streamlinkInterceptor, this.subject) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: this.subject }, }); } } // =================================================== class RecordDiscardHandlerInterceptor extends BaseInterceptor { constructor(providerId, streamlinkInterceptor, subject) { super(); this.providerId = providerId; this.streamlinkInterceptor = streamlinkInterceptor; this.subject = subject; } __post__(json) { json.type = "callback"; json.classType = "RecordDiscardHandler"; this.streamlinkInterceptor.__basePost__(json); } onDiscard(method, publisher) { this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: this.subject }, }); } } // =================================================== class JsonProviderInterceptor extends BaseInterceptor { constructor(providerId, streamlinkInterceptor) { super(); this.providerId = providerId; this.streamlinkInterceptor = streamlinkInterceptor; } __post__(json) { json.type = "callback"; json.classType = "JsonProvider"; this.streamlinkInterceptor.__basePost__(json); } onRequest(method, publisher) { method.args[0] = intercept( publisher, new JsonPublisherInterceptor(this.providerId, this.streamlinkInterceptor, publisher.getSubject()) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: publisher.getSubject() }, }); } } // =================================================== class JsonPublisherInterceptor extends BaseInterceptor { constructor(providerId, streamlinkInterceptor, subject) { super(); this.providerId = providerId; this.streamlinkInterceptor = streamlinkInterceptor; this.subject = subject; } __post__(json) { json.type = "call"; json.classType = "JsonPublisher"; this.streamlinkInterceptor.__basePost__(json); } send(method, jsonObject, commandListener) { if (commandListener) { method.args[1] = intercept( commandListener, new CommandListenerInterceptor(this.providerId, this.streamlinkInterceptor) ); } method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: this.subject, jsonObject: jsonObject, }, }); } setDiscardHandler(method, discardHandler) { method.args[0] = intercept( discardHandler, new JsonDiscardHandlerInterceptor(this.providerId, this.streamlinkInterceptor, this.subject) ); method.apply(method.args); this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: this.subject }, }); } } // =================================================== class JsonDiscardHandlerInterceptor extends BaseInterceptor { constructor(providerId, streamlinkInterceptor, subject) { super(); this.providerId = providerId; this.streamlinkInterceptor = streamlinkInterceptor; this.subject = subject; } __post__(json) { json.type = "callback"; json.classType = "JsonDiscardHandler"; this.streamlinkInterceptor.__basePost__(json); } onDiscard(method, publisher) { this.__post__({ method: method.name, subscriptionId: this.providerId, params: { subject: this.subject }, }); } } // =================================================== function formatDateStamp(dt) { var timeZoneOffset = dt.getTimezoneOffset(), timezoneOffsetAbsolute = Math.abs(timeZoneOffset), offsetSign = timeZoneOffset <= 0 ? "+" : "-", offsetHours = padZeros( Math.floor(Math.abs(timezoneOffsetAbsolute) / 60), 2 ), offsetMinutes = padZeros(timezoneOffsetAbsolute % 60, 2), paddedMonth = padZeros(dt.getMonth() + 1, 2), paddedDay = padZeros(dt.getDate(), 2), paddedHours = padZeros(dt.getHours(), 2), paddedMinutes = padZeros(dt.getMinutes(), 2), paddedSeconds = padZeros(dt.getSeconds(), 2), paddedMillis = padZeros(dt.getMilliseconds(), 3); return ( dt.getFullYear() + "/" + paddedMonth + "/" + paddedDay + "-" + paddedHours + ":" + paddedMinutes + ":" + paddedSeconds + "." + paddedMillis + " " + offsetSign + offsetHours + offsetMinutes ); } const zeroPadding = ["", "0", "00", "000"]; function padZeros(val, paddedLength) { var valLen = (val + "").length, diff = paddedLength - valLen; return zeroPadding[diff] + val; } function sortFields(fields) { const names = Object.keys(fields); names.sort(); const map = {}; for (const name of names) { map[name] = fields[name]; } return map; } function windowPost(json) { json.source = "__caplin_streamlink__"; // console.log("post " + JSON.stringify(json)); try { if (__caplin_devtools_show_gui_in_browser_tab__) { __caplin_devtools_webworker__.postMessage(json); } else { window.postMessage(json, "*"); } } catch (e) { console.log(e); } } function callingFunction(level) { let caller = null; try { throw new Error(); } catch (e) { // Inspect the call stack try { const stack = e.stack.split("\n"); const callerLine = stack[level]; // Line representing the calling function caller = callerLine.match(/at\s+(.*)\s+\(/)[1]; } catch (e2) { } } return caller; } function randomUUID() { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => ( c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4))) ).toString(16) ); } function intercept(obj, interceptor) { try { return new Proxy(obj, { get(target, prop, receiver) { if (typeof