You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

294 lines
11 KiB

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())