TagCoins Enhancement

Enhance your TagCoins Experience!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         TagCoins Enhancement
// @version      4.2
// @description  Enhance your TagCoins Experience!
// @author       Ko
// @icon         https://raw.githubusercontent.com/wilcooo/TagCoins-Enhancement/master/three_coins.png
// @download     https://raw.githubusercontent.com/wilcooo/TagCoins-Enhancement/master/tce.user.js
// @match        http://tagpro.lol/*
// @match        http://tagcoins.lol/*
// @supportURL   https://www.reddit.com/message/compose/?to=Wilcooo
// @website      https://redd.it/no-post-yet
// @connect      script.google.com
// @connect      script.googleusercontent.com
// @run-at       document-idle
// @license      MIT
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @namespace https://greasyfork.org/users/152992
// ==/UserScript==




////////////////////////////////////////////////////////////////////////////////////////////
//     Please consinder to donate :)             Looking for the options? Scroll down!    //
////////////////////////////////////////////////////////////////////////////////////////  //
                                                                                      //  //
// This script is free to use forever! If, however, you really want to support me,    //  //
// it will be highly valued if you donated. You can do so by changing the values      //  //
// below to what is right for you.                                                    //  //
                                                                                      //  //
// change false to true if you want to activate donation                              //  //
const DONATION_ENABLED = false;                                                        //  //
                                                                                      //  //
// How often would you like to donate?                                                //  //
// 1 means   'every time you visit tagpro.lol'                                        //  //
// 0.5 means 'half of the time you visit tagpro.lol'                                  //  //
// any other value between 0 and 1 will work too                                      //  //
// choose 0 if you want your donation to only be given once                           //  //
const DONATION_FREQUENCY = 0.7 ;                                                      //  //
                                                                                      //  //
// What amount would you like to donate at a time?                                    //  //
const DONATION_AMOUNT = 3 ;                                                           //  //
                                                                                      //  //
// What should be the 'reason' of your donation?                                      //  //
const DONATION_REASON = 'Donation for the "TagCoins Enhancement" script!';            //  //
                                                                                      //  //
////////////////////////////////////////////////////////////////////////////////////////  //
//                                                                                        //
////////////////////////////////////////////////////////////////////////////////////////////




////////////////////////////////////////////////////////////////////////////////////////////
//     ### --- OPTIONS --- ###                                                            //
////////////////////////////////////////////////////////////////////////////////////////  //
                                                                                      //  //
// Show a notification whenever a transaction occurs.                                 //  //
// Also works when the browser window is minimized!                                   //  //
const NOTIFY_TRANSACTION = true;                                                      //  //
                                                                                      //  //
// Show a timestamp next to every transaction that happens *after* you opened         //  //
// or refreshed the page. (timestamps of earlier transactions are impossible to know) //  //
const SHOW_TIMESTAMP = true;                                                          //  //
                                                                                      //  //
// When on someones personal transactions overview page, clicking on his or her big   //  //
// black bouncing name will get you to the connected profile on a TagPro server       //  //
const LINK_PROFILE = true;                                                            //  //
                                                                                      //  //
// Show the transaction ID next to every transaction that occurred *after* you opened //  //
// or refreshed.                                                                      //  //
const SHOW_ID = true;                                                                 //  //
                                                                                      //  //
// Show the amount of coins everyone has next to their name!                          //  //
// The amount shown is the capital someone had *before* that transaction              //  //
// This can make the loading of the page slow, and uses some more data.               //  //
const SHOW_CAPITAL = true;                                                            //  //
                                                                                      //  //
// Update your own capital (at the top of the page) whenever you send or receive.     //  //
const UPDATE_OWN_CAPITAL = true;                                                      //  //
                                                                                      //  //
// See whenever someone buys a ticket. Normally this is hidden.                       //  //
const SPY_PURCHASES = true;                                                           //  //
                                                                                      //  //
// The number of coins gets roughly multiplied by this number.                        //  //
const MOAR_COINS = 3;                                                                 //  //
                                                                                      //  //
