Skip to main content

nonebot-plugin-milock

把 milock 的一次性密码接到 QQ 群里:用户申请 → 管理员审核 → 私聊发码 → 全程汇报。

适用于"家里装了一把小米全自动门锁,想给访客/家人临时开门密码"的场景。 密码由 milock 服务离线生成,本插件只负责"审批 + 发放 + 留痕"。


1. 流程

用户:/密码            用户:/密码 21:00
  │                      │
  │                      └─ 取 21:00 所在窗口的前一个窗口(20:30-21:00);
  │                         该码有效期 = 本窗口+下一窗口 = 20:30-21:30
  │
  ├─ 未授权 ──▶ 建单送审 ──▶ 管理员私聊 + 管理群 收到「待审核」汇报(含期望时间)
  │                              │
  │                              ├─ /milock approve <单号> ─┐
  │                              └─ /milock deny <单号>    ─┴─▶ 私聊通知申请人
  │
  └─ 已授权 ──▶ (跳过审核)
                    │
                    ▼
        向 milock 取一个尚未发给任何人的密码
        (不指定时间 → 当前窗口;指定时间 → at 所在窗口的**前一个**窗口,
          因为一个码的有效期是两个窗口 = 1 小时)
                    │
                    ├─ 私聊投递成功 ──▶ 该密码被永久标记为已发放
                    └─ 私聊投递失败 ──▶ 立刻回收,密码可以让给别人

三条硬要求分别由下面几个机制保证:

要求 实现
首次申请必须管理员审核,之后免审 users 表按 QQ 号记录授权,批准后全局永久有效(可 /milock revoke 撤销);申请单超时自动作废
密码只发给申请人 只走私聊投递;群里只回「已私聊发送」,群消息里不含密码
每次申请/下发都汇报 申请、重复申请、审核、下发、失败、超时作废、撤销,全部推送到管理员私聊 + 管理群(两个渠道可用 MILOCK_REPORT_TO_ADMINS / MILOCK_REPORT_TO_GROUPS 单独开关)

2. ⚠️ 关于"一次性"的边界

插件为每个 (时间窗口, 密码序号) 建一条台账(grants 表):

  • 发放前先用 BEGIN IMMEDIATE 事务占位,投递成功才落成 delivered;
  • 同一个密码永远不会发给第二个人(即使多人同时申请、即使监听了同一条消息);
  • 投递失败会释放占位,密码可以给下一个人,不会白白浪费;
  • 同一用户每个窗口默认只能领 1 个(MILOCK_MAX_CODES_PER_WINDOW)。

一个密码的有效期是"本窗口 + 下一个窗口",也就是 1 小时(interval=30 时)。 依据是 Mi Home 自己的算法——GeneralOneTimePasswordGenerateActivity 等多处都写成:

valid_end = (窗口序号 + 2) * interval * 60    // +2 = 本窗口 + 下一窗口

所以窗口 20:30-21:00 的密码实际有效到 21:30。app 也用这个过期时刻做缓存键, 一小时内重复生成会复用同一个码。

但是:锁具固件认这些密码,插件无法在锁上作废它。所以"发出后即失效"只在 本插件这一侧成立——转发出去的密码在有效期结束前依然能开门;真正的物理失效 要等有效期过完(默认 1 小时)。

MILOCK_INTERVAL_MINUTES 可调(1–60)。窗口越小,单个密码的有效期越短 (有效期恒为 2 × interval 分钟)。默认 30 分钟窗口 → 1 小时有效期。

3. 安装

插件只依赖 nonebot2 与 nonebot-adapter-onebot,对 milock 的调用走标准库 urllib。

# 方式一:从 PyPI 安装(推荐)
pip install nonebot-plugin-milock

# 方式二:从本仓库源码装
pip install /path/to/nonebot-plugin-milock
# 开发时用可编辑安装:
pip install -e "/path/to/nonebot-plugin-milock[dev]"

# 方式三:直接把包目录拷进 NoneBot 项目的 plugins/ 下
cp -r nonebot_plugin_milock <你的项目>/plugins/

在 bot.py 里加载:

nonebot.load_plugin("nonebot_plugin_milock")
# 或放进 plugins/ 目录后:nonebot.load_plugins("plugins")

4. 配置(写进 NoneBot 的 .env)

