📝 千言万语

在任意网页都能留下自己的评论

// ==UserScript==
// @name         📝 千言万语
// @namespace    https://ez118.github.io/
// @version      0.2
// @description  在任意网页都能留下自己的评论
// @author       ZZY_WISU
// @match        https://*/*
// @match        http://*/*
// @connect      *
// @license      GNU GPLv3
// @icon         data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9InZlcnRpY2FsLWFsaWduOiBtaWRkbGU7ZmlsbDogY3VycmVudENvbG9yO292ZXJmbG93OiBoaWRkZW47IiB2aWV3Qm94PSIwIDAgMTA4OCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEwODggMHY3ODAuOGwtMjA0LjggNTEuMi0xNjAgMTc5LjJjLTYuNCA2LjQtMTkuMiAxMi44LTI1LjYgMTIuOC02LjQgMC0xMi44IDAtMTkuMi02LjQtMTIuOC0xMi44LTEyLjgtMzIgMC00NC44bDE3Mi44LTE5OC40IDE3OS4yLTQ0LjhWNjRINjR2NjU5LjJoNTc2YzE5LjIgMCAzMiAxMi44IDMyIDMycy0xMi44IDMyLTMyIDMySDBWMGgxMDg4ek0yNTYgMzg0YzAgMzguNCAyNS42IDY0IDY0IDY0czY0LTI1LjYgNjQtNjQtMjUuNi02NC02NC02NC02NCAyNS42LTY0IDY0eiBtMjI0IDBjMCAzOC40IDI1LjYgNjQgNjQgNjRzNjQtMjUuNiA2NC02NC0yNS42LTY0LTY0LTY0LTY0IDI1LjYtNjQgNjR6TTgzMiAzODRjMC0zOC40LTI1LjYtNjQtNjQtNjRzLTY0IDI1LjYtNjQgNjQgMjUuNiA2NCA2NCA2NCA2NC0zMiA2NC02NHoiIGZpbGw9IiM4MDgwODAiPjwvcGF0aD48L3N2Zz4=
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js
// @require      https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js
// ==/UserScript==

var currentServer = "http://localhost/";
var userInfo = { "name": "anonymous", "id": "1145141919810" };
var pageSize = 20;
var serverCfg = [];
var commentContainer;

function runAsync(url,send_type,data_ry) {
    var p = new Promise((resolve, reject)=> {
        GM_xmlhttpRequest({
            method: send_type, url: url, headers: {"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"}, data: data_ry,
            onload: function(response){resolve(response.responseText);}, onerror: function(response){resolve("response-error");}
        });
    });
    return p;
}

function getRequest(url, func) {
    runAsync(url,"GET","").then((result)=>{ return result; }).then(function(result){
        if(result == "response-error") { func(null) }
        func(result);
    });
}
function isTopWindow() {
    return window.self === window.top;
}

