Greasy Fork

Greasy Fork is available in English.

Bangumi评分脚本・改

改造自 http://bangumi.tv/group/topic/345087

当前为 2018-03-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bangumi Evaluation
// @name:zh-CN   Bangumi评分脚本・改
// @namespace    https://github.com/ipcjs/
// @version      1.0.5
// @description  Bangumi Evaluation Script
// @description:zh-CN 改造自 http://bangumi.tv/group/topic/345087
// @author       ipcjs
// @include      *://bgm.tv/ep/*
// @include      *://bgm.tv/character/*
// @include      *://bgm.tv/blog/*
// @include      *://bgm.tv/*/topic/*
// @include      *://bangumi.tv/ep/*
// @include      *://bangumi.tv/character/*
// @include      *://bangumi.tv/blogep/*
// @include      *://bangumi.tv/*/topic/*
// @include      *://chii.in/ep/*
// @include      *://chii.in/characterep/*
// @include      *://chii.in/blog/*
// @include      *://chii.in/*/topic/*
// @compatible   chrome
// @compatible   firefox
// @grant        none
// @run-at       document-end
// ==/UserScript==

'use strict'

// type, props, children
// type, props, innerHTML
// 'text', text
const util_ui_element_creator = (type, props, children) => {
    let elem = null;
    if (type === "text") {
        return document.createTextNode(props);
    } else {
        elem = document.createElement(type);
    }
    for (let n in props) {
        if (n === "style") {
            for (let x in props.style) {
                elem.style[x] = props.style[x];
            }
        } else if (n === "className") {
            elem.className = props[n];
        } else if (n === "event") {
            for (let x in props.event) {
                elem.addEventListener(x, props.event[x]);
            }
        } else {
            elem.setAttribute(n, props[n]);
        }
    }
    if (children) {
        if (typeof children === 'string') {
            elem.innerHTML = children;
        } else {
            for (let i = 0; i < children.length; i++) {
                if (children[i] != null)
                    elem.appendChild(children[i]);
            }
        }
    }
    return elem;
}
const _ = util_ui_element_creator

const addStyle = (css) => {
    document.head.appendChild(_('style', {}, [_('text', css)]))
}
const ajax = (...args) => new Promise((resolve, reject) => $(...args).done(resolve).fail(reject))

// language=CSS
addStyle(`
    .inputButton {
        background-color: #F09199;
        color: #fff;
        cursor: pointer;
        font-family: lucida grande, tahoma, verdana, arial, sans-serif;
        font-size: 11px;
        padding: 1px 3px;
        text-decoration: none;
    }

    .forum_category {
        background-color: #F09199;
        color: #fff;
        font-weight: 700;
        padding: 3px;
    }

    .vote_container {
        background-color: #e1e7f5
    }

    .forum_boardrow1 {
        background-color: #fff;
        border-color: #ebebeb;
        border-style: solid;
        border-width: 0;
        padding: 6px 4px;
        vertical-align: top
    }

    .form-option {
        float: right;
        color: #AAA;
        font-size: 12px;
    }
`)
const TRUE = 'Y'
const FALSE = ''
const HOME_URL_PATH = '/group/topic/345237'
const HOME_URL = 'https://bgm.tv' + HOME_URL_PATH
localStorage.beuj_need_mask === undefined && (localStorage.beuj_need_mask = FALSE)
localStorage.beuj_need_suffix === undefined && (localStorage.beuj_need_suffix = TRUE)
let beuj_only_one_suffix = TRUE // 一个页面最多放一个小尾巴
const COMMENTS_DEFAULT = '力荐 不错 一般 不喜欢 垃圾'
let commentTemplates = (localStorage.beuj_comment_templates || COMMENTS_DEFAULT).split(' ')

const is_login = !document.querySelector('div.guest')
const getUserId = () => {
    const $avatar = document.querySelector('div.idBadgerNeue a.avatar')
    return $avatar && $avatar.href.split('/')[4] || ''
}
const array_last = (arr) => arr[arr.length - 1]
const safe_prop = (obj, prop, defaultValue) => obj ? obj[prop] : defaultValue
const score_to_index = (score) => 5 - (score + 3)
const index_to_score = (index) => 5 - index - 3
const score_to_str = (score) => `${score >= 0 ? '+' : ''}${score}`

