// ==UserScript==
// @name Kekeke Dice Bot
// @namespace https://greasyfork.org
// @version 1.2.1
// @description 在kekeke跑團用的骰子BOT,支援COC、NC、四則運算規則,以及下載跑團紀錄
// @author Pixmi
// @icon http://www.google.com/s2/favicons?domain=https://kekeke.cc/
// @include https://kekeke.cc/*
// @include https://www.kekeke.cc/*
// @license MIT
// @grant none
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// ==/UserScript==
/* jshint esversion: 6 */
$(window).on('load',function () {
'use strict';
var config;
// 檢查Local Storage的kekeke_dice_config是否存在
if (localStorage.getItem('kekeke_dice_config') === null) {
// 對config做初次設定
config = {
Bot_Name: 'BOT',
Auto_Time: 5,
Auto_Time_Check: true,
Auto_Logs: false,
Logs_Time: false,
Download_Time: '1'
};
localStorage.setItem('kekeke_dice_config', JSON.stringify(config));
} else {
// 舊版本升級新版本時,預防Local Storage出錯的檢查與設定
config = JSON.parse(localStorage.getItem('kekeke_dice_config'));
if (config.Auto_Time === undefined) { config.Auto_Time = 5; }
if (config.Auto_Time_Check === undefined) { config.Auto_Time_Check = true; }
if (config.Auto_Logs === undefined) { config.Auto_Logs = false; }
if (config.Logs_Time === undefined) { config.Logs_Time = false; }
if (config.Logs_Clear === undefined) { config.Logs_Clear = true; }
if (config.Download_Time === undefined || config.Download_Time == '0') { config.Download_Time = '1'; }
localStorage.setItem('kekeke_dice_config', JSON.stringify(config));
}
// 發言時間限制
setInterval(function(){
config = JSON.parse(localStorage.getItem('kekeke_dice_config'));
config.Auto_Time_Check = true;
localStorage.setItem('kekeke_dice_config', JSON.stringify(config));
},config.Auto_Time*1000+1);
setTimeout(function () {
// 給部分物件加上ID
$('table.SquareCssResource-chatRoom > tbody > tr:last').find('table').attr('id', 'ChatTable');
$('td.SquareCssResource-submitInputButton').find('button').attr('id','submit_btn');
// 增加dice bot設定的按鈕
$('<td><div style="padding: 0 1px;"><img id="DiceConfig" src=" data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAD9AAAA/QBJxjN1gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAS4SURBVHic7ZtPTFRHHMc/83i7FHYFq64RhAQRaAQE8SLx0ANJa/rHtqFgUguKkZrWxHpo06Q3br303DaNFQNJG1dDa7S3pkn/xHiiIq1plD8aKBAFIQoL7L/pAdgm6ltmcXgOdT+X3cz8fjO/+b55s9+XvIU0adI8y4iVJra0fFdkRz3HpeB1kFuA5zXWpcIkiDEhuRS1I1+cOfPOrZUMsiIBjjYFTwohPpOQtZJ83UjBjEB8+E1Hw+lUc1MWoLX53EcSPgfIqymk6MUyfBv92FmeVId6IqKzEWbGpxn85QZjPUMAUiKOn+5s+CqVcVISoLX5/KsSeVEIrIr63RTUFqeSvmoMXRngelc3UhKVwnrldMfbP6nmWqqB7x0+XyGRZwGrdF+FMYsHKKwtpnRfBYAt4vHvj7wbLFfNVRLgWGMwNx6nC/Dn1RSyrW7HCktdPbbV7SCvphAEfssSXccag7kqecveAm1tbdZQf/kPIPY/eZluIi8Wbr/+VltbWzxZ1LI7YHig/JO1t3gAsX+4v/zTZaOSdbYeCjZIKc4Cll2cj7eyhIwNOZDp1VamVubDxO7dJ9zbR3RwBCAuBPWnOhovOKU4CnDkSDBgRUUfkJO5twrvzpJVqHj1CPfcZP5KL8C9iMgo6+ion3hcnOMtICLiBJBjF+evucUDeKtLsYvyADZ4iZ1winMWQPAmgLdy7S1+iaULJyVvOMUkOwSLAazAer1VuYgVSDyeOF7FZAL4AYRtayzJXYQnUfs6pxhlJ/h/xejLK6dDzF2+Rmx04QC3t2zEW1uJlevXNoexAsjpEDPnf0bOhxNtkVsjREfHyW6sw/Jla5nHWAHmLl9Dzoep2pXHweZdSAnfdv5Bb88Y85d7yXppj5Z5jD0DYsN3AGhqqWFTwEdgs4+mw7sX+v65o20eYwWQi59C/GdWhfVQpwaMFcDeGgCgs72bqclZJidn6WzvBiCjYLO+ebSNpJnMPZXERie4dnWUj0/+mGgXmV4y9+7UNo+xO8Bavw5fQx32tnyEx0Z4PdjF+Vp/AcDgHQAg/NlkvVy7qnMYuwPc4qnsADccniquC+CWw1PFdQHccniquH4GuOXwVHFdALccniquC+CWw1Oux+0J3XJ4qri+A9xyeKo8FR/ghsNTJe0EdQ5mksNTRZsApjk8VbQJYJrDU0XbGWCaw1NFmwCmOTxVtAlgmsNTRdsZYJrDU0XbDjDN4ami1QeY5PBUeeadYFqAVIJDF38ldOm3NdW2HMnOgGnAL6PRxFsisZHxR4JMbpOR6NLXB48EL5JMgAGgKn53ilCun5sj45Qudvz+50AiqNrAtu6+Ycq2BsiavL/U1ff4JSYRQEouCEFVuOcGt18oIjQXZibHz8O2zsS20FyYwbEJtv89mFiL0zqVXpSc2l7A7S2bnEKNpGhsgtz+IYCpiMgocXpRMsNpgKtXz4V2Vx/oBxqem7wvsmfniHo9RDw2iBX/02ZVEfE4vgchCgZHyF14AJMCmto7G7odc5Yb9Oih4EGk+BrwaazVDaaFkK2nOg6cTRakdCnfb+7aHJaxDxDyNYGoAEz1tiGJ/EvApbjNl+3tB+4+7YLSpEmTJo3J/AskbixTjeLTxgAAAABJRU5ErkJggg==" style="cursor: pointer;" title="開啟Kekeke Dice設定選項"></div></td>').insertBefore($('.SquareCssResource-submitInputButton'));
Observer();
}, 1500);
const observeConfig = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
const dd_check = /^([1-9][0-9]?)[d]([1-9][0-9]{0,2})([\+\-\*\/])?([1-9][0-9]{0,1})?([?])?/i,
cc_check = /^cc\(?([0-9]{1,2})\)?(([\+\-])([1-9]))?/i,
na_check = /^1na([\+\-\*\/])?([1-9][0-9]*)?/i,
nc_check = /^([1-9][0-9]?)[n][c]([\+\-\*\/])?([1-9][0-9]{0,1})?/i;
const url = /<a class="GlobalCssResource-external" target="_blank" ref="noopener" href="((http|https)(:\/\/)[\w\+\-\*\/\.\&\:\(\)\<\>\?\=]*)">((http|https)(:\/\/)[\w\+\-\*\/\.\&\:\(\)\<\>\?\=]*)<\/a>/ig,
emoji = /<img class="GlobalCssResource-smiley" src="\/com\.liquable\.hiroba\/emoji\/[\w]*\/[\w\+\-\*\/\^\.\&\:\(\)\<\>]*.[a-z]*" alt="([\w\+\-\*\/\^\.\&\:\(\)\<\>\@]*)">/ig,
br = /<br>/ig;
// 檢查名稱是否與BOT名稱一致
function BotNameCheck() {
let name = $('.gwt-TextBox.SquareCssResource-nicknameField').val(),
config = JSON.parse(localStorage.getItem('kekeke_dice_config'));
if (config.Bot_Name == name)
return true;
return false;
}
// 日期+時間函數
function getDateTimes(get,mode) {
let text = '';
if (get) {
let time = new Date(),
year = time.getFullYear(),
month = (time.getMonth() < 10 ? "0" : "") + time.getMonth()+1,
day = (time.getDate() < 10 ? "0" : "") + time.getDate(),
hours = (time.getHours() < 10 ? "0" : "") + time.getHours(),
minutes = (time.getMinutes() < 10 ? "0" : "") + time.getMinutes(),
seconds = (time.getSeconds() < 10 ? "0" : "") + time.getSeconds();
if (mode === 'logs') {
text = hours + ':' + minutes + ':' + seconds + " - ";
}
if (mode === 'files') {
text = year + month + day + '-' + hours + minutes + seconds;
}
}
return text;
}
// 擲骰
function getRandom(e) {
return Math.floor(Math.random()*e+1);
}
// 四則運算
function Arithmetics(symbols,value,count) {
let x = [];
if (symbols === '+') {
value.forEach(function(item,index){
x.push(value[index] + count);
});
}
if (symbols === '-') {
value.forEach(function(item,index){
if (value[index] - count <= 0) {
x.push(1);
} else {
x.push(value[index] - count);
}
});
}
if (symbols === '*') {
value.forEach(function(item,index){
x.push(value[index] * count);
});
}
if (symbols === '/') {
value.forEach(function(item,index){
x.push(Math.ceil(value[index] / count));
});
}
return x;
}
// 陣列中取最大值
Array.prototype.max = function (){
let max = this[0];
this.forEach (function(ele,index,arr){
if(ele > max) { max = ele; }
});
return max;
};
// 陣列中取最小值
Array.prototype.min = function (){
let min = this[0];
this.forEach (function(ele,index,arr){
if(ele < min) { min = ele; }
});
return min;
};
/* Array.prototype的參考資料
* https://gist.github.com/hoandang/5989980 */
// 回應function
function DiceRespond(content) {
config = JSON.parse(localStorage.getItem('kekeke_dice_config'));
// 輸入框放入BOT內容
$('textarea.SquareCssResource-messageInputField').val(content);
// 點擊發送按鈕送出訊息
document.getElementById("submit_btn").click();
// 發文時間檢查設為false
config.Auto_Time_Check = false;
// 儲存config
localStorage.setItem('kekeke_dice_config', JSON.stringify(config));
}
// 安排時間紀錄選項的選取值
function check_download_time(v,t) {
if (v == t)
return true;
return false;
}
function Observer() {
// 建立rootObserver監控
let rootObserver = new MutationObserver(function (mutations) {
// 設定要監控的元素
let chatElement = document.body.querySelector('table#ChatTable');
// 確認chatElement已經生成
if (chatElement) {
// 結束rootObserver監控
rootObserver.disconnect();
const title = document.title.split(' | ')[0];
console.log(`Kekeke Dice 1.2.1 已在 ${title} 啟動。`);
// 清空kekeke_logs紀錄
if (config.Auto_Logs) {
localStorage.setItem(`logs_${title}`, JSON.stringify([]));
}
// 檢查自動下載
if (BotNameCheck() && config.Download_Time !== '0' && config.Auto_Logs) {
let s = config.Download_Time*60*60*1000;
if (s < 5000 || s === null) { s = 60*60*1000; } // 避免local storage發生問題時(例如NaN、0值)造成瀏覽器崩潰
setInterval(function(){
console.save(JSON.parse(localStorage.getItem(`logs_${title}`)), `logs_${title}` + '_' + getDateTimes(true,'files') + '.log');
// 清空紀錄
localStorage.setItem(`logs_${title}`, JSON.stringify([]));
},s);
}
// 建立chatObserver監控聊天頻道
let chatObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// 有新的發言近來
if (mutation.type == 'childList' && mutation.addedNodes.length >= 1 && mutation.addedNodes[0].nodeName === 'DIV') {
if (mutation.addedNodes[0].classList.contains('SquareCssResource-chatContent') && BotNameCheck()) {
let config = JSON.parse(localStorage.getItem('kekeke_dice_config')),
Post_Name = $(mutation.addedNodes[0]).find('.GlobalCssResource-colorNickname'),
Post_Content = $(mutation.addedNodes[0]).find('.SquareCssResource-message'),
Message = Post_Content.text().split(' @ '),
content;
// 確認是否為DND骰
if (dd_check.test(Message[0])) {
// 拆解骰子細節並分配變數
let match = dd_check.exec(Message[0]),
unit = Number(match[1]),
flat = Number(match[2]),
symbols = typeof match[3] !== 'undefined' ? true : false,
count = typeof match[4] !== 'undefined' ? Number(match[4]) : 0,
sum = typeof match[5] !== 'undefined' ? true : false,
value = [];
// 擲骰
for (let i = 1; i <= unit; i++) {
value.push(getRandom(flat));
}
let sum_value = [value.reduce((a,b)=>a+b)],
sum_result = Arithmetics(match[3],sum_value,count),
result = Arithmetics(match[3],value,count);
if (sum && symbols) {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}](加總) ${match[3]} ${count} = [${sum_value}] ${match[3]} ${count} = ${sum_result}`;
} else if (sum && !symbols) {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}](加總) = ${sum_value}`;
} else if (!sum && symbols) {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}] ${match[3]} ${count} = [${result}]`;
} else {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}]`;
}
}
// 確認是否為COC骰
if (cc_check.test(Message[0])) {
let match = cc_check.exec(Message[0]),
// 比對值
flat = match[1] === '00' ? 100 : Number(match[1]),
// 是否有獎勵或懲罰骰
symbols = typeof match[3] !== 'undefined' ? true : false,
// 獎勵或懲罰的追加數量
count = typeof match[4] !== 'undefined' ? Number(match[4]) : 0,
// 要骰的總數
unit = (symbols) ? count + 1 : 1,
// 十位數
first_value = [],
// 個位數
last_value = getRandom(10);
// 擲骰十位數
for (let i = 1; i <= unit; i++) {
// -1讓十位數骰出結果為0-9
first_value.push(getRandom(10)-1);
}
// 確認是懲罰(取最大數)或獎勵(取最小數)當十位數
let value = (match[3] === '-') ? first_value.max() : first_value.min(),
result = (symbols) ? value*10 + last_value : getRandom(100),
judge = '';
if (result > flat) { // 失敗
judge = '失敗';
if (flat > 50 && result == 100) { judge = '大失敗'; }
if (flat <= 50 && result >= 96) { judge = '大失敗'; }
} else { // 成功
judge = '成功';
if (result <= Math.floor(flat*0.5)) { judge = '困難成功'; }
if (result <= Math.floor(flat*0.2)) { judge = '極難成功'; }
if (result == 1) { judge = '大成功'; }
}
if (symbols) {
let bonus = (match[3] === '-') ? '大' : '小';
content = `[${Post_Name.text()}] 的 CC${flat} 骰出了 [${first_value}](取最${bonus}x10) + [${last_value}] = ${result},判定為 [${judge}]`;
} else {
content = `[${Post_Name.text()}] 的 CC${flat} 骰出了 [${result}],判定為 [${judge}]`;
}
}
// 確認是否為NA骰
if (na_check.test(Message[0])) {
let array = ['','大失敗','失敗','失敗','失敗','失敗','對方隨意選擇','腳','身體','手','頭','己方隨意選擇'],
match = na_check.exec(Message[0]),
symbols = typeof match[1] !== 'undefined' ? true : false,
count = typeof match[2] !== 'undefined' ? Number(match[2]) : 0,
value = [getRandom(10)],
result = symbols ? Arithmetics(match[1],value,count) : value,
judge = result > 10 ? array[11] : array[result];
if (symbols) {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}] ${match[1]} ${count} = [${result}] 判定為 [${judge}]`;
} else {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}] 判定為 [${judge}]`;
}
}
// 確認是否為NC骰
if (nc_check.test(Message[0])) {
// 拆解骰子細節並分配變數
let match = nc_check.exec(Message[0]),
unit = Number(match[1]),
symbols = typeof match[2] !== 'undefined' ? true : false,
count = typeof match[3] !== 'undefined' ? Number(match[3]) : 0,
value = [];
// 擲骰
for (let i = 1; i <= unit; i++) {
value.push(getRandom(10));
}
let result = symbols ? Arithmetics(match[2],value,count) : value,
judge;
if (result.max() >= 6 ) { judge = '成功'; }
if (result.max() > 10 ) { judge = '大成功'; }
if (result.min() > 1 && result.max() <= 5 ) { judge = '失敗'; }
if (result.min() == 1 && result.max() <= 5 ) { judge = '大失敗'; }
if (symbols) {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}] ${match[2]} ${count} = [${result}] 判定為 [${judge}]`;
} else {
content = `[${Post_Name.text()}] 的 ${match[0]} 骰出了 [${value}] 判定為 [${judge}]`;
}
}
if (content !== '' && config.Auto_Time_Check) { DiceRespond(content); }
// 將聊天紀錄輸出到console.log
if (config.Auto_Logs) {
let arr = JSON.parse(localStorage.getItem(`logs_${title}`)),
// 設置去除表情符號html與超連結html的regex
text = Post_Content.html().split('<span class="SquareCssResource-chatDate">'),
// 刪去掉無用的html,保留表情符號的alt與超連結的href
logs_content = text[0].replace(url,'$1').replace(emoji,'$1').replace(br,' '),
// 設定時間戳記
time = getDateTimes(config.Logs_Time,'logs');
// 略過空白的對話內容
if (logs_content !== '') {
arr.push(time + Post_Name.text() + ' : ' + logs_content);
localStorage.setItem(`logs_${title}`, JSON.stringify(arr));
}
}
}
}
});
});
chatObserver.observe(chatElement, observeConfig);
// 進階設定
$('#DiceConfig').click(function () {
$(this).toggleClass('init');
if ($(this).hasClass('init')) {
config = JSON.parse(localStorage.getItem('kekeke_dice_config'));
$(this).parent().css('position', 'relative');
$('<div id="DiceConfig_panel"></div>').css({'background-color':'#fff','border':'1px solid #bbb','color':'#000','width':'300px','padding':'5px','position':'absolute','top':'30px','z-index':'999'}).insertAfter('#DiceConfig');
$('<table></table>').css({'width':'100%','table-layout':'fixed'}).appendTo('#DiceConfig_panel');
$('<tr><td style="width:65px">BOT名稱</td><td><input type="text" id="cb_Dice_Name" style="width:100%"></td></tr>').appendTo('#DiceConfig_panel > table');
$('<tr><td></td><td>(暱稱與此項相同才啟用BOT功能)</td></tr>').appendTo('#DiceConfig_panel > table');
$('<tr><td>自動記錄</td><td><label><input type="checkbox" id="cb_Auto_Logs" checked="checked">啟用自動紀錄</label></td></tr>').appendTo('#DiceConfig_panel > table');
$('<tr><td></td><td><label><input type="checkbox" id="cb_Logs_Time" checked="checked">加入時間戳記</label></td></tr>').appendTo('#DiceConfig_panel > table');
$('<tr><td>下載紀錄</td><td><select id="cb_Download_Time" style="width:100%"></select></td></tr>').appendTo('#DiceConfig_panel > table');
$('<option></option>').attr({'value':'0','selected':check_download_time(0,config.Download_Time)}).text('手動').appendTo('#cb_Download_Time');
$('<option></option>').attr({'value':'1','selected':check_download_time(1,config.Download_Time)}).text('每1小時').appendTo('#cb_Download_Time');
$('<option></option>').attr({'value':'2','selected':check_download_time(2,config.Download_Time)}).text('每2小時').appendTo('#cb_Download_Time');
$('<option></option>').attr({'value':'4','selected':check_download_time(4,config.Download_Time)}).text('每4小時').appendTo('#cb_Download_Time');
$('<option></option>').attr({'value':'6','selected':check_download_time(6,config.Download_Time)}).text('每6小時').appendTo('#cb_Download_Time');
$('<div></div>').css({'padding-top':'3px'}).appendTo('#DiceConfig_panel');
$('<button></button>').attr({'id':'save_dice_config','type':'button','title':'儲存後將自動重新整理'}).addClass('button_setting').text('儲存設定').appendTo('#DiceConfig_panel > div');
$('<button></button>').attr({'id':'save_logs','type':'button'}).addClass('button_setting').text('下載對話紀錄').appendTo('#DiceConfig_panel > div');
$('.button_setting').css({'height':'20px','float':'right','margin':'0 2px','padding':'0','font-size':'10px','box-sizing':'border-box'});
// 開啟面板時設定狀態
$('#cb_Dice_Name').val(config.Bot_Name);
if (BotNameCheck(name)) {
$('#cb_Auto_Logs').attr('disabled',false);
$('#cb_Logs_Time').attr('disabled',false);
$('#cb_Download_Time').attr({'disabled':false});
} else {
$('#cb_Auto_Logs').attr({'disabled':true});
$('#cb_Logs_Time').attr({'disabled':true});
$('#cb_Download_Time').attr({'disabled':true});
}
if (!config.Auto_Logs) $('#cb_Auto_Logs').attr('checked',false);
if (!config.Logs_Time) $('#cb_Logs_Time').attr('checked',false);
$('#cb_Dice_Name').on('change', function () {
config.Bot_Name = $('#cb_Dice_Name').val();
localStorage.setItem('kekeke_dice_config', JSON.stringify(config));
if (BotNameCheck(name)) {
$('#cb_Auto_Logs').attr('disabled',false);
$('#cb_Logs_Time').attr('disabled',false);
$('#cb_Download_Time').attr({'disabled':false});
} else {
$('#cb_Auto_Logs').attr({'disabled':true});
$('#cb_Logs_Time').attr({'disabled':true});
$('#cb_Download_Time').attr({'disabled':true});
}
});
$('#cb_Download_Time').on('change', function () {
config.Download_Time = $('#cb_Download_Time').val();
});
$('#cb_Auto_Logs').on('change', function () {
if (this.checked) { config.Auto_Logs = true; } else { config.Auto_Logs = false; }
});
$('#cb_Logs_Time').on('change', function () {
if (this.checked) { config.Logs_Time = true; } else { config.Logs_Time = false; }
});
$('#save_logs').click(function () {
console.save(JSON.parse(localStorage.getItem(`logs_${title}`)), `logs_${title}` + '_' + getDateTimes(true,'files') + '.log');
// 清空紀錄
localStorage.setItem(`logs_${title}`, JSON.stringify([]));
});
$('#save_dice_config').click(function () {
localStorage.setItem('kekeke_dice_config', JSON.stringify(config));
$('#DiceConfig').removeClass('init');
$('#DiceConfig_panel').remove();
location.reload();
});
} else {
$('#DiceConfig_panel').remove();
}
});
}
});
rootObserver.observe(document, observeConfig);
}
// 下載對話紀錄
(function(console){
console.save = function(data, filename){
if(!data) {
console.error('沒有保存任何對話紀錄');
return;
}
if(!filename) filename = 'autosave.log';
if(typeof data === "object"){
data = JSON.stringify(data, undefined, 4);
}
let blob = new Blob([data], {type: 'text/json'}),
e = document.createEvent('MouseEvents'),
a = document.createElement('a');
a.download = filename;
a.href = window.URL.createObjectURL(blob);
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(e);
};
})(console);
/* console.save的參考資料
* https://bgrins.github.io/devtools-snippets/#console-save */
});