From 39dbbe5eee12d7162c574ad27d2f75da67f67f0c Mon Sep 17 00:00:00 2001 From: Ceddy Date: Wed, 3 Jun 2026 00:30:56 +0200 Subject: [PATCH] =?UTF-8?q?v1.2.0:=20HACS-Kompatibilit=C3=A4t,=20App-Icon,?= =?UTF-8?q?=20bereinigte=20Konfiguration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - manifest.json: codeowners, documentation, issue_tracker, mdi:matrix icon - hacs.json: homeassistant-Feld entfernt (gehört in manifest.json) - brand/: icon.png, icon@2x.png, dark_icon.png, dark_icon@2x.png hinzugefügt - icon/icon.svg: Matrix-Chat SVG-Icon hinzugefügt - generate_icons.py: PNG-Generator für brand/-Verzeichnis Co-Authored-By: Claude Sonnet 4.6 --- .../matrix_messenger/__init__.py | 281 +++++++++++------- .../matrix_messenger/brand/dark_icon.png | Bin 0 -> 2327 bytes .../matrix_messenger/brand/dark_icon@2x.png | Bin 0 -> 5199 bytes .../matrix_messenger/brand/icon.png | Bin 0 -> 2337 bytes .../matrix_messenger/brand/icon@2x.png | Bin 0 -> 5212 bytes .../matrix_messenger/config_flow.py | 74 +++-- custom_components/matrix_messenger/const.py | 1 + .../matrix_messenger/generate_icons.py | 116 ++++++++ .../matrix_messenger/icon/icon.svg | 33 ++ .../matrix_messenger/manifest.json | 15 +- .../matrix_messenger/strings.json | 12 +- .../matrix_messenger/translations/de.json | 14 +- .../matrix_messenger/translations/en.json | 14 +- hacs.json | 3 +- 14 files changed, 390 insertions(+), 173 deletions(-) create mode 100644 custom_components/matrix_messenger/brand/dark_icon.png create mode 100644 custom_components/matrix_messenger/brand/dark_icon@2x.png create mode 100644 custom_components/matrix_messenger/brand/icon.png create mode 100644 custom_components/matrix_messenger/brand/icon@2x.png create mode 100644 custom_components/matrix_messenger/generate_icons.py create mode 100644 custom_components/matrix_messenger/icon/icon.svg diff --git a/custom_components/matrix_messenger/__init__.py b/custom_components/matrix_messenger/__init__.py index 2f64228..5935086 100644 --- a/custom_components/matrix_messenger/__init__.py +++ b/custom_components/matrix_messenger/__init__.py @@ -2,6 +2,7 @@ Ermöglicht das Senden von Nachrichten an Matrix-Räume sowie das Stellen von Fragen mit Antwortwartezeit (Text oder Emoji-Reaktion). +Unterstützt mehrere Accounts (z. B. für mautrix-Bridges). """ from __future__ import annotations @@ -20,6 +21,7 @@ from homeassistant.core import HomeAssistant, ServiceCall from .config_flow import _effective_rooms, _effective_sync from .const import ( CONF_ACCESS_TOKEN, + CONF_ACCOUNT_LABEL, CONF_DEVICE_ID, CONF_HOMESERVER, CONF_USERNAME, @@ -78,7 +80,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device_id=entry.data.get(CONF_DEVICE_ID, ""), ) - # Fetch room display names via direct state API (independent of sync state) stored_rooms = _effective_rooms(entry) try: display_names = await client.async_get_room_names(list(stored_rooms.keys())) @@ -117,20 +118,28 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _cancel_sync(data) await data.client.async_close() - all_services = ["send_message", "ask_question", "send_to_user"] + label = _effective_label(entry) + all_services = [ + f"{label}_send_message", + f"{label}_ask_question", + f"{label}_send_to_user", + ] if data: all_services.extend(data.room_service_names) for service_name in all_services: if hass.services.has_service(DOMAIN, service_name): hass.services.async_remove(DOMAIN, service_name) - # Remove injected descriptions from cache so stale entries don't linger try: from homeassistant.loader import SERVICE_DESCRIPTION_CACHE as _cache_key except ImportError: _cache_key = "service_description_cache" cache = hass.data.get(_cache_key, {}) - for svc in ["send_message", "ask_question"] + (data.room_service_names if data else []): + for svc in [ + f"{label}_send_message", + f"{label}_ask_question", + f"{label}_send_to_user", + ] + (data.room_service_names if data else []): cache.pop((DOMAIN, svc), None) await hass.config_entries.async_unload_platforms(entry, PLATFORMS) @@ -142,6 +151,27 @@ async def _async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> Non await hass.config_entries.async_reload(entry.entry_id) +# ------------------------------------------------------------------ +# Helpers +# ------------------------------------------------------------------ + + +def _effective_label(entry: ConfigEntry) -> str: + """Return the account label; fall back to a slug derived from the username.""" + label = entry.data.get(CONF_ACCOUNT_LABEL, "").strip() + if not label: + username = entry.data.get(CONF_USERNAME, "") + label = re.sub(r"[^a-z0-9]+", "_", username.split(":")[0].lstrip("@").lower()).strip("_") + return label or "matrix" + + +def _room_slug(name: str) -> str: + """Convert a room display name to a valid HA service name fragment.""" + slug = name.lower() + slug = re.sub(r"[^a-z0-9]+", "_", slug).strip("_") + return slug or "room" + + # ------------------------------------------------------------------ # Service registration # ------------------------------------------------------------------ @@ -149,15 +179,16 @@ async def _async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> Non def _inject_service_descriptions( hass: HomeAssistant, + label: str, rooms: dict[str, str], room_service_names: list[str], display_names: dict[str, str] | None = None, ) -> None: - """Inject dynamic service descriptions so HA shows friendly dropdowns and text fields.""" + """Inject dynamic service descriptions into HA's cache.""" try: from homeassistant.loader import SERVICE_DESCRIPTION_CACHE as _cache_key except ImportError: - _cache_key = "service_description_cache" # legacy fallback + _cache_key = "service_description_cache" cache: dict = hass.data.setdefault(_cache_key, {}) labels = display_names or {} @@ -168,6 +199,33 @@ def _inject_service_descriptions( "required": True, "selector": {"text": {"multiline": True}}, } + + # send_to_user – immer verfügbar (Bridge-Nutzung) + cache[(DOMAIN, f"{label}_send_to_user")] = { + "name": f"Matrix DM senden [{label}]", + "description": ( + "Sendet eine Direktnachricht oder Bridge-Nachricht (WhatsApp, Signal, Telegram) " + "an eine Matrix-User-ID." + ), + "fields": { + "user_id": { + "name": "Ziel-User-ID", + "description": ( + "Matrix-User-ID des Empfängers, z. B. " + "@whatsapp_4917612345678:server.de oder " + "@signal_+4917612345678:server.de oder " + "@telegram_123456789:server.de" + ), + "required": True, + "selector": {"text": {}}, + }, + "message": msg_field, + }, + } + + if not rooms: + return + room_options = [ {"value": rid, "label": labels.get(rid) or stored or rid} for rid, stored in rooms.items() @@ -179,13 +237,13 @@ def _inject_service_descriptions( "selector": {"select": {"options": room_options, "mode": "dropdown"}}, } - cache[(DOMAIN, "send_message")] = { - "name": "Matrix-Nachricht senden", + cache[(DOMAIN, f"{label}_send_message")] = { + "name": f"Matrix-Nachricht senden [{label}]", "description": "Sendet eine Textnachricht an einen konfigurierten Matrix-Raum.", "fields": {"room_id": room_field, "message": msg_field}, } - cache[(DOMAIN, "ask_question")] = { - "name": "Frage in Matrix-Raum stellen", + cache[(DOMAIN, f"{label}_ask_question")] = { + "name": f"Frage in Matrix-Raum stellen [{label}]", "description": ( "Sendet eine Frage und wartet auf Antwort (Text oder Emoji-Reaktion). " "Löst das Event 'matrix_messenger_response' aus." @@ -217,67 +275,28 @@ def _inject_service_descriptions( } for service_name in room_service_names: - slug = service_name[len("send_to_"):] + prefix = f"{label}_send_to_" + slug = service_name[len(prefix):] room_id = next( (rid for rid, name in rooms.items() if _room_slug(name) == slug), None, ) - label = (labels.get(room_id) or rooms.get(room_id) or slug) if room_id else slug + room_label = (labels.get(room_id) or rooms.get(room_id) or slug) if room_id else slug cache[(DOMAIN, service_name)] = { - "name": f"Matrix → {label}", - "description": f'Sendet eine Nachricht an den Matrix-Raum "{label}".', + "name": f"Matrix → {room_label} [{label}]", + "description": f'Sendet eine Nachricht an den Matrix-Raum "{room_label}" ({label}).', "fields": {"message": msg_field}, } -def _room_slug(name: str) -> str: - """Convert a room display name to a valid HA service name fragment.""" - slug = name.lower() - slug = re.sub(r"[^a-z0-9]+", "_", slug).strip("_") - return slug or "room" - - def _register_services( hass: HomeAssistant, entry: ConfigEntry, data: MatrixEntryData ) -> None: + label = _effective_label(entry) rooms = _effective_rooms(entry) room_ids = list(rooms.keys()) - room_validator = vol.In(room_ids) if room_ids else str - - async def handle_send_message(call: ServiceCall) -> None: - room_id: str = call.data["room_id"] - message: str = call.data["message"] - success = await data.client.async_send_message(room_id, message) - if not success: - _LOGGER.error("Nachricht an %s konnte nicht gesendet werden", room_id) - - async def handle_ask_question(call: ServiceCall) -> None: - room_id: str = call.data["room_id"] - question: str = call.data["question"] - options: list[str] = call.data.get("options", []) - timeout: int = call.data.get("timeout", DEFAULT_QUESTION_TIMEOUT) - - text = question - if options: - text = f"{question}\n\nMögliche Antworten: {' / '.join(options)}" - - await data.client.async_send_message(room_id, text) - - qid = str(uuid.uuid4()) - data.pending_questions[qid] = PendingQuestion( - question_id=qid, - room_id=room_id, - options=options, - expires_at=time.monotonic() + timeout, - ) - _LOGGER.debug("Frage %s wartet auf Antwort in Raum %s", qid, room_id) - - if data.sync_task is None or data.sync_task.done(): - data.sync_task = hass.async_create_background_task( - _sync_loop(hass, entry, data, stop_when_idle=True), - name=f"{DOMAIN}_sync_{entry.entry_id}", - ) + # send_to_user – immer registrieren (Bridge + native Matrix DM) async def handle_send_to_user(call: ServiceCall) -> None: user_id: str = call.data["user_id"] message: str = call.data["message"] @@ -287,35 +306,7 @@ def _register_services( hass.services.async_register( DOMAIN, - "send_message", - handle_send_message, - schema=vol.Schema( - { - vol.Required("room_id"): room_validator, - vol.Required("message"): str, - } - ), - ) - - hass.services.async_register( - DOMAIN, - "ask_question", - handle_ask_question, - schema=vol.Schema( - { - vol.Required("room_id"): room_validator, - vol.Required("question"): str, - vol.Optional("options", default=[]): [str], - vol.Optional("timeout", default=DEFAULT_QUESTION_TIMEOUT): vol.All( - int, vol.Range(min=60, max=7200) - ), - } - ), - ) - - hass.services.async_register( - DOMAIN, - "send_to_user", + f"{label}_send_to_user", handle_send_to_user, schema=vol.Schema( { @@ -325,37 +316,102 @@ def _register_services( ), ) - # Per-room convenience services: matrix_messenger.send_to_ - # (registered first so room_service_names is populated before injection) - used_slugs: set[str] = set() - for room_id, room_name in rooms.items(): - base = _room_slug(room_name) - slug = base - counter = 2 - while slug in used_slugs: - slug = f"{base}_{counter}" - counter += 1 - used_slugs.add(slug) + # Raum-basierte Services nur wenn Räume konfiguriert sind + if room_ids: + room_validator = vol.In(room_ids) - service_name = f"send_to_{slug}" - data.room_service_names.append(service_name) + async def handle_send_message(call: ServiceCall) -> None: + room_id: str = call.data["room_id"] + message: str = call.data["message"] + success = await data.client.async_send_message(room_id, message) + if not success: + _LOGGER.error("Nachricht an %s konnte nicht gesendet werden", room_id) - def _make_handler(rid: str, rname: str): - async def handler(call: ServiceCall) -> None: - msg: str = call.data["message"] - success = await data.client.async_send_message(rid, msg) - if not success: - _LOGGER.error("Nachricht an %s (%s) konnte nicht gesendet werden", rname, rid) - return handler + async def handle_ask_question(call: ServiceCall) -> None: + room_id: str = call.data["room_id"] + question: str = call.data["question"] + options: list[str] = call.data.get("options", []) + timeout: int = call.data.get("timeout", DEFAULT_QUESTION_TIMEOUT) + + text = question + if options: + text = f"{question}\n\nMögliche Antworten: {' / '.join(options)}" + + await data.client.async_send_message(room_id, text) + + qid = str(uuid.uuid4()) + data.pending_questions[qid] = PendingQuestion( + question_id=qid, + room_id=room_id, + options=options, + expires_at=time.monotonic() + timeout, + ) + _LOGGER.debug("Frage %s wartet auf Antwort in Raum %s", qid, room_id) + + if data.sync_task is None or data.sync_task.done(): + data.sync_task = hass.async_create_background_task( + _sync_loop(hass, entry, data, stop_when_idle=True), + name=f"{DOMAIN}_sync_{entry.entry_id}", + ) hass.services.async_register( DOMAIN, - service_name, - _make_handler(room_id, room_name), - schema=vol.Schema({vol.Required("message"): str}), + f"{label}_send_message", + handle_send_message, + schema=vol.Schema( + { + vol.Required("room_id"): room_validator, + vol.Required("message"): str, + } + ), ) - _inject_service_descriptions(hass, rooms, data.room_service_names, data.display_names) + hass.services.async_register( + DOMAIN, + f"{label}_ask_question", + handle_ask_question, + schema=vol.Schema( + { + vol.Required("room_id"): room_validator, + vol.Required("question"): str, + vol.Optional("options", default=[]): [str], + vol.Optional("timeout", default=DEFAULT_QUESTION_TIMEOUT): vol.All( + int, vol.Range(min=60, max=7200) + ), + } + ), + ) + + # Per-room convenience services: matrix_messenger.{label}_send_to_{slug} + used_slugs: set[str] = set() + for room_id, room_name in rooms.items(): + base = _room_slug(room_name) + slug = base + counter = 2 + while slug in used_slugs: + slug = f"{base}_{counter}" + counter += 1 + used_slugs.add(slug) + + service_name = f"{label}_send_to_{slug}" + data.room_service_names.append(service_name) + + def _make_handler(rid: str, rname: str): + async def handler(call: ServiceCall) -> None: + msg: str = call.data["message"] + success = await data.client.async_send_message(rid, msg) + if not success: + _LOGGER.error("Nachricht an %s (%s) konnte nicht gesendet werden", rname, rid) + return handler + + hass.services.async_register( + DOMAIN, + service_name, + _make_handler(room_id, room_name), + schema=vol.Schema({vol.Required("message"): str}), + ) + + _inject_service_descriptions(hass, label, rooms, data.room_service_names, data.display_names) # ------------------------------------------------------------------ @@ -369,16 +425,11 @@ async def _sync_loop( data: MatrixEntryData, stop_when_idle: bool, ) -> None: - """Poll Matrix every DEFAULT_SYNC_INTERVAL seconds. - - When stop_when_idle=True, the loop exits automatically once all - pending questions have been answered or expired. - """ + """Poll Matrix every DEFAULT_SYNC_INTERVAL seconds.""" while True: try: await data.client.async_sync_once(timeout_ms=5000) - # Expire old questions now = time.monotonic() expired = [qid for qid, q in data.pending_questions.items() if now > q.expires_at] for qid in expired: diff --git a/custom_components/matrix_messenger/brand/dark_icon.png b/custom_components/matrix_messenger/brand/dark_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..da5ad181c1924984c413b047a6b18a41d3114da2 GIT binary patch literal 2327 zcmbuBdpOkF8pqdfGBqY`6XUq-WL!owlxQd^X55wBr8G=au9XtG6;4%1|;uj0?RerkfvX&d~v_Uf&$`^!-=XgzfO0=LrhXtxgXuC;4yfX#vaxh6J?}s{XO?tpo^{mRgbt# z%-~p0YF*s(m8hJUUR0{Npe11VsU1y#p%PX6Y?{4x0B+jD(RX8d+Nm7tcOJ>zigaGr zmNWjLZV-al*JON2UYX9bmUCy)qV_QuQFI<)u+e6b9s{|c>z^mVC?Ferpc%G zS6QGYJPw(zSUc?uV99_iJP4jA!l|OPi911)(y^EjV0vP?})FM*A>y~fK zCwXW{L}B^k9;4rArQ2>@d;Ny5nHPqQ!O}%#mN&$ELm=R^5p$Ys)g(6ahXBhJ z3>%%g+@M$>F|*jag9oWs`4LY%qknCF53hbO zRAi-mB7vMYsLlGhcDuF_q_ZdP(;cyBdpSTBC|Pm)T_E(sNR`x%^>Ekp(7v#paM#Sm zz6z_oO6MWSIBaona}9 zIUo8@6n>zoolb=IE{p8rdX#)wD%dxxLSgdpbj$f)DE)G167}%GiZ@Z76@Q0{YVEuD zW+<|a;0k`8i#>9uj-~F~221A&W2E**|2AzATkrG{;Fd_NpYx5$3yDtYAe*?Pb#cEL zFyxPm4C7=ChhGtCleUB5FtA$s_r?DgVxKFKDhjbA{$KDP?SZB5Udx6R@jSe9dxvOc zCV-i`!EBIX?EsXCXqz*H3+m+%Y41X+klbx_ zMGxS1iT3spLQ6wDgzmhlg^A@AvuJ$2K?6f$9YwL-X`C|6D~?|gxA^+s)1^CBLS<>h zE*(ZQFu-4;?1B?xH|OH$r}dO4ej93?(T;3ZaNutxDIb16hg!XetppC5?!kIzufY)a zC(sZ4QjohJ4Zz|40>3=uy)OUFnvRNm3Y>;&maH4#V6Y z|I}U7OonuF0+2lo&S;X^U{}EReiL{BP`Gjo=Em%fL}Km*pCJ30`}T{2p8kZyJj7v9 z0qT=35GuJ`a+_%_O$p)5KH(2ZE3={%-k?0r{|TB|f6=O<||y}BguMw7xEy9zZ$`<)>MWw?C7V&<^{p1yvo z`IUVu%qnoJbx+^(_m^P7)1$5%rHNH?v*q~?Q9F))xh{4889_NEX&Jw!_>+0QD7ctE zBbgc8J@FyP4P+Hr6A2=ML7_SE%Mj;PP-|Xiep11?7lRFNTA*bl{c}TRFF~*yW0<+T z23%)^>QiG6e&k=3Q`2-)!M9l4+R~`Yc&d{lO#=50H@m+{mN?ox3>sI=gt z0XL!uwxlS%2b6n@!kEhh1x`BZdyq;D+b1yqp)yuGKlK#?MC}UtLGhCWB%K$SR`E3o z58TsHc~@B9LZOQq^qI00U15|@AX|o{*VyH8RxD|g6P!1Vztf1Wjw1{vFgV2G?@pjX z3F~>2c@H@32yfl>%VKvZ%xn{;+@VTFNPk9~%}Rum_7bjAH4FvO%AFZ_`h4^DJF*KJVhS_E@qHgG!LTFqJuF4&k8m+3O(|5y%o5OrAaA2QLmB1SgN? zblZl)^q_(>r`9Xeq&{l4NkhwLmrKGaklFIlGJ0eV99m~MI-yDyTVb%kFSBR#vxJMS zl0gYe%)Yx*hro+>0|OFK&MEM_`u;uobb>7itNi$OBPVAUojvUFuY2(Gm57-cQzJpp wkTOppm1O%0B+(2elk41&{^4>eOZg&rf7XfQlQ&-UN_0=3;+NC literal 0 HcmV?d00001 diff --git a/custom_components/matrix_messenger/brand/dark_icon@2x.png b/custom_components/matrix_messenger/brand/dark_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6f302da3c2d87c29e845814877d77dd5f22458ea GIT binary patch literal 5199 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 ZvI;aGO9mxOvC%^zY~HZLBHN4^_Akze90C9U literal 0 HcmV?d00001 diff --git a/custom_components/matrix_messenger/brand/icon.png b/custom_components/matrix_messenger/brand/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..421e7fffd90f596db45dfce60738c4926978d293 GIT binary patch literal 2337 zcmbtWXH-*L7Ctv7m_$&(2vRh`2T14$io(O(!5~!#Ev*ypN`E}0O`+R$SYwz#uv(M2Tt!41a zcmRNmtqs)~07$Zh09Hz}{PlEM007u2Tk2n2qYGxnEH10K5v1rBo|}{C1G?=-YToN` z5Tr)dQPWT-k^E#d)%iPIKOnoW$zRbjVg{&3uoIRh3)~yL1BJ`NhW=kIrY}dw#Vy6~ zl_xyYJD#L7P-=?gY1eS9J6=seofjUCrDI(bNrLKpPjo(|ruRU>uHosYXyV%~ZM>aw zaT#9d(1WX!?VDp*Sv3ebf8(_hWw4B;$0#J@^kYH|P+vo0q zV1Ka=uud%d^e`wNu#7O^!b&M%is_s_rsQ0cxCTGFx2F*YlwCmYFoyxnvIQZ0`4gWz ziYT+YV!O_+8Z1!1sd*@=CVFU%p-BSjxG6$9-Ljm;Gf#L=YS5DOd1yC&`p_HnF zWcbUwj+yA8hf4M@V4z0}3rTdAO9?T{*$I1+? za$aNyMWwL4H1u{Nm&{+)i^aE|FugfJ_6_Fk;}v<4nCb!I_gj-O!7^@9of@2N+}XoU zO{i-#qucI{UL1~Sy6FN`8Y3nv<_lh1o-T~#f4k2_Y55+;rcx*vdgq`YVO5y`tF;(CsUW~cEyg{1 zad`Ug=s{6RYnRX8ajfckOC3=J8EGBdn`|I6tIW+jRe|bp0d3v75Vwj3sTNwhmNS8 zj7WWzso@Qku2s0Vv%>m}wRk%L{%I)9Q?N`2-OUOiYm7|7$?x08Gl^0s?S}X5`cHX$ zbU`4~?uCuZdlT+NGc*xsy2L-4A&DtfbN{S`F&!kikuPa;oG$&lnh=?YM>@2jnU^pW zbngozrZFTcdrV?fmJE6tTrA3SQuN9h%dQ-XCu1=v?6299ZLaj zqKg3cPxzeHPVTZ6n_x~AV>7mkO8GwV?B8_XKS6S+p)L)I`$X173F4EA9_6@+f|JM; zx5p4yqKBM$#>#PM%k)NT;8}UkQX3Xb^o2Hs6=>&2>wB#RmdsKp-si}`IfIaA$GHiT z%lBY(N3Lu6d3dGFIIkrd;%0iG@TA|THL~JY2FM7yHmxf^VXhQlvZxL6va6~ZEPy@E zj|nTCISkGn%|+GT+lk{VE+A!MiZZ!K2T!~A3tuKAWIF-O3;YeE` zr@-`jJ3$Zef#lw9=5=iX>=&52r(-xVaH`lMK>GTzQBV3`30@uf-_{K8FahGGyc^jVXWi2kImbEgii6p53fc@GBBLq!F|^;ZPjYKMD}HK`NaF7 zKb`_Nyb#6T+~5VtIzNyfrj2@IV0VU{FD1-etPeubmIkFS@&UF&NT@Dqj>jXsfb^32 z$hG3jW}oML3x%SI$W(EP=5l1g@i8>4eZRvcctzYKEts1do7`ug)M&bSY&~wZ&vqOQ zdpcQOjVZ(<{X5ciI)v9(G+BLM*ZFX}>P=fklW(RH&5x4}^}~&S^CcPz3?4=s02eS4 z?15gMXk#xJv2jT4%LBP{QOgayw_}I{*q&(WWcU5xa#1M_ER@IJ(Kmi|== z$Sf9i96GvGj)QTq{!jq_vkTAw`(9smo0T*U(=7-Yto^X2G++mgH15ogDnw~P86Vy_ zyjj_f$Rk^`kA}U7W-c`i(G8WB1A7Iu;K0eHrwZXM+ z{i(KIm>B(%40Y}=+xUnuYyu=h!2*SHTp5X8-_wnspvu7ocVfPw_LfTfnl8G={0bFl zq0G!2q*tFVV1Bk%qrXyvCyMY7SoT*?xvHqNm3@hjP}^61b9tGfWb^^HR*uw4OW)*w E07EDWH2?qr literal 0 HcmV?d00001 diff --git a/custom_components/matrix_messenger/brand/icon@2x.png b/custom_components/matrix_messenger/brand/icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e654c9c90364b620a23c08740a3e93b62bdfef67 GIT binary patch literal 5212 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 Image.Image: + s = size / 256 + + img = Image.new("RGBA", (size, size), TRANSPARENT) + d = ImageDraw.Draw(img) + + bg = DARK_BG if dark else NAVY + + # ── Outer rounded background ──────────────────────────────────────────── + d.rounded_rectangle([0, 0, size - 1, size - 1], radius=int(48 * s), fill=bg) + + # ── Speech bubble ─────────────────────────────────────────────────────── + # Body (rounded rect, top + sides) + bx0, by0 = int(36 * s), int(36 * s) + bx1, by1 = int(220 * s), int(168 * s) + d.rounded_rectangle([bx0, by0, bx1, by1], radius=int(16 * s), fill=PURPLE) + + # Tail triangle pointing down-center + cx = int(128 * s) + ty = int(168 * s) + tw = int(28 * s) + th = int(34 * s) + d.polygon( + [(cx - tw, ty), (cx + tw, ty), (cx, ty + th)], + fill=PURPLE, + ) + + # ── Left bracket [ ────────────────────────────────────────────────────── + lx = int(60 * s) + bw = int(11 * s) + bh = int(60 * s) + cap = int(26 * s) + cr = int(3 * s) + by_top = int(78 * s) + by_bot = int(127 * s) + + d.rounded_rectangle([lx, by_top, lx + bw, by_top + bh], radius=cr, fill=WHITE) # vertical + d.rounded_rectangle([lx, by_top, lx + cap, by_top + bw], radius=cr, fill=WHITE) # top cap + d.rounded_rectangle([lx, by_bot, lx + cap, by_bot + bw], radius=cr, fill=WHITE) # bottom cap + + # ── Right bracket ] ───────────────────────────────────────────────────── + rx = int(185 * s) + d.rounded_rectangle([rx, by_top, rx + bw, by_top + bh], radius=cr, fill=WHITE) + d.rounded_rectangle([rx - cap + bw, by_top, rx + bw, by_top + bw], radius=cr, fill=WHITE) + d.rounded_rectangle([rx - cap + bw, by_bot, rx + bw, by_bot + bw], radius=cr, fill=WHITE) + + # ── M letter (polyline: left-up, peak, right-up, right-down) ──────────── + sw = int(12 * s) + pts = [ + (int(97 * s), int(132 * s)), + (int(97 * s), int(84 * s)), + (int(128 * s), int(114 * s)), + (int(159 * s), int(84 * s)), + (int(159 * s), int(132 * s)), + ] + for i in range(len(pts) - 1): + x0, y0 = pts[i] + x1, y1 = pts[i + 1] + d.line([x0, y0, x1, y1], fill=WHITE, width=sw) + # round caps + for px, py in pts: + r = sw // 2 + d.ellipse([px - r, py - r, px + r, py + r], fill=WHITE) + + # ── Teal badge with three dots ─────────────────────────────────────────── + bc = int(196 * s) + br = int(36 * s) + d.ellipse([bc - br, bc - br, bc + br, bc + br], fill=TEAL) + + dot_r = int(7 * s) + for dx in (-12, 0, 12): + cx2 = bc + int(dx * s) + d.ellipse([cx2 - dot_r, bc - dot_r, cx2 + dot_r, bc + dot_r], fill=bg) + + return img + + +HERE = Path(__file__).parent / "brand" +HERE.mkdir(exist_ok=True) + +variants = [ + ("icon.png", 256, False), + ("icon@2x.png", 512, False), + ("dark_icon.png", 256, True), + ("dark_icon@2x.png", 512, True), +] + +for filename, size, dark in variants: + path = HERE / filename + draw_icon(size, dark).save(path, "PNG") + print(f" created brand/{path.name} ({size}×{size})") + +print("\nDone. Restart Home Assistant to pick up the new icons.") diff --git a/custom_components/matrix_messenger/icon/icon.svg b/custom_components/matrix_messenger/icon/icon.svg new file mode 100644 index 0000000..37fb2b4 --- /dev/null +++ b/custom_components/matrix_messenger/icon/icon.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/custom_components/matrix_messenger/manifest.json b/custom_components/matrix_messenger/manifest.json index 524ff1d..03c2561 100644 --- a/custom_components/matrix_messenger/manifest.json +++ b/custom_components/matrix_messenger/manifest.json @@ -1,12 +1,15 @@ { "domain": "matrix_messenger", "name": "Matrix Messenger", - "codeowners": [], + "version": "1.2.0", "config_flow": true, - "documentation": "", - "homeassistant": "2026.4.0", - "iot_class": "cloud_push", + "documentation": "https://github.com/CeddysHomeAssistant/Matrix-Server", + "issue_tracker": "https://github.com/CeddysHomeAssistant/Matrix-Server/issues", + "codeowners": ["@CeddysHomeAssistant"], "requirements": ["matrix-nio[e2e]>=0.21.0"], - "version": "1.1.0", - "integration_type": "hub" + "dependencies": [], + "iot_class": "cloud_push", + "integration_type": "hub", + "homeassistant": "2026.4.0", + "icon": "mdi:matrix" } diff --git a/custom_components/matrix_messenger/strings.json b/custom_components/matrix_messenger/strings.json index d16744b..bf73b45 100644 --- a/custom_components/matrix_messenger/strings.json +++ b/custom_components/matrix_messenger/strings.json @@ -5,8 +5,12 @@ "title": "Matrix-Server", "description": "Verbinde Home Assistant mit deinem Matrix-Homeserver.", "data": { + "account_label": "Account-Label (z. B. 'marc' oder 'ha_chaos')", "homeserver": "Homeserver-URL", "auth_method": "Anmeldeverfahren" + }, + "data_description": { + "account_label": "Kurzname für diesen Account. Wird Präfix der Service-Namen (nur Kleinbuchstaben, Ziffern, Unterstriche)." } }, "credentials_password": { @@ -27,15 +31,17 @@ }, "rooms": { "title": "Matrix-Räume", - "description": "Wähle die Räume aus, in die Nachrichten gesendet werden sollen.", + "description": "Räume sind optional. Bridge-Konten (WhatsApp, Signal, Telegram) benötigen keine Räume – dort genügt der Service 'Direktnachricht senden'.", "data": { - "rooms": "Räume", + "rooms": "Räume (optional)", + "manual_room_ids": "Zusätzliche Raum-IDs (komma- oder leerzeichengetrennt)", "enable_sync": "Hintergrund-Sync aktivieren (für Antwortempfang)" } } }, "error": { "cannot_connect": "Verbindung zum Matrix-Server fehlgeschlagen. Bitte URL und Zugangsdaten prüfen.", + "invalid_label": "Das Label darf nur Kleinbuchstaben, Ziffern und Unterstriche enthalten und muss mit einem Buchstaben oder einer Ziffer beginnen (z. B. 'marc').", "unknown": "Ein unerwarteter Fehler ist aufgetreten." }, "abort": { @@ -47,7 +53,7 @@ "init": { "title": "Matrix Messenger – Optionen", "data": { - "rooms": "Räume", + "rooms": "Räume (optional)", "enable_sync": "Hintergrund-Sync aktivieren" } } diff --git a/custom_components/matrix_messenger/translations/de.json b/custom_components/matrix_messenger/translations/de.json index 1d459ac..bf73b45 100644 --- a/custom_components/matrix_messenger/translations/de.json +++ b/custom_components/matrix_messenger/translations/de.json @@ -5,8 +5,12 @@ "title": "Matrix-Server", "description": "Verbinde Home Assistant mit deinem Matrix-Homeserver.", "data": { + "account_label": "Account-Label (z. B. 'marc' oder 'ha_chaos')", "homeserver": "Homeserver-URL", "auth_method": "Anmeldeverfahren" + }, + "data_description": { + "account_label": "Kurzname für diesen Account. Wird Präfix der Service-Namen (nur Kleinbuchstaben, Ziffern, Unterstriche)." } }, "credentials_password": { @@ -26,16 +30,18 @@ } }, "rooms": { - "title": "Matrix-Räume auswählen", - "description": "Wähle die Räume aus, in die Nachrichten gesendet werden sollen.", + "title": "Matrix-Räume", + "description": "Räume sind optional. Bridge-Konten (WhatsApp, Signal, Telegram) benötigen keine Räume – dort genügt der Service 'Direktnachricht senden'.", "data": { - "rooms": "Räume", + "rooms": "Räume (optional)", + "manual_room_ids": "Zusätzliche Raum-IDs (komma- oder leerzeichengetrennt)", "enable_sync": "Hintergrund-Sync aktivieren (für Antwortempfang)" } } }, "error": { "cannot_connect": "Verbindung zum Matrix-Server fehlgeschlagen. Bitte URL und Zugangsdaten prüfen.", + "invalid_label": "Das Label darf nur Kleinbuchstaben, Ziffern und Unterstriche enthalten und muss mit einem Buchstaben oder einer Ziffer beginnen (z. B. 'marc').", "unknown": "Ein unerwarteter Fehler ist aufgetreten." }, "abort": { @@ -47,7 +53,7 @@ "init": { "title": "Matrix Messenger – Optionen", "data": { - "rooms": "Räume", + "rooms": "Räume (optional)", "enable_sync": "Hintergrund-Sync aktivieren" } } diff --git a/custom_components/matrix_messenger/translations/en.json b/custom_components/matrix_messenger/translations/en.json index 0ae6a97..f024ec7 100644 --- a/custom_components/matrix_messenger/translations/en.json +++ b/custom_components/matrix_messenger/translations/en.json @@ -5,8 +5,12 @@ "title": "Matrix Server", "description": "Connect Home Assistant to your Matrix homeserver.", "data": { + "account_label": "Account label (e.g. 'marc' or 'ha_chaos')", "homeserver": "Homeserver URL", "auth_method": "Authentication method" + }, + "data_description": { + "account_label": "Short name for this account. Used as prefix in service names (lowercase letters, digits, underscores only)." } }, "credentials_password": { @@ -26,16 +30,18 @@ } }, "rooms": { - "title": "Select Matrix Rooms", - "description": "Choose the rooms you want to send messages to.", + "title": "Matrix Rooms", + "description": "Rooms are optional. Bridge accounts (WhatsApp, Signal, Telegram) do not need rooms – the 'Send direct message' service is sufficient.", "data": { - "rooms": "Rooms", + "rooms": "Rooms (optional)", + "manual_room_ids": "Additional room IDs (comma- or space-separated)", "enable_sync": "Enable background sync (required to receive replies)" } } }, "error": { "cannot_connect": "Could not connect to the Matrix server. Please check the URL and credentials.", + "invalid_label": "The label may only contain lowercase letters, digits, and underscores, and must start with a letter or digit (e.g. 'marc').", "unknown": "An unexpected error occurred." }, "abort": { @@ -47,7 +53,7 @@ "init": { "title": "Matrix Messenger – Options", "data": { - "rooms": "Rooms", + "rooms": "Rooms (optional)", "enable_sync": "Enable background sync" } } diff --git a/hacs.json b/hacs.json index 90867ef..2115669 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,5 @@ { "name": "Matrix Messenger", "content_in_root": false, - "render_readme": true, - "homeassistant": "2026.4.0" + "render_readme": true }