function readVoteData() {
    const voters = {}
    const replys = document.querySelectorAll('.row_reply')
    const scoreReg = /^\s*([+-]\d+)(\W[^]*)?$/ // 以数字开头的评论
    const myUserId = getUserId()
    let myScore, myReplyId, hasSuffix = false
    let group
    for (let $reply of replys) {
        if (group = $reply.querySelector('.message').innerText.match(scoreReg)) {
            let score = Math.min(Math.max(-2, +group[1]), 2)
            let userId = array_last($reply.querySelector(':scope > a.avatar').href.split('/'))
            voters[userId] = score
            if (myUserId === userId) {
                myScore = score
                myReplyId = $reply.id
            }
            if (!hasSuffix && group[0].includes(HOME_URL_PATH)) {
                hasSuffix = true
            }
        }
    }
    const counts = [0, 0, 0, 0, 0] // 投+2->-2分的人数的数组
    for (let userId of Object.keys(voters)) {
        let score = voters[userId]
        counts[score_to_index(score)]++
    }
    const result = {
        counts,
        myScore,
        myReplyId,
        hasSuffix,
    }
    console.log('投票数据:', voters, result)
    return result
}

const vote_to_bgm = (score, comment, hasSuffix) => new Promise((resolve, reject) => {
    // 发送一条推广评论
    comment = (comment || '').trim()

    let text = ''
    let scoreText = score_to_str(score)
    text += localStorage.beuj_need_mask ? `[mask]${scoreText}[/mask]` : scoreText
    comment && (text += ' ' + comment)
    if (localStorage.beuj_need_suffix && !(beuj_only_one_suffix && hasSuffix)) {
        (text += `\n[url=${HOME_URL}]--来自Bangumi评分脚本・改[/url]`)
    }

    document.querySelector('textarea#content').value = text
    document.querySelector('#new_comment #ReplyForm [type=submit]').click()
    resolve('ok')
})

function main() {
    let $comment_list
    if (!($comment_list = document.getElementById('comment_list'))) {
        console.log('不存在#comment_list, 不支持投票...')
        return
    }
    // 番剧讨论页: https://bgm.tv/ep/767931
    let $container = document.querySelector('#columnEpA .epDesc')
    // 小组讨论页: https://bgm.tv/group/topic/345237
    // 条目讨论版: https://bgm.tv/subject/topic/3022
    if (!$container) $container = document.querySelector('div.topic_content')
    // 人物页: https://bgm.tv/character/77
    if (!$container) $container = document.querySelector('#columnCrtB > div.detail')
    // 日志页面: https://bgm.tv/blog/46986
    if (!$container) $container = document.querySelector('#entry_content.blog_entry')
    // 依然没有, 则创建
    if (!$container) {
        $container = _('div', { className: 'borderNeue', style: { marginTop: '10px' } })
        $comment_list.parentElement.insertBefore($container, $comment_list)
    }
    const $poll_container = _('div', { id: 'poll_container', style: {/* width: '670px'*/ } })
    $container.appendChild($poll_container)
    let voteData = readVoteData()
    if (is_login && voteData.myScore === undefined) {
        let title = document.title, $tmp
        // 番剧讨论页: https://bgm.tv/ep/767931
        // 人物页: https://bgm.tv/character/77
        if ($tmp = document.querySelector('#headerSubject .nameSingle a')) {
            title = $tmp.innerText
            if ($tmp = document.querySelector('div#columnEpA h2.title')) { // 番剧讨论页的ep
                title += ' ' + $tmp.innerText.split(' ')[0]
            }
        }
        const $voteForm = showVote(title, () => {
            const val = $voteForm.elements.pollOption.value
            if (!val) {
                alert("请选择后再投票!");
                return;
            }
            let score = +val
            vote_to_bgm(score, $poll_container.querySelector('#vote-comment').value, voteData.hasSuffix)
                .then((r) => {
                    voteData.counts[score_to_index(score)]++
                    voteData.myScore = score
                    voteData.myReplyId = safe_prop(array_last(document.querySelectorAll('#comment_list > .row_reply')), 'id', 'no_id') // 评论列表的最后一条
                    showVoteResult(voteData)
                })
                .catch(e => console.error(e))
        })
    } else {
        showVoteResult(voteData)
    }

    function showVoteResult(voteData) {
        $poll_container.innerHTML = createVoteResultHtml(voteData.counts, voteData.myScore, voteData.myReplyId)
    }

    function showVote(title, onSubmit) {
        $poll_container.innerHTML = createVoteHtml(title)
        let $voteForm = $poll_container.querySelector('#vote-form')
        $voteForm.onsubmit = function () {
            let comment_template
            let toSubmit = true
            if (comment_template = $voteForm.elements.comment_template.value) {
                toSubmit = false
                let templates = comment_template.split(' ')
                if (templates.length === commentTemplates.length) {
                    // 更新模板
                    commentTemplates = templates
                    localStorage.beuj_comment_templates = commentTemplates.join(' ')
                    toSubmit = true
                } else {
                    alert(`短评模板(${comment_template})不符合格式!\n需要用空格分隔, 例如:\n"${commentTemplates.join(' ')}"`)
                }
            }
            toSubmit && onSubmit()
            return false // no submit
        }
        $voteForm.addEventListener('change', (e) => {
            let name = e.target.name;
            let value = e.target.type === 'checkbox' ? (e.target.checked ? TRUE : FALSE) : e.target.value
            if (name.startsWith('beuj_')) {
                localStorage[name] = value
                console.log(name, ' => ', value);
            } else if (name === 'pollOption') {
                let score = +value
                let comment = $voteForm.elements.comment.value
                // 若简单评论为空, 或是评论模板中的值, 则修改简单评论为评论模板中的一个
                if (comment === '' || commentTemplates.includes(comment)) {
                    $voteForm.elements.comment.value = commentTemplates[score_to_index(score)]
                }
            } else if (name === 'modify_comment_template') {
                $voteForm.elements.comment_template.type = value ? 'text' : 'hidden'
            }
        })
        $voteForm.elements.beuj_need_mask.checked = localStorage.beuj_need_mask
        $voteForm.elements.beuj_need_suffix.checked = localStorage.beuj_need_suffix
        return $voteForm
    }
}

