Skip to content

Instantly share code, notes, and snippets.

@Radvylf
Last active May 28, 2022 18:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Radvylf/bdbaedb4c2bd1ba1127ea3251558bec6 to your computer and use it in GitHub Desktop.
Save Radvylf/bdbaedb4c2bd1ba1127ea3251558bec6 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Chat on Chat
// @namespace http://tampermonkey.net/
// @version 1.8.0
// @description It's like chat, but you use a chat program to chat :o
// @author Radvylf Programs
// @match https://chat.stackexchange.com/chatonchat
// @grant none
// ==/UserScript==
(() => {
var chat_on_chat = async () => {
var body = document.body;
while (body.firstChild)
body.removeChild(body.firstChild);
for (var stylsh of document.styleSheets)
stylsh.disabled = !0;
document.title = "Chat-on-Chat";
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible&family=Roboto+Mono&display=swap";
document.head.appendChild(link);
var stylsh = document.createElement("style");
stylsh.innerHTML = (
"body { margin: 0; font-family: 'Atkinson Hyperlegible', sans-serif; font-size: 12px; line-height: 14px; } h2 { margin: 0; margin-top: 6px; font-size: 10px; } .post { max-width: 100%; max-height: 480px; padding: 1px 4px; } " +
"p { margin: 0; } .post h1, .post h2, .post h3, .post h4, .post h5, .post h6 { margin: 0; padding: 6px 0; font-size: 12px; font-weight: bold; } " +
".post code { font-family: 'Roboto Mono', monospace; font-size: 10px; padding: 0 2px; background-color: #f0f0f0; } .gray { opacity: 0.6; } " +
".post a { color: #0000cc; } .post img { max-height: 240px; max-width: 100%; display: block; padding: 6px 0; } " +
".post a:visited { color: #440088; } " +
".post pre { margin: 0; padding: 6px; font-family: 'Roboto Mono', monospace; font-size: 10px; background-color: #f0f0f0; } " +
".post ol, .post ul { padding-left: 24px; margin-block-start: 6px; margin-block-end: 6px; margin-inline-start: 0; margin-inline-end: 0; } " +
".post blockquote { margin: 0; padding: 6px 0; padding-left: 24px; color: #444444; } " +
".post blockquote blockquote blockquote { display: none; } " +
".grow { display: grid; } .grow > * { display: block; } .grow::after { content: attr(data-copy) ' '; white-space: pre-wrap; visibility: hidden; } .grow > textarea { resize: none; overflow: hidden; } " +
"textarea, .grow::after { padding: 6px; border: 1px solid #888888; font-family: 'Roboto Mono', monospace; font-size: 10px; line-height: 12px; word-break: break-all; background-color: inherit; grid-area: 1 / 1 / 2 / 2; } " +
"textarea:focus { border: 1px solid #66b1ff; outline: 1px solid #66b1ff; } textarea::placeholder { color: #888888; } textarea:disabled { color: #666666; } " +
".grow { width: calc(100vw - 24px); position: absolute; left: 12px; bottom: 12px; } .posts { width: calc(100vw - 24px); height: calc(100vh - 56px); padding: 12px; padding-top: 6px; overflow-y: scroll; word-break: break-word; } " +
".thrown { padding: 6px 0; font-weight: bold; text-align: right; display: none; } " +
".post_cont { position: relative; } .post_cont:hover { outline: 1px solid #444444; } .tools { position: absolute; right: 0; bottom: 0; padding: 1px 4px; display: none; font-size: 10px; } .post_cont:hover .tools { display: block; } " +
".tools { user-select: none; cursor: initial; } .tools button { cursor: pointer; background: transparent; border: none; font: inherit; padding: 0; } .tools button:hover { font-weight: bold; }"
);
body.appendChild(stylsh);
var script;
script = document.createElement("script");
script.src = "https://questwriting.org/chatonchat/commonmark.min.js";
await new Promise((r) => {
script.onload = r;
body.appendChild(script);
});
script = document.createElement("script");
script.innerHTML = "MathJax = { tex: { inlineMath: [[\"\\\\$\", \"\\\\$\"]], displayMath: [[\"$$\", \"$$\"]], maxMacros: 24, maxBuffer: 1024 } }";
body.appendChild(script);
await new Promise((r) => window.setTimeout(r, 0));
script = document.createElement("script");
script.id = "MathJax-script";
script.src = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";
await new Promise((r) => {
script.onload = r;
body.appendChild(script);
});
console.log("Scripts ins'd");
var cm_p = new commonmark.Parser();
var cm_r = new commonmark.HtmlRenderer({
safe: true
});
console.log("CommonMark");
var rooms_body = document.createElement("div");
rooms_body.innerHTML = (await (await fetch("https://chat.stackexchange.com/rooms")).text());
var f_copy = rooms_body.querySelector("#fkey").value;
var who_id = rooms_body.querySelector(".topbar-menu-links a").href.match(/\d+/)[0];
var who = rooms_body.querySelector(".topbar-menu-links a").textContent;
var coc_kon = window.localStorage.getItem("coc-konami") == "1";
console.log("F_copy and who");
var room = 136548;
var transcript = new Map();
var chat_to_data = (chat) => {
if (chat.split("").some(c => c.charCodeAt(0) < 0x8000) || chat.length < 2)
return null;
return (chat.split("").map(c => c.charCodeAt(0).toString(2).slice(-14).padStart(14, "0")).join("") + "0000000").match(/.{8}/g).map(b => parseInt(b, 2));
};
var data_to_string = (data) => {
data = data.slice(2);
var string = "";
for (var i = 0; i < data.length; i++) {
if (data[i] == 0) {
continue;
} else if (data[i] < 128) {
string += String.fromCodePoint(data[i]);
} else if (data[i] < 192) {
if (i >= data.length - 1)
return null;
else
string += String.fromCodePoint((data[i] % 64) * 256 + data[++i] + 128);
} else {
if (i >= data.length - 2)
return null;
else if ((data[i] % 32) * 65536 + data[i + 1] * 256 + data[i + 2] + 16512 > 1114111)
return null;
else
string += String.fromCodePoint((data[i] % 32) * 65536 + data[++i] * 256 + data[++i] + 16512);
}
}
return string;
};
var chat_to_string = (chat) => {
var data = chat_to_data(chat);
if (!data || data[0] != 0xcc || data[1] != 0x01)
return null;
return data_to_string(data);
};
var string_to_data = (string) => {
var data = [0xcc, 0x01];
var point;
for (var char of string) {
point = char.codePointAt();
if (point < 128)
data.push(point);
else if (point < 16512)
data.push(128 + ((point - 128) / 256 | 0), (point - 128) % 256);
else
data.push(192 + ((point - 16512) / 65536 | 0), ((point - 16512) / 256 | 0) % 256, (point - 16512) % 256);
}
return data;
};
var data_to_chat = (data) => (data.map(d => d.toString(2).padStart(8, "0")).join("") + "0000000000000").match(/.{14}/g).map(x => String.fromCharCode(parseInt("10" + x, 2))).join("");
var string_to_chat = (string) => data_to_chat(string_to_data(string));
var gray = [];
var posts = document.createElement("div");
posts.className = "posts";
body.appendChild(posts);
var drop_post = async (id, tick = 1) => {
var fin = 0;
var mono, post_cont;
for (mono of [...posts.childNodes]) {
for (post_cont of [...mono.childNodes].slice(1)) {
if ("id" in post_cont.dataset && post_cont.dataset.id == id) {
if (mono.childNodes.length == 2) {
posts.removeChild(mono);
} else {
mono.removeChild(post_cont);
}
fin = 1;
break;
}
}
if (fin)
break;
}
transcript.delete(id);
};
var modify_post = async (id, string, tick = 1) => {
var fin = 0;
var mono, post_cont, imgs = [];
for (var mono of [...posts.childNodes]) {
for (var post_cont of [...mono.childNodes].slice(1)) {
if ("id" in post_cont.dataset && post_cont.dataset.id == id) {
post_cont.firstChild.innerHTML = cm_r.render(cm_p.parse(string));
imgs = post_cont.querySelectorAll("img");
fin = 1;
break;
}
}
if (fin)
break;
}
transcript.set(id, [...transcript.get(id).slice(0, 2), string]);
if (tick) {
await Promise.all([...imgs].map(i => new Promise((r) => i.complete ? r() : (i.onload = r))));
await MathJax.typesetPromise();
}
return imgs;
};
var ctr = 0;
var show_post = async (id, from_id, from, string, tick = 1, count = 0) => {
var p_scroll = (posts.scrollHeight - posts.offsetHeight) - posts.scrollTop;
if (string == null) {
if (transcript.has(id)) {
await drop_post(id, tick);
return [];
}
return [];
}
if (transcript.has(id))
return modify_post(id, string, tick);
if (count && !document.hasFocus())
document.title = "(" + ++ctr + ") Chat-on-Chat";
var n_post_cont = document.createElement("div");
n_post_cont.className = "post_cont";
n_post_cont.dataset.id = id;
var n_post = document.createElement("div");
n_post.className = "post";
n_post.innerHTML = cm_r.render(cm_p.parse(string));
var n_tools = document.createElement("div");
n_tools.className = "tools";
var mod = document.createElement("button");
mod.textContent = (coc_kon ? "Modify" : "Edit");
mod.onclick = () => {
// Modify
};
var drop = document.createElement("button");
drop.onclick = async () => {
var ab_ctrl, ab_st, data;
ab_ctrl = new AbortController();
ab_st = window.setTimeout(() => ab_ctrl.abort(), 8000);
data = await fetch("https://chat.stackexchange.com/messages/" + id + "/delete", {
method: "POST",
body: new URLSearchParams({
fkey: f_copy
}),
signal: ab_ctrl.signal
});
window.clearTimeout(ab_st);
if (data.status == 200 && await data.text() == "ok")
return;
/* cc 02 drop
ab_ctrl = new AbortController();
ab_st = window.setTimeout(() => ab_ctrl.abort(), 8000);
data = await fetch("https://chat.stackexchange.com/messages/" + id + "/delete", {
method: "POST",
body: new URLSearchParams({
fkey: f_copy
}),
signal: ab_ctrl.signal
});
window.clearTimeout(ab_st);*/
};
drop.textContent = (coc_kon ? "Drop" : "Delete");
var quotify = document.createElement("button");
quotify.onclick = () => {
// Quotify
};
quotify.textContent = (coc_kon ? "Quotify" : "Reply");
n_tools.appendChild(mod);
n_tools.appendChild(document.createTextNode(" \xb7 "));
n_tools.appendChild(drop);
n_tools.appendChild(document.createTextNode(" \xb7 "));
n_tools.appendChild(quotify);
n_post_cont.appendChild(n_post);
n_post_cont.appendChild(n_tools);
var imgs = n_post.querySelectorAll("img");
var mono, p_mono, post_cont, fin, s_mono, a_mono, dst, d_post_cont, n_mono, h2;
fin = 0;
for (mono of [...posts.childNodes]) {
for (post_cont of [...mono.childNodes].slice(1)) {
if (!("id" in post_cont.dataset))
continue;
if (+post_cont.dataset.id > id) {
if (mono.dataset.from == from_id) {
mono.insertBefore(n_post_cont, post_cont);
} else {
n_mono = document.createElement("div");
n_mono.className = "mono";
n_mono.dataset.from = from_id;
h2 = document.createElement("h2");
h2.textContent = from;
n_mono.appendChild(h2);
n_mono.appendChild(n_post_cont);
if (mono.childNodes[1] == post_cont) {
if (p_mono && p_mono.dataset.from == from_id) {
p_mono.appendChild(n_post_cont);
} else {
posts.insertBefore(n_mono, mono);
}
} else {
s_mono = document.createElement("div");
a_mono = document.createElement("div");
s_mono.className = "mono";
a_mono.className = "mono";
s_mono.dataset.from = mono.dataset.from;
a_mono.dataset.from = mono.dataset.from;
h2 = document.createElement("h2");
h2.textContent = mono.childNodes[0].textContent;
s_mono.appendChild(h2.cloneNode(true));
a_mono.appendChild(h2.cloneNode(true));
dst = 0;
for (d_post_cont of [...mono.childNodes].slice(1)) {
if (d_post_cont == post_cont)
dst = 1;
(dst == 0 ? s_mono : a_mono).appendChild(d_post_cont);
}
posts.insertBefore(a_mono, mono);
posts.insertBefore(n_mono, a_mono);
posts.insertBefore(s_mono, n_mono);
posts.removeChild(mono);
}
}
fin = 1;
break;
}
}
p_mono = mono;
if (fin)
break;
}
if (!fin) {
if (mono && mono.dataset.from == from_id) {
mono.appendChild(n_post_cont);
} else {
n_mono = document.createElement("div");
n_mono.className = "mono";
n_mono.dataset.from = from_id;
h2 = document.createElement("h2");
h2.textContent = from;
n_mono.appendChild(h2);
n_mono.appendChild(n_post_cont);
posts.appendChild(n_mono);
}
}
transcript.set(id, [from_id, from, string]);
if (tick) {
await Promise.all([...imgs].map(i => new Promise((r) => i.complete ? r() : (i.onload = r))));
await MathJax.typesetPromise();
if (p_scroll < 24)
posts.scrollTo(0, posts.scrollHeight);
}
return n_post.querySelectorAll("img");
};
var show_copy = async (copy, count = 0) => {
var p_scroll = (posts.scrollHeight - posts.offsetHeight) - posts.scrollTop;
var sp = 0;
var imgs = [];
for (var info of copy) {
if (info.event_type == 1 || info.event_type == 2) {
imgs = imgs.concat([...(await show_post(info.message_id, info.user_id, info.user_name, chat_to_string(info.content || ""), 0, count))]);
sp = 1;
} else if (info.event_type == 10) {
await drop_post(info.message_id, 0);
}
}
if (sp) {
await Promise.all(imgs.map(i => new Promise((r) => i.complete ? r() : (i.onload = r))));
await MathJax.typesetPromise();
if (p_scroll < 24)
posts.scrollTo(0, posts.scrollHeight);
}
};
var find_posts = async () => {
var copy = await (await fetch("https://chat.stackexchange.com/chats/" + room + "/events", {
method: "POST",
body: new URLSearchParams("since=0&mode=Messages&msgCount=500&fkey=" + f_copy)
})).json();
await show_copy(copy.events);
};
var post_string = async (string) => {
var data = string_to_data(string);
if (data.length > 832 + 2)
return {
chat: "",
status: 400,
body: "Too long (by " + (data.length - (832 + 2)) + "B)"
};
var chat = data_to_chat(data);
var ab_ctrl = new AbortController();
var ab_st = window.setTimeout(() => ab_ctrl.abort(), 8000);
var data = await fetch("https://chat.stackexchange.com/chats/" + room + "/messages/new", {
method: "POST",
body: new URLSearchParams({
fkey: f_copy,
text: chat
}),
signal: ab_ctrl.signal
});
window.clearTimeout(ab_st);
if (data.status != 200)
return {
chat: chat,
status: data.status,
body: await data.text()
};
return {
chat: chat,
status: data.status,
body: await data.json()
};
};
var start_ws = async () => {
console.log("Starting ws");
try {
var ws_auth = await (await fetch("https://chat.stackexchange.com/ws-auth", {
method: "POST",
body: new URLSearchParams("roomid=" + room + "&fkey=" + f_copy)
})).json();
var hash = await (await fetch("https://chat.stackexchange.com/chats/" + room + "/events", {
method: "POST",
body: new URLSearchParams("since=0&mode=Messages&msgCount=100&fkey=" + f_copy)
})).json();
var ws = new WebSocket(ws_auth.url + "?l=" + hash.time);
var sp = !1;
ws.onopen = () => console.log("Got ws");
ws.onmessage = async (info) => {
var json = JSON.parse(info.data);
if (!json["r" + room] || !json["r" + room].e)
return;
await show_copy(json["r" + room].e, 1);
};
ws.onclose = () => {
setTimeout(start_ws, 2000);
};
} catch (info) {
setTimeout(start_ws, 2000);
}
};
await find_posts();
console.log("Got posts");
start_ws();
window.onfocus = () => {
ctr = 0;
document.title = "Chat-on-Chat";
};
var thrown = document.createElement("div");
var t_int = 0;
var t_cool = 0;
thrown.className = "thrown";
body.appendChild(thrown);
var grow = document.createElement("div");
grow.className = "grow";
var input = document.createElement("textarea");
grow.appendChild(input);
input.rows = 1;
input.oninput = (info = null) => {
grow.dataset.copy = input.value;
var p_scroll = (posts.scrollHeight - posts.offsetHeight) - posts.scrollTop;
if (info && t_int && !t_cool)
t_int--;
if (t_int == 0)
thrown.style.display = "";
var data_ct = string_to_data(input.value).length;
if (t_int == 0 && !t_cool && data_ct > 832 + 2) {
thrown.style.display = "block";
thrown.style.opacity = "";
thrown.textContent = "Too long (by " + (data_ct - (832 + 2)) + "B)";
} else {
thrown.style.opacity = ((t_int / 4) * 100) + "%";
}
posts.style.height = "calc(100vh - " + (30 + input.offsetHeight + (thrown.style.display == "block" ? 14 : 0)) + "px)";
posts.scrollTo(0, (posts.scrollHeight - posts.offsetHeight) - p_scroll);
};
var shift = [...Array(10)].fill(null);
var stat_shift = [...Array(10)].fill(null);
input.onkeydown = async (info) => {
if (info.ctrlKey || info.altKey || info.metaKey || info.shiftKey)
return;
if (info.code == "Enter") {
info.preventDefault();
if (shift.join(":") == "ArrowUp:ArrowUp:ArrowDown:ArrowDown:ArrowLeft:ArrowRight:ArrowLeft:ArrowRight:KeyB:KeyA") {
coc_kon = !coc_kon;
window.localStorage.setItem("coc-konami", coc_kon ? "1" : "0");
for (var tools of posts.querySelectorAll(".tools")) {
tools.childNodes[0].textContent = (coc_kon ? "Modify" : "Edit");
tools.childNodes[2].textContent = (coc_kon ? "Drop" : "Delete");
tools.childNodes[4].textContent = (coc_kon ? "Quotify" : "Reply");
}
[input.value, input.selectionStart, input.selectionEnd] = stat_shift[0];
return;
}
t_int = 0;
input.oninput();
if (string_to_data(input.value).length > 832 + 2)
return;
input.disabled = true;
var post;
try {
post = await post_string(input.value);
} catch (info) {
console.error(info);
post = {
chat: "",
status: 400,
body: "Could not post"
};
}
var si;
if (post.status == 200) {
await show_post(post.body.id, who_id, who, input.value);
input.value = "";
input.disabled = false;
input.focus();
} else if (typeof post.body == "string" && post.body.startsWith("Too long")) {
thrown.style.display = "block";
thrown.textContent = post.body;
t_int = 4;
input.disabled = false;
input.focus();
} else if (typeof post.body == "string" && post.body.match(/\d+/) && +post.body.match(/\d+/)[0] < 30) {
thrown.style.display = "block";
thrown.textContent = "Cooldown: " + post.body.match(/\d+/)[0];
t_int = 4;
t_cool = Date.now() + (+post.body.match(/\d+/)[0] * 1000);
si = window.setInterval(() => {
if (Date.now() > t_cool) {
t_int = 0;
t_cool = 0;
thrown.style.display = "";
input.oninput();
input.disabled = false;
input.focus();
window.clearInterval(si);
return;
}
thrown.textContent = "Cooldown: " + Math.ceil((t_cool - Date.now()) / 1000) + "s";
}, 100);
} else {
thrown.style.display = "block";
thrown.textContent = "Could not post";
t_int = 4;
input.disabled = false;
input.focus();
}
input.oninput();
} else {
shift = [...shift.slice(1), info.code];
stat_shift = [...stat_shift.slice(1), [input.value, input.selectionStart, input.selectionEnd]];
}
};
document.body.appendChild(grow);
input.focus();
console.log("Built body");
};
var script = document.createElement("script");
script.innerHTML = "(" + chat_on_chat.toString() + ")()";
document.body.appendChild(script);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment