// ==UserScript==
// @name 预创建人物 / 人物页一键补完已填写未关联条目
// @namespace bangumi.wiki.missing.positions
// @version 0.3.0
// @description 像 AniDB 一样,无需等待维基人即可查看人物关联 / 维基人可一键补完已填写未关联条目或剧集
// @author you
// @icon https://bgm.tv/img/favicon.ico
// @match http*://bgm.tv/subject/*
// @match http*://bgm.tv/person/*
// @match http*://chii.in/subject/*
// @match http*://chii.in/person/*
// @match http*://bangumi.tv/subject/*
// @match http*://bangumi.tv/person/*
// @match http*://bgm.tv/person/*/add_related/anime
// @match http*://bgm.tv/person/*/add_related/book
// @match http*://bgm.tv/person/*/add_related/music
// @match http*://bgm.tv/person/*/add_related/game
// @match http*://bgm.tv/person/*/add_related/real
// @match http*://chii.in/person/*/add_related/anime
// @match http*://chii.in/person/*/add_related/book
// @match http*://chii.in/person/*/add_related/music
// @match http*://chii.in/person/*/add_related/game
// @match http*://chii.in/person/*/add_related/real
// @match http*://bangumi.tv/person/*/add_related/anime
// @match http*://bangumi.tv/person/*/add_related/book
// @match http*://bangumi.tv/person/*/add_related/music
// @match http*://bangumi.tv/person/*/add_related/game
// @match http*://bangumi.tv/person/*/add_related/real
// @grant none
// @license MIT
// @gf
// ==/UserScript==
(function () {
'use strict';
const styleEl = document.createElement('style');
styleEl.textContent = `.bgm-mp-settings {
padding: 12px;
display: flex;
flex-flow: column;
gap: 10px;
}
.bgm-mp-settings .bgm-mp-row {
display: flex;
align-items: center;
gap: 8px;
}
.bgm-mp-settings .bgm-mp-row label {
color: #909399;
white-space: nowrap;
flex-shrink: 0;
margin-right: 8px;
}
.bgm-mp-settings .bgm-mp-row input[type='text'] {
height: 32px;
padding: 0 10px;
border: 1px solid #e4e7ed;
border-radius: 6px;
font-size: 13px;
color: #303133;
background: #fff;
outline: none;
transition: all 0.2s ease;
width: 100%;
max-width: 360px;
}
.bgm-mp-settings .bgm-mp-row input:focus {
border-color: var(--primary-color, #f09199);
box-shadow: 0 0 0 2px rgb(240 145 153 / 15%);
}
html[data-theme='dark'] .bgm-mp-settings .bgm-mp-row label {
color: #9a9a9a;
}
html[data-theme='dark'] .bgm-mp-settings .bgm-mp-row input[type='text'] {
border-color: #404040;
color: #dcdcdc;
background: #2d2e2f;
}
html[data-theme='dark'] .bgm-mp-settings .bgm-mp-row input[type='text']:focus {
border-color: var(--primary-color, #f09199);
box-shadow: 0 0 0 2px rgb(240 145 153 / 25%);
}
.bgm-mp-toggle {
appearance: none;
width: 40px;
height: 22px;
background: #dcdfe6;
border-radius: 11px;
border: none;
cursor: pointer;
position: relative;
transition: background 0.25s ease;
flex-shrink: 0;
margin: 0;
}
.bgm-mp-toggle::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
background: #fff;
border-radius: 50%;
transition: transform 0.25s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.bgm-mp-toggle:checked {
background: var(--primary-color, #f09199);
}
.bgm-mp-toggle:checked::after {
transform: translateX(18px);
}
html[data-theme='dark'] .bgm-mp-toggle {
background: #404040;
}
html[data-theme='dark'] .bgm-mp-toggle:checked {
background: var(--primary-color, #f09199);
}
html[data-theme='dark'] .bgm-mp-toggle::after {
background: #dcdcdc;
}
.bgm-mp-type-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.bgm-mp-type-label {
font-size: 13px;
color: #303133;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
}
html[data-theme='dark'] .bgm-mp-type-label {
color: #dcdcdc;
}
.bgm-mp-popup-types {
display: flex;
gap: 5px;
margin-left: 8px;
margin-right: auto;
}
.bgm-mp-popup-type {
font-size: 11px;
color: #909399;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
padding: 0 3px;
border-radius: 4px;
}
.bgm-mp-popup-type:hover {
background: rgba(240, 145, 153, 0.1);
}
.bgm-mp-popup-type input:checked + * {
color: var(--primary-color, #f09199);
}
html[data-theme='dark'] .bgm-mp-popup-type {
color: #9a9a9a;
}
html[data-theme='dark'] .bgm-mp-popup-type:hover {
background: rgba(240, 145, 153, 0.15);
}
#bgm-mp-container {
display: flex;
flex-direction: column;
gap: 8px;
float: right;
margin: 5px 0;
align-items: flex-end;
}
.bgm-mp-group {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.bgm-mp-select {
height: 32px;
padding: 0 28px 0 10px;
border: 1px solid #e4e7ed;
border-radius: 6px;
font-size: 13px;
color: #303133;
background: #fff;
outline: none;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23909399' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
transition: all 0.2s ease;
field-sizing: content;
}
.bgm-mp-select:focus {
border-color: var(--primary-color, #f09199);
box-shadow: 0 0 0 2px rgb(240 145 153 / 15%);
}
.bgm-mp-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
html[data-theme='dark'] .bgm-mp-select {
border-color: #404040;
color: #dcdcdc;
background-color: #2d2e2f;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239a9a9a' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
}
html[data-theme='dark'] .bgm-mp-select:focus {
border-color: var(--primary-color, #f09199);
box-shadow: 0 0 0 2px rgb(240 145 153 / 25%);
}
.bgm-mp-input {
height: 32px;
padding: 0 10px;
border: 1px solid #e4e7ed;
border-radius: 6px;
font-size: 13px;
color: #303133;
background: #fff;
outline: none;
transition: all 0.2s ease;
field-sizing: content;
}
.bgm-mp-input:focus {
border-color: var(--primary-color, #f09199);
box-shadow: 0 0 0 2px rgb(240 145 153 / 15%);
}
html[data-theme='dark'] .bgm-mp-input {
border-color: #404040;
color: #dcdcdc;
background: #2d2e2f;
}
html[data-theme='dark'] .bgm-mp-input:focus {
border-color: var(--primary-color, #f09199);
box-shadow: 0 0 0 2px rgb(240 145 153 / 25%);
}
/* Notification - wikiEpStaffRelate tip box style */
.bgm-mp-notify {
position: fixed;
width: min(380px, 100vw);
backdrop-filter: blur(10px);
background: rgba(254, 254, 254, 0.8);
border-radius: 15px;
background-clip: padding-box;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 5px 30px 10px rgba(80, 80, 80, 0.5);
z-index: 9999;
overflow: hidden;
font-size: 13px;
color: #303133;
}
html[data-theme='dark'] .bgm-mp-notify {
background: rgba(40, 40, 40, 0.8);
color: #fff;
box-shadow: 0 5px 30px 10px rgba(0, 0, 0, 0.2);
}
.bgm-mp-notify .staff-tip-handle {
height: 36px;
line-height: 36px;
padding: 0 16px;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
font-size: 14px;
font-weight: 500;
cursor: move;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
color: var(--primary-color, #f09199);
}
.bgm-mp-notify .staff-tip-content {
padding: 12px 16px;
max-height: 400px;
overflow-y: auto;
font-size: 13px;
min-height: 120px;
}
.bgm-mp-notify .staff-tip-title {
margin: 0 0 8px;
padding-bottom: 4px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
font-size: 14px;
}
/* 警告提示样式(镜像 wikiEpStaffRelate) */
.bgm-mp-notify .staff-warning-section {
padding: 10px 12px;
margin: 0 0 16px;
background: rgba(255, 248, 225, 0.6);
border: 1px solid rgba(255, 153, 0, 0.3);
border-radius: 8px;
color: #856404;
overflow-wrap: break-word;
}
.bgm-mp-notify .staff-warning-title {
font-size: 14px;
font-weight: 500;
}
html[data-theme='dark'] .bgm-mp-notify .staff-warning-section {
background: rgba(60, 40, 0, 0.4);
border-color: rgba(255, 153, 0, 0.5);
color: #ffd700;
}
/* 错误提示样式(镜像 wikiRelDiff staff-error-section) */
.bgm-mp-notify .staff-error-section {
padding: 10px 12px;
margin: 0 0 16px;
background: rgba(255, 224, 178, 0.6);
border: 1px solid rgba(255, 99, 71, 0.3);
border-radius: 8px;
color: #8b0000;
overflow-wrap: break-word;
}
.bgm-mp-notify .staff-error-title {
font-size: 14px;
font-weight: 500;
}
html[data-theme='dark'] .bgm-mp-notify .staff-error-section {
background: rgba(80, 0, 0, 0.4);
border-color: rgba(255, 99, 71, 0.5);
color: #ffb6c1;
}
/* 确认加载按钮(与 warning 格式一致,背景偏白/夜间黑) */
.bgm-mp-notify .staff-confirm-section {
padding: 10px 12px;
margin: 0 0 16px;
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(200, 200, 200, 0.3);
border-radius: 8px;
color: #303133;
overflow-wrap: break-word;
cursor: pointer;
text-align: center;
font-weight: 500;
transition: background 0.2s ease;
}
.bgm-mp-notify .staff-confirm-section:hover {
background: rgba(240, 240, 240, 0.8);
}
html[data-theme='dark'] .bgm-mp-notify .staff-confirm-section {
background: rgba(40, 40, 40, 0.6);
border-color: rgba(100, 100, 100, 0.5);
color: #dcdcdc;
}
html[data-theme='dark'] .bgm-mp-notify .staff-confirm-section:hover {
background: rgba(50, 50, 50, 0.8);
}
.bgm-mp-notify .staff-tip-title.unmatched {
color: #a0222e;
}
html[data-theme='dark'] .bgm-mp-notify .staff-tip-title {
border-bottom-color: rgba(255, 255, 255, 0.1);
}
html[data-theme='dark'] .bgm-mp-notify .staff-tip-title.unmatched {
color: #e57373;
}
.bgm-mp-notify-close {
background: none;
border: none;
cursor: pointer;
font-size: 18px;
color: #909399;
padding: 0;
line-height: 1;
}
.bgm-mp-notify-close:hover {
color: #e07a85;
}
.bgm-mp-btn {
display: inline-block;
background: #fff;
color: #303133;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 0 12px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
height: 30px;
}
html[data-theme='dark'] .bgm-mp-btn {
background: #2d2e2f;
color: #dcdcdc;
border-color: #404040;
}
html[data-theme='dark'] .bgm-mp-btn:hover,
.bgm-mp-btn:hover {
color: var(--primary-color, #f09199);
border-color: var(--primary-color, #f09199);
}
.bgm-mp-spinner {
display: inline-block;
width: 32px;
height: 32px;
margin-bottom: 16px;
border: 3px solid #e4e7ed;
border-top-color: var(--primary-color, #f09199);
border-radius: 50%;
animation: bgm-mp-spin 1.2s linear infinite;
}
html[data-theme='dark'] .bgm-mp-spinner {
border-color: #404040;
border-top-color: var(--primary-color, #f09199);
}
@keyframes bgm-mp-spin {
to {
transform: rotate(360deg);
}
}
.bgm-mp-loading-text {
text-align: center;
color: #909399;
font-size: 13px;
}
.bgm-mp-loading-wrap {
text-align: center;
padding: 40px 20px;
color: #909399;
}
/* Popup content */
.bgm-mp-result-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 16px;
}
.bgm-mp-section-title {
font-weight: 700;
}
.bgm-mp-type-section {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 10px;
}
.bgm-mp-type-title {
font-weight: 600;
color: var(--primary-color, #f09199);
margin-bottom: 6px;
}
html[data-theme='dark'] .bgm-mp-type-title {
color: var(--primary-color, #f09199);
}
.bgm-mp-empty-hint {
color: #909399;
margin-bottom: 8px;
text-align: center;
}
.bgm-mp-popup-actions {
margin-top: 16px;
display: flex;
gap: 8px;
justify-content: flex-end;
}
.bgm-mp-name-link {
color: #1a7a1a;
cursor: pointer;
text-decoration: underline !important;
}
.bgm-mp-pending-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.bgm-mp-pending-header {
margin-bottom: 8px;
color: #909399;
}
html[data-theme='dark'] .bgm-mp-name-link {
color: #51cf66;
}
.bgm-mp-subject-popup {
width: 400px;
max-width: 100vw;
max-height: 80vh;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.bgm-mp-status-box {
display: none;
padding: 8px 10px;
max-width: 320px;
background: #f5f7fa;
border: 1px solid #d9dee4;
border-radius: 6px;
color: #606266;
font-size: 12px;
line-height: 1.6;
overflow-wrap: break-word;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
}
.bgm-mp-status-box.show {
display: block;
}
html[data-theme='dark'] .bgm-mp-status-box {
background: #252627;
border-color: #4a4b4c;
color: #b9b9b9;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.35);
}
ul.cat a.bgm-mp-has-remaining::before {
content: '';
display: inline-block;
width: 6px;
height: 6px;
background: var(--primary-color, #f09199);
border-radius: 50%;
margin-right: 4px;
vertical-align: middle;
}`;
document.head.appendChild(styleEl);
(() => {
// src/position-ids.js
var POSITION_IDS = {
2: {
1: "\u539F\u4F5C",
2: "\u5BFC\u6F14",
3: "\u811A\u672C",
4: "\u5206\u955C",
5: "\u6F14\u51FA",
6: "\u97F3\u4E50",
7: "\u4EBA\u7269\u539F\u6848",
8: "\u4EBA\u7269\u8BBE\u5B9A",
9: "\u6784\u56FE",
10: "\u7CFB\u5217\u6784\u6210",
11: "\u7F8E\u672F\u76D1\u7763",
13: "\u8272\u5F69\u8BBE\u8BA1",
14: "\u603B\u4F5C\u753B\u76D1\u7763",
15: "\u4F5C\u753B\u76D1\u7763",
16: "\u673A\u68B0\u8BBE\u5B9A",
17: "\u6444\u5F71\u76D1\u7763",
18: "\u76D1\u4FEE",
19: "\u9053\u5177\u8BBE\u8BA1",
20: "\u539F\u753B",
21: "\u7B2C\u4E8C\u539F\u753B",
22: "\u52A8\u753B\u68C0\u67E5",
24: "\u5236\u4F5C\u52A9\u7406",
25: "\u80CC\u666F\u7F8E\u672F",
26: "\u8272\u5F69\u6307\u5B9A",
27: "\u6570\u7801\u7ED8\u56FE",
28: "\u526A\u8F91",
29: "\u539F\u6848",
30: "\u4E3B\u9898\u6B4C\u7F16\u66F2",
31: "\u4E3B\u9898\u6B4C\u4F5C\u66F2",
32: "\u4E3B\u9898\u6B4C\u4F5C\u8BCD",
33: "\u4E3B\u9898\u6B4C\u6F14\u51FA",
34: "\u63D2\u5165\u6B4C\u6F14\u51FA",
35: "\u4F01\u753B",
36: "\u4F01\u5212\u5236\u4F5C\u4EBA",
37: "\u5236\u4F5C\u7BA1\u7406",
38: "\u5BA3\u4F20",
39: "\u5F55\u97F3",
40: "\u5F55\u97F3\u52A9\u7406",
41: "\u7CFB\u5217\u76D1\u7763",
42: "\u88FD\u4F5C",
43: "\u8BBE\u5B9A",
44: "\u97F3\u54CD\u76D1\u7763",
45: "\u97F3\u54CD",
46: "\u97F3\u6548",
47: "\u7279\u6548",
48: "\u914D\u97F3\u76D1\u7763",
49: "\u8054\u5408\u5BFC\u6F14",
50: "\u80CC\u666F\u8BBE\u5B9A",
51: "\u8865\u95F4\u52A8\u753B",
52: "\u6267\u884C\u5236\u7247\u4EBA",
53: "\u52A9\u7406\u5236\u7247\u4EBA",
54: "\u5236\u7247\u4EBA",
55: "\u97F3\u4E50\u52A9\u7406",
56: "\u5236\u4F5C\u8FDB\u884C",
57: "\u6F14\u5458\u76D1\u7763",
58: "\u603B\u5236\u7247\u4EBA",
59: "\u8054\u5408\u5236\u7247\u4EBA",
60: "\u53F0\u8BCD\u7F16\u8F91",
61: "\u540E\u671F\u5236\u7247\u534F\u8C03",
62: "\u5236\u4F5C\u52A9\u7406",
63: "\u5236\u4F5C",
64: "\u5236\u4F5C\u534F\u8C03",
65: "\u97F3\u4E50\u5236\u4F5C",
66: "\u7279\u522B\u9E23\u8C22",
67: "\u52A8\u753B\u5236\u4F5C",
69: "CG \u5BFC\u6F14",
70: "\u673A\u68B0\u4F5C\u753B\u76D1\u7763",
71: "\u7F8E\u672F\u8BBE\u8BA1",
72: "\u526F\u5BFC\u6F14",
73: "OP\u30FBED \u5206\u955C",
74: "\u603B\u5BFC\u6F14",
75: "3DCG",
76: "\u5236\u4F5C\u534F\u529B",
77: "\u52A8\u4F5C\u4F5C\u753B\u76D1\u7763",
80: "\u76D1\u5236",
81: "\u534F\u529B",
82: "\u6444\u5F71",
83: "\u5236\u4F5C\u8FDB\u884C\u534F\u529B",
84: "\u8BBE\u5B9A\u5236\u4F5C",
85: "\u97F3\u4E50\u5236\u4F5C\u4EBA",
86: "3DCG \u5BFC\u6F14",
87: "\u52A8\u753B\u5236\u7247\u4EBA",
88: "\u7279\u6548\u4F5C\u753B\u76D1\u7763",
89: "\u4E3B\u6F14\u51FA",
90: "\u4F5C\u753B\u76D1\u7763\u52A9\u7406",
91: "\u6F14\u51FA\u52A9\u7406",
92: "\u4E3B\u52A8\u753B\u5E08",
93: "\u4E0A\u8272",
94: "\u4E0A\u8272\u68C0\u67E5",
95: "\u8272\u5F69\u68C0\u67E5",
96: "\u7F8E\u672F\u677F",
97: "\u7F8E\u672F",
98: "\u5370\u8C61\u677F",
99: "2D \u8BBE\u8BA1",
100: "3D \u8BBE\u8BA1",
101: "\u6280\u672F\u5BFC\u6F14",
102: "\u7279\u6280\u5BFC\u6F14",
103: "\u8272\u5F69\u811A\u672C",
104: "\u5206\u955C\u534F\u529B",
105: "\u5206\u955C\u6284\u5199",
106: "\u526F\u4EBA\u7269\u8BBE\u5B9A",
107: "\u5BA2\u5EA7\u4EBA\u7269\u8BBE\u5B9A",
108: "\u6784\u56FE\u76D1\u4FEE",
109: "\u6784\u56FE\u4F5C\u753B\u76D1\u7763",
110: "\u603B\u4F5C\u753B\u76D1\u7763\u52A9\u7406",
111: "\u9053\u5177\u4F5C\u753B\u76D1\u7763",
112: "\u6982\u5FF5\u8BBE\u8BA1",
113: "\u670D\u88C5\u8BBE\u8BA1",
114: "\u6807\u9898\u8BBE\u8BA1",
115: "\u8BBE\u5B9A\u534F\u529B",
116: "\u97F3\u4E50\u76D1\u7763",
117: "\u9009\u66F2",
118: "\u63D2\u5165\u6B4C\u4F5C\u8BCD",
119: "\u63D2\u5165\u6B4C\u4F5C\u66F2",
120: "\u63D2\u5165\u6B4C\u7F16\u66F2",
121: "\u521B\u610F\u5236\u7247\u4EBA",
122: "\u526F\u5236\u7247\u4EBA",
123: "\u5236\u4F5C\u7EDF\u62EC",
124: "\u73B0\u573A\u5236\u7247\u4EBA",
125: "\u6587\u827A\u5236\u4F5C",
127: "\u4F01\u753B\u534F\u529B",
128: "OP\u30FBED \u6F14\u51FA",
129: "Bank \u5206\u955C\u6F14\u51FA",
130: "Live \u5206\u955C\u6F14\u51FA",
131: "\u5267\u4E2D\u5267\u5206\u955C\u6F14\u51FA",
132: "\u5267\u4E2D\u5267\u4EBA\u8BBE",
133: "\u89C6\u89C9\u5BFC\u6F14",
134: "\u521B\u610F\u603B\u76D1",
135: "\u7279\u6444\u6548\u679C",
136: "\u89C6\u89C9\u6548\u679C",
137: "\u52A8\u4F5C\u5BFC\u6F14",
138: "\u8F6C\u573A\u7ED8",
139: "\u63D2\u753B",
140: "\u89D2\u8272\u4F5C\u753B\u76D1\u7763",
141: "\u4F5C\u753B\u76D1\u4FEE",
142: "\u673A\u8BBE\u539F\u6848",
143: "\u6982\u5FF5\u827A\u672F",
144: "\u89C6\u89C9\u6982\u5FF5",
145: "\u753B\u9762\u8BBE\u8BA1",
146: "\u602A\u7269\u8BBE\u8BA1",
147: "\u6545\u4E8B\u6982\u5FF5",
148: "\u5267\u672C\u534F\u8C03",
149: "\u811A\u672C\u534F\u529B",
150: "\u526F\u7CFB\u5217\u6784\u6210",
151: "\u6784\u6210\u534F\u529B",
152: "\u5F55\u97F3\u5DE5\u4F5C\u5BA4",
153: "\u6574\u97F3",
154: "\u97F3\u54CD\u5236\u4F5C\u62C5\u5F53",
155: "\u5728\u7EBF\u526A\u8F91",
156: "\u79BB\u7EBF\u526A\u8F91",
157: "3D \u52A8\u753B\u5E08",
158: "CG \u5236\u4F5C\u4EBA",
159: "\u5BA3\u4F20\u5236\u7247\u4EBA",
160: "\u7F8E\u672F\u5236\u4F5C\u4EBA",
161: "\u97F3\u54CD\u5236\u4F5C\u4EBA",
162: "CG \u5236\u4F5C\u8FDB\u884C",
163: "\u7F8E\u672F\u5236\u4F5C\u8FDB\u884C",
164: "\u7F8E\u672F\u76D1\u7763\u52A9\u7406",
165: "\u8272\u5F69\u8BBE\u8BA1\u52A9\u7406",
166: "\u6444\u5F71\u76D1\u7763\u52A9\u7406",
167: "\u5236\u4F5C\u7BA1\u7406\u52A9\u7406",
168: "\u8BBE\u5B9A\u5236\u4F5C\u52A9\u7406",
169: "\u526A\u8F91\u52A9\u7406",
170: "\u5BA3\u4F20\u534F\u529B",
171: "\u5EFA\u6A21/\u6A21\u578B",
172: "\u89C6\u89C9\u5F00\u53D1/\u5916\u89C2\u5F00\u53D1",
173: "\u52A8\u753B\u5BFC\u6F14",
174: "\u53D1\u884C",
175: "\u539F\u4F5C\u534F\u529B",
176: "\u88FD\u4F5C\u534F\u529B",
177: "\u88FD\u4F5C\u7EDF\u62EC",
178: "\u5236\u4F5C\u4E8B\u52A1",
179: "OP\u30FBED \u52A8\u753B\u5236\u4F5C",
180: "3DCG \u5BFC\u6F14\u52A9\u7406",
181: "CG \u5BFC\u6F14\u52A9\u7406",
182: "\u5236\u4F5C\u5236\u7247\u4EBA"
},
1: {
2001: "\u4F5C\u8005",
2002: "\u4F5C\u753B",
2003: "\u63D2\u56FE",
2004: "\u51FA\u7248\u793E",
2005: "\u8FDE\u8F7D\u6742\u5FD7",
2006: "\u8BD1\u8005",
2007: "\u539F\u4F5C",
2008: "\u5BA2\u4E32",
2009: "\u4EBA\u7269\u539F\u6848",
2010: "\u811A\u672C",
2011: "\u4E66\u7CFB",
2012: "\u51FA\u54C1\u65B9",
2013: "\u56FE\u4E66\u54C1\u724C",
2014: "\u7F16\u8457",
2015: "\u6784\u6210",
2016: "\u76D1\u4FEE",
2017: "\u89E3\u8BF4"
},
4: {
1001: "\u5F00\u53D1",
1002: "\u53D1\u884C",
1003: "\u6E38\u620F\u8BBE\u8BA1\u5E08",
1004: "\u5267\u672C",
1005: "\u7F8E\u5DE5",
1006: "\u97F3\u4E50",
1007: "\u5173\u5361\u8BBE\u8BA1",
1008: "\u4EBA\u7269\u8BBE\u5B9A",
1009: "\u4E3B\u9898\u6B4C\u4F5C\u66F2",
1010: "\u4E3B\u9898\u6B4C\u4F5C\u8BCD",
1011: "\u4E3B\u9898\u6B4C\u6F14\u51FA",
1012: "\u63D2\u5165\u6B4C\u6F14\u51FA",
1013: "\u539F\u753B",
1014: "\u52A8\u753B\u5236\u4F5C",
1015: "\u539F\u4F5C",
1016: "\u5BFC\u6F14",
1017: "\u52A8\u753B\u76D1\u7763",
1018: "\u5236\u4F5C\u603B\u6307\u6325",
1019: "QC",
1020: "\u52A8\u753B\u5267\u672C",
1021: "\u7A0B\u5E8F",
1022: "\u534F\u529B",
1023: "CG \u76D1\u4FEE",
1024: "SD\u539F\u753B",
1025: "\u80CC\u666F",
1026: "\u76D1\u4FEE",
1027: "\u7CFB\u5217\u6784\u6210",
1028: "\u4F01\u753B",
1029: "\u673A\u68B0\u8BBE\u5B9A",
1030: "\u97F3\u54CD\u76D1\u7763",
1031: "\u4F5C\u753B\u76D1\u7763",
1032: "\u5236\u4F5C\u4EBA",
1033: "\u6D77\u62A5",
1034: "UI",
1035: "\u914D\u97F3\u5BFC\u6F14"
},
3: {
3001: "\u827A\u672F\u5BB6",
3002: "\u5236\u4F5C\u4EBA",
3003: "\u4F5C\u66F2",
3004: "\u5382\u724C",
3005: "\u539F\u4F5C",
3006: "\u4F5C\u8BCD",
3007: "\u5F55\u97F3",
3008: "\u7F16\u66F2",
3009: "\u63D2\u56FE",
3010: "\u811A\u672C",
3011: "\u51FA\u7248\u65B9",
3012: "\u6BCD\u5E26\u5236\u4F5C",
3013: "\u6DF7\u97F3",
3014: "\u4E50\u5668",
3015: "\u58F0\u4E50",
3016: "\u5FF5\u767D/\u65C1\u767D",
3017: "\u4EBA\u58F0\u7F16\u8F91",
3018: "\u4EBA\u58F0\u6307\u5BFC",
3019: "\u5BA2\u4E32",
3020: "\u97F3\u54CD\u76D1\u7763",
3021: "\u5408\u5531",
3022: "\u548C\u58F0",
3023: "\u8BBE\u8BA1"
},
6: {
4001: "\u539F\u4F5C",
4002: "\u5BFC\u6F14",
4003: "\u7F16\u5267",
4004: "\u97F3\u4E50",
4005: "\u6267\u884C\u5236\u7247\u4EBA",
4006: "\u5171\u540C\u6267\u884C\u5236\u4F5C",
4007: "\u5236\u7247\u4EBA/\u5236\u4F5C\u4EBA",
4008: "\u76D1\u5236",
4009: "\u526F\u5236\u4F5C\u4EBA/\u5236\u4F5C\u987E\u95EE",
4010: "\u6545\u4E8B",
4011: "\u7F16\u5BA1",
4012: "\u526A\u8F91",
4013: "\u521B\u610F\u603B\u76D1",
4014: "\u6444\u5F71",
4015: "\u4E3B\u9898\u6B4C\u6F14\u51FA",
4016: "\u4E3B\u6F14",
4017: "\u914D\u89D2",
4018: "\u5236\u4F5C",
4019: "\u51FA\u54C1",
4020: "\u914D\u97F3\u5BFC\u6F14",
4021: "\u5F55\u97F3",
4022: "\u6D77\u62A5",
4023: "\u4F01\u5212/\u7B56\u5212",
4024: "\u526F\u5BFC\u6F14",
4025: "\u7F8E\u672F",
4026: "\u8054\u5408\u5236\u7247\u4EBA/\u526F\u5236\u7247\u4EBA",
4027: "\u52A8\u4F5C\u5BFC\u6F14/\u6B66\u672F\u6307\u5BFC",
4028: "\u7279\u6280\u6307\u5BFC",
4029: "\u7279\u6444\u5BFC\u6F14",
4030: "\u5B9E\u62CD\u7279\u6548",
4031: "\u89C6\u89C9\u7279\u6548",
4032: "3DCG",
4033: "\u8BBE\u5B9A",
4034: "\u4EBA\u7269\u8BBE\u8BA1/\u76AE\u5957\u8BBE\u8BA1",
4035: "\u9020\u578B\u8BBE\u8BA1",
4036: "\u751F\u7269\u8BBE\u8BA1",
4037: "\u602A\u517D\u8BBE\u8BA1",
4038: "\u627F\u5236\u65B9/\u6444\u5236",
4039: "\u5BA3\u4F20",
4040: "\u534F\u529B",
4041: "\u7279\u522B\u9E23\u8C22",
4042: "\u97F3\u54CD\u76D1\u7763",
4043: "\u540E\u671F",
4044: "\u53D1\u884C"
}
};
// src/config.js
var PROVIDER_KEY = "wikiMissingPositionsProvider";
var SHOW_KEY = "wikiMissingPositionsShow";
var DEFAULT_PROVIDER = "https://bgq.iccci.cc.cd";
// src/api.js
function hasChiiApp() {
return typeof chiiApp !== "undefined" && chiiApp;
}
function get(key, def) {
if (hasChiiApp()) {
return chiiApp.cloud_settings.get(key) || def;
}
return localStorage.getItem(key) || def;
}
function getProvider() {
return get(PROVIDER_KEY, DEFAULT_PROVIDER);
}
function getShow() {
return get(SHOW_KEY, "on");
}
function save(key, val) {
if (hasChiiApp()) {
chiiApp.cloud_settings.update({ [key]: val });
return;
}
localStorage.setItem(key, val);
}
function saveProvider(val) {
save(PROVIDER_KEY, val);
}
function saveShow(val) {
save(SHOW_KEY, val);
}
// src/appear-eps.js
function parseAppearEps(input) {
if (!input) return /* @__PURE__ */ new Set([]);
const rawSegments = input.split(",").map((seg) => seg.trim()).filter((seg) => seg);
const resultSet = /* @__PURE__ */ new Set();
rawSegments.forEach((seg) => {
if (seg.includes("-")) {
const [s, e] = seg.split("-").map((p) => p.trim());
if (isStrictInt(s) && isStrictInt(e)) {
const min = Math.min(Number(s), Number(e));
const max = Math.max(Number(s), Number(e));
for (let i = min; i <= max; i++) {
resultSet.add(i.toString());
}
} else {
resultSet.add(seg);
}
} else {
resultSet.add(seg);
}
});
return new Set(sortAppearEps(Array.from(resultSet)));
}
function isStrictInt(str) {
return /^-?\d+$/.test(str);
}
function sortAppearEps(eps) {
return eps.sort((a, b) => {
const isANum = isStrictInt(a);
const isBNum = isStrictInt(b);
if (isANum && isBNum) return Number(a) - Number(b);
if (isANum) return -1;
if (isBNum) return 1;
return a.localeCompare(b);
});
}
function genAppearEps(epArr) {
if (!epArr || !epArr.length) return "";
epArr = sortAppearEps([...new Set(epArr)]);
const integers = epArr.filter(isStrictInt).map(Number);
const others = epArr.filter((e) => !isStrictInt(e));
const rangeParts = [];
if (integers.length > 0) {
let start = integers[0];
let prev = integers[0];
for (let i = 1; i <= integers.length; i++) {
const curr = integers[i];
if (i < integers.length && curr === prev + 1) {
prev = curr;
} else {
rangeParts.push(start === prev ? `${start}` : `${start}-${prev}`);
if (i < integers.length) {
start = curr;
prev = curr;
}
}
}
}
return [...rangeParts, ...others].join(",");
}
// src/search.js
var _searchError = false;
function lastSearchFailed() {
return _searchError;
}
var createFetch = (method) => async (url, body) => {
const options = method === "POST" ? { method, body: JSON.stringify(body) } : { method };
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
_searchError = false;
return await response.json();
} catch (e) {
console.error(e);
_searchError = true;
return null;
}
};
var fetchPost = createFetch("POST");
var postSearch = async (cat, keyword, filter, limit = 1) => {
const url = `https://api.bgm.tv/v0/search/${cat}?limit=${limit}`;
const body = { keyword, filter };
const result = await fetchPost(url, body);
return result?.data;
};
var searchPrsnAll = (keyword) => postSearch("persons", keyword, void 0, 5);
function normalize(name) {
return name.replace(/\s/g, "").replaceAll("-", "").replace(/[\u30A1-\u30F6]/g, function(match) {
return String.fromCharCode(match.charCodeAt(0) - 96);
}).replace(/[\uFF21-\uFF5A]/g, function(match) {
return String.fromCharCode(match.charCodeAt(0) - 65248);
}).toLowerCase();
}
// src/person.js
async function checkExistingPerson(personName) {
const result = {
aliased: null,
aliasedMulti: null,
directMatches: null,
bangumiError: false,
aliasesError: false
};
const normalized = normalize(personName);
try {
let aliased = null;
const provider = getProvider();
try {
const res = await fetch(`${provider}/api/aliases/${encodeURIComponent(personName)}`);
if (res.ok) {
const data = await res.json();
if (Array.isArray(data) && data.length > 0) {
aliased = data[0];
if (data.length > 1) {
result.aliasedMulti = data;
}
}
} else {
result.aliasesError = true;
}
} catch (e) {
console.error("aliases API failed:", e);
result.aliasesError = true;
}
if (!aliased) {
aliased = await window.personAliasQuery?.(personName);
}
if (aliased) result.aliased = { name: aliased.name, id: aliased.id };
const searchResults = await searchPrsnAll(personName);
result.bangumiError = lastSearchFailed();
if (searchResults) {
const matches = searchResults.filter((r) => normalized === normalize(r.name));
if (matches.length) {
result.directMatches = matches.map((r) => {
const cn = (r.infobox || []).find((f) => f.key === "\u7B80\u4F53\u4E2D\u6587\u540D");
return { name: r.name, id: r.id, display: cn?.value || r.name };
});
}
}
} catch (e) {
console.error("checkExistingPerson failed:", e);
}
return result;
}
// src/errors.js
var BANGUMI_ERROR_TEXT = '\u7F51\u7EDC\u51FA\u9519\u4E86\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u67E5\u770B\u73ED\u5A18\u60C5\u51B5';
function ourApiErrorText(name) {
const q = encodeURIComponent(name || "");
return `\u7F51\u7EDC\u51FA\u9519\u4E86\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u770B\u770B\u6709\u6CA1\u6709\u5DF2\u7ECF\u5B58\u50A8\u7684\u7ED3\u679C`;
}
// src/subject-page.js
function errSection(html) {
return `
${html}
`;
}
var cacheKey = (name, t, target) => `mp:${name}:${t}:${target}`;
var LOADING_MSGS = [
"\u5750\u548C\u653E\u5BBD",
"\u6B63\u5728\u51C6\u5907\u6570\u636E
\u8BF7\u52FF\u2122\u5173\u95ED\u8BA1\u7B97\u673A",
"\u597D\u4E1C\u897F\u5C31\u8981\u6765\u4E86\uFF01",
() => `\u4F60\u5DF2\u5B8C\u6210${10 * (2 + Math.floor(Math.random() * 7))}%`,
"\u6B63\u5728\u5904\u7406\u4E00\u4E9B\u4E8B\u60C5",
"\u4F60\u6B63\u5728\u6210\u529F\uFF01",
"\u4E0D\u5DE7\u7684\u662F\uFF0C\u5B83\u82B1\u8D39\u7684\u65F6\u95F4\u6BD4\u901A\u5E38\u8981\u957F",
"\u518D\u7B49\u4E00\u4E0B\u4E0B\u5C31\u597D\u4E86",
"\u8FD9\u901A\u5E38\u4E0D\u4F1A\u592A\u4E45",
"\u6211\u4EEC\u6B63\u5728\u5E2E\u4F60\u641E\u5B9A\u4E00\u5207"
];
function randomMsg() {
const m = LOADING_MSGS[Math.floor(Math.random() * LOADING_MSGS.length)];
return typeof m === "function" ? m() : m;
}
function initSubjectPage() {
if (getShow() === "off") return;
const href = document.querySelector(".focus").href.split("/").pop();
const typeCode = { anime: 2, book: 1, music: 3, game: 4, real: 6 }[href] || 0;
if (!typeCode) return;
const posNames = new Set(Object.values(POSITION_IDS[typeCode] || {}));
const infobox = document.querySelector("#infobox");
if (!infobox) return;
const DELIM_RE = /[()[\]{}()<>《》「」『』【】+×·→//、,,;;:&&\\等]+/;
infobox.querySelectorAll("li:not(.sub_container):not(.sub_group)").forEach((li) => {
const tip = li.querySelector(".tip");
if (!tip) return;
const fieldName = tip.textContent.replace(/[::]\s*$/, "").trim();
if (!posNames.has(fieldName)) return;
const linked = /* @__PURE__ */ new Set();
li.querySelectorAll("a").forEach((a) => linked.add(a.textContent.trim()));
const clone = li.cloneNode(true);
clone.querySelectorAll("a, .tip").forEach((el) => el.remove());
const text = clone.textContent;
const names = text.split(DELIM_RE).map((s) => s.trim()).filter(Boolean);
names.sort((a, b) => b.length - a.length);
let tipHTML = "";
if (tip) {
tipHTML = tip.outerHTML;
tip.remove();
}
const unlinked = names.filter((n) => !linked.has(n));
if (unlinked.length) {
const nameRE = new RegExp(
`(?<=^|[^<\\w])(${unlinked.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})([^<\\w]|$)`,
"g"
);
const walker = document.createTreeWalker(li, NodeFilter.SHOW_TEXT, {
acceptNode: (node) => node.parentElement.closest("a, .tip") ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT
});
const tNodes = [];
while (walker.nextNode()) tNodes.push(walker.currentNode);
for (const node of tNodes) {
const html = node.textContent.replace(
nameRE,
(_, name, p3) => `${name}${p3}`
);
if (html !== node.textContent) {
const span = document.createElement("span");
span.innerHTML = html;
node.replaceWith(...span.childNodes);
}
}
}
if (tipHTML) li.insertAdjacentHTML("afterbegin", tipHTML);
});
document.querySelectorAll(".bgm-mp-name").forEach((a) => {
a.addEventListener("click", () => openSubjectPopup(a.dataset.name, typeCode));
});
}
var _abortController = null;
function openSubjectPopup(personName, typeCode) {
if (_abortController) _abortController.abort();
_abortController = new AbortController();
const signal = _abortController.signal;
const existing = document.querySelector(".bgm-mp-subject-popup");
if (existing) existing.remove();
const provider = getProvider();
const popup = document.createElement("div");
popup.className = "bgm-mp-notify bgm-mp-subject-popup";
const handle = document.createElement("div");
handle.className = "staff-tip-handle";
const typeNames = { 1: "\u4E66", 2: "\u52A8", 3: "\u4E50", 4: "\u6E38", 6: "\u5B9E" };
const typeChecks = [1, 2, 3, 4, 6].map(
(t) => ``
).join("");
handle.innerHTML = `${personName}`;
const content = document.createElement("div");
content.className = "staff-tip-content";
popup.append(handle, content);
document.body.appendChild(popup);
content.innerHTML = ``;
popup.querySelector(".bgm-mp-notify-close").onclick = () => {
_abortController.abort();
popup.remove();
};
let offX = 0, offY = 0;
function cx(e) {
return e.touches ? e.touches[0].clientX : e.clientX;
}
function cy(e) {
return e.touches ? e.touches[0].clientY : e.clientY;
}
handle.onmousedown = handle.ontouchstart = (e) => {
if (e.target.closest(".bgm-mp-notify-close, .bgm-mp-popup-type, .bgm-mp-popup-types")) return;
const rect = popup.getBoundingClientRect();
popup.style.transform = "none";
popup.style.left = rect.left + "px";
popup.style.top = rect.top + "px";
offX = cx(e) - rect.left;
offY = cy(e) - rect.top;
document.onmousemove = document.ontouchmove = (ev) => {
popup.style.left = cx(ev) - offX + "px";
popup.style.top = cy(ev) - offY + "px";
};
document.onmouseup = document.ontouchend = () => {
document.onmousemove = document.ontouchmove = null;
};
};
const errs = { bangumi: false, ours: false };
const doMultiFetch = (existing2, targetParam) => {
if (!_ready) return;
const checked = [...popup.querySelectorAll(".bgm-mp-type-check:checked")].map(
(c) => Number(c.value)
);
const targetId = (existing2?.aliased?.id || existing2?.directMatches?.[0]?.id || 0).toString();
const encoded = encodeURIComponent(personName);
let uncached = [];
let cached = {};
for (const t of checked) {
const key = cacheKey(encoded, t, targetId);
const cachedData = sessionStorage.getItem(key);
if (cachedData) {
try {
cached[t] = JSON.parse(cachedData);
} catch {
uncached.push(t);
}
} else {
uncached.push(t);
}
}
if (uncached.length === 0) {
renderResults(content, cached, null, encoded, personName, errs);
return;
}
_abortController.abort();
_abortController = new AbortController();
const sig = _abortController.signal;
content.innerHTML = ``;
fetchMultiType(
personName,
provider,
sig,
content,
targetParam,
uncached,
cached,
targetId,
errs
);
};
popup.querySelectorAll(".bgm-mp-type-check").forEach((cb) => {
cb.addEventListener("change", () => doMultiFetch(_existing, _targetParam));
});
let _existing = null, _targetParam = "", _ready = false;
(async () => {
const existing2 = await checkExistingPerson(personName);
_existing = existing2;
let targetParam = "";
if (existing2.aliased) targetParam = `&target=${existing2.aliased.id}`;
else if (existing2.directMatches) targetParam = `&target=${existing2.directMatches[0].id}`;
_targetParam = targetParam;
_ready = true;
errs.bangumi = existing2.bangumiError;
errs.ours = existing2.aliasesError;
const hasExisting = existing2.aliased || existing2.directMatches;
if (hasExisting) {
let warningHtml = "";
if (existing2.bangumiError) warningHtml += errSection(BANGUMI_ERROR_TEXT);
if (existing2.aliasesError) warningHtml += errSection(ourApiErrorText(personName));
if (existing2.aliased) {
if (existing2.aliasedMulti && existing2.aliasedMulti.length > 1) {
warningHtml += `\u522B\u540D\u4E3A\u300C${personName}\u300D\u5339\u914D\u5230\u591A\u4E2A\u4EBA\u7269\uFF0C\u5DF2\u53D6\u7B2C\u4E00\u4E2A\uFF1A
`;
for (const p of existing2.aliasedMulti) {
warningHtml += `
${p.name} `;
}
warningHtml += "
";
} else {
warningHtml += ``;
}
}
if (existing2.directMatches) {
warningHtml += '\u540C\u540D\u4EBA\u7269\u5DF2\u5B58\u5728\uFF1A
';
for (const p of existing2.directMatches) {
warningHtml += `
${p.display || p.name} `;
}
warningHtml += "
";
}
warningHtml += '\u4ECD\u7136\u52A0\u8F7D
';
content.innerHTML = warningHtml;
document.querySelector("#bgm-mp-confirm-btn").onclick = () => {
document.querySelector("#bgm-mp-confirm-btn").remove();
doMultiFetch(existing2, targetParam);
};
} else {
doMultiFetch(existing2, targetParam);
}
})();
}
async function fetchMultiType(personName, provider, signal, content, targetParam, types, cached, targetId, errs) {
const encodedName = encodeURIComponent(personName);
let subjectsByType = { ...cached }, episodesData = null;
const fetches = types.map(
(t) => fetch(`${provider}/api/persons/${encodedName}/missing-subjects?type=${t}${targetParam}`, {
signal
}).then((res) => {
if (!res.ok) {
errs.ours = true;
return null;
}
return res.json();
}).then((data) => {
if (data && Object.keys(data).length) {
sessionStorage.setItem(cacheKey(encodedName, t, targetId), JSON.stringify(data));
}
return { type: t, data };
}).catch((e) => {
if (e.name === "AbortError") throw e;
console.error(`missing-subjects type=${t} failed:`, e);
errs.ours = true;
return { type: t, data: null };
})
);
try {
const results = await Promise.all(fetches);
for (const r of results) {
if (r.data && Object.keys(r.data).length) {
subjectsByType[r.type] = r.data;
}
}
} catch (e) {
if (e.name !== "AbortError") throw e;
return;
}
if (types.includes(2)) {
try {
const epQuery = targetParam ? "?" + targetParam.slice(1) : "";
const epRes = await fetch(
`${provider}/api/persons/${encodedName}/missing-episodes${epQuery}`,
{ signal }
);
if (epRes.ok) episodesData = await epRes.json();
else errs.ours = true;
} catch (e) {
if (e.name === "AbortError") return;
errs.ours = true;
}
}
renderResults(content, subjectsByType, episodesData, encodedName, personName, errs);
}
function renderResults(content, subjectsByType, episodesData, encodedName, personName, errs) {
const typeNamesFull = { 1: "\u4E66\u7C4D", 2: "\u52A8\u753B", 3: "\u97F3\u4E50", 4: "\u6E38\u620F", 6: "\u4E09\u6B21\u5143" };
const totalEntries = Object.values(subjectsByType).reduce((c, d) => c + Object.keys(d).length, 0);
const hasData = totalEntries || episodesData && (Object.keys(episodesData.matched || {}).length || Object.keys(episodesData.unmatched || {}).length);
let html = "";
if (errs?.bangumi) html += errSection(BANGUMI_ERROR_TEXT);
if (!hasData && errs?.ours) html += errSection(ourApiErrorText(personName));
if (totalEntries) {
html += '';
html += '
\u7F3A\u5931\u6761\u76EE\u5173\u8054\uFF1A
';
for (const t of [2, 1, 3, 4, 6]) {
const data = subjectsByType[t];
if (!data) continue;
html += `
${typeNamesFull[t]}
`;
for (const [sid, entry] of Object.entries(data)) {
const posNames = (entry.positions || []).map((pid) => POSITION_IDS[t]?.[pid] || pid).join("\u3001");
html += `
`;
}
html += "
";
}
html += "
";
}
if (episodesData) {
const matched = Object.entries(episodesData.matched || {});
const unmatched = Object.entries(episodesData.unmatched || {});
if (matched.length) {
html += '';
html += '
\u7F3A\u5931\u5267\u96C6\u5173\u8054\uFF1A
';
for (const [sid, entry] of matched) {
const parts = Object.entries(entry.episodes || {}).map(
([pid, labels]) => `${POSITION_IDS[2]?.[pid] || pid}\uFF1A${genAppearEps(labels)}`
);
html += `
`;
}
html += "
";
}
if (unmatched.length) {
html += '';
html += '
\u7591\u4F3C\u7F3A\u5931\u5267\u96C6\u5173\u8054\uFF1A
';
for (const [sid, entry] of unmatched) {
html += `
`;
}
html += "
";
}
}
if (!hasData && !errs?.bangumi && !errs?.ours) {
html = '\u672A\u627E\u5230\u7F3A\u5931\u5173\u8054
';
}
html += ``;
content.innerHTML = html;
document.querySelector("#bgm-mp-create-btn").onclick = () => {
if (!hasData) return;
const allSubjects = {};
for (const [t, data] of Object.entries(subjectsByType)) {
for (const [sid, entry] of Object.entries(data)) {
allSubjects[`${t}:${sid}`] = { ...entry, _type: Number(t) };
}
}
localStorage.setItem(
"bgm-mp-pending",
JSON.stringify({
personName,
subjectsData: allSubjects,
episodesData
})
);
window.open("/person/new");
};
}
async function initPersonNewPage() {
const params = new URLSearchParams(location.search);
let raw = null;
if (params.has("bgm_mp") && window.opener) {
raw = await new Promise((resolve) => {
const timer = setTimeout(() => resolve(null), 3e3);
const handler = (e) => {
if (e.data && e.data.type === "bgm_mp_data" && e.data.data) {
clearTimeout(timer);
window.removeEventListener("message", handler);
resolve(e.data.data);
}
};
window.addEventListener("message", handler);
window.opener.postMessage({ type: "bgm_mp_request" }, "*");
});
}
if (!raw) {
raw = localStorage.getItem("bgm-mp-pending");
}
if (!raw) {
raw = window.name && window.name.startsWith("{") ? window.name : null;
}
if (!raw) {
const nameParam = params.get("name");
if (nameParam) {
const input = document.querySelector("#crt_name");
if (input) input.value = nameParam;
}
return;
}
try {
const data = JSON.parse(raw);
const input = document.querySelector("#crt_name");
if (!input || !data.personName) {
localStorage.removeItem("bgm-mp-pending");
window.name = "";
return;
}
input.value = data.personName;
localStorage.setItem("bgm-mp-pending", raw);
window.name = "";
if (params.has("bgm_mp")) {
params.delete("bgm_mp");
const qs = params.toString();
history.replaceState(null, "", qs ? location.pathname + "?" + qs : location.pathname);
}
} catch (_e) {
localStorage.removeItem("bgm-mp-pending");
}
}
function addAliasLine(value, aliasName) {
const aliasLine = `[${aliasName}]`;
const lines = value.split("\n");
const aliasIdx = lines.findIndex((l) => /^\|别名=\{$/.test(l.trim()));
if (aliasIdx >= 0) {
if (lines.slice(aliasIdx + 1).some((l) => l.trim() === aliasLine)) return value;
lines.splice(aliasIdx + 1, 0, aliasLine);
return lines.join("\n");
}
const cnIdx = lines.findIndex((l) => /^\|简体中文名=/.test(l.trim()));
const insertIdx = cnIdx >= 0 ? cnIdx + 1 : 1;
lines.splice(insertIdx, 0, "|\u522B\u540D={", aliasLine, "}");
return lines.join("\n");
}
async function initPersonEditPage() {
const params = new URLSearchParams(location.search);
let raw = null;
if (params.has("bgm_mp_alias") && window.opener) {
raw = await new Promise((resolve) => {
const timer = setTimeout(() => resolve(null), 3e3);
const handler = (e) => {
if (e.data && e.data.type === "bgm_mp_alias_data" && e.data.data) {
clearTimeout(timer);
window.removeEventListener("message", handler);
resolve(e.data.data);
}
};
window.addEventListener("message", handler);
window.opener.postMessage({ type: "bgm_mp_alias_request" }, "*");
});
}
if (!raw) return;
try {
const data = JSON.parse(raw);
const variantName = data.personName;
if (!variantName) return;
const infoboxInput = document.querySelector("#subject_infobox");
const isNormal = typeof nowmode !== "undefined" && nowmode === "normal";
if (isNormal) {
if (typeof NormaltoWCODE !== "function") return;
NormaltoWCODE();
}
if (!infoboxInput) return;
infoboxInput.value = addAliasLine(infoboxInput.value, variantName);
if (isNormal) {
if (typeof WCODEtoNormal !== "function") return;
WCODEtoNormal();
}
infoboxInput.scrollIntoView({ block: "center" });
infoboxInput.style.outline = "2px solid #ffb300";
setTimeout(() => {
infoboxInput.style.outline = "";
}, 4e3);
} catch (_e) {
}
}
function initPersonPage() {
const raw = localStorage.getItem("bgm-mp-pending");
if (!raw) return;
const personId2 = location.pathname.match(/\/person\/(\d+)/)?.[1];
if (!personId2) return;
try {
const data = JSON.parse(raw);
if (!data.subjectsData) return;
if (data.personId != null) {
if (String(data.personId) !== personId2) return;
} else if (!document.referrer.includes("/person/new")) {
return;
}
const typeExts = { 1: "book", 2: "anime", 3: "music", 4: "game", 6: "real" };
const types = [
...new Set(
Object.values(data.subjectsData).map((e) => e._type).filter(Boolean)
)
];
if (types.length >= 1) {
const ext = typeExts[types[0]];
if (ext) {
if (data.personId == null) {
data.personId = Number(personId2);
}
localStorage.setItem("bgm-mp-pending", JSON.stringify(data));
location.href = `/person/${personId2}/add_related/${ext}`;
}
}
} catch (e) {
console.error("initPersonPage failed:", e);
}
}
// src/popup.js
function showPendingEps(allUnmatched, personName, type2) {
const existing = document.querySelector(".bgm-mp-notify");
if (existing) existing.remove();
const sections = allUnmatched.map(({ sid, entry }) => {
const eps = entry.episodes || [];
const epLinks = eps.map(
(ep) => `${ep.label}`
).join(", ");
return { sid, entry, eps, epLinks };
});
if (!sections.length) return;
const notify = document.createElement("div");
notify.className = "bgm-mp-notify";
const handle = document.createElement("div");
handle.className = "staff-tip-handle";
handle.innerHTML = '\u7591\u4F3C\u5339\u914D';
const content = document.createElement("div");
content.className = "staff-tip-content";
let html = '';
for (const sec of sections) {
html += ``;
}
content.innerHTML = html;
notify.append(handle, content);
notify.style.opacity = "0";
document.body.appendChild(notify);
const boxW = notify.offsetWidth;
const boxH = notify.offsetHeight;
const winW = window.innerWidth;
const winH = window.innerHeight;
let right = 50, bottom = 50;
right = Math.min(right, winW - boxW);
right = Math.max(right, 0);
bottom = Math.min(bottom, winH - boxH);
bottom = Math.max(bottom, 0);
notify.style.bottom = `${bottom}px`;
notify.style.right = `${right}px`;
notify.style.opacity = "";
notify.querySelectorAll(".bgm-mp-btn").forEach((btn) => {
btn.onclick = () => {
const sec = sections.find((s) => String(s.sid) === btn.dataset.sid);
if (!sec) return;
const epLabels = sec.eps.map((ep) => ep.label);
if (!epLabels.length) return;
subjectList = [
{
id: Number(sec.sid),
type_id: type2,
name: sec.entry.name,
name_cn: "",
url_mod: "subject"
}
];
addRelateSubject(0, "submitForm");
};
});
notify.querySelectorAll(".bgm-mp-copy-btn").forEach((btn) => {
btn.onclick = () => {
const sec = sections.find((s) => String(s.sid) === btn.dataset.sid);
if (!sec) return;
const epLabels = sec.eps.map((ep) => ep.label);
if (!epLabels.length) return;
navigator.clipboard.writeText(genAppearEps(epLabels));
const orig = btn.textContent;
btn.textContent = "\u590D\u5236\u6210\u529F";
setTimeout(() => btn.textContent = orig, 2e3);
};
});
notify.querySelectorAll(".bgm-mp-locate-btn").forEach((btn) => {
btn.onclick = () => {
document.querySelector('[data-group-mode="subject"]').click();
const l = document.querySelector(`[href="/subject/${btn.dataset.sid}"]`);
if (!l) return;
window.location.href += `#:~:text=${l.textContent}`;
};
});
let offX = 0, offY = 0;
function cx(e) {
return e.touches ? e.touches[0].clientX : e.clientX;
}
function cy(e) {
return e.touches ? e.touches[0].clientY : e.clientY;
}
handle.onmousedown = handle.ontouchstart = (e) => {
if (e.target.closest(".bgm-mp-notify-close")) return;
offX = cx(e) - notify.getBoundingClientRect().left;
offY = cy(e) - notify.getBoundingClientRect().top;
document.onmousemove = document.ontouchmove = (ev) => {
notify.style.left = cx(ev) - offX + "px";
notify.style.top = cy(ev) - offY + "px";
notify.style.right = "auto";
};
document.onmouseup = document.ontouchend = () => {
document.onmousemove = document.ontouchmove = null;
};
};
notify.querySelector(".bgm-mp-notify-close").onclick = () => notify.remove();
}
// src/add-related.js
var select;
var type;
var nameInput;
var epNameInput;
var epBtn;
var personId;
var statusBox;
function setStatusBox(html) {
if (!statusBox) return;
statusBox.innerHTML = html || "";
statusBox.classList.toggle("show", Boolean(html));
}
function addSubjectLi(sid, posId, name) {
const existing = document.querySelector(`#crtRelateSubjects li:has([href="/subject/${sid}"])`);
if (existing?.querySelector('select[name$="[prsnPos]"]')?.value === posId) return existing;
subjectList = [{ id: Number(sid), type_id: type, name, name_cn: "", url_mod: "subject" }];
addRelateSubject(0, "submitForm");
document.querySelector('#crtRelateSubjects select[name$="[prsnPos]"]').value = posId;
return document.querySelector(`#crtRelateSubjects li:has([href="/subject/${sid}"])`);
}
async function processEpisodesData(data, queryName) {
let none = true;
for (const [sid, entry] of Object.entries(data.matched || {})) {
for (const [posId, labels] of Object.entries(entry.episodes || {})) {
const li = addSubjectLi(sid, posId, entry.name);
const epInput = li.querySelector('[name$="[appear_eps]"]');
if (epInput) {
const currentSet = parseAppearEps(epInput.value);
const hasAll = labels.every((l) => currentSet.has(l));
if (!hasAll) {
none = false;
epInput.value = genAppearEps(labels);
if (li.classList.contains("old")) {
li.style.background = document.documentElement.getAttribute("data-theme") === "dark" ? "rgba(255, 248, 165, 0.08)" : "rgba(255, 248, 165, 0.2)";
}
}
}
}
}
const allUnmatched = [];
for (const [sid, entry] of Object.entries(data.unmatched || {})) {
none = false;
allUnmatched.push({ sid, entry });
}
if (allUnmatched.length) showPendingEps(allUnmatched, queryName, type);
return none;
}
function resolveTarget(name) {
if (!name || !personId) return "";
return `&target=${personId}`;
}
async function runEpisodeCheck() {
const alias = epNameInput.value.trim();
const queryName = alias || document.querySelector(".nameSingle").textContent.trim();
epBtn.disabled = true;
setStatusBox("\u83B7\u53D6\u4E2D\u2026\u2026");
const targetParam = await resolveTarget(alias);
const pending = getPendingData();
if (pending && pending.episodesData && (Object.keys(pending.episodesData.matched || {}).length || Object.keys(pending.episodesData.unmatched || {}).length)) {
const none = await processEpisodesData(pending.episodesData, queryName);
setStatusBox(none ? "\u672A\u67E5\u627E\u5230\u4EFB\u4F55\u5DF2\u586B\u5199\u5267\u96C6" : "\u5267\u96C6\u5173\u8054\u5B8C\u6210\uFF01");
epBtn.disabled = false;
return;
}
const provider = getProvider();
try {
const url = `${provider}/api/persons/${encodeURIComponent(queryName)}/missing-episodes${targetParam ? "?" + targetParam.slice(1) : ""}`;
const res = await fetch(url);
const data = await res.json();
const none = await processEpisodesData(data, queryName);
setStatusBox(none ? "\u672A\u67E5\u627E\u5230\u4EFB\u4F55\u5DF2\u586B\u5199\u5267\u96C6" : "\u5267\u96C6\u5173\u8054\u5B8C\u6210\uFF01");
} catch (e) {
console.error(e);
setStatusBox(ourApiErrorText(queryName));
} finally {
epBtn.disabled = false;
}
}
function initAddRelated() {
const personName = document.querySelector(".nameSingle").textContent.trim();
const pidMatch = location.pathname.match(/\/person\/(\d+)/);
personId = pidMatch ? pidMatch[1] : "";
type = {
anime: 2,
book: 1,
music: 3,
game: 4,
real: 6
}[document.querySelector(".cat .selected").href.split("/").pop()];
select = document.createElement("select");
select.className = "bgm-mp-select";
let posOpts = '';
Object.keys(POSITION_IDS[type] || {}).map(Number).sort(function(a, b) {
return a - b;
}).forEach(function(id) {
posOpts += ``;
});
select.innerHTML = posOpts;
const container = document.createElement("div");
container.id = "bgm-mp-container";
const group1 = document.createElement("div");
group1.className = "bgm-mp-group";
nameInput = document.createElement("input");
nameInput.type = "text";
nameInput.className = "bgm-mp-input";
nameInput.placeholder = "\u522B\u540D\uFF08\u53EF\u9009\uFF09";
const btn = document.createElement("button");
btn.textContent = "\u5173\u8054\u5DF2\u586B\u5199\u6761\u76EE";
btn.id = "missingPositions";
btn.className = "bgm-mp-btn";
btn.addEventListener("click", async () => {
const position = select.value;
const pending = getPendingData();
if (pending && pending.subjectsData && Object.keys(pending.subjectsData).length) {
const resEntries = Object.entries(pending.subjectsData);
let none = true;
for (const [key, entry] of resEntries) {
if ((entry._type || 0) !== type) continue;
const sid = key.split(":").pop();
for (const pos of entry.positions || []) {
if (position && String(pos) !== position) continue;
const existing = document.querySelector(
`#crtRelateSubjects li:has([href="/subject/${sid}"])`
);
if (existing?.querySelector('select[name$="[prsnPos]"]')?.value !== pos) {
none = false;
subjectList = [
{ id: Number(sid), type_id: type, name: entry.name, name_cn: "", url_mod: "subject" }
];
addRelateSubject(0, "submitForm");
document.querySelector('#crtRelateSubjects select[name$="[prsnPos]"]').value = pos;
}
}
}
setStatusBox(none ? "\u672A\u67E5\u627E\u5230\u4EFB\u4F55\u5DF2\u586B\u5199\u6761\u76EE" : "\u5173\u8054\u586B\u5199\u5B8C\u6210\uFF01");
return;
}
const provider = getProvider();
try {
btn.disabled = true;
setStatusBox("\u83B7\u53D6\u4E2D\u2026\u2026");
const alias = nameInput.value.trim();
const targetParam = await resolveTarget(alias);
const res = await fetch(
`${provider}/api/persons/${encodeURIComponent(alias || personName)}/missing-subjects?type=${type}&position=${position}${targetParam}`
);
const data = await res.json();
const resEntries = Object.entries(data);
let none = true;
for (const [id, entry] of resEntries) {
for (const pos of entry.positions) {
const existing = document.querySelector(
`#crtRelateSubjects li:has([href="/subject/${id}"])`
);
if (existing?.querySelector('select[name$="[prsnPos]"]')?.value !== pos) {
none = false;
subjectList = [
{ id: Number(id), type_id: type, name: entry.name, name_cn: "", url_mod: "subject" }
];
addRelateSubject(0, "submitForm");
document.querySelector('#crtRelateSubjects select[name$="[prsnPos]"]').value = pos;
}
}
}
setStatusBox(none ? "\u672A\u67E5\u627E\u5230\u4EFB\u4F55\u5DF2\u586B\u5199\u6761\u76EE" : "\u5173\u8054\u586B\u5199\u5B8C\u6210\uFF01");
} catch (e) {
console.error(e);
setStatusBox(ourApiErrorText(personName));
} finally {
btn.disabled = false;
}
});
const group2 = document.createElement("div");
group2.className = "bgm-mp-group";
if (type === 2) {
epNameInput = document.createElement("input");
epNameInput.type = "text";
epNameInput.className = "bgm-mp-input";
epNameInput.placeholder = "\u522B\u540D\uFF08\u53EF\u9009\uFF09";
epBtn = document.createElement("button");
epBtn.textContent = "\u5173\u8054\u5DF2\u586B\u5199\u5267\u96C6";
epBtn.id = "missingEpisodes";
epBtn.className = "bgm-mp-btn";
epBtn.addEventListener("click", runEpisodeCheck);
group2.append(epNameInput, epBtn);
}
group1.append(nameInput, select, btn);
statusBox = document.createElement("div");
statusBox.className = "bgm-mp-status-box";
container.append(group1, group2, statusBox);
document.querySelector("#indexCatBox").after(container);
processPendingData();
}
var _pendingData = null;
var _relateBackdoor = false;
function getPendingData() {
return _pendingData;
}
function processPendingData() {
const urlParams = new URLSearchParams(location.search);
if (urlParams.has("bgm_mp_relate") && window.opener && !_relateBackdoor) {
const handler = (e) => {
if (e.data && e.data.type === "bgm_mp_relate_data" && e.data.data) {
window.removeEventListener("message", handler);
_relateBackdoor = true;
localStorage.setItem("bgm-mp-pending", e.data.data);
processPendingData();
}
};
window.addEventListener("message", handler);
window.opener.postMessage({ type: "bgm_mp_relate_request" }, "*");
return;
}
const raw = localStorage.getItem("bgm-mp-pending");
if (!raw) return;
const referrer = document.referrer;
if (!_relateBackdoor && !referrer.includes("/person/new") && !referrer.match(/\/person\/(\d+)/))
return;
try {
const data = JSON.parse(raw);
_pendingData = data;
if (!data.subjectsData) {
localStorage.removeItem("bgm-mp-pending");
return;
}
if (data.personId != null && String(data.personId) !== personId) return;
const typeMap = { book: 1, anime: 2, music: 3, game: 4, real: 6 };
const pageType = typeMap[location.pathname.split("/").pop()] || 0;
const matching = {};
const remaining = {};
let hasRemaining = false;
for (const [key, entry] of Object.entries(data.subjectsData)) {
if ((entry._type || 0) === pageType) {
matching[key] = entry;
} else {
remaining[key] = entry;
hasRemaining = true;
}
}
let consumed = true;
for (const [key, entry] of Object.entries(matching)) {
for (const posId of entry.positions || []) {
const li = addSubjectLi(Number(key.split(":").pop()), posId, entry.name);
if (li && !li.classList.contains("old")) {
consumed = false;
}
if (li && li.classList.contains("old")) {
li.style.background = document.documentElement.getAttribute("data-theme") === "dark" ? "rgba(255, 248, 165, 0.08)" : "rgba(255, 248, 165, 0.2)";
}
}
}
if (pageType === 2 && data.episodesData?.matched) {
for (const [sid, entry] of Object.entries(data.episodesData.matched)) {
for (const [posId, labels] of Object.entries(entry.episodes || {})) {
const li = addSubjectLi(Number(sid), Number(posId), entry.name);
const epInput = li.querySelector('[name$="[appear_eps]"]');
if (epInput) {
epInput.value = genAppearEps(labels);
if (li.classList.contains("old")) {
li.style.background = document.documentElement.getAttribute("data-theme") === "dark" ? "rgba(255, 248, 165, 0.08)" : "rgba(255, 248, 165, 0.2)";
}
}
}
}
}
if (pageType === 2 && Object.keys(data.episodesData?.unmatched || {}).length) {
const allUnmatched = Object.entries(data.episodesData.unmatched).map(([sid, entry]) => ({
sid,
entry
}));
showPendingEps(allUnmatched, data.personName, 2);
}
if (hasRemaining) {
localStorage.setItem(
"bgm-mp-pending",
JSON.stringify({
personName: data.personName,
subjectsData: remaining,
episodesData: null,
personId: data.personId
})
);
} else {
localStorage.removeItem("bgm-mp-pending");
}
markRemainingTypes(remaining);
if (hasRemaining && consumed) {
const typeExts = { 1: "book", 2: "anime", 3: "music", 4: "game", 6: "real" };
const nextType = [
...new Set(
Object.values(remaining).map((e) => e._type).filter(Boolean)
)
][0];
if (nextType) {
const personIdMatch = location.pathname.match(/\/person\/(\d+)/);
if (personIdMatch)
location.href = `/person/${personIdMatch[1]}/add_related/${typeExts[nextType]}`;
}
}
} catch (e) {
localStorage.removeItem("bgm-mp-pending");
}
}
function markRemainingTypes(remaining) {
const cat = document.querySelector("ul.cat");
if (!cat) return;
const types = new Set(
Object.values(remaining).map((e) => e._type).filter(Boolean)
);
const typeExts = { 1: "book", 2: "anime", 3: "music", 4: "game", 6: "real" };
cat.querySelectorAll("a").forEach((a) => {
a.classList.remove("bgm-mp-has-remaining");
const href = a.getAttribute("href") || "";
for (const [t, ext] of Object.entries(typeExts)) {
if (types.has(Number(t)) && href.endsWith("/" + ext)) {
a.classList.add("bgm-mp-has-remaining");
return;
}
}
});
}
// src/index.js
var pathname = location.pathname;
(function route() {
if (/^\/subject\/\d+$/.test(pathname)) {
initSubjectPage();
return;
}
if (pathname === "/person/new") {
initPersonNewPage();
return;
}
if (/^\/person\/\d+\/edit$/.test(pathname)) {
initPersonEditPage();
return;
}
if (/^\/person\/\d+$/.test(pathname)) {
initPersonPage();
return;
}
initAddRelated();
})();
if (typeof chiiLib !== "undefined" && chiiLib.ukagaka && chiiLib.ukagaka.addPanelTab) {
chiiLib.ukagaka.addPanelTab({
tab: "wiki_missing_positions",
label: "\u7F3A\u5931\u804C\u4F4D",
type: "custom",
customContent: function() {
const provider = getProvider();
const show = getShow();
return (
/* html */
`
`
);
},
onInit: function(tabSelector, $tabContent) {
$tabContent.off("change", "#bgm-mp-provider").on("change", "#bgm-mp-provider", function() {
saveProvider($(this).val());
});
$tabContent.off("change", "#bgm-mp-show").on("change", "#bgm-mp-show", function() {
saveShow(this.checked ? "on" : "off");
});
}
});
}
})();
})();