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
    1
    ·
    1 year ago

    At first I actually wanted to create an additional button for the editor in create post, to paste the link to, but then I decided against it, since I wanted a more universal approach.

    But now I’m actually interested in writing lemmy UI plugins, maybe I’ll rewrite my code :).

    First I was confused but now I think I get it. You already did something on here, that nsfw stuff is unblurred by default (since it is at least like that for me now), but you would like to make that toggleable. What talks against the “disableblur” and “shownsfw” themes that are selectable. Are these also experiments from you?

    I’ll do some research regarding lemmy ui now.
    It would actually be better, when there already would be a way to extend behavior server side, like modifying the HTML that gets served, instead of doing it ad-hoc on client side (performance). Also if at point there are to many scripts “patched in” they’ll probably start to be incompatible. Example, I append a child node to a element called “x” and another script removes the element “x” for some reason, which would break mine. Let’s see if extensions are planned in lemmy. Else forking the entire UI and expanding on that would also be a (but more time intensive) possibility.

    I’ll come back to it, after I did some more research.

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