# ---- milock 服务 ----
MILOCK_API_BASE=http://192.168.139.50:8787
MILOCK_API_KEY=你的APIKey          # 服务端没开鉴权就留空
MILOCK_DID=1234567890              # 设备的 did,服务端自动取密钥
MILOCK_PINCODE=1234                # 安全密码(encrypt_type=1 时需要)

# ---- 谁能审、汇报给谁 ----
MILOCK_ADMINS=["10001","10002"]        # 额外管理员(NoneBot 的 SUPERUSERS 自动算管理员)
MILOCK_ADMIN_GROUPS=["987654321"]      # 管理群:汇报目标 + 群内审核的唯一合法场所
MILOCK_REPORT_TO_ADMINS=true       # 汇报是否推送到管理员私聊
MILOCK_REPORT_TO_GROUPS=true       # 汇报是否推送到管理群

# ---- 谁可以要(与"谁能审"独立)----
MILOCK_REQUEST_GROUPS=[]           # 只允许这些群索要;留空 = 不限制群
MILOCK_ALLOW_PRIVATE_REQUEST=true  # 是否允许私聊索要

# ---- 门锁事件提醒(门铃 / 开锁)----
MILOCK_EVENT_ENABLED=false          # 需要服务端已登录米家账号
MILOCK_EVENT_POLL_INTERVAL=30       # 轮询间隔(秒)
MILOCK_EVENT_FORWARD_KINDS=["doorbell","unlock","abnormal"]  # 转发到管理群
MILOCK_EVENT_PRINT_KINDS=["doorbell","abnormal"]             # 打印小票(强提醒)
MILOCK_EVENT_PRINT_COOLDOWN=60      # 同类打印冷却(秒),防连按撕纸

# ---- 出码与限流 ----
MILOCK_MAX_CODES_PER_WINDOW=1      # 每人每窗口可领几个
MILOCK_MIN_REMAINING_SECONDS=60    # 剩余有效期低于此值只打告警日志(不再改变发码)
MILOCK_APPROVAL_TIMEOUT=300        # 申请单多少秒没人处理就作废
MILOCK_REPORT_PASSWORD=true        # 汇报给管理员私聊时是否带密码明文(审计用)
MILOCK_REPORT_PASSWORD_IN_GROUPS=false  # 管理群汇报是否也带明文(默认不带)

# ---- 其它 ----
MILOCK_DB_PATH=data/milock.sqlite3 # 授权与发放台账,建议指向挂载卷
MILOCK_COMMAND=密码                # 用户申请命令名,默认 /密码
MILOCK_ALLOW_KEYWORD_TRIGGER=false # 允许直接说"申请密码"触发
MILOCK_ALLOW_TIME_REQUEST=true     # 允许 /密码 21:00 取指定时段的码
MILOCK_MAX_ADVANCE_HOURS=24        # 最多提前多久取码

完整字段与含义见 nonebot_plugin_milock/config.py。 集合类型与 NoneBot 的 SUPERUSERS 一样写 JSON 数组,也接受逗号分隔字符串 (裸数字也行,如 MILOCK_ADMINS=10001)。

配置项速查

全部 26 个字段,⭐ 标记的是后加的、容易漏看的项。

milock 服务

字段 默认值 说明
MILOCK_API_BASE http://127.0.0.1:8787 milock HTTP 服务地址
MILOCK_API_KEY 空 服务端 X-API-Key;未启用鉴权就留空
MILOCK_DID 空 设备 did,服务端自动取密钥(推荐)
MILOCK_PINCODE 空 安全密码,配合 MILOCK_DID 解包 encrypt_type=1
MILOCK_SEED_HEX 空 设备主密钥(十六进制);填了就不用 did,注意保密
MILOCK_TIMEOUT 10.0 单次 HTTP 请求超时(秒)

权限:谁能审

字段 默认值 说明
MILOCK_ADMINS 空 额外管理员 QQ;NoneBot 的 SUPERUSERS 始终算管理员
MILOCK_ADMIN_GROUPS 空 管理群:汇报目标 + 群内审核的唯一合法场所

权限:谁能要(与"谁能审"独立,见下节)

字段 默认值 说明
⭐ MILOCK_REQUEST_GROUPS 空 只允许这些群索要密码;留空 = 不限制群
⭐ MILOCK_ALLOW_PRIVATE_REQUEST true 是否允许私聊索要;设为 false 时必须配 MILOCK_REQUEST_GROUPS

汇报

