Bitcoin Forum
June 05, 2026, 07:18:45 PM *
News: Latest Bitcoin Core release: 31.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: « 1 [2]  All
  Print  
Author Topic: [Userscript] Bitcointalk Merit Automation: Queue & Schedule Merit Distribution  (Read 688 times)
vapourminer
Legendary
*
Offline

Activity: 5054
Merit: 6268


what is this "brake pedal" you speak of?


View Profile
June 02, 2026, 12:37:22 PM
Last edit: June 02, 2026, 01:00:57 PM by vapourminer
 #21

used the no_post_trigger for 8:30 AM

mostly ran ok but tossed this during the run, and its stuck in the queuelist

EDIT ok a couple minutes later my queue is now empty and loycev got merited twice on the same post a few minutes apart

its likely just me getting click happy

edit2:  yes i think it was because i was in the no_post_trigger queue list editing the time live.. i think i crossed the post threshold time once or twice into the past/future with my messing with the trigger times..


vapourminer
Legendary
*
Offline

Activity: 5054
Merit: 6268


what is this "brake pedal" you speak of?


View Profile
June 02, 2026, 01:49:48 PM
Last edit: June 02, 2026, 09:47:25 PM by Mr. Big
Merited by promise444c5 (1)
 #22

new request: is it possible make the queue scrollable as when i have to many the last entries are not visible and the save button is off the bottom of the screen and unclickable. until i shrink the page (cia CTRL -) till it fits i can then see it the whole list and button.


regular size last few are cut off, no save button



vs


shrunken size to see full list and save button





can this be done?

or how about just reverse the list and add the latest to the top?

what happens is i qmerit a post that is the default one merit but if i want to change it to another value right then that last one is not visible.. so i shrink the page, do my thing, expand it back.. no big deal. but maybe change the list to most recent at the top and put the save button at the top also. then it doesnt matter as much.

no matter what, i can still do everything i want. this script rocks!!



haha it double fired again

i think i trigger the double by editing a post right after posting maybe?

using post_trigger
Catenaccio
Sr. Member
****
Offline

Activity: 1148
Merit: 345



View Profile
June 02, 2026, 04:07:20 PM
 #23

haha it double fired again

i think i trigger the double by editing a post right after posting maybe?

using post_trigger
What you mean with Editing a post right after posting?

This userscript is for merit automatic distribution, and it's not about your posts at all. I don't use it but I am very confusing when reading your post about it?
Or did you mean about changes you make in selection and when you edit it, the bot meriting posts a second time?

R


▀▀▀▀▀▀▀██████▄▄
████████████████
▀▀▀▀█████▀▀▀█████
████████▌███▐████
▄▄▄▄█████▄▄▄█████
████████████████
▄▄▄▄▄▄▄██████▀▀
LLBIT|
4,000+ GAMES
███████████████████
██████████▀▄▀▀▀████
████████▀▄▀██░░░███
██████▀▄███▄▀█▄▄▄██
███▀▀▀▀▀▀█▀▀▀▀▀▀███
██░░░░░░░░█░░░░░░██
██▄░░░░░░░█░░░░░▄██
███▄░░░░▄█▄▄▄▄▄████
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
█████████
▀████████
░░▀██████
░░░░▀████
░░░░░░███
▄░░░░░███
▀█▄▄▄████
░░▀▀█████
▀▀▀▀▀▀▀▀▀
█████████
░░░▀▀████
██▄▄▀░███
█░░█▄░░██
░████▀▀██
█░░█▀░░██
██▀▀▄░███
░░░▄▄████
▀▀▀▀▀▀▀▀▀
|||
▄▄████▄▄
▀█▀
▄▀▀▄▀█▀
▄░░▄█░██░█▄░░▄
█░▄█░▀█▄▄█▀░█▄░█
▀▄░███▄▄▄▄███░▄▀
▀▀█░░░▄▄▄▄░░░█▀▀
░░██████░░█
█░░░░▀▀░░░░█
▀▄▀▄▀▄▀▄▀▄
▄░█████▀▀█████░▄
▄███████░██░███████▄
▀▀██████▄▄██████▀▀
▀▀████████▀▀
.
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
░▀▄░▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄░▄▀
███▀▄▀█████████████████▀▄▀
█████▀▄░▄▄▄▄▄███░▄▄▄▄▄▄▀
███████▀▄▀██████░█▄▄▄▄▄▄▄▄
█████████▀▄▄░███▄▄▄▄▄▄░▄▀
███████████░███████▀▄▀
███████████░██▀▄▄▄▄▀
███████████░▀▄▀
████████████▄▀
███████████
▄▄███████▄▄
▄████▀▀▀▀▀▀▀████▄
▄███▀▄▄███████▄▄▀███▄
▄██▀▄█▀▀▀█████▀▀▀█▄▀██▄
▄██▀▄███░░░▀████░███▄▀██▄
███░████░░░░░▀██░████░███
███░████░█▄░░░░▀░████░███
███░████░███▄░░░░████░███
▀██▄▀███░█████▄░░███▀▄██▀
▀██▄▀█▄▄▄██████▄██▀▄██▀
▀███▄▀▀███████▀▀▄███▀
▀████▄▄▄▄▄▄▄████▀
▀▀███████▀▀
OFFICIAL PARTNERSHIP
SOUTHAMPTON FC
FAZE CLAN
SSC NAPOLI
vapourminer
Legendary
*
Offline

