Compare commits

...

14 commits

17 changed files with 2323 additions and 1019 deletions

View file

@ -1,4 +1,4 @@
fore.st - Pineapple Express++ (v1.1.1) fore.st - Pineapple Express += 3 (v1.1.3)
====== ======
fore.st is the server software for ourfore.st, a community based chat & synced video embedding site tailored to service fore.st is the server software for ourfore.st, a community based chat & synced video embedding site tailored to service
@ -24,44 +24,8 @@ You can reach out by bugging rainbownapkin on the ttn discord or ourfore.st, you
- Thanks to calzoneman for making [cytube](https://github.com/calzoneman/sync), that saved our asses. - Thanks to calzoneman for making [cytube](https://github.com/calzoneman/sync), that saved our asses.
- Thanks to the core TTN community and everyone else who's ever used it, I was only there for the last handful of years but it was an absolute fuckin' ride. You guys are the best, it isn't TTN but I hope this at least help fills the gap. - Thanks to the core TTN community and everyone else who's ever used it, I was only there for the last handful of years but it was an absolute fuckin' ride. You guys are the best, it isn't TTN but I hope this at least help fills the gap.
## Pineapple Express++ 1.1.1 Release Notes ## Pineapple Express += 3 1.1.3 Release Notes
Abriged Patchnotes: - Add bulk-queueing from Internet Archive
- bugfixes/QoL tweaks
- remove duplicated blazem from tokefile
- fix playlist on submit channel for standard users
- only show nested menu on playlist if user rank is <= 2
- show "playlist" button for users rank 1 on r/submit channel
- stop tokebot from including full toke messages
- fully disconnect/kick unregistered users from channels connect
- limit words/link display text(DO NOT MODIFY LINK HREF) to 40 chars to prevent breaking chat width
- limit image embed width
- fix video/chat/autobump UI components not expanding after collapsing in chromium based browsers
- remove close playlist button when legacy playlist is disabled
- fix expand/shade playlist items button
- add end date to playlist items
- userlist collapse/expand persistent accross browser sessions
- cinema mode persistent accross browser sessions
- dissapearing server whispers/join messages
- fix input field lengths in quicksettings
- prevent temporary items on submit channel
- always block anonymous user (ignore channel setting)
- use regex for whitespace detection in commands
- client side commands
- process commands that start with "/" or "(whitespace)/" in the browser
- allow admins to trigger client side commands remotely
- show words that start with "/" as a link that chatsmacks command(similiar to behavior with words that start with !)
- /thunder
- Disable lightning (seizure prevention)
- update tab completion
- add non-toke server-side commands to tab completion(check perms for respective commands)
- add client-side commands to tab completion
- show autocomplete in text box
- kick users w/ old client
- spooktober themes
## License ## License
Original fore.st code is provided under the Affero General Public License v3 in order to prevent fore.st being used in proprietary software. Original fore.st code is provided under the Affero General Public License v3 in order to prevent fore.st being used in proprietary software.

View file

@ -114,6 +114,7 @@ io:
#pull info from invidious #pull info from invidious
invidious-backend: true invidious-backend: true
yt-dlp-path: 'yt-dlp'
#invidious source, defaults to vid.puffyan.us, not affiliated, simply a well known US based instance #invidious source, defaults to vid.puffyan.us, not affiliated, simply a well known US based instance
#invidious-source: 'vid.puffyan.us' #invidious-source: 'vid.puffyan.us'

2767
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "fore.st", "name": "fore.st",
"version": "1.1.1", "version": "1.1.3",
"description": "fore.st: A fork of cytube tailored for the TTN Community", "description": "fore.st: A fork of cytube tailored for the TTN Community",
"main": "index.js", "main": "index.js",
"directories": { "directories": {
@ -23,7 +23,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"express-minify": "^1.0.0", "express-minify": "^1.0.0",
"json-typecheck": "^0.1.3", "json-typecheck": "^0.1.3",
"knex": "^0.95.2", "knex": "^3.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1", "mysql": "^2.18.1",
@ -38,7 +38,8 @@
"source-map-support": "^0.5.19", "source-map-support": "^0.5.19",
"toml": "^3.0.0", "toml": "^3.0.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"yamljs": "^0.2.8" "yamljs": "^0.2.8",
"youtube-dl-exec": "^3.0.10"
}, },
"scripts": { "scripts": {
"build-server": "babel -D --source-maps --out-dir lib/ src/", "build-server": "babel -D --source-maps --out-dir lib/ src/",

View file

@ -1,60 +1,5 @@
dev goals for 1.1.1 pineapple express++: dev goals for 1.1.3 pineapple Express += 3:
-- Add bulk-queueing from Internet Archive
- bugfixes/QoL tweaks - automagically pull most web-compatible version of all videos within a specific upload to IA
- remove duplicated blazem from tokefile - allow queueing admin to filter directory by video length in minutes
- fix playlist on submit channel for standard users - allow for bulk naming
- only show nested menu on playlist if user rank is <= 2
- show "playlist" button for users rank 1 on r/submit channel
- stop tokebot from including full toke messages
- fully disconnect/kick unregistered users from channels connect
- limit words/link display text(DO NOT MODIFY LINK HREF) to 40 chars to prevent breaking chat width
- limit image embed width
- fix video/chat/autobump UI components not expanding after collapsing in chromium based browsers
- remove close playlist button when legacy playlist is disabled
- fix expand/shade playlist items button
- add end date to playlist items
- userlist collapse/expand persistent accross browser sessions
- cinema mode persistent accross browser sessions
- dissapearing server whispers/join messages
- fix input field lengths in quicksettings
- prevent temporary items on submit channel
- always block anonymous user (ignore channel setting)
- use regex for whitespace detection in commands
- client side commands
- process commands that start with "/" or "(whitespace)/" in the browser
- allow admins to trigger client side commands remotely
- remove / from client command when calling sendcmd if present
- do not send command as chat (unless it starts with whitespace, matching serverside command behavior)
- show words that start with "/" as a link that chatsmacks command(similiar to behavior with words that start with !)
- /thunder
- Disable lightning (seizure prevention)
- update tab completion
- add non-toke server-side commands to tab completion(check perms for respective commands)
- me
- announce
- af
- sp
- afk
- poll
- hpoll
- mute
- smute
- unmute
- kick
- ban
- ipban
- clear
- clean
- cleantitle
- sendcmd
- add client-side commands to tab completion
- show autocomplete in text box
- send clientside version on connect
- kick if version old/not received
- spooktober themes
- Candycorn
- Candycorn lite

View file

@ -107,11 +107,13 @@ function loadLists(cb, callp){
bumplists = new Map();//create new map to load lists into, this clears the variable as well as lets the channel know whether or not they have been loaded yet. bumplists = new Map();//create new map to load lists into, this clears the variable as well as lets the channel know whether or not they have been loaded yet.
if(item != null){
item.forEach(function(list, i){ item.forEach(function(list, i){
if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){ if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){
loadList("bumps/" + list); loadList("bumps/" + list);
} }
}); });
}
if(cb != null){//lil' nasty but it calls loadconfig after loading lists :P if(cb != null){//lil' nasty but it calls loadconfig after loading lists :P
if(callp != null){ if(callp != null){

View file

@ -67,6 +67,7 @@ const TYPE_QUEUE = {
pos: "string", pos: "string",
title: "string,boolean,optional", title: "string,boolean,optional",
duration: "number,optional", duration: "number,optional",
minDuration: "number,optional",
temp: "boolean,optional", temp: "boolean,optional",
subtitle: "string" subtitle: "string"
}; };
@ -445,7 +446,7 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
* Specifying a custom title is currently only allowed for custom media * Specifying a custom title is currently only allowed for custom media
* and raw files * and raw files
*/ */
if (typeof data.title !== "string" || (data.type !== "cu" && data.type !== "fi")) { if (typeof data.title !== "string" || (data.type !== "cu" && data.type !== "fi" && data.type !== "ia")) {
data.title = false; data.title = false;
} }
@ -466,9 +467,9 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
} }
/* Certain media types require special permission to add */ /* Certain media types require special permission to add */
if (data.type === "yp" && !perms.canAddList(user)) { if ((data.type === "yp" || data.type === "ia") && !perms.canAddList(user)) {
user.socket.emit("queueFail", { user.socket.emit("queueFail", {
msg: "You don't have permission to add playlists", msg: "You don't have permission to bulk queue",
link: link, link: link,
id: id id: id
}); });
@ -504,6 +505,11 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
duration = !isNaN(data.duration) ? data.duration : undefined; duration = !isNaN(data.duration) ? data.duration : undefined;
} }
var minDuration = undefined;
if (typeof data.minDuration === "number") {
minDuration = !isNaN(data.minDuration) ? data.minDuration : 0;
}
var limit = { var limit = {
burst: 3, burst: 3,
sustained: 1 sustained: 1
@ -543,11 +549,14 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
shouldAddToLibrary: true,//for now chan library will act as a history shouldAddToLibrary: true,//for now chan library will act as a history
queueby: queueby, queueby: queueby,
duration: duration, duration: duration,
minDuration: minDuration,
maxlength: maxlength maxlength: maxlength
}; };
if (data.type === "yp") { if (data.type === "yp") {
this.queueYouTubePlaylist(user, data); this.queueYouTubePlaylist(user, data);
} else if (data.type === "ia") {
this.queueBulkIA(user, data);
} else { } else {
this.queueStandard(user, data); this.queueStandard(user, data);
} }
@ -576,7 +585,38 @@ PlaylistModule.prototype.queueStandard = function (user, data) {
lock.release(); lock.release();
self.channel.refCounter.unref("PlaylistModule::queueStandard"); self.channel.refCounter.unref("PlaylistModule::queueStandard");
}); });
}, data.minDuration);
}); });
};
PlaylistModule.prototype.queueBulkIA = function (user, data) {
var error = function (what) {
user.socket.emit("queueFail", {
msg: what,
link: data.link,
id: data.id
});
};
const self = this;
this.channel.refCounter.ref("PlaylistModule::queueBulkIA");
this.semaphore.queue(function (lock) {
InfoGetter.getMedia(data.id, data.type, function (err, vids) {
if (err) {
error(XSS.sanitizeText(String(err)));
self.channel.refCounter.unref("PlaylistModule::queueBulkIA");
return lock.release();
}
vids.forEach(function(media){
self._addItem(media, data, user, null, data.minDuration);
});
self.channel.refCounter.unref("PlaylistModule::queueBulkIA");
lock.release();
}, data.minDuration);
}); });
}; };
@ -1117,7 +1157,7 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb, abump) {
queueby: data.queueby queueby: data.queueby
}); });
if (data.title && (media.type === "cu" || media.type === "fi")) { if (data.title && (media.type === "cu" || media.type === "fi" || media.type === "ia")) {
media.setTitle(data.title); media.setTitle(data.title);
} }
@ -1289,11 +1329,16 @@ PlaylistModule.prototype.startPlayback = function (time) {
} }
if(self.current.media.type == "yt"){//if its yt if(self.current.media.type == "yt"){//if its yt
InfoGetter.getYTRaw(self.current.media.id,function(url){//get raw link from invidious api InfoGetter.getRawCopy(self.current.media.id,function(url){//get raw link from invidious api
self.current.media.meta.rawLink = url;//set to meta self.current.media.meta.rawLink = url;//set to meta
self.sendChangeMedia(self.channel.users);//fuggin SEND IT self.sendChangeMedia(self.channel.users);//fuggin SEND IT
}); });
}else if(self.current.media.type == "dm"){
InfoGetter.getRawCopy(`https://dailymotion.com/video/${self.current.media.id}`,function(url){//get raw link from invidious api
self.current.media.meta.rawLink = url;//set to meta
self.sendChangeMedia(self.channel.users);//fuggin SEND IT
});
}else{ }else{
self.sendChangeMedia(self.channel.users); self.sendChangeMedia(self.channel.users);
} }