// Don't allow the same transaction to be send twice (without re-entering it)         //  //
// Also known as the 'double-spend bug'                                               //  //
const FIX_DOUBLE_SPEND = true;                                                        //  //
                                                                                      //  //
// Show a notification whenever the lottery info gets updated.                        //  //
// (not yet recommended because it doesn't work well yet)                             //  //
const NOTIFY_LOTTERY = false;                                                         //  //
                                                                                      //  //
// Hide the lottery statement while it's the same as it has been for a month now.     //  //
// (as of january 2018)                                                               //  //
const HIDE_LOTTERY_STATEMENT = false;                                                 //  //
                                                                                      //  //
// Replace 'for now' by 'forever' in the text on the top left, because it seems       //  //
// to be more accurate                                                                //  //
const LOTTERY_IS_DEAD = true;                                                         //  //
                                                                                      //  //
// Don't forget to save by pressing Ctrl S  (or click the save icon)                  //  //
// Got any other recomendations? Let me know!                                         //  //
                                                                                      //  //
////////////////////////////////////////////////////////////////////////////////////////  //
//                                                     ### --- END OF OPTIONS --- ###     //
////////////////////////////////////////////////////////////////////////////////////////////






//////////////////////////////////////
// SCROLL FURTHER AT YOUR OWN RISK! //
//////////////////////////////////////







console.log('START: ' + GM_info.script.name + ' (v' + GM_info.script.version + ' by ' + GM_info.script.author + ')');

// The data of each transaction (of everyone) will be sent to a sheet (at tiny.cc/tagcoins)
// This way I try to get a history of TagCoins, public to anyone!
// No personal data will be send. Check this scripts code to confirm for yourself just a bit after line 264 :)
// Please don't send false data to this URL.

const DataDropURL = 'https://script.google.com/macros/s/AKfycbznkUUI6B9nPjgU9XCBSPFdYwBqNEv3fv6nQc79YrDGZOkdhfaP/exec';


// Auto Donator: only works if DONATION_ENABLED is true.
// If you fear for your TagCoins, ask a second opinion from someone who can read
// computer code to confirm that this script really doesn't steal your coins.

if (DONATION_ENABLED && window.location.pathname == '/') {

    var AlreadyDonated = GM_getValue('AlreadyDonated');

    if ( (DONATION_FREQUENCY === 0 && !AlreadyDonated) || Math.random() < DONATION_FREQUENCY ) {

        $.post("/send/tagcoins", {
            recipient: 'Ko',
            amount: DONATION_AMOUNT,
            reason: DONATION_REASON || null,
            _csrf: $("input[name=csrfToken]").val()
        }, function(res) {
            if (!res.valid) {
                showError('Error while trying to donate to Ko: '+res.errorMessage);
            }
        });

        GM_setValue('AlreadyDonated', true);
    }

}

// The next bit of code adds a donation button.
// A click on the button will never send coins.
// You'll have to manually click the 'send' button
//   after clicking the 'Donate to Ko' button.

if (!DONATION_ENABLED && window.location.pathname == '/') {

    var donate_button = document.createElement('a');
    donate_button.className = "btn btn-primary pull-left";
    donate_button.style.marginLeft = "4px";
    donate_button.innerText = "Donate to Ko";
    donate_button.title = "Click this button, set your amount and click 'send'.";
    donate_button.onclick = function(){
        var recipient = document.getElementsByClassName('form-input recipient')[0];
        var amount = document.getElementsByClassName('form-input amount')[0];
        var reason = document.getElementsByClassName('form-input reason')[0];

        if (!reason.value || recipient.value != "Ko") reason.value = 'Donation for the "TagCoins Enhancement" script!';

        recipient.value = "Ko";

        amount.value = Math.max(amount.value, 10);

        var send_button = document.getElementById('send');
        send_button.style.transition = 'background-color 0.8s';

        var animation = setInterval(function(){
            if (send_button.style.backgroundColor) send_button.style.backgroundColor = "";
            else send_button.style.backgroundColor = 'cyan';
        },800);

        send_button.onclick = function(){
            send_button.style.backgroundColor = "";
            clearInterval(animation);
        };
    };

    document.getElementsByClassName('userbar')[0].appendChild(donate_button);

}