Activity: 5054
Merit: 6268


what is this "brake pedal" you speak of?


View Profile
June 02, 2026, 04:48:55 PM
Merited by Catenaccio (1)
 #24

haha it double fired again

i think i trigger the double by editing a post right after posting maybe?

using post_trigger
What you mean with Editing a post right after posting?

This userscript is for merit automatic distribution, and it's not about your posts at all. I don't use it but I am very confusing when reading your post about it?


it is about my posts. the post is the trigger. the script uploads the accumulated merits when i post. it should only do that once. it seems i can trick the script into doing that twice by rapidly editing the post that triggered the merits to upload in the 1st place.

my theory anyway.
promise444c5 (OP)
Hero Member
*****
Offline

Activity: 1036
Merit: 907


All things are numbers


View Profile WWW
June 03, 2026, 10:57:30 AM
Last edit: June 03, 2026, 11:11:56 AM by promise444c5
Merited by vapourminer (2)
 #25

~snip ~
edit2:  yes i think it was because i was in the no_post_trigger queue list editing the time live.. i think i crossed the post threshold time once or twice into the past/future with my messing with the trigger times..


Can you possibly reproduce the error  while your browser console is open.. check the error message if it occurs again[ like the  error message before the second merit for LoyceV went through].

new request: is it possible make the queue scrollable as when i have to many the last entries are not visible and the save button is off the bottom of the screen and unclickable. until i shrink the page (cia CTRL -) till it fits i can then see it the whole list and button.

regular size last few are cut off, no save button



vs


shrunken size to see full list and save button



can this be done?

Yeah overflow with a max-height .. I haven’t really touched the styling part, I will fix that today.


Quote
or how about just reverse the list and add the latest to the top?
Sure.. I will make it descending (I.e latest comes firs)
Quote

what happens is i qmerit a post that is the default one merit but if i want to change it to another value right then that last one is not visible.. so i shrink the page, do my thing, expand it back.. no big deal. but maybe change the list to most recent at the top and put the save button at the top also. then it doesnt matter as much.

no matter what, i can still do everything i want. this script rocks!!
Thanks..

Longer list will eventually get too small in size with zoom out if it doesn’t reach the browser limit .. the fix is a necessary one.


Quote
haha it double fired again

i think i trigger the double by editing a post right after posting maybe?

using post_trigger
Like how many minutes apart this time around.. I’m currently investigating your merit history and post. I should be able to fix it today too.

haha it double fired again

i think i trigger the double by editing a post right after posting maybe?

using post_trigger
What you mean with Editing a post right after posting?

This userscript is for merit automatic distribution, and it's not about your posts at all. I don't use it but I am very confusing when reading your post about it?


it is about my posts. the post is the trigger. the script uploads the accumulated merits when i post. it should only do that once. it seems i can trick the script into doing that twice by rapidly editing the post that triggered the merits to upload in the 1st place.

my theory anyway.
I’ve noted everything, I will look into it today..

███████████████████████████
███████▄████████████▄██████
████████▄████████▄████████
███▀█████▀▄███▄▀█████▀███
█████▀█▀▄██▀▀▀██▄▀█▀█████
███████▄███████████▄███████
███████████████████████████
███████▀███████████▀███████
████▄██▄▀██▄▄▄██▀▄██▄████
████▄████▄▀███▀▄████▄████
██▄███▀▀█▀██████▀█▀███▄███
██▀█▀████████████████▀█▀███
███████████████████████████
.
.Duelbits PREDICT..
█████████████████████████
█████████████████████████
███████████▀▀░░░░▀▀██████
██████████░░▄████▄░░████
█████████░░████████░░████
█████████░░████████░░████
█████████▄▀██████▀▄████
████████▀▀░░░▀▀▀▀░░▄█████
██████▀░░░░██▄▄▄▄████████
████▀░░░░▄███████████████
█████▄▄█████████████████
█████████████████████████
█████████████████████████
.
.WHERE EVERYTHING IS A MARKET..
█████
██
██







██
██
██████
Will Bitcoin hit $200,000
before January 1st 2027?

    No @1.15         Yes @6.00    
█████
██
██







██
██
██████

  CHECK MORE > 
vapourminer
Legendary
*
Offline

Activity: 5054
Merit: 6268


what is this "brake pedal" you speak of?


View Profile
June 03, 2026, 04:15:04 PM
Last edit: June 03, 2026, 06:43:01 PM by vapourminer
Merited by promise444c5 (2)
 #26

lol



==============================

new error




looks like i can trigger it by editing the post immediately after posting. if the script is still running during the edit it wipes out?

so maybe


post reply
scripts starts running
i edit the post quick and repost
poof i beat the script before it ends
TADA!

oh and it sent merits twice again.

ima gonna need to calm down and wait a couple minutes before any edits lol
promise444c5 (OP)
Hero Member
*****
Offline

Activity: 1036
Merit: 907


All things are numbers


View Profile WWW
June 03, 2026, 07:13:14 PM
Last edit: June 04, 2026, 12:30:35 AM by promise444c5
Merited by vapourminer (2)
 #27

lol



 I knew someone will try that  Grin

Quote
new error




looks like i can trigger it by editing the post immediately after posting. if the script is still running during the edit it wipes out?

so maybe