View file

@ -67,6 +67,7 @@ var defaults = {
}, },
"invidious-backend": true, "invidious-backend": true,
"invidious-source": 'inv.riverside.rocks', "invidious-source": 'inv.riverside.rocks',
"yt-dlp-path": 'yt-dlp',
"youtube-v3-key": "", "youtube-v3-key": "",
"channel-blacklist": [], "channel-blacklist": [],
"channel-path": "r", "channel-path": "r",

View file

@ -49,9 +49,14 @@ const Vimeo = require("@cytube/mediaquery/lib/provider/vimeo");
const Streamable = require("@cytube/mediaquery/lib/provider/streamable"); const Streamable = require("@cytube/mediaquery/lib/provider/streamable");
const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod"); const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod");
const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip"); const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip");
const { create: makeYTDLP } = require('youtube-dl-exec')
//Specify path assuming yt-dlp is installed locally with path set properly (version packaged w/ npm package doesn't behave)
const YTDLP = makeYTDLP(Config.get('yt-dlp-path'));
import { Counter } from 'prom-client'; import { Counter } from 'prom-client';
import { lookup as lookupCustomMetadata } from './custom-media'; import { lookup as lookupCustomMetadata } from './custom-media';
const LOGGER = require('@calzoneman/jsli')('get-info'); const LOGGER = require('@calzoneman/jsli')('get-info');
const lookupCounter = new Counter({ const lookupCounter = new Counter({
name: 'cytube_media_lookups_total', name: 'cytube_media_lookups_total',
@ -111,7 +116,7 @@ function getBlocked(reg){
var Getters = { var Getters = {
/* youtube.com */ /* youtube.com */
yt: function (id, callback) { yt: async function (id, callback) {
if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky) if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky)
if (!Config.get("youtube-v3-key")) { if (!Config.get("youtube-v3-key")) {
@ -122,9 +127,11 @@ var Getters = {
YouTube.lookup(id).then(function (video) { YouTube.lookup(id).then(function (video) {
var meta = {}; var meta = {};
if (video.meta.blocked) { if (video.meta.blocked) {
meta.restricted = video.meta.blocked; meta.restricted = video.meta.blocked;
} }
if (video.meta.ytRating) { if (video.meta.ytRating) {
meta.ytRating = video.meta.ytRating; meta.ytRating = video.meta.ytRating;
} }
@ -134,8 +141,31 @@ var Getters = {
}).catch(function (err) { }).catch(function (err) {
callback(err.message || err, null); callback(err.message || err, null);
}); });
}else{//invidious api calls (google bad) }else{
var options = { //yt-dlp calls (google bad)
try{
var video = await YTDLP(`youtu.be/${id}`,{
dumpSingleJson: true,
format: "b"
}
)
var meta = {
ytRating: video.like_count,
rawLink: video.requested_downloads[0].url
}
var media = new Media(video.id, video.title, video.duration, "yt", meta);
callback(false, media);
}catch(err){
callback(err.message || err, null);
}
//callback("test funciton, remove this call!" || err, null);
//invidious api calls (o7 Stream on, you magnificent bastard. We'll always remember you! <3 2018-2024)
/*var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
path: "/api/v1/videos/" + id, path: "/api/v1/videos/" + id,
@ -161,13 +191,13 @@ var Getters = {
var media = new Media(vid.videoId, vid.title, vid.lengthSeconds, "yt", meta); var media = new Media(vid.videoId, vid.title, vid.lengthSeconds, "yt", meta);
return callback(false, media); return callback(false, media);
}); });*/
} }
}, },
/* youtube.com playlists */ /* youtube.com playlists */
yp: function (id, callback) { yp: async function (id, callback) {
if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky) if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky)
if (!Config.get("youtube-v3-key")) { if (!Config.get("youtube-v3-key")) {
return callback("The YouTube API now requires an API key. You could sign up for an API key, but you're a lot better off using the invidious backend!" + return callback("The YouTube API now requires an API key. You could sign up for an API key, but you're a lot better off using the invidious backend!" +
@ -188,7 +218,35 @@ var Getters = {
}).catch(function (err) { }).catch(function (err) {
callback(err.message || err, null); callback(err.message || err, null);
}); });
}else{//invidious api calls (google bad) }else{//yt-dlp wrapper calls (google bad)
try{
var list = await YTDLP(`youtu.be/${id}`,{
dumpSingleJson: true,
format: "b"
}
);
var videos = new Array;
list.entries.forEach(function(video){
if(video != null){
var meta = {
ytRating: video.like_count,
rawLink: video.requested_downloads[0].url
}
videos.push(new Media(video.id, video.title, video.duration, "yt", meta));
}
});
callback(null, videos);
}catch(err){
callback(err.message || err, null);
}
/*invidious api calls (o7 RIP 2018-2024)
var options = { var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
@ -209,12 +267,12 @@ var Getters = {
}); });
return callback(null, vids); return callback(null, vids);
}); });*/
} }
}, },
/* youtube.com search */ /* youtube.com search */
ytSearch: function (query, callback) { ytSearch: async function (query, callback) {
if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky) if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky)
if (!Config.get("youtube-v3-key")) { if (!Config.get("youtube-v3-key")) {
@ -239,7 +297,33 @@ var Getters = {
}).catch(function (err) { }).catch(function (err) {
callback(err.message || err, null); callback(err.message || err, null);
}); });
}else{//invidious api calls (google bad) }else{
try{
var results = await YTDLP(`ytsearch8:${query}`,{
dumpSingleJson: true,
}
);
var videos = new Array;
results.entries.forEach(function(result){
var meta = {
ytRating: result.like_count
}
var video = new Media(result.id, result.title, result.duration, "yt", meta);
video.thumb = {url: result.thumbnails[5].url};
videos.push(video);
});
callback(null, videos);
}catch(err){
callback(err.message || err, null);
}
/*invidious api calls (o7 RIP 2018-2024)
var options = { var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
@ -264,7 +348,7 @@ var Getters = {
}); });
return callback(null, vids.filter(rs => rs != null)); return callback(null, vids.filter(rs => rs != null));
}); });*/
} }
}, },
@ -287,6 +371,8 @@ var Getters = {
}, },
/* dailymotion.com */ /* dailymotion.com */
//The dailymotion player has been broken, however their basic API remains intact.
//This will stay *for now* but will be replaced by yt-dlp at the first sign of resistance.
dm: function (id, callback) { dm: function (id, callback) {
var m = id.match(/([\w-]+)/); var m = id.match(/([\w-]+)/);
if (m) { if (m) {
@ -582,21 +668,114 @@ var Getters = {
callback, callback,
"As of July 2020, Mixer is no longer in service." "As of July 2020, Mixer is no longer in service."
); );
},
/*Internet Archive Bulk Grabber*/
ia: function(id, minDuration, callback){
try{
//Get metadata on the directory
var options = {
host: "archive.org",
port: 443,
path: "/metadata/" + id,
method: "GET",
timeout: 1000
};
//pull the URL
urlRetrieve(https, options, function (status, data) {
//if we fucked up
if(status !== 200) {
return callback("Archive.org HTTPS error code: " + status, null);
}
//Parse the dump
var dump = JSON.parse(data);
//if we have files
if(dump.files != null){
var vids = new Array;
var derivative = new Array;
var media = new Array;
//sift through files to find .mp4's
dump.files.forEach(function(file){
//Skip out on videos that dont meet the requested minimum duration
if(file.length >= minDuration){
//if its a standard .mp4 (either MPEG, or h.264)
if(file.format == "h.264" || file.format == "MPEG4"){
//add the file to the video array
vids.push(file);
//if it's been transcoded by archive.org to ensure web-compatibility
}else if(file.format == "h.264 IA"){
//add the file to the derivative array
derivative.push(file);
}
}
});
//chose derivatives over originals to save on bandwith and ensure web-compatibility, even if they don't always look as nice :P
derivative.forEach(function(file){
//sift through standard files to find matching originals
for(var i = 0; i < vids.length; i++){
//if we have a match
if(vids[i].name == file.original){
//replace the file with the correct derivative
vids[i] = file;
}
}
});
//Lets try this just using info from IA's api. It would take up way less fucking time, even if we can't fill in everything...
vids = vids.map(function (file) {
return new Media(`https://${dump.d1}${dump.dir}/${file.name}`, dump.metadata.title, file.length, "fi", {codec: "mov/h264"});
});
process.nextTick(callback, false, vids);
//if we fucked up some other way
}else if(dump.error != null){
return callback(`Archive.org error: ${dump.error}`);
}else{
return callback("Unkown metadata error from archive.org!");
}
});
} catch (err) {
callback(err.message);
}
} }
}; };
module.exports = { module.exports = {
Getters: Getters, Getters: Getters,
getMedia: function (id, type, callback) { getMedia: function (id, type, callback, minDuration) {
if (type in this.Getters) { if (type in this.Getters) {
LOGGER.info("Looking up %s:%s", type, id); LOGGER.info("Looking up %s:%s", type, id);
lookupCounter.labels(type).inc(1, new Date()); lookupCounter.labels(type).inc(1, new Date());
if(type == "ia"){
this.Getters.ia(id, minDuration, callback);
}else{
this.Getters[type](id, callback); this.Getters[type](id, callback);
}
} else { } else {
callback("Unknown media type '" + type + "'", null); callback("Unknown media type '" + type + "'", null);
} }
}, },
getYTRaw: function (id, cb){ getRawCopy: async function (id, cb){
try{
var video = await YTDLP(`youtu.be/${id}`,{
dumpSingleJson: true,
format: "b"
}
)
cb(video.requested_downloads[0].url);
}catch(err){
console.log(err.message);
}
/* Invidious Code (o7 RIP 2018-2024)
var options = { var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
@ -606,6 +785,8 @@ module.exports = {
}; };
urlRetrieve(https, options, function (status, data) { urlRetrieve(https, options, function (status, data) {
old invidious code (o7 2018 - 2024)
if(status !== 200) { if(status !== 200) {
console.log("Invidious HTTPS error code: " + status); console.log("Invidious HTTPS error code: " + status);
} }
@ -615,6 +796,7 @@ module.exports = {
if(vid.formatStreams[0] != null){//TEMPORARY FOR FRONTEND DEV PURPOSES, PULL LINK AND SET AGAIN WHEN VIDEO QUEUED(shit expires) if(vid.formatStreams[0] != null){//TEMPORARY FOR FRONTEND DEV PURPOSES, PULL LINK AND SET AGAIN WHEN VIDEO QUEUED(shit expires)
cb(vid.formatStreams[vid.formatStreams.length - 1].url); cb(vid.formatStreams[vid.formatStreams.length - 1].url);
} }
});
});*/
} }
}; };

View file

@ -56,4 +56,4 @@ block content
| No spamming submit channel or chat | No spamming submit channel or chat
p. p.
Comments? Questions? Feature requests? DMCA Notices? <a href="mailto:ourforest@420blaze.it">Email us!</a> Comments? Questions? Feature requests? DMCA Notices? <a href="mailto:ourforest@420blaze.it">Email us!</a>
h4 fore.st version: Pineapple Express++ (v1.1.1) h4 fore.st version: Pineapple Express += 3 (v1.1.3)

View file

@ -131,6 +131,8 @@ html(lang="en")
#plcontrol.btn-group #plcontrol.btn-group
button#showmediaurl.btn.btn-sm.btn-default(title="Add video from URL", data-toggle="collapse", data-target="#addfromurl") button#showmediaurl.btn.btn-sm.btn-default(title="Add video from URL", data-toggle="collapse", data-target="#addfromurl")
span.glyphicon.glyphicon-plus span.glyphicon.glyphicon-plus
button#golive.btn.btn-sm.btn-danger(title="Go Live")
span.glyphicon.glyphicon-record
button#showsearch.btn.btn-sm.btn-default(title="Channel History + Video Search", data-toggle="collapse", data-target="#searchcontrol") button#showsearch.btn.btn-sm.btn-default(title="Channel History + Video Search", data-toggle="collapse", data-target="#searchcontrol")
span.glyphicon.glyphicon-search span.glyphicon.glyphicon-search
button#showplaylistmanager.btn.btn-sm.btn-default(title="Manage playlists", data-toggle="collapse", data-target="#playlistmanager") button#showplaylistmanager.btn.btn-sm.btn-default(title="Manage playlists", data-toggle="collapse", data-target="#playlistmanager")

