Browse Source

kamaz ai 2

master
gsd 4 weeks ago
parent
commit
f67a297991
  1. 364
      other_ext/kamaz_ai_2.py
  2. 8
      other_ext/webhook_helper.py

364
other_ext/kamaz_ai_2.py

@ -0,0 +1,364 @@
from collections import defaultdict, Counter
import math, traceback
from discord.ext import tasks
import discord
import asyncio
import aiohttp
import os
# ============================================================
# ХРАНИЛИЩЕ ДАННЫХ (in-memory, наполняется при инициализации)
# ============================================================
class DataStore:
def __init__(self):
self.reports = {} # id -> report dict
self.actions = defaultdict(list) # report_id -> [action, ...]
# индексы для быстрого поиска по обвиняемому
self.by_accused = defaultdict(list) # r_steam']['steam2'] -> [report_id]
self.by_author = defaultdict(list) # a_steam']['steam2'] -> [report_id]
def load(self, reports):
"""Полная загрузка/перезагрузка данных от главного приложения."""
self.reports.clear()
self.actions.clear()
self.by_accused.clear()
self.by_author.clear()
permitions = []
for r in reports:
try:
if r['r_permition'] not in permitions:
permitions.append(r['r_permition'])
if r['a_permition'] not in permitions:
permitions.append(r['a_permition'])
self.reports[r['id']] = r
if r['r_steam']:
self.by_accused[r['r_steam']['steam2']].append(r['id'])
if r['a_steam']:
self.by_author[r['a_steam']['steam2']].append(r['id'])
except:
print(r)
traceback.print_exc()
raise Exception
for a in reports:
self.actions[a['id']] = a['actions']
print(permitions)
def add_report(self, report):
"""Добавить новую жалобу (после того как модератор принял решение)."""
self.reports[report['id']] = report
self.by_accused[report['r_steam']['steam2']].append(report['id'])
self.by_author[report['a_steam']['steam2']].append(report['id'])
def add_action(self, report_id, action):
self.actions[report_id].append(action)
class KamazAi2:
action_translate = {
'ban': "забанить игрока",
'inspect': "проверить профиль стима и решить что делать дальше",
'kick': "кикнуть игрока",
'ban30': "легкий бан на 30 минут",
'ban120': "бан на пару часов",
'noreason': "не вводить причину",
'author_kick': "кикнуть автора репорта",
'mute': "замьютить игрока",
'unban': "разбанить игрока если тот в бане, ебанутое решение",
'author_inspect': 'глянуть профиль автора репорта',
'none': 'ничего не делать, лучше не лезть'
}
# ============================================================
# КЛАССИФИКАЦИЯ ACTION
# ============================================================
# Действия делятся на: применённые к обвиняемому, к автору, нейтральные.
ACTION_GROUPS = {
# против обвиняемого (жалоба обоснована)
'ban': {'target': 'accused', 'severity': 3, 'guilty': True},
'kick': {'target': 'accused', 'severity': 2, 'guilty': True},
'inspect': {'target': 'accused', 'severity': 1, 'guilty': None},
# против автора (жалоба ложная / абуз репорта)
'author_kick': {'target': 'author', 'severity': 2, 'guilty': False},
'author_inspect': {'target': 'author', 'severity': 1, 'guilty': None},
# нейтральные / отклонение
'noreason': {'target': 'none', 'severity': 0, 'guilty': False},
'none': {"target": 'none', 'severity': 0, 'guilty': False}
}
def permition_translate(self, permition):
if not permition or permition == 'DONT_HAVE':
return 0
if permition in ['VIP', 'FREE']:
return 1
if permition in ['MODERATOR']:
return 4
if permition in ['ADMIN', 'ROOT']:
return 5
return 0
def __init__(self, reports):
self.store = DataStore()
self.store.load(reports)
# ============================================================
# ЭВРИСТИКИ ПО ИГРОВЫМ МЕТРИКАМ
# ============================================================
async def kd_ratio(self, kills, deads):
try:
return kills / max(deads, 1)
except Exception as e:
return 1
async def analyze_metrics(self, report):
"""
Возвращает список факторов-сигналов (флагов) с весом и пояснением.
Положительный вес score -> жалоба скорее обоснована (вина обвиняемого).
Отрицательный -> жалоба скорее ложная.
"""
factors = []
a_kd = await self.kd_ratio(report['a_kills'], report['a_deads'])
r_kd = await self.kd_ratio(report['r_kills'], report['r_deads'])
reason = (report.get('reasons') or '').lower()
# --- Подозрение на читы: аномально высокий KD у обвиняемого ---
if any(w in reason for w in ['чит', 'cheat', 'аим', 'aim', 'wh', 'хак']):
if r_kd >= 5 and report['r_kills'] >= 30:
factors.append((+0.4, f"Обвиняемый имеет очень высокий KD ({r_kd:.1f}) "
f"при {report['r_kills']} убийствах — признак читов"))
elif r_kd < 2:
factors.append((-0.3, f"Жалоба на читы, но KD обвиняемого низкий ({r_kd:.1f}) "
f"— читы маловероятны"))
else:
factors.append((+0.1, f"Жалоба на читы, KD обвиняемого {r_kd:.1f} — требуется проверка"))
# --- Абуз прав (VIP/FreeVIP абуз) ---
if 'абуз' in reason or 'vip' in reason:
if self.permition_translate(report['r_permition']) > 0:
factors.append((+0.2, f"Обвиняемый имеет права (permition={report['r_permition']}), "
f"жалоба на абуз прав правдоподобна"))
else:
factors.append((-0.4, "Жалоба на абуз прав, но у обвиняемого нет прав — необоснованно"))
# --- Обвиняемый — администратор/модератор (защита от ложных жалоб) ---
if self.permition_translate(report['r_permition']) >= 4:
factors.append((-0.3, f"Обвиняемый имеет высокие права (permition={report['r_permition']}) "
f"— возможна ложная жалоба от обиженного игрока"))
# --- Автор играет плохо и жалуется на сильного игрока (тильт-репорт) ---
if a_kd < 0.5 and r_kd > 2 and 'чит' not in reason:
factors.append((-0.2, f"Автор играет слабо (KD {a_kd:.1f}) против сильного оппонента "
f"(KD {r_kd:.1f}) — возможен репорт «на эмоциях»"))
# --- Слишком мало времени на сервере у обвиняемого (нечего оценивать) ---
if report['r_seconds'] < 120:
factors.append((-0.15, f"Обвиняемый провёл на сервере мало времени "
f"({report['r_seconds']} сек) — мало данных"))
return factors
# ============================================================
# ПОИСК ПОХОЖИХ ИСТОРИЧЕСКИХ ЖАЛОБ
# ============================================================
async def reason_similarity(self, r1, r2):
"""Простое сравнение причин по словам (Жаккар)."""
s1 = set((r1 or '').lower().split())
s2 = set((r2 or '').lower().split())
if not s1 or not s2:
return 0.0
return len(s1 & s2) / len(s1 | s2)
async def similarity(self, new_report, old_report):
"""Оценка схожести двух жалоб (0..1)."""
score = 0.0
# тот же обвиняемый — очень сильный сигнал
if new_report['r_steam'] and old_report['r_steam'] and new_report['r_steam']['steam2'] == old_report['r_steam']['steam2']:
score += 0.5
# тот же автор
if new_report['a_steam'] and old_report['a_steam'] and new_report['a_steam']['steam2'] == old_report['a_steam']['steam2']:
score += 0.15
# похожая причина
score += 0.25 * await self.reason_similarity(new_report.get('reasons'), old_report.get('reasons'))
# схожесть KD обвиняемого
new_kd = await self.kd_ratio(new_report['r_kills'], new_report['r_deads'])
old_kd = await self.kd_ratio(old_report['r_kills'], old_report['r_deads'])
kd_diff = abs(new_kd - old_kd)
score += 0.10 * math.exp(-kd_diff / 3.0) # затухание
return min(score, 1.0)
async def find_similar(self, new_report, top_k=5, min_sim=0.15):
results = []
for rid, old in self.store.reports.items():
acts = self.store.actions.get(rid)
if not acts:
continue # без решения история бесполезна
sim = await self.similarity(new_report, old)
if sim >= min_sim:
results.append((sim, rid, old, acts))
results.sort(key=lambda x: x[0], reverse=True)
return results[:top_k]
# ============================================================
# ИТОГОВОЕ РЕШЕНИЕ
# ============================================================
async def decide(self, new_report):
# 1. Метрические факторы
factors = await self.analyze_metrics(new_report)
metric_score = sum(w for w, _ in factors)
# 2. Похожие прошлые жалобы
similar = await self.find_similar(new_report)
# Взвешенное голосование по action из похожих кейсов
action_votes = Counter()
guilty_score = 0.0
sim_total = 0.0
similar_payload = []
for sim, rid, old, acts in similar:
sim_total += sim
for act in acts:
action_votes[act] += sim
meta = self.ACTION_GROUPS.get(act, {})
if meta.get('guilty') is True:
guilty_score += sim
elif meta.get('guilty') is False:
guilty_score -= sim
similar_payload.append({
'report_id': rid,
'similarity': round(sim, 3),
'reasons': old.get('reasons'),
'r_steam2': old['r_steam']['steam2'],
'actions': acts,
})
# Нормализуем вклад истории
history_score = (guilty_score / sim_total) if sim_total > 0 else 0.0
# 3. Финальный счёт: история весит больше, чем эвристики
final_score = 0.6 * history_score + 0.4 * metric_score
# 4. Рекомендованное действие
if action_votes:
recommended_action = action_votes.most_common(1)[0][0]
else:
recommended_action = None
# Маппинг финального счёта в вердикт
if final_score >= 0.35:
verdict = 'GUILTY' # жалоба обоснована — наказать обвиняемого
if not recommended_action or self.ACTION_GROUPS.get(recommended_action, {}).get('target') != 'accused':
recommended_action = 'inspect'
elif final_score <= -0.25:
verdict = 'FALSE_REPORT' # ложная жалоба
if not recommended_action or self.ACTION_GROUPS.get(recommended_action, {}).get('target') == 'accused':
recommended_action = 'none'
else:
verdict = 'UNCERTAIN'
recommended_action = recommended_action or 'inspect'
confidence = min(abs(final_score) + 0.1 * len(similar), 1.0)
return {
'verdict': verdict,
'recommended_action': recommended_action,
'action_translate': self.action_translate.get(recommended_action, recommended_action),
'confidence': round(confidence, 3),
'final_score': round(final_score, 3),
'metric_score': round(metric_score, 3),
'history_score': round(history_score, 3),
'action_distribution': dict(action_votes),
'explanations': [text for _, text in factors],
'similar_reports': similar_payload,
}
class Extension:
core = None
def __init__(self, core):
self.core = core
self.kamazai2 = 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 kamazAI2, 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.kamazai2 == 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.kamazai2 = KamazAi2(self.reports_list)
print("KamazAI2 Enabled")
except:
traceback.print_exc()
async def __call__(self, message: discord.Message = None):
if self.kamazai2 == None:
print("call kamazAi2 but he is not init")
return await message.reply(content=f'KamazAI2 не иницилизирован')
try:
report_id = message.embeds[0].color.value
#print("Found report id", report_id)
except:
return await message.reply(content=f'KamazAI2 не может получить индификатор репорта')
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.kamazai2.decide(report)
content = f'KamazAI2 решил что с этим репортом надо сделать: \n' + result['action_translate'] + '\n' + 'Обоснование: ' + "\n".join(result['explanations']) + "\nОцените решение камаза где :thumbsup: - норм или :thumbsdown: - не очень"
response = await message.reply(content=content)
try:
await response.add_reaction('👍')
await response.add_reaction('👎')
except:
pass
return response
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 = KamazAi2(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.decide(test_report1))
print()
print(test_report2)
print(await kamazAi.decide(test_report2))
import asyncio
asyncio.run(run())

8
other_ext/webhook_helper.py

@ -68,6 +68,14 @@ class Extension:
print("Cannot call kamazAI")
traceback.print_exc()
try:
#kamazai2
kwargs = {'message': message}
await self.core.loaded_extensions['kamaz_ai_2'](**kwargs)
except:
print("Cannot call kamazAI2")
traceback.print_exc()
return
@core.listen()

Loading…
Cancel
Save