Skip to content

Instantly share code, notes, and snippets.

@lleaff
Last active September 9, 2021 22:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lleaff/8514033dc8e54ce02d6adf3c2e46d8ff to your computer and use it in GitHub Desktop.
Save lleaff/8514033dc8e54ce02d6adf3c2e46d8ff to your computer and use it in GitHub Desktop.
Adds a "open in popup" button to channels
// ==UserScript==
// @name Discord - Open chat in popup
// @namespace lleaff
// @updateURL https://gist.github.com/lleaff/8514033dc8e54ce02d6adf3c2e46d8ff/raw/Discord_-_Open_chat_in_popup.user.js
// @supportURL https://gist.github.com/lleaff/8514033dc8e54ce02d6adf3c2e46d8ff#comments
// @match https://discordapp.com/channels/*
// @version 1
// @run-at document-end
// @grant none
// @noframes
// ==/UserScript==
/* =Configuration
*------------------------------------------------------------*/
const POPUP_WIDTH /*: pixels */ = 400;
const WAIT_FOR_LOAD_TRY_INTERVAL /*: milliseconds */ = 0.5e3;
/* =DOM Utilities
*------------------------------------------------------------*/
const $ = (selector, el) => Array.from((el || document).querySelectorAll(selector));
function hideAllSiblings(el) {
const siblings = getSiblings(el);
siblings.forEach(el => el.style.display = 'none');
}
function getSiblings(el) {
const all = Array.from(el.parentNode.childNodes);
return all.filter(child => child !== el);
}
/**
* Open and return a popup window. Ususally blocked by browser by default.
* @param options - https://developer.mozilla.org/en-US/docs/Web/API/Window/open#Position_and_size_features
*/
function openPopup(url, name, options) {
const opts = Object.assign({
menubar: false,
location: false,
resizable: true,
scrollbars: false,
status: false
}, options);
const convertOptVal = val => {
switch(val) {
case false: return 'no';
case true: return 'yes';
default: return val;
}
}
const optionsStr = Object.keys(opts)
.map(key =>`${key}=${convertOptVal(opts[key])}`, '')
.join(',');
return window.open(url,
name || `popup:${encodeURI(url)}`,
optionsStr);
}
/* =Main window
*------------------------------------------------------------*/
/**
* Main function to be executed on main page
*/
function mainBootstrap() {
const contacts = $('.channel:not(.btn-friends)');
if (!contacts.length) {
return false;
}
contacts.forEach(contact => {
addOpenPopupBtn(contact);
});
}
/* =Popup button
*------------------------------------------------------------*/
function addOpenPopupBtn(el) {
const btn = createOpenPopupBtnDOM();
btn.addEventListener('click',
(e) => { openChatPopup(el); e.preventDefault(); });
const refButton = $('.close', el)[0] || $('.icon-instant-invite', el)[0];
refButton.parentNode.insertBefore(btn, refButton);
}
function createOpenPopupBtnDOM() {
let btn = document.createElement('button');
btn.className = 'close';
btn.style = `
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEGSURBVCjPZZChS0NxFIW/9zZQ1Dnf3IYwu2AUu3+BYLOJgsWkWdAyMAiCYclgFy3+EYIGq6xoGiLIwoTN4dw+w5Pxe3puu+e73MNBglmxZaiRz2SA2G3bGWSIGFm0YsWqJWN37YVEnhJ7bFAA+pxxTY08oWw48N2mTesmHvuZeSF2bLlm2bIz/21FfXBWxH37gfOVZokBEZhknYnx5wHnHNAG1HsLItYdjq9PnTJ2x9cQmPPC71973k0Tc66GACaeeOehRY98tCqSBTDntJFln2xaEckDUVDLkC4QEWO6yNNjgSWaATZimTIvfKXAFVvc8Japd5GEWzpp1TUv7f6p78OGpTTVD4gGJTUFNt2qAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTEwLTIxVDA5OjQ0OjIzLTA1OjAwu1bogAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0xMC0yMVQwOTo0NDoyMy0wNTowMMoLUDwAAAAASUVORK5CYII=');
width: 16px;
height: 16px;
content: ' ';
-webkit-background-size: 10px;
-moz-background-size: 10px;
background-size: 10px;
margin-right: 1px;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
`;
return btn;
}
function openChatPopup(contactDiv) {
const linkEl = $('a', contactDiv)[0];
if (!linkEl) { return false; }
const href = linkEl.href;
const params = (/\?/.test(href) ? '&' : '?') + 'chat-only';
const url = href + params;
const popup = openPopup(url, 'popup', { width: POPUP_WIDTH });
return popup;
}
/* =Popup
*------------------------------------------------------------*/
/**
* Main function to be executed on popup page
*/
function mainPopup() {
if (!hideAllButChat()) {
return false;
}
addGlobalStyle(`
/* Make inline-images adapt to container */
.message-group .image {
max-width: 100%!important;
height: auto !important;
}
`);
}
function hideAllButChat() {
const chat = $('.chat')[0];
if (!chat) { return false; }
hideAllSiblings(chat);
return true;
}
function addGlobalStyle(styleStr) {
let styleEl = document.createElement('style');
styleEl.innerHTML = styleStr;
document.head.appendChild(styleEl);
}
/* =Script running
*------------------------------------------------------------*/
/**
* Try running a function until it returns something other than `null` or `false`.
*/
function tryRunFunc(fn, interval) {
if ([null, false].includes(fn())) {
setTimeout(() => tryRunFunc(fn, interval), interval)
}
}
function isInPopup() {
return /[?&]chat-only/.test(location.search);
}
function main() {
const mainFunc = isInPopup() ? mainPopup : mainBootstrap;
tryRunFunc(mainFunc, WAIT_FOR_LOAD_TRY_INTERVAL);
}
/* =Execution
*------------------------------------------------------------*/
main();
@20kdc
Copy link