View file

@ -1,47 +0,0 @@
extends layout.pug
block content
.col-md-8.col-md-offset-2
h1 Google Drive Userscript
h2 Disclaimer
.alert.alert-danger.messagebox
strong Unsupported
p.
This functionality is provided <strong>as-is</strong> for backwards
compatibility for existing users for whom it already is known to work.
There are many reasons, known and unknown, for which it may
<strong>not</strong> work for you; please note the staff in CyTube
support channels cannot provide any troubleshooting assistance and you
will be asked to simply use a different video provider.
p.
This functionality was originally added so that users could share their
own personal videos stored in their Drive. No support whatsoever will
be provided to users attempting to use it to circumvent copyright
restrictions on third-party video hosts.
h2 How It Works
p.
The userscript is a short script that you can install using a browser
extension such as Greasemonkey or Tampermonkey that runs on the page
and provides additional functionality needed to play Google Drive
videos.
h2 Installation
ul
li
strong Chrome
| &mdash;Install <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo" target="_blank">Tampermonkey</a>.
li
strong Firefox
| &mdash;Install <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/" target="_blank">Tampermonkey</a>
| or <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/" target="_blank">Greasemonkey</a>.
li
strong Other Browsers
| &mdash;Install the appropriate userscript plugin for your browser.
| Tampermonkey supports many browsers besides Chrome.
p.
Once you have installed the userscript manager addon for your browser,
you can <a href="/js/cytube-google-drive.user.js" target="_blank">
install the userscript</a>. If this link 404s, it means the administrator
of this server hasn't generated it yet.
p.
You can find a guide with screenshots of the installation process
<a href="https://github.com/calzoneman/sync/wiki/Google-Drive-Userscript-Installation-Guide" target="_blank">on GitHub</a>.

