mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-17 05:02:06 +00:00
fix(music-together): typing
This commit is contained in:
@ -472,6 +472,7 @@
|
|||||||
"disconnected": "Music Together disconnected",
|
"disconnected": "Music Together disconnected",
|
||||||
"host-failed": "Failed to host Music Together",
|
"host-failed": "Failed to host Music Together",
|
||||||
"id-copied": "Host ID copied to clipboard",
|
"id-copied": "Host ID copied to clipboard",
|
||||||
|
"id-copy-failed": "Failed to copy Host ID to clipboard",
|
||||||
"join-failed": "Failed to join Music Together",
|
"join-failed": "Failed to join Music Together",
|
||||||
"joined": "Joined Music Together",
|
"joined": "Joined Music Together",
|
||||||
"permission-changed": "Music Together permission changed to \"{{permission}}\"",
|
"permission-changed": "Music Together permission changed to \"{{permission}}\"",
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import prompt from 'custom-electron-prompt';
|
import prompt from 'custom-electron-prompt';
|
||||||
|
|
||||||
|
import { DataConnection } from 'peerjs';
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import promptOptions from '@/providers/prompt-options';
|
import promptOptions from '@/providers/prompt-options';
|
||||||
@ -17,7 +19,6 @@ import style from './style.css?inline';
|
|||||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||||
import type { RendererContext } from '@/types/contexts';
|
import type { RendererContext } from '@/types/contexts';
|
||||||
import type { VideoDataChanged } from '@/types/video-data-changed';
|
import type { VideoDataChanged } from '@/types/video-data-changed';
|
||||||
import { DataConnection } from 'peerjs';
|
|
||||||
|
|
||||||
type RawAccountData = {
|
type RawAccountData = {
|
||||||
accountName: {
|
accountName: {
|
||||||
@ -34,7 +35,44 @@ type RawAccountData = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createPlugin({
|
export default createPlugin<
|
||||||
|
unknown,
|
||||||
|
unknown,
|
||||||
|
{
|
||||||
|
connection?: Connection;
|
||||||
|
ipc?: RendererContext<never>['ipc'];
|
||||||
|
api: HTMLElement & AppAPI | null;
|
||||||
|
queue?: Queue;
|
||||||
|
playerApi?: YoutubePlayer;
|
||||||
|
showPrompt: (title: string, label: string) => Promise<string>;
|
||||||
|
popups: {
|
||||||
|
host: ReturnType<typeof createHostPopup>;
|
||||||
|
guest: ReturnType<typeof createGuestPopup>;
|
||||||
|
setting: ReturnType<typeof createSettingPopup>;
|
||||||
|
};
|
||||||
|
elements: {
|
||||||
|
setting: HTMLElement;
|
||||||
|
icon: SVGElement;
|
||||||
|
spinner: HTMLElement;
|
||||||
|
};
|
||||||
|
stateInterval?: number;
|
||||||
|
updateNext: boolean;
|
||||||
|
ignoreChange: boolean;
|
||||||
|
rollbackInjector?: (() => void);
|
||||||
|
me?: Omit<Profile, 'id'>;
|
||||||
|
profiles: Record<string, Profile>;
|
||||||
|
permission: Permission;
|
||||||
|
videoChangeListener: (event: CustomEvent<VideoDataChanged>) => void;
|
||||||
|
videoStateChangeListener: () => void;
|
||||||
|
onHost: () => Promise<boolean>;
|
||||||
|
onJoin: () => Promise<boolean>;
|
||||||
|
onStop: () => void;
|
||||||
|
putProfile: (id: string, profile?: Profile) => void;
|
||||||
|
showSpinner: () => void;
|
||||||
|
hideSpinner: () => void;
|
||||||
|
initMyProfile: () => void;
|
||||||
|
}
|
||||||
|
>({
|
||||||
name: () => t('plugins.music-together.name'),
|
name: () => t('plugins.music-together.name'),
|
||||||
description: () => t('plugins.music-together.description'),
|
description: () => t('plugins.music-together.description'),
|
||||||
restartNeeded: false,
|
restartNeeded: false,
|
||||||
@ -43,50 +81,38 @@ export default createPlugin({
|
|||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
stylesheets: [style],
|
stylesheets: [style],
|
||||||
backend: {
|
backend({ ipc }) {
|
||||||
async start({ ipc }) {
|
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
||||||
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
|
title,
|
||||||
title,
|
label,
|
||||||
label,
|
type: 'input',
|
||||||
type: 'input',
|
...promptOptions()
|
||||||
...promptOptions()
|
}));
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
connection: null as Connection | null,
|
updateNext: false,
|
||||||
ipc: null as RendererContext<never>['ipc'] | null,
|
ignoreChange: false,
|
||||||
|
permission: 'playlist',
|
||||||
api: null as (HTMLElement & AppAPI) | null,
|
|
||||||
queue: null as Queue | null,
|
|
||||||
playerApi: null as YoutubePlayer | null,
|
|
||||||
showPrompt: (async () => null) as ((title: string, label: string) => Promise<string | null>),
|
|
||||||
|
|
||||||
elements: {} as {
|
|
||||||
setting: HTMLElement;
|
|
||||||
icon: SVGElement;
|
|
||||||
spinner: HTMLElement;
|
|
||||||
},
|
|
||||||
popups: {} as {
|
popups: {} as {
|
||||||
host: ReturnType<typeof createHostPopup>;
|
host: ReturnType<typeof createHostPopup>;
|
||||||
guest: ReturnType<typeof createGuestPopup>;
|
guest: ReturnType<typeof createGuestPopup>;
|
||||||
setting: ReturnType<typeof createSettingPopup>;
|
setting: ReturnType<typeof createSettingPopup>;
|
||||||
},
|
},
|
||||||
stateInterval: null as number | null,
|
elements: {} as {
|
||||||
updateNext: false,
|
setting: HTMLElement;
|
||||||
ignoreChange: false,
|
icon: SVGElement;
|
||||||
rollbackInjector: null as (() => void) | null,
|
spinner: HTMLElement;
|
||||||
|
},
|
||||||
me: null as Omit<Profile, 'id'> | null,
|
profiles: {},
|
||||||
profiles: {} as Record<string, Profile>,
|
showPrompt: () => Promise.resolve(''),
|
||||||
permission: 'playlist' as Permission,
|
api: null,
|
||||||
|
|
||||||
/* events */
|
/* events */
|
||||||
videoChangeListener(event: CustomEvent<VideoDataChanged>) {
|
videoChangeListener(event: CustomEvent<VideoDataChanged>) {
|
||||||
if (event.detail.name === 'dataloaded' || this.updateNext) {
|
if (event.detail.name === 'dataloaded' || this.updateNext) {
|
||||||
if (this.connection?.mode === 'host') {
|
if (this.connection?.mode === 'host') {
|
||||||
const videoList: VideoData[] = this.queue?.flatItems.map((it: any) => ({
|
const videoList: VideoData[] = this.queue?.flatItems.map((it) => ({
|
||||||
videoId: it.videoId,
|
videoId: it!.videoId,
|
||||||
ownerId: this.connection!.id
|
ownerId: this.connection!.id
|
||||||
} satisfies VideoData)) ?? [];
|
} satisfies VideoData)) ?? [];
|
||||||
|
|
||||||
@ -123,8 +149,8 @@ export default createPlugin({
|
|||||||
if (!wait) return false;
|
if (!wait) return false;
|
||||||
|
|
||||||
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
if (!this.me) this.me = getDefaultProfile(this.connection.id);
|
||||||
const rawItems = this.queue?.flatItems?.map((it: any) => ({
|
const rawItems = this.queue?.flatItems?.map((it) => ({
|
||||||
videoId: it.videoId,
|
videoId: it!.videoId,
|
||||||
ownerId: this.connection!.id
|
ownerId: this.connection!.id
|
||||||
} satisfies VideoData)) ?? [];
|
} satisfies VideoData)) ?? [];
|
||||||
this.queue?.setOwner({
|
this.queue?.setOwner({
|
||||||
@ -170,7 +196,7 @@ export default createPlugin({
|
|||||||
case 'REMOVE_SONG': {
|
case 'REMOVE_SONG': {
|
||||||
if (conn && this.permission === 'host-only') return;
|
if (conn && this.permission === 'host-only') return;
|
||||||
|
|
||||||
await this.queue?.removeVideo(event.payload.index);
|
this.queue?.removeVideo(event.payload.index);
|
||||||
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
await this.connection?.broadcast('REMOVE_SONG', event.payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -295,11 +321,11 @@ export default createPlugin({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'REMOVE_SONG': {
|
case 'REMOVE_SONG': {
|
||||||
await this.queue?.removeVideo(event.payload.index);
|
this.queue?.removeVideo(event.payload.index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'MOVE_SONG': {
|
case 'MOVE_SONG': {
|
||||||
await this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'IDENTIFY': {
|
case 'IDENTIFY': {
|
||||||
@ -461,7 +487,7 @@ export default createPlugin({
|
|||||||
this.queue?.removeQueueOwner();
|
this.queue?.removeQueueOwner();
|
||||||
if (this.rollbackInjector) {
|
if (this.rollbackInjector) {
|
||||||
this.rollbackInjector();
|
this.rollbackInjector();
|
||||||
this.rollbackInjector = null;
|
this.rollbackInjector = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.profiles = {};
|
this.profiles = {};
|
||||||
@ -530,7 +556,7 @@ export default createPlugin({
|
|||||||
|
|
||||||
start({ ipc }) {
|
start({ ipc }) {
|
||||||
this.ipc = ipc;
|
this.ipc = ipc;
|
||||||
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label);
|
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
|
||||||
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
|
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
|
||||||
|
|
||||||
/* setup */
|
/* setup */
|
||||||
@ -571,10 +597,15 @@ export default createPlugin({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (id === 'music-together-copy-id') {
|
if (id === 'music-together-copy-id') {
|
||||||
navigator.clipboard.writeText(this.connection?.id ?? '');
|
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||||
|
.then(() => {
|
||||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||||
hostPopup.dismiss();
|
hostPopup.dismiss();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||||
|
hostPopup.dismiss();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id === 'music-together-permission') {
|
if (id === 'music-together-permission') {
|
||||||
@ -614,9 +645,14 @@ export default createPlugin({
|
|||||||
this.hideSpinner();
|
this.hideSpinner();
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
navigator.clipboard.writeText(this.connection?.id ?? '');
|
navigator.clipboard.writeText(this.connection?.id ?? '')
|
||||||
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
.then(() => {
|
||||||
hostPopup.showAtAnchor(setting);
|
this.api?.openToast(t('plugins.music-together.toast.id-copied'));
|
||||||
|
hostPopup.showAtAnchor(setting);
|
||||||
|
}).catch(() => {
|
||||||
|
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
|
||||||
|
hostPopup.showAtAnchor(setting);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.api?.openToast(t('plugins.music-together.toast.host-failed'));
|
this.api?.openToast(t('plugins.music-together.toast.host-failed'));
|
||||||
}
|
}
|
||||||
@ -642,7 +678,7 @@ export default createPlugin({
|
|||||||
guest: guestPopup,
|
guest: guestPopup,
|
||||||
setting: settingPopup
|
setting: settingPopup
|
||||||
};
|
};
|
||||||
setting.addEventListener('click', async () => {
|
setting.addEventListener('click', () => {
|
||||||
let popup = settingPopup;
|
let popup = settingPopup;
|
||||||
if (this.connection?.mode === 'host') popup = hostPopup;
|
if (this.connection?.mode === 'host') popup = hostPopup;
|
||||||
if (this.connection?.mode === 'guest') popup = guestPopup;
|
if (this.connection?.mode === 'guest') popup = guestPopup;
|
||||||
|
|||||||
@ -1,12 +1,51 @@
|
|||||||
import { getMusicQueueRenderer } from './song';
|
import { getMusicQueueRenderer } from './song';
|
||||||
import { mapQueueItem } from './utils';
|
import { mapQueueItem } from './utils';
|
||||||
|
|
||||||
import type { Profile, QueueAPI, VideoData } from '../types';
|
|
||||||
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
|
import type { Profile, QueueAPI, VideoData } from '../types';
|
||||||
|
import type { QueueItem } from '@/types/datahost-get-state';
|
||||||
|
|
||||||
const getHeaderPayload = (() => {
|
const getHeaderPayload = (() => {
|
||||||
let payload: unknown = null;
|
let payload: {
|
||||||
|
items?: QueueItem[] | undefined;
|
||||||
|
title: {
|
||||||
|
runs: {
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
subtitle: {
|
||||||
|
runs: {
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
buttons: {
|
||||||
|
chipCloudChipRenderer: {
|
||||||
|
style: {
|
||||||
|
styleType: string;
|
||||||
|
};
|
||||||
|
text: {
|
||||||
|
runs: {
|
||||||
|
text: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
navigationEndpoint: {
|
||||||
|
saveQueueToPlaylistCommand: unknown;
|
||||||
|
};
|
||||||
|
icon: {
|
||||||
|
iconType: string;
|
||||||
|
};
|
||||||
|
accessibilityData: {
|
||||||
|
accessibilityData: {
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
isSelected: boolean;
|
||||||
|
uniqueId: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
@ -58,7 +97,7 @@ const getHeaderPayload = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export type QueueOptions = {
|
export type QueueOptions = {
|
||||||
@ -70,11 +109,11 @@ export type QueueOptions = {
|
|||||||
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
export type QueueEventListener = (event: ConnectionEventUnion) => void;
|
||||||
|
|
||||||
export class Queue {
|
export class Queue {
|
||||||
private queue: (HTMLElement & QueueAPI) | null = null;
|
private queue: (HTMLElement & QueueAPI);
|
||||||
private originalDispatch: ((obj: {
|
private originalDispatch?: (obj: {
|
||||||
type: string;
|
type: string;
|
||||||
payload?: unknown;
|
payload?: { items?: QueueItem[] | undefined; };
|
||||||
}) => void) | null = null;
|
}) => void;
|
||||||
private internalDispatch = false;
|
private internalDispatch = false;
|
||||||
private ignoreFlag = false;
|
private ignoreFlag = false;
|
||||||
private listeners: QueueEventListener[] = [];
|
private listeners: QueueEventListener[] = [];
|
||||||
@ -83,7 +122,7 @@ export class Queue {
|
|||||||
|
|
||||||
constructor(options: QueueOptions) {
|
constructor(options: QueueOptions) {
|
||||||
this.getProfile = options.getProfile;
|
this.getProfile = options.getProfile;
|
||||||
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue');
|
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue')!;
|
||||||
this.owner = options.owner ?? null;
|
this.owner = options.owner ?? null;
|
||||||
this._videoList = options.videoList ?? [];
|
this._videoList = options.videoList ?? [];
|
||||||
}
|
}
|
||||||
@ -96,7 +135,7 @@ export class Queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get selectedIndex() {
|
get selectedIndex() {
|
||||||
return mapQueueItem((it) => it?.selected, this.queue?.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
return mapQueueItem((it) => it?.selected, this.queue.store.getState().queue.items).findIndex(Boolean) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get rawItems() {
|
get rawItems() {
|
||||||
@ -146,7 +185,7 @@ export class Queue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeVideo(index: number) {
|
removeVideo(index: number) {
|
||||||
this.internalDispatch = true;
|
this.internalDispatch = true;
|
||||||
this._videoList.splice(index, 1);
|
this._videoList.splice(index, 1);
|
||||||
this.queue?.dispatch({
|
this.queue?.dispatch({
|
||||||
@ -233,10 +272,10 @@ export class Queue {
|
|||||||
if (event.type === 'ADD_ITEMS') {
|
if (event.type === 'ADD_ITEMS') {
|
||||||
if (this.ignoreFlag) {
|
if (this.ignoreFlag) {
|
||||||
this.ignoreFlag = false;
|
this.ignoreFlag = false;
|
||||||
const videoList = mapQueueItem((it: any) => ({
|
const videoList = mapQueueItem((it) => ({
|
||||||
videoId: it.videoId,
|
videoId: it!.videoId,
|
||||||
ownerId: this.owner!.id
|
ownerId: this.owner!.id
|
||||||
} satisfies VideoData), (event.payload as any).items);
|
} satisfies VideoData), event.payload!.items!);
|
||||||
const index = this._videoList.length + videoList.length - 1;
|
const index = this._videoList.length + videoList.length - 1;
|
||||||
|
|
||||||
if (videoList.length > 0) {
|
if (videoList.length > 0) {
|
||||||
@ -255,15 +294,17 @@ export class Queue {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if ((event.payload as any).items.length === 1) {
|
} else if ((event.payload as {
|
||||||
|
items: unknown[];
|
||||||
|
}).items.length === 1) {
|
||||||
this.broadcast({ // add playlist
|
this.broadcast({ // add playlist
|
||||||
type: 'ADD_SONGS',
|
type: 'ADD_SONGS',
|
||||||
payload: {
|
payload: {
|
||||||
// index: (event.payload as any).index,
|
// index: (event.payload as any).index,
|
||||||
videoList: mapQueueItem((it: any) => ({
|
videoList: mapQueueItem((it) => ({
|
||||||
videoId: it.videoId,
|
videoId: it!.videoId,
|
||||||
ownerId: this.owner!.id
|
ownerId: this.owner!.id
|
||||||
} satisfies VideoData), (event.payload as any).items)
|
} satisfies VideoData), event.payload!.items!)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -275,8 +316,12 @@ export class Queue {
|
|||||||
this.broadcast({
|
this.broadcast({
|
||||||
type: 'MOVE_SONG',
|
type: 'MOVE_SONG',
|
||||||
payload: {
|
payload: {
|
||||||
fromIndex: (event.payload as any).fromIndex,
|
fromIndex: (event.payload as {
|
||||||
toIndex: (event.payload as any).toIndex
|
fromIndex: number;
|
||||||
|
}).fromIndex,
|
||||||
|
toIndex: (event.payload as {
|
||||||
|
toIndex: number;
|
||||||
|
}).toIndex
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -306,7 +351,7 @@ export class Queue {
|
|||||||
event.payload = undefined;
|
event.payload = undefined;
|
||||||
}
|
}
|
||||||
if (event.type === 'SET_PLAYER_UI_STATE') {
|
if (event.type === 'SET_PLAYER_UI_STATE') {
|
||||||
if (event.payload === 'INACTIVE' && this.videoList.length > 0) {
|
if (event.payload as string === 'INACTIVE' && this.videoList.length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,12 +366,12 @@ export class Queue {
|
|||||||
dispatch: this.originalDispatch
|
dispatch: this.originalDispatch
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.originalDispatch!.call(fakeContext, event);
|
this.originalDispatch?.call(fakeContext, event);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sync */
|
/* sync */
|
||||||
async initQueue() {
|
initQueue() {
|
||||||
if (!this.queue) return;
|
if (!this.queue) return;
|
||||||
|
|
||||||
this.internalDispatch = true;
|
this.internalDispatch = true;
|
||||||
@ -369,13 +414,13 @@ export class Queue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncQueueOwner() {
|
syncQueueOwner() {
|
||||||
const allQueue = document.querySelectorAll('#queue');
|
const allQueue = document.querySelectorAll('#queue');
|
||||||
|
|
||||||
allQueue.forEach((queue) => {
|
allQueue.forEach((queue) => {
|
||||||
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
|
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
|
||||||
|
|
||||||
list.forEach((item, index) => {
|
list.forEach((item, index: number | undefined) => {
|
||||||
if (typeof index !== 'number') return;
|
if (typeof index !== 'number') return;
|
||||||
|
|
||||||
const id = this._videoList[index]?.ownerId;
|
const id = this._videoList[index]?.ownerId;
|
||||||
|
|||||||
@ -12,14 +12,14 @@ export function SHA1Hash(): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processBlock(block: number[]): void {
|
function processBlock(block: number[]): void {
|
||||||
let words: number[] = [];
|
const words: number[] = [];
|
||||||
for (let i = 0; i < 64; i += 4) {
|
for (let i = 0; i < 64; i += 4) {
|
||||||
words[i / 4] = block[i] << 24 | block[i + 1] << 16 | block[i + 2] << 8 | block[i + 3];
|
words[i / 4] = (block[i] << 24) | (block[i + 1] << 16) | (block[i + 2] << 8) | block[i + 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 16; i < 80; i++) {
|
for (let i = 16; i < 80; i++) {
|
||||||
let temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
|
const temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
|
||||||
words[i] = (temp << 1 | temp >>> 31) & 4294967295;
|
words[i] = ((temp << 1) | (temp >>> 31)) & 4294967295;
|
||||||
}
|
}
|
||||||
|
|
||||||
let a = hash[0],
|
let a = hash[0],
|
||||||
@ -30,22 +30,22 @@ export function SHA1Hash(): {
|
|||||||
for (let i = 0; i < 80; i++) {
|
for (let i = 0; i < 80; i++) {
|
||||||
let f, k;
|
let f, k;
|
||||||
if (i < 20) {
|
if (i < 20) {
|
||||||
f = d ^ b & (c ^ d);
|
f = d ^ (b & (c ^ d));
|
||||||
k = 1518500249;
|
k = 1518500249;
|
||||||
} else if (i < 40) {
|
} else if (i < 40) {
|
||||||
f = b ^ c ^ d;
|
f = b ^ c ^ d;
|
||||||
k = 1859775393;
|
k = 1859775393;
|
||||||
} else if (i < 60) {
|
} else if (i < 60) {
|
||||||
f = b & c | d & (b | c);
|
f = (b & c) | (d & (b | c));
|
||||||
k = 2400959708;
|
k = 2400959708;
|
||||||
} else {
|
} else {
|
||||||
f = b ^ c ^ d;
|
f = b ^ c ^ d;
|
||||||
k = 3395469782;
|
k = 3395469782;
|
||||||
}
|
}
|
||||||
let temp = ((a << 5 | a >>> 27) & 4294967295) + f + e + k + words[i] & 4294967295;
|
const temp = (((a << 5) | (a >>> 27)) & 4294967295) + f + e + k + words[i] & 4294967295;
|
||||||
e = d;
|
e = d;
|
||||||
d = c;
|
d = c;
|
||||||
c = (b << 30 | b >>> 2) & 4294967295;
|
c = ((b << 30) | (b >>> 2)) & 4294967295;
|
||||||
b = a;
|
b = a;
|
||||||
a = temp;
|
a = temp;
|
||||||
}
|
}
|
||||||
@ -58,8 +58,9 @@ export function SHA1Hash(): {
|
|||||||
|
|
||||||
function update(message: string | number[], length?: number): void {
|
function update(message: string | number[], length?: number): void {
|
||||||
if ('string' === typeof message) {
|
if ('string' === typeof message) {
|
||||||
|
// HACK: to decode UTF-8
|
||||||
message = unescape(encodeURIComponent(message));
|
message = unescape(encodeURIComponent(message));
|
||||||
let bytes: number[] = [];
|
const bytes: number[] = [];
|
||||||
for (let i = 0, len = message.length; i < len; ++i)
|
for (let i = 0, len = message.length; i < len; ++i)
|
||||||
bytes.push(message.charCodeAt(i));
|
bytes.push(message.charCodeAt(i));
|
||||||
message = bytes;
|
message = bytes;
|
||||||
@ -67,48 +68,59 @@ export function SHA1Hash(): {
|
|||||||
length || (length = message.length);
|
length || (length = message.length);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
if (0 == currentLength)
|
if (0 == currentLength)
|
||||||
for (; i + 64 < length;)
|
for (; i + 64 < length;) {
|
||||||
processBlock(message.slice(i, i + 64)),
|
processBlock(message.slice(i, i + 64));
|
||||||
i += 64,
|
i += 64;
|
||||||
|
totalLength += 64;
|
||||||
|
}
|
||||||
|
for (; i < length;) {
|
||||||
|
if (buffer[currentLength++] = message[i++], totalLength++, 64 == currentLength)
|
||||||
|
for (currentLength = 0, processBlock(buffer); i + 64 < length;) {
|
||||||
|
processBlock(message.slice(i, i + 64));
|
||||||
|
i += 64;
|
||||||
totalLength += 64;
|
totalLength += 64;
|
||||||
for (; i < length;)
|
}
|
||||||
if (buffer[currentLength++] = message[i++],
|
}
|
||||||
totalLength++,
|
|
||||||
64 == currentLength)
|
|
||||||
for (currentLength = 0,
|
|
||||||
processBlock(buffer); i + 64 < length;)
|
|
||||||
processBlock(message.slice(i, i + 64)),
|
|
||||||
i += 64,
|
|
||||||
totalLength += 64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalize(): number[] {
|
function finalize(): number[] {
|
||||||
let result: number[] = []
|
const result: number[] = [];
|
||||||
, bits = 8 * totalLength;
|
let bits = 8 * totalLength;
|
||||||
if (currentLength < 56)
|
if (currentLength < 56) {
|
||||||
update(padding, 56 - currentLength);
|
update(padding, 56 - currentLength);
|
||||||
else
|
} else {
|
||||||
update(padding, 64 - (currentLength - 56));
|
update(padding, 64 - (currentLength - 56));
|
||||||
for (let i = 63; i >= 56; i--)
|
}
|
||||||
buffer[i] = bits & 255,
|
for (let i = 63; i >= 56; i--) {
|
||||||
bits >>>= 8;
|
buffer[i] = bits & 255;
|
||||||
|
bits >>>= 8;
|
||||||
|
}
|
||||||
processBlock(buffer);
|
processBlock(buffer);
|
||||||
for (let i = 0; i < 5; i++)
|
for (let i = 0; i < 5; i++) {
|
||||||
for (let j = 24; j >= 0; j -= 8)
|
for (let j = 24; j >= 0; j -= 8) {
|
||||||
result.push(hash[i] >> j & 255);
|
result.push((hash[i] >> j) & 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer: number[] = [], padding: number[] = [128], totalLength: number, currentLength: number;
|
const buffer: number[] = [];
|
||||||
for (let i = 1; i < 64; ++i)
|
const padding: number[] = [128];
|
||||||
|
let totalLength: number;
|
||||||
|
let currentLength: number;
|
||||||
|
|
||||||
|
for (let i = 1; i < 64; ++i) {
|
||||||
padding[i] = 0;
|
padding[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
return {
|
return {
|
||||||
reset: initialize,
|
reset: initialize,
|
||||||
update: update,
|
update: update,
|
||||||
digest: finalize,
|
digest: finalize,
|
||||||
digestString: function(): string {
|
digestString: function(): string {
|
||||||
let hash = finalize(), hex = '';
|
const hash = finalize();
|
||||||
|
let hex = '';
|
||||||
for (let i = 0; i < hash.length; i++)
|
for (let i = 0; i < hash.length; i++)
|
||||||
hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16);
|
hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16);
|
||||||
return hex;
|
return hex;
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
export const mapQueueItem = <T>(map: (item: any | null) => T, array: any[]): T[] => array
|
import {
|
||||||
|
ItemPlaylistPanelVideoRenderer,
|
||||||
|
PlaylistPanelVideoWrapperRenderer,
|
||||||
|
QueueItem
|
||||||
|
} from '@/types/datahost-get-state';
|
||||||
|
|
||||||
|
export const mapQueueItem = <T>(map: (item?: ItemPlaylistPanelVideoRenderer) => T, array: QueueItem[]): T[] => array
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
if ('playlistPanelVideoWrapperRenderer' in item) {
|
if ('playlistPanelVideoWrapperRenderer' in item) {
|
||||||
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer.primaryRenderer);
|
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer!.primaryRenderer) as (keyof PlaylistPanelVideoWrapperRenderer['primaryRenderer'])[];
|
||||||
return item.playlistPanelVideoWrapperRenderer.primaryRenderer[keys[0]];
|
return item.playlistPanelVideoWrapperRenderer!.primaryRenderer[keys[0]];
|
||||||
}
|
}
|
||||||
if ('playlistPanelVideoRenderer' in item) {
|
if ('playlistPanelVideoRenderer' in item) {
|
||||||
return item.playlistPanelVideoRenderer;
|
return item.playlistPanelVideoRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('Music Together: Unknown item', item);
|
console.error('Music Together: Unknown item', item);
|
||||||
return null;
|
return undefined;
|
||||||
})
|
})
|
||||||
.map(map);
|
.map(map);
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
<div class="music-together-status">
|
<div class="music-together-status">
|
||||||
<div class="music-together-status-container">
|
<div class="music-together-status-container">
|
||||||
<img class="music-together-profile big">
|
<img class="music-together-profile big" alt="Profile Image">
|
||||||
<div class="music-together-status-item">
|
<div class="music-together-status-item">
|
||||||
<ytmd-trans key="plugins.music-together.name"></ytmd-trans>
|
<ytmd-trans key="plugins.music-together.name"></ytmd-trans>
|
||||||
<span id="music-together-status-label">
|
<span id="music-together-status-label">
|
||||||
<ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans>
|
<ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans>
|
||||||
</span>
|
</span>
|
||||||
<span id="music-together-permission-label">
|
<marquee id="music-together-permission-label">
|
||||||
<ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans>
|
<ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans>
|
||||||
</span>
|
</marquee>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="music-together-divider horizontal" style="margin: 16px 0;"></div>
|
<div class="music-together-divider horizontal" style="margin: 16px 0;"></div>
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
import { YoutubePlayer } from '@/types/youtube-player';
|
import { YoutubePlayer } from '@/types/youtube-player';
|
||||||
type StoreState = any;
|
import { GetState, QueueItem } from '@/types/datahost-get-state';
|
||||||
|
|
||||||
|
type StoreState = GetState;
|
||||||
type Store = {
|
type Store = {
|
||||||
dispatch: (obj: {
|
dispatch: (obj: {
|
||||||
type: string;
|
type: string;
|
||||||
payload?: unknown;
|
payload?: {
|
||||||
|
items?: QueueItem[];
|
||||||
|
};
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
getState: () => StoreState;
|
getState: () => StoreState;
|
||||||
replaceReducer: (param1: unknown) => unknown;
|
replaceReducer: (param1: unknown) => unknown;
|
||||||
subscribe: (callback: () => void) => unknown;
|
subscribe: (callback: () => void) => unknown;
|
||||||
};
|
}
|
||||||
export type QueueAPI = {
|
export type QueueAPI = {
|
||||||
dispatch(obj: {
|
dispatch(obj: {
|
||||||
type: string;
|
type: string;
|
||||||
@ -28,8 +32,6 @@ export type AppAPI = {
|
|||||||
// TODO: Add more
|
// TODO: Add more
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type Profile = {
|
export type Profile = {
|
||||||
id: string;
|
id: string;
|
||||||
handleId: string;
|
handleId: string;
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
import { ElementFromHtml } from '@/plugins/utils/renderer';
|
||||||
import statusHTML from '../templates/status.html?raw';
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
|
import statusHTML from '../templates/status.html?raw';
|
||||||
|
|
||||||
import type { Permission, Profile } from '../types';
|
import type { Permission, Profile } from '../types';
|
||||||
|
|
||||||
export const createStatus = () => {
|
export const createStatus = () => {
|
||||||
@ -9,7 +11,7 @@ export const createStatus = () => {
|
|||||||
|
|
||||||
const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!;
|
const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!;
|
||||||
const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!;
|
const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!;
|
||||||
const permissionLabel = element.querySelector<HTMLSpanElement>('#music-together-permission-label')!;
|
const permissionLabel = element.querySelector<HTMLMarqueeElement>('#music-together-permission-label')!;
|
||||||
|
|
||||||
profile.src = icon?.src ?? '';
|
profile.src = icon?.src ?? '';
|
||||||
|
|
||||||
|
|||||||
@ -127,7 +127,7 @@ export default (api: YoutubePlayer) => {
|
|||||||
|
|
||||||
const waitingEvent = new Set<string>();
|
const waitingEvent = new Set<string>();
|
||||||
// Name = "dataloaded" and abit later "dataupdated"
|
// Name = "dataloaded" and abit later "dataupdated"
|
||||||
api.addEventListener('videodatachange', (name: string, videoData) => {
|
api.addEventListener('videodatachange', (name, videoData) => {
|
||||||
videoEventDispatcher(name, videoData);
|
videoEventDispatcher(name, videoData);
|
||||||
|
|
||||||
if (name === 'dataupdated' && waitingEvent.has(videoData.videoId)) {
|
if (name === 'dataupdated' && waitingEvent.has(videoData.videoId)) {
|
||||||
|
|||||||
@ -256,6 +256,7 @@ export type VideoDataChangeValue = Record<string, unknown> & {
|
|||||||
export interface PlayerAPIEvents {
|
export interface PlayerAPIEvents {
|
||||||
videodatachange: {
|
videodatachange: {
|
||||||
value: VideoDataChangeValue;
|
value: VideoDataChangeValue;
|
||||||
} & ({ name: 'dataloaded' } | { name: 'dataupdated ' });
|
name: 'dataloaded' | 'dataupdated';
|
||||||
|
};
|
||||||
onStateChange: number;
|
onStateChange: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -357,8 +357,8 @@ export interface YoutubePlayer {
|
|||||||
type: K,
|
type: K,
|
||||||
listener: (
|
listener: (
|
||||||
this: Document,
|
this: Document,
|
||||||
name: PlayerAPIEvents[K]['name'],
|
name: K extends 'videodatachange' ? PlayerAPIEvents[K]['name'] : never,
|
||||||
data: PlayerAPIEvents[K]['value'],
|
data: K extends 'videodatachange' ? PlayerAPIEvents[K]['value'] : never,
|
||||||
) => void,
|
) => void,
|
||||||
options?: boolean | AddEventListenerOptions | undefined,
|
options?: boolean | AddEventListenerOptions | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
@ -366,8 +366,8 @@ export interface YoutubePlayer {
|
|||||||
type: K,
|
type: K,
|
||||||
listener: (
|
listener: (
|
||||||
this: Document,
|
this: Document,
|
||||||
name: PlayerAPIEvents[K]['name'],
|
name: K extends 'videodatachange' ? PlayerAPIEvents[K]['name'] : never,
|
||||||
data: PlayerAPIEvents[K]['value'],
|
data: K extends 'videodatachange' ? PlayerAPIEvents[K]['value'] : never,
|
||||||
) => void,
|
) => void,
|
||||||
options?: boolean | EventListenerOptions | undefined,
|
options?: boolean | EventListenerOptions | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user