chat
Furry Chat purringfox 1 year ago 100%

Bookmarklet to automatically create attribution from e621

Hey all. I've created for me a bookmarklet to automate the attribution from e621 posts and I figured I'm gonna share it.

The bookmarklet creates the Markdown syntax for including an image from e621 with the correct artist name and all his pages. It won't generate an output for all artists that are also tagged with "conditional dnp", there you have to manually look if you can post the image or not.

Disclaimer: I'm a random dude from the internet, providing you some code to run on your device. Please be aware that this is an dangerous action, if you don't know what you are doing. Just as an example, discord tokens where stolen with such bookmarklets. But I append also the source code, so you can have a look for yourself. Also, I don't take any responsibility for potential damage my code could do (even though I cannot imagine how this code should harm anything).

Bookmarklet

javascript:(function()%7Basync%20function%20run()%20%7B%0D%0A%20%20let%20location%20%3D%20window.location%3B%0D%0A%20%20try%20%7B%0D%0A%20%20%20%20url%20%3D%20new%20URL(location)%3B%0D%0A%20%20%20%20if%20(url.hostname%20%3D%3D%3D%20%22e621.net%22%20%7C%7C%20url.hostname%20%3D%3D%3D%20%22www.e621.net%22)%20%7B%0D%0A%20%20%20%20%20%20prepareOutput()%3B%0D%0A%20%20%20%20%20%20const%20text%20%3D%20await%20parsePost(url)%3B%0D%0A%20%20%20%20%20%20insertMD(text)%3B%0D%0A%20%20%20%20%7D%20else%20%7B%0D%0A%20%20%20%20%20%20alert(%22This%20script%20only%20works%20with%20e621.%22)%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%20catch%20(error)%20%7B%0D%0A%20%20%20%20alert(%22Error%20while%20parsing%20URL%2C%20see%20console.%22)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0A%2F**%0D%0A%20*%0D%0A%20*%20%40param%20%7BURL%7D%20url%0D%0A%20*%2F%0D%0Aasync%20function%20parsePost(url)%20%7B%0D%0A%20%20%2F%2F%20remove%20search%20query%0D%0A%20%20url.search%20%3D%20%22%22%3B%0D%0A%0D%0A%20%20let%20path%20%3D%20url.pathname.split(%22%2F%22)%3B%0D%0A%0D%0A%20%20if%20(path%5B1%5D%20!%3D%3D%20%22posts%22)%20%7B%0D%0A%20%20%20%20alert(%22This%20script%20can%20only%20handle%20posts.%22)%3B%0D%0A%20%20%20%20return%3B%20%2F%2F%20ends%20script%0D%0A%20%20%7D%0D%0A%0D%0A%20%20const%20image%20%3D%20%7B%0D%0A%20%20%20%20url%3A%20%22%22%2C%0D%0A%20%20%20%20artists%3A%20%5B%5D%2C%0D%0A%20%20%7D%3B%0D%0A%0D%0A%20%20image.url%20%3D%20document.querySelector(%22%23image%22).src%3B%0D%0A%0D%0A%20%20const%20artistTagList%20%3D%20document.querySelectorAll(%22.artist-tag-list%20%3E%20li%22)%3B%0D%0A%0D%0A%20%20for%20(const%20li%20of%20artistTagList)%20%7B%0D%0A%20%20%20%20const%20name%20%3D%20li.querySelector(%22.search-tag%22).text%3B%0D%0A%0D%0A%20%20%20%20%2F%2F%20Aborts%20on%20conditional%20dnp%2C%20comment%20to%20disable%0D%0A%20%20%20%20if%20(name%20%3D%3D%3D%20%22conditional%20dnp%22)%20%7B%0D%0A%20%20%20%20%20%20return%20%22Conditional%20do%20not%20post%20detected.%20Please%20check%20if%20you%20are%20allowed%20to%20share%20this%20post.%20Manual%20work%20required.%22%3B%0D%0A%20%20%20%20%7D%0D%0A%0D%0A%20%20%20%20const%20sources%20%3D%20await%20parseArtist(%0D%0A%20%20%20%20%20%20new%20URL(li.querySelector(%22.wiki-link%22).href)%0D%0A%20%20%20%20)%3B%0D%0A%20%20%20%20image.artists.push(%7B%0D%0A%20%20%20%20%20%20name%3A%20name%2C%0D%0A%20%20%20%20%20%20sources%3A%20sources%2C%0D%0A%20%20%20%20%7D)%3B%0D%0A%20%20%7D%0D%0A%0D%0A%20%20let%20text%20%3D%20%60!%5B%5D(%24%7Bimage.url%7D)%20%20%5Cnby%60%3B%0D%0A%0D%0A%20%20for%20(const%20i%20in%20image.artists)%20%7B%0D%0A%20%20%20%20if%20(i%20%3E%200)%20%7B%0D%0A%20%20%20%20%20%20text%20%2B%3D%20%22%20and%22%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%20%20text%20%2B%3D%20%60%20%24%7Bimage.artists%5Bi%5D.name%7D%60%3B%0D%0A%20%20%20%20for%20(const%20y%20in%20image.artists%5Bi%5D.sources)%20%7B%0D%0A%20%20%20%20%20%20if%20(y%20%3C%201)%20%7B%0D%0A%20%20%20%20%20%20%20%20text%20%2B%3D%20%22%20%22%3B%0D%0A%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%20%20text%20%2B%3D%20%60%5B%5B%24%7BNumber(y)%20%2B%201%7D%5D(%24%7Bimage.artists%5Bi%5D.sources%5By%5D%7D)%5D%60%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%20%20text%20%2B%3D%20%22.%22%3B%0D%0A%0D%0A%20%20return%20text%3B%0D%0A%7D%0D%0A%0D%0A%2F**%0D%0A%20*%0D%0A%20*%20%40param%20%7BURL%7D%20url%0D%0A%20*%2F%0D%0Aasync%20function%20parseArtist(url)%20%7B%0D%0A%20%20%2F%2F%20relative%20url%20has%20to%20be%20fixed%0D%0A%20%20const%20correctURL%20%3D%20new%20URL(url.pathname%2C%20%22https%3A%2F%2Fe621.net%22)%3B%0D%0A%20%20correctURL.search%20%3D%20url.search%3B%0D%0A%20%20const%20response%20%3D%20await%20fetch(correctURL)%3B%0D%0A%20%20const%20html%20%3D%20await%20response.text()%3B%0D%0A%0D%0A%20%20const%20parser%20%3D%20new%20DOMParser()%3B%0D%0A%20%20const%20eDoc%20%3D%20parser.parseFromString(html%2C%20%22text%2Fhtml%22)%3B%0D%0A%0D%0A%20%20const%20res%20%3D%20eDoc.evaluate(%0D%0A%20%20%20%20%22%2F%2F*%5B%40id%3D'c-artists'%5D%2Fdiv%2Fdiv%2Ful%2Fli%5Bstrong%2Ftext()%3D'URLs'%5D%2Ffollowing-sibling%3A%3Aul%2Fli%2Fa%22%2C%0D%0A%20%20%20%20eDoc%0D%0A%20%20)%3B%0D%0A%0D%0A%20%20const%20sources%20%3D%20%5B%5D%3B%0D%0A%0D%0A%20%20let%20n%3B%0D%0A%20%20while%20((n%20%3D%20res.iterateNext()))%20%7B%0D%0A%20%20%20%20sources.push(n.href)%3B%0D%0A%20%20%7D%0D%0A%0D%0A%20%20return%20sources%3B%0D%0A%7D%0D%0A%0D%0Afunction%20prepareOutput()%20%7B%0D%0A%20%20const%20imageContainer%20%3D%20document.querySelector(%22%23image-container%22)%3B%0D%0A%20%20const%20copyDiv%20%3D%20document.createElement(%22div%22)%3B%0D%0A%20%20copyDiv.id%20%3D%20%22to-md%22%3B%0D%0A%20%20copyDiv.classList.add(%22comment%22%2C%20%22comment-post-grid%22)%3B%0D%0A%20%20copyDiv.style.marginBottom%20%3D%20%221em%22%3B%0D%0A%0D%0A%20%20copyDiv.innerHTML%20%3D%20%60%0D%0A%20%20%20%20%3Cdiv%20class%3D%22author-info%22%3E%0D%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22name-rank%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%3Ch4%20class%3D%22author-name%22%3E%3Ca%20href%3D%22https%3A%2F%2Fyiffit.net%2Fu%2Fpurringfox%22%20target%3D%22_blank%22%20class%3D%22user-member%20with-style%22%3EPurringFox%3C%2Fa%3E%3C%2Fh4%3E%0D%0A%20%20%20%20%20%20%20%20%20%20Hackerman%0D%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22avatar%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22post-thumbnail%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Ca%20href%3D%22https%3A%2F%2Fe621.net%2Fposts%2F2294957%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cimg%20class%3D%22post-thumbnail-img%22%20src%3D%22https%3A%2F%2Fstatic1.e621.net%2Fdata%2Fpreview%2F64%2Fa2%2F64a24eba91e5c666cf1ed34833fa86ab.jpg%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fa%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%3Cdiv%20class%3D%22content%22%3E%0D%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22body%20dtext-container%22%3E%0D%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22styled-dtext%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%3Cpre%20id%3D%22md-text%22%3Eloading...%3C%2Fpre%3E%0D%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22content-menu%22%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%3Cmenu%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cli%3E%3Ca%20id%3D%22copy-md-link%22%20href%3D%22%23copy-md%22%20class%3D%22reply-link%20comment-reply-link%22%20style%3D%22visibility%3A%20hidden%3B%22%3ECopy%3C%2Fa%3E%3C%2Fli%3E%0D%0A%20%20%20%20%20%20%20%20%20%20%3C%2Fmenu%3E%0D%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%3C%2Fdiv%3E%0D%0A%20%20%20%20%60%3B%0D%0A%0D%0A%20%20imageContainer.parentElement.insertBefore(copyDiv%2C%20imageContainer)%3B%0D%0A%7D%0D%0A%0D%0Afunction%20insertMD(text)%20%7B%0D%0A%20%20document.querySelector(%22%23md-text%22).innerHTML%20%3D%20text%3B%0D%0A%0D%0A%20%20const%20link%20%3D%20document.querySelector(%22%23copy-md-link%22)%3B%0D%0A%20%20link.style.visibility%20%3D%20%22visible%22%3B%0D%0A%20%20link.addEventListener(%22click%22%2C%20(e)%20%3D%3E%20toClipboard(e%2C%20text))%3B%0D%0A%7D%0D%0A%0D%0A%2F**%0D%0A%20*%0D%0A%20*%20%40param%20%7BEvent%7D%20e%0D%0A%20*%2F%0D%0Afunction%20toClipboard(e%2C%20text)%20%7B%0D%0A%20%20e.preventDefault()%3B%0D%0A%20%20navigator.clipboard.writeText(text)%3B%0D%0A%20%20e.target.style.color%20%3D%20%22var(--color-score-positive)%22%3B%0D%0A%20%20setTimeout(()%20%3D%3E%20%7B%0D%0A%20%20%20%20e.target.style.color%20%3D%20%22%22%3B%0D%0A%20%20%7D%2C%203000)%3B%0D%0A%7D%3Brun()%3B%7D)()%3B

