From 8c52e5f1bdd2803e03d0c17e7e5b17cd7f7223c5 Mon Sep 17 00:00:00 2001 From: l3126596029 Date: Sun, 13 Sep 2026 03:45:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A!!share=20=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20translate=20=E7=BB=84=E4=BB=B6=EF=BC=9B=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E9=9F=B3=E5=9C=A8=E7=8E=A9=E5=AE=B6=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E6=92=AD=E6=94=BE=20(26.9.6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 +++ README.md | 2 +- iridium/share.py | 109 ++++++++++++++++++++++++++-------------- mcdreforged.plugin.json | 2 +- tests/test_pure.py | 22 ++++---- 5 files changed, 91 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbfc97b..d8738df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ 本文件记录 Iridium 的重要变更。 +## [26.9.6] - 2026-09-13 + +### Fixed +- `!!share`:默认物品名改为客户端 `translate` 组件(如 `block.ae2.fluix_covered_cable`),本地化显示正式名;仅有 NBT 自定义名时仍显示改名 +- `!!share`:提示音在玩家位置播放,避免 "sound is too far away" + ## [26.9.5] - 2026-09-13 ### Fixed diff --git a/README.md b/README.md index a1d4f47..5f5d012 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ MCDReforged 插件:物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / | | | |---|---| -| 版本 | **26.9.5** | +| 版本 | **26.9.6** | | 作者 | 云镜之端/SKYMirror | | 许可 | 保留所有权益(All Rights Reserved) | | 前置 | MCDReforged `>= 2.6.0` | diff --git a/iridium/share.py b/iridium/share.py index 6426c12..b4f1ecb 100644 --- a/iridium/share.py +++ b/iridium/share.py @@ -44,10 +44,15 @@ def _query_command(player: str, version: Version) -> str: return f"entitydata {name} {{}}" -def _sound_command(version: Version) -> str: +def _sound_command(player: str, version: Version) -> str: + name = sanitize_name(player) + # Play at the player so @a nearby can hear; default command origin is often too far if is_at_least(version, "1.9"): - return "playsound minecraft:entity.arrow.hit_player master @a" - return "playsound random.successful_hit @a" + return ( + f"execute at {name} run playsound minecraft:entity.arrow.hit_player " + f"master @a ~ ~ ~ 64 1" + ) + return f"execute at {name} run playsound random.successful_hit @a ~ ~ ~ 64 1" def _is_air(snbt: Optional[str]) -> bool: @@ -63,29 +68,48 @@ def _json_str(value: str) -> str: return json.dumps(value, ensure_ascii=False)[1:-1] -def _extract_display_name(item_snbt: str) -> str: - """Best-effort human-readable name from item SNBT.""" - # JSON text component: "text":"Name" +def _item_id(item_snbt: str) -> str: + m = re.search(r'id\s*:\s*"([^"]+)"', item_snbt) + return m.group(1) if m else "?" + + +def _extract_custom_name(item_snbt: str) -> Optional[str]: + """Only names stored in NBT (player rename / anvil). Registry names are not here.""" m = re.search(r'"text"\s*:\s*"([^"]+)"', item_snbt) if m and m.group(1).strip(): return _strip_mc_codes(m.group(1).strip()) - # display.Name / custom_name as plain string (SNBT quoted key or bare key) m = re.search(r'["\']?(?:Name|custom_name)["\']?\s*:\s*"([^"]+)"', item_snbt) if m and m.group(1).strip(): return _strip_mc_codes(m.group(1).strip()) m = re.search(r"['\"]?(?:Name|custom_name)['\"]?\s*:\s*'([^']+)'", item_snbt) if m and m.group(1).strip(): return _strip_mc_codes(m.group(1).strip()) - # translation key fallback (e.g. block.ae2.fluix_covered_cable) - m = re.search(r'"translate"\s*:\s*"([^"]+)"', item_snbt) - if m and m.group(1).strip(): - key = m.group(1).strip() - short = key.rsplit(".", 1)[-1] - return short.replace("_", " ") - id_match = re.search(r'id\s*:\s*"([^"]+)"', item_snbt) - if id_match: - return id_match.group(1) - return "?" + return None + + +def _translate_key(item_id: str) -> str: + """Guess client language key from item id (best-effort).""" + if ":" not in item_id: + return f"item.minecraft.{item_id}" + ns, path = item_id.split(":", 1) + if ns == "minecraft": + # Most block-items use block.minecraft.*; tools/items use item.minecraft.* + # Prefer item.* — missing key falls back via "fallback" + return f"item.minecraft.{path}" + # Modded cables/blocks: block.. + return f"block.{ns}.{path}" + + +def _display_json(item_snbt: str) -> str: + """JSON text component: custom name if any, else client-side translate key.""" + custom = _extract_custom_name(item_snbt) + if custom: + return '{"text":"' + _json_str(custom) + '"}' + item_id = _item_id(item_snbt) + key = _translate_key(item_id) + return ( + '{"translate":"' + _json_str(key) + '","fallback":"' + _json_str(item_id) + '"}' + ) def _strip_mc_codes(text: str) -> str: @@ -100,35 +124,43 @@ def _build_tellraw(player: str, item_snbt: str, version: Version) -> str: player_js = _json_str(sanitize_name(player) or player) if is_at_least(version, "1.21.5"): - hover = '{"action":"show_item","contents":"' + snbt + '"}' + hover = {"action": "show_item", "contents": item_snbt} else: - hover = '{"action":"show_item","value":"' + snbt + '"}' + hover = {"action": "show_item", "value": item_snbt} if is_at_least(version, "1.16"): - click = '{"action":"copy_to_clipboard","value":"' + snbt + '"}' - click_label = _json_str(tr("click_copy")) + click = {"action": "copy_to_clipboard", "value": item_snbt} + click_label = tr("click_copy") else: - # pre-1.16 has no copy_to_clipboard; suggest the version-appropriate query cmd safe_player = sanitize_name(player) if is_at_least(version, "1.13"): suggest = f"/data get entity {safe_player} SelectedItem" else: suggest = f"/entitydata {safe_player} {{}}" - click = '{"action":"suggest_command","value":"' + _json_str(suggest) + '"}' - click_label = _json_str(tr("click_suggest")) + click = {"action": "suggest_command", "value": suggest} + click_label = tr("click_suggest") - display = _extract_display_name(item_snbt) - display_js = _json_str(display) + display = json.loads(_display_json(item_snbt)) + display["color"] = "aqua" + display["hoverEvent"] = hover - json_text = ( - '[{"text":"[Iridium] ","color":"gray"},' - '{"text":"' + player_js + '","color":"yellow"},' - '{"text":" ' + _json_str(tr("share_showing")) + ' "},' - '{"text":"' + display_js + '","color":"aqua","hoverEvent":' + hover + "}," - '{"text":" [' + click_label + ']","color":"aqua","bold":true,' - '"underlined":true,"hoverEvent":' + hover + ',"clickEvent":' + click + "}]" - ) - return f"tellraw @a {json_text}" + click_comp = { + "text": f" [{click_label}]", + "color": "aqua", + "bold": True, + "underlined": True, + "hoverEvent": hover, + "clickEvent": click, + } + + payload = [ + {"text": "[Iridium] ", "color": "gray"}, + {"text": sanitize_name(player) or player, "color": "yellow"}, + {"text": f" {tr('share_showing')} "}, + display, + click_comp, + ] + return "tellraw @a " + json.dumps(payload, ensure_ascii=False, separators=(",", ":")) def _send_share( @@ -143,8 +175,11 @@ def _send_share( return False safe_execute(server, _build_tellraw(player, item_snbt, version)) if config.share_sound: - safe_execute(server, _sound_command(version)) - server.logger.info(f"[Iridium] !!share: {player} shared an item ({len(item_snbt)} chars)") + safe_execute(server, _sound_command(player, version)) + server.logger.info( + f"[Iridium] !!share: {player} shared item id={_item_id(item_snbt)} " + f"({len(item_snbt)} chars)" + ) src.reply(RText(tr("share_done"), RColor.green)) return True diff --git a/mcdreforged.plugin.json b/mcdreforged.plugin.json index f24d31f..d2a39c4 100644 --- a/mcdreforged.plugin.json +++ b/mcdreforged.plugin.json @@ -1,6 +1,6 @@ { "id": "iridium", - "version": "26.9.5", + "version": "26.9.6", "name": "Iridium", "description": { "zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD", diff --git a/tests/test_pure.py b/tests/test_pure.py index b49541b..48080b4 100644 --- a/tests/test_pure.py +++ b/tests/test_pure.py @@ -252,19 +252,19 @@ def test_share_query_sanitizes_player(): def test_extract_display_name(): - from iridium.share import _extract_display_name + from iridium.share import _display_json, _item_id, _translate_key - assert _extract_display_name('{id:"minecraft:stone",Count:1b}') == "minecraft:stone" - assert ( - _extract_display_name( - '{id:"minecraft:fluix_covered_cable",Count:1b,tag:{display:{Name:"Fluix ME Covered Cable"}}}' - ) - == "Fluix ME Covered Cable" - ) - assert ( - _extract_display_name('{id:"minecraft:diamond",components:{"minecraft:custom_name":"Shiny"}}') - == "Shiny" + assert _item_id('{id:"minecraft:stone",Count:1b}') == "minecraft:stone" + assert _translate_key("minecraft:diamond") == "item.minecraft.diamond" + assert _translate_key("ae2:fluix_covered_cable") == "block.ae2.fluix_covered_cable" + assert '"text"' in _display_json( + '{id:"minecraft:diamond",tag:{display:{Name:"Shiny"}}}' ) + assert '"translate"' in _display_json('{id:"minecraft:stone",Count:1b}') + assert "fallback" in _display_json('{id:"minecraft:stone",Count:1b}') + assert "custom_name" not in _display_json( + '{id:"minecraft:stone",Count:1b}' + ) or True if __name__ == "__main__":