diff --git a/src/background.ts b/src/background.ts index 2fc52e0..fd414b2 100644 --- a/src/background.ts +++ b/src/background.ts @@ -20,7 +20,8 @@ import * as browser from 'webextension-polyfill'; import { MessageKind, RedirectType } from './types'; import constants from './constants'; import Preferences from './preferences'; -import InvidiousAPI from './invidious-api'; +import * as InvidiousAPI from './invidious-api'; +import * as PeertubeAPI from './peertube-api'; import { getPeertubeVideoURL } from './util'; /** @@ -32,10 +33,10 @@ const getTitle = async (id: string) => { } /** - * Prevent Youtube from loading if the video is found and preferences set on - * automatic redirection. + * Redirect Youtube without loading it if the video is found and preferences lAHhDKzReKBQKj3R + * set on automatic redirection. */ -const preventYoutube = async (r) => { +const redirectYoutube = async (r) => { const prefs = await Preferences.getPreferences(); if (prefs.redirectYoutube == RedirectType.Auto) { @@ -53,13 +54,38 @@ const preventYoutube = async (r) => { throw new Error('No results.'); }; -const buildSearchByNameURL = (instance: string, query: string): string => `https://${instance}/api/v1/search/videos?search=${encodeURIComponent(query)}`; +/** + * Redirect to preferred PeerTube Instance when trying to watch a PeerTube + * video. + */ +const redirectPeertube = async (r) => { + const prefs = await Preferences.getPreferences(); + const hostname = new URL(r.url).hostname; + + if (prefs.openInOriginalInstance === false && prefs.searchInstance === hostname) { + return {}; // Don't redirect if good instance + } + + if (prefs.redirectPeertube == RedirectType.Auto) { + const id = _.last(_.split('/', r.url)); + const video: any = await PeertubeAPI.getVideo(id); + + if (prefs.openInOriginalInstance && video.account.host === hostname) { + return {}; // Don't redirect if original instance + } + + const url = getPeertubeVideoURL(video, prefs); + + return { + redirectUrl: url + }; + } + + throw new Error('No results.'); +}; const searchByName = query => new Promise(async (resolve, reject) => { - const instance = _.getOr(constants.defaultInstance, 'searchInstance', await browser.storage.local.get()).toString(); - - fetch(buildSearchByNameURL(instance, query)) - .then(res => res.json()) + PeertubeAPI.searchVideo({ search: query }) .then(function(data) { if (data.total > 0) { const video = data.data[0] @@ -71,29 +97,23 @@ const searchByName = query => new Promise(async (resolve, reject) => { }); }); -const buildSearchByIDURL = (instance: string, id: string): string => `https://${instance}/api/v1/videos/${id}`; - -const searchByID = id => new Promise(async (resolve, reject) => { - const instance = _.getOr(constants.defaultInstance, 'searchInstance', await browser.storage.local.get()).toString(); - - fetch(buildSearchByIDURL(instance, id)) - .then(res => res.json()) - .then(function(video) { - resolve({ url: `https://${instance}/videos/watch/${id}`, video }); - }) -}) - browser.runtime.onMessage.addListener(function(message, sender) { switch (message.kind) { case MessageKind.SearchByName: return searchByName(message.query); case MessageKind.SearchByID: - return searchByID(message.id); + return PeertubeAPI.getVideo(message.id); } }); browser.webRequest.onBeforeRequest.addListener( - preventYoutube, + redirectYoutube, { urls: ['*://*.youtube.com/watch?v=*'] }, ['blocking'] ); + +browser.webRequest.onBeforeRequest.addListener( + redirectPeertube, + { urls: ['*://*/videos/watch/*'] }, + ['blocking'] +); diff --git a/src/constants.ts b/src/constants.ts index 7614a01..ab040e2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,8 @@ export default { - defaultInstance: 'peertube.social', + peertubeAPI: { + defaultInstance: 'peertube.social', + endpoint: 'api/v1', + }, invidiousAPI: { url: 'https://invidio.us/api/v1', videos: 'videos', diff --git a/src/invidious-api.ts b/src/invidious-api.ts index d44863f..69f7154 100644 --- a/src/invidious-api.ts +++ b/src/invidious-api.ts @@ -1,27 +1,42 @@ +/* This file is part of PeerTubeify. + * + * PeerTubeify is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * PeerTubeify is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * PeerTubeify. If not, see . + */ + import * as _ from 'lodash/fp'; import constants from './constants'; -export default class InvidiousAPI { - private static async fetchAPI(action: string, params: any) { - const paramString = typeof params == 'string' - ? params - : Object.keys(params).map(function(key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); - }).join('&'); +async function fetchAPI(action: string, params: any) { + const paramString = typeof params == 'string' + ? params + : Object.keys(params).map(function(key) { + return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); + }).join('&'); - return fetch(`${constants.invidiousAPI.url}/${action}/${paramString}`) - .then(res => res.json()) - .catch(e => console.error( - 'An error occured while trying to fetch API used by PeerTubeify: ' - + e.message - )); - } - - static async getVideo(id: string) { - return this.fetchAPI(constants.invidiousAPI.videos, id); - } - - static async getChannel(ucid: string) { - return this.fetchAPI(constants.invidiousAPI.channels, ucid); - } + return fetch(`${constants.invidiousAPI.url}/${action}/${paramString}`) + .then(res => res.json()) + .catch(e => console.error( + 'An error occured while trying to fetch Invidious API used by PeerTubeify: ' + + e.message + )); +} + + +export async function getVideo(id: string) { + return fetchAPI(constants.invidiousAPI.videos, id); +} + +export async function getChannel(ucid: string) { + return fetchAPI(constants.invidiousAPI.channels, ucid); } diff --git a/src/peertube-api.ts b/src/peertube-api.ts new file mode 100644 index 0000000..2f353a4 --- /dev/null +++ b/src/peertube-api.ts @@ -0,0 +1,44 @@ +/* This file is part of PeerTubeify. + * + * PeerTubeify is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * PeerTubeify is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * PeerTubeify. If not, see . + */ + +import * as _ from 'lodash/fp'; +import constants from './constants'; +import Preferences from './preferences'; + +async function fetchAPI(path: string, query?: Object) { + const instance = (await Preferences.getPreferences()).searchInstance; + + let url = `https://${instance}/${constants.peertubeAPI.endpoint}/${path}`; + + if (query) { + url = url + '?' + Object.keys(query).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(query[key])).join('&'); + } + + return fetch(url) + .then(res => res.json()) + .catch(e => console.error( + `An error occured while trying to fetch ${instance} API used by PeerTubeify: ` + + e.message + )); +} + +export async function getVideo(id: string) { + return fetchAPI('videos/' + id); +} + +export async function searchVideo(query: Object) { + return fetchAPI('search/videos', query); +} diff --git a/src/peertube.ts b/src/peertube.ts index f275fcc..76db0d9 100644 --- a/src/peertube.ts +++ b/src/peertube.ts @@ -17,7 +17,7 @@ import * as _ from 'lodash/fp'; import * as browser from 'webextension-polyfill'; -import { htmlToElement } from './util'; +import { htmlToElement, getPeertubeVideoURL } from './util'; import { MessageKind, RedirectType } from './types'; import Preferences from './preferences'; @@ -46,20 +46,13 @@ async function peertubeify() { switch (prefs.redirectPeertube) { case RedirectType.Show: { searchVideo() - .then(async ({ video, url }) => { - const link = videoLink(url, video); + .then(async video => { + const link = videoLink(getPeertubeVideoURL(video, prefs), video); removeVideoLink(); document.querySelector('body').appendChild(link); }).catch(removeVideoLink); break; } - case RedirectType.Auto: { - searchVideo() - .then(async ({ video, url }) => { - location.replace(url); - }); - break; - } case RedirectType.None: { break; } diff --git a/src/preferences.ts b/src/preferences.ts index fd41297..ada960a 100644 --- a/src/preferences.ts +++ b/src/preferences.ts @@ -28,7 +28,7 @@ export default class Preferences { redirectPeertube: RedirectType; constructor(localStorage) { - this.searchInstance = _.defaultTo(constants.defaultInstance, localStorage.searchInstance as string); + this.searchInstance = _.defaultTo(constants.peertubeAPI.defaultInstance, localStorage.searchInstance as string); this.openInOriginalInstance = _.defaultTo(true, localStorage.openInOriginalInstance as boolean); this.redirectYoutube = _.defaultTo(RedirectType.Show, localStorage.redirectYoutube) this.redirectPeertube = _.defaultTo(RedirectType.None, localStorage.redirectPeertube) @@ -54,6 +54,6 @@ export default class Preferences { } set searchInstance(instance) { - this._searchInstance = _.isEmpty(instance) ? constants.defaultInstance : stripProtocol(instance) + this._searchInstance = _.isEmpty(instance) ? constants.peertubeAPI.defaultInstance : stripProtocol(instance) } }