View file

@ -299,3 +299,37 @@ yikes
booyak booyak
bust bust
bustin bustin
weedeven
even
succ
barm
propane
dope
yep
dangit
bobby
dabs
mclovin
delaware
tight
doh
ass
dick
scottbaio
resin
penisman
punchy
hotto
geekedup
comedy
bake
baked
shweed
kungfu
dream
MDK
3ven
SmoothAsEggs
nosedive
rip
slorp

View file

@ -37,7 +37,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
var CL_VERSION = "1.1.1"; var CL_VERSION = "1.1.3";
var GS_VERSION = 1.7; // Google Drive Userscript var GS_VERSION = 1.7; // Google Drive Userscript
var CLIENT = { var CLIENT = {

View file

@ -1773,7 +1773,7 @@
e = error1; e = error1;
return console.error(e); return console.error(e);
} }
} else if ((USEROPTS.yt_source !== "OFYT" && data.type == "yt")) { } else if ((USEROPTS.yt_source !== "OFYT" && data.type == "yt") || data.type == "dm") {
data.ofyt = data.id; data.ofyt = data.id;
data.id = data.meta.rawLink;//set link and spoof mov/h264 data.id = data.meta.rawLink;//set link and spoof mov/h264
data.type = "fi"; data.type = "fi";

View file

@ -686,6 +686,7 @@ function queue(pos, src) {
var duration = undefined; var duration = undefined;
var title = undefined; var title = undefined;
var subtitle = ""; var subtitle = "";
var minDuration = 0;
if (data.type === "fi") { if (data.type === "fi") {
if (data.id.match(/^http:/)) { if (data.id.match(/^http:/)) {
Callbacks.queueFail({ Callbacks.queueFail({
@ -716,7 +717,12 @@ function queue(pos, src) {
// Raw files allow title overrides since the ffprobe tag data // Raw files allow title overrides since the ffprobe tag data
// is not always correct. // is not always correct.
title = $("#addfromurl-title-val").val(); title = $("#addfromurl-title-val").val();
subtitle = $("#addfromurl-subtitle-val").val(); subtitle = "";
}else if(data.type === "ia"){
title = $("#addfromurl-title-val").val();
minDuration = $("#addfromurl-duration-val").val();
//Convert minDuration to a number that represents seconds instead of a string which represents minutes
minDuration = (minDuration == "" ? 0 : (Number.parseInt(minDuration) * 60));
} }
if (data.id == null || data.type == null) { if (data.id == null || data.type == null) {
@ -731,6 +737,7 @@ function queue(pos, src) {
pos: pos, pos: pos,
duration: duration, duration: duration,
title: title, title: title,
minDuration: minDuration,
temp: addTemp, temp: addTemp,
link: link, link: link,
subtitle: subtitle subtitle: subtitle
@ -770,15 +777,28 @@ $("#queue_end").click(queue.bind(this, "end", "url"));
$("#ce_queue_next").click(queue.bind(this, "next", "customembed")); $("#ce_queue_next").click(queue.bind(this, "next", "customembed"));
$("#ce_queue_end").click(queue.bind(this, "end", "customembed")); $("#ce_queue_end").click(queue.bind(this, "end", "customembed"));
$("#golive").click(function(ev){
socket.emit('queue', {
id: "https://stream.ourfore.st",
type: 'hl',
pos: "next",
minDuration: 0,
temp: true,
subtitle: ''
});
});
$("#mediaurl").keyup(function(ev) { $("#mediaurl").keyup(function(ev) {
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
queue("end", "url"); queue("end", "url");
} else { } else {
var editTitle = false; var editTitle = false;
var editDur = false;
try { try {
if (parseMediaLink($("#mediaurl").val()).type === "fi") { editTitle = (parseMediaLink($("#mediaurl").val()).type === "fi" || parseMediaLink($("#mediaurl").val()).type === "ia");
editTitle = true;
}
editDur = (parseMediaLink($("#mediaurl").val()).type === "ia");
} catch (error) { } catch (error) {
} }
@ -804,10 +824,11 @@ $("#mediaurl").keyup(function(ev) {
}) })
.appendTo($("#addfromurl-title")).show("blind");//append and show .appendTo($("#addfromurl-title")).show("blind");//append and show
if(editDur){
$("<input/>").addClass("form-control")//create title field $("<input/>").addClass("form-control")//create title field
.attr("type", "text")//the attributes .attr("type", "text")//the attributes
.attr("id", "addfromurl-subtitle-val") .attr("id", "addfromurl-duration-val")
.attr("placeholder", "Alternate Subtitle Track") .attr("placeholder", "Minimum Duration Filter")
.attr("style", "display: none; width: 100%;") .attr("style", "display: none; width: 100%;")
.keydown(function (ev) { .keydown(function (ev) {
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
@ -816,6 +837,8 @@ $("#mediaurl").keyup(function(ev) {
}) })
.appendTo($("#addfromurl-title")).show("blind");//append and show .appendTo($("#addfromurl-title")).show("blind");//append and show
} }
}
} else { } else {
$("#addfromurl-title").hide("blind"); $("#addfromurl-title").hide("blind");
$("#addfromurl-title").remove();//otherwise remove $("#addfromurl-title").remove();//otherwise remove

View file

@ -1615,6 +1615,13 @@ function parseMediaLink(url) {
}; };
} }
if((m = url.match(/archive\.org\/(?:details|download)\/([a-zA-Z0-9_-]+)(?!.|\/)/))){
return{
id: m[1],
type: "ia"
}
}
/* Shorthand URIs */ /* Shorthand URIs */
// So we still trim DailyMotion URLs // So we still trim DailyMotion URLs
if((m = url.match(/^dm:([^\?&#_]+)/))) { if((m = url.match(/^dm:([^\?&#_]+)/))) {
@ -3445,7 +3452,7 @@ function startQueueSpinner(data) {
} }
var id = data.id; var id = data.id;
if (data.type === "yp") { if (data.type === "yp" || data.type === "ia") {
id = "$any"; id = "$any";
} }
@ -3493,6 +3500,7 @@ function stopQueueSpinner(data) {
$("#queueprogress").data("queue-id") === data.id); $("#queueprogress").data("queue-id") === data.id);
shouldRemove = shouldRemove || data === null; shouldRemove = shouldRemove || data === null;
shouldRemove = shouldRemove || $("#queueprogress").data("queue-id") === "$any"; shouldRemove = shouldRemove || $("#queueprogress").data("queue-id") === "$any";
//This is a gross way to fix the issue with IA but it works, and it's not like cytube was a pretty codebase anywho...
if (shouldRemove) { if (shouldRemove) {
$("#queueprogress").remove(); $("#queueprogress").remove();
} }