post reply
scripts starts running
i edit the post quick and repost
poof i beat the script before it ends
TADA!

oh and it sent merits twice again.

ima gonna need to calm down and wait a couple minutes before any edits lol
okay.. currently, it's around half of the post you merited that received the double-send merits ..Did any other post failed among the ones you iqueue initially?
there's something happening when you hit that "save".. i added some changes but i will have to take a look on the "edit" page specifically.

here's the requested changes on both and some other changes.. it will be updated on the first OP pot as well:


No_Post_Trigger : [edited- posted the old at first]
Code:
// ==UserScript==
// @name         Bitcointalk Merit Automation [No_Post_Trigger]
// @namespace    http://...
// @version      1.0
// @description  Queue posts to merit them later with precise time execution.. User has to login for scrpt to load.
// @author       promise444c5
// @match        https://bitcointalk.org/index.php?topic=*
// @noframes
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(() => {
  "use strict";

  const getLocalISOTime = (dateObj) => {
    const offset = dateObj.getTimezoneOffset() * 60000;
    return new Date(dateObj - offset).toISOString().slice(0, 16);
  };

  GM_addStyle(`
        /* Modal Overlay */
        #merit-queue-modal { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(3px); }

        /* Modal */
        #merit-queue-content {
            background-color: #ffffff; margin: 4% auto; padding: 24px; border-radius: 12px;
            width: 85%; max-width: 850px; max-height: 85vh; /* Keep within viewport */
            display: flex; flex-direction: column; /* Allows nested list to fill space & scroll */
            box-shadow: 0 10px 40px rgba(0,0,0,0.3); color: #333; font-family: system-ui, -apple-system, sans-serif;
            box-sizing: border-box; max-height: 80vh; overflow-y: auto; to #merit-queue-content
        }

        /* Header & Close */
        .mq-close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; transition: color 0.2s; line-height: 1; }
        .mq-close:hover { color: #e74c3c; }
        #merit-queue-content h2 { margin-top: 0; margin-bottom: 5px; border-bottom: 2px solid #f0f0f0; padding-bottom: 12px; font-size: 20px; color: #2c3e50;}

        /* Scrolling List */
        #mq-list {
            flex-grow: 1; overflow-y: auto; overflow-x: hidden;
            padding-right: 12px; margin: 15px 0;
            display: flex; flex-direction: column; gap: 10px;
        }

        /* Smooth Scrollbar for mq-list */
        #mq-list::-webkit-scrollbar { width: 8px; }
        #mq-list::-webkit-scrollbar-track { background: #f8f9fa; border-radius: 4px; }
        #mq-list::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
        #mq-list::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }

        /* Queue Item Cards */
        .mq-item {
            display: flex; align-items: center; justify-content: space-between;
            background: #f8f9fc; padding: 16px; border-radius: 8px; border: 1px solid #eaedf1;
            transition: all 0.2s ease-in-out;
            flex-wrap: wrap; gap: 10px;
        }
        .mq-item > div:nth-child(2) {
            display: flex;
            align-items: center;
            gap: 15px;
            flex-wrap: wrap;

         }
        .mq-item:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(0,0,0,0.05); border-color: #d1d5db; flex:wrap }

        /* Inputs & Form Elements */
        .mq-amount, .mq-date { padding: 6px 10px; border: 0.5px solid #cbd5e1; border-radius: 6px; font-family: inherit; font-size: 13px; color: #333; outline: none; transition: border-color 0.2s; }
        .mq-amount:focus, .mq-date:focus { border-color: #3b82f6; }

        /* Action Buttons */
        .mq-remove {
            background: #fee2e2; color: #ef4444; border: none; border-radius: 50%;
            width: 32px; height: 32px; font-weight: bold; cursor: pointer;
            display: flex; align-items: center; justify-content: center; transition: all 0.2s; margin-left: 20px;
            margin: 0 !important;
            flex-shrink: 0;
        }
        .mq-remove:hover { background: #fca5a5; color: #7f1d1d; transform: scale(1.05); }

        #mq-save-container { text-align: center; margin-top: auto; padding-top: 15px; border-top: 2px solid #f0f0f0; }
        #mq-save-btn {
            padding: 12px 35px; font-size: 15px; font-weight: 600; cursor: pointer;
            background: #047857; color: #fff;
            border: none; border-radius: 6px; box-shadow: 0 4px 10px rgba(16, 185, 129, 0.3); transition: all 0.2s;
        }
        #mq-save-btn:hover { background: #059669; box-shadow: 0 6px 14px rgba(16, 185, 129, 0.4); translateY(-1px); }
        #mq-save-btn:active { transform: translateY(2px); box-shadow: 0 2px 5px rgba(16, 185, 129, 0.3); }

        #open-mq-btn { position: fixed; bottom: 25px; right: 25px; padding: 12px 20px; font-weight: 600; background: #375f82; color: white; border: none; border-radius: 50px; cursor: pointer; z-index: 9998; box-shadow: 0 6px 12px rgba(59, 130, 246, 0.3); transition: transform 0.2s; }
        #open-mq-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(59, 130, 246, 0.4); opacity:0.95; background:#3e6488 }`);

  /* Loads active queue from storage on script initialization*/
  let meritQueue = GM_getValue("merit_queue", []);

  const queueBtnTemplate = document.createElement("a");
  queueBtnTemplate.href = "javascript:void(0);";
  queueBtnTemplate.innerHTML =
    '&nbsp;&nbsp;<span style="vertical-align: middle;"><b>+Queue</b></span>';

  const topicMatch = window.location.href.match(/topic=(\d+)/);
  const topicId = topicMatch ? topicMatch[1] : "0";


  document.querySelectorAll('a[href*="action=merit;msg="]').forEach((link) => {
    const match = link.href.match(/msg=(\d+)/);
    if (!match) return;

    const [, msgId] = match;

    /* Optional Chaining & Nullish Coalescing */
    const username =
      link
        .closest(".windowbg, .windowbg2")
        ?.querySelector(".poster_info b a")
        ?.textContent.trim() ?? "Unknown";

    const queueBtn = queueBtnTemplate.cloneNode(true);
    queueBtn.addEventListener("click", (e) => {
      e.preventDefault();
      /* confirmation dialog, for unexpected clicks  */
      if (confirm(`Are you sure you want to queue 1 merit for ${username}?`)) {
        addToQueue(topicId, msgId, username);
      }
    });

    link.parentNode.insertBefore(queueBtn, link.nextSibling);
  });

  const addToQueue = (topicId, msgId, username) => {
    if (meritQueue.some((item) => item.msgId === msgId))
      return alert("Post is already in the queue!");

    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);

    meritQueue = [
      ...meritQueue,
      {
        topicId,
        msgId,
        username,
        amount: 1,
        date: getLocalISOTime(tomorrow),
      },
    ];
    console.log("Updated Merit Queue:", meritQueue);
    GM_setValue("merit_queue", meritQueue);
    alert(`Added a post from ${username} to Merit Queue`);
  };

  /* Modal*/
  document.body.insertAdjacentHTML(
    "beforeend",
    `
        <div id="merit-queue-modal">
            <div id="merit-queue-content">
                <div>
                  <span class="mq-close">&times;</span>
                  <h2 style="margin-top:0; border-bottom:2px solid #ccc; padding-bottom:10px;">Merit Queue Settings</h2>
                </div>
                <div id="mq-list"></div>
                <div id="mq-save-container"><button id="mq-save-btn">Save Changes</button></div>
            </div>
        </div>
        <button id="open-mq-btn">View Merit Queue</button>
    `,
  );

  const modal = document.getElementById("merit-queue-modal");
  const listContainer = document.getElementById("mq-list");

  const renderQueue = () => {
    if (!meritQueue.length) {
      listContainer.innerHTML = "<p>No posts in queue.</p>";
      return;
    }

    /*Queue items */
    listContainer.innerHTML = meritQueue
      .map((item, index) => {
        const predefinedMeritValues = [1, 2, 3, 4, 6, 8, 10, 20, 40, 50];
        const options = predefinedMeritValues
          .map(
            (i) =>
              `<option value="${i}" ${item.amount == i ? "selected" : ""}>${i}</option>`,
          )
          .join("");

        return `
                <div class="mq-item" ${item.status === "failed" ? 'style="background-color: #ffe6e6; padding: 10px;"' : ""}>
                    <div>
                        <strong>User: ${item.username}</strong><br>
                        <a href="https://bitcointalk.org/index.php?topic=${item.topicId}.msg${item.msgId}#msg${item.msgId}" target="_blank" style="font-size:12px;">Link to Msg: #${item.msgId}</a>
                        ${item.error ? `<br><span style="color:red; font-size:12px;"><b>Error:</b> ${item.error}</span>` : ""}
                    </div>
                    <div>
                        <label>Merit: <select class="mq-amount" data-index="${index}">${options}</select></label>
                        <label style="margin-left: 15px;">Date & Time: <input type="datetime-local" class="mq-date" data-index="${index}" value="${item.date}"></label>
                        <button class="mq-remove" data-index="${index}" title="Remove from queue" style="margin-left: 15px; color: red;">X</button>
                    </div>
                </div>
            `;
      })
      .reverse()
      .join("");
  };

  /* remove button */
  listContainer.addEventListener("click", (e) => {
    if (e.target?.classList.contains("mq-remove")) {
      meritQueue.splice(e.target.dataset.index, 1);
      GM_setValue("merit_queue", meritQueue);
      renderQueue();
    }
  });

  document.getElementById("open-mq-btn").onclick = () => {
    meritQueue = GM_getValue("merit_queue", []);
    renderQueue();
    modal.style.display = "block";
  };

  document.querySelector(".mq-close").onclick = () => {
    modal.style.display = "none";
  };

  window.onclick = (e) => {
    if (e.target === modal) {
      modal.style.display = "none";
    }
  };

  document.getElementById("mq-save-btn").onclick = () => {
    document.querySelectorAll(".mq-amount").forEach((sel) => {
      meritQueue[sel.dataset.index].amount = sel.value;
    });

    document.querySelectorAll(".mq-date").forEach((inp) => {
      meritQueue[inp.dataset.index].date = inp.value;
    });

    GM_setValue("merit_queue", meritQueue);
    alert("Queue settings successfully saved!");
    modal.style.display = "none";
  };

  /* Main automation logic */
  const processMeritQueue = async () => {
 let queue = GM_getValue("merit_queue", []);    
 if (!queue.length) return;

    const currentLocalTime = getLocalISOTime(new Date());

    const sc = document
      .querySelector('a[href*="action=logout;sesc="]')
      ?.href.match(/sesc=([a-f0-9]+)/)?.[1];

    if (!sc) return;

   for (let i = queue.length - 1; i >= 0; i--) {
      queue = GM_getValue("merit_queue", []);
      
      let item = queue[i];
      if (!item) continue;
       /* Prevent unnecessary network requests for known failed items */
      if (item.status === "failed") continue;

      // Evaluates precise minute accuracy instead of just the day
      if (item.date <= currentLocalTime) {
        console.log(
          `Time reached! Attempting to dispense ${item.amount} merit to MsgID ${item.msgId}...`,
        );

        const formData = new URLSearchParams({
          merits: item.amount,
          msgID: item.msgId,
          sc,
        });

        try {
          const response = await fetch(
            `https://bitcointalk.org/index.php?action=merit;msg=${item.msgId}`,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/x-www-form-urlencoded",
                Accept:
                  "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
              },
              body: formData.toString(),
            },
          );

          if (response.ok) {
            const html = await response.text();

            if (html.includes("An Error Has Occurred")) {
              console.error(`Failed to send merit for MsgID ${item.msgId}`);
              let errorMsg = "Unknown error occurred.";

              const lowerHtml = html.toLowerCase();

              /** Checks for error
               * Note: not sure about the exact message for "no merit" yet, so I'm doing a contains check for "enough smerit" which should cover both "not enough sMerit" and "you don't have any sMerit" maybe
               * For any reason , if the error message doesn't match or there are changes in the future, it will default to "Unknown error occurred." instead of just saying "Failed to send merit" which is less informative for the user.
               * */

              if (lowerHtml.includes("enough smerit")) {
                errorMsg = "Not enough sMerit.";
              } else if (lowerHtml.includes("cannot send merit to yourself")) {
                errorMsg = "Cannot send merit to yourself tuff guy.";
              }

              /* Flags the failed item in the queue instead of deleting it */
              /** Flags and update  the failed item in the queue instead of deleting it */
              queue[i].status = "failed";
              queue[i].error = errorMsg;
              GM_setValue("merit_queue", queue);
            
            } else {
              
              console.log(
                `Successfully sent ${item.amount} merit for MsgID ${item.msgId}`,
              );
              // Remove by exact index mapping (faster than filter)
            queue.splice(i, 1);
            GM_setValue("merit_queue", queue);
            }
          }

          await new Promise((res) => setTimeout(res, 3000)); // wait to prevent rate-limiting before next attempt
        } catch (err) {
          console.error(
            "Error dispensing merit, keeping in queue:", err);
        }
      } else console.log("Time not Fufiiled yet...");
    }

  };

  processMeritQueue();
})();