// Replace the lottery text because it takes forever.

var lottery_container = document.getElementsByClassName('lottery-container')[0];


// Fully hide the lottery statement, if it's the exact text as the following:
const lottery_statement = "Disabled lotto for now. \nDamn banks - you got me lol\nAlso the site kept crashing so i need to fix that. I'll check the records for corrupt lotteries and reinstate your tickets";

if ( HIDE_LOTTERY_STATEMENT && lottery_container && lottery_container.innerText == lottery_statement ) {
    lottery_container.hidden = true;
}

if( LOTTERY_IS_DEAD ) {
    if (lottery_container) lottery_container.innerHTML = lottery_container.innerHTML.replace('for now', '<b><i>forever</i></b>');
}





// Increase the number of coins according to the option (lower frequency = moar coins)

emitter.frequency /= MOAR_COINS;


// Redefining the 'emitter' object, to purposefully crash the function that should add new transactions to the table

var real_emitter = emitter;
emitter = {update: function(...args){real_emitter.update(...args);},
           frequency: {valueOf: function(){throw "fakeError";}}};


// Now we can write our own event handler to add transactions to the table, but on our way :)

var your_profile_id = document.querySelector('[name=user_profile_id]').value;
var your_capital = parseInt(document.querySelector('[name=user_profile_id]').parentNode.childNodes[6].textContent);

