修复:!!share 使用 translate 组件;提示音在玩家位置播放 (26.9.6)

This commit is contained in:
SKYMirror committed 2026-09-13 03:45:21 +08:00
1 parent 8881af586b
commit 8c52e5f1bd
5 files changed
+91 -50

No files matched your search

+6
View File
@@ -2,6 +2,12 @@
本文件记录 Iridium 的重要变更。 本文件记录 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 ## [26.9.5] - 2026-09-13
### Fixed ### Fixed
+1 -1
View File
@@ -4,7 +4,7 @@ MCDReforged 插件:物品展示 / 戴头 / 获取头颅 / 游戏内计算器 /
| | | | | |
|---|---| |---|---|
| 版本 | **26.9.5** | | 版本 | **26.9.6** |
| 作者 | 云镜之端/SKYMirror | | 作者 | 云镜之端/SKYMirror |
| 许可 | 保留所有权益(All Rights Reserved) | | 许可 | 保留所有权益(All Rights Reserved) |
| 前置 | MCDReforged `>= 2.6.0` | | 前置 | MCDReforged `>= 2.6.0` |
+72 -37
View File
@@ -44,10 +44,15 @@ def _query_command(player: str, version: Version) -> str:
return f"entitydata {name} {{}}" 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"): if is_at_least(version, "1.9"):
return "playsound minecraft:entity.arrow.hit_player master @a" return (
return "playsound random.successful_hit @a" 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: 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] return json.dumps(value, ensure_ascii=False)[1:-1]
def _extract_display_name(item_snbt: str) -> str: def _item_id(item_snbt: str) -> str:
"""Best-effort human-readable name from item SNBT.""" m = re.search(r'id\s*:\s*"([^"]+)"', item_snbt)
# JSON text component: "text":"Name" 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) m = re.search(r'"text"\s*:\s*"([^"]+)"', item_snbt)
if m and m.group(1).strip(): if m and m.group(1).strip():
return _strip_mc_codes(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) m = re.search(r'["\']?(?:Name|custom_name)["\']?\s*:\s*"([^"]+)"', item_snbt)
if m and m.group(1).strip(): if m and m.group(1).strip():
return _strip_mc_codes(m.group(1).strip()) return _strip_mc_codes(m.group(1).strip())
m = re.search(r"['\"]?(?:Name|custom_name)['\"]?\s*:\s*'([^']+)'", item_snbt) m = re.search(r"['\"]?(?:Name|custom_name)['\"]?\s*:\s*'([^']+)'", item_snbt)
if m and m.group(1).strip(): if m and m.group(1).strip():
return _strip_mc_codes(m.group(1).strip()) return _strip_mc_codes(m.group(1).strip())
# translation key fallback (e.g. block.ae2.fluix_covered_cable) return None
m = re.search(r'"translate"\s*:\s*"([^"]+)"', item_snbt)
if m and m.group(1).strip():
key = m.group(1).strip() def _translate_key(item_id: str) -> str:
short = key.rsplit(".", 1)[-1] """Guess client language key from item id (best-effort)."""
return short.replace("_", " ") if ":" not in item_id:
id_match = re.search(r'id\s*:\s*"([^"]+)"', item_snbt) return f"item.minecraft.{item_id}"
if id_match: ns, path = item_id.split(":", 1)
return id_match.group(1) if ns == "minecraft":
return "?" # 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.<mod>.<path>
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: 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) player_js = _json_str(sanitize_name(player) or player)
if is_at_least(version, "1.21.5"): if is_at_least(version, "1.21.5"):
hover = '{"action":"show_item","contents":"' + snbt + '"}' hover = {"action": "show_item", "contents": item_snbt}
else: else:
hover = '{"action":"show_item","value":"' + snbt + '"}' hover = {"action": "show_item", "value": item_snbt}
if is_at_least(version, "1.16"): if is_at_least(version, "1.16"):
click = '{"action":"copy_to_clipboard","value":"' + snbt + '"}' click = {"action": "copy_to_clipboard", "value": item_snbt}
click_label = _json_str(tr("click_copy")) click_label = tr("click_copy")
else: else:
# pre-1.16 has no copy_to_clipboard; suggest the version-appropriate query cmd
safe_player = sanitize_name(player) safe_player = sanitize_name(player)
if is_at_least(version, "1.13"): if is_at_least(version, "1.13"):
suggest = f"/data get entity {safe_player} SelectedItem" suggest = f"/data get entity {safe_player} SelectedItem"
else: else:
suggest = f"/entitydata {safe_player} {{}}" suggest = f"/entitydata {safe_player} {{}}"
click = '{"action":"suggest_command","value":"' + _json_str(suggest) + '"}' click = {"action": "suggest_command", "value": suggest}
click_label = _json_str(tr("click_suggest")) click_label = tr("click_suggest")
display = _extract_display_name(item_snbt) display = json.loads(_display_json(item_snbt))
display_js = _json_str(display) display["color"] = "aqua"
display["hoverEvent"] = hover
json_text = ( click_comp = {
'[{"text":"[Iridium] ","color":"gray"},' "text": f" [{click_label}]",
'{"text":"' + player_js + '","color":"yellow"},' "color": "aqua",
'{"text":" ' + _json_str(tr("share_showing")) + ' "},' "bold": True,
'{"text":"' + display_js + '","color":"aqua","hoverEvent":' + hover + "}," "underlined": True,
'{"text":" [' + click_label + ']","color":"aqua","bold":true,' "hoverEvent": hover,
'"underlined":true,"hoverEvent":' + hover + ',"clickEvent":' + click + "}]" "clickEvent": click,
) }
return f"tellraw @a {json_text}"
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( def _send_share(
@@ -143,8 +175,11 @@ def _send_share(
return False return False
safe_execute(server, _build_tellraw(player, item_snbt, version)) safe_execute(server, _build_tellraw(player, item_snbt, version))
if config.share_sound: if config.share_sound:
safe_execute(server, _sound_command(version)) safe_execute(server, _sound_command(player, version))
server.logger.info(f"[Iridium] !!share: {player} shared an item ({len(item_snbt)} chars)") 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)) src.reply(RText(tr("share_done"), RColor.green))
return True return True
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"id": "iridium", "id": "iridium",
"version": "26.9.5", "version": "26.9.6",
"name": "Iridium", "name": "Iridium",
"description": { "description": {
"zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD", "zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD",
+11 -11
View File
@@ -252,19 +252,19 @@ def test_share_query_sanitizes_player():
def test_extract_display_name(): 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 _item_id('{id:"minecraft:stone",Count:1b}') == "minecraft:stone"
assert ( assert _translate_key("minecraft:diamond") == "item.minecraft.diamond"
_extract_display_name( assert _translate_key("ae2:fluix_covered_cable") == "block.ae2.fluix_covered_cable"
'{id:"minecraft:fluix_covered_cable",Count:1b,tag:{display:{Name:"Fluix ME Covered Cable"}}}' assert '"text"' in _display_json(
) '{id:"minecraft:diamond",tag:{display:{Name:"Shiny"}}}'
== "Fluix ME Covered Cable"
)
assert (
_extract_display_name('{id:"minecraft:diamond",components:{"minecraft:custom_name":"Shiny"}}')
== "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__": if __name__ == "__main__":