Post_Trigger:
Code:
// ==UserScript==
// @name         Bitcointalk Merit Automation [Post_Trigger]
// @namespace    http://...
// @version      1.0
// @description  Queue posts to merit them later.. User has to login for scrpt to load.
// @author       promise444c5
// @match        https://bitcointalk.org/index.php?topic=*
// @match        https://bitcointalk.org/index.php?action=post*
// @noframes
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(() => {
  "use strict";

  GM_addStyle(`
       /* Modal Overlay */
        #merit-queue-modal { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(3px); }

        /* Modal */
        #merit-queue-content {
            background-color: #ffffff; margin: 4% auto; padding: 24px; border-radius: 12px;
            width: 85%; max-width: 850px; max-height: 85vh; /* Keep within viewport */
            display: flex; flex-direction: column; /* Allows nested list to fill space & scroll */
            box-shadow: 0 10px 40px rgba(0,0,0,0.3); color: #333; font-family: system-ui, -apple-system, sans-serif;
            box-sizing: border-box; max-height: 80vh; overflow-y: auto; to #merit-queue-content
        }

        /* Header & Close */
        .mq-close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; transition: color 0.2s; line-height: 1; }
        .mq-close:hover { color: #e74c3c; }
        #merit-queue-content h2 { margin-top: 0; margin-bottom: 5px; border-bottom: 2px solid #f0f0f0; padding-bottom: 12px; font-size: 20px; color: #2c3e50;}

        /* Scrolling List */
        #mq-list {
            flex-grow: 1; overflow-y: auto; overflow-x: hidden;
            padding-right: 12px; margin: 15px 0;
            display: flex; flex-direction: column; gap: 10px;
        }

        /* Smooth Scrollbar for mq-list */
        #mq-list::-webkit-scrollbar { width: 8px; }
        #mq-list::-webkit-scrollbar-track { background: #f8f9fa; border-radius: 4px; }
        #mq-list::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
        #mq-list::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }

        /* Queue Item Cards */
        .mq-item {
            display: flex; align-items: center; justify-content: space-between;
            background: #f8f9fc; padding: 16px; border-radius: 8px; border: 1px solid #eaedf1;
            transition: all 0.2s ease-in-out;
            flex-wrap: wrap; gap: 10px;
        }
        .mq-item > div:nth-child(2) {
            display: flex;
            align-items: center;
            gap: 15px;
            flex-wrap: wrap;

         }
        .mq-item:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(0,0,0,0.05); border-color: #d1d5db; flex:wrap }

        /* Inputs & Form Elements */
        .mq-amount, .mq-date { padding: 6px 10px; border: 0.5px solid #cbd5e1; border-radius: 6px; font-family: inherit; font-size: 13px; color: #333; outline: none; transition: border-color 0.2s; }
        .mq-amount:focus, .mq-date:focus { border-color: #3b82f6; }

        /* Action Buttons */
        .mq-remove {
            background: #fee2e2; color: #ef4444; border: none; border-radius: 50%;
            width: 32px; height: 32px; font-weight: bold; cursor: pointer;
            display: flex; align-items: center; justify-content: center; transition: all 0.2s; margin-left: 20px;
            margin: 0 !important;
            flex-shrink: 0;
        }
        .mq-remove:hover { background: #fca5a5; color: #7f1d1d; transform: scale(1.05); }

        #mq-save-container { text-align: center; margin-top: auto; padding-top: 15px; border-top: 2px solid #f0f0f0; }
        #mq-save-btn {
            padding: 12px 35px; font-size: 15px; font-weight: 600; cursor: pointer;
            background: #047857; color: #fff;
            border: none; border-radius: 6px; box-shadow: 0 4px 10px rgba(16, 185, 129, 0.3); transition: all 0.2s;
        }
        #mq-save-btn:hover { background: #059669; box-shadow: 0 6px 14px rgba(16, 185, 129, 0.4); translateY(-1px); }
        #mq-save-btn:active { transform: translateY(2px); box-shadow: 0 2px 5px rgba(16, 185, 129, 0.3); }

        #open-mq-btn { position: fixed; bottom: 25px; right: 25px; padding: 12px 20px; font-weight: 600; background: #375f82; color: white; border: none; border-radius: 50px; cursor: pointer; z-index: 9998; box-shadow: 0 6px 12px rgba(59, 130, 246, 0.3); transition: transform 0.2s; }
        #open-mq-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(59, 130, 246, 0.4); opacity:0.95; background:#3e6488 }`);

  /* Loads active queue from storage on script initialization*/
  let meritQueue = GM_getValue("merit_queue", []);

  const queueBtnTemplate = document.createElement("a");
  queueBtnTemplate.href = "javascript:void(0);";
  queueBtnTemplate.innerHTML =
    '&nbsp;<span style="vertical-align: middle;"><b>+Queue</b></span>';

  const topicMatch = window.location.href.match(/topic=(\d+)/);
  const topicId = topicMatch ? topicMatch[1] : "0";

  document.querySelectorAll('a[href*="action=merit;msg="]').forEach((link) => {
    const match = link.href.match(/msg=(\d+)/);
    if (!match) return;

    const [, msgId] = match;

    /* Optional Chaining & Nullish Coalescing */
    const username =
      link
        .closest(".windowbg, .windowbg2")
        ?.querySelector(".poster_info b a")
        ?.textContent.trim() ?? "Unknown";

    const queueBtn = queueBtnTemplate.cloneNode(true);
    queueBtn.addEventListener("click", (e) => {
      e.preventDefault();
      /* confirmation dialog, for unexpected clicks  */
      if (confirm(`Are you sure you want to queue 1 merit for ${username}?`)) {
        addToQueue(topicId, msgId, username);
      }
    });

    link.parentNode.insertBefore(queueBtn, link.nextSibling);
  });

  const addToQueue = (topicId, msgId, username) => {
    if (meritQueue.some((item) => item.msgId === msgId))
      return alert("Post is already in the queue!");

    meritQueue = [
      ...meritQueue,
      {
        topicId,
        msgId,
        username,
        amount: 1,
      },
    ];

    GM_setValue("merit_queue", meritQueue);
      alert(`Added a post from ${username} to Merit Queue`);

  };

  /* Modal*/
  document.body.insertAdjacentHTML(
    "beforeend",
    `
        <div id="merit-queue-modal">
            <div id="merit-queue-content">
              <div>
                <span class="mq-close">&times;</span>
                <h2 style="margin-top:0; border-bottom:2px solid #ccc; padding-bottom:10px;">Merit Queue Settings</h2>
              </div>
                <div id="mq-list"></div>
                <div id="mq-save-container"><button id="mq-save-btn">Save Changes</button></div>
            </div>
        </div>
        <button id="open-mq-btn">View Merit Queue</button>
    `,
  );

  const modal = document.getElementById("merit-queue-modal");
  const listContainer = document.getElementById("mq-list");

  const renderQueue = () => {
    if (!meritQueue.length) {
      listContainer.innerHTML = "<p>No posts in queue.</p>";
      return;
    }

    /* Queue items */
    listContainer.innerHTML = meritQueue
      .map((item, index) => {
        const predefinedMeritValues = [1, 2, 3, 4, 6, 8, 10, 20, 40, 50];
        const options = predefinedMeritValues
          .map(
            (i) =>
              `<option value="${i}" ${item.amount == i ? "selected" : ""}>${i}</option>`,
          )
          .join("");

        return `
                <div class="mq-item" ${item.status === "failed" ? 'style="background-color: #ffe6e6; padding: 10px;"' : ""}>
                    <div>
                        <strong>User: ${item.username}</strong><br>
                        <a href="https://bitcointalk.org/index.php?topic=${item.topicId}.msg${item.msgId}#msg${item.msgId}" target="_blank" style="font-size:12px;">Link to Msg: #${item.msgId}</a>
                        ${item.error ? `<br><span style="color:red; font-size:12px;"><b>Error:</b> ${item.error}</span>` : ""}
                    </div>
                    <div>
                        <label>Merit: <select class="mq-amount" data-index="${index}">${options}</select></label>
                        <!-- (Keep your date input line here if modifying merit_automation.user.js) -->
                        <button class="mq-remove" data-index="${index}" title="Remove from queue" style="margin-left: 15px; color: red;">X</button>
                    </div>
                </div>
            `;
      })
      .reverse()
      .join("");
  };

  /* remove button */
  listContainer.addEventListener("click", (e) => {
    if (e.target?.classList.contains("mq-remove")) {
      meritQueue.splice(e.target.dataset.index, 1);
      GM_setValue("merit_queue", meritQueue);
      renderQueue();
    }
  });

  document.getElementById("open-mq-btn").onclick = () => {
    meritQueue = GM_getValue("merit_queue", []);
    renderQueue();
    modal.style.display = "block";
  };

  document.querySelector(".mq-close").onclick = () => {
    modal.style.display = "none";
  };

  window.onclick = (e) => {
    if (e.target === modal) {
      modal.style.display = "none";
    }
  };

  document.getElementById("mq-save-btn").onclick = () => {
    document.querySelectorAll(".mq-amount").forEach((sel) => {
      meritQueue[sel.dataset.index].amount = sel.value;
    });
    GM_setValue("merit_queue", meritQueue);
    alert("Queue settings successfully saved!");
    modal.style.display = "none";
  };

    /* Main automation logic */
  const processMeritQueue = async () => {
    const isNewPost = GM_getValue("NewPost", false);
    if (!isNewPost) return;
    
    /* Clear trigger flag immediately */
    GM_setValue("NewPost", false);

    let queue = GM_getValue("merit_queue", []);
    if (!queue.length) return;

    const sc = document
      .querySelector('a[href*="action=logout;sesc="]')
      ?.href.match(/sesc=([a-f0-9]+)/)?.[1];
    if (!sc) return;

    for (let i = queue.length - 1; i >= 0; i--) {
      queue = GM_getValue("merit_queue", []);
      
      let item = queue[i];
      if (!item) continue;
      
      /* Prevent unnecessary network requests for known failed items */
      if (item.status === "failed") continue;

      console.log(
        `Attempting to dispense ${item.amount} merit to MsgID ${item.msgId}...`,
      );

      const formData = new URLSearchParams({
        merits: item.amount,
        msgID: item.msgId,
        sc,
      });

      try {
        const response = await fetch(
          `https://bitcointalk.org/index.php?action=merit;msg=${item.msgId}`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
              Accept:
                "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
            },
            body: formData.toString(),
          },
        );

        if (response.ok) {
          const html = await response.text();
          
          if (html.includes("An Error Has Occurred")) {
            console.error(`Failed to send merit for MsgID ${item.msgId}`);
            let errorMsg = "Unknown error occurred.";
            
            const lowerHtml = html.toLowerCase();

            /** Checks for error
             * Note: not sure about the exact message for "no merit" yet, so I'm doing a contains check for "enough smerit" which should cover both "not enough sMerit" and "you don't have any sMerit" maybe
             * For any reason , if the error message doesn't match or there are changes in the future, it will default to "Unknown error occurred." instead of just saying "Failed to send merit" which is less informative for the user.
             * */
            if (lowerHtml.includes("enough smerit")) {
              errorMsg = "Not enough sMerit.";
            } else if (lowerHtml.includes("cannot send merit to yourself")) {
              errorMsg = "Cannot send merit to yourself tuff guy.";
            }

            /** Flags and update  the failed item in the queue instead of deleting it */
            queue[i].status = "failed";
            queue[i].error = errorMsg;
            GM_setValue("merit_queue", queue);
            
          } else {
            console.log(
              `Successfully sent ${item.amount} merit for MsgID ${item.msgId}`,
            );
            
            // Remove by exact index mapping (faster than filter)
            queue.splice(i, 1);
            GM_setValue("merit_queue", queue);
          }
        }
        await new Promise((res) => setTimeout(res, 3000)); // wait to prevent rate-limiting before next attempt
      } catch (err) {
        console.error("Error dispensing merit, keeping in queue:", err);
      }
    }
  };

  const postForm =
    document.forms.postmodify ||
    document.querySelector('form[action*="action=post2"]');
    
  if (postForm) {
    postForm.addEventListener("submit", () => {
      GM_setValue("NewPost", true);
    });
  }

  processMeritQueue();
})();