var es = new EventSource('/stream/updates');
es.addEventListener("transaction", function(event) {
    var data = JSON.parse(event.data);
    data.reverse().forEach(function (t) {

        // ignore ticket purchases that aren't this user (except if the SPY option is enabled)
        if (t.type === 1 && t.sender_profile_id != $("input[name=user_profile_id]").val() && SPY_PURCHASES === false) {
            return;
        }

        real_emitter.frequency /= 9;
        timeremit = setTimeout(function() {
            real_emitter.frequency *= 9;
        }, 5000);

        var notification = "";

        var tmpl = "<tr>";
        tmpl += '<td>';
        if (t.sender_profile_id) {
            tmpl += "<a class=\"sender\" href=\"/tagpro/"+t.sender_profile_id+"\">";
            tmpl += "<span class=\"flair "+ t.sender_flair;
            tmpl += "\" style=\"background-position: "+ t.sender_flair_x +"px "+ t.sender_flair_y +"px\"></span> ";
            tmpl += t.sender_name + "</a> ";

            notification += t.sender_name + " ";
        } else {
            tmpl += "Lottery";
            notification += "Lottery ";
        }

        if (t.type === 0) {
            tmpl += "sent ";
            notification += "sent ";
        } else {
            tmpl += "paid ";
            notification += "paid ";
        }

        tmpl += "<span class=\"coins\">";

        t.coinFlairs.forEach(function(className) {
            tmpl += "<span class=\"currency "+className+"\"></span>\n";
        });

        tmpl += "</span> ";
        tmpl += "<span class=\"tgc-amount\">"+t.amount+" TGC</span> ";
        notification += t.amount + " TGC ";

        if (t.recipient_profile_id) {
            tmpl += "to ";
            tmpl += "<a class=\"recipient\" href=\"/tagpro/"+t.recipient_profile_id+"\">";
            tmpl += "<span class=\"flair "+ t.recipient_flair;
            tmpl += "\" style=\"background-position: "+ t.recipient_flair_x +"px "+ t.recipient_flair_y +"px\"></span> ";
            tmpl += t.recipient_name;
            tmpl += "</a>";

            notification += "to " + t.recipient_name;
        }

        if (t.type === 1) {
            tmpl += " for a lottery ticket";
            notification += " for a lottery ticket";
        }

        if (t.type === 2) {
            tmpl += " for winning lottery";
            notification += " for winning lottery";
        }

        tmpl += '<div style="float:right">';

        //if (SHOW_TIMESTAMP) tmpl += new Date(t.occurred * 1000).toTimeString().substr(0,8);

        if (SHOW_TIMESTAMP) {

            var timestamp = new Date(t.occurred * 1000).toTimeString().substr(0,8);

            tmpl += '<i class="timestamp" time="' + t.occurred*1000 + '" title="' + timestamp + '" style="color:brown"></i>';

            updateTimestamps();
        }

        if (SHOW_ID) tmpl += ' <sup>'+ t.id + '</sup>';

        tmpl += '</div>';

        if (t.reason) {
            tmpl += "<div class=\"reason\"></div>";
        }

        tmpl += "</td>";
        tmpl += "</tr>";

        var $transaction = $(tmpl);

        if (t.reason) {
            $transaction.find(".reason").text(t.reason);
        }

        if (window.location.pathname == '/')
            $(".table").prepend($transaction);

        // Show a notification
        if (NOTIFY_TRANSACTION) GM_notification( t.reason, notification, null, window.focus );

        // Put the capital behind the transaction.
        offset_capital["/tagpro/"+t.recipient_profile_id] += t.amount;
        offset_capital["/tagpro/"+t.sender_profile_id]    -= t.amount;
        updateCapitals();

        // The transaction is also send to a google sheet at tiny.cc/tagcoins
        $.post( DataDropURL, t );


        // Show a 'Thank you' message when the transaction is sent from you to Ko
        if (t.recipient_profile_id == "568c0e575f205782559d87c9" && t.sender_profile_id == your_profile_id) {
            var title = document.getElementById('title');
            title.innerText = "Thank you :)";
            title.style.color = 'limegreen';

            setTimeout(function(){
                title.innerText = "TagCoins";
                title.style.color = 'black';
            },5000);
        }

        // Update your own balance at the top of the page when the transaction is from or to you
        if (UPDATE_OWN_CAPITAL) {
            if (t.recipient_profile_id == your_profile_id){
                your_capital += t.amount;
            } else if (t.sender_profile_id == your_profile_id){
                your_capital -= t.amount;
            }
            document.querySelector('[name=user_profile_id]').parentNode.childNodes[6].textContent = your_capital+" TGC ";
        }

    });
});

es.addEventListener("lottery", function(event) {
    var data = JSON.parse(event.data);

    // data tags: id, amount, ticket_price, ticket_sold, ends_at, limit_per_user
    // also: event.ends_at

    if (NOTIFY_LOTTERY) GM_notification( 'Some information about the lottery got updated. This script doesn\'t know what it means (yet). (see console)', 'Lottery update!?', null, window.focus );
    console.log('Lottery update!?',data);
});



if (FIX_DOUBLE_SPEND) {
    var send_button = $('#send');

    send_button.off('click'); // Remove the normal handler

    send_button.click( function onSend() {
        send_button[0].disabled = true;
        send_button[0].style.cursor = 'default';
        loader.show();

        var recipient = $("input[name=recipient]").val();
        var amount = parseInt($("input[name=amount]").val(), 10);
        var csrfToken = $("input[name=csrfToken]").val();
        var reason = $("textarea[name=reason]").val() || null;

        $.post("/send/tagcoins", {
            recipient: recipient,
            amount: amount,
            reason: reason,
            _csrf: csrfToken
        }, function(res) {
            if (!res.valid) {
                showError(res.errorMessage);
                send_button[0].disabled = false;
                send_button[0].style.cursor = 'pointer';
                loader.hide();
            } else {
                showSuccess();
                send_button[0].disabled = false;
                send_button[0].style.cursor = 'pointer';
                loader.hide();
            }
        });
    });



    // Add a loading diamond

    // Find the correct styleSheet
    for (var styleSheet of document.styleSheets) if (styleSheet.href.includes('/style.css')) break;

    // Add a rule to the sheet for the timestamp
    styleSheet.insertRule(`.loader {
                               background-image: url('/static/images/diamond_green.png');
                               width: 40;
                               height: 40;
                               animation: spin 1s linear infinite;
                           }`);
    styleSheet.insertRule(`
                           @keyframes spin {
                               0% { transform: rotate(0deg); }
                               100% { transform: rotate(360deg); }
                           }`);

    var loader = $("<div></div>").addClass("loader").width(32).height(27).css({left:400,top:16,position:'absolute'}).appendTo(send_button.parent()).hide();
    loader.parent().css({position:'relative'});

}


