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.

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.

  • PurringFox@yiffit.netOP
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 year ago

    I’m researching right now. Regarding the blurring there is already a pull request, that implements the exact feature you would like to do (if I understand you correctly) https://github.com/LemmyNet/lemmy-ui/pull/1640.

    I agree, maintaining a fork of lemmy-ui and rebuilding it every change is not feasible. Sadly it looks like there are not much more possibilities of extending the ui than the custom header and custom theme css.

    So I think the header scripts it is… But one thing I think would be nice is, that these scripts can be enabled/disabled by the users individually and should be off by default. So that people not interested in that are not impacted by possible performance issues or bugs. Nice would be if it would be possible to create an additional settings tab, where all these extra scripts are toggleable.

    Let’s see, if I have time I’ll setup a dev-environment to experiment with stuff.

    • Wander@yiffit.netM
      link
      fedilink
      English
      arrow-up
      1
      ·
      1 year ago

      Thank you <3 There’s no rush, although I’m pretty excited about the potential!