To install just create a new bookmark, name as you want it, and past the above as url. Be sure to copy the whole content. Starts with "javascript" and ends with "%3B".

To use it, just open the e621 post and press the bookmark.

The bookmarklet has to be encode with encodeURIComponent that's why it looks like gibberisch, you can view below the code before it is encoded.

::: spoiler Source code


async function run() {
  let location = window.location;
  try {
    url = new URL(location);
    if (url.hostname === "e621.net" || url.hostname === "www.e621.net") {
      prepareOutput();
      const text = await parsePost(url);
      insertMD(text);
    } else {
      alert("This script only works with e621.");
    }
  } catch (error) {
    alert("Error while parsing URL, see console.");
  }
}

/**
 *
 * @param {URL} url
 */
async function parsePost(url) {
  // remove search query
  url.search = "";

  let path = url.pathname.split("/");

  if (path[1] !== "posts") {
    alert("This script can only handle posts.");
    return; // ends script
  }

  const image = {
    url: "",
    artists: [],
  };

  image.url = document.querySelector("#image").src;

  const artistTagList = document.querySelectorAll(".artist-tag-list > li");

  for (const li of artistTagList) {
    const name = li.querySelector(".search-tag").text;

    // Aborts on conditional dnp, comment to disable
    if (name === "conditional dnp") {
      return "Conditional do not post detected. Please check if you are allowed to share this post. Manual work required.";
    }

    const sources = await parseArtist(
      new URL(li.querySelector(".wiki-link").href)
    );
    image.artists.push({
      name: name,
      sources: sources,
    });
  }

  let text = `![](${image.url})  \nby`;

  for (const i in image.artists) {
    if (i > 0) {
      text += " and";
    }
    text += ` ${image.artists[i].name}`;
    for (const y in image.artists[i].sources) {
      if (y < 1) {
        text += " ";
      }
      text += `[[${Number(y) + 1}](${image.artists[i].sources[y]})]`;
    }
  }
  text += ".";

  return text;
}

