From f197253e52e98d5d9294a267f6c8ca485295f7c3 Mon Sep 17 00:00:00 2001 From: Ealhad Date: Sat, 15 Sep 2018 18:31:00 +0200 Subject: [PATCH] Add the possibility to display a link when on another PeerTube instance --- README.org | 4 ++ extension/manifest.json | 5 +- package.json | 2 +- src/background.ts | 25 +++++++-- src/options.html | 7 +++ src/options.ts | 15 +++--- src/peertube.ts | 117 ++++++++++++++++++++++++++++++++++++++++ src/types.ts | 4 ++ src/util.ts | 5 ++ src/youtube.ts | 11 ++-- webpack.config.js | 1 + 11 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 src/peertube.ts create mode 100644 src/types.ts create mode 100644 src/util.ts diff --git a/README.org b/README.org index 07734ae..7e8acc9 100644 --- a/README.org +++ b/README.org @@ -4,6 +4,10 @@ PeerTubeify is a browser extension to help discovering which YouTube videos are also available on [[https://joinpeertube.org/][PeerTube]], by displaying a link and a thumbnail below the video title, when watching a video on YouTube. +It also allows you to choose a preferred PeerTube instance; when you watch a +video on another instance, a link to the same video in your preferred instance +is displayed. + PeerTube is a federated video streaming platform. PeerTubeify is not affiliated with PeerTube. diff --git a/extension/manifest.json b/extension/manifest.json index 8c7f219..fc6bdab 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "PeerTubeify", - "version": "0.2.5", + "version": "0.3.0", "description": "On YouTube, displays a link to the same video on PeerTube, if it exists.", "homepage_url": "https://gitlab.com/Ealhad/peertubeify", "icons": { @@ -20,6 +20,9 @@ "content_scripts": [{ "matches": ["*://*.youtube.com/*"], "js": ["dist/youtube.js"] + }, { + "matches": ["https://*/videos/watch/*"], + "js": ["dist/peertube.js"] }], "options_ui": { "page": "dist/options.html" diff --git a/package.json b/package.json index 03c1414..c0af4af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peertubeify", - "version": "0.2.5", + "version": "0.3.0", "description": "PeerTubeify is a browser extension to help discovering which YouTube videos are also available on PeerTube.", "main": "webpack.config.js", "dependencies": {}, diff --git a/src/background.ts b/src/background.ts index 50dbfc6..698732e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -17,14 +17,15 @@ import * as _ from 'lodash/fp'; import * as browser from 'webextension-polyfill'; +import { MessageKind } from './types'; import { constants } from './constants'; -const buildSearchURL = (instance: string, query: string): string => `https://${instance}/api/v1/search/videos?search=${encodeURIComponent(query)}`; +const buildSearchByNameURL = (instance: string, query: string): string => `https://${instance}/api/v1/search/videos?search=${encodeURIComponent(query)}`; -const search = query => new Promise(async (resolve, reject) => { +const searchByName = query => new Promise(async (resolve, reject) => { const instance = _.getOr(constants.defaultInstance, 'searchInstance', await browser.storage.local.get()).toString(); - fetch(buildSearchURL(instance, query)) + fetch(buildSearchByNameURL(instance, query)) .then(res => res.json()) .then(function(data) { if (data.total > 0) { @@ -37,7 +38,23 @@ const search = 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) { - return search(message.query) + switch (message.kind) { + case MessageKind.SearchByName: + return searchByName(message.query); + case MessageKind.SearchByID: + return searchByID(message.id); + } }); diff --git a/src/options.html b/src/options.html index be63bf2..faeaf65 100644 --- a/src/options.html +++ b/src/options.html @@ -22,6 +22,13 @@ + + diff --git a/src/options.ts b/src/options.ts index 8ce741a..cab7a23 100644 --- a/src/options.ts +++ b/src/options.ts @@ -21,14 +21,16 @@ import { constants } from './constants'; function id(id: string): Element { return document.getElementById(id); } -const searchInstanceInput = () => id('search-instance') as HTMLInputElement; -const openInOriginalInstanceInput = () => id('open-in-original-instance') as HTMLInputElement; +const searchInstance = () => id('search-instance') as HTMLInputElement; +const openInOriginalInstance = () => id('open-in-original-instance') as HTMLInputElement; +const showOnPeertube = () => id('show-on-peertube') as HTMLInputElement; function saveOptions(e) { e.preventDefault(); browser.storage.local.set({ - searchInstance: _.defaultTo(constants.defaultInstance, stripProtocol(searchInstanceInput().value)), - openInOriginalInstance: _.defaultTo(true, openInOriginalInstanceInput().checked), + searchInstance: _.defaultTo(constants.defaultInstance, stripProtocol(searchInstance().value)), + openInOriginalInstance: _.defaultTo(true, openInOriginalInstance().checked), + showOnPeertube: _.defaultTo(false, showOnPeertube().checked), }); } @@ -36,8 +38,9 @@ const stripProtocol = _.replace(/^https?:\/\//, ''); function restoreOptions() { browser.storage.local.get().then(result => { - searchInstanceInput().value = _.defaultTo(constants.defaultInstance, result.searchInstance as string); - openInOriginalInstanceInput().checked = _.defaultTo(true, result.openInOriginalInstance as boolean); + searchInstance().value = _.defaultTo(constants.defaultInstance, result.searchInstance as string); + openInOriginalInstance().checked = _.defaultTo(true, result.openInOriginalInstance as boolean); + showOnPeertube().checked = _.defaultTo(false, result.showOnPeertube as boolean); }) } diff --git a/src/peertube.ts b/src/peertube.ts new file mode 100644 index 0000000..80cc3b8 --- /dev/null +++ b/src/peertube.ts @@ -0,0 +1,117 @@ +/* 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 * as browser from 'webextension-polyfill'; + +import { htmlToElement } from './util'; +import { MessageKind } from './types'; +import { constants } from './constants'; + +const watchURL = (host, uuid) => `https://${host}/videos/watch/${uuid}`; +const thumbnailURL = (host, path) => `https://${host}${path}`; + +const LINK_ID = 'peertube-link'; + +async function peertubeify() { + const options = await browser.storage.local.get(); + const showOnPeertube = _.getOr(false, 'showOnPeertube', options); + const preferredInstance = _.getOr(constants.defaultInstance, 'searchInstance', options); + const isPreferredInstance = _.equals(preferredInstance, location.hostname) + if (showOnPeertube && !isPreferredInstance) { + const id = _.last(_.split('/', location.href)); + + browser.runtime.sendMessage({ + kind: MessageKind.SearchByID, + id + }).then(async ({video, url}) => { + const link = videoLink(url, video); + + removeVideoLink(); + document.querySelector('body').appendChild(link); + }).catch(removeVideoLink); + } +} + +const throttledPeertubeify = _.throttle(1000, peertubeify); +const observer = new MutationObserver(function(mutationsList) { + for (const mutation of mutationsList) { + if ((mutation.target as Element).id =='video-element-wrapper') { + throttledPeertubeify(); + } + } +}); + +observer.observe(document.body, { + childList: true, + subtree: true, +}) + +const videoLink = (url, video) => htmlToElement(` + +`); + +function removeVideoLink() { + const existingLink = document.getElementById(LINK_ID); + existingLink && existingLink.remove(); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..2b30ec7 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,4 @@ +export enum MessageKind { + SearchByName, + SearchByID, +} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..f768668 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,5 @@ +export function htmlToElement(html: string): Element { + const template = document.createElement('template'); + template.innerHTML = html.trim(); + return template.content.firstElementChild; +} diff --git a/src/youtube.ts b/src/youtube.ts index cbcd845..aed0981 100644 --- a/src/youtube.ts +++ b/src/youtube.ts @@ -17,6 +17,8 @@ import * as _ from 'lodash/fp'; import * as browser from 'webextension-polyfill'; +import { htmlToElement } from './util'; +import { MessageKind } from './types'; import { constants } from './constants'; const watchURL = (host, uuid) => `https://${host}/videos/watch/${uuid}`; @@ -26,7 +28,8 @@ const LINK_ID = 'peertube-link'; function peertubeify(query: string) { browser.runtime.sendMessage({ - query + kind: MessageKind.SearchByName, + query, }).then(async video => { const options = await browser.storage.local.get(); const openInOriginalInstance = _.getOr(true, 'openInOriginalInstance', options); @@ -58,12 +61,6 @@ observer.observe(document.body, { subtree: true, }) -function htmlToElement(html: string): Element { - const template = document.createElement('template'); - template.innerHTML = html.trim(); - return template.content.firstElementChild; -} - const videoLink = (url, video) => htmlToElement(`