MediaWiki:Common.js:修订间差异
MediaWiki界面页面
更多操作
无编辑摘要 |
无编辑摘要 |
||
| 第1,162行: | 第1,162行: | ||
var affectionKey = 'wiki_pet_affection'; | var affectionKey = 'wiki_pet_affection'; | ||
var affection = parseInt(localStorage.getItem(affectionKey)) || 0; | var affection = parseInt(localStorage.getItem(affectionKey)) || 0; | ||
var affectionPerPet = 5; | var affectionPerPet = 5; | ||
var affectionCooldown = | var affectionCooldown = 500; | ||
var lastPetTime = 0; | var lastPetTime = 0; | ||
var levels = [ | var levels = [ | ||
| 第1,202行: | 第1,202行: | ||
} | } | ||
function spawnHeart(x, y) { | function spawnHeart(x, y) { | ||
var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨']; | var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨']; | ||
| 第1,215行: | 第1,214行: | ||
} | } | ||
function triggerLevelUp() { | function triggerLevelUp() { | ||
$pet.addClass('level-up'); | $pet.addClass('level-up'); | ||
| 第1,245行: | 第1,243行: | ||
$img.attr('src', pettingGif); | $img.attr('src', pettingGif); | ||
$pet.addClass('petting'); | $pet.addClass('petting'); | ||
petTimer = setTimeout(function() { setPetState('idle'); }, 1200); | petTimer = setTimeout(function() { setPetState('idle'); }, 1200); | ||
} | } | ||
| 第1,258行: | 第1,255行: | ||
setPetState('petting'); | setPetState('petting'); | ||
var oldLevel = getLevel(affection).title; | var oldLevel = getLevel(affection).title; | ||
affection += affectionPerPet; | affection += affectionPerPet; | ||
| 第1,264行: | 第1,260行: | ||
showAffectionPanel(); | showAffectionPanel(); | ||
var newLevel = getLevel(affection).title; | var newLevel = getLevel(affection).title; | ||
if (newLevel !== oldLevel) { | if (newLevel !== oldLevel) { | ||
triggerLevelUp(); | triggerLevelUp(); | ||
$tooltip.text('好感度提升!' + newLevel + '!🎉'); | $tooltip.text('好感度提升!' + newLevel + '!🎉'); | ||
setTimeout(function() { | |||
var lvl = getLevel(affection); | |||
$tooltip.text('点击抚摸我~ ' + lvl.emoji); | |||
}, 2000); | |||
} | } | ||
var offset = $pet.offset(); | var offset = $pet.offset(); | ||
spawnHeart(offset.left + 30, offset.top - 10); | spawnHeart(offset.left + 30, offset.top - 10); | ||
}); | }); | ||
setInterval(function() { | setInterval(function() { | ||
if (petState === 'idle' && Math.random() < 0.3) { | if (petState === 'idle' && Math.random() < 0.3) { | ||
| 第1,286行: | 第1,283行: | ||
}, 10000); | }, 10000); | ||
$pet.on('touchstart', function(e) { | $pet.on('touchstart', function(e) { | ||
e.stopPropagation(); | e.stopPropagation(); | ||
| 第1,302行: | 第1,298行: | ||
if (newLevel !== oldLevel) { | if (newLevel !== oldLevel) { | ||
triggerLevelUp(); | triggerLevelUp(); | ||
$tooltip.text('好感度提升!' + newLevel + '!🎉'); | |||
setTimeout(function() { | |||
var lvl = getLevel(affection); | |||
$tooltip.text('点击抚摸我~ ' + lvl.emoji); | |||
}, 2000); | |||
} | } | ||
2026年6月13日 (六) 01:41的版本
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */
// 自动加载 MediaWiki:Footer 页面内容,并插入到每个页面底部
$(document).ready(function() {
const namespace = mw.config.get('wgNamespaceNumber');
const action = mw.config.get('wgAction');
if (namespace !== 0 || action !== 'view') {
return;
}
fetch("/api.php?action=parse&page=MediaWiki:Footer&format=json")
.then(res => res.json())
.then(data => {
if (data.parse && data.parse.text) {
const html = data.parse.text['*'];
$('#mw-content-text').append('<div class="global-footer">' + html + '</div>');
}
});
});
// 用户颜色、图标、状态、师徒、昵称、里程碑、公告、植物/僵尸筛选、好友、私信 主逻辑
$(function () {
// ==================== 动态注入彩虹样式 ====================
var rainbowStyle = document.createElement('style');
rainbowStyle.textContent =
'.rainbow-user {' +
'font-weight: bold;' +
'background: repeating-linear-gradient(90deg, red 0px, orange 10px, yellow 20px, green 30px, blue 40px, indigo 50px, violet 60px);' +
'-webkit-background-clip: text;' +
'-webkit-text-fill-color: transparent;' +
'background-clip: text;' +
'}';
document.head.appendChild(rainbowStyle);
// ==================== 配置区 ====================
var mentorList = ['愤怒的郎朗', 'Yuyaabc', '虚位以待', '知更鸟头号粉丝', '凌玥'];
var newbieEditThreshold = 25;
var milestoneThresholds = [10, 50, 100, 500, 1000, 2500, 5000, 10000, 20000, 50000];
// ==================== 辅助函数 ====================
function getUserName(link) {
var $span = $(link).find('span').first();
if ($span.length) return $span.text().trim();
return $(link).text().trim();
}
function isUserLink(link) {
return $(link).is('a.mw-userlink') || $(link).find('span').length > 0;
}
function getLevelIcons(editcount) {
var totalStars = Math.floor(editcount / 100);
if (totalStars === 0) return '';
var crowns = Math.floor(totalStars / 125);
var remaining = totalStars % 125;
var suns = Math.floor(remaining / 25);
remaining %= 25;
var moons = Math.floor(remaining / 5);
var stars = remaining % 5;
var icons = '';
if (crowns > 0) icons += '👑'.repeat(crowns);
if (suns > 0) icons += '☀️'.repeat(suns);
if (moons > 0) icons += '🌙'.repeat(moons);
if (stars > 0) icons += '⭐'.repeat(stars);
return icons;
}
function updateLevelIcons($link, editcount) {
var iconStr = getLevelIcons(editcount);
var $iconSpan = $link.next('.user-level-icons');
if (iconStr === '') {
$iconSpan.remove();
return;
}
if ($iconSpan.length === 0) {
$iconSpan = $('<span class="user-level-icons"></span>');
$link.after($iconSpan);
}
$iconSpan.text(iconStr);
}
function getTitleByEditcount(ec) {
if (ec >= 10000) return '🐉 神话';
if (ec >= 5000) return '👑 传奇';
if (ec >= 2500) return '🏆 大师';
if (ec >= 1000) return '💎 专家';
if (ec >= 500) return '🔥 资深';
if (ec >= 100) return '⭐ 活跃';
if (ec >= 50) return '🌳 入门';
if (ec >= 10) return '🌿 新手';
return '🌱 萌新';
}
function addTitleTag($link, editcount) {
if ($link.data('title-tag-added')) return;
$link.data('title-tag-added', true);
var title = getTitleByEditcount(editcount);
var $tag = $('<span class="user-title-tag">' + title + '</span>');
$link.after($tag);
}
// ==================== 核心:颜色 + 图标 + 状态 + 师徒 + 昵称 ====================
function colorizeAndStatus($links) {
if ($links.length === 0) return;
var $fresh = $links.filter(function () {
return !$(this).data('user-status-processed');
});
if ($fresh.length === 0) return;
var users = [];
$fresh.each(function () {
var name = getUserName(this);
if (name && users.indexOf(name) === -1) users.push(name);
});
if (users.length === 0) return;
$fresh.each(function () {
$(this).data('user-status-processed', true);
});
var batchSize = 50;
var batches = [];
for (var i = 0; i < users.length; i += batchSize) {
batches.push(users.slice(i, i + batchSize));
}
var processBatch = function (batch) {
var api = new mw.Api();
return api.get({
action: 'query',
list: 'users',
ususers: batch.join('|'),
usprop: 'editcount'
}).then(function (data) {
var classMap = {};
var editCountMap = {};
if (data.query && data.query.users) {
data.query.users.forEach(function (u) {
var ec = u.editcount || 0;
editCountMap[u.name] = ec;
if (ec >= 5000) classMap[u.name] = 'rainbow-user';
else if (ec >= 2500) classMap[u.name] = 'gold-user';
else if (ec >= 1000) classMap[u.name] = 'platinum-user';
else if (ec >= 500) classMap[u.name] = 'silver-user';
else if (ec >= 1) classMap[u.name] = 'bronze-user';
});
}
$fresh.each(function () {
var $this = $(this);
var name = getUserName(this);
var cls = classMap[name];
if (cls) {
$this.removeClass('bronze-user silver-user platinum-user gold-user rainbow-user');
$this.addClass(cls);
}
var ec = editCountMap[name];
if (typeof ec !== 'undefined') {
updateLevelIcons($this, ec);
var isInsideCard = $this.closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
if (!isInsideCard) {
addTitleTag($this, ec);
}
}
});
});
};
var colorPromise = $.Deferred().resolve();
batches.forEach(function (batch) {
colorPromise = colorPromise.then(function () {
return processBatch(batch);
});
});
$fresh.each(function () {
if (isUserLink(this)) {
var isInsideCard = $(this).closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
if (!isInsideCard) {
var username = getUserName(this);
addStatusDot($(this), username);
addMentorTag($(this), username);
}
}
});
}
// ==================== 状态圆点 ====================
function addStatusDot($link, username) {
if ($link.data('status-dot-added')) return;
$link.data('status-dot-added', true);
var $dot = $('<span class="user-status-dot status-offline" title="离线"></span>');
$link.after($dot);
var cacheKey = 'mw_user_status_' + mw.config.get('wgDBname') + '_' + username;
var cached = localStorage.getItem(cacheKey);
var now = Date.now();
if (cached) {
try {
var data = JSON.parse(cached);
if (now - data.timestamp < 5 * 60 * 1000) {
updateDotStyle($dot, data.lastEditTime);
return;
}
} catch (e) {}
}
var api = new mw.Api();
api.get({
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: 1,
ucprop: 'timestamp'
}).then(function (data) {
var lastEditTime = null;
if (data.query && data.query.usercontribs && data.query.usercontribs.length > 0) {
lastEditTime = data.query.usercontribs[0].timestamp;
}
localStorage.setItem(cacheKey, JSON.stringify({
lastEditTime: lastEditTime,
timestamp: now
}));
updateDotStyle($dot, lastEditTime);
}).fail(function () {});
}
function updateDotStyle($dot, lastEditTime) {
if (!lastEditTime) {
$dot.attr('title', '离线');
$dot.removeClass('status-online status-away').addClass('status-offline');
return;
}
var last = new Date(lastEditTime).getTime();
var diffMinutes = (Date.now() - last) / 60000;
if (diffMinutes < 15) {
$dot.attr('title', '在线(15分钟内活跃)');
$dot.removeClass('status-offline status-away').addClass('status-online');
} else if (diffMinutes < 60) {
$dot.attr('title', '近期活跃(1小时内)');
$dot.removeClass('status-offline status-online').addClass('status-away');
} else {
$dot.attr('title', '离线');
$dot.removeClass('status-online status-away').addClass('status-offline');
}
}
// ==================== 师徒标签 ====================
function addMentorTag($link, username) {
if ($link.data('mentor-tag-added')) return;
if ($link.next('.user-tag').length) return;
var $tag;
if (mentorList.indexOf(username) !== -1) {
$link.data('mentor-tag-added', true);
$tag = $('<span class="user-tag user-tag-mentor">导师</span>');
$link.after($tag);
return;
}
if ($link.data('newbie-tag-checked')) return;
$link.data('newbie-tag-checked', true);
var cacheKey = 'mw_newbie_check_' + mw.config.get('wgDBname') + '_' + username;
var cached = localStorage.getItem(cacheKey);
var now = Date.now();
if (cached) {
try {
var data = JSON.parse(cached);
if (now - data.timestamp < 60 * 60 * 1000) {
if (data.editcount <= newbieEditThreshold) {
$tag = $('<span class="user-tag user-tag-newbie">新手</span>');
$link.after($tag);
}
return;
}
} catch (e) {}
}
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: username,
usprop: 'editcount'
}).then(function (data) {
var editcount = 0;
if (data.query && data.query.users && data.query.users.length > 0) {
editcount = data.query.users[0].editcount || 0;
}
localStorage.setItem(cacheKey, JSON.stringify({
editcount: editcount,
timestamp: now
}));
if (editcount <= newbieEditThreshold) {
if ($link.next('.user-tag-newbie').length === 0) {
$tag = $('<span class="user-tag user-tag-newbie">新手</span>');
$link.after($tag);
}
}
}).fail(function () {});
}
// ==================== 编辑里程碑弹窗 ====================
var currentUser = mw.config.get('wgUserName');
if (currentUser) {
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: currentUser,
usprop: 'editcount'
}).then(function(data) {
var editcount = 0;
if (data.query && data.query.users && data.query.users.length > 0) {
editcount = data.query.users[0].editcount || 0;
}
var achievedMilestone = null;
milestoneThresholds.forEach(function(m) {
if (editcount >= m) achievedMilestone = m;
});
if (achievedMilestone) {
var storageKey = 'mw_milestone_shown_' + currentUser + '_' + achievedMilestone;
if (!localStorage.getItem(storageKey)) {
localStorage.setItem(storageKey, '1');
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.5)', 'z-index': 99998,
display: 'flex', 'align-items': 'center', 'justify-content': 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', 'border-radius': '12px', padding: '30px 40px',
'text-align': 'center', 'box-shadow': '0 4px 20px rgba(0,0,0,0.3)',
'max-width': '400px', width: '80%', position: 'relative'
}
});
var $title = $('<h2>', {
text: '🎉 恭喜!',
css: { 'margin-bottom': '15px', 'font-size': '24px', color: '#333' }
});
var $msg = $('<p>', {
text: '你已达到 ' + achievedMilestone + ' 次编辑!',
css: { 'font-size': '18px', 'margin-bottom': '25px', color: '#555' }
});
var $btn = $('<button>', {
text: '太棒了!',
css: {
background: '#4CAF50', color: '#fff', border: 'none',
padding: '10px 30px', 'border-radius': '8px', 'font-size': '16px', cursor: 'pointer'
},
click: function() {
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
$box.append($title, $msg, $btn);
$overlay.append($box);
$('body').append($overlay);
}
}
}).fail(function() {});
}
// ==================== 用户链接处理器启动 ====================
var linkSelector = 'a.mw-userlink, .citizen-menu_card-content a, .citizen-userMenu a';
colorizeAndStatus($(linkSelector));
var observer = new MutationObserver(function () {
colorizeAndStatus($(linkSelector));
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setInterval(function () {
colorizeAndStatus($(linkSelector));
}, 3000);
// ==================== 植物卡片筛选 ====================
if ($('#pf-name').length) {
var $cards = $('.pvzhe-card');
var $name = $('#pf-name');
var $sunMax = $('#pf-sun-max');
var $cdMax = $('#pf-cd-max');
var $type = $('#pf-type');
var $version = $('#pf-version');
var $reset = $('#pf-reset');
function filterPlants() {
var name = $name.val().toLowerCase();
var sunRaw = $sunMax.val().trim();
var cdRaw = $cdMax.val().trim();
var sunTarget = sunRaw !== '' ? parseFloat(sunRaw) : null;
var cdTarget = cdRaw !== '' ? parseFloat(cdRaw) : null;
var type = $type.val();
var version = $version.val();
$cards.each(function () {
var $card = $(this);
var n = $card.find('.pvzhe-card-name').text().toLowerCase();
var sun = parseFloat($card.data('sun')) || 0;
var cd = parseFloat($card.data('cooldown')) || 0;
var t = ($card.data('type') || '').toString();
var v = ($card.data('version') || '').toString();
var show = true;
if (name && n.indexOf(name) === -1) show = false;
if (sunTarget !== null && sun !== sunTarget) show = false;
if (cdTarget !== null && cd !== cdTarget) show = false;
if (type) {
var cardTypes = t.split(',').map(function (s) { return s.trim(); });
if (cardTypes.indexOf(type) === -1) show = false;
}
if (version && v !== version) show = false;
$card.toggleClass('hidden-card', !show);
});
}
$name.on('input', filterPlants);
$sunMax.on('input keyup', filterPlants);
$cdMax.on('input keyup', filterPlants);
$type.on('change', filterPlants);
$version.on('change', filterPlants);
$reset.on('click', function () {
$name.val('');
$sunMax.val('');
$cdMax.val('');
$type.val('');
$version.val('');
filterPlants();
});
}
// ==================== 僵尸卡片筛选 ====================
function getArmorArray(healthStr) {
if (!healthStr || healthStr.indexOf('+') === -1) return [];
var armorPart = healthStr.split('+')[0].trim();
return armorPart.split(',').map(function(s) { return s.trim(); });
}
function extractHealth(healthStr) {
var matches = healthStr.match(/\d+/g);
if (matches && matches.length > 0) {
return parseFloat(matches[matches.length - 1]) || 0;
}
return 0;
}
if ($('#zf-name').length) {
var $zcards = $('.pvzhe-card');
var $zname = $('#zf-name');
var $healthMin = $('#zf-health-min');
var $armor = $('#zf-armor');
var $speed = $('#zf-speed');
var $ztype = $('#zf-type');
var $zversion = $('#zf-version');
var $zreset = $('#zf-reset');
function filterZombies() {
var name = $zname.val().toLowerCase();
var healthRaw = $healthMin.val().trim();
var healthTarget = healthRaw !== '' ? parseFloat(healthRaw) : null;
var armor = $armor.val();
var speed = $speed.val();
var type = $ztype.val();
var version = $zversion.val();
$zcards.each(function () {
var $card = $(this);
var n = $card.find('.pvzhe-card-name').text().toLowerCase();
var t = ($card.data('type') || '').toString();
var s = ($card.data('speed') || '').toString();
var healthStr = ($card.data('health') || '').toString();
var health = extractHealth(healthStr);
var armors = getArmorArray(healthStr);
var v = ($card.data('version') || '').toString();
var show = true;
if (name && n.indexOf(name) === -1) show = false;
if (healthTarget !== null && health !== healthTarget) show = false;
if (armor === 'none') {
if (armors.length > 0) show = false;
} else if (armor) {
if (armors.indexOf(armor) === -1) show = false;
}
if (speed) {
var cardSpeeds = s.split(',').map(function (v) { return v.trim(); });
if (cardSpeeds.indexOf(speed) === -1) show = false;
}
if (type) {
var cardTypes = t.split(',').map(function (v) { return v.trim(); });
if (cardTypes.indexOf(type) === -1) show = false;
}
if (version && v !== version) show = false;
$card.toggleClass('hidden-card', !show);
});
}
$zname.on('input', filterZombies);
$healthMin.on('input keyup', filterZombies);
$armor.on('change', filterZombies);
$speed.on('change', filterZombies);
$ztype.on('change', filterZombies);
$zversion.on('change', filterZombies);
$zreset.on('click', function () {
$zname.val('');
$healthMin.val('');
$armor.val('');
$speed.val('');
$ztype.val('');
$zversion.val('');
filterZombies();
});
}
// ==================== 返回顶部按钮 ====================
$('body').append('<button id="back-to-top" title="返回顶部">⬆</button>');
var $backBtn = $('#back-to-top');
$(window).scroll(function() {
$backBtn.toggle($(this).scrollTop() > 300);
});
$backBtn.click(function() {
$('html, body').animate({ scrollTop: 0 }, 400);
});
// ==================== 好友系统 ====================
var friendApi = new mw.Api();
function getFriendPageName(username) {
return 'User:' + username + '/friends';
}
function getFriendRequestsPageName(username) {
return 'User:' + username + '/friendrequests';
}
function loadFriendList(username, callback) {
friendApi.get({
action: 'query',
titles: getFriendPageName(username),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function loadFriendRequests(username, callback) {
friendApi.get({
action: 'query',
titles: getFriendRequestsPageName(username),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function saveToPage(pageName, data, summary, callback) {
friendApi.postWithEditToken({
action: 'edit',
title: pageName,
text: JSON.stringify(data, null, 2),
summary: summary,
minor: true,
bot: true
}).then(function() {
if (callback) callback(true);
}).fail(function() {
if (callback) callback(false);
});
}
function sendFriendRequest(toUser) {
if (!toUser || toUser === currentUser) return;
loadFriendList(currentUser, function(myFriends) {
if (myFriends.indexOf(toUser) !== -1) {
alert('你们已经是好友了!');
return;
}
loadFriendRequests(toUser, function(requests) {
var alreadySent = requests.some(function(r) { return r.from === currentUser; });
if (alreadySent) {
alert('你已经发送过好友请求了,请等待对方回应。');
return;
}
requests.push({ from: currentUser, time: Date.now() });
saveToPage(getFriendRequestsPageName(toUser), requests, currentUser + ' 发送了好友请求', function(success) {
if (success) {
alert('好友请求已发送给 ' + toUser + '!');
updateFriendButton(toUser, 'pending');
} else {
alert('发送失败,请稍后重试。');
}
});
});
});
}
function acceptFriendRequest(fromUser) {
loadFriendList(currentUser, function(myFriends) {
myFriends.push(fromUser);
saveToPage(getFriendPageName(currentUser), myFriends, '添加好友: ' + fromUser, function() {
loadFriendList(fromUser, function(theirFriends) {
theirFriends.push(currentUser);
saveToPage(getFriendPageName(fromUser), theirFriends, '添加好友: ' + currentUser, function() {
loadFriendRequests(currentUser, function(requests) {
requests = requests.filter(function(r) { return r.from !== fromUser; });
saveToPage(getFriendRequestsPageName(currentUser), requests, '接受好友请求', function() {
if ($('#friend-requests-list').length) showFriendRequests();
updateFriendButton(fromUser, 'added');
});
});
});
});
});
});
}
function rejectFriendRequest(fromUser) {
loadFriendRequests(currentUser, function(requests) {
requests = requests.filter(function(r) { return r.from !== fromUser; });
saveToPage(getFriendRequestsPageName(currentUser), requests, '拒绝好友请求', function() {
if ($('#friend-requests-list').length) showFriendRequests();
});
});
}
function removeFriend(friendName) {
if (!confirm('确定要删除好友 ' + friendName + ' 吗?')) return;
loadFriendList(currentUser, function(myFriends) {
myFriends = myFriends.filter(function(f) { return f !== friendName; });
saveToPage(getFriendPageName(currentUser), myFriends, '删除好友: ' + friendName, function() {
loadFriendList(friendName, function(theirFriends) {
theirFriends = theirFriends.filter(function(f) { return f !== currentUser; });
saveToPage(getFriendPageName(friendName), theirFriends, '删除好友: ' + currentUser, function() {
if ($('#friend-list-container').length) showFriendList();
updateFriendButton(friendName, 'add');
});
});
});
});
}
function updateFriendButton(username, status) {
var $btn = $('#friend-action-btn');
if (!$btn.length) return;
$btn.removeClass('friend-add-btn friend-added-btn friend-pending-btn');
if (status === 'added') {
$btn.addClass('friend-added-btn').text('✓ 已添加');
$btn.off('click').click(function() { removeFriend(username); });
} else if (status === 'pending') {
$btn.addClass('friend-pending-btn').text('⏳ 等待确认').off('click');
} else {
$btn.addClass('friend-add-btn').text('+ 加好友');
$btn.off('click').click(function() { sendFriendRequest(username); });
}
}
function showFriendRequests() {
loadFriendRequests(currentUser, function(requests) {
var $list = $('#friend-requests-list');
if (!$list.length) return;
$list.empty();
if (requests.length === 0) {
$list.append('<p style="color:#999;">暂无好友请求</p>');
return;
}
requests.forEach(function(r) {
var $item = $('<div>', { class: 'friend-request-item' });
$item.append('<span><a href="/w/User:' + encodeURIComponent(r.from) + '">' + r.from + '</a></span>');
var $actions = $('<div>');
$actions.append('<button class="friend-accept-btn" data-from="' + r.from + '">接受</button>');
$actions.append('<button class="friend-reject-btn" data-from="' + r.from + '">拒绝</button>');
$item.append($actions);
$list.append($item);
});
$list.off('click').on('click', '.friend-accept-btn', function() {
acceptFriendRequest($(this).data('from'));
}).on('click', '.friend-reject-btn', function() {
rejectFriendRequest($(this).data('from'));
});
});
}
function showFriendList() {
loadFriendList(currentUser, function(friends) {
loadFriendRequests(currentUser, function(requests) {
var $overlay = $('<div>', { class: 'friend-overlay' });
var $dialog = $('<div>', { class: 'friend-dialog' });
var realCount = friends.length;
$dialog.append('<h3>👥 好友列表 <span class="friend-count">' + realCount + '人</span></h3>');
if (requests.length > 0) {
$dialog.append('<p style="color:#f44336;cursor:pointer;" id="friend-req-notice">📩 有 ' + requests.length + ' 条好友请求,点击查看</p>');
}
$dialog.append('<div id="friend-requests-list" style="display:none;margin:10px 0;"></div>');
if (realCount === 0) {
$dialog.append('<p style="color:#999;">还没有好友,去其他用户页面加好友吧!</p>');
} else {
var $container = $('<div>', { id: 'friend-list-container', style: 'margin:10px 0;' });
friends.forEach(function(f) {
var $item = $('<span>', { class: 'friend-list-item' });
$item.append('<a href="/w/User:' + encodeURIComponent(f) + '">' + f + '</a>');
$item.append(' <a href="javascript:void(0)" class="friend-msg-link" data-friend="' + f + '" title="发私信">✉️</a>');
$item.append('<span class="friend-remove" data-friend="' + f + '"> ×</span>');
$container.append($item);
});
$dialog.append($container);
}
$dialog.append('<button style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
$overlay.append($dialog);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
$overlay.remove();
}
});
$dialog.on('click', '#friend-req-notice', function() {
var $reqList = $('#friend-requests-list');
$reqList.toggle();
if ($reqList.is(':visible')) showFriendRequests();
});
$dialog.on('click', '.friend-remove', function() {
removeFriend($(this).data('friend'));
});
$dialog.on('click', '.friend-msg-link', function() {
var friendName = $(this).data('friend');
$overlay.remove();
sendMessage(friendName);
});
});
});
}
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
var pageUser = mw.config.get('wgTitle');
if (pageUser !== currentUser) {
var $btn = $('<button>', {
id: 'friend-action-btn',
class: 'friend-btn friend-add-btn',
text: '+ 加好友'
});
$('#firstHeading').append($btn);
loadFriendList(currentUser, function(myFriends) {
if (myFriends.indexOf(pageUser) !== -1) {
updateFriendButton(pageUser, 'added');
} else {
loadFriendRequests(pageUser, function(requests) {
var alreadySent = requests.some(function(r) { return r.from === currentUser; });
if (alreadySent) {
updateFriendButton(pageUser, 'pending');
} else {
$btn.click(function() { sendFriendRequest(pageUser); });
}
});
}
});
}
}
if (currentUser) {
var $userMenu = $('#pt-userpage, .citizen-userMenu').first();
if ($userMenu.length) {
$userMenu.after(' <a href="javascript:void(0)" id="friend-list-trigger" style="font-size:13px;" title="好友列表">👥</a>');
}
$(document).on('click', '#friend-list-trigger', function() {
showFriendList();
});
setInterval(function() {
loadFriendRequests(currentUser, function(requests) {
var $notice = $('#friend-request-count');
if (requests.length > 0) {
if (!$notice.length && $('#friend-list-trigger').length) {
$('#friend-list-trigger').after('<span id="friend-request-count" style="color:#f44336;font-size:11px;vertical-align:super;">' + requests.length + '</span>');
} else if ($notice.length) {
$notice.text(requests.length);
}
} else {
$notice.remove();
}
});
}, 60000);
}
// ==================== 私信系统 ====================
var msgApi = new mw.Api();
function getMsgPageName(username) {
return 'User:' + username + '/messages';
}
function loadMessages(callback) {
if (!currentUser) { callback([]); return; }
msgApi.get({
action: 'query',
titles: getMsgPageName(currentUser),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
}
}
callback([]);
}).fail(function() { callback([]); });
}
function saveMessages(msgs, callback) {
if (!currentUser) return;
msgApi.postWithEditToken({
action: 'edit',
title: getMsgPageName(currentUser),
text: JSON.stringify(msgs, null, 2),
summary: '更新私信',
minor: true,
bot: true
}).then(function() {
if (callback) callback(true);
}).fail(function() {
if (callback) callback(false);
});
}
function getUnreadCount(msgs) {
var count = 0;
msgs.forEach(function(m) { if (!m.read) count++; });
return count;
}
function updateMsgBadge() {
loadMessages(function(msgs) {
var count = getUnreadCount(msgs);
var $badge = $('#msg-badge');
if ($badge.length === 0) {
var $drawer = $('.citizen-drawer').first();
if ($drawer.length) {
$drawer.css('position', 'relative');
$drawer.append('<span id="msg-badge" class="msg-badge" title="新私信">0</span>');
$badge = $('#msg-badge');
}
}
if ($badge.length) {
$badge.text(count).toggle(count > 0);
}
});
}
function showInbox() {
loadMessages(function(msgs) {
var $overlay = $('<div>', { class: 'msg-overlay' });
var $box = $('<div>', { class: 'msg-box' });
$box.append('<h3 style="margin-bottom:15px;">📬 私信箱</h3>');
msgs.sort(function(a, b) { return b.time - a.time; });
if (msgs.length === 0) {
$box.append('<p style="color:#999;">暂无消息</p>');
} else {
msgs.forEach(function(m, index) {
var $item = $('<div>', { class: 'msg-item' + (m.read ? '' : ' unread') });
var timeStr = new Date(m.time).toLocaleString('zh-CN');
$item.append(
'<div><span class="msg-sender">' + m.from + '</span><span class="msg-time">' + timeStr + '</span></div>',
'<div class="msg-text">' + m.text.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>') + '</div>',
'<div class="msg-actions">' +
(m.read ? '' : '<button class="mark-read" data-index="' + index + '">已读</button>') +
'<button class="del-msg" data-index="' + index + '">删除</button>' +
'<button class="reply-msg" data-index="' + index + '" data-from="' + m.from + '">回复</button>' +
'</div>'
);
$box.append($item);
});
}
$box.append('<button style="margin-top:15px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
$overlay.remove();
updateMsgBadge();
}
});
$box.on('click', '.mark-read', function() {
var idx = parseInt($(this).data('index'));
msgs[idx].read = true;
saveMessages(msgs, function() {
updateMsgBadge();
$overlay.remove();
showInbox();
});
});
$box.on('click', '.del-msg', function() {
var idx = parseInt($(this).data('index'));
msgs.splice(idx, 1);
saveMessages(msgs, function() {
$overlay.remove();
showInbox();
});
});
$box.on('click', '.reply-msg', function() {
var toUser = $(this).data('from');
$overlay.remove();
sendMessage(toUser);
});
});
}
function sendMessage(toUser) {
if (!toUser) return;
var $overlay = $('<div>', { class: 'msg-overlay' });
var $box = $('<div>', { class: 'msg-box' });
$box.append('<h3 style="margin-bottom:10px;">✉️ 发送私信给 ' + toUser + '</h3>');
$box.append('<textarea class="msg-textarea" placeholder="输入消息内容..."></textarea>');
$box.append(
'<button class="msg-send-submit" style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#4CAF50;color:#fff;border:none;border-radius:6px;">发送</button>',
'<button style="margin-left:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">取消</button>'
);
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay) || $(e.target).text() === '取消') {
$overlay.remove();
}
});
$box.on('click', '.msg-send-submit', function() {
var text = $box.find('.msg-textarea').val().trim();
if (!text) return;
$box.find('.msg-send-submit').prop('disabled', true).text('发送中...');
var tempApi = new mw.Api();
tempApi.get({
action: 'query',
titles: getMsgPageName(toUser),
prop: 'revisions',
rvprop: 'content',
rvlimit: 1
}).then(function(data) {
var pages = data.query.pages;
var msgs = [];
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try { msgs = JSON.parse(pages[id].revisions[0]['*']); } catch(e) {}
}
}
msgs.push({
from: currentUser,
to: toUser,
text: text,
time: Date.now(),
read: false
});
tempApi.postWithEditToken({
action: 'edit',
title: getMsgPageName(toUser),
text: JSON.stringify(msgs, null, 2),
summary: currentUser + ' 发来一条私信',
minor: false,
bot: false
}).then(function() {
$overlay.remove();
alert('私信已发送给 ' + toUser + '!');
}).fail(function(err) {
$overlay.remove();
alert('发送失败:' + (err.error && err.error.info ? err.error.info : '未知错误'));
});
}).fail(function() {
$overlay.remove();
alert('发送失败,请稍后重试。');
});
});
}
if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
var msgPageUser = mw.config.get('wgTitle');
if (msgPageUser !== currentUser) {
var $msgBtn = $('<button>', {
text: '✉️ 发送私信',
class: 'msg-send-btn',
click: function() { sendMessage(msgPageUser); }
});
$('#firstHeading').append($msgBtn);
}
}
if (currentUser) {
updateMsgBadge();
$(document).on('click', '#msg-badge', function() {
showInbox();
});
var $msgEntry = $('<a>', {
href: 'javascript:void(0)',
id: 'msg-inbox-trigger',
text: '✉️',
title: '私信箱',
css: { 'font-size': '13px', 'margin-left': '8px', 'cursor': 'pointer', 'text-decoration': 'none' }
});
var $friendTrigger = $('#friend-list-trigger');
if ($friendTrigger.length) {
$friendTrigger.after(' ');
$friendTrigger.after($msgEntry);
}
$(document).on('click', '#msg-inbox-trigger', function() {
showInbox();
});
setInterval(function() {
updateMsgBadge();
}, 30000);
}
// ==================== 版本更新公告弹窗 ====================
function checkUpdateNotice() {
var $versionEl = $('.update-version');
if ($versionEl.length === 0) return;
var currentVersion = $versionEl.text().trim();
if (!currentVersion) return;
var dismissedVersion = localStorage.getItem('mw_update_dismissed');
if (dismissedVersion !== currentVersion) {
var $overlay = $('<div>', {
css: {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
background: 'rgba(0,0,0,0.6)', 'z-index': 99999,
display: 'flex', 'align-items': 'center', 'justify-content': 'center'
}
});
var $box = $('<div>', {
css: {
background: '#fff', 'border-radius': '12px', padding: '30px',
'max-width': '550px', width: '90%', 'max-height': '80vh',
'overflow-y': 'auto', 'box-shadow': '0 8px 30px rgba(0,0,0,0.4)',
position: 'relative'
}
});
var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html(
'<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;">【植物大战僵尸杂交版0.22版本更新公告】</div>' +
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">更新时间:2026年6月7号</div>'+
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">电脑(Windows)版链接:https://pan.quark.cn/s/b145573873c3</div>'+
'<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">其他版本:https://pan.quark.cn/s/3ffc82554918</div>'+
'<b>🌱 植物更新</b><br>全新的植物加入了我们,我们的实力将更加强悍!<br>'+
'<b>白卡:</b>幽灵吞噬者、僵尸地刺、魅惑咖啡豆、魅惑三叶草、灵感菇、蒜鸟、咖啡三叶草、叶子高坚果、巨型南瓜壳、绷带高坚果、磁力樱桃炸弹、樱桃土豆雷<br>' +
'<b>至尊金卡:</b>黄金西瓜投手、大王钢齿花<br>' +
'<b>臻享钻卡:</b>生命重塑者<br>' +
'<b>星光闪卡:</b>黄金锤子<br><br>' +
'<b>🧟 僵尸/障碍物更新</b><br>飞行器僵尸、胆小鬼僵尸、传送门僵尸<br><br>' +
'<b>🗺️ 关卡更新</b><br>' +
'冒险:第八章 1~4关<br>' +
'金卡挑战:大王钢齿花 1~3,黄金西瓜投手 1~3<br>' +
'钻卡挑战:生命重塑者 1~6<br><br>' +
'<b>🎨 杂项更新</b><br>' +
'植物皮肤更新:黄金西瓜投手-幸运之王、大王钢齿花-银锋锐影、生命重塑者-浮生净莲(商店15000购买)、魔鬼辣椒-拘灵炼狱(在线关卡5水晶兑换)<br>' +
'道具更新:骷髅铲<br><br>' +
'<b>⚙️ 全新功能</b><br>' +
'新增游戏指令:/phonk [on/off] [弹性强度数]、/dismember [on/off]<br>' +
'局内设置新增果冻弹性效果开关与强度调整<br><br>' +
'<b>⚖️ 平衡性调整</b><br>' +
'植物价格调整:巨型南瓜雪橇 200→300、墓碑爆破者 150→100<br>' +
'植物强度调整:宝藏树桩亡语 1~3张黄金碎片→1张<br><br>' +
'<b>🛠️ 游戏优化</b><br>' +
'优化了关卡进度存档、返回选关体验、在线关卡分类筛选与通关率显示、更多模式板块内容返回体验<br>' +
'修复商店与植物试玩切换假死BUG、追加宝藏树桩亡语掉落黄金碎片、荧光木槌可对僵尸使用、本次更新修复了一堆BUG<br><br>' +
'<b>💎 水晶兑换商店</b><br>' +
'巨型南瓜壳 5 | 磁力樱桃炸弹 10 | 绷带高坚果 10<br>' +
'叶子高坚果 15 | 樱桃土豆雷 15 | 魔鬼辣椒皮肤-拘灵炼狱 5<br><br>' +
'<span style="color:#888;">结语:我们会持续建立与玩家社群的紧密联系,您的支持就是对我们工作最大的认可</span>'
);
var $closeBtn = $('<button>', {
text: '我知道了',
css: {
display: 'block', margin: '20px auto 0',
background: '#4CAF50', color: '#fff', border: 'none',
padding: '10px 30px', 'border-radius': '8px',
'font-size': '16px', cursor: 'pointer'
},
click: function() {
localStorage.setItem('mw_update_dismissed', currentVersion);
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
$box.append($content, $closeBtn);
$overlay.append($box);
$('body').append($overlay);
$overlay.on('click', function(e) {
if ($(e.target).is($overlay)) {
$overlay.fadeOut(300, function() { $(this).remove(); });
}
});
}
}
setTimeout(function() {
checkUpdateNotice();
}, 500);
// ==================== Wiki 宠物 ====================
(function() {
var petState = 'idle';
var petTimer = null;
var idleGif = 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif';
var pettingGif = 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif';
// 好感度系统
var affectionKey = 'wiki_pet_affection';
var affection = parseInt(localStorage.getItem(affectionKey)) || 0;
var affectionPerPet = 5;
var affectionCooldown = 500;
var lastPetTime = 0;
var levels = [
{ min: 0, title: '陌生', emoji: '🤔' },
{ min: 50, title: '认识', emoji: '👋' },
{ min: 150, title: '友好', emoji: '😊' },
{ min: 400, title: '亲密', emoji: '💚' },
{ min: 1000, title: '挚友', emoji: '💖' },
{ min: 2500, title: '灵魂伴侣', emoji: '✨' }
];
function getLevel(aff) {
var lvl = levels[0];
for (var i = levels.length - 1; i >= 0; i--) {
if (aff >= levels[i].min) { lvl = levels[i]; break; }
}
return lvl;
}
function saveAffection() {
localStorage.setItem(affectionKey, affection);
}
function showAffectionPanel() {
var lvl = getLevel(affection);
var nextLvl = null;
for (var i = 0; i < levels.length; i++) {
if (affection < levels[i].min) { nextLvl = levels[i]; break; }
}
var text = lvl.emoji + ' ' + lvl.title + ' (' + affection + ')';
if (nextLvl) {
var progress = Math.round((affection - lvl.min) / (nextLvl.min - lvl.min) * 100);
text += ' → ' + nextLvl.emoji + ' ' + progress + '%';
} else {
text += ' MAX';
}
$affectionPanel.text(text);
}
function spawnHeart(x, y) {
var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨'];
var heart = hearts[Math.floor(Math.random() * hearts.length)];
var $heart = $('<div>', {
class: 'pet-heart',
text: heart,
css: { left: x, top: y }
});
$('body').append($heart);
setTimeout(function() { $heart.remove(); }, 1500);
}
function triggerLevelUp() {
$pet.addClass('level-up');
setTimeout(function() { $pet.removeClass('level-up'); }, 800);
}
var $pet = $('<div>', { class: 'wiki-pet', css: { opacity: 0 } });
var $img = $('<img>', { src: idleGif, alt: '冰瓜香蒲' });
var $affectionPanel = $('<div>', { class: 'pet-affection' });
var $tooltip = $('<div>', { class: 'pet-tooltip', text: '点击抚摸我~ 🐱' });
$pet.append($img, $affectionPanel, $tooltip);
$('body').append($pet);
showAffectionPanel();
setTimeout(function() { $pet.animate({ opacity: 1 }, 500); }, 1000);
function setPetState(state) {
if (petState === state) return;
petState = state;
clearTimeout(petTimer);
$pet.removeClass('petting');
if (state === 'idle') {
$img.attr('src', idleGif);
var lvl = getLevel(affection);
$tooltip.text('点击抚摸我~ ' + lvl.emoji);
} else if (state === 'petting') {
$img.attr('src', pettingGif);
$pet.addClass('petting');
petTimer = setTimeout(function() { setPetState('idle'); }, 1200);
}
}
$pet.on('click', function(e) {
e.stopPropagation();
var now = Date.now();
if (now - lastPetTime < affectionCooldown) return;
lastPetTime = now;
setPetState('petting');
var oldLevel = getLevel(affection).title;
affection += affectionPerPet;
saveAffection();
showAffectionPanel();
var newLevel = getLevel(affection).title;
if (newLevel !== oldLevel) {
triggerLevelUp();
$tooltip.text('好感度提升!' + newLevel + '!🎉');
setTimeout(function() {
var lvl = getLevel(affection);
$tooltip.text('点击抚摸我~ ' + lvl.emoji);
}, 2000);
}
var offset = $pet.offset();
spawnHeart(offset.left + 30, offset.top - 10);
});
setInterval(function() {
if (petState === 'idle' && Math.random() < 0.3) {
$pet.css('transform', 'scale(1.05) translateY(-5px)');
setTimeout(function() {
$pet.css('transform', 'scale(1) translateY(0)');
}, 500);
}
}, 10000);
$pet.on('touchstart', function(e) {
e.stopPropagation();
var now = Date.now();
if (now - lastPetTime < affectionCooldown) return;
lastPetTime = now;
setPetState('petting');
var oldLevel = getLevel(affection).title;
affection += affectionPerPet;
saveAffection();
showAffectionPanel();
var newLevel = getLevel(affection).title;
if (newLevel !== oldLevel) {
triggerLevelUp();
$tooltip.text('好感度提升!' + newLevel + '!🎉');
setTimeout(function() {
var lvl = getLevel(affection);
$tooltip.text('点击抚摸我~ ' + lvl.emoji);
}, 2000);
}
var offset = $pet.offset();
spawnHeart(offset.left + 30, offset.top - 10);
});
})();
}); // 结束主 $(function () { ... })