***
-increased timeout to 3 seconds
-fixed overflow both [x[horizontal]-y-[vertical]] [ both screen & device responsiveness ]
-updated the queue to display inorder latest -> old ..
-updated the logic to make sure the new post indicator[indicates that user made a post] is set to false immediatey we check for the length of the queue  before the loop itself
-also made the loop to check the updated storarge on every api request..
-i also filter out post with error for now (i purposely left it out before because i want the script to retry every time page reloads but im rethinking that now Tongue )..until i know the "out of merit" message before icome up with anew update for it.

i got one from Loycev but i also need to know what it say when only the source finishes and  your own personal s-merit is all that's left to spend.


Try recreating the error with above.




███████████████████████████
███████▄████████████▄██████
████████▄████████▄████████
███▀█████▀▄███▄▀█████▀███
█████▀█▀▄██▀▀▀██▄▀█▀█████
███████▄███████████▄███████
███████████████████████████
███████▀███████████▀███████
████▄██▄▀██▄▄▄██▀▄██▄████
████▄████▄▀███▀▄████▄████
██▄███▀▀█▀██████▀█▀███▄███
██▀█▀████████████████▀█▀███
███████████████████████████
.
.Duelbits PREDICT..
█████████████████████████
█████████████████████████
███████████▀▀░░░░▀▀██████
██████████░░▄████▄░░████
█████████░░████████░░████
█████████░░████████░░████
█████████▄▀██████▀▄████
████████▀▀░░░▀▀▀▀░░▄█████
██████▀░░░░██▄▄▄▄████████
████▀░░░░▄███████████████
█████▄▄█████████████████
█████████████████████████
█████████████████████████
.
.WHERE EVERYTHING IS A MARKET..
█████
██
██







