About Posting to HIVE - STEEM & Blurt - Tampermonkey Script to Show Chain Activities

@louis88 · 2025-08-18 15:40 · hive

Hey everyone,

Today I came across a chat discussion that made me stop and think for a moment. The topic was about whether users who actively post their unique content on Hive should also publish the same content on other platforms. Some people were asking if this is allowed, if it should be done, and if it is even fair.

I want to share my personal opinion about this before I go into the details of the software I am currently building with ChatGPT.

For me, if a user decides to publish on Hive and becomes part of the reward pool, then the content should only be posted here. There are several reasons for this, but I want to focus on two main points.

The first point is about search engines. Content that is published on Hive is indexed and processed by Google and others. If the exact same text is also published on Steemit, Blurt, or any other platform, it creates duplicate content. This is not only bad for SEO in general, it can also hurt the reputation of articles published on Hive. Hive deserves to stand strong in search results and duplicate content works against that.

The second point is about mindset. I personally see it as a kind of greediness when someone tries to collect rewards from multiple platforms with the same article. Hive has its own reward system, its own pool, and its own community. By posting the same thing everywhere, the value of that content feels reduced. It makes me question if the person really believes in Hive or just wants to squeeze as much as possible out of every chain.

Of course, I know that people have different views and some will disagree with me. But my position is clear: if you want to be part of Hive, then publish on Hive. Keep your content unique here and give Hive the full benefit of your work.

image.png


A Solution?

Back when the discussion about the K-E Ratio was started by @azircon, I built a small extension for myself.
https://peakd.com/development/@louis88/the-ke-ratio-on-peakd-with-tampermonkey-just-a-prototype

The idea was simple. It injected the K-E Ratio right next to the username on the PeakD frontend. In the background, the extension sent a query, processed the result, and displayed it directly in the feed.

That experience gave me the foundation for another idea. With the help of ChatGPT, I created something similar, but this time it is focused on cross-chain activity. The logic works in a very straightforward way. Every user that appears in a feed — for example in Trending, in the Following feed, in comments, or in Recent Posts — triggers a background request. The extension then asks the Steemit and Blurt APIs whether that account has shown any activity in the last 30 days.

The result is then displayed right inside the Hive frontend. Next to the username you can immediately see if that person has been active or inactive on Steem or Blurt during the past month.

Right now, the script is set to look back exactly 30 days in the API. That way it is very clear at a glance whether a user is only active on Hive, or if they are still also posting or voting somewhere else.

I have taken some screenshots so you can see how this looks in different feeds. Whether it is Trending, Following, Comments, or Recent Posts, the status appears the same way: a clean little indicator next to the username showing “Active” or “Not Active” for Steem and Blurt.

image.png
Hidden to not blame

image.png

image.png

image.png

Showing to demonstrate greediness

What I like is that I have discovered little to no activity on external blockchains in the top trending posts. Occasionally, there was a user who at least votes on Steemit, but they probably just forgot to turn off their auto-votes and are not personally active there.

image.png

So let me sum it up once again. I believe that every content creator should make a decision for one blockchain. I do not want to see our Hive reward pool being shared with content that is also published somewhere else or going to people who are still supporting legacy chains.

  • Are you a Hiver? Then blog on Hive.
  • Are you a Steemian? Then stay on Steemit.
  • Are you a Blurter? Then publish on Blurt.

But please, do not dance at all weddings at the same time.

For my part, I will now keep my script running for a while. With it I can check activity on other blockchains and remove rewards from Hive users where I feel it makes sense. I will also clean up my own following feed. This means I will unfollow people who I actually like, but whose double or triple posting habits I cannot support. From me they will get a de-follow.

To be clear, this is not about starting a witch hunt or pointing fingers. It is about transparency. I want to make the current situation visible and give people a clearer picture. If you see the numbers and the activity right next to the usernames, it becomes very obvious who is loyal to Hive and who is trying to get rewards from everywhere.

In the end, everyone can decide for themselves. I simply share my view: if you want to be part of Hive, then publish here and stand behind it.

And finally, feel free to use the script I shared. Try it out, give me your feedback, and let me know your thoughts in the comments. I am very curious to hear how the community feels about this topic.

Thanks for reading.

The actual Code by ChatGPT for adding it to Tampermonkey (Chrome Extension)

