Compare commits

..

1 Commits

Author SHA1 Message Date
fa5afd5074 Mise à jour de 'package.json' 2021-07-23 11:43:09 +02:00
5 changed files with 181 additions and 251 deletions

View File

@ -1,42 +1,3 @@
# Auto Import YouTube
# PeerTube plugin Quickstart
## Config
To use this plugin, you need to give some valid admins credential inside the plugin's settings.
After this you have to specify some YouTube Channel who need to be bind with a PeerTube Channel.
For this you have to use this format inside the plugins's setting. Into `URL list of Youtube channel to synchronize`'s text area. :
```json
[
{
"address":"https://www.youtube.com/feeds/videos.xml?channel_id=${MyYouTubeChannelID}",
"ChannelId":"MyPeertubeChannelId"
} ,
...
]
```
This an array of object who's in this format :
```ts
type SettingsContent = {
address: string;
channelId: string;
timeloop?: number;
};
```
### address
For exemple, to the Youtube channel : `https://www.youtube.com/channel/YouTube`, the channel id is `UCBR8-60-B28hp2BmDPdntcQ`. You can get it when you're clicking in the channel button on the video player. (He's not Highlighted by YouTube. Cheer Up !)
So you need to specify the following address inside your configuration : `https://www.youtube.com/feeds/videos.xml?channel_id=UCBR8-60-B28hp2BmDPdntcQ`.
### channelId
The peertube's channel id is a number associated to a peertube's channel. He's unique per channel inside a same instance.
### timeloop
It's represent the time between two update of the videos list.
It's not needful to specify the timeloop. The default value is set to 5 minutes.
See https://docs.joinpeertube.org/#/contribute-plugins?id=write-a-plugintheme

View File