/**
 *
 * @param {URL} url
 */
async function parseArtist(url) {
  // relative url has to be fixed
  const correctURL = new URL(url.pathname, "https://e621.net");
  correctURL.search = url.search;
  const response = await fetch(correctURL);
  const html = await response.text();

  const parser = new DOMParser();
  const eDoc = parser.parseFromString(html, "text/html");

  const res = eDoc.evaluate(
    "//*[@id='c-artists']/div/div/ul/li[strong/text()='URLs']/following-sibling::ul/li/a",
    eDoc
  );

  const sources = [];

  let n;
  while ((n = res.iterateNext())) {
    sources.push(n.href);
  }

  return sources;
}

function prepareOutput() {
  const imageContainer = document.querySelector("#image-container");
  const copyDiv = document.createElement("div");
  copyDiv.id = "to-md";
  copyDiv.classList.add("comment", "comment-post-grid");
  copyDiv.style.marginBottom = "1em";

  copyDiv.innerHTML = `
    <div class="author-info">
      <div class="name-rank">
          <h4 class="author-name"><a href="https://yiffit.net/u/purringfox" target="_blank" class="user-member with-style">PurringFox</a></h4>
          Hackerman
        </div>
        <div class="avatar">
          <div class="post-thumbnail">
            <a href="https://e621.net/posts/2294957">
              <img class="post-thumbnail-img" src="https://static1.e621.net/data/preview/64/a2/64a24eba91e5c666cf1ed34833fa86ab.jpg">
            </a>
          </div>
        </div>
    </div>
    <div class="content">
      <div class="body dtext-container">
        <div class="styled-dtext">
          <pre id="md-text">loading...</pre>
        </div>
      </div>
        <div class="content-menu">
          <menu>
              <li><a id="copy-md-link" href="#copy-md" class="reply-link comment-reply-link" style="visibility: hidden;">Copy</a></li>
          </menu>
      </div>
    </div>
    `;

  imageContainer.parentElement.insertBefore(copyDiv, imageContainer);
}

function insertMD(text) {
  document.querySelector("#md-text").innerHTML = text;

  const link = document.querySelector("#copy-md-link");
  link.style.visibility = "visible";
  link.addEventListener("click", (e) => toClipboard(e, text));
}

/**
 *
 * @param {Event} e
 */
function toClipboard(e, text) {
  e.preventDefault();
  navigator.clipboard.writeText(text);
  e.target.style.color = "var(--color-score-positive)";
  setTimeout(() => {
    e.target.style.color = "";
  }, 3000);
}

:::

If you find bugs, you can post them here. I might have a look at them. might.

Post in example video by hecatta [1][2].

Profile picture in example video by redcrystal.

18
7
Comments 7