Add the possibility to display a link when on another PeerTube instance
This commit is contained in:
parent
59027cb927
commit
f197253e52
|
@ -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
|
also available on [[https://joinpeertube.org/][PeerTube]], by displaying a link and a thumbnail below the video
|
||||||
title, when watching a video on YouTube.
|
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.
|
PeerTube is a federated video streaming platform.
|
||||||
|
|
||||||
PeerTubeify is not affiliated with PeerTube.
|
PeerTubeify is not affiliated with PeerTube.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "PeerTubeify",
|
"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.",
|
"description": "On YouTube, displays a link to the same video on PeerTube, if it exists.",
|
||||||
"homepage_url": "https://gitlab.com/Ealhad/peertubeify",
|
"homepage_url": "https://gitlab.com/Ealhad/peertubeify",
|
||||||
"icons": {
|
"icons": {
|
||||||
|
@ -20,6 +20,9 @@
|
||||||
"content_scripts": [{
|
"content_scripts": [{
|
||||||
"matches": ["*://*.youtube.com/*"],
|
"matches": ["*://*.youtube.com/*"],
|
||||||
"js": ["dist/youtube.js"]
|
"js": ["dist/youtube.js"]
|
||||||
|
}, {
|
||||||
|
"matches": ["https://*/videos/watch/*"],
|
||||||
|
"js": ["dist/peertube.js"]
|
||||||
}],
|
}],
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "dist/options.html"
|
"page": "dist/options.html"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "peertubeify",
|
"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.",
|
"description": "PeerTubeify is a browser extension to help discovering which YouTube videos are also available on PeerTube.",
|
||||||
"main": "webpack.config.js",
|
"main": "webpack.config.js",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
|
|
|
@ -17,14 +17,15 @@
|
||||||
import * as _ from 'lodash/fp';
|
import * as _ from 'lodash/fp';
|
||||||
import * as browser from 'webextension-polyfill';
|
import * as browser from 'webextension-polyfill';
|
||||||
|
|
||||||
|
import { MessageKind } from './types';
|
||||||
import { constants } from './constants';
|
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();
|
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(res => res.json())
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (data.total > 0) {
|
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) {
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,13 @@
|
||||||
<input type="checkbox" id="open-in-original-instance">
|
<input type="checkbox" id="open-in-original-instance">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label style="margin-bottom: 1rem; display: flex; align-items: center;">
|
||||||
|
<p style="margin-right: 1rem">
|
||||||
|
Show a message on other PeerTube instances to redirect to your preferred one?
|
||||||
|
</p>
|
||||||
|
<input type="checkbox" id="show-on-peertube">
|
||||||
|
</label>
|
||||||
|
|
||||||
<button style="display: block; margin-left: auto" type="submit">Save</button>
|
<button style="display: block; margin-left: auto" type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -21,14 +21,16 @@ import { constants } from './constants';
|
||||||
|
|
||||||
function id(id: string): Element { return document.getElementById(id); }
|
function id(id: string): Element { return document.getElementById(id); }
|
||||||
|
|
||||||
const searchInstanceInput = () => id('search-instance') as HTMLInputElement;
|
const searchInstance = () => id('search-instance') as HTMLInputElement;
|
||||||
const openInOriginalInstanceInput = () => id('open-in-original-instance') as HTMLInputElement;
|
const openInOriginalInstance = () => id('open-in-original-instance') as HTMLInputElement;
|
||||||
|
const showOnPeertube = () => id('show-on-peertube') as HTMLInputElement;
|
||||||
|
|
||||||
function saveOptions(e) {
|
function saveOptions(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
browser.storage.local.set({
|
browser.storage.local.set({
|
||||||
searchInstance: _.defaultTo(constants.defaultInstance, stripProtocol(searchInstanceInput().value)),
|
searchInstance: _.defaultTo(constants.defaultInstance, stripProtocol(searchInstance().value)),
|
||||||
openInOriginalInstance: _.defaultTo(true, openInOriginalInstanceInput().checked),
|
openInOriginalInstance: _.defaultTo(true, openInOriginalInstance().checked),
|
||||||
|
showOnPeertube: _.defaultTo(false, showOnPeertube().checked),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +38,9 @@ const stripProtocol = _.replace(/^https?:\/\//, '');
|
||||||
|
|
||||||
function restoreOptions() {
|
function restoreOptions() {
|
||||||
browser.storage.local.get().then(result => {
|
browser.storage.local.get().then(result => {
|
||||||
searchInstanceInput().value = _.defaultTo(constants.defaultInstance, result.searchInstance as string);
|
searchInstance().value = _.defaultTo(constants.defaultInstance, result.searchInstance as string);
|
||||||
openInOriginalInstanceInput().checked = _.defaultTo(true, result.openInOriginalInstance as boolean);
|
openInOriginalInstance().checked = _.defaultTo(true, result.openInOriginalInstance as boolean);
|
||||||
|
showOnPeertube().checked = _.defaultTo(false, result.showOnPeertube as boolean);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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(`
|
||||||
|
<div id="${LINK_ID}"
|
||||||
|
style="
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: inset 0px 4px 8px -3px rgba(17, 17, 17, .06);
|
||||||
|
background-color: #000;
|
||||||
|
">
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick="document.getElementById('${LINK_ID}').remove()"
|
||||||
|
style="
|
||||||
|
all: unset;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 24px;
|
||||||
|
position: absolute;
|
||||||
|
line-height: .8;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;">
|
||||||
|
⨯
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;"
|
||||||
|
href=${url}>
|
||||||
|
<img src="${thumbnailURL(video.account.host, video.thumbnailPath)}">
|
||||||
|
<div style="
|
||||||
|
font-size: 18px;
|
||||||
|
margin-left: 1rem;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
">
|
||||||
|
<p style="margin: 0 0 1rem;">
|
||||||
|
${video.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
function removeVideoLink() {
|
||||||
|
const existingLink = document.getElementById(LINK_ID);
|
||||||
|
existingLink && existingLink.remove();
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export enum MessageKind {
|
||||||
|
SearchByName,
|
||||||
|
SearchByID,
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export function htmlToElement(html: string): Element {
|
||||||
|
const template = document.createElement('template');
|
||||||
|
template.innerHTML = html.trim();
|
||||||
|
return template.content.firstElementChild;
|
||||||
|
}
|
|
@ -17,6 +17,8 @@
|
||||||
import * as _ from 'lodash/fp';
|
import * as _ from 'lodash/fp';
|
||||||
import * as browser from 'webextension-polyfill';
|
import * as browser from 'webextension-polyfill';
|
||||||
|
|
||||||
|
import { htmlToElement } from './util';
|
||||||
|
import { MessageKind } from './types';
|
||||||
import { constants } from './constants';
|
import { constants } from './constants';
|
||||||
|
|
||||||
const watchURL = (host, uuid) => `https://${host}/videos/watch/${uuid}`;
|
const watchURL = (host, uuid) => `https://${host}/videos/watch/${uuid}`;
|
||||||
|
@ -26,7 +28,8 @@ const LINK_ID = 'peertube-link';
|
||||||
|
|
||||||
function peertubeify(query: string) {
|
function peertubeify(query: string) {
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
query
|
kind: MessageKind.SearchByName,
|
||||||
|
query,
|
||||||
}).then(async video => {
|
}).then(async video => {
|
||||||
const options = await browser.storage.local.get();
|
const options = await browser.storage.local.get();
|
||||||
const openInOriginalInstance = _.getOr(true, 'openInOriginalInstance', options);
|
const openInOriginalInstance = _.getOr(true, 'openInOriginalInstance', options);
|
||||||
|
@ -58,12 +61,6 @@ observer.observe(document.body, {
|
||||||
subtree: true,
|
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(`
|
const videoLink = (url, video) => htmlToElement(`
|
||||||
<div id="${LINK_ID}"
|
<div id="${LINK_ID}"
|
||||||
style="
|
style="
|
||||||
|
|
|
@ -5,6 +5,7 @@ module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
background: 'background.ts',
|
background: 'background.ts',
|
||||||
youtube: 'youtube.ts',
|
youtube: 'youtube.ts',
|
||||||
|
peertube: 'peertube.ts',
|
||||||
options: ['options.ts', 'options.html'],
|
options: ['options.ts', 'options.html'],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|
Loading…
Reference in New Issue