Last active
May 24, 2025 18:23
-
-
Save Tarrgon/ce7dfe6df6f1c73a2c3cac18822c3f10 to your computer and use it in GitHub Desktop.
Adds buttons to set Walltaker links from e621 directly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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