字段 默认值 说明
MILOCK_REPORT_TO_ADMINS true 是否把汇报推送到管理员私聊
MILOCK_REPORT_TO_GROUPS true 是否把汇报推送到管理群
MILOCK_REPORT_PASSWORD true 汇报给管理员私聊时是否带密码明文
MILOCK_REPORT_PASSWORD_IN_GROUPS false 汇报到管理群时是否也带明文

出码与限流

字段 默认值 说明
MILOCK_INTERVAL_MINUTES 服务端默认(30) 窗口长度(分钟),1–60;留空用服务端值
MILOCK_DIGITS 服务端默认(8) 密码位数,1–8;留空用服务端值
MILOCK_MIN_REMAINING_SECONDS 60 剩余低于该值时只打告警日志,不改变发码
MILOCK_MAX_CODES_PER_WINDOW 1 每人每窗口最多领几个
⭐ MILOCK_ALLOW_TIME_REQUEST true 是否允许 /密码 <时间> 取指定时段
⭐ MILOCK_MAX_ADVANCE_HOURS 24 指定时间最多提前多久(小时)

审核与存储

字段 默认值 说明
MILOCK_APPROVAL_TIMEOUT 300 申请单多少秒没人处理就作废
MILOCK_RESERVATION_TTL 90 密码"占位"存活秒数;投递失败后自动回收
MILOCK_SWEEP_INTERVAL 30 后台清理任务间隔(秒)
MILOCK_DB_PATH data/milock.sqlite3 SQLite 路径,建议指向挂载卷(见第 7 节)

门锁事件(门铃 / 开锁)

字段 默认值 说明
MILOCK_EVENT_ENABLED false 是否轮询门锁事件并转发
MILOCK_EVENT_POLL_INTERVAL 30 轮询间隔(秒),最小 5
MILOCK_EVENT_FORWARD_KINDS doorbell,unlock,abnormal 哪些类别转发到管理群
MILOCK_EVENT_PRINT_KINDS doorbell,abnormal 哪些类别打印小票
MILOCK_EVENT_PRINT_COOLDOWN 60 同类打印最小间隔(秒),0 = 不限
MILOCK_EVENT_LOOKBACK_MINUTES 10 首次运行最多回溯多久
MILOCK_EVENT_KEEP 500 保留多少条去重记录

交互

字段 默认值 说明
MILOCK_COMMAND 密码 用户申请命令名;只写命令名,不要带 /
MILOCK_ALLOW_KEYWORD_TRIGGER false 允许直接说"申请密码"触发(群里可能误触)

汇报渠道怎么选

MILOCK_REPORT_TO_ADMINS / MILOCK_REPORT_TO_GROUPS 分别控制两类汇报目标,互不影响:

配置 效果
两个都开(默认) 管理员私聊 + 管理群都收到汇报
只开私聊 群里完全安静,适合"不想让整个群看到审批流水"
只开群 管理员私聊不被打扰,适合"管理员都在群里"
两个都关 静默运行,只在日志里留痕(不建议,出错时不利于排查)

⚠️ 这两个开关只管汇报推送,不影响群内审核资格:能不能在群里执行 /milock approve 仍然只取决于群号是否在 MILOCK_ADMIN_GROUPS 里。 所以 MILOCK_REPORT_TO_ADMINS=false 时必须配 MILOCK_ADMIN_GROUPS—— 否则申请没人看得见,插件会直接在启动时报错拒绝这种配置。

谁能要 vs 谁能审

这是两组独立的配置,刻意没有合并——审核场所和分发场所常常不是同一批群:

配置 管什么
MILOCK_REQUEST_GROUPS + MILOCK_ALLOW_PRIVATE_REQUEST 谁能发起申请(要码)
MILOCK_ADMIN_GROUPS + MILOCK_ADMINS 谁能批准(审批)

常用组合:

配置 效果
默认(群白名单为空、私聊开) 任何群、任何私聊都能要码
MILOCK_REQUEST_GROUPS=["555"] 只有 555 群能要;私聊仍可以
再设 MILOCK_ALLOW_PRIVATE_REQUEST=false 只有 555 群能要,私聊也不行
只设 MILOCK_ALLOW_PRIVATE_REQUEST=false 启动即报错(没有地方能要码了)

要点:

  • 被拒时只回一句「这里不能索要密码」,不会透露白名单内容,避免被拿去试探;
  • 管理员与 SUPERUSERS 不受白名单限制:被拉进新群时仍能帮人取码, 也避免"私聊关掉 + 群号漏配"时把自己彻底锁死;
  • 申请单上的「来源群」仍照实记录,审批时能看到是谁在哪个群要的。