██
██
██████
Will Bitcoin hit $200,000
before January 1st 2027?

    No @1.15         Yes @6.00    
█████
██
██







██
██
██████

  CHECK MORE > 
vapourminer
Legendary
*
Offline

Activity: 5054
Merit: 6268


what is this "brake pedal" you speak of?


View Profile
June 04, 2026, 11:21:16 AM
Last edit: June 04, 2026, 11:36:48 AM by vapourminer
Merited by promise444c5 (2)
 #28

Try recreating the error with above.

using new post_trigger script


11 source merits left
7 merits to sent in this current queue

if it double fires ill cross into my merits

will post and quickly edit.. here goes

edit 1

5 left in list


2 left

....   and clicking save occasionally


edit:

ok one fired twice. this one. it was left over after posting, and it stayed visible in the queue for a while. then disappeared apparently when it sent a merit the second time.



its gone now.

so out of 7, 1 double fired

i do have the whole console output but im not sure if its ok to post here.


joker_josue
Legendary
*
Offline

Activity: 2408
Merit: 7088


**In BTC since 2013**


View Profile WWW
June 04, 2026, 11:37:44 AM
Merited by promise444c5 (2)
 #29

Congratulations on creating this script. It seems to work well for those who are more interested in distributing merits later.

Personally, I don't see myself using it, but I understand its usefulness.  Wink

 
 b1exch.to 
  ETH      DAI   
  BTC      LTC   
  USDT     XMR    
