9dm综合优化

2024/3/14 16:35:09

// ==UserScript==
// @name        9dm综合优化
// @namespace   Violentmonkey Scripts
// @match       *://www.9dmsgame.net/*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addStyle
// @grant       GM_registerMenuCommand
// @homepageURL https://greasyfork.org/zh-CN/scripts/491902-9dm%E7%BB%BC%E5%90%88%E4%BC%98%E5%8C%96
// @homepage    https://greasyfork.org/zh-CN/scripts/491902-9dm%E7%BB%BC%E5%90%88%E4%BC%98%E5%8C%96
// @version     1.04
// @author      lazy cat
// @description 2024/3/14 16:35:09
// @run-at      document-end
// @require     https://cdn.jsdelivr.net/npm/sweetalert2@11
// ==/UserScript==
// 添加透明样式
function add_transparent_css(...selector) {
    // 储存搜索时间
    document.querySelector('button[class="pn pnc"]')?.addEventListener('click', ()=>{
        GM_setValue('search_time', parseInt(new Date().getTime() / 1000))})
    if (!document.URL.includes('search.php')) return
    let new_css = ''
    selector.forEach((e) => {
        new_css += e + ' {background-color: transparent !important; opacity: 0 !important; user-select: none !important; pointer-events: none !important; pointer-events: none !important;width: 0 !important;height: 0 !important;}\n'
    })
    let style = document.createElement('style')
    style.textContent = new_css
    document.head.appendChild(style)
    // 储存搜索时间
    document.querySelector('#scform_submit')?.addEventListener('click', ()=>{
        GM_setValue('search_time', parseInt(new Date().getTime() / 1000))})
}

// response取出文本
async function get_re_text(response, encoding='GBK'){
    const buffer = await response.arrayBuffer();
    const decoder = new TextDecoder(encoding);
    const text = decoder.decode(buffer);
    return text
}

// 发送请求
async function get(_url) {
    const _response = await fetch(_url, {
        method: 'GET',
        credentials: 'include' })
    if (_response.status !== 200) {
        console.log('发送请求出现错误', 'url: ', _url, '\n', _response)
        return 'error'
    }
    return await get_re_text(_response)
}

// 获取回复验证码
async function get_reply_code() {
    const hash_text = await get('http://www.9dmsgame.net/forum.php?mod=ajax&action=checkpostrule&ac=reply&inajax=1&ajaxtarget=vfastpostseccheck')
    if (hash_text === 'error') return 144514
    const hash_rage = /secqaa_(\w+)/
    const hash_code = hash_rage.exec(hash_text)?.length >= 1 ? hash_rage.exec(hash_text)[1] : 'error'
    if (hash_code === 'error') {
        console.log('获取回复hash失败', hash_text)
        return 144514
    }
    const code_text = await get('http://www.9dmsgame.net/misc.php?mod=secqaa&action=update&idhash=' + hash_code)
    if (code_text === 'error') return 144514
    const code_rage = /(\d+ ?[+-x/] ?\d+) ?= ?\?/
    const code_eval = code_rage.exec(code_text)?.length >= 1 ? code_rage.exec(code_text)[1] : 'error'
    if (code_eval === 'error') {
        console.log('获取回复验证码失败', code_text)
        return 114514
    }
    let code
    try {
        code = eval(code_eval)
    } catch (e) {
        console.log('计算验证码出错', code_eval)
        return 114514
    }
    return { hash: hash_code, code: code }
}

// 发送消息
async function send_message(message, hash, code) {
    console.log('开始留言')
    const tid = /-(\d+)-/.exec(document.URL)[1]
    const url = `http://www.9dmsgame.net/forum.php?mod=post&action=reply&fid=40&tid=${tid}&fromvf=1&extra=page=1&replysubmit=yes&infloat=yes&handlekey=vfastpost&inajax=1`
    const input = document.querySelector('input[name="formhash"]')
    let formhash = ''
    if (input) formhash = input.getAttribute('value')
    else return 'error'
    const form = new FormData()
    form.append('formhash', formhash)
    form.append('message', message)
    if (hash && code) {
        form.append('secqaahash', hash)
        form.append('secanswer', code) }
    const response = await fetch(url, {
        method: 'POST',
        credentials: 'include',
        body: form })
    let response_text = await get_re_text(response)
    console.log(response_text)
    if (response_text.includes('感谢') && response_text.includes('成功')) return 'success'
    if (response_text.includes('间隔少于')) throw new Error('冷却尚未完成, 请稍后再试')
    if (response.status !== 200 ) return 'error'
    console.log('出现未知错误', response)
}

