修复:!!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 的重要变更。
## [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
+1 -1
View File
@@ -4,7 +4,7 @@ MCDReforged 插件:物品展示 / 戴头 / 获取头颅 / 游戏内计算器 /
| | |
|---|---|
| 版本 | **26.9.5** |
| 版本 | **26.9.6** |
| 作者 | 云镜之端/SKYMirror |
| 许可 | 保留所有权益(All Rights Reserved) |
| 前置 | MCDReforged `>= 2.6.0` |
+72 -37
View File
@@ -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.<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:
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"id": "iridium",
"version": "26.9.5",
"version": "26.9.6",
"name": "Iridium",
"description": {
"zh_cn": "物品展示 / 戴头 / 获取头颅 / 游戏内计算器 / 进服 MOTD",
+11 -11
View File
@@ -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__":