发布 26.9.14:进服文案与默认配置优化,配置可自动重建

This commit is contained in:
SKYMirror committed 2026-09-14 01:13:11 +08:00
1 parent 1fdf17a47c
commit 18e13a861b
10 files changed
+290 -75

No files matched your search

+85 -34
View File
@@ -1,69 +1,120 @@
# Changelog # Changelog / 更新日志
本文件记录 Iridium 的重要变更。 本文件记录 Iridium 的重要变更。
This file records notable changes to Iridium.
---
## [26.9.14] - 2026-09-14
### 修复 / Fixed
- 删除 `config/iridium.json` 后重载可正确重新生成(多路径探测 + 写入失败日志)
- Deleting `config/iridium.json` then reloading correctly recreates the file (multi-path probe + write-failure logs)
### 变更 / Changed
- `motd_start_day` 默认为配置生成当天(YYYY-MM-DD),不再为空
- `motd_start_day` defaults to the config creation date (`YYYY-MM-DD`), no longer empty
- 去掉 MOTD 标题「服务器信息」/「Server Info」
- Removed the MOTD header title (`服务器信息` / `Server Info`)
- 默认 QQ 群增加可点击链接 `https://skymirror.top/qq`
- Default QQ group line is now a clickable link to `https://skymirror.top/qq`
- 进服命令提示文案去掉「已启用 Iridium」
- Join tip header no longer says “Iridium is enabled”
### 新增 / Added
- 英文文档 `README_en.md`
- English documentation `README_en.md`
---
## [26.9.13] - 2026-09-13 ## [26.9.13] - 2026-09-13
### Added ### 新增 / Added
- 首次启动自动生成带中文注释的 `config/iridium.json` - 首次启动自动生成带中文注释的 `config/iridium.json`
- On first load, generate a commented `config/iridium.json`
- 配置文件支持 `//` 行注释 - 配置文件支持 `//` 行注释
- Config file supports `//` line comments
### Changed ### 变更 / Changed
- 提交信息改为中文 - 提交信息改为中文
- Commit messages rewritten in Chinese
- 清理历史 Release,仅保留当前版本 - 清理历史 Release,仅保留当前版本
- Cleaned old Releases; keep only the current version
---
## [26.9.8] - 2026-09-13 ## [26.9.8] - 2026-09-13
### Changed ### 变更 / Changed
- `!!share`:去掉「点击复制完整数据」,仅保留物品名 + 悬停预览(与 ShowIt 风格一致) - `!!share`:去掉「点击复制完整数据」,仅保留物品名 + 悬停预览
- `!!share`: removed the copy-to-clipboard button; name + hover only
---
## [26.9.7] - 2026-09-13 ## [26.9.7] - 2026-09-13
### Fixed ### 修复 / Fixed
- `!!share`:无 NBT 改名时,正文 fallback 改为 ID 美化名(如 `Time Twister Wireless`),不再直接显示 `kubejs:...`;语言键命中则显示正式本地化名 - `!!share`:无 NBT 改名时 fallback 为 ID 美化名(如 `Time Twister Wireless`)
- `!!share`: humanized ID fallback when the item has no NBT custom name
---
## [26.9.6] - 2026-09-13 ## [26.9.6] - 2026-09-13
### Fixed ### 修复 / Fixed
- `!!share`:默认物品名改为客户端 `translate` 组件(如 `block.ae2.fluix_covered_cable`),本地化显示正式名;仅有 NBT 自定义名时仍显示改名 - `!!share`:使用客户端 `translate` 组件显示本地化名
- `!!share`:提示音在玩家位置播放,避免 "sound is too far away" - `!!share`: use client `translate` component for localized names
- `!!share`:提示音在玩家位置播放
- `!!share`: play sound at the player to avoid “too far away”
---
## [26.9.5] - 2026-09-13 ## [26.9.5] - 2026-09-13
### Fixed ### 修复 / Fixed
- `!!hat`(1.17+):改用副手缓冲做真正交换,避免复杂 NBT 重建失败导致原头盔消失;副手非空时拒绝并提示 - `!!hat`(1.17+):副手缓冲交换,避免复杂 NBT 重建失败导致物品消失
- `!!share`:展示名优先取 `custom_name` / `display.Name` / translate,不再只显示物品 ID - `!!hat` (1.17+): offhand-buffer swap so complex NBT cannot wipe items
- `!!share`:优先显示自定义名 / translate
- `!!share`: prefer custom name / translate over raw item id
---
## [26.9.4] - 2026-09-13 ## [26.9.4] - 2026-09-13
### Added ### 新增 / Added
- 操作日志:`!!share` / `!!hat` / `!!head` / `!!c` 成功时写入 MCDR 控制台(`[Iridium] ...`) - 操作日志写入 MCDR 控制台
- Command operations logged to the MCDR console
---
## [26.9.3] - 2026-09-13 ## [26.9.3] - 2026-09-13
### Fixed ### 修复 / Fixed
- `!!share` / `!!hat` / `!!head`:`info.version` 若为字符串时比较崩溃(`Cannot compare Version and str`) - 版本比较在 `str` / `Version` 之间崩溃
- `!!c`:命令回调少绑定 `server` 导致 `do_calc() missing ... context` - Version comparison crash between `str` and `Version`
- `!!c` 回调缺少 `server` 绑定
- `!!c` callback missing `server` binding
---
## [26.9.2] - 2026-09-13 ## [26.9.2] - 2026-09-13
### Fixed ### 修复 / Fixed
- 语言文件 YAML 解析失败(值中含 `:` 未加引号导致 `mapping values are not allowed`) - 语言文件 YAML 因值中冒号未加引号而解析失败
- 进服事件名错误:`PLAYER_JOIN` → `PLAYER_JOINED`(否则插件 on_load 失败) - Language YAML failed when values contained unquoted colons
- 进服事件名:`PLAYER_JOIN` → `PLAYER_JOINED`
- Join event name: `PLAYER_JOIN` → `PLAYER_JOINED`
---
## [26.9.1] - 2026-09-13 ## [26.9.1] - 2026-09-13
### Added ### 新增 / Added
- `!!share`:展示主手物品(悬停完整数据,1.16+ 点击复制,可选提示音) - `!!share` / `!!hat` / `!!head` / `!!c`、进服 MOTD 与命令提示
- `!!hat`:主手物品与头部装备互换(需 RCON) - `!!share` / `!!hat` / `!!head` / `!!c`, join MOTD and command tips
- `!!head [player]`:获取一个玩家头颅,支持冷却与权限 - 按 Minecraft 版本分支;不支持的功能会提示
- `!!c <expression>`:游戏内四则运算计算器(AST 白名单,零依赖) - Minecraft-version branching with clear prompts when unsupported
- 进服 MOTD:开服天数、自定义文案、可点击链接、`§` 颜色
- 进服命令提示(可点击填入聊天框)
- 按 Minecraft 版本分支;不支持的功能会明确提示
### Security ### 安全 / Security
- 玩家名统一清洗为 `[A-Za-z0-9_]` 并限长后再拼进 `/give`、`data get`、`item replace` - 玩家名清洗、命令去换行、tellraw 转义、链接仅 http(s)
- 控制台命令去除换行,防止拆成多行二次执行 - Name sanitization, newline stripping, tellraw escaping, http(s)-only links
- tellraw 对物品 SNBT 做 JSON 转义与长度截断
- MOTD 链接仅允许 `http://` / `https://`
+4 -4
View File
@@ -4,7 +4,7 @@ MCDReforged 插件:物品展示 / 戴头 / 获取头颅 / 游戏内计算器 /
| | | | | |
|---|---| |---|---|
| 版本 | **26.9.13** | | 版本 | **26.9.14** |
| 作者 | 云镜之端/SKYMirror | | 作者 | 云镜之端/SKYMirror |
| 许可 | 保留所有权益(All Rights Reserved) | | 许可 | 保留所有权益(All Rights Reserved) |
| 前置 | MCDReforged `>= 2.6.0` | | 前置 | MCDReforged `>= 2.6.0` |
@@ -91,12 +91,12 @@ rcon.password=your_password
"join_tip": true, "join_tip": true,
"join_tip_delay_seconds": 1.5, "join_tip_delay_seconds": 1.5,
"motd_enabled": true, "motd_enabled": true,
"motd_start_day": "", "motd_start_day": "2026-09-14",
"motd_lines": [ "motd_lines": [
"§6欢迎 §e{player}§6 加入服务器!", "§6欢迎 §e{player}§6 加入服务器!",
"§7服务器已开服 §b{days}§7 天", "§7服务器已开服 §b{days}§7 天",
"§7官网: {link:§b点击打开官网|https://www.skymirror.top}", "§7官网: {link:§b点击打开官网|https://www.skymirror.top}",
"§7QQ群: §f985402607" "§7QQ群: {link:§f985402607|https://skymirror.top/qq}"
] ]
} }
``` ```
@@ -111,7 +111,7 @@ rcon.password=your_password
| `join_tip` | `true` | 进服是否提示可用命令 | | `join_tip` | `true` | 进服是否提示可用命令 |
| `join_tip_delay_seconds` | `1.5` | 进服消息延迟;`0` 立即 | | `join_tip_delay_seconds` | `1.5` | 进服消息延迟;`0` 立即 |
| `motd_enabled` | `true` | 是否发送 MOTD | | `motd_enabled` | `true` | 是否发送 MOTD |
| `motd_start_day` | `""` | 开服日期 `YYYY-MM-DD`(当天算第 1 天);留空不显示天数 | | `motd_start_day` | 生成配置当天 | 开服日期 `YYYY-MM-DD`(当天算第 1 天);可改成真实开服日 |
| `motd_lines` | 见上 | MOTD 文本行 | | `motd_lines` | 见上 | MOTD 文本行 |
修改配置后执行 `!!MCDR reload plugin` 生效。 修改配置后执行 `!!MCDR reload plugin` 生效。
+95
View File
@@ -0,0 +1,95 @@
# Iridium
MCDReforged plugin: item share / wear on head / player head / calculator / join MOTD.
| | |
|---|---|
| Version | **26.9.14** |
| Author | 云镜之端/SKYMirror |
| License | All Rights Reserved |
| Requires | MCDReforged `>= 2.6.0` |
| Minecraft | **1.7.10+** (feature-gated by version) |
| Python deps | None |
## Features
| Command | Description |
|---------|-------------|
| `!!share` | Share held item: name in chat, hover for full data |
| `!!hat` | Swap main-hand item with head slot |
| `!!head [player]` | Get **one** player head (default: yourself); cooldown |
| `!!c <expression>` | In-game calculator: `+ - * / // % ** ^` |
| Join MOTD | Uptime days, custom lines, clickable links, `§` colors |
| Join tips | Command list; click fills chat |
### Version support
| Feature | 1.7.10 | 1.8–1.12 | 1.13–1.16 | 1.17–1.20.4 | 1.20.5+ |
|---------|:------:|:--------:|:---------:|:-----------:|:-------:|
| `!!c` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `!!head` | ✅ | ✅ | ✅ | ✅ | ✅ profile |
| `!!share` | ❌ | ✅ entitydata | ✅ data get | ✅ | ✅ |
| `!!hat` | ❌ | ✅ RCON | ✅ RCON | ✅ RCON | ✅ RCON |
| MOTD / tips | ✅ | ✅ | ✅ | ✅ | ✅ |
## Install
1. Download `Iridium.mcdr`
2. Put it in MCDR `plugins/`
3. `!!MCDR reload plugin` or restart MCDR
### RCON (required for `!!hat`)
```properties
enable-rcon=true
rcon.port=25575
rcon.password=your_password
```
## Config
Path: `config/iridium.json` (auto-created with comments on first load)
| Field | Default | Description |
|------|---------|-------------|
| `permission` | `1` | Min permission for commands |
| `head_cooldown_seconds` | `60` | `!!head` cooldown; `0` = none |
| `hat_cooldown_seconds` | `3` | `!!hat` cooldown; `0` = none |
| `share_sound` | `true` | Sound on `!!share` |
| `force_version` | `""` | Force MC version; empty = auto |
| `join_tip` | `true` | Command tips on join |
| `join_tip_delay_seconds` | `1.5` | Join message delay |
| `motd_enabled` | `true` | Send join MOTD |
| `motd_start_day` | today | Server start date `YYYY-MM-DD` (day 1 = that date) |
| `motd_lines` | see file | MOTD lines with placeholders |
Default `motd_lines` (QQ group is clickable):
```text
§6Welcome §e{player}§6 !
§7Server has been open for §b{days}§7 days
§7Website: {link:§bOpen website|https://www.skymirror.top}
§7QQ: {link:§f985402607|https://skymirror.top/qq}
```
### MOTD placeholders
| Token | Meaning |
|-------|---------|
| `{player}` | Joining player |
| `{days}` / `{day}` | Days since `motd_start_day` |
| `{online}` | Online count or `?` |
| `{link:text\|https://url}` | Clickable link |
| `{link:https://url}` | Clickable URL |
| `§` | Vanilla color/style codes |
## Security
- Calculator: AST whitelist, no arbitrary Python
- Player names sanitized to `[A-Za-z0-9_]` before game commands
- Newlines stripped from console commands
- MOTD links limited to `http(s)` only
## Copyright
Copyright (c) 2026 云镜之端/SKYMirror. All Rights Reserved. See `LICENSE`.
+1 -1
View File
@@ -12,6 +12,6 @@
"labels": ["tool"], "labels": ["tool"],
"introduction": { "introduction": {
"zh_cn": "README.md", "zh_cn": "README.md",
"en_us": "README.md" "en_us": "README_en.md"
} }
} }
+94 -25
View File
@@ -1,11 +1,15 @@
import json import json
import re from datetime import date
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
from mcdreforged.api.all import * from mcdreforged.api.all import *
def _today() -> str:
return date.today().strftime("%Y-%m-%d")
class Config(Serializable): class Config(Serializable):
# 使用 !!share / !!hat / !!head / !!c 所需的最低权限等级 # 使用 !!share / !!hat / !!head / !!c 所需的最低权限等级
permission: int = 1 permission: int = 1
@@ -23,8 +27,8 @@ class Config(Serializable):
join_tip_delay_seconds: float = 1.5 join_tip_delay_seconds: float = 1.5
# 是否发送进服 MOTD(开服天数 + 自定义行) # 是否发送进服 MOTD(开服天数 + 自定义行)
motd_enabled: bool = True motd_enabled: bool = True
# 开服日期,格式 YYYY-MM-DD,用于计算开服天数;留空则不显示天数 # 开服日期,格式 YYYY-MM-DD;默认为配置生成当天(当天算第 1 天)
motd_start_day: str = "" motd_start_day: str = _today()
# MOTD 文本行。支持: # MOTD 文本行。支持:
# {player} 玩家名 # {player} 玩家名
# {days} 开服天数 # {days} 开服天数
@@ -36,14 +40,17 @@ class Config(Serializable):
"§6欢迎 §e{player}§6 加入服务器!", "§6欢迎 §e{player}§6 加入服务器!",
"§7服务器已开服 §b{days}§7 天", "§7服务器已开服 §b{days}§7 天",
"§7官网: {link:§b点击打开官网|https://www.skymirror.top}", "§7官网: {link:§b点击打开官网|https://www.skymirror.top}",
"§7QQ群: §f985402607", "§7QQ群: {link:§f985402607|https://skymirror.top/qq}",
] ]
CONFIG_FILE = "config/iridium.json" CONFIG_FILE = "config/iridium.json"
# 首次生成时写入带注释的 JSON(MCDR 本身不写注释)
DEFAULT_CONFIG_TEXT = """{ def build_default_config_text() -> str:
"""带注释的默认配置;开服日期为生成当天。"""
today = _today()
return f"""{{
// 使用 !!share / !!hat / !!head / !!c 所需的最低权限等级 // 使用 !!share / !!hat / !!head / !!c 所需的最低权限等级
// 0=控制台 1=普通玩家 2=OP/管理员 // 0=控制台 1=普通玩家 2=OP/管理员
"permission": 1, "permission": 1,
@@ -71,22 +78,26 @@ DEFAULT_CONFIG_TEXT = """{
"motd_enabled": true, "motd_enabled": true,
// 开服日期,格式 YYYY-MM-DD(当天算第 1 天) // 开服日期,格式 YYYY-MM-DD(当天算第 1 天)
// 留空则不显示开服天数 // 默认为本配置文件生成当天,可自行修改为真实开服日
"motd_start_day": "", "motd_start_day": "{today}",
// MOTD 文本行 // MOTD 文本行
// 占位符: {player} 玩家名 {days} 开服天数 {online} 在线人数 // 占位符: {{player}} 玩家名 {{days}} 开服天数 {{online}} 在线人数
// 链接: {link:显示文字|https://url} 或 {link:https://url} // 链接: {{link:显示文字|https://url}} 或 {{link:https://url}}
// 颜色: 支持原版 § 颜色代码 // 颜色: 支持原版 § 颜色代码
"motd_lines": [ "motd_lines": [
"§6欢迎 §e{player}§6 加入服务器!", "§6欢迎 §e{{player}}§6 加入服务器!",
"§7服务器已开服 §b{days}§7 天", "§7服务器已开服 §b{{days}}§7 天",
"§7官网: {link:§b点击打开官网|https://www.skymirror.top}", "§7官网: {{link:§b点击打开官网|https://www.skymirror.top}}",
"§7QQ群: §f985402607" "§7QQ群: {{link:§f985402607|https://skymirror.top/qq}}"
] ]
} }}
""" """
# 兼容旧引用
DEFAULT_CONFIG_TEXT = build_default_config_text()
# 模块级单例;on_load 时原地更新字段,避免各模块持有过期引用 # 模块级单例;on_load 时原地更新字段,避免各模块持有过期引用
config = Config() config = Config()
@@ -136,22 +147,80 @@ def _strip_json_comments(text: str) -> str:
return "".join(out) return "".join(out)
def _config_path(server: PluginServerInterface) -> Path: def _candidate_paths(server: PluginServerInterface) -> List[Path]:
wd = Path(server.get_mcdr_config()["working_directory"]) """Possible config file locations, best first."""
return wd / CONFIG_FILE paths: List[Path] = []
seen = set()
def add(p: Path) -> None:
try:
rp = p.expanduser().resolve()
except Exception:
rp = p
key = str(rp)
if key not in seen:
seen.add(key)
paths.append(rp)
try:
wd = server.get_mcdr_config().get("working_directory")
if wd:
add(Path(wd) / CONFIG_FILE)
except Exception:
pass
try:
add(Path.cwd() / CONFIG_FILE)
except Exception:
pass
# Fallback: next to CWD if MCDR launched from elsewhere
add(Path.cwd() / "config" / "iridium.json")
return paths
def config_path(server: PluginServerInterface) -> Path:
"""Prefer an existing file; else the first writable candidate."""
candidates = _candidate_paths(server)
for p in candidates:
if p.is_file():
return p
return candidates[0]
def ensure_config_file(server: PluginServerInterface) -> Path:
"""Create commented default config if missing. Always returns absolute path."""
path = config_path(server)
try:
path.parent.mkdir(parents=True, exist_ok=True)
except Exception as e:
server.logger.error(f"无法创建配置目录 {path.parent}: {e}")
return path
if not path.is_file():
try:
path.write_text(build_default_config_text(), encoding="utf-8")
server.logger.info(f"已生成配置文件: {path}")
except Exception as e:
server.logger.error(f"写入配置文件失败 {path}: {e}")
return path
def load_config(server: PluginServerInterface) -> Config: def load_config(server: PluginServerInterface) -> Config:
"""Load config/iridium.json; create a commented default on first run.""" """Load config; recreate a commented default whenever the file is missing."""
path = _config_path(server) path = ensure_config_file(server)
path.parent.mkdir(parents=True, exist_ok=True) if not path.is_file():
if not path.exists(): server.logger.warning(f"配置文件不存在且无法创建,使用内存默认值: {path}")
path.write_text(DEFAULT_CONFIG_TEXT, encoding="utf-8") return Config()
server.logger.info(f"已生成配置文件: {CONFIG_FILE}") try:
raw = path.read_text(encoding="utf-8") raw = path.read_text(encoding="utf-8")
except Exception as e:
server.logger.error(f"读取配置失败 {path}: {e},使用默认值")
return Config()
try: try:
data = json.loads(_strip_json_comments(raw)) data = json.loads(_strip_json_comments(raw))
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
server.logger.error(f"配置文件 JSON 解析失败,使用默认值: {e}") server.logger.error(f"配置文件 JSON 解析失败 {path}: {e},使用默认值")
return Config() return Config()
try:
return Config.deserialize(data) return Config.deserialize(data)
except Exception as e:
server.logger.error(f"配置反序列化失败 {path}: {e},使用默认值")
return Config()
+3 -4
View File
@@ -175,10 +175,9 @@ def build_motd(player: str, server: ServerInterface) -> Optional[RTextList]:
lines = list(config.motd_lines or []) lines = list(config.motd_lines or [])
if not lines: if not lines:
return None return None
parts: List[RText] = [ parts: List[RText] = []
RText(tr("motd_header"), RColor.gold, styles=[RStyle.bold]) for i, line in enumerate(lines):
] if i:
for line in lines:
parts.append(RText("\n")) parts.append(RText("\n"))
parts.extend(render_motd_line(line, player, server)) parts.extend(render_motd_line(line, player, server))
# Only auto-append the day footer when no line already uses {days}/{day} # Only auto-append the day footer when no line already uses {days}/{day}
+1 -2
View File
@@ -27,7 +27,6 @@ iridium.calc_help: 'Evaluate an expression, e.g. !!c 1+2*(3-1)'
iridium.calc_usage: 'Usage: !!c <expression>, e.g. !!c 1+2*(3-1)' iridium.calc_usage: 'Usage: !!c <expression>, e.g. !!c 1+2*(3-1)'
iridium.calc_error: 'Cannot calculate {0}: {1}' iridium.calc_error: 'Cannot calculate {0}: {1}'
iridium.calc_div_zero: 'Division by zero' iridium.calc_div_zero: 'Division by zero'
iridium.join_tip_header: 'Iridium is enabled. Available commands: (click to fill chat)' iridium.join_tip_header: 'Available commands: (click to fill chat)'
iridium.join_tip_click: 'click to fill chat' iridium.join_tip_click: 'click to fill chat'
iridium.motd_header: 'Server Info'
iridium.motd_days: 'Day {0} since server start' iridium.motd_days: 'Day {0} since server start'
+1 -2
View File
@@ -27,7 +27,6 @@ iridium.calc_help: '计算表达式,例如 !!c 1+2*(3-1)'
iridium.calc_usage: '用法: !!c <表达式>,例如 !!c 1+2*(3-1)' iridium.calc_usage: '用法: !!c <表达式>,例如 !!c 1+2*(3-1)'
iridium.calc_error: '无法计算 {0}: {1}' iridium.calc_error: '无法计算 {0}: {1}'
iridium.calc_div_zero: '除数不能为 0' iridium.calc_div_zero: '除数不能为 0'
iridium.join_tip_header: '本服已启用 Iridium,可用命令:(点击命令可填入聊天框)' iridium.join_tip_header: '可用命令:(点击命令可填入聊天框)'
iridium.join_tip_click: '点击填入聊天栏' iridium.join_tip_click: '点击填入聊天栏'
iridium.motd_header: '服务器信息'
iridium.motd_days: '开服第 {0} 天' iridium.motd_days: '开服第 {0} 天'
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"id": "iridium", "id": "iridium",
"version": "26.9.13", "version": "26.9.14",
"name": "Iridium", "name": "Iridium",
"description": { "description": {
"zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD", "zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD",
+5 -2
View File
@@ -269,12 +269,15 @@ def test_extract_display_name():
def test_strip_json_comments(): def test_strip_json_comments():
import json import json
from datetime import date
from iridium.config import DEFAULT_CONFIG_TEXT, _strip_json_comments from iridium.config import build_default_config_text, _strip_json_comments
parsed = json.loads(_strip_json_comments(DEFAULT_CONFIG_TEXT)) text = build_default_config_text()
parsed = json.loads(_strip_json_comments(text))
assert parsed["permission"] == 1 assert parsed["permission"] == 1
assert "skymirror.top" in parsed["motd_lines"][2] assert "skymirror.top" in parsed["motd_lines"][2]
assert parsed["motd_start_day"] == date.today().strftime("%Y-%m-%d")
# comment-looking text inside string is kept # comment-looking text inside string is kept
assert json.loads(_strip_json_comments('{"a": "http://x"}'))["a"] == "http://x" assert json.loads(_strip_json_comments('{"a": "http://x"}'))["a"] == "http://x"