// 冷却提示窗
async function cooling_tips(){
    const loc_time = GM_getValue('reply_time', 0)
    const now_time = parseInt(new Date().getTime() / 1000)
    if (now_time - loc_time < 8) {
        const result = await Swal.fire({
            title: '回复冷却中',
            text: '一定要现在发送请求吗, 大概率不会成功哦',
            icon: 'error',
            confirmButtonText: '确定',
            showCancelButton: true,
            cancelButtonText: '取消', })
        if (result.isConfirmed) return true
        return false}
    return true
    
}

// 文本内嵌回复按钮回调函数
async function text_embedding_callback(e) {
    // 冷却提示
    if (!(await cooling_tips())) return
    Swal.fire({
        title: "输入你想要回复的内容",
        input: "text",
        showConfirmButton: true,
        showCancelButton: true,
        cancelButtonText: '取消',
        confirmButtonText: "发送",
        showLoaderOnConfirm: true,
        allowOutsideClick: () => !Swal.isLoading(),
        allowEscapeKey: () => !Swal.isLoading(),
        allowEnterKey: () => !Swal.isLoading(),
        preConfirm: async (text) => {
            try { if (text.length < 5) throw new Error('文本小于5个字符, 请重新输入')
                Swal.update({ text: '正在发送留言' })
                let result = await send_message(text, '', '')
                if (result === 'success') return 'success'
                Swal.update({ text: '正在获取验证码' })
                let code_data = await get_reply_code()
                console.log('验证码', code_data)
                if (code_data === 144514) throw new Error('获取验证码失败, 请重试或联系作者')
                Swal.update({ text: '正在发送留言' })
                result = await send_message(text, code_data.hash, code_data.code)
                if (result !== 'success') throw new Error('发送失败, 请重试或联系作者')
                return 'success'
            } catch (error) {
                console.log('发生错误', error)
                Swal.showValidationMessage(error+'\n如果长时间多次如此, 那可能是发送成功而脚本未识别') 
            } }
    }).then((result) => { if (result.value === 'success') {
        // 请求成功后记录时间
        GM_setValue('reply_time', parseInt(new Date().getTime() / 1000))
        Swal.fire('发送成功, 请刷新页面后查看', "", "success") }})
}

// 更便捷的回复
async function good_reply() {
    let reply_btns = document.querySelectorAll('div[class="locked"]>a')
    if (!reply_btns) return
    for (let reply_btn of reply_btns) {
        reply_btn.removeAttribute('href')
        reply_btn.removeAttribute('onclick')
        // 阻止回复跳转
        reply_btn.addEventListener('click', (e) => { e.preventDefault() })
        // 自定义回复函数
        reply_btn.addEventListener('click', text_embedding_callback) }
    // 真正的快捷回复
    let quick_reply = document.querySelector('#vreplysubmit')
    if (!quick_reply) return
    $('vreplysubmit').onclick = null
    quick_reply.removeAttribute('href')
    quick_reply.addEventListener('click', async () => {
        // 冷却提示
        if (!(await cooling_tips())) return
        text_embedding_callback(null)
        let input_text = document.querySelector("#vmessage")?.value
        if (!input_text || input_text.includes('在这里快速回复')) input_text = '6666666666'
        await new Promise((resout, gg)=>{setTimeout(() => {resout()}, 150)})
        document.querySelector('#swal2-input').value = input_text
        document.querySelector('button[class="swal2-confirm swal2-styled"]').click() })
}

// 可视化的计时器
// 创建计时器元素
function creat_timer(){
    GM_addStyle('#reply_time,#search_time {background-color: #ffa987a8;width: 200px;height: 75px;position: fixed;border-radius: 20px;left: 50px;display: flex;flex-direction: column;justify-content: center;align-items: center;color: #b64e1779;font-weight: bold;font-size: large;transition-property: display, top, left, background-color, color;transition-duration: 0.5s;}')
    GM_addStyle('#reply_time:hover,#search_time:hover {background-color: #E54B4B;color: #FFA987;animation-name: shake;animation-duration: 0.3s;animation-timing-function: ease-in-out;animation-iteration-count: 1;}')
    GM_addStyle('@keyframes shake {0% {transform: translate(-5px, -5px) rotate(5deg);}25% {transform: translate(5px, 5px) rotate(5deg);}50% {transform: translate(-3px, -3px) rotate(-3deg);}75% {transform: translate(3px, 3px) rotate(3deg);}100% {transform: translate(0, 0) rotate(0);}}')
    GM_addStyle('#reply_time {top: 50px;}')
    GM_addStyle('#search_time { top: 150px; }')
    GM_addStyle('.only {top: 50px !important;left: 50px !important; }')
    GM_addStyle('.over { left: -500px !important; }')
    GM_addStyle('#look_time { position: absolute; z-index: 9999; }')
    let div = document.createElement('looktime')
    document.body.appendChild(div)
    div = document.querySelector('looktime')
    div.outerHTML = `<div id="look_time">
    <p id="reply_time" class="over">回复冷却: 60s ✔️</p>
    <p id="search_time" class="over">搜索冷却: 60s ❌</p> </div>`
    // 双击隐藏冷却计时器
    let reply = document.querySelector('#reply_time')
    let search = document.querySelector('#search_time')
    reply.addEventListener('dblclick', ()=>{
        reply.className = 'over'
        setTimeout(() => {reply.style = 'display: none;'}, 300) })
    search.addEventListener('dblclick', ()=>{
        search.className = 'over'
        setTimeout(()=>{search.style = 'display: none;'}, 300)})
}
// 更新计时器
function update_timer(reply_time, search_time){
    // 判断是否存在显示元素
    let state_class = {
        '只有一个': 'only',
        '两个都有': '',
        '没有': 'over' }
    let reply = document.querySelector('#reply_time')
    let search = document.querySelector('#search_time')
    if (!reply || !search) return
    // 判断是否为新的计时器
    if (reply_time >= 60) reply.style = ''
    if (search_time >= 30) search.style = ''
    // 全部冷却完成
    if (reply_time <= 0 && search_time <= 0) {
        reply.className = state_class['没有']
        search.className = state_class['没有'] }
    // 只有回复冷却完成
    if (reply_time <= 0 && search_time >= 0) {
        reply.className = state_class['没有']
        search.className = state_class['只有一个'] }
    // 只有搜索冷却完成
    if (reply_time >= 0 && search_time <= 0) {
        reply.className = state_class['只有一个']
        search.className = state_class['没有'] }
    // 全部冷却未完成
    if (reply_time >= 0 && search_time >= 0) {
        reply.className = state_class['两个都有']
        search.className = state_class['两个都有'] }
    if (reply_time <= 0) reply_time = 0
    if (search_time <= 0) search_time = 0
    reply.innerText = `回复冷却: ${reply_time}s ` + (reply_time <= 0 ? ' ✔️' : '❌')
    search.innerText = `搜索冷却: ${search_time}s ` + (search_time <= 0 ? ' ✔️' : '❌')
}
// 创建元素并显示冷却时间
function clook_timer(){
    // 获取本地时间
    let now_time = parseInt(new Date().getTime() / 1000)
    // 获取本地时间
    let reply_time = 8 - now_time + GM_getValue('reply_time', 0)
    let search_time = 35 - now_time + GM_getValue('search_time', 0)
    // 判断是否存在显示元素
    let reply = document.querySelector('#reply_time')
    let search = document.querySelector('#search_time')
    if (!reply || !search) creat_timer()
    // 更新时间
    update_timer(reply_time, search_time)
}

// 拜托在新标签页打开
function open_in_new_tab() {
    if (!GM_getValue('open_in_new_tab', true)) return
	let all_doc = document.querySelectorAll('a')
	for (a of all_doc){
		if (a.getAttribute('href')){
			a.setAttribute('target', '_blank')
            a.removeAttribute('onclick') 
        } }
}

(function () {
    // 屏蔽搜索页验证
    add_transparent_css('.my-mask')
    // 更棒的回复方式
    good_reply()
    // 每秒监视冷却并进行可视化
    setInterval(clook_timer, 1000)
    // 实验性功能, 所有链接全在xin标签页打开
    setInterval(open_in_new_tab, 1000)
    const tips = '在新标签页打开所有链接' + (GM_getValue('open_in_new_tab')?'✔️':'❌')
    GM_registerMenuCommand(tips, ()=>{
        GM_setValue('open_in_new_tab', !GM_getValue('open_in_new_tab', true))
        location.reload()
    })
})()