Compare commits
	
		
			1 Commits
		
	
	
		
			florent-pa
			...
			testing
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fa5afd5074 | 
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -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
									
									
									
								
							
							
						
						
									
										35
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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", | ||||
|   | ||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							| @@ -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" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										97
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								src/main.ts
									
									
									
									
									
								
							| @@ -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 = { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user