20kdc commented Aug 18, 2018

I think recent Discord CSS changes have broken this.
To save you some time, here's some helper code:

// I release this user-script into the public domain.

// Since iterating through the entire DOM would be performance suicide,
//  let's try to detect classes in ANY OTHER WAY.
var dragonequus;
dragonequus = {
    version: 2,
    getAllClassesLen: 0,
    getAllClassesCache: [],
    getAllClasses: function () {
        var sheets = document.styleSheets;
        if (sheets.length == dragonequus.getAllClassesLen) {
            return dragonequus.getAllClassesCache;
        }
        var workspace = [];
        var seen = {};
        for (var k = 0; k < sheets.length; k++) {
            var sheet = sheets[k];
            for (var k2 = 0; k2 < sheet.cssRules.length; k2++) {
                var rule = sheet.cssRules[k2];
                if (rule.type == CSSRule.STYLE_RULE) {
                    // .A:I .B:I, .A .B
                    var majors = rule.selectorText.split(",");
                    for (var k3 = 0; k3 < majors.length; k3++) {
                        var minors = majors[k3].split(" ");
                        for (var k4 = 0; k4 < minors.length; k4++) {
                            // Minor starts off as say .A:B
                            var minor = minors[k4];
                            // Must be class
                            if (!minor.startsWith("."))
                                continue;
                            // Cut off any : and remove .
                            var selectorBreak = minor.indexOf(":");
                            if (selectorBreak != -1) {
                                minor = minor.substring(1, selectorBreak);
                            } else {
                                minor = minor.substring(1);
                            }
                            if (seen[minor])
                                continue;
                            seen[minor] = true;
                            workspace.push(minor);
                        }
                    }
                }
            }
        }
        dragonequus.getAllClassesLen = sheets.length;
        dragonequus.getAllClassesCache = workspace;
        return workspace;
    },
    isValidDC: function (obfuscated, real) {
        if (!obfuscated.startsWith(real + "-"))
            return false;
        if (obfuscated.length != real.length + 7)
            return false;
        return true;
    },
    findByDiscordClass: function (name) {
        var q = document.querySelector("." + name);
        if (q)
            return q;
        var classes = dragonequus.getAllClasses();
        for (var k in classes) {
            var n = classes[k];
            if (dragonequus.isValidDC(n, name)) {
                q = document.querySelector("." + n);
                if (q)
                    return q;
            }
        }
        return null;
    },
    toDiscordClass: function (name) {
        var classes = dragonequus.getAllClasses();
        for (var k in classes) {
            var n = classes[k];
            if (dragonequus.isValidDC(n, name))
                return n;
        }
        return name;
    }
};

@20kdc
Copy link

20kdc commented Aug 19, 2018

Ok, so apparently I'm incredibly dumb and didn't notice that the popup thing appeared on DMs.
Please just ignore the previous message.
(EDIT: Nevermind, it still doesn't actually successfully strip the popup of the non-chatlog parts. Bug report still stands.)

@evanricard
Copy link

hey @lleaf, does this script still work? Thanks!

@lleaf
Copy link

lleaf commented May 18, 2020

hey @lleaf, does this script still work? Thanks!

I don't know, I'm not @lleaff ;)

@Dampfie93
Copy link

Hello lleaff,

I found your beautiful work for a Discord script to popout the call and wanted to ask you to help me on a project.

I try to screen capture every screen and camera from discord for streaming. Therefor i need a high res window oft the content, like the popout call from discord.
Problem is, that only one Screen can be displayed in full window while the others remain hidden in the background. Ort they could share the window in tiles, but wouldn’t be displayed in a high resolution.

I tryed to click the popout button twice but got as a result a hard restart oft he discord client.

I saw that you digged into the undefined js code of discord and wanted to ask you if you can help me to solve this problem.

Thank you for your attention

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment