diff --git a/src/lib/server/timeline.ts b/src/lib/server/timeline.ts index fe9b8e9..c861180 100644 --- a/src/lib/server/timeline.ts +++ b/src/lib/server/timeline.ts @@ -1,22 +1,41 @@ -import { HASHTAG_FILTER, URL_FILTER } from '$env/static/private'; +import { HASHTAG_FILTER, URL_FILTER, YOUTUBE_API_KEY } from '$env/static/private'; import type { Post, Tag, TimelineEvent } from '$lib/mastodon/response'; import { savePost } from '$lib/server/db'; import { WebSocket } from "ws"; -/* -Filter youtube: /v3/videos (part: snippet), /v3/videoCategories (part: snippet). Category 10 is Music -*/ const YOUTUBE_REGEX = new RegExp('https?:\/\/(www\.)?youtu((be.com\/.*v=)|(\.be\/))(?[a-zA-Z_0-9-]+)'); export class TimelineReader { private static _instance: TimelineReader; + + private static async isMusicVideo(videoId: string) { + const searchParams = new URLSearchParams([ + ['part', 'snippet'], + ['id', videoId], + ['key', YOUTUBE_API_KEY]]); + const youtubeVideoUrl = new URL(`https://www.googleapis.com/youtube/v3/videos?${searchParams}`); + const resp = await fetch(youtubeVideoUrl); + const respObj = await resp.json(); + const item = respObj.items[0]; + if (item.tags?.includes('music')) { + return true; + } + const categorySearchParams = new URLSearchParams([ + ['part', 'snippet'], + ['id', item.categoryId], + ['key', YOUTUBE_API_KEY]]); + const youtubeCategoryUrl = new URL(`https://www.googleapis.com/youtube/v3/videoCategories?${categorySearchParams}`); + const categoryTitle: string = await fetch(youtubeCategoryUrl).then(r => r.json()).then(r => r.items[0]?.title); + return categoryTitle === 'Music'; + } + private constructor() { //const socket = new WebSocket("wss://metalhead.club/api/v1/streaming/public/local") const socket = new WebSocket("wss://metalhead.club/api/v1/streaming") socket.onopen = (_event) => { socket.send('{ "type": "subscribe", "stream": "public:local"}'); }; - socket.onmessage = (event) => { + socket.onmessage = (async (event) => { try { const data: TimelineEvent = JSON.parse(event.data.toString()); if (data.event !== 'update') { @@ -34,24 +53,35 @@ export class TimelineReader { } const youtubeMatches = found_urls.map(u => u.match(YOUTUBE_REGEX)).filter(i => i !== null); - if (found_urls.length > 0 && youtubeMatches.length === found_urls.length) { - // TODO: Check with YouTube API if it is in YT music or labeled as music - console.info('Found youtube urls', youtubeMatches); + let hasMusicLink = false; + // Only check url if no tags are present and if no other matched URLs (e.g. to bandcamp) are found + if (found_tags.length === 0 && found_urls.length > 0 && youtubeMatches.length === found_urls.length) { + for (let match of youtubeMatches) { + if (match === null) { + continue; + } + try { + const isMusic = await TimelineReader.isMusicVideo(match.groups?.videoId ?? ''); + if (isMusic) { + hasMusicLink = true; + break; + } + } catch (e) { + console.error('Could not check if', youtubeMatches, 'is a music video', e); + } + } + if (!hasMusicLink) { + console.info('Found youtube urls, but none is music', youtubeMatches); + return; + } } - console.log( - "message", - `Update by @${post.account?.username}: ${post.content}`, - 'tags', - post.tags, - found_tags, - found_urls); savePost(post); } catch (e) { console.error("error message", event, event.data, e) } - }; + }); socket.onclose = (event) => { console.log("Closed", event, event.code, event.reason) };