@ -1,10 +1,13 @@
// import { ImplementableApi } from './implementableApi';
// Api request lib
import fetch, { Headers } from "node-fetch";
import fetch, { FetchError, Headers } from "node-fetch";
import { URL, URLSearchParams } from "url";
import FormData from "form-data";
// import dedent from "ts-dedent";
namespace PeerTubeRequester {
export type Config = {
domainName: string | URL;
domain_name: string | URL;
username: string;
password: string;
};
@ -16,30 +19,45 @@ type UploadInstruction = {
targetUrl: string;
};
type ClientToken = {
client_id: string;
client_secret: string;
grant_type: "password";
response_type: "code";
username: string;
password: string;
};
type UserToken = {
access_token: string;
token_type: string;
expires_in: string;
refresh_token: string;
};
class PeerTubeRequester {
readonly domainName: URL;
readonly domain_name: URL;
readonly username: string;
readonly password: string;
constructor(readonly config: PeerTubeRequester.Config) {
this.domainName = new URL("/", config.domainName);
this.domain_name = new URL("/", config.domain_name);
this.username = config.username;
this.password = config.password;
}
async requestAuthToken(): Promise<any> {
async apiRequest(message: UploadInstruction): Promise<void> {
let response = await fetch(
new URL(`/api/v1/oauth-clients/local`, this.domainName)
new URL(`/api/v1/oauth-clients/local`, this.domain_name)
);
if (!response.ok) {
throw new Error("Cannot get client credentials : " + response.statusText); // CRASH
throw new Error(response.statusText); // CRASH
}
const { client_id: clientId, client_secret: clientSecret } =
await response.json();
const { client_id, client_secret } = await response.json();
const clientInfo: { [key: string]: string } = {
client_id: clientId,
client_secret: clientSecret,
const client_info: { [key: string]: string } = {
client_id,
client_secret,
grant_type: "password",
response_type: "code",
username: this.username,
@ -47,28 +65,25 @@ class PeerTubeRequester {
};
let myParams = new URLSearchParams();
for (const key in clientInfo) myParams.append(key, clientInfo[key]);
for (const key in client_info) myParams.append(key, client_info[key]);
response = await fetch(new URL(`/api/v1/users/token`, this.domainName), {
response = await fetch(new URL(`/api/v1/users/token`, this.domain_name), {
method: "post",
body: myParams,
});
if (!response.ok) {
throw new Error("Cannot get access Token : " + response.statusText); // CRASH
}
const { access_token: accessToken } = await response.json();
return accessToken;
throw new Error(response.statusText); // CRASH
}
const { access_token } = await response.json();
async uploadFromUrl(message: UploadInstruction): Promise<void> {
const accessToken = await this.requestAuthToken();
// Upload
const myUploadForm = new URLSearchParams();
const myHeader = new Headers();
myHeader.append("Authorization", `Bearer ${accessToken}`);
myHeader.append("Authorization", `Bearer ${access_token}`);
for (const key in message) myUploadForm.append(key, message[key]);
const response = await fetch(
new URL("/api/v1/videos/imports", this.domainName),
response = await fetch(
new URL(`/api/v1/videos/imports`, this.domain_name),
{
method: "post",
headers: myHeader,
@ -80,9 +95,9 @@ class PeerTubeRequester {
switch (response.status) {
case 400:
throw new Error(
`Bad or malformed request. Probably because your target URL (from Youtube?) was not accepted by the API.\
The target URL you attempted to pass: ${message.targetUrl}.
Response from the server: ${response.statusText}`
`Your target URL was not accepted by the API.\
Actualy it's : ${message.targetUrl}
${response.statusText}`
);
break;
case 403:
@ -90,15 +105,15 @@ class PeerTubeRequester {
break;
case 409:
throw new Error(
`Oops, your instance did not allowed the HTTPS import.\
`Oops, your instance had not allowed the HTTPS import.\
Contact your administrator.
${response.statusText}`
);
break;
default:
throw new Error(
`Oh, you encountered an undocumented issues.\
Please create an issue to the plugin project.
`Oh, you resolved an undocumented issues.\
Please report this on the git if you have the time.
ERROR: ${response.statusText}`
);
break;

35
package-lock.json generated
View File

@ -1,16 +1,15 @@
{
"name": "peertube-plugin-auto-import-ytb",
"version": "0.0.1",
"version": "0.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.0.1",
"version": "0.0.2",
"dependencies": {
"@types/node-fetch": "^2.5.11",
"form-data": "^4.0.0",
"listener-rss": "^0.0.3",
"listener-rss-aggregator": "^0.0.5",
"listener-rss-agregator": "git+https://zeteo.me/gitea/Outils-PeerTube/listener-rss-agregators#1b36afbb34",
"node-fetch": "^2.6.1"
},
"devDependencies": {
@ -399,10 +398,11 @@
"rss-parser": "^3.11.0"
}
},
"node_modules/listener-rss-aggregator": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/listener-rss-aggregator/-/listener-rss-aggregator-0.0.5.tgz",
"integrity": "sha512-0QE7kkzurjsWr4gNAJ4X+C7UFSyaVuYq2mKYXZ3shvyj81kULpBgFZSg/70MZkUoqixgWQ5P8oxyztRDOP78tw==",
"node_modules/listener-rss-agregator": {
"version": "0.0.3",
"resolved": "git+https://zeteo.me/gitea/Outils-PeerTube/listener-rss-agregators#1b36afbb344112c3827b5bcea92e1cc60bd8cf6a",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@databases/sqlite": "^3.0.0",
"listener-rss": "^0.0.3"
@ -799,9 +799,9 @@
}
},
"node_modules/tar": {
"version": "4.4.14",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.14.tgz",
"integrity": "sha512-ouN3XcSWYOAHmXZ+P4NEFJvqXL50To9OZBSQNNP30vBUFJFZZ0PLX15fnwupv6azfxMUfUDUr2fhYw4zGAEPcg==",
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"dependencies": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
@ -1270,10 +1270,9 @@
"rss-parser": "^3.11.0"
}
},
"listener-rss-aggregator": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/listener-rss-aggregator/-/listener-rss-aggregator-0.0.5.tgz",
"integrity": "sha512-0QE7kkzurjsWr4gNAJ4X+C7UFSyaVuYq2mKYXZ3shvyj81kULpBgFZSg/70MZkUoqixgWQ5P8oxyztRDOP78tw==",
"listener-rss-agregator": {
"version": "git+https://zeteo.me/gitea/Outils-PeerTube/listener-rss-agregators#1b36afbb344112c3827b5bcea92e1cc60bd8cf6a",
"from": "listener-rss-agregator@git+https://zeteo.me/gitea/Outils-PeerTube/listener-rss-agregators#1b36afbb34",
"requires": {
"@databases/sqlite": "^3.0.0",
"listener-rss": "^0.0.3"
@ -1605,9 +1604,9 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"tar": {
"version": "4.4.14",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.14.tgz",
"integrity": "sha512-ouN3XcSWYOAHmXZ+P4NEFJvqXL50To9OZBSQNNP30vBUFJFZZ0PLX15fnwupv6azfxMUfUDUr2fhYw4zGAEPcg==",
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",

View File

@ -1,9 +1,9 @@
{
"name": "peertube-plugin-auto-import-ytb",
"description": "Peertube plugin to auto import videos from a youtube channel to a local peertube channel",
"description": "PeerTube plugin quickstart",
"version": "0.0.2",
"author": "AmauryJOLY",
"bugs": "https://zeteo.me/gitea/Outils-PeerTube/peertube-plugin-auto-import-ytb/issues",
"bugs": "https://framagit.org/framasoft/peertube/peertube-plugin-quickstart/issues",
"clientScripts": [],
"css": [],
"devDependencies": {
@ -13,7 +13,7 @@
"engine": {
"peertube": ">=3.2.0"
},
"homepage": "https://zeteo.me/gitea/Outils-PeerTube/peertube-plugin-auto-import-ytb",
"homepage": "https://framagit.org/framasoft/peertube/peertube-plugin-quickstart",
"keywords": [
"peertube",
"plugin"
@ -24,6 +24,7 @@
"README.md"
],
"scripts": {
"prepack": "npm i && npm run build",
"predeploy": "npm run build",
"deploy": "bash ./scripts/deploy.sh",
"build": "tsc"
@ -33,8 +34,7 @@
"dependencies": {
"@types/node-fetch": "^2.5.11",
"form-data": "^4.0.0",
"listener-rss-aggregator": "^0.0.5",
"listener-rss": "^0.0.3",
"listener-rss-agregator": "git+https://zeteo.me/gitea/Outils-PeerTube/listener-rss-agregators#1b36afbb34",
"node-fetch": "^2.6.1"
}
}

View File

@ -1,4 +1,4 @@
import { ListenerRssAggregator } from "listener-rss-aggregator";
import { ListenerRssAggregator } from "listener-rss-agregator";
import { ListenerRss } from "listener-rss";
import { PeerTubeRequester } from "../lib/peertubeRequester";
@ -10,8 +10,7 @@ type ListenerData = ListenerRss.Config & {
let myManager: ListenerRssAggregator;
let listenersDataBinding = new Map<string, ListenerData>();
let logger: any;
let peertube: PeerTubeRequester | undefined = undefined;
let goodPeertubeCredential: boolean = false;
let peertube: PeerTubeRequester;
import * as path from "path";
import fs from "fs";
@ -26,23 +25,11 @@ async function register({
registerSetting({
name: "ytb-urls",
label: "URL list of Youtube channel to synchronize",
label: "liste des urls youtube a auto-importer",
type: "input-textarea",
});
registerSetting({
name: "admin-name",
label: "Admin Username",
type: "input",
});
registerSetting({
name: "admin-password",
label: "Admin Password",
type: "input-password",
});
logger.debug("setting register");
logger.warn("setting register");
fs.appendFileSync(path.join(basePath, "/storage.bd"), "");
const configAggregator = await ListenerRssAggregator.instantiateAggregator(
@ -50,80 +37,47 @@ async function register({
);
myManager = new ListenerRssAggregator(configAggregator);
logger.debug("Aggregator created");
const settingYtbUrls = await settingsManager.getSetting("ytb-urls");
if (settingYtbUrls) await addListeners(settingYtbUrls);
const settingCredentials: any = await settingsManager.getSettings([
"admin-name",
"admin-password",
]);
if (settingCredentials["admin-name"] && settingCredentials["admin-password"])
apiRequestInitializer({
domainName: peertubeHelpers.config.getWebserverUrl(),
username: settingCredentials["admin-name"],
password: settingCredentials["admin-password"],
peertube = new PeerTubeRequester({
domain_name: "http://localhost:9000",
username: "root",
password: "test",
});
logger.debug("Actual config loaded");
logger.warn("Aggregator created");
const inputs = await settingsManager.getSetting("ytb-urls");
if (inputs) await addListeners(inputs);
logger.warn("Config loaded");
settingsManager.onSettingsChange(async (settings: any) => {
if (
!peertube ||
peertube.username != settings["admin-name"] ||
peertube.password != settings["admin-password"]
)
apiRequestInitializer({
domainName: peertubeHelpers.config.getWebserverUrl(),
username: settings["admin-name"],
password: settings["admin-password"],
});
await addListeners(settings["ytb-urls"]);
});
myManager.on("newEntries", async (entries: any) => {
myManager.on("newEntries", (entries: any) => {
const datas = listenersDataBinding.get(entries.addressListener);
if (!datas) return;
logger.debug(
"New entries detected from channel #%i: %s",
datas.channelId,
JSON.stringify(entries)
logger.warn(
"Nouvelles entrées détéctées: " +
JSON.stringify(entries) +
" de " +
datas.channelId
);
for (const item of entries.items)
if (peertube)
await peertube.uploadFromUrl({
peertube.apiRequest({
channelId: datas.channelId,
targetUrl: item.link,
});
else {
logger.warn("Bad credential provides. New entries Skipped.");
}
});
}
async function apiRequestInitializer(data: PeerTubeRequester.Config) {
peertube = new PeerTubeRequester(data);
try {
await peertube.requestAuthToken();
goodPeertubeCredential = true;
logger.debug("credential ok");
} catch (error) {
logger.warn("Error during the credential validation : " + error);
peertube = undefined;
goodPeertubeCredential = false;
}
}
async function addListeners(listenerInput: string) {
let listeners: ListenerData[];
try {
listeners = JSON.parse(listenerInput);
} catch {
logger.error("Malformed URL");
logger.warn("Erreur: malformé");
return;
}
let newListeners = listeners.filter(
@ -143,13 +97,14 @@ async function addListeners(listenerInput: string) {
myManager.stopAll();
await myManager.saveOverride(listeners);
if (logger) logger.debug("Configuration changed: " + listenerInput);
if (logger) logger.warn("Configuration modifiée: " + listenerInput);
if (goodPeertubeCredential) myManager.startAll();
myManager.startAll();
}
async function unregister() {
myManager.stopAll();
return;
}
module.exports = {