Started inital search patch with ytjs
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.13.19",
|
||||
"version": "1.14.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Materialious",
|
||||
"version": "1.13.19",
|
||||
"version": "1.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getVideoTYjs } from '$lib/api/youtubejs';
|
||||
import { getVideoYTjs } from '$lib/api/youtubejs/video';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
@@ -13,15 +13,12 @@ import {
|
||||
synciousInstanceStore
|
||||
} from '../store';
|
||||
import type {
|
||||
Channel,
|
||||
ChannelContentPlaylists,
|
||||
ChannelContentVideos,
|
||||
ChannelPage,
|
||||
Comments,
|
||||
DeArrow,
|
||||
Feed,
|
||||
HashTag,
|
||||
Playlist,
|
||||
PlaylistPage,
|
||||
ResolvedUrl,
|
||||
ReturnYTDislikes,
|
||||
@@ -29,8 +26,12 @@ import type {
|
||||
Subscription,
|
||||
ApiExntendedProgressModel,
|
||||
Video,
|
||||
VideoPlay
|
||||
VideoPlay,
|
||||
SearchOptions,
|
||||
SearchResults
|
||||
} from './model';
|
||||
import { searchSetDefaults } from './misc';
|
||||
import { getSearchYTjs } from './youtubejs/search';
|
||||
|
||||
export function buildPath(path: string): URL {
|
||||
return new URL(`${get(instanceStore)}/api/v1/${path}`);
|
||||
@@ -86,13 +87,13 @@ export async function getVideo(
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<VideoPlay> {
|
||||
if (get(playerYouTubeJsAlways) && Capacitor.isNativePlatform()) {
|
||||
return await getVideoTYjs(videoId);
|
||||
return await getVideoYTjs(videoId);
|
||||
}
|
||||
|
||||
const resp = await fetch(setRegion(buildPath(`videos/${videoId}?local=${local}`)), fetchOptions);
|
||||
|
||||
if (!resp.ok && get(playerYouTubeJsFallback) && Capacitor.isNativePlatform()) {
|
||||
return await getVideoTYjs(videoId);
|
||||
return await getVideoYTjs(videoId);
|
||||
} else {
|
||||
await fetchErrorHandle(resp);
|
||||
}
|
||||
@@ -193,31 +194,14 @@ export async function getHashtag(tag: string, page: number = 0): Promise<{ resul
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
sort_by?: 'relevance' | 'rating' | 'upload_date' | 'view_count';
|
||||
type?: 'video' | 'playlist' | 'channel' | 'all';
|
||||
duration?: 'short' | 'medium' | 'long';
|
||||
date?: 'hour' | 'today' | 'week' | 'month' | 'year';
|
||||
features?: string;
|
||||
page?: string;
|
||||
}
|
||||
|
||||
export async function getSearch(
|
||||
search: string,
|
||||
options: SearchOptions,
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<(Channel | Video | Playlist | HashTag)[]> {
|
||||
if (typeof options.sort_by === 'undefined') {
|
||||
options.sort_by = 'relevance';
|
||||
}
|
||||
): Promise<SearchResults> {
|
||||
searchSetDefaults(options);
|
||||
|
||||
if (typeof options.type === 'undefined') {
|
||||
options.type = 'all';
|
||||
}
|
||||
|
||||
if (typeof options.page === 'undefined') {
|
||||
options.page = '1';
|
||||
}
|
||||
await getSearchYTjs(search, options);
|
||||
|
||||
const path = buildPath('search');
|
||||
path.search = new URLSearchParams({ ...options, q: search }).toString();
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { SearchOptions } from './model';
|
||||
|
||||
export function searchSetDefaults(options: SearchOptions) {
|
||||
if (typeof options.sort_by === 'undefined') {
|
||||
options.sort_by = 'relevance';
|
||||
}
|
||||
|
||||
if (typeof options.type === 'undefined') {
|
||||
options.type = 'all';
|
||||
}
|
||||
|
||||
if (typeof options.page === 'undefined') {
|
||||
options.page = '1';
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
import type { ApiResponse, Innertube, YT } from 'youtubei.js';
|
||||
|
||||
export interface SearchOptions {
|
||||
sort_by?: 'relevance' | 'rating' | 'upload_date' | 'view_count';
|
||||
type?: 'video' | 'playlist' | 'channel' | 'all';
|
||||
duration?: 'short' | 'medium' | 'long';
|
||||
date?: 'hour' | 'today' | 'week' | 'month' | 'year';
|
||||
features?: string;
|
||||
page?: string;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
url: string;
|
||||
width: number;
|
||||
@@ -7,7 +16,6 @@ export interface Image {
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
quality: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -295,3 +303,5 @@ export interface ApiExntendedProgressModel {
|
||||
export interface SynciousSaveProgressModel {
|
||||
time: number;
|
||||
}
|
||||
|
||||
export type SearchResults = (Channel | Video | Playlist | HashTag)[];
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { interfaceRegionStore } from '$lib/store';
|
||||
import { USER_AGENT } from 'bgutils-js';
|
||||
import { get } from 'svelte/store';
|
||||
import Innertube, { UniversalCache } from 'youtubei.js';
|
||||
|
||||
let innertube: Innertube | undefined;
|
||||
|
||||
export async function getInnertube(): Promise<Innertube> {
|
||||
if (innertube) return innertube;
|
||||
|
||||
innertube = await Innertube.create({
|
||||
fetch: fetch,
|
||||
cache: new UniversalCache(true),
|
||||
location: get(interfaceRegionStore),
|
||||
user_agent: USER_AGENT
|
||||
});
|
||||
|
||||
return innertube;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { cleanNumber, extractNumber } from '$lib/numbers';
|
||||
import { convertToSeconds } from '$lib/time';
|
||||
import { getInnertube } from '.';
|
||||
import { searchSetDefaults } from '../misc';
|
||||
import type { Channel, SearchOptions, SearchResults, Thumbnail, Video } from '../model';
|
||||
import { YTNodes, type Types } from 'youtubei.js';
|
||||
|
||||
export async function getSearchYTjs(
|
||||
search: string,
|
||||
options: SearchOptions
|
||||
): Promise<SearchResults> {
|
||||
const innertube = await getInnertube();
|
||||
|
||||
searchSetDefaults(options);
|
||||
|
||||
const innerResults = await innertube.search(search, {
|
||||
sort_by: options.sort_by,
|
||||
duration: options.duration,
|
||||
features: [options.features] as Types.Feature[],
|
||||
upload_date: options.date
|
||||
});
|
||||
|
||||
const searchResults: SearchResults = [];
|
||||
|
||||
innerResults.results.forEach((result) => {
|
||||
if (result.is(YTNodes.Video)) {
|
||||
const views = extractNumber(result.view_count?.toString() || '');
|
||||
const patchedResult: Video = {
|
||||
type: 'video',
|
||||
title: result.title.toString(),
|
||||
videoId: result.video_id,
|
||||
viewCountText: cleanNumber(views),
|
||||
viewCount: views,
|
||||
videoThumbnails: result.thumbnails as Thumbnail[],
|
||||
published: 0,
|
||||
publishedText: result.published?.toString() || '',
|
||||
description: '',
|
||||
descriptionHtml: '',
|
||||
authorUrl: `/channel/${result.author.id}`,
|
||||
authorId: result.author.id,
|
||||
authorVerified: false,
|
||||
liveNow: false,
|
||||
isUpcoming: false,
|
||||
premium: false,
|
||||
author: result.author.name,
|
||||
lengthSeconds: result.length_text?.text ? convertToSeconds(result.length_text.text) : 0
|
||||
};
|
||||
searchResults.push(patchedResult);
|
||||
} else if (result.is(YTNodes.Channel)) {
|
||||
const patchedResult: Channel = {
|
||||
type: 'channel',
|
||||
authorId: result.id,
|
||||
author: result.author.name,
|
||||
authorUrl: `/channel/${result.id}`,
|
||||
authorVerified: result.author.is_verified === true,
|
||||
subCount: result.video_count.text ? extractNumber(result.video_count.text) : 0,
|
||||
totalViews: 0,
|
||||
autoGenerated: false,
|
||||
description: result.description_snippet.text ?? '',
|
||||
descriptionHml: result.description_snippet.toHTML() ?? '',
|
||||
authorThumbnails: result.author.thumbnails as Thumbnail[]
|
||||
};
|
||||
searchResults.push(patchedResult);
|
||||
}
|
||||
});
|
||||
|
||||
return searchResults;
|
||||
}
|
||||
+21
-22
@@ -8,13 +8,14 @@ import type {
|
||||
VideoBase,
|
||||
VideoPlay
|
||||
} from '$lib/api/model';
|
||||
import { interfaceRegionStore, poTokenCacheStore } from '$lib/store';
|
||||
import { poTokenCacheStore } from '$lib/store';
|
||||
import { convertToSeconds } from '$lib/time';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { USER_AGENT } from 'bgutils-js';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Types } from 'youtubei.js';
|
||||
import { Innertube, UniversalCache, Utils, YT, YTNodes, Platform } from 'youtubei.js';
|
||||
import { Utils, YT, YTNodes, Platform } from 'youtubei.js';
|
||||
import { getInnertube } from '.';
|
||||
import { cleanNumber, extractNumber } from '$lib/numbers';
|
||||
|
||||
Platform.shim.eval = async (
|
||||
data: Types.BuildScriptResult,
|
||||
@@ -35,17 +36,12 @@ Platform.shim.eval = async (
|
||||
return new Function(code)();
|
||||
};
|
||||
|
||||
export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
export async function getVideoYTjs(videoId: string): Promise<VideoPlay> {
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
throw new Error('Platform not supported');
|
||||
}
|
||||
|
||||
const youtube = await Innertube.create({
|
||||
fetch: fetch,
|
||||
cache: new UniversalCache(false),
|
||||
location: get(interfaceRegionStore),
|
||||
user_agent: USER_AGENT
|
||||
});
|
||||
const innertube = await getInnertube();
|
||||
|
||||
const requestKey = 'O43z0dpjhgX20SCx4KAo';
|
||||
|
||||
@@ -57,7 +53,7 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
const clientPlaybackNonce = Utils.generateRandomString(16);
|
||||
|
||||
const watchEndpoint = new YTNodes.NavigationEndpoint({ watchEndpoint: { videoId } });
|
||||
const rawPlayerResponse = await watchEndpoint.call(youtube.actions, {
|
||||
const rawPlayerResponse = await watchEndpoint.call(innertube.actions, {
|
||||
contentCheckOk: true,
|
||||
racyCheckOk: true,
|
||||
playbackContext: {
|
||||
@@ -65,16 +61,16 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
pyv: true
|
||||
},
|
||||
contentPlaybackContext: {
|
||||
signatureTimestamp: youtube.session.player?.signature_timestamp
|
||||
signatureTimestamp: innertube.session.player?.signature_timestamp
|
||||
}
|
||||
}
|
||||
});
|
||||
const rawNextResponse = await watchEndpoint.call(youtube.actions, {
|
||||
const rawNextResponse = await watchEndpoint.call(innertube.actions, {
|
||||
override_endpoint: '/next'
|
||||
});
|
||||
const video = new YT.VideoInfo(
|
||||
[rawPlayerResponse, rawNextResponse],
|
||||
youtube.actions,
|
||||
innertube.actions,
|
||||
clientPlaybackNonce
|
||||
);
|
||||
|
||||
@@ -82,7 +78,7 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
throw new Error('Unable to pull video info from youtube.js');
|
||||
}
|
||||
|
||||
const challengeResponse = await youtube.getAttestationChallenge('ENGAGEMENT_TYPE_UNBOUND');
|
||||
const challengeResponse = await innertube.getAttestationChallenge('ENGAGEMENT_TYPE_UNBOUND');
|
||||
poTokenCacheStore.set(await platformMinter(requestKey, videoId, challengeResponse));
|
||||
|
||||
let dashUri: string | undefined;
|
||||
@@ -139,7 +135,7 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
|
||||
let authorThumbnails: Image[];
|
||||
if (video.basic_info.channel_id) {
|
||||
const channel = await youtube.getChannel(video.basic_info.channel_id);
|
||||
const channel = await innertube.getChannel(video.basic_info.channel_id);
|
||||
authorThumbnails = channel.metadata.avatar as Image[];
|
||||
} else {
|
||||
authorThumbnails = [];
|
||||
@@ -151,7 +147,7 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
|
||||
url.searchParams.set('potc', '1');
|
||||
url.searchParams.set('pot', get(poTokenCacheStore) ?? '');
|
||||
url.searchParams.set('c', youtube.session.context.client.clientName);
|
||||
url.searchParams.set('c', innertube.session.context.client.clientName);
|
||||
url.searchParams.set('fmt', 'vtt');
|
||||
|
||||
// Remove &xosf=1 as it adds `position:63% line:0%` to the subtitle lines
|
||||
@@ -190,10 +186,13 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
videoThumbnails: (recommended?.content_image.image as Thumbnail[]) || [],
|
||||
videoId: recommended.content_id,
|
||||
title: recommended.metadata.title.toString(),
|
||||
viewCountText:
|
||||
(recommended.metadata.metadata.metadata_rows[1]?.metadata_parts?.[0]?.text ?? '')
|
||||
.toString()
|
||||
.replace('views', '') || '',
|
||||
viewCountText: cleanNumber(
|
||||
extractNumber(
|
||||
(
|
||||
recommended.metadata.metadata.metadata_rows[1]?.metadata_parts?.[0]?.text ?? ''
|
||||
).toString()
|
||||
)
|
||||
),
|
||||
author:
|
||||
(
|
||||
recommended.metadata.metadata.metadata_rows[0]?.metadata_parts?.[0]?.text ?? ''
|
||||
@@ -271,7 +270,7 @@ export async function getVideoTYjs(videoId: string): Promise<VideoPlay> {
|
||||
keywords: video.basic_info.keywords || [],
|
||||
allowedRegions: [],
|
||||
ytjs: {
|
||||
innertube: youtube,
|
||||
innertube: innertube,
|
||||
video: video,
|
||||
clientPlaybackNonce: clientPlaybackNonce,
|
||||
rawApiResponse: rawPlayerResponse
|
||||
@@ -44,7 +44,7 @@
|
||||
synciousStore
|
||||
} from '../store';
|
||||
import { setStatusBarColor } from '../theme';
|
||||
import { getVideoTYjs } from '$lib/api/youtubejs';
|
||||
import { getVideoYTjs } from '$lib/api/youtubejs/video';
|
||||
import {
|
||||
goToNextVideo,
|
||||
goToPreviousVideo,
|
||||
@@ -624,7 +624,7 @@
|
||||
|
||||
async function reloadVideo() {
|
||||
showVideoRetry = false;
|
||||
data.video = await getVideoTYjs(data.video.videoId);
|
||||
data.video = await getVideoYTjs(data.video.videoId);
|
||||
await loadVideo();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import humanNumber from 'human-number';
|
||||
|
||||
export function extractNumber(input: string): number {
|
||||
const digits = input.replace(/\D+/g, '');
|
||||
return digits === '' ? NaN : Number(digits);
|
||||
}
|
||||
|
||||
export function numberWithCommas(number: number) {
|
||||
if (typeof number === 'undefined') return;
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { getSearch, type SearchOptions } from '$lib/api';
|
||||
import { getSearch } from '$lib/api';
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import { searchCacheStore } from '$lib/store';
|
||||
import { _ } from '$lib/i18n';
|
||||
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
|
||||
import ItemsList from '$lib/components/ItemsList.svelte';
|
||||
import type { SearchOptions } from '$lib/api/model.js';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user