.███████████▄▀▄▀
█████████▄█▄▀
███████████
███████▄█▀
█▀█
▄▄▀░░██▄▄
▄▀██▄▀█████▄
██▄▀░▄██████
███████░█████
█░████░█████████
█░█░█░████░█████
█░█░█░██░█████
▀▀▀▄█▄████▀▀▀
promise444c5 (OP)
Hero Member
*****
Offline

Activity: 1036
Merit: 907


All things are numbers


View Profile WWW
June 04, 2026, 01:57:08 PM
Last edit: June 04, 2026, 03:01:15 PM by promise444c5
Merited by vapourminer (1)
 #30

Try recreating the error with above.

using new post_trigger script


11 source merits left
7 merits to sent in this current queue

if it double fires ill cross into my merits

will post and quickly edit.. here goes

edit 1

5 left in list


2 left

....   and clicking save occasionally


edit:

ok one fired twice. this one. it was left over after posting, and it stayed visible in the queue for a while. then disappeared apparently when it sent a merit the second time.



its gone now.

so out of 7, 1 double fired

i do have the whole console output but im not sure if its ok to post here.


Alright.. i will send a dm to get the logs  but with your current explanation, it seems the one that was  trying to go through at the moment you hit the save button, sent  twice..  
I will check the edit page and see if i can possibly get a fix with the help of the log, i will try recreating it aswell to see if it's a persisting error.