function showSuccess() {
    $("input[name=recipient]").val("");
    $("input[name=amount]").val("");
    $("textarea[name=reason]").val("");

    $("#title").text("Success!");

    setTimeout(function() {
        $("#title").text("Tag Coins");
    }, 5000);
}




if (LINK_PROFILE && window.location.pathname.includes('tagpro')) {
    var title = document.getElementsByTagName('h1')[0];
    title.onclick = function(){
        window.location.href = 'http://tagpro-orbit.koalabeast.com/profile/'+window.location.pathname.split('/')[2];
    };
    title.style.cursor = 'pointer';
}




// Fetch capital of both parties (if enabled)

var cached_capital = {};
var offset_capital = {}; // When new transactions come in, the relative balance of the accounts involved is stored here

function updateCapitals() {

    if (SHOW_CAPITAL) {

        document.querySelectorAll('a.sender:not(.capital),a.recipient:not(.capital)').forEach( function(a){

            a.classList.add('capital');

            if (cached_capital[a.href]) {
                cached_capital[a.href].then( capital => a.innerHTML += ' <i style="color:goldenrod">'+(capital+(offset_capital[a.href] || ''))+'</i>' );

            } else {

                cached_capital[a.href] = new Promise( function(resolve, reject) {

                    GM_xmlhttpRequest({
                        method: "GET",
                        url: a.href,
                        onload: function(response) {
                            var profile_document = new DOMParser().parseFromString(response.responseText, "text/html");

                            let text_center;

                            if ((text_center = profile_document.querySelector('div.text-center'))) {
                                var capital = parseInt(profile_document.querySelector('div.text-center').innerText);
                                a.innerHTML += '<i style="color:goldenrod">'+(capital+(offset_capital[a.href] || ''))+'</i>';

                                resolve(capital);
                            }
                        },
                    });
                });

            }

        });
    }

}

updateCapitals();





const m = 60e3,
      h = 36e5,
      d = 864e5,
      w = 6048e5,
      n = 2629743830,
      y = 31556926e3,
      c = 31556926e5,
      o = 31556926e6,
      g = 713186527e10;


function updateTimestamps() {

    document.querySelectorAll('.timestamp').forEach( function(t) {

        var passed = Date.now() - t.getAttribute('time');

        if      (passed < m) t.innerText =                          "very recently";
        else if (passed < h) t.innerText = Math.floor( passed/m ) + " minutes ago";
        else if (passed < d) t.innerText = Math.floor( passed/h ) + " hours ago";
        else if (passed < w) t.innerText = Math.floor( passed/d ) + " days ago";
        else if (passed < n) t.innerText = Math.floor( passed/w ) + " weeks ago";
        else if (passed < y) t.innerText = Math.floor( passed/n ) + " months ago";
        else if (passed < c) t.innerText = Math.floor( passed/y ) + " years ago";
        else if (passed < o) t.innerText = Math.floor( passed/c ) + " centuries ago";
        else if (passed < g) t.innerText = Math.floor( passed/o ) + " millennia ago";
        else                 t.innerText = Math.floor( passed/g ) + " galactic years ago";

        // #futureproofing xD. Long live the TagCoin

    });
}

if (SHOW_TIMESTAMP) setInterval(updateTimestamps, 20e3);