5. 命令

身份 命令 说明
用户 /密码(别名 /申请密码 /取密码 /otp) 申请密码,首次会送审
用户 /密码 <时间> 取指定时段的密码,如 /密码 21:00
用户 /密码 help(用法/?/帮助 同义) 查看用法说明
管理员 /milock(或 /milock help) 查看管理命令帮助
管理员 /milock approve <单号>(批准/通过 同义) 批准并立即私聊发码
管理员 /milock deny <单号>(拒绝 同义) 拒绝并通知申请人
管理员 /milock pending 查看待审核申请
管理员 /milock revoke <QQ> 撤销某人的授权
管理员 /milock users 查看已授权用户
管理员 /milock status 插件状态、milock 可达性、发放统计

本插件不注册 /help、/帮助 这类全局命令——那属于专门的帮助插件 (如 nonebot-plugin-help)的职责,各插件抢占会互相冲突。用法的入口是 /密码 help 与 /milock,同时插件也声明了 PluginMetadata.usage, 帮助类插件可直接读取展示。

指定时间取码

/密码 21:00 会立刻发给你一个"21:00 前后各半小时内都能开锁"的密码 (不是到点才发),所以你可以提前把码准备好。支持的写法:

写法 含义
21:00 下一个即将到来的 21:00(已过则算明天)
09-15 21:00 今年 9 月 15 日 21:00(已过则算明年)
2026-09-15 21:00 指定年月日(也可写 T 分隔)
+30m / +2h / +1h30m 相对现在的偏移

怎么做到"前后各半小时":因为一个密码的有效期是两个窗口 (本窗口 + 下一窗口,见第 2 节),所以取 at 所在窗口的前一个窗口即可:

输入 发出的码所属窗口 有效期
/密码 21:00 20:30-21:00 20:30 - 21:30(1 小时)
/密码 21:15 21:00-21:30 21:00 - 22:00
/密码 08:00 07:30-08:00 07:30 - 08:30

21:00 恰好落在窗口边界上,所以有效区间正好以它为中心、前后各 30 分钟。 只发 1 个码——不需要为了覆盖而多发,暴露面最小。

私聊收到的文案会写明完整有效期:

【一次性密码】12345678
有效时段 20:30 - 21:30(约 15 分钟后开始生效)

⚠️ 时间不在半点/整点时(如 21:15),有效区间是 21:00-22:00—— 仍然覆盖 21:15,但不再是严格的"前后各 30 分钟"。想要严格对称就写整/半点。

其他约束:

  • 最多提前 MILOCK_MAX_ADVANCE_HOURS(默认 24)小时;更远的会被拒绝;
  • 已经结束的时段会被跳过;一个可用时段都不剩则直接拒绝, 不会拿一个"还能用的码"敷衍你;
  • 取多个窗口时任一步失败会回滚已占位的码,不会白占着不放;
  • 指定时段的码同样受"一人一窗口一个"的限制(MILOCK_MAX_CODES_PER_WINDOW), 且同一个码永不发给第二个人;
  • 未授权用户可以用 /密码 21:00 申请,审批单上会写明"期望可用时间", 管理员批准后仍按该时段发码(不会因为拖了几分钟就变成批准那一刻的窗口);
  • 想关掉这个功能:MILOCK_ALLOW_TIME_REQUEST=false。

权限规则:私聊里必须是管理员;群里必须是管理员且位于 MILOCK_ADMIN_GROUPS(超管不受群限制)。

6. 门锁事件提醒(门铃 / 开锁)

除了"用户来要密码",插件还能反过来把门锁发生的事推给管理员:

  • 有人按门铃、有人开门、有人在试密码 → 转发到管理群;
  • 需要 staff 立刻反应的两类(门铃、异常)→ 可同时打印小票做强提醒。
MILOCK_EVENT_ENABLED=true
MILOCK_EVENT_FORWARD_KINDS=["doorbell","unlock","abnormal"]
MILOCK_EVENT_PRINT_KINDS=["doorbell","abnormal"]

事件从哪来

门锁事件由服务端从米家消息中心取出,插件轮询 GET /api/v1/mijia/events。 所以这条链路要求 milock 服务端已登录米家账号(服务端要能扫码登录)。

为什么不直接读设备属性? 门锁的 miot-spec 里确实有 Lock Opened / Door Knocked 等事件,但它们的 参数属性(operation-method、abnormal-condition)在规格里是 access: [], 直接读会返回 -704040998,只能由云端推送。消息中心是唯一同时给出 开门方式(指纹/密码/一次性密码/应急钥匙)和门铃的来源。

