diff --git a/README.md b/README.md index 5e76ab4e..a80558bb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -fore.st - Pineapple Express += 2 (v1.1.2) +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 @@ -24,8 +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 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 += 2 1.1.2 Release Notes - - Fixed busted video providers with YT-DLP integration (youtube/dailymotion) +## Pineapple Express += 3 1.1.3 Release Notes + - Add bulk-queueing from Internet Archive ## 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. diff --git a/package-lock.json b/package-lock.json index 8848b04d..d92f05c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fore.st", - "version": "1.1.2", + "version": "1.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "fore.st", - "version": "1.1.2", + "version": "1.1.3", "hasInstallScript": true, "dependencies": { "@calzoneman/jsli": "^2.0.1", diff --git a/package.json b/package.json index 7557966c..3a58e6b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fore.st", - "version": "1.1.2", + "version": "1.1.3", "description": "fore.st: A fork of cytube tailored for the TTN Community", "main": "index.js", "directories": { diff --git a/patchnotes.md b/patchnotes.md index 1beb8470..a08db3e0 100644 --- a/patchnotes.md +++ b/patchnotes.md @@ -1,2 +1,5 @@ -dev goals for 1.1.2 pineapple Express += 2: - --Fix broken video providers (dailymotion/youtube) \ No newline at end of file +dev goals for 1.1.3 pineapple Express += 3: + -- Add bulk-queueing from Internet Archive + - automagically pull most web-compatible version of all videos within a specific upload to IA + - allow queueing admin to filter directory by video length in minutes + - allow for bulk naming \ No newline at end of file diff --git a/src/channel/autobump.js b/src/channel/autobump.js index 2be7a7f3..882a3df2 100644 --- a/src/channel/autobump.js +++ b/src/channel/autobump.js @@ -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. - item.forEach(function(list, i){ - if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){ - loadList("bumps/" + list); - } - }); + if(item != null){ + item.forEach(function(list, i){ + if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){ + loadList("bumps/" + list); + } + }); + } if(cb != null){//lil' nasty but it calls loadconfig after loading lists :P if(callp != null){ diff --git a/src/channel/playlist.js b/src/channel/playlist.js index cd1ec86c..d2fe1ad3 100644 --- a/src/channel/playlist.js +++ b/src/channel/playlist.js @@ -67,6 +67,7 @@ const TYPE_QUEUE = { pos: "string", title: "string,boolean,optional", duration: "number,optional", + minDuration: "number,optional", temp: "boolean,optional", subtitle: "string" }; @@ -445,7 +446,7 @@ PlaylistModule.prototype.handleQueue = function (user, data) { * Specifying a custom title is currently only allowed for custom media * 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; } @@ -466,9 +467,9 @@ PlaylistModule.prototype.handleQueue = function (user, data) { } /* 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", { - msg: "You don't have permission to add playlists", + msg: "You don't have permission to bulk queue", link: link, id: id }); @@ -504,6 +505,11 @@ PlaylistModule.prototype.handleQueue = function (user, data) { duration = !isNaN(data.duration) ? data.duration : undefined; } + var minDuration = undefined; + if (typeof data.minDuration === "number") { + minDuration = !isNaN(data.minDuration) ? data.minDuration : 0; + } + var limit = { burst: 3, sustained: 1 @@ -537,17 +543,20 @@ PlaylistModule.prototype.handleQueue = function (user, data) { type: data.type, pos: data.pos, title: data.title, - subtitle: data.subtitle, + subtitle: data.subtitle, link: link, temp: temp, shouldAddToLibrary: true,//for now chan library will act as a history queueby: queueby, duration: duration, + minDuration: minDuration, maxlength: maxlength }; if (data.type === "yp") { this.queueYouTubePlaylist(user, data); + } else if (data.type === "ia") { + this.queueBulkIA(user, data); } else { this.queueStandard(user, data); } @@ -576,7 +585,38 @@ PlaylistModule.prototype.queueStandard = function (user, data) { lock.release(); 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 }); - 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); } diff --git a/src/get-info.js b/src/get-info.js index 2ce57153..37d750b5 100644 --- a/src/get-info.js +++ b/src/get-info.js @@ -231,7 +231,6 @@ var Getters = { }); - console.log(videos); callback(null, videos); }catch(err){ callback(err.message || err, null); @@ -660,18 +659,99 @@ var Getters = { callback, "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(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 = { Getters: Getters, - getMedia: function (id, type, callback) { - if(type in this.Getters) { - LOGGER.info("Looking up %s:%s", type, id); - lookupCounter.labels(type).inc(1, new Date()); + getMedia: function (id, type, callback, minDuration) { + if (type in this.Getters) { + LOGGER.info("Looking up %s:%s", type, id); + lookupCounter.labels(type).inc(1, new Date()); + if(type == "ia"){ + this.Getters.ia(id, minDuration, callback); + }else{ this.Getters[type](id, callback); + } } else { - callback("Unknown media type '" + type + "'", null); + callback("Unknown media type '" + type + "'", null); } }, getRawCopy: async function (id, cb){ diff --git a/templates/about.pug b/templates/about.pug index 6d342991..c5e7ec9b 100644 --- a/templates/about.pug +++ b/templates/about.pug @@ -56,4 +56,4 @@ block content | No spamming submit channel or chat p. Comments? Questions? Feature requests? DMCA Notices? Email us! - h4 fore.st version: Pineapple Express += 2 (v1.1.2) + h4 fore.st version: Pineapple Express += 3 (v1.1.3) diff --git a/www/js/data.js b/www/js/data.js index ca85bb7b..9dea5cd9 100644 --- a/www/js/data.js +++ b/www/js/data.js @@ -37,7 +37,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var CL_VERSION = "1.1.2"; +var CL_VERSION = "1.1.3"; var GS_VERSION = 1.7; // Google Drive Userscript var CLIENT = { diff --git a/www/js/ui.js b/www/js/ui.js index 5ed78f3b..326a74d3 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -686,6 +686,7 @@ function queue(pos, src) { var duration = undefined; var title = undefined; var subtitle = ""; + var minDuration = 0; if (data.type === "fi") { if (data.id.match(/^http:/)) { Callbacks.queueFail({ @@ -716,7 +717,12 @@ function queue(pos, src) { // Raw files allow title overrides since the ffprobe tag data // is not always correct. title = $("#addfromurl-title-val").val(); - subtitle = $("#addfromurl-subtitle-val").val(); + subtitle = ""; + }else{ + 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) { @@ -731,6 +737,7 @@ function queue(pos, src) { pos: pos, duration: duration, title: title, + minDuration: minDuration, temp: addTemp, link: link, subtitle: subtitle @@ -775,10 +782,12 @@ $("#mediaurl").keyup(function(ev) { queue("end", "url"); } else { var editTitle = false; + var editDur = false; try { - if (parseMediaLink($("#mediaurl").val()).type === "fi") { - editTitle = true; - } + editTitle = (parseMediaLink($("#mediaurl").val()).type === "fi" || parseMediaLink($("#mediaurl").val()).type === "ia"); + + + editDur = (parseMediaLink($("#mediaurl").val()).type === "ia"); } catch (error) { } @@ -795,8 +804,8 @@ $("#mediaurl").keyup(function(ev) { $("").addClass("form-control")//create title field .attr("type", "text")//the attributes .attr("id", "addfromurl-title-val") - .attr("placeholder", "Alternate Title") - .attr("style", "display: none; width: 100%;") + .attr("placeholder", "Alternate Title") + .attr("style", "display: none; width: 100%;") .keydown(function (ev) { if (ev.keyCode === 13) { queue("end", "url"); @@ -804,17 +813,20 @@ $("#mediaurl").keyup(function(ev) { }) .appendTo($("#addfromurl-title")).show("blind");//append and show - $("").addClass("form-control")//create title field - .attr("type", "text")//the attributes - .attr("id", "addfromurl-subtitle-val") - .attr("placeholder", "Alternate Subtitle Track") - .attr("style", "display: none; width: 100%;") - .keydown(function (ev) { - if (ev.keyCode === 13) { - queue("end", "url"); - } - }) - .appendTo($("#addfromurl-title")).show("blind");//append and show + if(editDur){ + $("").addClass("form-control")//create title field + .attr("type", "text")//the attributes + .attr("id", "addfromurl-duration-val") + .attr("placeholder", "Minimum Duration Filter") + .attr("style", "display: none; width: 100%;") + .keydown(function (ev) { + if (ev.keyCode === 13) { + queue("end", "url"); + } + }) + .appendTo($("#addfromurl-title")).show("blind");//append and show + } + } } else { $("#addfromurl-title").hide("blind"); diff --git a/www/js/util.js b/www/js/util.js index 9729bd4d..1a165c5c 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -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 */ // So we still trim DailyMotion URLs if((m = url.match(/^dm:([^\?&#_]+)/))) { @@ -3445,7 +3452,7 @@ function startQueueSpinner(data) { } var id = data.id; - if (data.type === "yp") { + if (data.type === "yp" || data.type === "ia") { id = "$any"; } @@ -3493,7 +3500,8 @@ function stopQueueSpinner(data) { $("#queueprogress").data("queue-id") === data.id); shouldRemove = shouldRemove || data === null; shouldRemove = shouldRemove || $("#queueprogress").data("queue-id") === "$any"; - if (shouldRemove) { + //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) { $("#queueprogress").remove(); } }