Skip to content

Instantly share code, notes, and snippets.

@Tarrgon
Last active May 24, 2025 18:23
Show Gist options
  • Select an option

  • Save Tarrgon/ce7dfe6df6f1c73a2c3cac18822c3f10 to your computer and use it in GitHub Desktop.

Select an option

Save Tarrgon/ce7dfe6df6f1c73a2c3cac18822c3f10 to your computer and use it in GitHub Desktop.
Adds buttons to set Walltaker links from e621 directly.
// ==UserScript==
// @name Walltaker Master
// @namespace WalltakerMaster
// @version 1.1.0
// @description Adds buttons to set Walltaker links from e621 directly.
// @author Tarrgon
// @match https://e621.net/posts/*
// @match https://e926.net/posts/*
// @include https://walltaker.joi.how
// @updateURL https://gist.github.com/Tarrgon/ce7dfe6df6f1c73a2c3cac18822c3f10/raw/walltakerMaster.user.js
// @downloadURL https://gist.github.com/Tarrgon/ce7dfe6df6f1c73a2c3cac18822c3f10/raw/walltakerMaster.user.js
// @icon https://www.google.com/s2/favicons?sz=64&domain=e621.net
// @grant GM_cookie
// @grant GM_xmlhttpRequest
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// ==/UserScript==
const BASE_URL = 'https://walltaker.joi.how';
function fetch(url, options) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: options?.method ?? 'GET',
url,
headers: options?.headers ?? {},
data: options.body,
onload: async (res) => {
resolve(res);
},
onerror: reject
});
});
}
function getCookieFromSite(site, cookieName) {
return new Promise((resolve, reject) => {
GM_cookie.list({ url: site, name: cookieName }, function (cookies, error) {
if (!error) resolve(cookies[0]?.value);
else reject(error);
});
});
}
async function getAuthenticityToken(cookie, linkId) {
const res = await fetch(`${BASE_URL}/links/${linkId}`, {
headers: {
Cookie: `_walltaker_session=${cookie}`
}
});
const dom = document.createElement('html');
dom.innerHTML = res.responseText;
const el = dom.querySelector(".set-post-id > input[name='authenticity_token']");
if (!el) return null;
return el.getAttribute('value');
}
async function setLink(linkId, postId) {
const cookie = await getCookieFromSite('https://walltaker.joi.how', '_walltaker_session');
const authenticityToken = await getAuthenticityToken(cookie, linkId);
const data = new URLSearchParams();
data.append('link[post_id]', postId.toString());
data.append('_method', 'PATCH');
data.append('commit', 'Update+Link');
data.append('authenticity_token', authenticityToken);
const res = await fetch(`${BASE_URL}/links/${linkId}`, {
headers: {
Cookie: `_walltaker_session=${cookie}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
method: 'POST',
body: data
});
if (res.status != 200) {
console.error(res);
}
const dom = document.createElement('html');
dom.innerHTML = res.responseText;
const online = dom.querySelector('.link--presence > .online') != null;
return [res.status == 200, online];
}
async function getHistory(id) {
return JSON.parse(await GM.getValue(id.toString(), '[]'));
}
async function addToHistory(id, postId) {
const history = await getHistory(id);
history.push(postId);
if (history.length > 10000) {
history.shift();
}
await GM.setValue(id.toString(), JSON.stringify(history));
}
async function addLink(name, id) {
const links = await getLinks();
links.push({ name, id });
await GM.setValue('links', JSON.stringify(links));
}
async function removeLink(id) {
const links = await getLinks();
await GM.setValue('links', JSON.stringify(links.filter(e => e.id != id)));
}
async function getLinks() {
return JSON.parse(await GM.getValue('links', '[]'));
}
async function setSelectedLinkIndex(index) {
const links = await getLinks();
const linkId = links[index]?.id ?? -1;
await GM.setValue('selectedLink', linkId);
}
async function getSelectedLink() {
return await GM.getValue('selectedLink', -1);
}
async function createOption(value, text) {
const postId = getPostId();
const history = await getHistory(value.toString());
const option = document.createElement('option');
option.value = value;
if (history.includes(postId)) {
option.innerText = `${text} ⚠️`;
option.title = "Has been set before";
} else {
option.innerText = text;
}
return option;
}
function createLinkCreator() {
const option = document.createElement('option');
option.innerText = 'Create Link';
option.addEventListener('click', async (e) => {
e.preventDefault();
e.stopImmediatePropagation();
const name = prompt('Link name:');
if (!name) return;
const id = prompt('Link ID:');
if (!id) return;
await addLink(name, parseInt(id));
setup();
});
return option;
}
async function createSelector() {
const div = document.createElement('div');
const select = document.createElement('select');
select.id = 'walltaker-link-selector';
select.classList.add('button', 'btn-neutral');
select.addEventListener('change', async () => {
await setSelectedLinkIndex(select.selectedIndex);
});
div.appendChild(select);
const links = await getLinks();
for (const link of links) {
select.appendChild(await createOption(link.id, link.name));
}
select.appendChild(createLinkCreator());
const selectedLink = await getSelectedLink();
select.selectedIndex = selectedLink == -1 ? 0 : links.findIndex(link => link.id == selectedLink);
if (selectedLink == -1 && links.length > 0) await setSelectedLinkIndex(0);
return div;
}
async function addSelector() {
const existing = document.getElementById('walltaker-link-selector');
if (existing) existing.parentElement.remove();
const favButtonsGroup = document.querySelector('.fav-buttons');
favButtonsGroup.after(await createSelector());
}
function getPostId() {
const info = document.getElementById('post-information');
return info.children[1].firstElementChild.innerText.split(' ')[1];
}
function addSetButton() {
const existing = document.getElementById('walltaker-set-link-button');
if (existing) existing.parentElement.remove();
const div = document.createElement('div');
const button = document.createElement('button');
button.id = 'walltaker-set-link-button';
button.classList.add('button', 'btn-success');
button.innerText = 'Set Link';
div.appendChild(button);
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopImmediatePropagation();
const selected = await getSelectedLink();
const postId = getPostId();
const [ok, online] = await setLink(selected, postId);
if (ok) {
await addToHistory(selected, postId);
Danbooru.notice(`Successfully set link. ${online ? 'Link is online' : 'Link is offline'}`);
setTimeout(() => {
notice.style.display = "none";
}, 5000)
} else {
Danbooru.error("Error setting link");
setTimeout(() => {
notice.style.display = "none";
}, 5000)
}
});
button.addEventListener('contextmenu', async (e) => {
e.preventDefault();
e.stopImmediatePropagation();
const selected = await getSelectedLink();
if (selected == -1) return;
await removeLink(selected);
await setSelectedLinkIndex(0);
setup();
});
const selector = document.getElementById('walltaker-link-selector');
selector.parentElement.after(div);
}
async function setup() {
await addSelector();
addSetButton();
}
(async function () {
'use strict';
setup();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment