机娘霜天酱是万类霜天[更多]讨论页贡献上传历史封禁及历史被删贡献移动日志巡查日志用户权限操作的机器人,主要用于闪耀幻想曲专题维护。
| 这个用户的母语是Python。 |
暂无
| 展开查看程序代码 |
|---|
|
暂无 |
暂无
| 展开查看程序代码 |
|---|
|
暂无 |
| 展开查看程序代码 |
|---|
import pywikibot
import re
import time
import logging
from itertools import chain
from datetime import datetime
import sys
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("user_infobox_notifier.log"),
logging.StreamHandler()
]
)
def get_user_pages_with_template(site):
"""搜索用户空间中使用了{{User Infobox}}相关模板的页面(处理不同大小写)"""
try:
# 搜索语法:用户空间下包含指定模板(处理不同大小写)
search_patterns = [
'User: hastemplate:"User_Infobox"',
'User: hastemplate:"User_InfoBox"',
'User: hastemplate:"User_infobox"'
]
# 生成多个搜索器并合并结果(去重)
generators = []
for pattern in search_patterns:
gen = pywikibot.pagegenerators.SearchPageGenerator(pattern, site=site)
generators.append(gen)
# 合并生成器并去重
seen_titles = set()
user_pages = []
for page in chain(*generators):
# 验证命名空间(返回字符串如"User:")
ns = str(page.namespace())
if ns == "User:" and page.title() not in seen_titles:
seen_titles.add(page.title())
user_pages.append(page)
logging.info(f"找到使用目标模板的用户页面: {page.title()}")
logging.info(f"共找到 {len(user_pages)} 个符合条件的用户页面(去重后)")
return user_pages
except Exception as e:
logging.error(f"搜索用户页面时出错: {str(e)}", exc_info=True)
return []
def get_target_talk_page(user_page):
"""获取目标用户讨论页,处理重定向情况"""
site = user_page.site
# 从用户页面标题提取用户名(格式:User:用户名)
username = user_page.title().split(':', 1)[1]
talk_page_title = f"User talk:{username}"
talk_page = pywikibot.Page(site, talk_page_title)
# 检查是否为重定向
if talk_page.isRedirectPage():
try:
target_page = talk_page.getRedirectTarget()
# 检查目标是否为用户讨论页(命名空间应为"User talk:")
if str(target_page.namespace()) == "User talk:":
logging.info(f"用户讨论页 {talk_page.title()} 是重定向,已转向 {target_page.title()}")
return target_page, True, user_page.title()
else:
logging.warning(f"用户讨论页 {talk_page.title()} 重定向到非用户讨论页 {target_page.title()},将跳过")
return None, False, None
except Exception as e:
logging.error(f"处理重定向页面 {talk_page.title()} 时出错: {str(e)}")
return None, False, None
return talk_page, False, None
def has_existing_section(talk_page):
"""检查讨论页是否已有"关于{{tl|User Infobox}}"二级标题"""
try:
if not talk_page.exists():
return False
text = talk_page.text
# 匹配二级标题(== 关于{{tl|User Infobox}} ==,忽略大小写)
pattern = r'==\s*关于{{tl\|User (I|i)nfobox}}\s*=='
return re.search(pattern, text) is not None
except Exception as e:
logging.error(f"检查页面 {talk_page.title()} 是否有现有章节时出错: {str(e)}")
return False
def prepare_notification_text(is_redirect, original_user_page):
"""准备要添加的通知内容(修正重复文本问题)"""
base_text = """== 关于{{tl|User Infobox}} ==
您好,{user_page_ref}中的{{tl|User Infobox}}模板近期有重要改动,现在自定义参数可能不会按照您的输入顺序来显示。若需要使自定义参数按照输入顺序显示,请将自定义参数名后的赋值号<code>=</code>改为半角双冒号<code>::</code>。详情请参阅模板说明页。
给您带来的不便,敬请谅解!——~~~~
"""
if is_redirect and original_user_page:
# 重定向情况,使用用户页链接
user_ref = f"用户页[[{original_user_page}]]"
else:
# 正常情况
user_ref = "您用户页"
return base_text.format(user_page_ref=user_ref)
def add_notification_to_talk_page(talk_page, notification_text):
"""向讨论页底部添加通知内容"""
try:
if talk_page.exists():
current_text = talk_page.text
# 在页面底部添加新内容(避免重复空行)
new_text = current_text.rstrip() + "\n\n" + notification_text
else:
# 页面不存在,直接使用通知文本
new_text = notification_text
talk_page.text = new_text
return True
except Exception as e:
logging.error(f"准备更新页面 {talk_page.title()} 时出错: {str(e)}")
return False
def main():
try:
# 连接到萌娘百科
site = pywikibot.Site()
logging.info("成功连接到萌娘百科")
# 获取所有使用了目标模板的用户页面
user_pages = get_user_pages_with_template(site)
if not user_pages:
logging.info("没有找到符合条件的用户页面,程序将退出")
return
# 编辑频率控制变量
edit_timestamps = [] # 存储编辑时间戳
SIX_HOURS = 6 * 3600 # 6小时的秒数
MAX_EDITS_PER_SIX_HOURS = 50 # 6小时内最大编辑次数
MIN_INTERVAL = 20 # 两次编辑的最小间隔(秒)
last_edit_time = 0
EDIT_CONFLICT_RETRIES = 3 # 编辑冲突重试次数
EDIT_CONFLICT_WAIT = 5 # 重试间隔(秒)
# 处理每个用户页面
for user_page in user_pages:
try:
# 获取目标讨论页
talk_page, is_redirect, original_user_page = get_target_talk_page(user_page)
if not talk_page:
continue
# 检查讨论页是否已有相关章节
if has_existing_section(talk_page):
logging.info(f"讨论页 {talk_page.title()} 已有相关章节,将跳过")
continue
# 准备通知文本
notification_text = prepare_notification_text(is_redirect, original_user_page)
# 编辑频率控制 - 6小时内不超过50次
current_time = time.time()
# 清理6小时前的时间戳
edit_timestamps = [t for t in edit_timestamps if current_time - t < SIX_HOURS]
if len(edit_timestamps) >= MAX_EDITS_PER_SIX_HOURS:
# 计算需要等待的时间
earliest_edit = edit_timestamps[0]
wait_time = (earliest_edit + SIX_HOURS) - current_time + 1 # 加1秒保险
logging.info(f"已达6小时内{MAX_EDITS_PER_SIX_HOURS}次编辑上限,需等待{wait_time:.1f}秒")
time.sleep(wait_time)
# 再次清理时间戳
edit_timestamps = [t for t in edit_timestamps if time.time() - t < SIX_HOURS]
# 编辑频率控制 - 两次编辑间隔至少20秒
time_since_last = current_time - last_edit_time
if edit_timestamps and time_since_last < MIN_INTERVAL:
wait_time = MIN_INTERVAL - time_since_last
logging.info(f"等待 {wait_time:.1f} 秒以满足编辑间隔要求")
time.sleep(wait_time)
current_time = time.time()
# 添加通知到讨论页(处理编辑冲突重试)
success = False
for retry in range(EDIT_CONFLICT_RETRIES):
try:
if add_notification_to_talk_page(talk_page, notification_text):
# 保存页面
talk_page.save(
summary="通知User Infobox模板改动",
minor=False,
tags="Automation tool"
)
logging.info(f"已成功向 {talk_page.title()} 添加通知(重试次数:{retry})")
success = True
break
except pywikibot.exceptions.EditConflictError:
if retry < EDIT_CONFLICT_RETRIES - 1:
logging.warning(f"页面 {talk_page.title()} 发生编辑冲突,将在{EDIT_CONFLICT_WAIT}秒后重试({retry+1}/{EDIT_CONFLICT_RETRIES})")
time.sleep(EDIT_CONFLICT_WAIT)
else:
logging.error(f"页面 {talk_page.title()} 多次编辑冲突,已放弃")
if success:
# 更新编辑时间记录
edit_timestamps.append(current_time)
last_edit_time = current_time
else:
logging.warning(f"未能向 {talk_page.title()} 添加通知")
except pywikibot.exceptions.LockedPageError:
logging.warning(f"页面 {talk_page.title()} 已锁定,无法编辑")
except Exception as e:
logging.error(f"处理用户页面 {user_page.title()} 时出错: {str(e)}", exc_info=True)
continue
logging.info("所有用户页面处理完毕")
except Exception as e:
logging.critical(f"程序运行出错: {str(e)}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()
|