// ==UserScript==
// @name LuoguEmojiSender
// @namespace https://github.com/Maxmilite/LuoguEmojiSender
// @version 1.4.4
// @description 一款可以帮助您在洛谷轻松发送 QQ 表情信息的插件.
// @author Maxmilite
// @match https://www.luogu.com.cn/*
// @match http://www.luogu.com.cn/*
// @grant unsafeWindow
// @require https://code.jquery.com/jquery-2.1.1.min.js
// ==/UserScript==
(function () {
// 这是第一代 LuoguEmojiSender 的最终版本,内容已经相当完善,此后作者将会着力于第二代的开发,第一代基本不会更新。
// 最后更新时间 2021.6.14
// 最后版本 1.4.4
// 第二代目标:实现图形化,近似于 QQ 发送表情
// 作者在这个版本留下了一个臭了的彩蛋
// 1.1 更新内容:
// 优化操作逻辑,增加用户配置区
// 1.2 更新内容:
// 增加了更多的 QQ 图片,更改了图床
// 1.3 更新内容:
// 进一步优化操作逻辑,修复了图片加载的一个BUG,现在可以无忧无虑使用无缝模式了
// 1.3.1 更新内容:
// 紧急修复一个由菜刀表情引发的严重BUG
// 1.4 更新内容:
// 修复了 1.3.1 版本更新日志版本号的bug,修复输入问题,第一代最终版本
// 修复光标漂移问题,修复无缝衔接问题,修复菜刀表情问题,修复若干问题
// 1.4.1 更新内容:
// 更换表情源,增加 “替换表情” 按钮,具体详见说明文档
// 1.4.2 更新内容:
// 修复一个无缝模式的 bug,添加了部分表情
// 1.4.3 更新内容:
// 增加了一个开关自动替换按钮,现在您可以自行决定是否自动替换文中内容了,修复了一个bug,更新了雀魂表情库
// 1.4.4 更新内容:
// 增加设置菜单,现在允许对插件进行有关设置了,所有设置将会保存在本地,不会因为版本更新而丢失;增加快速查询表情按钮
var functionIsOn = true, seamlessMode = false, queryIsOn = true;
var prefix = "{", suffix = "}";
var userElement = {
"样例1": "XXX1.com",
"样例2": "XXX2.com",
"样例3": "XXX3.com",
"样例4": "XXX4.com"
};
const replaceElement = {
"/ybyb": "",
"/wosl": "",
"/hs": "",
"/psj": "",
"/na": "",
"/bx": "",
"/qdqd": "",
"/zy": "",
"/nqct": "",
"/nzqk": "",
"/mjl": "",
"/gun": "",
"/cb": "",
"/my": "",
"/mwbq": "",
"/kx": "",
"/jl": "",
"/wyx": "",
"/ww": "",
"/mdfq": "",
"/banzz": "",
"/mgx": "",
// ----------------- 以上为 1.4.2 更新内容 -----------------
"/aini": "",
"/aiq": "",
"/am": "",
"/azgc": "",
"/baiy": "",
"/bangbangt": "",
"/baojin": "",
"/bb": "",
"/bkx": "",
"/bl": "",
"/bobo": "",
"/bp": "",
"/bq": "",
"/bs": "",
"/bt": "",
"/bu": "",
"/bz": "",
// "/cd": "",
"/cengyiceng": "",
"/cg": "",
"/ch": "",
"/chi": "",
"/cj": "",
"/cp": "",
"/cs": "",
"/cy": "",
"/dan": "",
"/dao": "",
"/db": "",
"/dg": "",
"/dgg": "",
"/dk": "",
"/dl": "",
"/doge": "",
"/dx": "",
"/dy": "",
"/dz": "",
"/ee": "",
"/emm": "",
"/fad": "",
"/fade": "",
"/fan": "",
"/fd": "",
"/fendou": "",
"/fj": "",
"/fn": "",
"/fw": "",
"/gg": "",
"/gy": "",
"/gz": "",
"/hanx": "",
"/haob": "",
"/hb": "",
"/hc": "",
"/hd": "",
"/hec": "",
"/hhd": "",
"/hn": "",
"/hp": "",
"/hq": "",
"/hsh": "",
"/ht": "",
"/huaix": "",
"/hx": "",
"/jd": "",
"/jh": "",
"/jiaybb": "",
"/jiaybs": "",
"/jie": "",
"/jk": "",
"/jw": "",
"/jx": "",
"/jy": "",
"/ka": "",
"/kb": "",
"/kel": "",
"/kf": "",
"/kg": "",
"/kk": "",
"/kl": "",
"/kt": "",
"/kuk": "",
"/kun": "",
"/kzht": "",
"/lb": "",
"/lengh": "",
"/lh": "",
"/ll": "",
"/lm": "",
"/lq": "",
"/lw": "",
"/lyj": "",
"/meigui": "",
"/mm": "",
"/ng": "",
"/nkt": "",
"/oh": "",
"/oy": "",
"/pch": "",
"/pj": "",
"/pp": "",
"/pt": "",
"/px": "",
"/qd": "",
"/qiang": "",
"/qiao": "",
"/qq": "",
"/qt": "",
"/ruo": "",
"/sa": "",
"/se": "",
"/sh": "",
"/shd": "",
"/shl": "",
"/shuai": "",
"/shui": "",
"/shxi": "",
"/sr": "",
"/tiao": "",
"/tl": "",
"/tnl": "",
"/tp": "",
"/ts": "",
"/tsh": "",
"/tt": "",
"/tuu": "",
"/tx": "",
"/taiyang": "",
"/tyt": "",
"/wbk": "",
"/whl": "",
"/wl": "",
"/wn": "",
"/wq": "",
"/ws": "",
"/wul": "",
"/wx": "",
"/wzm": "",
"/xhx": "",
"/xia": "",
"/xig": "",
"/xin": "",
"/xjj": "",
"/xk": "",
"/xs": "",
"/xu": "",
"/xw": "",
"/xy": "",
"/xyx": "",
"/yao": "",
"/yb": "",
"/yhh": "",
"/yiw": "",
"/yl": "",
"/youl": "",
"/youtj": "",
"/yt": "",
"/yun": "",
"/yx": "",
"/zhd": "",
"/zhem": "",
"/zhh": "",
"/zhm": "",
"/zhq": "",
"/zj": "",
"/zk": "",
"/zq": "",
"/zt": "",
"/zuotj": "",
"/114514": "[](https://github.com/Maxmilite/LuoguEmojiSender)",
"/maj-1!": "",
"/maj-2!": "",
"/maj-3!": "",
"/maj-4!": "",
"/maj-5!": "",
"/maj-6!": "",
"/maj-7!": "",
"/maj-8!": "",
"/maj-9!": "",
"/maj-10!": "",
"/maj-11!": "",
"/maj-12!": "",
"/maj-13!": "",
"/maj-14!": "",
"/maj-15!": "",
"/maj-16!": "",
"/maj-17!": "",
"/maj-18!": "",
"/maj-19!": "",
"/maj-20!": "",
"/maj-21!": "",
"/maj-22!": "",
"/maj-23!": "",
"/maj-24!": "",
"/maj-25!": "",
"/maj-26!": "",
"/maj-27!": "",
"/maj-28!": "",
"/maj-29!": "",
"/maj-30!": "",
"/maj-31!": "",
"/maj-32!": "",
"/maj-33!": "",
"/maj-34!": "",
"/maj-35!": "",
"/maj-36!": "",
"/maj-37!": "",
"/maj-38!": "",
"/maj-39!": "",
"/maj-40!": "",
"/maj-41!": "",
"/maj-42!": "",
"/maj-43!": "",
"/maj-44!": "",
"/maj-45!": "",
"/maj-46!": "",
"/maj-47!": "",
"/maj-48!": "",
"/maj-49!": "",
"/maj-50!": "",
"/maj-51!": "",
"/maj-52!": "",
"/maj-53!": "",
"/maj-54!": "",
"/maj-55!": "",
"/maj-56!": "",
"/maj-57!": "",
"/maj-58!": "",
"/maj-59!": "",
"/maj-60!": "",
"/maj-61!": "",
"/maj-62!": "",
"/maj-63!": "",
"/maj-64!": "",
"/maj-65!": "",
"/maj-66!": "",
"/maj-67!": "",
"/maj-68!": "",
"/maj-69!": "",
"/maj-70!": "",
"/maj-71!": "",
"/maj-72!": "",
"/maj-73!": "",
"/maj-74!": "",
"/maj-75!": "",
"/maj-76!": "",
"/maj-77!": "",
"/maj-78!": "",
"/maj-79!": "",
"/maj-80!": "",
"/maj-81!": "",
"/maj-82!": "",
"/maj-83!": "",
"/maj-84!": "",
"/maj-85!": "",
"/maj-86!": "",
"/maj-87!": "",
"/maj-88!": "",
"/maj-89!": "",
"/maj-90!": "",
"/maj-91!": "",
"/maj-92!": "",
"/maj-93!": "",
"/maj-94!": "",
"/maj-95!": "",
"/maj-96!": "",
"/maj-97!": "",
"/maj-98!": ""
};
const $ = unsafeWindow.$ || jQuery, markdownPalettes = unsafeWindow.markdownPalettes;
function ShowTip(tip, type) {
var $tip = $('#tip');
if ($tip.length == 0) {
$tip = $('<span id="tip" style="position:fixed; top:50px; left: 50%; z-index:9999; height: 35px; padding: 0 20px; line-height: 35px; background-color: white; border: 5px; opacity: 75%"></span>');
$('body').append($tip);
}
$tip.stop(true).prop('class', 'alert alert-' + type).text(tip).css('margin-left', -$tip.outerWidth() / 2).fadeIn(250).delay(500).fadeOut(250);
}
function ShowMsg(msg) {
ShowTip(msg, 'info');
}
function ShowSuccess(msg) {
ShowTip(msg, 'success');
}
function ShowFailure(msg) {
ShowTip(msg, 'danger');
}
function ShowWarn(msg, $focus, clear) {
ShowTip(msg, 'warning');
if ($focus) {
$focus.focus();
if (clear) $focus.val('');
}
return false;
}
function getSubString(sourceString = "", findPos = -1) {
if (findPos == -1) {
return "zr.tk";
}
if (findPos <= 5) {
return "";
}
let resultString = "";
for (let i = findPos - 5; i < findPos; i++) {
resultString += sourceString[i];
}
// if (resultString == "tps:/") {
// return "9zr.tk";
// }
return resultString;
}
function sliceString(sourceString = "", leftSide = 0, rightSide = 0) {
let resultString = ""
for (let i = leftSide; i <= rightSide; i++) {
resultString += sourceString[i];
}
return resultString;
}
function replaceString(stringToChange = "") {
let isChanged = false;
for (let i in replaceElement) {
let changedStr = prefix + i + suffix;
while (getSubString(stringToChange, stringToChange.lastIndexOf(changedStr)) != "zr.tk" && getSubString(stringToChange, stringToChange.lastIndexOf(changedStr)) != "jsoul") {
// console.log(getSubString(stringToChange, stringToChange.lastIndexOf(changedStr)))
isChanged = true;
// stringToChange = stringToChange.replace(changedStr, replaceElement[i]);
stringToChange = sliceString(stringToChange, 0, stringToChange.lastIndexOf(changedStr) - 1) + replaceElement[i] + sliceString(stringToChange, stringToChange.lastIndexOf(changedStr) + changedStr.length, stringToChange.length - 1);
}
}
for (let i in userElement) {
let changedStr = prefix + i + suffix;
while (getSubString(stringToChange, stringToChange.lastIndexOf(changedStr)) != "zr.tk" && getSubString(stringToChange, stringToChange.lastIndexOf(changedStr)) != "jsoul") {
isChanged = true;
// stringToChange = stringToChange.replace(changedStr, userElement[i]);
stringToChange = sliceString(stringToChange, 0, stringToChange.lastIndexOf(changedStr) - 1) + userElement[i] + sliceString(stringToChange, stringToChange.lastIndexOf(changedStr) + changedStr.length, stringToChange.length - 1);
}
}
if (isChanged == true) {
return stringToChange;
}
else {
return undefined;
}
}
function main() {
if (functionIsOn == false) {
return;
}
if (typeof markdownPalettes != "undefined") {
let changedStr = replaceString($(".CodeMirror-wrap textarea").val());
if (changedStr != undefined) {
$(".CodeMirror-wrap textarea").val(changedStr);
$(".CodeMirror-wrap textarea").trigger("input");
}
}
if (document.getElementById("feed-content") != null) {
let changedStr = replaceString(document.getElementById("feed-content").value);
if (changedStr != undefined) {
document.getElementById("feed-content").value = changedStr;
}
}
}
function replaceAll() {
if (replaceString(markdownPalettes.content) != undefined) {
markdownPalettes.content = replaceString(markdownPalettes.content);
ShowSuccess("文中所有表情已手动替换");
return;
}
else {
return;
}
}
function writeConfig() {
lsSet("LuoguEmojiSenderPrefix", prefix);
lsSet("LuoguEmojiSenderSuffix", suffix);
lsSet("LuoguEmojiSenderIsOn", JSON.stringify(functionIsOn));
lsSet("LuoguEmojiSenderIsSeamless", JSON.stringify(seamlessMode));
lsSet("LuoguEmojiSenderQueryIsOn", JSON.stringify(queryIsOn));
lsSet("LuoguEmojiSenderUserElements", JSON.stringify(userElement, null, 4));
}
function readConfig() {
prefix = lsGet("LuoguEmojiSenderPrefix");
suffix = lsGet("LuoguEmojiSenderSuffix");
functionIsOn = JSON.parse(lsGet("LuoguEmojiSenderIsOn"));
seamlessMode = JSON.parse(lsGet("LuoguEmojiSenderIsSeamless"));
queryIsOn = JSON.parse(lsGet("LuoguEmojiSenderQueryIsOn"));
if (seamlessMode == true) {
prefix = "", suffix = "";
}
userElement = JSON.parse(lsGet("LuoguEmojiSenderUserElements"));
}
function lsGet(x = "") {
return unsafeWindow.localStorage.getItem(x);
}
function lsSet(x = "", content = "") {
unsafeWindow.localStorage.setItem(x, content);
}
var setting = new Object;
setting.openSetting = function () {
$("#lesSettingDiv").slideToggle();
if (seamlessMode == true) {
document.getElementsByName("seamless")[0].click();
}
else {
document.getElementsByName("seamless")[1].click();
}
if (queryIsOn == true) {
document.getElementsByName("query")[0].click();
}
else {
document.getElementsByName("query")[1].click();
}
}
setting.cancelSetting = function () {
if (confirm("当前设置未保存,确认取消?") == true) {
location.reload();
}
}
setting.saveSetting = function () {
if (confirm("确认保存当前设置?") == true) {
seamlessMode = $("[name='seamless']")[0].checked;
userElement = JSON.parse($("#userElementBox")[0].value);
prefix = $("[name='prefix']")[0].value;
suffix = $("[name='suffix']")[0].value;
queryIsOn = $("[name='query']")[0].checked;
writeConfig();
alert("保存成功");
location.reload();
}
}
function init() {
$(`<li data-v-6d5597b1 id="replaceEmoji">
<a data-v-6d5597b1="" title="手动替换表情" unselectable="on">
<img style="margin: 2px 0; padding: 0; inline-size: 22px; align-items: center; justify-content: center" src="https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/285/grinning-face-with-sweat_1f605.png">
</a>
</li>`).appendTo($(".mp-editor-menu"));
$("#replaceEmoji").on("click", function () {
replaceAll();
});
if (localStorage.getItem("LuoguEmojiSender") == null) {
localStorage.setItem("LuoguEmojiSender", "哇这里竟然有彩蛋");
writeConfig();
ShowSuccess("自动发表情插件初始化完毕");
}
else {
readConfig();
}
if (functionIsOn == true) {
addOnButton();
}
else {
addOffButton();
}
document.addEventListener("input", function () {
main();
})
if (document.getElementById("feed-content") != null) {
$(` <p>
<strong>插件设置</strong><br>
<a id="LuoguEmojiSenderSetting" target="_blank">LuoguEmojiSender 设置</a>
<br>
</p>`).appendTo($(".am-hide-sm"));
$("#LuoguEmojiSenderSetting").on("click", function () {
setting.openSetting();
});
$(`
<div id="lesSettingDiv" style="display: none">
<p style="text-align: center; font-weight: bold">LuoguEmojiSender 设置</p>
<hr>
<form name="lesSetting">
<strong>
无缝模式:
</strong>
<input type="radio" name="seamless" value="enabled" >启用</input>
<input type="radio" name="seamless" value="disabled" >禁用</input>
<br>
自定义前缀: <input type="text" name="prefix" style="width: 100px; text-align: center">
<br>
自定义后缀: <input type="text" name="suffix" style="width: 100px; text-align: center">
<br>
<strong>
查表情按钮:
</strong>
<input type="radio" name="query" value="enabled" >启用</input>
<input type="radio" name="query" value="disabled" >禁用</input>
<br>
用户定义表情:<br>
<textarea style="height: 200px; font-size: 12px; width: 218.59px; padding: 10px" id="userElementBox"></textarea>
<p style="text-align: center; color: red; user-select:none;" id=resetPlugin>点击重置 LuoguEmojiSender</p>
</form>
<br>
<button id="saveSetting" onclick="setting.saveSetting()" style="color: white; background-color: #dd514c; border: none; width: 80px; height: 32px; font-size: 16px; float: left;">
保存
</button>
<button id="cancelSetting" onclick="setting.cancelSetting()" style="color: white; background-color: #dd514c; border: none; width: 80px; height: 32px; font-size: 16px; float: right;">
取消
</button>
<br>
</div>
`).appendTo($(".am-hide-sm"));
$("#saveSetting").on("click", function () {
setting.saveSetting();
});
$("#cancelSetting").on("click", function () {
setting.cancelSetting();
});
$("#resetPlugin").on("click", function () {
if (prompt("您确定要重置 LuoguEmojiSender 设置吗?如果确定,请在下面的输入框里输入 “确定” 两个汉字:") == "确定") {
localStorage.removeItem("LuoguEmojiSender");
alert("重置完成")
location.reload();
}
});
$("#userElementBox")[0].value = JSON.stringify(userElement, null, 4);
$("[name='seamless']").on("click", function () {
if ($("[name='seamless']")[1].checked == true) {
$("[name='prefix']")[0].disabled = false;
$("[name='suffix']")[0].disabled = false;
$("[name='prefix']")[0].value = prefix;
$("[name='suffix']")[0].value = suffix;
}
else {
$("[name='prefix']")[0].disabled = true;
$("[name='suffix']")[0].disabled = true;
$("[name='prefix']")[0].value = "";
$("[name='suffix']")[0].value = "";
}
});
}
if ((markdownPalettes != undefined || document.getElementById("feed-content") != null) && queryIsOn == true) {
$(`
<div id="queryButton" style="margin: 0; padding: 0; position: fixed; width: 100px; height: 32px; right: 0px; bottom: 50vh; color: black; background: white; opacity: 80%;">
<a href="https://maxmilite.gitee.io/archive/emoji-library.html" target="_blank"><p style="text-align: center; padding: 4px 0; margin: 0; font-size: 16px; user-select:none; color: black">查看表情</p></a>
</div>
<div id="switchQuery" style="margin: 0; padding: 0; position: fixed; width: 20px; height: 32px; right: 100px; bottom: 50vh; color: black; background: white; opacity: 80%;">
<p style="text-align: center; padding: 4px 4px; margin: 0;font-size: 16px; user-select:none;" id="switchContent">></p>
</div>
`).appendTo($("body")[0]);
$("#switchQuery").on("click", function () {
if ($("#queryButton").width() != 0) {
$("#queryButton").animate({width: "0px"});
$("#switchQuery").animate({right: "0px"});
$("#switchContent")[0].innerText = "<";
}
else {
$("#queryButton").animate({width: "100px"});
$("#switchQuery").animate({right: "100px"});
$("#switchContent")[0].innerText = ">";
}
});
}
if (markdownPalettes != undefined || document.getElementById("feed-content") != null) {
ShowSuccess("自动发表情插件已加载完毕");
}
}
function addOffButton() {
if (document.getElementById("buttonOff") != null) {
document.getElementById("buttonOff").remove();
ShowSuccess("自动替换表情功能已关闭");
}
$(`<li data-v-6d5597b1 id="buttonOn">
<a data-v-6d5597b1="" title="开启自动替换" unselectable="on">
关
</ a>
</li>`).appendTo($(".mp-editor-menu"));
$("#buttonOn").on("click", function () {
addOnButton();
});
functionIsOn = false;
writeConfig();
}
function addOnButton() {
if (document.getElementById("buttonOn") != null) {
document.getElementById("buttonOn").remove();
ShowSuccess("自动替换表情功能已开启");
}
$(`<li data-v-6d5597b1 id="buttonOff">
<a data-v-6d5597b1="" title="关闭自动替换" unselectable="on">
开
</a>
</li>`).appendTo($(".mp-editor-menu"));
$("#buttonOff").on("click", function () {
addOffButton();
});
functionIsOn = true;
writeConfig();
}
// It seemed this function didn't work :(
// To be fixed
// Fixed on 2021.5.30
init();
})();