UI/UX: Adiciona campo opcional para colagem manual de transcrição no VideoMind
This commit is contained in:
+233
@@ -0,0 +1,233 @@
|
||||
// noinspection ES6MissingAwait
|
||||
import { EventEmitter } from '../../utils/index.js';
|
||||
import { InnertubeError, Platform, u8ToBase64 } from '../../utils/Utils.js';
|
||||
import { LiveChatContinuation, Parser } from '../index.js';
|
||||
import SmoothedQueue from './SmoothedQueue.js';
|
||||
import RunAttestationCommand from '../classes/commands/RunAttestationCommand.js';
|
||||
import AddChatItemAction from '../classes/livechat/AddChatItemAction.js';
|
||||
import UpdateDateTextAction from '../classes/livechat/UpdateDateTextAction.js';
|
||||
import UpdateDescriptionAction from '../classes/livechat/UpdateDescriptionAction.js';
|
||||
import UpdateTitleAction from '../classes/livechat/UpdateTitleAction.js';
|
||||
import UpdateToggleButtonTextAction from '../classes/livechat/UpdateToggleButtonTextAction.js';
|
||||
import UpdateViewershipAction from '../classes/livechat/UpdateViewershipAction.js';
|
||||
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
|
||||
import ItemMenu from './ItemMenu.js';
|
||||
import { LiveMessageParams } from '../../../protos/generated/misc/params.js';
|
||||
export default class LiveChat extends EventEmitter {
|
||||
#actions;
|
||||
#video_id;
|
||||
#channel_id;
|
||||
#continuation;
|
||||
#mcontinuation;
|
||||
#retry_count = 0;
|
||||
smoothed_queue;
|
||||
initial_info;
|
||||
metadata;
|
||||
running = false;
|
||||
is_replay = false;
|
||||
constructor(video_info) {
|
||||
super();
|
||||
this.#video_id = video_info.basic_info.id;
|
||||
this.#channel_id = video_info.basic_info.channel_id;
|
||||
this.#actions = video_info.actions;
|
||||
this.#continuation = video_info.livechat?.continuation;
|
||||
this.is_replay = video_info.livechat?.is_replay || false;
|
||||
this.smoothed_queue = new SmoothedQueue();
|
||||
this.smoothed_queue.callback = async (actions) => {
|
||||
if (!actions.length) {
|
||||
// Wait 2 seconds before requesting an incremental continuation if the action group is empty.
|
||||
await this.#wait(2000);
|
||||
}
|
||||
else if (actions.length < 10) {
|
||||
// If there are less than 10 actions, wait until all of them are emitted.
|
||||
await this.#emitSmoothedActions(actions);
|
||||
}
|
||||
else if (this.is_replay) {
|
||||
/**
|
||||
* NOTE: Live chat replays require data from the video player for actions to be emitted timely
|
||||
* and as we don't have that, this ends up being quite innacurate.
|
||||
*/
|
||||
this.#emitSmoothedActions(actions);
|
||||
await this.#wait(2000);
|
||||
}
|
||||
else {
|
||||
// There are more than 10 actions, emit them asynchronously so we can request the next incremental continuation.
|
||||
this.#emitSmoothedActions(actions);
|
||||
}
|
||||
if (this.running) {
|
||||
this.#pollLivechat();
|
||||
}
|
||||
};
|
||||
}
|
||||
on(type, listener) {
|
||||
super.on(type, listener);
|
||||
}
|
||||
once(type, listener) {
|
||||
super.once(type, listener);
|
||||
}
|
||||
start() {
|
||||
if (!this.running) {
|
||||
this.running = true;
|
||||
this.#pollLivechat();
|
||||
this.#pollMetadata();
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
this.smoothed_queue.clear();
|
||||
this.running = false;
|
||||
}
|
||||
#pollLivechat() {
|
||||
(async () => {
|
||||
try {
|
||||
const response = await this.#actions.execute(this.is_replay ? 'live_chat/get_live_chat_replay' : 'live_chat/get_live_chat', { continuation: this.#continuation, parse: true });
|
||||
const contents = response.continuation_contents;
|
||||
if (!contents) {
|
||||
this.emit('error', new InnertubeError('Unexpected live chat incremental continuation response', response));
|
||||
this.emit('end');
|
||||
this.stop();
|
||||
}
|
||||
if (!(contents instanceof LiveChatContinuation)) {
|
||||
this.stop();
|
||||
this.emit('end');
|
||||
return;
|
||||
}
|
||||
this.#continuation = contents.continuation.token;
|
||||
// Header only exists in the first request
|
||||
if (contents.header) {
|
||||
this.initial_info = contents;
|
||||
this.emit('start', contents);
|
||||
if (this.running)
|
||||
this.#pollLivechat();
|
||||
}
|
||||
else {
|
||||
this.smoothed_queue.enqueueActionGroup(contents.actions);
|
||||
}
|
||||
this.#retry_count = 0;
|
||||
}
|
||||
catch (err) {
|
||||
this.emit('error', err);
|
||||
if (this.#retry_count++ < 10) {
|
||||
await this.#wait(2000);
|
||||
this.#pollLivechat();
|
||||
}
|
||||
else {
|
||||
this.emit('error', new InnertubeError('Reached retry limit for incremental continuation requests', err));
|
||||
this.emit('end');
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
/**
|
||||
* Ensures actions are emitted at the right speed.
|
||||
* This and {@link SmoothedQueue} were based off of YouTube's own implementation.
|
||||
*/
|
||||
async #emitSmoothedActions(action_queue) {
|
||||
const base = 1E4;
|
||||
let delay = action_queue.length < base / 80 ? 1 : Math.ceil(action_queue.length / (base / 80));
|
||||
const emit_delay_ms = delay == 1 ? (delay = base / action_queue.length,
|
||||
delay *= Math.random() + 0.5,
|
||||
delay = Math.min(1E3, delay),
|
||||
delay = Math.max(80, delay)) : delay = 80;
|
||||
for (const action of action_queue) {
|
||||
await this.#wait(emit_delay_ms);
|
||||
this.emit('chat-update', action);
|
||||
}
|
||||
}
|
||||
#pollMetadata() {
|
||||
(async () => {
|
||||
try {
|
||||
const payload = { videoId: this.#video_id };
|
||||
if (this.#mcontinuation) {
|
||||
payload.continuation = this.#mcontinuation;
|
||||
}
|
||||
const response = await this.#actions.execute('/updated_metadata', payload);
|
||||
const data = Parser.parseResponse(response.data);
|
||||
this.#mcontinuation = data.continuation?.token;
|
||||
this.metadata = {
|
||||
title: data.actions?.array().firstOfType(UpdateTitleAction) || this.metadata?.title,
|
||||
description: data.actions?.array().firstOfType(UpdateDescriptionAction) || this.metadata?.description,
|
||||
views: data.actions?.array().firstOfType(UpdateViewershipAction) || this.metadata?.views,
|
||||
likes: data.actions?.array().firstOfType(UpdateToggleButtonTextAction) || this.metadata?.likes,
|
||||
date: data.actions?.array().firstOfType(UpdateDateTextAction) || this.metadata?.date
|
||||
};
|
||||
this.emit('metadata-update', this.metadata);
|
||||
await this.#wait(5000);
|
||||
if (this.running)
|
||||
this.#pollMetadata();
|
||||
}
|
||||
catch {
|
||||
await this.#wait(2000);
|
||||
if (this.running)
|
||||
this.#pollMetadata();
|
||||
}
|
||||
})();
|
||||
}
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param text - Text to send.
|
||||
*/
|
||||
async sendMessage(text) {
|
||||
const writer = LiveMessageParams.encode({
|
||||
params: {
|
||||
ids: {
|
||||
videoId: this.#video_id,
|
||||
channelId: this.#channel_id
|
||||
}
|
||||
},
|
||||
number0: 1,
|
||||
number1: 4
|
||||
});
|
||||
const params = btoa(encodeURIComponent(u8ToBase64(writer.finish())));
|
||||
const response = await this.#actions.execute('/live_chat/send_message', {
|
||||
richMessage: { textSegments: [{ text }] },
|
||||
clientMessageId: Platform.shim.uuidv4(),
|
||||
client: 'WEB',
|
||||
parse: true,
|
||||
params
|
||||
});
|
||||
if (!response.actions)
|
||||
throw new InnertubeError('Unexpected response from send_message', response);
|
||||
return response.actions.array().as(AddChatItemAction, RunAttestationCommand);
|
||||
}
|
||||
/**
|
||||
* Applies given filter to the live chat.
|
||||
* @param filter - Filter to apply.
|
||||
*/
|
||||
applyFilter(filter) {
|
||||
if (!this.initial_info)
|
||||
throw new InnertubeError('Cannot apply filter before initial info is retrieved.');
|
||||
const menu_items = this.initial_info?.header?.view_selector?.sub_menu_items;
|
||||
if (filter === 'TOP_CHAT') {
|
||||
if (menu_items?.at(0)?.selected)
|
||||
return;
|
||||
this.#continuation = menu_items?.at(0)?.continuation;
|
||||
}
|
||||
else {
|
||||
if (menu_items?.at(1)?.selected)
|
||||
return;
|
||||
this.#continuation = menu_items?.at(1)?.continuation;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieves given chat item's menu.
|
||||
*/
|
||||
async getItemMenu(item) {
|
||||
if (!item.hasKey('menu_endpoint') || !item.key('menu_endpoint').isInstanceof(NavigationEndpoint))
|
||||
throw new InnertubeError('This item does not have a menu.', item);
|
||||
const response = await item.key('menu_endpoint').instanceof(NavigationEndpoint).call(this.#actions, { parse: true });
|
||||
if (!response)
|
||||
throw new InnertubeError('Could not retrieve item menu.', item);
|
||||
return new ItemMenu(response, this.#actions);
|
||||
}
|
||||
/**
|
||||
* Equivalent to "clicking" a button.
|
||||
*/
|
||||
async selectButton(button) {
|
||||
return await button.endpoint.call(this.#actions, { parse: true });
|
||||
}
|
||||
async #wait(ms) {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(), ms));
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=LiveChat.js.map
|
||||
Reference in New Issue
Block a user