function createVoteHtml(title) {
    let rows = ''
    for (let i = 0; i < commentTemplates.length; i++) {
        let scoreStr = score_to_str(index_to_score(i))
        rows += `<div style="margin: 3px 0;"><label><input type="radio" name="pollOption" value="${scoreStr}"> ${scoreStr} ${commentTemplates[i]}</label></div>`
    }
    return `
<div class="forum_category">${title} 投票</div>
<div class="forum_boardrow1" style="border-width: 0 1px 1px 1px;">
<form id="vote-form">
    ${rows}
    <textarea name="comment" id="vote-comment" class="reply" rows="1" placeholder="简短评价"></textarea>
    <input type="hidden" name="comment_template" class="inputtext" style="margin-bottom: 6px;" placeholder="短评模板; +2 +1 +0 -1 -2 分别对应的短评;使用空格分隔;">
    <br/>
    <input type="submit" name="voteButton" value="投票" class="inputButton" id="voteButton">
    <label class="form-option"><input type="checkbox" name="modify_comment_template" > 修改短评模板 </input></label>
    <label class="form-option"><input type="checkbox" name="beuj_need_suffix" title="在评分的结尾追加'来自xxx脚本'的小尾巴, 为了防止刷屏, 只有当前页没有出现过小尾巴时才会追加." > 帮助推广脚本 </input></label>
    <label class="form-option"><input type="checkbox" name="beuj_need_mask" > Mask评分 </input></label>
</form>
</div>
    `
}

function createVoteResultHtml(counts, score, replyId) {
    let voters = counts.reduce((a, b) => a + b, 0)
    let html = '';
    const myIndex = score_to_index(score)
    for (let i = 0; i < commentTemplates.length; i++) {
        let width = (counts[i] / voters * 100).toFixed(1)
        let your_vote = myIndex === i
        html += `
            <tr>
                <td align="left">${score_to_str(index_to_score(i))} ${commentTemplates[i]}${your_vote ? `<a href="#${replyId}" class="l">(your vote)</a>` : ''}</td>
                <td width="35%"><div class="vote_container" style="width: ${width}%">&nbsp;</div></td>
                <td width="25" align="center">${counts[i]}</td>
                <td width="40" align="right">${width}%</td>
            </tr>`
    }
    return `
<div class="forum_category">投票结果</div><div class="forum_boardrow1" style="border-width: 0 1px 1px 1px;">
    <table border="0" width="100%" cellpadding="" cellspacing="5">
        ${html}
    </table>
    <div style="text-align: center;">Voters: ${voters}</div>
</div>`
}

main()