function escapeHtml(str) {
    const htmlEntities = {
        '&': '&',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;'
    };
    return str.replace(/[&<>"']/g, (match) => htmlEntities[match]);
}

function getCurrentUrl(){
    var url = window.location.href + "#";
    return url.split("#")[0];
}

function getServerConfig(url, func){
    getRequest(url, function(res){
        try{
            var result = JSON.parse(res);
            if(!result.info || !result.api) { alert("无法获得服务器基本信息"); return; }
            if(result.info.status != "online") { alert("服务器被配置为非工作状态,暂时无法连接"); alert("【公告】" + result.info.noticeboard); return; }
            result.info.url = url;
            func(result);
        } catch {
            alert("无法处理服务器传回的信息");
            return;
        }
    });
}

function getComments(url, servercfg, containerEle){
    var newUrl = servercfg.api.get_comments.replace("{{url}}", encodeURI(url)).replace("{{page_size}}", pageSize).replace("{{page_number}}", 1);
    getRequest(currentServer + "/" + newUrl, function(res){
        var result = JSON.parse(res);
        if(result.code != 0) { containerEle.innerHTML += "<b>服务器返回了错误信息</b><br><i>" + result.msg + "</i>"; return; }
        var html = "<h1>📟&nbsp;评论</h1><p class='notice'>【公告】" + escapeHtml(servercfg.info.noticeboard) + "</p>";
        for(let i = result.data.length - 1; i >= 0; i --) {
            html += `<div class="item">
                     <span class="username">` + escapeHtml(result.data[i].user_name) + `</span>&nbsp;<span class="commentid">[评论ID:` + escapeHtml(result.data[i].cid) + `]</span><br>
                     ` + escapeHtml(result.data[i].content) + `<br>
                     <span class="pubdate">` + escapeHtml(result.data[i].date) + `</span>
                 </div>`;
        }
        /* ▣◩ */
        containerEle.innerHTML = html;
    });
}

function sendComments(url, servercfg, content, username, userid){
    var newUrl = servercfg.api.submit_comments.replace("{{url}}", encodeURI(url)).replace("{{user_id}}", encodeURI(userid)).replace("{{user_name}}", encodeURI(username)).replace("{{content}}", encodeURI(content));
    getRequest(currentServer + "/" + newUrl, function(res){
        var result = JSON.parse(res);
        if(result.code == 0) { alert("已发送"); getComments(url, servercfg, commentContainer) }
        else { alert(result.msg) }
    });
}

function delComments(url, servercfg, commentid, userid){
    var newUrl = servercfg.api.delete_comments.replace("{{comment_id}}", encodeURI(commentid)).replace("{{user_id}}", encodeURI(userid));
    getRequest(currentServer + "/" + newUrl, function(res){
        var result = JSON.parse(res);
        if(result.code == 0) { alert("已删除"); }
        else { alert(result.msg) }
    });
}

var menu1 = GM_registerMenuCommand('用户设置', function () {
    try{ var origval = GM_getValue("userInfo").name; } catch { var origval = null; }
    var val = prompt("【用户设置】请设置用户名以使用脚本(请勿以敏感字符作为用户名)", origval ?? "");
    var gen_id = md5(val + Date.now()).substring(0,10);

    if(val == "" || val.length > 20) { alert("无效的用户名,请重试"); return; }
    else if(val == null) { return; }
    else { GM_setValue("userInfo", {"name": val, "id": gen_id}) }
}, 'u');

var menu2 = GM_registerMenuCommand('删除评论', function () {
    var val = prompt("【删除评论】请输入需要删除的评论ID(只能删除自己的评论)", "");
    if(val == "") { alert("无效的ID"); return; }
    else if(val == null) { return; }
    else { delComments(getCurrentUrl(), serverCfg, val, userInfo.id); }
}, 'd');

var menu3 = GM_registerMenuCommand('配置服务器', function () {
    try{ var origval = GM_getValue("serverInfo").info.url; } catch { var origval = null; }
    var val = prompt("【配置服务器】请输入一个有效的服务器链接\n(访问Github仓库获取官方测试服务器链接)", origval ?? "");

    getServerConfig(currentServer, function(server_cfg){
        GM_setValue("serverInfo", server_cfg);
        serverCfg = server_cfg;
        alert("【配置服务器】已连接到服务器。\n服务器信息:\n名称:" + server_cfg.info.name + ";\n描述:" + server_cfg.info.description + ";\n公告:" + server_cfg.info.noticeboard);
    });
}, 's');

(function() {
    'use strict';
    /* 如果页面在iframe内,则不执行脚本 */
    if(!isTopWindow()) { return; }
    /* 未设置用户名提示 */
    if(!GM_getValue("userInfo")) { alert("【千言万语】未设置用户名,请在该页面任意位置右键->Tampermonkey->评论一切->用户设置,自定义个人昵称/用户名,方可使用"); return; }
    else { userInfo = GM_getValue("userInfo"); }
    /* 未配置服务器提示 */
    if(!GM_getValue("serverInfo")) { alert("【千言万语】未设置服务器,请在该页面任意位置右键->Tampermonkey->评论一切->配置服务器,方可使用"); return; }
    else { serverCfg = GM_getValue("serverInfo"); }

    GM_addStyle(`
        body { -webkit-appearance: none !important; }
        .userscript-commentContainer{ position:fixed; top:5vh; right:-290px; bottom:5vh; z-index:9998; width:310px; height:40px; background:#e4e9f6d1; color:#333; transition: all .4s; border:1px solid #7282adba; overflow-x:hidden; overflow-y:scroll; border-radius:15px 0 0 15px; padding:5px; font-family:"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; }
        .userscript-commentContainer:hover{ right:-1px; height:calc(90vh - 10px); }
        .userscript-commentContainer h1{ font-size:large; font-weight:bold; margin:10px; user-select:none; }
        .userscript-commentContainer .notice{ font-size:medium; font-weight:light; padding:5px; border:1px solid #52add2; background:#bfecff; color:#3c98bd; word-wrap:break-word; word-break:normal; margin:5px 10px; width:260px; border-radius:10px; }
        .userscript-commentContainer .item{ font-size:medium; font-weight:light; padding: 5px; border:1px solid #969baa; margin: 5px 10px; word-wrap:break-word; word-break:normal; width:260px; background:#e1e6f5cd; border-radius:10px; user-select:text; }
        .userscript-commentContainer .item .pubdate{ font-size:small; color:#666; user-select:none; }
        .userscript-commentContainer .item .commentid{ font-size:small; color:#555; user-select:text; }
        .userscript-commentContainer .item .username{ font-size:medium; font-weight:bold; color:#173852; user-select:none; }
        .userscript-commentBtn { position:fixed; top:0px; right:-6vh; padding:5px; border:1px solid #969baa; border-radius:0 0 0 15px; z-index:9999; width:5vh; height:5vh; font-size:larger; background:#e1e6f5cd; cursor:pointer; transition:all .4s; }
        .userscript-commentBtn:hover { border:1px solid #138AF1; right:-1px; }
        .userscript-commentContainer:hover ~ .userscript-commentBtn { right:-1px; }
    `);

    /* 添加评论容器 */
    commentContainer = document.createElement("div");
    commentContainer.setAttribute("class", "userscript-commentContainer");
    commentContainer.setAttribute("id", "userscript-commentContainer");
    document.body.appendChild(commentContainer);
    commentContainer.innerHTML = "";

    /* 添加评论按钮 */
    var commentBtn = document.createElement("button");
    var commentBtnTxt = document.createTextNode("📝");
    commentBtn.setAttribute("class", "userscript-commentBtn");
    commentBtn.setAttribute("title", "发表评论");
    document.body.appendChild(commentBtn);
    commentBtn.appendChild(commentBtnTxt);

    /* 获取评论 */
    var url = getCurrentUrl();
    getComments(url, serverCfg, commentContainer);

    /* 发表评论 */
    $(commentBtn).click(function(){
        var val = prompt("输入评论内容", "");
        if(val){ sendComments(url, serverCfg, val, userInfo.name, userInfo.id); }
    });
})();