The first implementation before posting on the forum actually prevented posts from being submitted until the entire merit is being dispensed, but I removed it because of the direct relationship between the queue and the wait time.
[edit]
found out both edit and post have same action Tongue


But in the mean time you can wait till the process  is complete before editing any post.

Congratulations on creating this script. It seems to work well for those who are more interested in distributing merits later.

Personally, I don't see myself using it, but I understand its usefulness.  Wink
Thanks.. Joker

███████████████████████████
███████▄████████████▄██████
████████▄████████▄████████
███▀█████▀▄███▄▀█████▀███
█████▀█▀▄██▀▀▀██▄▀█▀█████
███████▄███████████▄███████
███████████████████████████
███████▀███████████▀███████
████▄██▄▀██▄▄▄██▀▄██▄████
████▄████▄▀███▀▄████▄████
██▄███▀▀█▀██████▀█▀███▄███
██▀█▀████████████████▀█▀███
███████████████████████████
.
.Duelbits PREDICT..
█████████████████████████
█████████████████████████
███████████▀▀░░░░▀▀██████
██████████░░▄████▄░░████
█████████░░████████░░████
█████████░░████████░░████
█████████▄▀██████▀▄████
████████▀▀░░░▀▀▀▀░░▄█████
██████▀░░░░██▄▄▄▄████████
████▀░░░░▄███████████████
█████▄▄█████████████████
█████████████████████████
█████████████████████████
.
.WHERE EVERYTHING IS A MARKET..
█████
██
██







██
██
██████
Will Bitcoin hit $200,000
before January 1st 2027?

    No @1.15         Yes @6.00    
█████
██
██







██
██
██████

  CHECK MORE > 
Pages: « 1 [2]  All
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!