插件拿到的文案是米家已经渲染好的中文,例如:

🔔 门铃|23:12 有人按门铃
设备:小米全自动智能门锁(工作室)

🚪 开锁|17:55 一次性密码门外开锁
设备:小米全自动智能门锁(工作室)

⚠️ 异常|14:38 有错误指纹\密码\NFC卡在频繁尝试开锁,请及时确认安全
设备:小米全自动智能门锁(工作室)
请确认是否需要处理。

顺带一提:一次性密码门外开锁 说明有人用了本插件发出的码,可以和取码台账对照审计。

事件分类

米家只给两个类别码(7 门铃、11 开锁),而 11 把正常开门和 "有人在频繁试密码"混在一起,所以插件再按标题关键词细分:

类别 触发 默认转发 默认打印
doorbell 有人按门铃 ✅ ✅
unlock 正常开门(指纹/密码/一次性密码/应急钥匙) ✅ ❌
abnormal 频繁输错、防撬、验证失败等 ✅ ✅
other 其它事件(将来新增的类别码) ✅ ❌

正常开锁一天几十次,默认不打印,否则会把纸撕光;需要就把它加进 MILOCK_EVENT_PRINT_KINDS。

打印小票(强提醒)

打印复用同实例里的 nonebot_plugin_receipts: 插件直接调用它的渲染与 spooler 客户端,不重复实现 ESC/POS,也不重复配置 spooler 地址与令牌——那些仍然配在 receipts 插件侧 (RECEIPTS_SPOOLER_URL / RECEIPTS_SPOOLER_TOKEN / RECEIPT_FONT_PATH 等)。

因此:

  • 没装 nonebot_plugin_receipts 也不影响转发到群,只是不打印(会记一条日志);
  • 想调小票样式(字体、模板、走纸)请改 receipts 插件的配置。

本插件只依赖那个插件的公开入口 nonebot_plugin_receipts.api (print_text / ReceiptTemplateContext),不碰它的 renderer / spooler 等内部模块——所以那边重构内部实现不会影响这里。

仍然做了降级处理:对方没装、或公开入口不存在时,打印会停但 事件转发照常,日志里会写明原因,不会连带把整个插件拖挂。

MILOCK_EVENT_PRINT_COOLDOWN 用来防连按:门铃被连按五下只会出一张票。

⚠️ 开着 MILOCK_EVENT_ENABLED 却把两类都配成空,插件会在启动时报错拒绝—— 那等于"一直轮询但什么都不做",不报错的话现象只是"开了开关没反应",很难查。 另外若配了 MILOCK_EVENT_PRINT_KINDS 但同实例没有 receipts 插件, 启动时会直接打一条 error 日志,而不是等到有人按门铃才发现打不出来。

不会重复播报

已处理的事件按 msg_id 记在 SQLite 的 seen_events 表里,所以重启机器人 不会把同一条门铃再播一次。游标(上次见过的最大时间)也存在库里, 首次运行只看最近 MILOCK_EVENT_LOOKBACK_MINUTES 分钟,不会把历史事件 一次性倒出来。

单条事件转发失败时会撤销登记并把游标停在它之前,下一轮重试——提醒不会被 静默吞掉。

7. 数据与审计

单文件 SQLite(默认 data/milock.sqlite3,WAL 模式):

  • users —— 谁被授权、谁批的、什么时候批的;
  • requests —— 每一张申请单(含自动通过的),带窗口和密码序号;
  • grants —— 一次性密码台账,(window_start, code_index) 唯一,记录发给谁、状态;
  • seen_events —— 已播报过的门锁事件(msg_id 主键),重启不重播;
  • meta —— 键值表,目前存事件轮询游标。

因为台账落盘,重启机器人不会导致同一个密码被重复发放(门锁事件同理不会被 重复播报);多个进程共享同一个 DB 文件时,BEGIN IMMEDIATE 事务同样能防止 并发重复发放。

容器部署:数据库目录必须可写

最常见的启动失败是 SQLite 打不开库:

sqlite3.OperationalError: unable to open database file

它几乎总是目录不可写,而不是 SQLite 本身的问题。典型场景:镜像构建时以 root 创建了 /app/data,容器却以非 root 用户运行,于是该目录对运行用户只读; 只读挂载(:ro)同理。