// ==UserScript==
// @name         PeakD Cross-Activity (Steemit/Blurt) — Pretty Tooltip
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Shows if a user was active on Steemit/Blurt in the last N days, with a custom hover tooltip. More robust RPC payloads & Blurt nodes.
// @author       louis88
// @match        https://peakd.com/*
// @grant        GM_xmlhttpRequest
// @connect      api.steemit.com
// @connect      api.justyy.com
// @connect      rpc.blurt.world
// @connect      api.blurt.blog
// @connect      blurt-rpc.saboin.com
// @connect      blurtrpc.actifit.io
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  // ---------- Config ----------
  const LOOKBACK_DAYS = 30;

  const STEEM_NODES = [
    'https://api.steemit.com',
    'https://api.justyy.com'
  ];

  // refreshed Blurt nodes (you can reorder)
  const BLURT_NODES = [
    'https://rpc.blurt.world',
    'https://api.blurt.blog',
    'https://blurt-rpc.saboin.com',
    'https://blurtrpc.actifit.io'
  ];

  // ---------- Helpers ----------
  const cache = new Map();
  const inflight = new Map();

  const usernameFromHref = href => (href.match(/\/@([\w\-.]+)/) || [])[1] || null;
  const toDateOrNull = s => (!s || s.startsWith('1970-01-01')) ? null : new Date(s.endsWith('Z') ? s : s + 'Z');
  const maxDate = arr => arr.filter(Boolean).sort((a,b)=>b-a)[0] || null;
  const daysAgo = d => Math.floor((Date.now()-d.getTime())/(1000*60*60*24));
  const within = d => d && (Date.now()-d.getTime()) <= LOOKBACK_DAYS*86400000;

  // Try multiple payload styles per node to handle different RPC implementations
  function rpcMulti(node, method, params){
    const payloads = [
      // JSON-RPC 2.0 (common on Steem/Blurt)
      { jsonrpc:'2.0', id:1, method, params },
      // Legacy Appbase style: call("condenser_api","get_accounts", [[username]])
      { jsonrpc:'2.0', id:2, method:'call', params:['condenser_api', 'get_accounts', params] }
    ];

    // Send one payload; resolve if any works
    return new Promise((resolve, reject) => {
      let idx = 0;
      const tryNext = () => {
        if (idx >= payloads.length) return reject(new Error('all-payloads-failed'));
        const body = JSON.stringify(payloads[idx++]);
        GM_xmlhttpRequest({
          method: 'POST',
          url: node,
          headers: { 'Content-Type': 'application/json' },
          data: body,
          timeout: 12000,
          onload: (res) => {
            try {
              const json = JSON.parse(res.responseText);
              if (json.error) return tryNext(); // try next payload
              resolve(json.result);
            } catch (e) {
              tryNext();
            }
          },
          onerror: () => tryNext(),
          ontimeout: () => tryNext()
        });
      };
      tryNext();
    });
  }

  async function getAccount(nodes, username){
    let lastErr;
    for (const n of nodes){
      try{
        const res = await rpcMulti(n, 'condenser_api.get_accounts', [[username]]);
        if (Array.isArray(res) && res[0]) return {account: res[0], node: n};
      }catch(e){ lastErr = e; }
    }
    throw lastErr || new Error('All nodes failed');
  }

  async function check(chain, nodes, username){
    const key = `${chain}:${username}`;
    if (cache.has(key)) return cache.get(key);
    if (inflight.has(key)) return inflight.get(key);

    const p = (async ()=>{
      try{
        const {account, node} = await getAccount(nodes, username);
        const lastVote = toDateOrNull(account.last_vote_time);
        const lastPost = toDateOrNull(account.last_post);
        const lastRoot = toDateOrNull(account.last_root_post);
        const last = maxDate([lastVote,lastPost,lastRoot]);
        const active = within(last);
        const sourceField =
          last && lastVote && +last===+lastVote ? 'last_vote_time' :
          last && lastPost && +last===+lastPost ? 'last_post' :
          last && lastRoot && +last===+lastRoot ? 'last_root_post' : null;
        const out = {active,last,sourceField,node,error:false};
        cache.set(key,out);
        return out;
      }catch(e){
        const out = {active:false,last:null,sourceField:null,node:null,error:true};
        cache.set(key,out);
        return out;
      }finally{
        inflight.delete(key);
      }
    })();

    inflight.set(key,p);
    return p;
  }

  // ---------- Pretty tooltip ----------
  const tip = document.createElement('div');
  Object.assign(tip.style, {
    position:'fixed', zIndex: 99999,
    maxWidth:'420px', padding:'10px 12px', borderRadius:'10px',
    background:'rgba(20,22,25,0.96)', color:'#e8e8e8',
    fontSize:'12px', lineHeight:'1.35',
    boxShadow:'0 8px 24px rgba(0,0,0,0.35)',
    border:'1px solid rgba(255,255,255,0.08)',
    pointerEvents:'none', display:'none', whiteSpace:'pre-line'
  });
  document.body.appendChild(tip);
  const showTip = (html, x, y) => {
    tip.innerHTML = html;
    tip.style.left = Math.min(x+12, window.innerWidth-440)+'px';
    tip.style.top = Math.min(y+12, window.innerHeight-20)+'px';
    tip.style.display = 'block';
  };
  const hideTip = ()=>{ tip.style.display='none'; };

  // ---------- UI ----------
  function makeBadge(label, active, brand){
    const el = document.createElement('span');
    el.textContent = `${label}: ${active ? 'ACTIVE' : 'NO'}`;
    Object.assign(el.style, {
      display:'inline-flex', alignItems:'center',
      padding:'3px 8px', fontSize:'90%', fontWeight:'600', color:'#fff',
      borderRadius:'999px', marginLeft:'6px', lineHeight:'1.3', letterSpacing:'.2px',
      background: active
        ? (brand==='STEEM' ? 'linear-gradient(135deg,#4CAF50,#2E7D32)'
           : 'linear-gradient(135deg,#3f7fbf,#274b7c)')
        : 'linear-gradient(135deg,#666,#444)',
      transition:'filter .2s'
    });
    el.addEventListener('mouseenter', ()=> el.style.filter='brightness(1.15)');
    el.addEventListener('mouseleave', ()=> el.style.filter='none');
    return el;
  }

  function ensureContainer(repRoot){
    let box = repRoot.parentElement.querySelector('.xchain-activity-badges');
    if (!box){
      box = document.createElement('span');
      box.className = 'xchain-activity-badges';
      box.style.display = 'inline-flex';
      box.style.alignItems = 'center';
      box.style.marginLeft = '6px';
      repRoot.parentElement.appendChild(box);
    }
    return box;
  }

  // ---------- Per-link processing ----------
  async function processLink(link){
    const username = usernameFromHref(link.getAttribute('href'));
    if (!username) return;

    const rep = link.parentElement?.querySelector('.reputation-label');
    if (!rep) return;

    // avoid double injection on the same rep area
    if (rep.parentElement.querySelector('.xchain-activity-badges')) return;

    const box = ensureContainer(rep);

    const ph = document.createElement('span');
    ph.textContent = 'checking…';
    ph.style.opacity = '0.6';
    ph.style.fontSize = '90%';
    ph.style.marginLeft = '6px';
    box.appendChild(ph);

    const [steem, blurt] = await Promise.all([
      check('STEEM', STEEM_NODES, username),
      check('BLURT', BLURT_NODES, username)
    ]);

    ph.remove();

    const bSteem = makeBadge('STEEM', !!steem.active, 'STEEM');
    bSteem.addEventListener('mouseenter', (ev)=>{
      const html = steem.error
        ? `<b>STEEM</b>\nFetch failed.`
        : (steem.last
            ? `<b>STEEM</b>\nLast: ${steem.last.toISOString().slice(0,19).replace('T',' ')} UTC\nRelative: ${daysAgo(steem.last)} days ago\nField: ${steem.sourceField}\nNode: ${steem.node}`
            : `<b>STEEM</b>\nNo activity found.`);
      showTip(html, ev.clientX, ev.clientY);
    });
    bSteem.addEventListener('mousemove', ev => showTip(tip.innerHTML, ev.clientX, ev.clientY));
    bSteem.addEventListener('mouseleave', hideTip);

    const bBlurt = makeBadge('BLURT', !!blurt.active, 'BLURT');
    bBlurt.addEventListener('mouseenter', (ev)=>{
      const html = blurt.error
        ? `<b>BLURT</b>\nFetch failed.`
        : (blurt.last
            ? `<b>BLURT</b>\nLast: ${blurt.last.toISOString().slice(0,19).replace('T',' ')} UTC\nRelative: ${daysAgo(blurt.last)} days ago\nField: ${blurt.sourceField}\nNode: ${blurt.node}`
            : `<b>BLURT</b>\nNo activity found.`);
      showTip(html, ev.clientX, ev.clientY);
    });
    bBlurt.addEventListener('mousemove', ev => showTip(tip.innerHTML, ev.clientX, ev.clientY));
    bBlurt.addEventListener('mouseleave', hideTip);

    box.appendChild(bSteem);
    box.appendChild(bBlurt);
  }

  function processUsers(){
    document.querySelectorAll('a[href^="/@"]').forEach(processLink);
  }

  let raf = null;
  const observer = new MutationObserver(()=> {
    if (raf) return;
    raf = requestAnimationFrame(()=> { raf = null; processUsers(); });
  });

  processUsers();
  observer.observe(document.body, {childList:true, subtree:true});
})();
#hive #steem #blurt #blog #community #blogchain #development #code #tampermonkey #makeafuckingdecision
Payout: 23.227 HBD
Votes: 350
More interactions (upvote, reblog, reply) coming soon.