Implemented get comment replies
This commit is contained in:
@@ -3,7 +3,6 @@ import { Capacitor } from '@capacitor/core';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
authStore,
|
||||
backendInUseStore,
|
||||
deArrowInstanceStore,
|
||||
deArrowThumbnailInstanceStore,
|
||||
instanceStore,
|
||||
@@ -29,10 +28,15 @@ import type {
|
||||
Video,
|
||||
VideoPlay,
|
||||
SearchOptions,
|
||||
SearchResults
|
||||
SearchResults,
|
||||
CommentsOptions
|
||||
} from './model';
|
||||
import { searchSetDefaults } from './misc';
|
||||
import { commentsSetDefaults, searchSetDefaults } from './misc';
|
||||
import { getSearchYTjs } from './youtubejs/search';
|
||||
import { isYTBackend } from '$lib/misc';
|
||||
import { getSearchSuggestionsYTjs } from './youtubejs/searchSuggestions';
|
||||
import { getResolveUrlYTjs } from './youtubejs/misc';
|
||||
import { getCommentsYTjs } from './youtubejs/comments';
|
||||
|
||||
export function buildPath(path: string): URL {
|
||||
return new URL(`${get(instanceStore)}/api/v1/${path}`);
|
||||
@@ -73,11 +77,20 @@ export function buildAuthHeaders(): { headers: Record<string, string> } {
|
||||
}
|
||||
|
||||
export async function getPopular(fetchOptions?: RequestInit): Promise<Video[]> {
|
||||
if (isYTBackend()) {
|
||||
// Home page will be subscription page when using Youtube backend
|
||||
return [];
|
||||
}
|
||||
|
||||
const resp = await fetchErrorHandle(await fetch(buildPath('popular'), fetchOptions));
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
export async function getResolveUrl(url: string): Promise<ResolvedUrl> {
|
||||
if (isYTBackend()) {
|
||||
return await getResolveUrlYTjs(url);
|
||||
}
|
||||
|
||||
const resp = await fetchErrorHandle(await fetch(`${buildPath('resolveurl')}?url=${url}`));
|
||||
return await resp.json();
|
||||
}
|
||||
@@ -87,7 +100,7 @@ export async function getVideo(
|
||||
local: boolean = false,
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<VideoPlay> {
|
||||
if (get(playerYouTubeJsAlways) && Capacitor.isNativePlatform()) {
|
||||
if ((get(playerYouTubeJsAlways) && Capacitor.isNativePlatform()) || isYTBackend()) {
|
||||
return await getVideoYTjs(videoId);
|
||||
}
|
||||
|
||||
@@ -113,23 +126,17 @@ export async function getDislikes(
|
||||
|
||||
export async function getComments(
|
||||
videoId: string,
|
||||
parameters: {
|
||||
sort_by?: 'top' | 'new';
|
||||
source?: 'youtube' | 'reddit';
|
||||
continuation?: string;
|
||||
},
|
||||
options: CommentsOptions,
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<Comments> {
|
||||
if (typeof parameters.sort_by === 'undefined') {
|
||||
parameters.sort_by = 'top';
|
||||
}
|
||||
commentsSetDefaults(options);
|
||||
|
||||
if (typeof parameters.source === 'undefined') {
|
||||
parameters.source = 'youtube';
|
||||
if (isYTBackend()) {
|
||||
return await getCommentsYTjs(videoId, options);
|
||||
}
|
||||
|
||||
const path = buildPath(`comments/${videoId}`);
|
||||
path.search = new URLSearchParams(parameters).toString();
|
||||
path.search = new URLSearchParams(options).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path, fetchOptions));
|
||||
return await resp.json();
|
||||
}
|
||||
@@ -184,6 +191,10 @@ export async function getSearchSuggestions(
|
||||
search: string,
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<SearchSuggestion> {
|
||||
if (isYTBackend()) {
|
||||
return getSearchSuggestionsYTjs(search);
|
||||
}
|
||||
|
||||
const path = buildPath('search/suggestions');
|
||||
path.search = new URLSearchParams({ q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(path, fetchOptions));
|
||||
@@ -200,12 +211,12 @@ export async function getSearch(
|
||||
options: SearchOptions,
|
||||
fetchOptions?: RequestInit
|
||||
): Promise<SearchResults> {
|
||||
if (get(backendInUseStore) === 'yt') {
|
||||
searchSetDefaults(options);
|
||||
|
||||
if (isYTBackend()) {
|
||||
return await getSearchYTjs(search, options);
|
||||
}
|
||||
|
||||
searchSetDefaults(options);
|
||||
|
||||
const path = buildPath('search');
|
||||
path.search = new URLSearchParams({ ...options, q: search }).toString();
|
||||
const resp = await fetchErrorHandle(await fetch(setRegion(path), fetchOptions));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SearchOptions } from './model';
|
||||
import type { CommentsOptions, SearchOptions } from './model';
|
||||
|
||||
export function searchSetDefaults(options: SearchOptions) {
|
||||
if (typeof options.sort_by === 'undefined') {
|
||||
@@ -13,3 +13,9 @@ export function searchSetDefaults(options: SearchOptions) {
|
||||
options.page = '1';
|
||||
}
|
||||
}
|
||||
|
||||
export function commentsSetDefaults(options: CommentsOptions) {
|
||||
if (typeof options.sort_by === 'undefined') {
|
||||
options.sort_by = 'top';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ export interface Comment {
|
||||
replyCount: number;
|
||||
continuation: string;
|
||||
};
|
||||
getReplies?: () => Promise<Comments>;
|
||||
}
|
||||
|
||||
export interface Comments {
|
||||
@@ -307,3 +308,8 @@ export interface SynciousSaveProgressModel {
|
||||
export type SearchResults = (Channel | Video | Playlist | HashTag)[] & {
|
||||
getContinuation?: () => Promise<SearchResults>;
|
||||
};
|
||||
|
||||
export type CommentsOptions = {
|
||||
sort_by?: 'top' | 'new';
|
||||
continuation?: string;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { extractNumber } from '$lib/numbers';
|
||||
import { YTNodes } from 'youtubei.js';
|
||||
import { getInnertube } from '.';
|
||||
import type { Comment, Comments, CommentsOptions, Thumbnail } from '../model';
|
||||
|
||||
function invidiousSchema(result: YTNodes.CommentView): Comment | undefined {
|
||||
if (!result.author) return;
|
||||
|
||||
return {
|
||||
author: result.author.name ?? '',
|
||||
authorId: result.author.id ?? '',
|
||||
authorUrl: `/channel/${result.author.id}`,
|
||||
authorThumbnails: result.author.thumbnails as Thumbnail[],
|
||||
isEdited: false,
|
||||
isPinned: result.is_pinned === true,
|
||||
content: result.content?.text ?? '',
|
||||
contentHtml: result.content?.toHTML() ?? '',
|
||||
published: 0, // Placeholder, adjust accordingly
|
||||
publishedText: result.published_time ?? '',
|
||||
likeCount: extractNumber(result.like_count ?? '0'),
|
||||
authorIsChannelOwner: result.author_is_channel_owner === true,
|
||||
creatorHeart: {
|
||||
creatorName: result.heart_active_tooltip?.split('@')[1] ?? '',
|
||||
creatorThumbnail: result.creator_thumbnail_url ?? ''
|
||||
},
|
||||
replies: {
|
||||
replyCount: extractNumber(result.reply_count ?? '0'),
|
||||
continuation: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function getCommentsYTjs(
|
||||
videoId: string,
|
||||
options: CommentsOptions
|
||||
): Promise<Comments> {
|
||||
const innertube = await getInnertube();
|
||||
|
||||
const innerResults = await innertube.getComments(
|
||||
videoId,
|
||||
options.sort_by === 'top' ? 'TOP_COMMENTS' : 'NEWEST_FIRST'
|
||||
);
|
||||
|
||||
const comments: Comment[] = [];
|
||||
|
||||
innerResults.contents.forEach(async (result) => {
|
||||
if (result.comment) {
|
||||
const invidiousResult = invidiousSchema(result.comment);
|
||||
if (invidiousResult) {
|
||||
if (result.has_replies)
|
||||
invidiousResult.getReplies = async (): Promise<Comments> => {
|
||||
const replies = await result.getReplies();
|
||||
if (!replies.replies) return { videoId: '', comments: [], commentCount: 0 };
|
||||
|
||||
const comments: Comment[] = replies.replies
|
||||
.map((reply) => invidiousSchema(reply))
|
||||
.filter((comment): comment is Comment => !!comment);
|
||||
|
||||
return {
|
||||
videoId: '',
|
||||
comments: comments,
|
||||
commentCount: 0
|
||||
};
|
||||
};
|
||||
|
||||
comments.push(invidiousResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
videoId: videoId,
|
||||
comments: comments,
|
||||
commentCount: extractNumber(innerResults.header?.comments_count.text ?? '0')
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getInnertube } from '.';
|
||||
import type { ResolvedUrl } from '../model';
|
||||
|
||||
export async function getResolveUrlYTjs(url: string): Promise<ResolvedUrl> {
|
||||
const innertube = await getInnertube();
|
||||
|
||||
const resloved = await innertube.resolveURL(url);
|
||||
|
||||
return {
|
||||
pageType: resloved.metadata.page_type ?? '',
|
||||
params: '',
|
||||
ucid: resloved.metadata.url ? resloved.metadata.url?.split('/')[2] : ''
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
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, YT } from 'youtubei.js';
|
||||
|
||||
@@ -68,8 +67,6 @@ export async function getSearchYTjs(
|
||||
): Promise<SearchResults> {
|
||||
const innertube = await getInnertube();
|
||||
|
||||
searchSetDefaults(options);
|
||||
|
||||
const innerResults = await innertube.search(search, {
|
||||
sort_by: options.sort_by,
|
||||
duration: options.duration,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getInnertube } from '.';
|
||||
import type { SearchSuggestion } from '../model';
|
||||
|
||||
export async function getSearchSuggestionsYTjs(search: string): Promise<SearchSuggestion> {
|
||||
const innertube = await getInnertube();
|
||||
|
||||
const searchSuggestions = await innertube.getSearchSuggestions(search);
|
||||
|
||||
return {
|
||||
query: search,
|
||||
suggestions: searchSuggestions
|
||||
};
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
import { Network, type ConnectionStatus } from '@capacitor/network';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { addToast } from './Toast.svelte';
|
||||
import { isMobile, truncate } from '$lib/misc';
|
||||
import { isMobile, isYTBackend, truncate } from '$lib/misc';
|
||||
import {
|
||||
generateThumbnailWebVTT,
|
||||
drawTimelineThumbnail,
|
||||
@@ -485,7 +485,7 @@
|
||||
|
||||
sabrAdapter = await injectSabr(data.video, player);
|
||||
|
||||
if (data.video.fallbackPatch === 'youtubejs') {
|
||||
if (data.video.fallbackPatch === 'youtubejs' && !isYTBackend()) {
|
||||
addToast({ data: { text: $_('player.youtubeJsFallBack') } });
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,17 @@
|
||||
const replyText: string = comment.replies?.replyCount > 1 ? $_('replies') : $_('reply');
|
||||
|
||||
async function loadReplies(continuation: string) {
|
||||
try {
|
||||
replies = await getComments(videoId, {
|
||||
continuation: continuation,
|
||||
sort_by: 'top',
|
||||
source: 'youtube'
|
||||
});
|
||||
} catch {
|
||||
// Continue regardless of error
|
||||
if (comment.getReplies) {
|
||||
replies = await comment.getReplies();
|
||||
} else {
|
||||
try {
|
||||
replies = await getComments(videoId, {
|
||||
continuation: continuation,
|
||||
sort_by: 'top'
|
||||
});
|
||||
} catch {
|
||||
// Continue regardless of error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import he from 'he';
|
||||
import type Peer from 'peerjs';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
backendInUseStore,
|
||||
interfaceAllowInsecureRequests,
|
||||
interfaceAndroidUseNativeShare,
|
||||
isAndroidTvStore
|
||||
@@ -222,3 +223,7 @@ export function findElementForTime<T>(
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isYTBackend(): boolean {
|
||||
return get(backendInUseStore) === 'yt' && Capacitor.isNativePlatform();
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ export async function getWatchDetails(videoId: string, url: URL) {
|
||||
|
||||
let comments;
|
||||
try {
|
||||
comments = video.liveNow
|
||||
? null
|
||||
: getComments(videoId, { sort_by: 'top', source: 'youtube' }, { priority: 'low' });
|
||||
comments = video.liveNow ? null : getComments(videoId, { sort_by: 'top' }, { priority: 'low' });
|
||||
} catch {
|
||||
comments = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user