mirror of
https://gitverse.ru/kpa39l/icq.git
synced 2026-09-29 09:55:11 +00:00
feat: OMEMO по умолчанию, admin_web+BOSH, MUC-модули (vcard_muc, muc_moderation), модули из apt в modules/, scripts/omemo_check.py, backup.sh на Яндекс.Диск
- index.html: omemo_default=true (XEP-0384, встроен в Converse v14) - prosody.cfg.lua: admin_web + bosh (глобальные), MUC: vcard_muc (XEP-0153) + muc_moderation (XEP-0425) - modules/: mod_admin_web (+www_files), mod_http_upload_external, mod_muc_moderation, mod_vcard_muc — из prosody-modules (apt) - scripts/omemo_check.py — проверка OMEMO-бандлов - backup.sh — ежедневный бэкап проекта (data/config/certs/modules/webchat/доки) на /mnt/yandex-disk, ротация 7/30 дней - STATUS/WALKTHROUGH: OMEMO, admin_web, HTTP Upload итоги
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Проверка OMEMO-связки на сервере nixg.ru (XEP-0384, PEP).
|
||||
|
||||
Что делает:
|
||||
- логинится admin@nixg.ru по WebSocket (wss://xmpp.nixg.ru/xmpp-websocket);
|
||||
- читает OMEMO devicelist (eu.siacs.conversations.axolotl.devicelist);
|
||||
- по желанию публикует новое устройство (--publish <id>);
|
||||
- по желанию чистит список (--clean, оставляет только реальные устройства).
|
||||
|
||||
Использование:
|
||||
PASSWORD=... /opt/icq/.venv/bin/python /opt/icq/scripts/omemo_check.py [--publish 7777] [--clean]
|
||||
JID=admin@nixg.ru (по умолчанию) — можно переопределить env JID.
|
||||
|
||||
Зависимости: venv /opt/icq/.venv (slixmpp 1.17).
|
||||
Служебные находки (проверено 2026-08-29):
|
||||
- slixmpp 1.17: connect((url,)) сам управляет циклом, process() НЕТ;
|
||||
- плагины регистрировать явно: register_plugin('xep_0060');
|
||||
- publish payload = XML-элемент (slixmpp.xmlstream.ET), не строка;
|
||||
- get_items через WS иногда возвращает пусто — смотреть запись на диске
|
||||
data/nixg%2eru/pep_eu%2esiacs%2econversations%2eaxolotl%2edevicelist/admin.list
|
||||
"""
|
||||
import asyncio, os, ssl, sys
|
||||
sys.path.insert(0, '/opt/icq/.venv/lib/python3.12/site-packages')
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.xmlstream import ET
|
||||
|
||||
JID = os.environ.get('JID', 'admin@nixg.ru')
|
||||
PASSWORD = os.environ.get('PASSWORD', '')
|
||||
if not PASSWORD:
|
||||
print('ОШИБКА: задайте PASSWORD (env)'); sys.exit(2)
|
||||
|
||||
WS_URL = 'wss://xmpp.nixg.ru/xmpp-websocket'
|
||||
NS_DEVICELIST = 'eu.siacs.conversations.axolotl.devicelist'
|
||||
NS_OMEMO = 'eu.siacs.conversations.axolotl'
|
||||
|
||||
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
||||
|
||||
|
||||
class OmemoCheck(slixmpp.ClientXMPP):
|
||||
def __init__(self, do_publish=None, do_clean=False):
|
||||
super().__init__(JID, PASSWORD)
|
||||
self.register_plugin('xep_0060')
|
||||
self.do_publish = do_publish
|
||||
self.do_clean = do_clean
|
||||
self.add_event_handler('session_start', self.go)
|
||||
|
||||
async def read_devicelist(self):
|
||||
try:
|
||||
iq = await self['xep_0060'].get_items(jid=JID, node=NS_DEVICELIST, max_items='')
|
||||
its = iq['pubsub']['items']['item']
|
||||
ids = []
|
||||
for it in (its or []):
|
||||
for dev in it.xml.iter('{%s}device' % NS_OMEMO):
|
||||
ids.append(dev.get('id'))
|
||||
print('[read] devicelist:', ids or '(пусто — смотри на диске, см. доки)')
|
||||
return ids
|
||||
except IqError as ex:
|
||||
print('[read] IqError:', ex.iq['error']['condition']); return None
|
||||
except Exception as ex:
|
||||
print('[read]', type(ex).__name__, ex); return None
|
||||
|
||||
async def go(self, event):
|
||||
self.send_presence(); await self.get_roster()
|
||||
print('[session] OK')
|
||||
ids = await self.read_devicelist()
|
||||
|
||||
if self.do_publish:
|
||||
lst = ET.Element('{%s}list' % NS_OMEMO)
|
||||
for d in (ids or []):
|
||||
ET.SubElement(lst, '{%s}device' % NS_OMEMO, {'id': d})
|
||||
ET.SubElement(lst, '{%s}device' % NS_OMEMO, {'id': self.do_publish})
|
||||
try:
|
||||
await self['xep_0060'].publish(jid=JID, node=NS_DEVICELIST, id='current', payload=lst)
|
||||
print('[publish]', self.do_publish, 'ACCEPTED')
|
||||
except IqError as ex:
|
||||
print('[publish] IqError:', ex.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
print('[publish] timeout')
|
||||
except Exception as ex:
|
||||
print('[publish]', type(ex).__name__, ex)
|
||||
|
||||
if self.do_clean:
|
||||
keep = [d for d in (ids or []) if d != self.do_publish] if self.do_publish else (ids or [])
|
||||
# без явного списка чистить неоткуда — просто ре-публикуем текущий
|
||||
lst = ET.Element('{%s}list' % NS_OMEMO)
|
||||
for d in keep:
|
||||
ET.SubElement(lst, '{%s}device' % NS_OMEMO, {'id': d})
|
||||
try:
|
||||
await self['xep_0060'].publish(jid=JID, node=NS_DEVICELIST, id='current', payload=lst)
|
||||
print('[clean] devicelist ->', keep, 'ACCEPTED')
|
||||
except Exception as ex:
|
||||
print('[clean]', type(ex).__name__, ex)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
self.disconnect()
|
||||
|
||||
|
||||
async def main():
|
||||
do_publish = None
|
||||
do_clean = False
|
||||
args = sys.argv[1:]
|
||||
if '--publish' in args:
|
||||
do_publish = args[args.index('--publish') + 1]
|
||||
if '--clean' in args:
|
||||
do_clean = True
|
||||
|
||||
bot = OmemoCheck(do_publish=do_publish, do_clean=do_clean)
|
||||
bot.ssl_context = ctx
|
||||
try:
|
||||
fut = bot.connect((WS_URL,))
|
||||
await asyncio.wait_for(fut, timeout=25)
|
||||
await asyncio.sleep(8)
|
||||
try: bot.disconnect()
|
||||
except Exception: pass
|
||||
except Exception as ex:
|
||||
print('connect error:', ex)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user