Maverick / Goals Manager

// ==UserScript==
// @name         Goals Manager
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  script for collecting users' statistics | visit https://smartprogress.do/post/4024944 | (c) Blogger 2015 aka Maverick, 2020
// @author       languageblog96@gmail.com
// @match        https://smartprogress.do/goal/*
// @grant        none
// @require      https://cdn.jsdelivr.net/npm/chart.js@2.8.0
// @license      MIT
// @updateURL    https://openuserjs.org/install/Maverick/Goals_Manager.user.js
// ==/UserScript==

(function() {
    'use strict';
    window.onload = async () => {
        const PRINT_STATS = false // Whether or not to print the overall statistics in the console
        const LINK = 'https://smartprogress.do/post/4024944' // Link to my post
        const GOALS_ARR = {
            "323684" : "1 000 000 приседаний",
            "295640" : "1 000 000 отжиманий. Продолжение",
            "327035" : "1 000 000 отжиманий на брусьях",
            "266116" : "100 000 подтягиваний на перекладине",
            "114389" : "1 000 000 прочитанных страниц",
            "343508" : "Ешь слона по кусочкам - ежедневно читать по 10 минут! Командная. Присоединяйтесь!",
            "341025" : "2 сезон - 450 000 Книжных страниц. Читаем по 20 страниц в день [Командная цель]",
            "308260" : "50 000 минут занятий на пресс (командная)",
            "357460" : "'Съедим' 20 000 помодоро вместе (Продолжение) Присоединяйтесь!",
            "361568" : "100 000 минут на наведение чистоты и порядка (командная) - 2",
            "358598" : "100 000 минут на приготовление пищи (командная, групповая)",
            "368916" : "Дневник успеха.Сезон 2 [Командная цель]",
            "329170" : "50 000 минут ухода за собой (командная цель)",
            "331883" : "Групповая программа II сезон - 1 000 000 очков на LinguaLeo",
            "356969" : "Выучить 10000 иностранных слов (Командная, групповая)",
            "348638" : "2 Сезон - 100 000 000 Груповых Шагов - [Групповая]",
        }

        if (window.GOALS_DATA == null || window.USER == null) return
        const USER_ID = window.USER.id.toString()
        const GOAL_ID = window.GOALS_DATA[Object.keys(window.GOALS_DATA)[0]].id
        if (Object.keys(GOALS_ARR).includes(GOAL_ID)){
            magicInput()
            showStats()
        }

        var json // Json data where all posts will be stored

        // Creates list of users and ranks them
        async function showStats(){

            // BarChart
            var canvas = document.createElement('canvas')
            var ctx = canvas.getContext('2d');
            let data = {
                labels: [], // Will be filled later
                datasets: [{
                    barPercentage: 0.5,
                    barThickness: 6,
                    maxBarThickness: 8,
                    minBarLength: 2,
                    data: [] // Will be filled later
                }]
            }
            let options = {
                legend: { display: false },
                maintainAspectRatio: false,
                scales: {
                    xAxes: [{
                        gridLines: {
                            offsetGridLines: true
                        }
                    }]
                },
                title: {
                    display: true,
                    text: 'Top 10'
                }
            }

            // Creating DOM elements
            var sort_div = document.getElementsByClassName("post-sort")[0] // Div on the right side
            var points_div = document.createElement('div') // Div with users
            var select_div = document.getElementsByClassName('_3tR2Mvw7Q0')[0].cloneNode(true)
            var select = select_div.getElementsByTagName('select')[0]
            var label = select_div.getElementsByTagName('label')[0]
            var span = select_div.getElementsByTagName('span')[0]
            var info = document.createElement('div') // Users' posts
            var about = document.createElement('div') // Link to my goal :)
            var blogger = document.createElement('span')

            // Configuring DOM
            canvas.style.maxHeight = '200px'
            points_div.classList.add('post-sort__wrap')
            span.innerHTML = 'loading...'
            info.classList.add('post-sort__wrap')
            info.style.paddingTop = '20px'
            info.style.maxHeight = '100px'
            info.style.overflowY = 'scroll'
            about.classList.add('post-sort__wrap')
            about.style.paddingTop = '20px'
            about.style.paddingBottom = '20px'
            about.innerHTML = '<hr>'
            label.innerHTML = 'Пользователи оставившие комментарий'
            blogger.classList.add('action')
            blogger.style.fontSize = '11px'
            blogger.style.float = 'right'
            blogger.innerHTML = 'Blogger 2015'
            blogger.onclick = () => {window.open(LINK)}
            blogger.title = 'Поддержите)'

            points_div.appendChild(select_div)
            sort_div.appendChild(points_div)
            sort_div.appendChild(info)
            sort_div.appendChild(canvas)
            sort_div.appendChild(about)
            about.appendChild(blogger)

            for(let i = select.options.length - 1 ; i >= 0 ; i--){
                select.remove(i);
            }

            // Getting the last post's id
            let last_few_posts
            let response = await fetch('https://smartprogress.do/blog/getPosts?sorting=new_top&start_id=0&end_id=0&step_id=0&only_author=0&change_sorting=1&obj_id=' + GOAL_ID + '&obj_type=0')

            if (response.ok) {
                last_few_posts = await response.json();
            } else {
                points_div.innerHTML = 'Error'
                return
            }

            const LAST_ID = last_few_posts.blog[0].id

            // Getting all posts for current goal
            response = await fetch('https://smartprogress.do/blog/getPosts?sorting=old_top&start_id=0&end_id=' + (LAST_ID+1000) + '&step_id=0&only_author=0&change_sorting=1&obj_id=' + GOAL_ID + '&obj_type=0')
            if (response.ok) {
                json = await response.json();
            } else {
                points_div.innerHTML = 'Error'
                return
            }

            // Filtering unique users and sorting them
            let members_list = json.blog.filter((user, index, self) =>{
                return index === self.findIndex((t) => {
                    return t.user_id === user.user_id && t.username === user.username
                })
            })
            members_list.sort((el1, el2) => el1.username.localeCompare(el2.username))

            // Adding users to select
            members_list.forEach((user, index) => {
                let el = document.createElement("option")
                el.textContent = user.username
                el.value = user.username
                el.id = user.user_id
                el.username = user.username
                select.appendChild(el)
                user.reps_arr = []
            })
            select.options[0].selected = true
            span.innerHTML = select.value

            // Collecting data for each user
            let temp
            for (let i = 0; i < json.blog.length; i++){
                let user_id = json.blog[i].user_id
                let username = json.blog[i].username
                let user_index_in_member_list = members_list.findIndex(el => el.user_id == user_id)
                let current_result = 0
                let next_post = i < json.blog.length - 1 ? json.blog[i+1].id : '#'
                let prev_post = i > 0 ? json.blog[i-1].id : '#'
                let current_post_id = json.blog[i].id
                let result = getResult(i)
                if ((result.last - result.first) == result.sum || (result.last - result.first) == result.product){
//                     if (i > 0 && temp.last == result.first){
                    if (result.sum < result.first || i < 30){ // e.g. someone writes 10 + 1000 = 1010 when it shoud be 1000 + 10 = 1010
                        current_result = result.last - result.first
                    }
                }
                else{
                    // console.log(result.last - result.first + '<a href = "https://smartprogress.do/post/' + json.blog[i].id + '"></a>')
                }
                members_list[user_index_in_member_list].reps_arr.push({
                    'result' : current_result,
                    'next_post' : next_post,
                    'prev_post' : prev_post,
                    'current_post_id' : current_post_id,
                    'msg' : json.blog[i].msg,
                })

                temp = result
                }

            // Sorting users by total reps count
            members_list.sort((a, b) => {
                function getSum(arr){
                    let sum = 0
                    for (let i = 0; i < arr.length; i++)
                    {
                        sum += arr[i].result
                    }
                    return sum
                }
                let sum1 = getSum(a.reps_arr)
                let sum2 = getSum(b.reps_arr)
                return sum2 - sum1
            })


            for (let i = 0; i < 10 && i < members_list.length; i++){
                data.labels.push(members_list[i].username)
                let sum = 0
                for (let j = 0; j < members_list[i].reps_arr.length; j++)
                {
                    sum += members_list[i].reps_arr[j].result
                }
                data.datasets[0].data.push(sum)
                
            }

            // Creates chart
            var myBarChart = new Chart(ctx, {
                type: 'horizontalBar',
                data: data,
                options: options
            })

            if (PRINT_STATS == true){
                let stats = ''
                for (let i = 0; i < members_list.length; i++){
                    let sum = 0
                    for (let j = 0; j < members_list[i].reps_arr.length; j++){
                        sum += members_list[i].reps_arr[j].result
                    }
                    stats += '<p><a href = "https://smartprogress.do/user/' + members_list[i].user_id + '">' + members_list[i].username + '</a> - ' + sum + '</p>'
                }
                console.log(stats)
            }

            // Displaying user's posts
            select.onchange = () => {
                info.innerHTML = ''
                span.innerHTML = select.value
                let user_id = select.options[select.selectedIndex].id
                let username = select.options[select.selectedIndex].username
                let user_index_in_member_list = members_list.findIndex(el => el.user_id == user_id)
                let sum = 0
                for (let j = 0; j < members_list[user_index_in_member_list].reps_arr.length; j++){
                    let current_result = members_list[user_index_in_member_list].reps_arr[j].result
                    let last = members_list[user_index_in_member_list].reps_arr[j].last
                    let first = members_list[user_index_in_member_list].reps_arr[j].first
                    sum += Number(current_result)
                    let prev_post = members_list[user_index_in_member_list].reps_arr[j].prev_post
                    let next_post = members_list[user_index_in_member_list].reps_arr[j].next_post
                    let current_post_id = members_list[user_index_in_member_list].reps_arr[j].current_post_id
                    let msg = members_list[user_index_in_member_list].reps_arr[j].msg

                    info.innerHTML += '<a href = "/post/' + current_post_id + '">Запись # ' + (j+1) + '</a> (+' +
                        current_result + ')</h3><br>' +
//                         '<a href = "/post/' + prev_post + '"><</a> <a href = "/post/' + next_post + '">></a>' +
                        msg + '<br>'
                }
                info.innerHTML += '<br>Всего: ' + sum
            }
            select.onchange()
        }

        // Creates input for adding new results
        async function magicInput(){
            if (window.GOALS_DATA[GOAL_ID].team_users_list.includes(USER_ID)){
                // Creating DOM elements
                var redactor = document.getElementsByClassName('redactor_editor')[0]
                //var form = document.getElementsByClassName('_3yHbNGC-vy')[0]//.getElementsByTagName('button')[0].parentNode
                var span = document.createElement('span')
                var input = document.createElement('input')
                var redactor_btn = document.getElementsByClassName('redactor_btn')[1]

                // Configuring DOM
                input.style.cssText = 'width: 100px;padding: 12px 20px;margin: 8px 0;display: inline-block;border: 1px solid #ccc;border-radius: 4px;box-sizing: border-box;'
                input.type = 'number'
                input.min = 0
                input.value = 0
                input.id = 'magic'

                span.classList.add('submit-helper--send')
                span.classList.add('submit-helper')

                var onchange = () => {
                    if (input !== document.activeElement){
                        try{
                            let form = document.getElementsByClassName('_3yHbNGC-vy')[0].getElementsByTagName('button')[0].parentNode
                            form.appendChild(input)
                            form.appendChild(span)
                            redactor_btn = document.getElementsByClassName('redactor_btn')[1]
                            redactor = document.getElementsByClassName('redactor_editor')[0]
                        }
                        catch(err){}
                    }
                }
                var timer = setInterval(onchange, 2000)

                let response = await fetch('https://smartprogress.do/blog/getPosts?sorting=new_top&start_id=0&end_id=0&step_id=0&only_author=0&change_sorting=1&obj_id=' + GOAL_ID + '&obj_type=0')

                if (response.ok) {
                    json = await response.json();
                } else {
                    return
                }

                let last_result = getResult(0).last

                if (last_result != 0){
                    input.oninput = () => write_post (last_result, Number(input.value))

                    input.onmouseout = () => {
                        redactor_btn.click()
                        redactor_btn.click()
                    }

                    span.innerHTML = ''
                }
                else
                {
                    span.innerHTML = 'Error'
                }

                function put_whitespaces(number){
                    let result = ""
                    let str = number.toString()
                    while (str.length > 0)
                    {
                        result = str.substring(str.length-3,str.length) + " " + result;
                        str = str.substring(str.length-6, str.length-3);
                    }
                    return result
                }
                function write_post(last_result, value){
                    if (value > 0)
                    {
                        redactor.innerHTML = '<h3>&#8203;' + put_whitespaces(last_result) + ' + ' + put_whitespaces(value) + ' = ' + put_whitespaces(last_result + value) +'</h3>'
                    }
                    else
                    {
                        redactor.innerHTML = ''
                    }
                }
            }
        }

        // Getting result
        function getResult(post_index){
            function filter(str){
                return str.includes("+") && str.includes("=")// && !str.toLowerCase().includes("л") && !str.toLowerCase().includes("с")
            }
            function getLastNumberInString(str){
                return str.replace(/\s+/g,'').split(/\D+/g).filter(el => el.length >0).pop()
            }

            let index = json.blog.findIndex(el => el.id == post_index)
            let message = json.blog[post_index].msg
            let message_initial = message
            let user_id = json.blog[post_index].user_id
            try{
                message = message.match(/<[^> ]+[^>]*>[^<]*/g).map(el => el.replace(/<\/?[^>]+(>|$)/g, "")).filter(filter)

                let max_result = Math.max.apply(null, message.map(el => getLastNumberInString(el)))

                message = message.filter(el => getLastNumberInString(el) == max_result)

                if (message.length == 1){
                    let msg = message[0]
                    let arr = msg.replace(/\s+/g,'').split(/\D+/g).filter(el => el.length >0).map(Number)
                    let last = Number(arr.pop())
                    let first = Number(arr.shift())
                    let sum = arr.reduce((a, b) => a + b, 0)
                    let product = Number(arr.reduce((a, b) => a * b, 1))
                    return {"last" : last, "first" : first, "sum" : sum, "product" : product} // Returns values extracted from the message
                }
                else
                {
                    return 0 // If something goes wrong the result will be set to 0 :(
                }
            }
            catch(err){
                return 0
            }

        }
    }
})()