Add HeXi bot codebase: custom plugins, web frontends, tests

- hexi core: message handling, rate limiting, cooldown, plugin manager
- Custom plugins: BF stats, daily check-in, quotes, persona cards, etc.
- Community plugins vendored under hexi/plugins with local fixes
- Web admin frontends (learning-chat, persona-admin), unified hexi/web
- Tests for rate_limit/cooldown/memes/persona; poetry.lock

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-01 13:13:40 +08:00
co-authored by Claude
parent 1783c60afa
commit b61d09f09f
3201 changed files with 160436 additions and 171 deletions
@@ -0,0 +1,33 @@
from nonebot import require
from nonebot.plugin import PluginMetadata
require("nonebot_plugin_alconna")
__plugin_meta__ = PluginMetadata(
name="群管理工具",
description=(
"群管理指令与群管小功能合集:龙王查询、禁言、头衔、名片、群名管理、"
"违禁词语音、聊天互动、退群通知、随机复读、禁言套餐"
),
usage=(
"注意本页指令可能由于风控在您的群不可用\n"
"[谁是龙王] 查看本群龙王\n"
"[龙王榜] 查看本群B话最多的人\n"
"以下指令需要机器人是管理员, 且被执行人是普通群员\n"
"[修改群名XXX] 群名修改为XXX\n"
"[修改名片XXX@某人] 修改某人的群名片为XXX\n"
"[来发口球360@某人] 禁言某人360秒, 可以修改为自己需要的时间\n"
"[解除口球@某人] 取消某人的禁言\n"
"[全员口球] 开启全员禁言\n"
"[取消全员禁言] 关闭全员禁言\n"
"[极限一换一60@某人]神秘功能, 60可以修改为30以下的秒数\n"
"[申请头衔XXX] 申请/删除专属头衔(bot需为群主)\n"
"[违禁词] 触发黑手语音\n"
"[沙雕机器人/老婆/老公/mua/何意味/确实/内鬼...] 聊天互动\n"
"[睡眠套餐/精致睡眠] 8小时禁言(bot需具有群管理权限)\n"
"[来一份精致昏睡下午茶套餐] 随机时长禁言(bot需具有群管理权限)"
),
type="application",
)
from .handlers import chat, management, notices, repeater # noqa: E402,F401
@@ -0,0 +1,16 @@
from pathlib import Path
# 插件根目录(放 dk_is_me.jpg 等根级资源)
PLUGIN_ROOT = Path(__file__).resolve().parent
IMG_DIR = PLUGIN_ROOT
# 资源目录:res/ 下的 record/ 与 img/
RES_DIR = PLUGIN_ROOT / "res"
# 视频临时目录(项目数据目录下)
TEMP_DIR = PLUGIN_ROOT.parents[1] / "data" / "temp"
# 违禁词(触发黑手语音)
BANNED_WORDS = (
"rbq", "RBQ", "憨批", "废物", "死妈", "崽种", "傻逼", "傻逼玩意",
"没用东西", "傻B", "傻b", "SB", "sb", "煞笔", "cnm", "爬", "kkp",
"nmsl", "D区", "口区", "我是你爹", "nmbiss", "弱智", "给爷爬", "杂种爬", "爪巴",
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1 @@
"""群管理工具插件的消息/事件处理器子包。"""
@@ -0,0 +1,164 @@
import random
from nonebot import get_driver, on_command, on_fullmatch, on_keyword, require
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
from nonebot.rule import to_me
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import UniMessage
from ..constants import BANNED_WORDS, RES_DIR
from ..services.moderation import silence
from ..utils.media import image_to_base64, random_audio
SUPERUSERS = get_driver().config.superusers
# ---------- 基础打招呼 ----------
say_hello = on_command(
"zai?",
aliases={"在?", "在?", "在吗", "在么?", "在嘛", "在嘛?"},
rule=to_me(),
block=True,
)
@say_hello.handle()
async def say_hello_handle(ev: MessageEvent):
await UniMessage.text("はい!私はいつも貴方の側にいますよ!").send()
# ---------- 关键词/整句互动 ----------
say_sorry = on_fullmatch("沙雕机器人")
@say_sorry.handle()
async def say_sorry_handle(ev: MessageEvent):
await UniMessage.text("ごめんなさい!嘤嘤嘤(〒︿〒)").send()
say_heweidao = on_keyword({"何意味"})
@say_heweidao.handle()
async def say_heweidao_handle(ev: MessageEvent):
await UniMessage.text("何意味是什么意思喵~").send()
chat_waifu = on_fullmatch(("老婆", "waifu", "laopo"), rule=to_me())
@chat_waifu.handle()
async def chat_waifu_handle(ev: MessageEvent):
is_superuser = str(ev.user_id) in SUPERUSERS
img_name = "love.jpg" if is_superuser else "laopo.jpg"
base64_str = image_to_base64(RES_DIR / "img" / img_name)
if not base64_str:
return
if is_superuser:
msg = UniMessage.text("mua~") + UniMessage.image(url=base64_str)
else:
msg = UniMessage.image(url=base64_str)
await msg.send()
chat_laogong = on_fullmatch("老公", rule=to_me())
@chat_laogong.handle()
async def chat_laogong_handle(ev: MessageEvent):
await UniMessage.text("你给我滚!").send(at_sender=True)
chat_mua = on_fullmatch("mua", rule=to_me())
@chat_mua.handle()
async def chat_mua_handle(ev: MessageEvent):
await UniMessage.text("笨蛋~").send(at_sender=True)
ddhaole = on_fullmatch(("我有个朋友说他好了", "我朋友说他好了"))
@ddhaole.handle()
async def ddhaole_handle(bot: Bot, ev: MessageEvent):
await UniMessage.text("那个朋友是不是你弟弟?").send()
await silence(bot, ev, 30)
nihaole = on_fullmatch("我好了")
@nihaole.handle()
async def nihaole_handle(bot: Bot, ev: MessageEvent):
await UniMessage.text("不许好,憋回去!").send()
# await silence(bot, ev, 30)
play_zhanshi = on_keyword({"优质的战士"})
@play_zhanshi.handle()
async def play_zhanshi_handle(ev: MessageEvent):
base64_str = random_audio(RES_DIR / "record" / "voice" / "youzhizhanshi")
if base64_str:
await UniMessage.voice(url=base64_str).send()
play_ice = on_fullmatch("溜冰")
@play_ice.handle()
async def play_ice_handle(ev: MessageEvent):
base64_str = random_audio(RES_DIR / "record" / "voice" / "ice")
if base64_str:
await UniMessage.voice(url=base64_str).send()
play_dxl = on_fullmatch("罕见")
@play_dxl.handle()
async def play_dxl_handle(ev: MessageEvent):
base64_str = random_audio(RES_DIR / "record" / "voice" / "dxl")
if base64_str:
await UniMessage.voice(url=base64_str).send()
chat_queshi = on_keyword({"确实", "有一说一", "u1s1", "yysy"})
@chat_queshi.handle()
async def chat_queshi_handle(ev: MessageEvent):
if random.random() < 0.05:
base64_str = image_to_base64(RES_DIR / "img" / "确实.jpg")
if base64_str:
await UniMessage.image(url=base64_str).send()
chat_neigui = on_keyword({"内鬼"})
@chat_neigui.handle()
async def chat_neigui_handle(ev: MessageEvent):
if random.random() < 0.10:
base64_str = image_to_base64(RES_DIR / "img" / "内鬼.png")
if base64_str:
await UniMessage.image(url=base64_str).send()
# ---------- 违禁词黑手语音 ----------
banned_word_voice = on_command(
"ban_word", aliases=set(BANNED_WORDS), rule=to_me(), block=True
)
@banned_word_voice.handle()
async def banned_word_voice_handle(ev: MessageEvent):
base64_str = random_audio(RES_DIR / "record" / "voice" / "heishou")
if base64_str:
await UniMessage.voice(url=base64_str).send()
@@ -0,0 +1,399 @@
import asyncio
import math
import random
import re
import time
from nonebot import logger, on_fullmatch, on_message, on_regex, on_startswith, require
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent
from nonebot.rule import Rule, is_type
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import UniMessage
from hexi.plugins.nonebot_plugin_hexi_core.custom_utils import check_manage
from ..constants import IMG_DIR, TEMP_DIR
from ..services.moderation import group_silence, member_silence, silence
from ..services.profile import card_edit, group_name, title_get
from ..services.video import download_video, get_video_info, local_video
from ..utils.media import image_to_base64
from ..utils.message import get_at_id, has_at_all
from ..utils.permissions import check_perm, honor_info, self_member_info
from ..utils.text import is_number, parse_seconds, strip_prefix
# ============ 管理员与公告 ============
set_admin = on_startswith("设置管理员")
unset_admin = on_startswith("取消管理员")
set_group_notice = on_startswith("设置群公告")
@set_admin.handle()
async def set_admin_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_perm(bot, ev):
return
for seg in ev.message:
if seg.type == "at" and seg.data.get("qq") != "all":
await bot.set_group_admin(
group_id=ev.group_id, user_id=int(seg.data["qq"]), enable=True
)
await UniMessage.text("我好了").send()
@unset_admin.handle()
async def unset_admin_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_perm(bot, ev):
return
for seg in ev.message:
if seg.type == "at" and seg.data.get("qq") != "all":
await bot.set_group_admin(
group_id=ev.group_id, user_id=int(seg.data["qq"]), enable=False
)
await UniMessage.text("我好了").send()
@set_group_notice.handle()
async def set_group_notice_handle(bot: Bot, ev: GroupMessageEvent):
content = ev.message.extract_plain_text().removeprefix("设置群公告").strip()
if not content:
return
if not await check_perm(bot, ev):
return
await bot.call_api("_send_group_notice", group_id=ev.group_id, content=content)
await UniMessage.text("我好了").send()
# ============ 禁言相关 ============
def _is_ban_cmd(event: GroupMessageEvent) -> bool:
"""分钟制禁言命令:"禁言@xxx 5" 表示禁言 5 分钟;排除秒制的 "禁言一下" """
text = event.message.extract_plain_text()
return text.startswith("禁言") and not text.startswith("禁言一下")
ban_user = on_message(rule=Rule(_is_ban_cmd) & is_type(GroupMessageEvent), block=True)
ban_member = on_startswith(("来发口球", "塞口球", "禁言一下"))
one_for_one = on_startswith("极限一换一")
cancel_ban = on_startswith(("解除口球", "取消口球", "摘口球", "脱口球", "取消禁言", "解除禁言", "解禁"))
ban_all = on_fullmatch(("全员口球", "全员禁言"))
cancel_ban_all = on_fullmatch(("取消全员口球", "取消全员禁言", "解除全员口球", "解除全员禁言"))
sleep_8h = on_fullmatch(("睡眠套餐", "休眠套餐", "精致睡眠", "来一份精致睡眠套餐"))
tea = on_regex(r"(来|來)(.*(份|个)(.*)(睡|茶)(.*))套餐")
@ban_user.handle()
async def ban_user_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_perm(bot, ev, silent=True):
return
msg = ev.message.extract_plain_text().removeprefix("禁言").strip()
at_seg = next(
(seg for seg in ev.message if seg.type == "at" and seg.data.get("qq") != "all"),
None,
)
if not at_seg:
return
try:
uid = int(at_seg.data["qq"])
minutes = 1 if not msg else int(msg)
await bot.set_group_ban(
group_id=ev.group_id, user_id=uid, duration=minutes * 60
)
await UniMessage.text("我好了").send()
except Exception as ex:
logger.error(ex)
@ban_member.handle()
async def ban_member_handle(bot: Bot, ev: GroupMessageEvent):
uid = ev.user_id
gid = ev.group_id
duration_text = strip_prefix(
ev.message.extract_plain_text(), ("来发口球", "塞口球", "禁言一下")
)
if not duration_text:
duration_text = "30"
if has_at_all(ev):
await group_silence(bot, ev, gid, True)
return
sid = get_at_id(ev) or uid
await member_silence(bot, ev, uid, sid, gid, duration_text, silent=False)
@one_for_one.handle()
async def one_for_one_handle(bot: Bot, ev: GroupMessageEvent):
uid = ev.user_id
gid = ev.group_id
duration_text = strip_prefix(ev.message.extract_plain_text(), ("极限一换一",))
if has_at_all(ev):
await UniMessage.text("人干事?").send(at_sender=True)
await member_silence(bot, ev, uid, uid, gid, "180", silent=False)
return
seconds = parse_seconds(duration_text)
if seconds is None:
await UniMessage.text("请输入正确时长~").send(at_sender=True)
return
if seconds > 30:
await UniMessage.text("时长不可大于30秒哟~").send(at_sender=True)
return
sid = get_at_id(ev) or uid
await member_silence(bot, ev, uid, sid, gid, str(seconds), silent=True)
await member_silence(bot, ev, uid, uid, gid, str(seconds), silent=True)
await (
UniMessage.at(user_id=str(uid))
.text("成功一换一")
.at(user_id=str(sid))
.text(f"{seconds}秒~")
.send()
)
@cancel_ban.handle()
async def cancel_ban_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_manage(bot, ev):
await UniMessage.text("权限不足").send()
return
uid = ev.user_id
gid = ev.group_id
if has_at_all(ev):
await group_silence(bot, ev, gid, False)
return
sid = get_at_id(ev)
if sid is None:
await UniMessage.text("请@需要摘口球的群员哦w").send()
return
await member_silence(bot, ev, uid, sid, gid, "0", silent=False)
@ban_all.handle()
async def ban_all_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_manage(bot, ev):
await UniMessage.text("权限不足").send()
return
await group_silence(bot, ev, ev.group_id, True)
@cancel_ban_all.handle()
async def cancel_ban_all_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_manage(bot, ev):
await UniMessage.text("权限不足").send()
return
await group_silence(bot, ev, ev.group_id, False)
@sleep_8h.handle()
async def sleep_8h_handle(bot: Bot, ev: MessageEvent):
await silence(bot, ev, 8 * 60 * 60)
@tea.handle()
async def tea_handle(bot: Bot, ev: MessageEvent):
base = 0 if "午" in ev.plain_text else 5 * 60 * 60
length = len(ev.plain_text)
sleep_time = base + round(
math.sqrt(length) * 60 * 30 + 60 * random.randint(-15, 15)
)
await silence(bot, ev, sleep_time)
# ============ 头衔 / 名片 / 群名 ============
special_title = on_startswith("申请头衔")
del_special_title = on_fullmatch(("删除头衔", "清除头衔", "收回头衔", "回收头衔"))
card_set = on_startswith(("修改名片", "修改群名片", "设置名片", "设置群名片"))
set_group_name = on_startswith(("修改群名", "设置群名"))
@special_title.handle()
async def special_title_handle(bot: Bot, ev: GroupMessageEvent):
uid = ev.user_id
gid = ev.group_id
title = strip_prefix(ev.message.extract_plain_text(), ("申请头衔",))
sid = get_at_id(ev) or uid
await title_get(bot, ev, uid, sid, gid, title)
# go-cqhttp似乎暂时不支持收回专属头衔...
@del_special_title.handle()
async def del_special_title_handle(bot: Bot, ev: GroupMessageEvent):
uid = ev.user_id
gid = ev.group_id
sid = get_at_id(ev) or uid
await title_get(bot, ev, uid, sid, gid, None)
@card_set.handle()
async def card_set_handle(bot: Bot, ev: GroupMessageEvent):
uid = ev.user_id
gid = ev.group_id
card_text = strip_prefix(
ev.message.extract_plain_text(), ("修改名片", "修改群名片", "设置名片", "设置群名片")
)
sid = get_at_id(ev) or uid
await card_edit(bot, ev, uid, sid, gid, card_text)
@set_group_name.handle()
async def set_group_name_handle(bot: Bot, ev: GroupMessageEvent):
if not await check_manage(bot, ev):
await UniMessage.text("权限不足").send()
return
gid = ev.group_id
name_text = strip_prefix(ev.message.extract_plain_text(), ("修改群名", "设置群名"))
await group_name(bot, ev, gid, name_text)
# ============ 龙王 ============
whois_dragon = on_fullmatch(("谁是龙王", "迫害龙王", "龙王是谁"))
dragon_list = on_fullmatch(("龙王榜", "龙王排行", "龙王排行榜", "龙王榜单"))
@whois_dragon.handle()
async def whois_dragon_handle(bot: Bot, ev: GroupMessageEvent):
gid = ev.group_id
self_info = await self_member_info(bot, gid)
sid = self_info["user_id"]
ta_info = await honor_info(bot, gid, "talkative")
if not ta_info or not ta_info.get("current_talkative"):
await UniMessage.text("本群没有开启龙王标志哦~").send()
return
dk = ta_info["current_talkative"]["user_id"]
if sid == dk:
pic = image_to_base64(IMG_DIR / "dk_is_me.jpg")
await (
UniMessage.text("你们这群丢人玩意,龙王怎么又是我\n")
.image(url=pic)
.send()
)
else:
action = random.choice(["龙王出来挨透", "龙王出来喷水"])
dk_avatar = ta_info["current_talkative"]["avatar"] + "640" + f"&t={dk}"
await (
UniMessage.at(user_id=str(dk))
.text(f"\n{action}\n")
.image(url=dk_avatar)
.send()
)
@dragon_list.handle()
async def dragon_list_handle(bot: Bot, ev: GroupMessageEvent):
gid = ev.group_id
ta_info = await honor_info(bot, gid, "talkative")
if not ta_info or not ta_info.get("talkative_list"):
await UniMessage.text("本群暂时没有龙王数据哦~").send()
return
dragon_king_list_info = ta_info["talkative_list"]
# 处理description和nickname关系
dkdir = {}
for item in dragon_king_list_info:
user_id = item["user_id"]
nickname = item["nickname"]
description = item["description"]
temp = re.findall(r"\d+", description)
# 累计获得龙王的次数
dragon_king_days = int(temp[0])
# 连续获得龙王
dragon_king_days_continuous = int(temp[1])
dkdir[nickname] = {
"user_id": user_id,
"days_max": dragon_king_days,
"days_contin": dragon_king_days_continuous,
}
# 瞎写的排序
dkdir_sorted = sorted(dkdir.items(), key=lambda d: d[1]["days_max"], reverse=True)
dkk = dkdir_sorted[0][1]["user_id"]
msg = UniMessage.text(f"本群获得龙王次数最多的是:\n{dkdir_sorted[0][0]}\n")
dk_avatar = f"http://q1.qlogo.cn/g?b=qq&nk={dkk}&s=5"
msg += UniMessage.image(url=dk_avatar)
msg += UniMessage.text("\n本群龙王榜\n")
for i, it in enumerate(dkdir_sorted[:5]):
_nickname = it[0]
_days_max = it[1]["days_max"]
_days_contin = it[1]["days_contin"]
msg += UniMessage.text(
f"第{i + 1}名:{_nickname},共{_days_max}次,最长连续{_days_contin}天获得龙王\n"
)
await msg.send()
# ============ 视频下载 ============
down_video = on_startswith("/video")
@down_video.handle()
async def down_video_handle(bot: Bot, ev: GroupMessageEvent):
url = ev.message.extract_plain_text().removeprefix("/video").strip()
if not url:
return
match = re.match(r"https?:/{2}\w.+$", url)
if not match:
await UniMessage.text("参数不合法").send()
return
timespan = int(time.time())
filename = f"{timespan}"
file = TEMP_DIR / f"{filename}.mp4"
fileabs = str(file)
cover = TEMP_DIR / f"{filename}.mp4.jpg"
TEMP_DIR.mkdir(parents=True, exist_ok=True)
try:
info = await asyncio.to_thread(get_video_info, url)
match = re.search(r"MPEG-4 video(.*)\s*Size:(.*)MiB", info)
if not match:
await UniMessage.text("参数不正确").send()
return
info_type = match.group(1).strip()
if info_type != "(video/mp4)":
await UniMessage.text("未找到视频信息").send()
return
info_size = match.group(2).strip()
if not is_number(info_size):
await UniMessage.text("无法获取视频大小").send()
return
size = float(info_size)
if size >= 6.0:
await UniMessage.text("视频体积过大").send()
return
await UniMessage.text("正在发送...").send()
await asyncio.to_thread(download_video, url, TEMP_DIR, filename)
except Exception as e:
logger.error(e)
await UniMessage.text("获取视频失败了").send()
file.unlink(missing_ok=True)
cover.unlink(missing_ok=True)
return
try:
await local_video(f"file:///{fileabs}").send()
except Exception:
await UniMessage.text("发送失败了").send()
file.unlink(missing_ok=True)
cover.unlink(missing_ok=True)
# ============ 病毒消息拦截 ============
fuck_xxs = on_message(rule=is_type(GroupMessageEvent), block=False)
@fuck_xxs.handle()
async def fuck_xxs_handle(bot: Bot, ev: GroupMessageEvent):
for m in ev.message:
if m.type == "json" and '"app":"com.tencent.autoreply"' in m.data.get(
"data", ""
):
await bot.delete_msg(message_id=ev.message_id)
await UniMessage.text(
"这是onse病毒码,会获取到你手机的xxs,然后发起攻击...以下省略6行需要全文背诵的金句"
).send()
return
@@ -0,0 +1,37 @@
from nonebot import on_notice, require
from nonebot.adapters.onebot.v11 import (
Bot,
GroupDecreaseNoticeEvent,
GroupIncreaseNoticeEvent,
)
from nonebot.rule import is_type
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import UniMessage
group_decrease_notice = on_notice(rule=is_type(GroupDecreaseNoticeEvent))
@group_decrease_notice.handle()
async def group_decrease_handle(bot: Bot, ev: GroupDecreaseNoticeEvent):
sub_type = ev.sub_type
uid = ev.user_id
operator_id = ev.operator_id
data = await bot.get_stranger_info(user_id=uid)
name = data["nickname"]
if sub_type == "kick":
await UniMessage.at(user_id=operator_id).text(
f"认为{name}({uid})不适合本群"
).send()
elif sub_type == "leave":
await UniMessage.text(f"{name}({uid})离开了我们,相信会有再见的一天!").send()
group_increase_notice = on_notice(rule=is_type(GroupIncreaseNoticeEvent))
@group_increase_notice.handle()
async def group_increase_handle(bot: Bot, ev: GroupIncreaseNoticeEvent):
if ev.user_id == ev.self_id:
return # 忽略自己
await UniMessage.text("欢迎入群~,请认真阅读群规").send(at_sender=True)
@@ -0,0 +1,49 @@
import random
from nonebot import on_message, require
from nonebot.adapters.onebot.v11 import GroupMessageEvent
from nonebot.log import logger
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import UniMessage
PROB_A = 1.4
# group_id: (last_msg, is_repeated, p)
group_stat: dict[int, tuple[str, bool, float]] = {}
"""
不复读率 随 复读次数 指数级衰减
从第2条复读,即第3条重复消息开始有几率触发复读
a 设为一个略大于1的小数,最好不要超过2,建议1.6
复读概率计算式:p_n = 1 - 1/a^n
递推式:p_n+1 = 1 - (1 - p_n) / a
"""
repeater = on_message(block=False)
@repeater.handle()
async def repeater_handle(ev: GroupMessageEvent):
group_id = ev.group_id
msg = str(ev.message)
if group_id not in group_stat:
group_stat[group_id] = (msg, False, 0.0)
return
last_msg, is_repeated, p = group_stat[group_id]
if last_msg == msg: # 群友正在复读
if not is_repeated: # 机器人尚未复读过,开始测试复读
if random.random() < p: # 概率测试通过,复读并设 flag
try:
group_stat[group_id] = (msg, True, 0.0)
await UniMessage.text(ev.message.extract_plain_text()).send()
except Exception as e:
logger.error(f"复读失败: {type(e)}")
logger.exception(e)
else: # 概率测试失败,蓄力
p = 1 - (1 - p) / PROB_A
group_stat[group_id] = (msg, False, p)
else: # 不是复读,重置
group_stat[group_id] = (msg, False, 0.0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Some files were not shown because too many files have changed in this diff Show More