Skip to content

Instantly share code, notes, and snippets.

@jangxx
Last active April 14, 2021 09:01
Show Gist options
  • Save jangxx/b4e6c15b7b5907e2f11fd12d0f464448 to your computer and use it in GitHub Desktop.
Save jangxx/b4e6c15b7b5907e2f11fd12d0f464448 to your computer and use it in GitHub Desktop.
Reddit integration for watch2gether.com. Allows the automatic creation of playlists based on videos posted in a subreddit.
// ==UserScript==
// @name Watch2Gether Reddit Integration
// @namespace https://literalchaos.de
// @version 1.2.1
// @description Add videos from reddit to a watch2gether playlist
// @author jangxx
// @match https://www.watch2gether.com/rooms/*
// @match https://w2g.tv/rooms/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @connect reddit.com
// @require https://userscripts-mirror.org/scripts/source/107941.user.js
// @downloadURL https://gist.github.com/jangxx/b4e6c15b7b5907e2f11fd12d0f464448/raw/watch2gether_reddit_integration.user.js
// @updateURL https://gist.github.com/jangxx/b4e6c15b7b5907e2f11fd12d0f464448/raw/watch2gether_reddit_integration.user.js
// ==/UserScript==
const LOADING_SPINNER_CSS = `
.loader-container {
display: inline-flex;
align-items: center;
}
.loader-o3R47I {
font-size: 10px;
text-indent: -9999em;
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(to right, #555 10%, rgba(0,0,0, 0) 42%);
position: relative;
-webkit-animation: load3 1s infinite linear;
animation: load3 1s infinite linear;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
.loader-o3R47I:before {
width: 50%;
height: 50%;
background: #555;
border-radius: 100% 0 0 0;
position: absolute;
top: 0;
left: 0;
content: '';
}
.loader-o3R47I:after {
background: #fff;
width: 75%;
height: 75%;
border-radius: 50%;
content: '';
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@-webkit-keyframes load3 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load3 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
`;
(function() {
'use strict';
// DOM stuff
const loadingSpinnerStyle = document.createElement("style");
loadingSpinnerStyle.appendChild(document.createTextNode(LOADING_SPINNER_CSS));
document.head.appendChild(loadingSpinnerStyle);
const redditTabElem = document.createElement("div");
redditTabElem.innerHTML = "Reddit";
document.querySelector("#w2g-sidebar-menu").appendChild(redditTabElem);
const redditTabContent = document.createElement("div");
redditTabContent.className = "w2g-menu-tab";
redditTabContent.id = "w2g-us-reddit";
redditTabContent.innerHTML =
`
<form class="ui form" id="w2g-us-redditform">
<div class="field">
<label>Subreddit name</label>
<input placeholder="Enter subreddit name" id="w2g-us-subreddit">
</div>
<div class="field">
<label>Subreddit section</label>
<div class="ui input">
<select class="ui fluid selection dropdown" id="w2g-us-subredditsection">
<option name="hot">hot</option>
<option name="new">new</option>
<option name="rising">rising</option>
<option name="controversial">controversial</option>
<option name="top">top</option>
<option name="gilded">gilded</option>
</select>
</div>
</div>
<div class="field">
<label>Number of posts</label>
<input placeholder="Number of posts to look through" autocomplete="off" id="w2g-us-postcount" value="100" type="number">
</div>
<div class="field">
<label>Upvotes</label>
<input placeholder="Minimum number of upvotes" autocomplete="off" id="w2g-us-minupvotes" value="100" type="number">
</div>
<div class="loader-container">
<button class="ui button" id="w2g-us-addtoplaylist-button" type="submit">Create new playlist</button>
<div id="w2g-us-redditplaylist-loader" class="loader-o3R47I" style="margin-left: 10px; display: none;"></div>
<div id="w2g-us-redditplaylist-status" style="margin-left: 10px; display: none"></div>
</div>
</form>
`;
document.querySelector(".w2g-content-right").appendChild(redditTabContent);
// load saved values if available
if (GM_SuperValue.get("last-subreddit", null) != null) {
redditTabContent.querySelector("#w2g-us-subreddit").value = GM_SuperValue.get("last-subreddit", null);
}
if (GM_SuperValue.get("last-section", null) != null) {
redditTabContent.querySelector("#w2g-us-subredditsection").value = GM_SuperValue.get("last-section", null);
}
if (GM_SuperValue.get("last-postcount", null) != null) {
redditTabContent.querySelector("#w2g-us-postcount").value = GM_SuperValue.get("last-postcount", null);
}
if (GM_SuperValue.get("last-minupvotes", null) != null) {
redditTabContent.querySelector("#w2g-us-minupvotes").value = GM_SuperValue.get("last-minupvotes", null);
}
redditTabElem.addEventListener("click", evt => {
document.querySelector(".w2g-active").classList.remove("w2g-active");
redditTabContent.classList.add("w2g-active");
redditTabElem.classList.add("w2g-active");
});
redditTabContent.querySelector("#w2g-us-redditform").addEventListener("submit", evt => {
evt.preventDefault();
redditTabContent.querySelector("#w2g-us-subreddit").parentNode.classList.remove("error");
redditTabContent.querySelector("#w2g-us-postcount").parentNode.classList.remove("error");
redditTabContent.querySelector("#w2g-us-minupvotes").parentNode.classList.remove("error");
let subreddit = redditTabContent.querySelector("#w2g-us-subreddit").value;
let section = redditTabContent.querySelector("#w2g-us-subredditsection").value;
let postcount = redditTabContent.querySelector("#w2g-us-postcount").value;
let minupvotes = redditTabContent.querySelector("#w2g-us-minupvotes").value;
if (subreddit == "") {
redditTabContent.querySelector("#w2g-us-subreddit").parentNode.classList.add("error");
}
if (postcount == "") {
redditTabContent.querySelector("#w2g-us-postcount").parentNode.classList.add("error");
}
if (minupvotes == "") {
redditTabContent.querySelector("#w2g-us-minupvotes").parentNode.classList.add("error");
}
let date = new Date();
let dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
// store preferences
GM_SuperValue.set("last-subreddit", subreddit);
GM_SuperValue.set("last-section", section);
GM_SuperValue.set("last-postcount", Number(postcount));
GM_SuperValue.set("last-minupvotes", Number(minupvotes));
let playlistName = `/r/${subreddit}/${section} ${dateStr}`;
createPlaylist(playlistName).then(pid => {
redditTabContent.querySelector("#w2g-us-redditplaylist-loader").style.display = "inline-block";
redditTabContent.querySelector("#w2g-us-redditplaylist-status").style.display = "block";
redditTabContent.querySelector("#w2g-us-redditplaylist-status").innerHTML = "Loading videos...";
postMessage({
type: "loadSubredditVideos",
subreddit: subreddit,
post_count: Number(postcount),
min_upvotes: Number(minupvotes),
section: section,
playlist: pid,
});
});
return false;
});
// Userscript functionality
window.addEventListener("message", function(evt) {
var message;
try {
message = JSON.parse(evt.data);
} catch(e) {}
if(!message) return;
if(message.type === undefined) return;
switch (message.type) {
case "loadSubredditVideos":
loadSubredditVideos(message);
break;
case "addVideos":
redditTabContent.querySelector("#w2g-us-redditplaylist-status").innerHTML = "Adding videos to playlist...";
addVideos(message);
break;
}
}, false);
function createPlaylist(full_name) {
let name = full_name.substr(0, 40);
// step 1: create new playlist
return $w2g.postJSON("/rooms/" + w2g.stream + "/playlists/sync_update", { new_list: name } ).then((r) => {
// step 2: retrieve list of all playlists to find out the id of our new playlist
return $w2g.getJSON("/rooms/" + w2g.stream + "/sync_state", "GET");
}).then(resp => {
let state = resp.state;
let playlists = state.find(elem => elem[0] == "playlists");
let items;
try {
items = playlists[1].payload.lists;
} catch(e) {
console.error(e);
alert("Received invalid response");
return;
}
let item = items.find(i => i.title == name);
if (item == undefined) {
alert("Received invalid response");
return;
}
return item.key;
});
}
function addVideos(message) {
let entries = message.data;
let count = message.post_count;
let playlist = message.playlist;
let videolist = [];
for(let i = 0; i < count; i++) {
let entry = entries[i].data;
let media, media_type;
try {
media_type = entry.secure_media.type;
media = entry.secure_media.oembed;
} catch(e) {
// skip video without media info
continue;
}
if(media_type == "youtube.com" && entry.score >= message.min_upvotes) {
console.log("Add video:", entry.url, "(" + entry.score + ")");
try {
videolist.push({
title: decodeHtml(media.title) + ` (${entry.score})`,
url: entry.url,
thumb: media.thumbnail_url,
});
} catch(e) {}
}
}
addVideolist(playlist, videolist).finally(() => {
document.querySelector("#w2g-us-redditplaylist-loader").style.display = "none";
document.querySelector("#w2g-us-redditplaylist-status").style.display = "none";
});
}
async function addVideolist(playlist_id, videolist) {
// add videos to the playlist in chunks of 25
for(let i = 0; i < videolist.length/25; i++) {
// call function provided by the website to add filtered videos
await $w2g.postJSON("/rooms/" + w2g.stream + "/playlists/" + playlist_id + "/playlist_items/sync_update", { add_items: JSON.stringify( videolist.slice(25*i, 25*i+25) ) } );
}
}
// https://stackoverflow.com/a/42182294/1342618
function decodeHtml(html) {
let txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
function postMessage(messageObj) {
try {
let message = JSON.stringify(messageObj);
window.postMessage(message, "*");
} catch(e) {
alert("Error while sending message");
}
}
function loadSubredditVideos(message) {
let subreddit = message.subreddit;
let section = message.section;
let post_count = message.post_count;
let min_upvotes = message.min_upvotes;
let playlist = message.playlist;
requestMoreVideos(subreddit + "/" + section, post_count, min_upvotes, playlist);
}
function requestMoreVideos(subreddit, count, min_upvotes, playlist, videos, after) {
if(videos === undefined) videos = [];
after = (after === undefined) ? "" : "?after=" + after;
GM_xmlhttpRequest({
url: "https://www.reddit.com/r/" + subreddit + ".json" + after,
method: "GET",
headers: {
"User-Agent": "watch2gether userscript Loader"
},
ignoreCache: true,
responseType: "json",
onload: function(resp) {
if(resp.status != 200) return;
videos = videos.concat(resp.response.data.children);
redditTabContent.querySelector("#w2g-us-redditplaylist-status").innerHTML = `Loading videos (${videos.length})`;
if(videos.length >= count) {
postMessage({type: "addVideos", data: videos, min_upvotes: min_upvotes, post_count: count, playlist: playlist});
} else {
requestMoreVideos(subreddit, count, min_upvotes, playlist, videos, resp.response.data.after);
}
},
onerror: function() {
document.querySelector("#w2g-us-redditplaylist-loader").style.display = "none";
document.querySelector("#w2g-us-redditplaylist-status").style.display = "none";
}
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment