chat_control.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. """
  2. Дополниельные компоненты для работы плагина chat_control.
  3. - Получени информации о чате.
  4. - Исключение пользователя из чата.
  5. - Управление предупредждениями пользователя
  6. - Отображение динамики изменений показателей чата.
  7. Author: Milinuri Nirvalen
  8. Ver: 2.4 (13)
  9. """
  10. from chiori import Config
  11. from chiori import Event
  12. from chiori import Context
  13. from libs.vk import get_profiles
  14. import os
  15. from typing import Optional
  16. chats = "data/chats/"
  17. """
  18. Описание данных чата
  19. ====================
  20. banned: ID участников в чёрном списке чата с указанием времени возврата
  21. warns: ID участников с предупреждениями, с указанием причины
  22. last_messages: Время последних сообщений от пользователей
  23. Количество сообщений в минуту от пользователя
  24. marriages: Информация о браках: Кто с кем, когда, код редактирования
  25. m_requests: Отправленные запросы на вступление в брак
  26. counters: Подсчёт кол-ва слов и сообщений от пользователей за 300 дней
  27. timers: Время до следующего дневного/недельного обновления счётчиков
  28. words: Список запрешённых регулярных выражений чата
  29. edit_code: Код для редактирования данных беседы
  30. holy_members: ID свяых участников и время снятия их роли
  31. tokens: Количество жетонов активности каждого участника за 7 дней
  32. tokens_history: Информация о динамике изменения жетонов активности
  33. за 14 дней
  34. silent_mide: Настройка "тихого режима"
  35. leawes: ID вышедших учстников с указанием даты выхода из беседы
  36. rules: Сохранённые правила беседыы
  37. """
  38. chat_data = {
  39. "banned":{}, "warns":{},
  40. "last_nessages":{}, "marriages":[], "m_requests":{},
  41. "counters":{}, "timers":{"day":0, "week":0},
  42. "words":[], "edit_code":"000000", "holy_members":{}, "tokens":{},
  43. "tokens_history":[ [0, 0, 0] for x in range(15) ],
  44. "silent_mode":True, "leaves":{}, "rules":""
  45. }
  46. # Получени данных о чате
  47. # ======================
  48. def get_chat_ids() -> list[str]:
  49. """Возвращает ID всех зарегистрированных чатов."""
  50. return [x.split('.')[0] for x in os.listdir(chats)]
  51. def get_chat(event: Event) -> Config:
  52. """Получает данные чата.
  53. Args:
  54. event (Event): Экземпляр события Чио
  55. Returns:
  56. Config: Экземпляр Config с данными чата
  57. """
  58. c = Config(filepath=f"{chats}mirror.toml").file_data
  59. is_mirror = False
  60. # Является ли чат зеркалом
  61. if str(event.get("to.id")) in c:
  62. cid = c[str(event.get("to.id"))]
  63. is_mirror = True
  64. else:
  65. cid = str(event.get("to.id"))
  66. # Данные о чате и зеркале
  67. chat = Config(None, chat_data, filepath=f"{chats}{cid}.toml")
  68. if chat.file_data:
  69. chat.file_data["cid"] = cid
  70. chat.file_data["is_mirror"] = is_mirror
  71. for k, v in chat_data.items():
  72. if k not in chat.file_data:
  73. chat.file_data[k] = v
  74. return chat
  75. # Работы с временем
  76. # =================
  77. def count_time(seconds: int) -> list[int]:
  78. """Переводит секунды в днич, часы, минуты, секунды.
  79. Args:
  80. seconds (int): Колчиество секунд
  81. Returns:
  82. list[int]: Спиок времени [Дней, Часов, Минут, Секунд]
  83. """
  84. if seconds > 0:
  85. d, h = divmod(seconds, 86400)
  86. h, m = divmod(h, 3600)
  87. m, s = divmod(m, 60)
  88. res = [d, h, m, s]
  89. else:
  90. res = [0, 0, 0, 0]
  91. return res
  92. def get_str_time(time_list: list[int] | int) -> str:
  93. """Преобразует список времени в строку прошедшего времени.
  94. Args:
  95. time_list (list[int]): [Дней, Часов, Минут, Секунд]
  96. Returns:
  97. str: Строка с указанием прошедшего времени
  98. """
  99. # Если нам передали число секунд
  100. if isinstance(time_list, int):
  101. time_list = count_time(time_list)
  102. if not sum(time_list[:-1]) and time_list[-1] < 5:
  103. return "Только что"
  104. res = ""
  105. if time_list[0] > 0:
  106. res += f"{time_list[0]} д."
  107. res += f" {time_list[1]} ч."
  108. res += f" {time_list[2]}" if time_list[2] > 0 else "00"
  109. if time_list[3] > 0:
  110. res += f":{time_list[3]}"
  111. return res
  112. # Работа с чатом
  113. # ==============
  114. async def check_chat(e: Event, ctx: Context, prior: Optional[int]=2) -> bool:
  115. """проверяющая функции чата.
  116. Args:
  117. e (Event): Экзмепляр события Чио
  118. ctx (Context): Экземпляр контекста события
  119. prior (int, optional): Требуемый уровень допуска:
  120. 0: Все пользователи.
  121. 1: Администраторы привязанных чатов.
  122. 2: Администраторы зеркал, локальных чатов.
  123. Returns:
  124. bool: True если чат настроен и вы его администратор
  125. """
  126. if e.get("to.is_admin") and e.get("to.is_chat"):
  127. c = get_chat(e)
  128. if not str(e.get("to.id")) in get_chat_ids():
  129. c.save()
  130. if not prior:
  131. return True
  132. elif prior == 1 and e.get("from.is_admin") or e.get("level") == 10:
  133. return True
  134. elif not c.file_data["is_mirror"] and prior == 2 and e.get("from.is_admin"):
  135. return True
  136. async def kick(e: Event, ctx: Context, uid: list[str]) -> str:
  137. """Исключает участника из чата.
  138. Args:
  139. e (Event): Экземпляр события
  140. ctx (Context): Экземпляр контекста события
  141. uid (str): Список ID участиков для исключения из чата
  142. Returns:
  143. str: Сообщение со статусов исключения
  144. """
  145. if isinstance(uid, (int, str)):
  146. uid = [uid]
  147. profiles = await get_profiles(e, ctx, uid)
  148. text = ""
  149. for k, v in profiles.items():
  150. if v.get("is_admin"):
  151. text += f"\n[{v['name']}] Администратор чата."
  152. continue
  153. res = await ctx.request("messages.removeChatUser",
  154. chat_id=e.get("to.id") - 2000000000, member_id=k
  155. )
  156. if res["error"]:
  157. text += f"\n{v['name']} {res['response']['error_msg']}"
  158. else:
  159. text += f"\b{v['name']} исключён."
  160. return text
  161. async def add_warn(c: Config, profiles: dict, time: int, uid: list[str],
  162. arg="Причина не указана.") -> str:
  163. """Выдаёт предупреждение участнику чата.
  164. Args:
  165. c (Config): Экземпляр настроек чата
  166. profiles (dict): Словарь профилей участников чата
  167. time (int): Время снятия предупреждения
  168. uid (list): Список ID участников для выдачи предупреждения
  169. arg (str, optional): Причина выдачи предупреждения
  170. Returns:
  171. str: Сообщение со статусов выдачи предупреждений
  172. """
  173. text = ""
  174. for x in uid:
  175. if x not in profiles:
  176. text += f"\n-- [{x}] Не участник чата."
  177. continue
  178. pr = profiles[x]
  179. if pr.get("is_admin"):
  180. text += f"\n-- {pr['name']} администратор чата."
  181. continue
  182. if x in c.file_data["holy_members"]:
  183. text += f"\n-- {pr['name']} неприкосновенный(ая)."
  184. continue
  185. if len(c.file_data["warns"].get(x, [])) == 5:
  186. text += f"\n-- {pr['name']} достиг своего предела."
  187. continue
  188. if x in c.file_data["warns"]:
  189. c.file_data["warns"][x][str(time)] = arg
  190. else:
  191. c.file_data["warns"][x] = {str(time):arg}
  192. warns = len(c.file_data["warns"][x])
  193. text += f"\n-- {pr['name']} выдано предупрждение ({warns}/5)\n> {arg}"
  194. c.save()
  195. return text
  196. async def remove_warn(c: Config, profiles: dict, uid: list,
  197. count: Optional[int] = 1) -> str:
  198. """Удаляет предупредждения у участников чата.
  199. Args:
  200. c (Config): Экземпляр данных чата
  201. profiles (dict): Словрь профилей участников чата
  202. uid (list): Список ID участников для удаления предупреждений
  203. count (int, optional): Количество удаляемых предупреждений
  204. Returns:
  205. str: Описание
  206. """
  207. text = ""
  208. for x in uid:
  209. if x not in profiles:
  210. text += f"\n-- [{x}] Нет в списке предупрждений"
  211. continue
  212. pr = profiles[x]
  213. if c not in c.file_data["warns"]:
  214. text += f"\n-- [{pr['name']}] Нет предупреждений"
  215. continue
  216. elif count >= len(c.file_data["warns"].get(x, [])):
  217. del c.file_data["warns"][x]
  218. text += f"\n-- [{pr['name']}] Сняты все предупрждения"
  219. continue
  220. else:
  221. for i, k in enumerate(c.file_data["warns"][x]):
  222. del c.file_data["warns"][x][k]
  223. if i == count:
  224. break
  225. warns = len(c.file_data["warns"][x])
  226. text += f'\n-- [{pr["name"]}]: -{count} ({warns}/5 пр.)'
  227. c.save()
  228. return text
  229. # Динамика изменения жетонов
  230. # ==========================
  231. def get_diff(a: int, b: int) -> str:
  232. if a == b:
  233. return "0"
  234. return f"🔻{a-b}" if a > b else f"🔺️{b-a}"
  235. def tokens_log(c) -> dict:
  236. text = ""
  237. data = {"min_tokens": min(map(lambda x: x[0], c)) or 1,
  238. "max_tokens": max(map(lambda x: x[0], c)) or 1,
  239. "avarage_tokens": round(sum(map(lambda x: x[0], c)) / 15) or 1,
  240. "min_lose": min(map(lambda x: x[1], c)) or 1,
  241. "max_lose": max(map(lambda x: x[1], c)) or 1,
  242. "avarage_lose": round(sum(map(lambda x: x[1], c)) / 15) or 1,
  243. "min_added": min(map(lambda x: x[2], c)) or 1,
  244. "max_added": max(map(lambda x: x[2], c)) or 1,
  245. "avarage_added": round(sum(map(lambda x: x[2], c)) / 15) or 1}
  246. for x in c:
  247. hp = round(x[0] / data["max_tokens"] * 100, 2)
  248. text += "\n-- ["
  249. if x[0] == data["max_tokens"]:
  250. text += "❤"
  251. if x[1] == data["max_lose"]:
  252. text += "🔶"
  253. if x[2] == data["max_added"]:
  254. text += "🔷️"
  255. if x[0]:
  256. text += f"{hp}%; {get_diff(x[1], x[2])}] {x[0]} (-{x[1]}; +{x[2]})"
  257. data["text"] = text
  258. return data