from discord.ext import tasks import discord import traceback import asyncio import aiohttp import os import numpy as np from collections import Counter #ai sloop class KamazAI: WEIGHTS = { 'steam_identity': 0.4, 'reason': 0.2, 'permissions': 0.1, 'numerics': 0.2, 'server': 0.1 } K_NEIGHBORS = 5 ''' { "id":2, "a_nickname":"Роботяга", "a_permition":"DONT_HAVE", "a_kills":6, "a_deads":5, "a_seconds":392, "r_nickname":"zombiskell", "r_permition":"DONT_HAVE", "r_kills":10, "r_deads":10, "r_seconds":2022, "reasons":"Читы", "utime":1717502378, "srv":"srv9", "online":18, "type":"IN_GAME", "actions":["inspect"], "serverName":"Норильск 2019", "a_steam":{ "steam3":"[U:1:1338527208]", "steam2":"STEAM_0:0:669263604", "steam64":"76561199298792936", "community_url":"https://steamcommunity.com/profiles/76561199298792936", "account_id":1338527208 }, "r_steam":{ "steam3":"[U:1:1554861952]", "steam2":"STEAM_0:0:777430976", "steam64":"76561199515127680", "community_url":"https://steamcommunity.com/profiles/76561199515127680", "account_id":1554861952 } } ''' def __init__(self, reports_list): print("Reports list size: ", len(reports_list)) self.historical_reports = reports_list self.report_actions = {} for report in reports_list: try: self.report_actions[report["id"]] = report["actions"] except: print("Cannot build action", report) self.numeric_stats = self.normalize_numerics(reports_list) ''' def init_data(reports: list, actions: list): """Загрузка данных при старте или через специальный endpoint.""" global historical_reports, report_actions historical_reports = reports report_actions.clear() for act in actions: rid = act['report_id'] report_actions.setdefault(rid, []).append(act['action']) ''' def normalize_numerics(self, reports): """Вычисляет min/max для всех числовых полей (обучающая выборка).""" fields = ['a_kills', 'a_deads', 'a_seconds', 'r_kills', 'r_deads', 'r_seconds', 'online'] stats = {} for f in fields: values = [r[f] for r in reports if r[f] is not None] stats[f] = {'min': min(values), 'max': max(values)} return stats async def similarity(self, report1, report2, num_stats): """Возвращает взвешенное сходство (0..1).""" score = 0.0 # 1. Совпадение пары Steam-аккаунтов if False: identity_score = 0.0 if report1['a_steam2'] == report2['a_steam2'] and report1['r_steam2'] == report2['r_steam2']: identity_score = 1.0 elif report1['a_steam2'] == report2['a_steam2'] or report1['r_steam2'] == report2['r_steam2']: identity_score = 0.5 score += self.WEIGHTS['steam_identity'] * identity_score # 2. Причина (точное совпадение) try: reason_score = 1.0 if report1['reasons'] == report2['reasons'] else 0.0 score += self.WEIGHTS['reason'] * reason_score except: print("Skip reason calc", report1, report2) traceback.print_exc() # 3. Права try: perm_score = 0.0 if report1['a_permition'] == report2['a_permition']: perm_score += 0.5 if report1['r_permition'] == report2['r_permition']: perm_score += 0.5 score += self.WEIGHTS['permissions'] * perm_score except: print("Skip permition calc", report1, report2) traceback.print_exc() # 4. Числовые поля (нормированное евклидово расстояние -> сходство) num_fields = ['a_kills', 'a_deads', 'a_seconds', 'r_kills', 'r_deads', 'r_seconds', 'online'] dist_sq = 0.0 for f in num_fields: min_val = num_stats[f]['min'] max_val = num_stats[f]['max'] if max_val == min_val: norm_diff = 0.0 else: try: norm_diff = (report1[f] - report2[f]) / (max_val - min_val) except: norm_diff = 0.0 dist_sq += norm_diff ** 2 eucl_dist = np.sqrt(dist_sq / len(num_fields)) # Превращаем расстояние в сходство (1 - нормализованное расстояние) num_similarity = 1.0 - min(eucl_dist, 1.0) score += self.WEIGHTS['numerics'] * num_similarity # 5. Сервер if False: srv_score = 1.0 if report1['srv'] == report2['srv'] else 0.0 score += self.WEIGHTS['server'] * srv_score return score async def predict(self, new_report): # Вычисляем сходство со всеми историческими заявками similarities = [] for hist in self.historical_reports: sim = await self.similarity(new_report, hist, self.numeric_stats) similarities.append((hist['id'], sim)) # Сортируем по убыванию сходства similarities.sort(key=lambda x: x[1], reverse=True) top_k = similarities[:self.K_NEIGHBORS] #print(top_k) # Собираем все действия, назначенные на эти заявки action_counter = Counter() similar_report_ids = [] for rid, sim in top_k: #print(rid, sim) if sim > 0: # можно задать порог, чтобы отсечь шум similar_report_ids.append(rid) if rid in self.report_actions: for act in self.report_actions[rid]: action_counter[act] += 1 total = sum(action_counter.values()) suggestions = [] if total > 0: for action, count in action_counter.most_common(): suggestions.append({ 'action': action, 'confidence': round(count / total, 4) }) else: # Если ни одного похожего – предложение "inspect" по умолчанию suggestions.append({'action': 'inspect', 'confidence': 1.0}) return { 'suggestions': suggestions, 'similar_reports': similar_report_ids } class Extension: core = None action_translate = { 'ban': "забанить игрока", 'inspect': "проверить профиль стима и решить что делать дальше", 'kick': "кикнуть игрока", 'ban30': "легкий бан на 30 минут", 'ban120': "бан на пару часов", 'noreason': "не вводить причину", 'author_kick': "кикнуть автора репорта", 'mute': "замьютить игрока", 'unban': "разбанить игрока если тот в бане, ебанутое решение", 'author_inspect': 'глянуть профиль автора репорта' } def __init__(self, core): self.core = core self.kamazai = None self.reports_list = [] async def task(self, timeout = 15): if os.getenv('BACKEND_URL') and os.getenv("BACKEND_SECRETKEY"): pass else: print("Cannot init kamazAI, BACKEND_URL or BACKEND_SECRETKEY is missing in env") return await self.core.wait_until_ready() while True: await self.updater() await asyncio.sleep(timeout) async def updater(self): try: if self.kamazai == None: print("Sync report list") async with aiohttp.ClientSession(cookies={ "secretkey":os.getenv("BACKEND_SECRETKEY")}) as session: async with session.get(f"{os.getenv('BACKEND_URL')}/api/discord/report/s", ssl = False) as response: self.reports_list = await response.json() self.kamazai = KamazAI(self.reports_list) print("KamazAI Enabled") except: traceback.print_exc() async def __call__(self, message: discord.Message = None): if self.kamazai == None: print("call kamazAi but he is not init") return await message.reply(content=f'KamazAI не иницилизирован') try: report_id = message.embeds[0].color.value #print("Found report id", report_id) except: return await message.reply(content=f'KamazAI не может получить индификатор репорта') async with aiohttp.ClientSession(cookies={ "secretkey":os.getenv("BACKEND_SECRETKEY")}) as session: async with session.get(f"{os.getenv('BACKEND_URL')}/api/discord/report/{report_id}", ssl = False) as response: report = await response.json() result = await self.kamazai.predict(report) suggestions = result.get("suggestions", []) s = [] for ss in suggestions: #s.append(f'{ss["action"]} c вероятностью {ss["confidence"]}') s.append(f'{round(ss["confidence"] * 100)}%) {self.action_translate.get(ss["action"], ss["action"])}') content = f'KamazAI решил что с этим репортом надо сделать: \n' + "\n".join(s) + "\nОцените решение камаза где :thumbsup: - норм или :thumbsdown: - не очень" response = await message.reply(content=content) try: await response.add_reaction('👍') await response.add_reaction('👎') except: pass return response #self.reports_list.append(report) #self.kamazai = KamazAI(self.reports_list) if __name__ == "__main__": async def run(): print("run") from json import load with open("/Users/gsd/Downloads/reports.json", "r", encoding="utf8") as report_list: kamazAi = KamazAI(load(report_list)) test_report1 = {"id":23059,"a_nickname":"серийный чувак","a_permition":"DONT_HAVE","a_kills":29,"a_deads":38,"a_seconds":3237,"r_nickname":"Корабль Бомж 1","r_permition":"DONT_HAVE","r_kills":44,"r_deads":31,"r_seconds":3598,"reasons":"Читы","utime":1780842262,"srv":"srv9","online":18,"type":"IN_GAME","actions":[],"serverName":"Норильск 2019","a_steam":{"steam3":"[U:1:1675616295]","steam2":"STEAM_0:1:837808147","steam64":"76561199635882023","community_url":"https://steamcommunity.com/profiles/76561199635882023","account_id":1675616295},"r_steam":{"steam3":"[U:1:1546493291]","steam2":"STEAM_0:1:773246645","steam64":"76561199506759019","community_url":"https://steamcommunity.com/profiles/76561199506759019","account_id":1546493291}} test_report2 = {"id":23048,"a_nickname":"Евгений Задротов","a_permition":"FREE","a_kills":63,"a_deads":68,"a_seconds":5031,"r_nickname":"Kapusta_KvSH","r_permition":"VIP","r_kills":11,"r_deads":2,"r_seconds":436,"reasons":"VIP абуз","utime":1780837315,"srv":"srv5","online":21,"type":"IN_GAME","actions":[],"serverName":"Завод Ultimate","a_steam":{"steam3":"[U:1:1623272121]","steam2":"STEAM_0:1:811636060","steam64":"76561199583537849","community_url":"https://steamcommunity.com/profiles/76561199583537849","account_id":1623272121},"r_steam":{"steam3":"[U:1:196806785]","steam2":"STEAM_0:1:98403392","steam64":"76561198157072513","community_url":"https://steamcommunity.com/profiles/76561198157072513","account_id":196806785}} print(test_report1) print(await kamazAi.predict(test_report1)) print() print(test_report2) print(await kamazAi.predict(test_report2)) import asyncio asyncio.run(run())