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.

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

    Right now the blur is controlled via css. Disableblur is a theme that is set by default and which incorporates the css classes that override the default behavior. The problem is that blur is enabled again when a user chooses a different theme.

    What I was thinking of doing was having a script that would fetch your preference from local storage and then dynamically change CSS. I agree to keep it light, but there’s a lot of potential. Doing things serverside is possible, but I’d have to learn how to compile and dockerize from source, which might be complicated because the site is written in inferno and would also break future updates.

    • 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!