插件会把这个错误翻译成一条带路径、uid、目录权限和修法的提示,例如:

无法打开 SQLite 数据库:/app/data/milock.sqlite3
当前身份:uid=1000 gid=1000 cwd=/app
最近的已存在目录:/app/data(权限 dr-xr-xr-x,当前用户可写=False)
原因:该目录**不可写**。

对策(任选其一):

  • 让目录属于运行用户:chown -R 1000:1000 /app/data;
  • 改用运行用户可写的路径,例如挂载一个可写卷并指向它;
  • 镜像里用 VOLUME / mkdir 时就带上正确属主。

排查时可临时设 MILOCK_DB_PATH=:memory: 确认问题确实出在路径上 (重启即丢数据,只用于排查)。

8. 开发与测试

本仓库是独立仓库(不再挂在 milock 主仓库的 contrib/ 下),测试需要 NoneBot 运行时:

python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest -q            # 全部用例
.venv/bin/ruff check . && .venv/bin/ruff format --check .   # 使用本仓库的 ruff 配置

ruff 不在 dev 额外依赖里,需要的话单独 pip install ruff。

覆盖的关键场景:并发不重发、投递失败回收、配额、有效期按两窗口计算、审核与拒绝、 汇报同时到达管理员私聊与管理群、管理员私聊挂掉时的容错、超时作废。 其中 tests/test_plugin_load.py 会在子进程里真正跑一次 nonebot.load_plugin, 验证 matcher 注册、启动钩子与 SQLite 落盘。

9. 发布到 PyPI

用 Trusted Publishing(OIDC) 发布,仓库里不需要保存任何 PyPI Token。 工作流见 .github/workflows/publish.yml。

一次性配置(在 PyPI 网站上操作,我无法代做)

在本仓库的 PyPI 账号里添加 pending publisher(首次发布会自动建项目):

项目 填什么
PyPI Project Name nonebot-plugin-milock
Owner parhelion-rev
Repository name nonebot-plugin-milock
Workflow name publish.yml
Environment name pypi

⚠️ 本仓库是私有仓库。Trusted Publishing 本身支持私有仓库,但 actions/checkout 在私有仓库下需要 contents: read 权限,否则构建 job 会以 Repository not found 失败——工作流里已经显式声明,别删。

发布流程

# 1) 先发 TestPyPI 演练(版本自动带后缀,可反复跑)
#    Actions → Publish → Run workflow → target=testpypi → version_suffix=.dev1
#    验证安装:
pip install --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple/ nonebot-plugin-milock

# 2) 确认无误后发正式:改好 pyproject.toml 的 version,打标签即可
git tag v0.1.0 && git push origin v0.1.0

打 v* 标签会自动校验「标签版本 == pyproject.toml 版本」,不一致直接失败—— 这样能避免把错版本发出去。

两个设计约束(别改回去)

  • 构建与发布拆成两个 job,发布 job 里绝不 checkout。官方明确反对在持有 id-token 的 job 中执行第三方构建代码(供应链风险);产物流经 artifact 传递。
  • 版本号一旦上传就不可重用(PyPI 与 TestPyPI 都是),删除也不行,只能 yank。 所以演练要靠 version_suffix 换版本号,而不是重发同一个版本。

Metadata

Release files for nonebot-plugin-milock 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nonebot-plugin-milock 0.3.0
File Size Uploaded
nonebot_plugin_milock-0.3.0.tar.gz 107.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nonebot-plugin-milock 0.3.0
File Interpreter ABI Platform
nonebot_plugin_milock-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 179.1 kB

Release files / nonebot_plugin_milock-0.3.0.tar.gz

Download URL nonebot_plugin_milock-0.3.0.tar.gz
Size 107.8 kB
Tags Source
SHA-256 checksum
How to use checksums
610f01140cf4df598468a99de8a201c345c635b39df94b5a845797aedc99af8f
BLAKE2b-256 checksum
How to use checksums
3c1f17fac1bfaec4276986a6a78b26664bfe047c00654a1a21241070901b22ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release files / nonebot_plugin_milock-0.3.0-py3-none-any.whl

Download URL nonebot_plugin_milock-0.3.0-py3-none-any.whl
Size 71.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3104dd783a2670592232a0a19075b0dbccf9e8a2aa754d54f4ad7f7f8ad19a4f
BLAKE2b-256 checksum
How to use checksums
f82b6433be1d303b2de2158dc37fcf3e59675669c1f9be71897719818cc5a122
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page