diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..cbf6357 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,51 @@ +name: Docker + +on: + pull_request: + # Publish `main` as Docker `latest` image. + branches: + - master + + # Publish `v1.2.3` tags as releases. + tags: + - v* + + # Run tests for any PRs. + push: + +env: + # Image name + IMAGE_NAME: gamestats-telegram + +jobs: + # Push image to GitHub Packages. + # See also https://docs.docker.com/docker-hub/builds/ + push: + + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v2 + + - name: Build image + run: docker build . --file Dockerfile --tag $IMAGE_NAME + + - name: Log into registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin + + - name: Push image + run: | + IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME + # Change all uppercase to lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + # Strip "v" prefix from tag name + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + # Use Docker `latest` tag convention + [ "$VERSION" == "main" ] && VERSION=latest + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..049398b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3 + +WORKDIR /usr/src/app + +ENV token 2023757880:AAEJxQ4483PnCDHPmrKgh7w1C4HBI4mP5Ng +ENV redishost redis-availability.1e8uve.ng.0001.euc1.cache.amazonaws.com +ENV redisport 6379 +ENV redispass '' + +COPY ./requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +# https://github.com/goverfl0w/discord-interactions/ +COPY . . + +CMD python ./app.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..bafca58 --- /dev/null +++ b/app.py @@ -0,0 +1,61 @@ +# https://docs.aiogram.dev/en/latest/ +import re +import os +import asyncio +from battlefield.api.api_requests import onReadyCheck +import logging +from aiogram import Bot, Dispatcher, executor, types +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from aiogram.dispatcher.filters.state import State, StatesGroup + +API_TOKEN = os.environ["token"] + +# Configure logging +logging.basicConfig(level=logging.INFO) + + +class StatState(StatesGroup): + bf2 = State() + bf3 = State() + bfh = State() + bf4 = State() + bf1 = State() + bf5 = State() + + bf2weapongraph = State() + bf3weapongraph = State() + bfhweapongraph = State() + bf4weapongraph = State() + bf1weapongraph = State() + bf5weapongraph = State() + + bf2vehiclegraph = State() + bf3vehiclegraph = State() + bfhvehiclegraph = State() + bf4vehiclegraph = State() + bf1vehiclegraph = State() + bf5vehiclegraph = State() + + +# Initialize bot and dispatcher +bot = Bot(token=API_TOKEN) + +storage = MemoryStorage() +dp = Dispatcher(bot, storage=storage) + + +@dp.message_handler(commands=["start", "help"]) +async def send_welcome(message: types.Message): + """ + This handler will be called when user sends `/start` or `/help` command + """ + await message.reply( + "This is the first initial version of the gamestats bot for telegram\nWe will start by adding the main commands:\n/bf1stats, /bf5stats, /bf4stats etc..." + ) + + +from battlefield.handlers import * + +if __name__ == "__main__": + asyncio.ensure_future(onReadyCheck()) + executor.start_polling(dp, skip_updates=True) diff --git a/battlefield/api/__init__.py b/battlefield/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battlefield/api/api_requests.py b/battlefield/api/api_requests.py new file mode 100644 index 0000000..38e27c6 --- /dev/null +++ b/battlefield/api/api_requests.py @@ -0,0 +1,423 @@ +import pymongo + +from ..mongo import SingletonClient +from . import constants +import json +import re +import uuid +from time import time +import urllib.parse +import sys +import os +import datetime + +from tabulate import tabulate + +"""be able to do api's""" +import aiohttp +from ..api import authSession +from ..api.loginProcess import requestCookie +from .searchPlayer import checkMe, searchName, originID, platformSelect, getAccessToken +from influxdb_client.client.write_api import SYNCHRONOUS + +"""make the command sleep while others run""" +import asyncio + +regions = {} + +BLREGIONS = {1: "NAm", 2: "SAm", 4: "AU", 8: "Africa", 16: "EU", 32: "Asia", 64:"OC"} + +async def ingame_request_headers(code): + return { + "X-GatewaySession": f"{code}", + "X-ClientVersion": "release-bf1-lsu35_26385_ad7bf56a_tunguska_all_prod", + "X-DbId": "Tunguska.Shipping2PC.Win32", + "X-CodeCL": "3779779", + "X-DataCL": "3779779", + "X-SaveGameVersion": "26", + "X-HostingGameId": "tunguska", + "X-Sparta-Info": "tenancyRootEnv=unknown; tenancyBlazeEnv=unknown", + } + +async def onReadyCheck(): + """this script will run every 30 minutes""" + constants.COOKIE["sid"], constants.COOKIE["remid"], access_code = await requestCookie("api3@gametools.network", "FY5d9uEsv4Js9pQjygyDpzvRFgQUaEHE") + while True: + try: + await getAccessToken() + except Exception as e: + print(f"on_ready: {e}") + await asyncio.sleep(1800) + +async def otherNameApi(message): + db = SingletonClient.get_data_base() + playersInfo = await db.playerList.find_insensitive_more({"usedNames": message}) + results = "" + + if len(playersInfo) != 0: + for player in playersInfo: + otherNames = "" + for name in player["usedNames"]: + otherNames += f"{name}\n" + results += f"{player['playerName']}'s names: \n {otherNames}" + results += f"Last seen in server: \n{player['lastServer']}" + return {"title": "Manager", "description": f"{results}"} + else: + return {"title": "Manager", "description": "player not found"} + +async def bfBanApi(message, strUserId): + """used for checking if he's on bfban""" + async with aiohttp.ClientSession() as session: + if strUserId is None: + return {"color":0xe74c3c, "title":"BFBan", "description":"Not a valid origin username\n[BFBan.com](https://bfban.com/#/)"} # user not found + else: + try: + url = f"https://bfban.gametools.network/api/cheaters/{strUserId}" + async with session.get(url) as r: + userInfo = await r.json() + for i in range(len(userInfo["data"]["cheater"])): + if strUserId == userInfo["data"]["cheater"][i]["originUserId"]: + if userInfo["data"]["cheater"][i]["status"] == "1": + otherNames = "" + for i in range(len(userInfo["data"]["origins"])): + otherNames += f'{userInfo["data"]["origins"][i]["cheaterGameName"]}\n' + if otherNames == "": + otherNames = "[BFBan.com](https://bfban.com/#/)" + else: + otherNames += f"[BFBan.com](https://bfban.com/#/cheaters/{strUserId})" + return {"color":0xFFA500, "title":f"BFBan", "description":f"{message} found in BFBan, all usernames he used according to bfban:\n {otherNames}"} + return {"color":0xe74c3c, "title":"BFBan", "description":f"{message} found in BFBan, but not labeled as cheater\n[BFBan.com](https://bfban.com/#/cheaters/{strUserId})"} + return {"color":0xe74c3c, "title":"BFBan", "description":f"{message} isn't in BFBan\n[BFBan.com](https://bfban.com/#/)"} + except: + return {"color":0xe74c3c, "title":"BFBan", "description":"BFBan is down\n[BFBan.com](https://bfban.com/#/)"} # api down + + +async def getEA(playerId, method, game, code, session): + if game == "casablanca": + url = "https://sparta-gw-bfv.battlelog.com/jsonrpc/pc/api" + else: + url = "https://sparta-gw.battlelog.com/jsonrpc/web/api" + ids = uuid.uuid1() + headers = { + "content-type": "application/json", + "X-GatewaySession": f"{code}", + "X-ClientVersion": "companion-4569f32f" + } + payload = { + "jsonrpc":"2.0", + "method":f"{method}", + "params":{"game":f"{game}", "personaId":f"{playerId}"}, + "id":f"{ids}" + } + async with session.post(url, data=json.dumps(payload), headers=headers) as r: + return await r.json() + +async def getBothWeVeFromEa(message, game): + async with aiohttp.ClientSession() as session: + strpersonaId = await searchName(None, message, session, game) + tasks = getEA(strpersonaId['personaId'], "Progression.getWeaponsByPersonaId", game, authSession.code[f'{game}:pc'], session), \ + getEA(strpersonaId['personaId'], "Progression.getVehiclesByPersonaId", game, authSession.code[f'{game}:pc'], session) + statsWeapon, statsVehicle = await asyncio.gather(*tasks) + + statsWeapon["pid"] = strpersonaId['personaId'] + statsWeapon["avatar"] = strpersonaId["avatar"] + statsWeapon["personaName"] = strpersonaId["personaName"] + + return statsWeapon, statsVehicle + + + +async def getMultipleFromEa(message, firstMethod, game): + async with aiohttp.ClientSession() as session: + strpersonaId = await searchName(None, message, session, game) + tasks = getEA(strpersonaId['personaId'], firstMethod, game, authSession.code[f'{game}:pc'], session), \ + getEA(strpersonaId['personaId'], "Stats.detailedStatsByPersonaId", game, authSession.code[f'{game}:pc'], session), \ + getEA(strpersonaId['personaId'], "Platoons.getActivePlatoon", game, authSession.code[f'{game}:pc'], session) + stats, detailed_stats, activePlatoon = await asyncio.gather(*tasks) + + stats["pid"] = strpersonaId['personaId'] + stats["avatar"] = strpersonaId["avatar"] + stats["personaName"] = strpersonaId["personaName"] + + stats["detailed"] = detailed_stats + stats["activePlatoon"] = activePlatoon + return stats + + +async def getDataFromEa(message, method, game): + """get requested method and detailed stats in ['detailed'], personaId == ['personaId']""" + async with aiohttp.ClientSession() as session: + if game == "casablanca": + url = "https://sparta-gw-bfv.battlelog.com/jsonrpc/pc/api" + elif game == "bf4": + url = "https://sparta-gw.battlelog.com/jsonrpc/web/api" + else: + url = "https://sparta-gw.battlelog.com/jsonrpc/web/api" + strpersonaId = await searchName(None, message, session, game) + # request based on method + ids = uuid.uuid1() + headers = { + "content-type": "application/json", + "X-GatewaySession": f"{authSession.code[f'{game}:pc']}", + "X-ClientVersion": "companion-4569f32f" + } + payload = { + "jsonrpc":"2.0", + "method":f"{method}", + "params":{"game":f"{game}", "personaId":f"{strpersonaId['personaId']}"}, + "id":f"{ids}" + } + async with session.post(url, data=json.dumps(payload), headers=headers) as r: + stats = await r.json() + stats["pid"] = strpersonaId['personaId'] + if strpersonaId["battlelog"]: + stats["platformid"] = strpersonaId['platformid'] + if strpersonaId["platformName"] == "cem_ea_id": + stats["platformName"] = "pc" + else: + stats["platformName"] = strpersonaId["platformName"] + else: + stats["platformName"] = "pc" + stats["personaId"] = strpersonaId['personaId'] + stats["avatar"] = strpersonaId["avatar"] + stats["personaName"] = strpersonaId["personaName"] + return stats + +async def getServerDataFromEa(game, method, params, platform: str = "pc"): + """get details about a server ingame based on given method""" + async with aiohttp.ClientSession() as session: + code = await authSession.checkGatewaySession(game, session, platform=platform) + ids = uuid.uuid1() + headers = await ingame_request_headers(code[f'{game}:{platform}']) + payload = { + "jsonrpc":"2.0", + "method": method, + "params": params, + "id":f"{ids}" + } + url = f"https://sparta-gw.battlelog.com/jsonrpc/{platform}/api" + if game == "casablanca": + url = f"https://sparta-gw-bfv.battlelog.com/jsonrpc/{platform}/api" + async with session.post(url, data=json.dumps(payload), headers=headers) as r: + items = await r.json() + return items + +async def getServerPrefixes(serverName): + """for some special servers, change prefix""" + bobPrefix = serverName[0:9] + if "AMG" in bobPrefix: + serverDetails = serverName.split("-") + prefix = f'[AMG]#{int(list(filter(str.isdigit, serverName))[0])}' + elif "epiX" in bobPrefix: + epixPrefix = serverName[0:8].replace(" ","") + serverDetails = serverName.split("-") + prefix = epixPrefix + " " + serverDetails[1].strip() + elif "Community" in bobPrefix: + serverNumber = re.search(r'\d+', serverName).group() + prefix = f"Operations #{serverNumber}" + else: + prefix = serverName[0:30] + return prefix + +async def getBattlelogServersBasedOnName(message, game): + """gets list of servers for bf3, bf4 and bfh""" + async with aiohttp.ClientSession() as session: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0.2) Gecko/20100101 Firefox/6.0.2', + 'Accept': '*/*', + 'Accept-Language': 'en-us,en;q=0.5', + 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'X-Requested-With': 'XMLHttpRequest', + 'X-AjaxNavigation': '1' + } + url = f"https://battlelog.battlefield.com/{game}/servers/pc/?q={urllib.parse.quote(message)}" + async with session.get(url, headers=headers) as r: + items = await r.json() + return items + +async def getBf2DataFromEa(message): + """gets stats for bf2 players""" + # if message.lower() in ["me"]: + # message = await checkMe(self, ctx) + async with aiohttp.ClientSession() as session: + newmessage = re.escape(message).replace("_","\_") + url = f'http://bf2web.bf2hub.com/ASP/searchforplayers.aspx?nick={newmessage}' + async with session.get(url) as r: + webData = await r.text() + statsData = str(webData).split("\n") + cols = statsData[3].split("\t") + data = statsData[4].split("\t") + response = dict(zip(cols, data)) + pid = str(response["pid"]) + info = 'per*,cmb*,twsc,cpcp,cacp,dfcp,kila,heal,rviv,rsup,rpar,tgte,dkas,dsab,cdsc,rank,cmsc,kick,kill,deth,suic,ospm,klpm,klpr,dtpr,bksk,wdsk,bbrs,tcdr,ban,dtpm,lbtl,osaa,vrk,tsql,tsqm,tlwf,mvks,vmks,mvn*,vmr*,fkit,fmap,fveh,fwea,wtm-,wkl-,wdt-,wac-,wkd-,vtm-,vkl-,vdt-,vkd-,vkr-,atm-,awn-,alo-,abr-,ktm-,kkl-,kdt-,kkd-' + headers = {'User-agent': 'GameSpyHTTP/1.0'} + url = f'http://bf2web.bf2hub.com/ASP/getplayerinfo.aspx?pid={pid}&info={info}&nocache={round(time())}' + async with session.get(url, headers=headers) as r: + response = await r.text() + statsData = str(response).split("\n") + cols = statsData[3].split("\t") + data = statsData[4].split("\t") + stats = dict(zip(cols, data)) + return stats + +async def getDataFromBattlelog(method, message, game): + """get stats for bf3, bf4 and bfh players""" + async with aiohttp.ClientSession() as session: + platformData = await platformSelect(message) + strpersonaId = await searchName(None, platformData["new_message"], session, game, platformData["current"]) + if method == "overviewPopulateStats": + url = f"http://battlelog.battlefield.com/{game}/overviewPopulateStats/{strpersonaId['personaId']}/None/{strpersonaId['platformid']}/" + else: + url = f"http://battlelog.battlefield.com/{game}/{method}/{strpersonaId['personaId']}/{strpersonaId['platformid']}/" + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0.2) Gecko/20100101 Firefox/6.0.2', + 'Accept': '*/*', + 'Accept-Language': 'en-us,en;q=0.5', + 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'X-Requested-With': 'XMLHttpRequest', + 'X-AjaxNavigation': '1'} + async with session.get(url, headers=headers) as r: + stats = await r.json() + stats["platformName"] = platformData['current'] + stats["platformid"] = strpersonaId['platformid'] + stats["personaId"] = stats["data"]["personaId"] + stats["avatar"] = strpersonaId["avatar"] + stats["personaName"] = strpersonaId["personaName"] + return stats + +async def doServerChanges(game, request, params, cookie): + async with aiohttp.ClientSession() as session: + code = await authSession.checkGatewaySession(game, session, cookie) + ids = uuid.uuid1() + url = "https://sparta-gw.battlelog.com/jsonrpc/web/api" + headers = await ingame_request_headers(code) + payload = { + "jsonrpc": "2.0", + "method": f"{request}", + "params": params, + "id": f"{ids}" + } + async with session.post(url, data=json.dumps(payload), headers=headers) as r: + items = await r.json() + return items + +async def checkBanApi(message): + async with aiohttp.ClientSession() as session: + db = SingletonClient.get_data_base() + userId = await originID(session, message) + if userId["totalCount"] == 0: + return {"color":0xe74c3c, "title":"", "description":"Not a valid origin username", "strUserId": None, "playerId": None} # user not found + else: + strUserId = userId["userId"] + playerId = userId["strpersonaId"] + playerInfo = await db.globalIngameBans.find_one({'_id': str(playerId)}) + if playerInfo is not None: + servers = "" + for item in playerInfo["servers"]: + servers += f'{item}\n' + return {"color":0xFFA500, "title": f"{playerInfo['displayName']} is banned in:", "description":servers, "strUserId": strUserId, "playerId": playerId} + else: + return {"color":0xe74c3c, "title": "", "description":f"{message} isn't in global banlist of manager", "strUserId": strUserId, "playerId": playerId} + +async def getMostRecentSession(ctx, playerName): + db = SingletonClient.get_data_base() + async with aiohttp.ClientSession() as session: + strpersonaId = await searchName(None, playerName, session, "tunguska") + recentStats = [] + i = 1 + async for document in db.playerDiffStats.find({'playerId': int(strpersonaId['personaId'])}, sort=[( 'timeStamp', pymongo.DESCENDING )]): + recentStats.append(document) + if i == 5: + break + i+=1 + # embed=discord.Embed(color=0xFFA500, title=f"{strpersonaId['personaName']}\'s most recent playsessions") + + # add current stats when the player is still ingame + try: + join = await db.playerJoinStats.fine_one({'playerId': int(strpersonaId['personaId'])}, sort=[( 'timeStamp', pymongo.DESCENDING )]) + mostRecentPlayerupdate = await db.playerList.find_one({'_id': int(strpersonaId['personaId'])}) + if len(recentStats) != 0: + runStats = recentStats[0]["timeStamp"] < mostRecentPlayerupdate["timeStamp"] and join["timeStamp"] < mostRecentPlayerupdate["timeStamp"] + elif join is None or mostRecentPlayerupdate is None: + runStats = False + else: + runStats = join["timeStamp"] < mostRecentPlayerupdate["timeStamp"] + joinStats = join["stats"] + if runStats: + detailedStats = (await getDataFromEa(ctx, playerName, "Stats.detailedStatsByPersonaId", "tunguska")) + statsList = [{ + "Wins": int(detailedStats["result"]["basicStats"]["wins"] - joinStats["wins"]), + "Losses": int(detailedStats["result"]["basicStats"]["losses"] - joinStats["losses"]), + "Kills": int(detailedStats["result"]["basicStats"]["kills"] - joinStats["kills"]), + "Deaths": int(detailedStats["result"]["basicStats"]["deaths"] - joinStats["deaths"]), + "Time played": int(detailedStats["result"]["basicStats"]["timePlayed"] - joinStats["timePlayed"]), + }] + + kits = {} + for kit in joinStats["kits"]: + kits[kit["name"]] = kit + for newKit in detailedStats["result"]["kitStats"]: + kits[newKit["name"]]["score"] = int(newKit["score"] - kits[newKit["name"]]["score"]) + kits[newKit["name"]]["kills"] = int(newKit["kills"] - kits[newKit["name"]]["kills"]) + kits[newKit["name"]]["timePlayed"] = datetime.timedelta(seconds=newKit["secondsAs"] - kits[newKit["name"]]["timePlayed"]) + kits = list(kits.values()) + + gamemodes = {} + for gamemode in joinStats["gamemodes"]: + gamemodes[gamemode["name"]] = gamemode + for newGamemode in detailedStats["result"]["gameModeStats"]: + gamemodes[newGamemode["name"]]["score"] = int(newGamemode["score"] - gamemodes[newGamemode["name"]]["score"]) + gamemodes[newGamemode["name"]]["wins"] = int(newGamemode["wins"] - gamemodes[newGamemode["name"]]["wins"]) + gamemodes[newGamemode["name"]]["losses"] = int(newGamemode["losses"] - gamemodes[newGamemode["name"]]["losses"]) + gamemodes = list(gamemodes.values()) + + gamemodeList = [constants.GAMEMODES[d["name"]] for d in gamemodes if d['score'] != 0] + + kitList = [d.values() for d in kits if d['timePlayed'] != datetime.timedelta(seconds=0)] + if statsList[0]["Time played"] != 0: + statsStr = f""" + ```{tabulate(statsList, headers="keys")} + \n{tabulate(kitList, ["Classes", "Score", "Kills", "Time played as"])} + \n(servername not shown for privacy) - {"/".join(gamemodeList)}``` + """ + embed.add_field(name="current KD ingame", value=statsStr, inline=False) + except Exception as e: + print(e) + pass + i = 1 + for info in recentStats: + stats = info["stats"] + serverInfo = await db.communityPlayers.find_one({"_id": info["serverId"]}) + for item in stats["kits"]: + item["timePlayed"] = datetime.timedelta(seconds=item["timePlayed"]) + kitList = [d.values() for d in stats["kits"] if d['timePlayed'] != datetime.timedelta(seconds=0)] + gamemodeList = [constants.GAMEMODES[d["name"]] for d in stats["gamemodes"] if d['score'] != 0] + + statsList = [{ + "Wins": stats['wins'], + "Losses": stats['losses'], + "Kills": stats['kills'], + "Deaths": stats['deaths'], + "Time played": datetime.timedelta(seconds=stats['timePlayed']) + }] + statsStr = f""" + ```{tabulate(statsList, headers="keys")} + \n{tabulate(kitList, ["Classes", "Score", "Kills", "Time played as"])} + \n({info["timeStamp"].strftime("%d-%m-%Y - %H:%M")}) - {"/".join(gamemodeList)}``` + """ + embed.add_field(name=serverInfo["info"]["prefix"], value=statsStr, inline=False) + i+=1 + if len(recentStats) == 0 and runStats == False: + embed.add_field(name="Play more rounds before trying again", value="It only tracks the session within this week", inline=False) + embed.set_footer(text="(Only works on servers using our manager)") + await ctx.send(embed=embed) + +async def platoonCheck(playerName): + try: + stats = await getDataFromEa(playerName, "Platoons.getActivePlatoon", "tunguska") + if stats.get("result"): + return f'[{stats["result"]["name"]}](https://gametools.network/platoons/{stats["result"]["guid"]})' + else: + return "player isn't in any platoon" + except Exception as e: + return "player not found" \ No newline at end of file diff --git a/battlefield/api/authSession.py b/battlefield/api/authSession.py new file mode 100644 index 0000000..c3db1fe --- /dev/null +++ b/battlefield/api/authSession.py @@ -0,0 +1,88 @@ +import uuid +import urllib +import json + +from . import constants + +code = {} + +async def checkGatewaySession(game, session, cookie: str = None, platform: str = "pc"): + """check if X-GatewaySession is valid for bf5""" + custom = False + ids = uuid.uuid1() + global code + if game == "casablanca": + gameUrl = f"https://sparta-gw-bfv.battlelog.com/jsonrpc/{platform}/api" + else: + gameUrl = f"https://sparta-gw.battlelog.com/jsonrpc/{platform}/api" + payload = { + "jsonrpc":"2.0", + "method":"Stats.detailedStatsByPersonaId", + "params":{"personaId": "794397421", "game": game}, + "id":f"{ids}" + } + # check if still valid + if code.get(f'{game}:{platform}', None) is not None and cookie is None: + headers = { + "content-type": "application/json", + "X-GatewaySession": f"{code[f'{game}:{platform}']}", + "X-ClientVersion": "companion-4569f32f" + } + async with session.post(url=gameUrl, headers=headers, data=json.dumps(payload)) as r: + result = await r.json() + else: + result = "error" + if cookie is not None: + custom = True + else: + # set cookie after cookie check + cookie = constants.COOKIE + if "error" in result: + # get new one if fails + if platform == "xboxone": + authPlatform = "xone" + else: + authPlatform = platform + url = f"https://accounts.ea.com/connect/auth?client_id=sparta-backend-as-user-{authPlatform}&response_type=code&release_type=none" + headers = { + "User-Agent": "Mozilla/5.0 EA Download Manager Origin/10.5.89.45622", + "X-Origin-Platform": "PCWIN", + "Cookie": f"sid={constants.COOKIE['sid']}; remid={constants.COOKIE['remid']}; _ga=GA1.2.1989678628.1594206869; _nx_mpcid=c002360c-1ab0-4ce2-83d8-9a850d649e13; ealocale=nl-nl; ak_bmsc=E1AB16946A53CE2E1495B6BBF258CB7758DDA1073361000061E5405FB2BC7035~pl1h1xXQwIfdbF9zU3hMOujtpJ70BXJaFRqFWYPsE32q4qwE4onR+N6sn+T0KrqdpyZaf2ir3u+Xh68W3cEjOb+VLrQvI0jwTr1GFp5bgZXU812telOSjDjYPK3ddYJ7kOfbHtEglOs9UhdzOFi+UIWIAFdwcNq5ZoPv9Z0i53SspWuI4W3POlFqX70LxwwXcLMTQZgnGVs8R98goScJjo0Oo4D/+8LLzJsJfIUpr4AcgSC5B/ks2m5yz8ft9nh+9w; bm_sv=0B8CB6864011B573F81CA53CE1A570AD~qrTfU4mNBNCod1jfAFpeU+tG5OJ7w4r7lIg/ezmX6RLIIemuAT6EnFMcuhkUGHhJQSwjeV1kpadZtdXJAlbDoPxAafNmbaeQC/hteaiVlTMVV7R6gKPT8kWM22cFv6Y3Axp/S5XFhd6/Aw4cH6+Bng==; notice_behavior=implied,eu; _gid=GA1.2.827595775.1598088932; _gat=1", + "Host": "accounts.ea.com" + } + async with session.get(url=url, headers=headers, allow_redirects=False) as r: + redirect = r.headers["Location"] + access_code = urllib.parse.urlparse(redirect).query.split("=")[1] + headers = { + "User-Agent": "ProtoHttp 1.3/DS 15.1.2.1.0 (Windows)", + "X-Guest": "no-session-id", + "X-ClientVersion": "release-bf1-lsu35_26385_ad7bf56a_tunguska_all_prod", + "X-DbId": "Tunguska.Shipping2PC.Win32", + "X-CodeCL": "3779779", + "X-DataCL": "3779779", + "X-SaveGameVersion": "26", + "X-HostingGameId": "tunguska", + "X-Sparta-Info": "tenancyRootEnv=unknown; tenancyBlazeEnv=unknown", + } + payload = { + "jsonrpc": "2.0", + "method": "Authentication.getEnvIdViaAuthCode", + "params": { + "authCode": f"{access_code}", + "product": None, + "dtab": None, + "locale": "en-us", + "branchName": "Tunguska", + "changeListNumber": "3779779", + "minutesToUTC": "60" + }, + "id": f"{ids}" + } + async with session.post(url=gameUrl, headers=headers, data=json.dumps(payload)) as r: + result = await r.json() + if custom: + return result["result"]["sessionId"] + code[f'{game}:{platform}'] = result["result"]["sessionId"] + return code + else: + return code \ No newline at end of file diff --git a/battlefield/api/constants.py b/battlefield/api/constants.py new file mode 100644 index 0000000..c3ca501 --- /dev/null +++ b/battlefield/api/constants.py @@ -0,0 +1,81 @@ +COOKIE = { + "sid": "", + "remid": "" +} + +SPARTA_GAMES = ["tunguska", "casablanca"] +SPARTA_REGIONS = ["EU", "Asia", "NAm", "SAm", "AU", "OC"] +SPARTA_TITLES = ["pc", "xbox", "psn"] +ALL_BF_GAMES = ["bf2", "bf3", "bf4", "bfh", "bf1", "bf5"] +BATTLELOG_GAMES = ["bf3", "bf4", "bfh"] +BATTLELOG_REGIONS = {1: "NAm", 2: "SAm", 4: "AU", 8: "Africa", 16: "EU", 32: "Asia", 64: "OC"} +BATTLELOG_PLATFORM_NUMBERS = {'cem_ea_id': 1, 'xbox360': 2, 'ps3': 4, 'xbox': 8, 'ps2': 16, 'ps4': 32, 'xboxone': 64} +BATTLELOG_GAME_NUMBERS = {'bfbc2': 1, 'bf3': 2, 'bf1942': 4, 'bf1943': 8, 'bfvietnam': 16, 'bf2': 32, 'bf2142': 64, + 'bfbc': 128, 'bfheroes': 256, 'bfmc': 512, 'bfp4f': 1024, 'bf4': 2048, 'mohw': 4096, 'bfh': 8192} +BATTLELOG_BF4_KIT = {1: "Assault", 2: "Engineer", 32: "Support", 8: "Recon", 2048: "Commander"} +BATTLELOG_BFH_KIT = {2048: "Commander", 4096: "Enforcer", 8192: "Mechanic", 16384: "Operator", 32768: "Professional"} +BATTLELOG_BF3_KIT = {1: "Assault", 2: "Engineer", 8: "Recon", 32: "Support"} +FACTIONS = { + "BFFactionId_FactionAHU": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/30/21/FactionsColorAustroHungarianEmpireLarge-e2ebe691.png", + "key": "AHU", + "name": "Austro-Hungarian Empire", + }, + "BFFactionId_FactionGER": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/117/41/FactionsColorGermanEmpireLarge-8bd7e888.png", + "key": "GER", + "name": "German Empire", + }, + "BFFactionId_FactionOTM": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/69/73/FactionsColorOttomanEmpireLarge-45b70933.png", + "key": "OTM", + "name": "Ottoman Empire", + }, + "BFFactionId_FactionITA": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/22/73/FactionsColorKingdomOfItalyLarge-eab7049b.png", + "key": "ITA", + "name": "Kingdom of Italy", + }, + "BFFactionId_FactionUK": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/103/37/FactionsColorUnitedKingdomLarge-672527a9.png", + "key": "UK", + "name": "United Kingdom", + }, + "BFFactionId_FactionUSA": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/10/110/FactionsColorUnitedStatesOfAmericaLarge-0a6e66a7.png", + "key": "USA", + "name": "United States of America", + }, + "BFFactionId_FactionFRA": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/93/43/FactionsColorFranceLarge-a3d5b514.png", + "key": "FRA", + "name": "France", + }, + "BFFactionId_FactionRUS": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/113/64/FactionsColorRussia-8f40526a.png", + "key": "RUS", + "name": "Russian Empire", + }, + "BFFactionId_FactionBOL": { + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/63/106/FactionsLargeColorBolshevik-3f6ac72d.png", + "key": "BOL", + "name": "Red Army", + }, + "BFFactionId_FactionWA": { # BFFactionId_FactionRUS image + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/113/64/FactionsColorRussia-8f40526a.png", + "key": "WA", + "name": "White Army", + }, + + + + "BFFactionId_FactionUKM": { # BFFactionId_FactionUK image + "image": "https://eaassets-a.akamaihd.net/battlelog/battlebinary/gamedata/Tunguska/103/37/FactionsColorUnitedKingdomLarge-672527a9.png", + "key": "UK", + "name": "Royal Marines", + }, +} +GAMEMODES = {"TugOfWar": "Frontlines", "Domination": "Domination", "Breakthrough": "Shock Operations", "Rush": "Rush", + "BreakthroughLarge": "Operations", "Conquest": "Conquest", "Possession": "War pigeons", "TeamDeathMatch": "Team Deathmatch", + "AirAssault": "Air assault", "ZoneControl": "Rush", "Airborne": "Airborne", "FinalStand": "Grand Operations", "Outpost": "Outpost", + "RushLarge": "Rush large", "Frontlines": "Frontlines", "FrontlinesMedium": "Frontlines Medium", "SquadConquest": "Squad Conquest"} \ No newline at end of file diff --git a/battlefield/api/loginProcess.py b/battlefield/api/loginProcess.py new file mode 100644 index 0000000..168f883 --- /dev/null +++ b/battlefield/api/loginProcess.py @@ -0,0 +1,70 @@ +import asyncio +import re +import aiohttp +import urllib.parse +import random +import string + +# main +async def requestCookie(email: str, passw: str): + async with aiohttp.ClientSession() as session: + fid = await requestFid(session) + jSessionId, signInCookie, location = await requestJsSessionID(session, fid) + secondFid = await requestSecondFid(session, jSessionId, signInCookie, location, email, passw) + return await requestCookieAuth(session, secondFid) + +async def requestFid(session: aiohttp.ClientSession): + data = await session.get("https://accounts.ea.com:443/connect/auth?display=originXWeb%2Flogin&response_type=code&release_type=prod&redirect_uri=https%3A%2F%2Fwww.origin.com%2Fviews%2Flogin.html&locale=zh_TW&client_id=ORIGIN_SPA_ID", allow_redirects=False) + redirect = data.headers["Location"] + return redirect.split("=")[1] + +async def requestJsSessionID(session: aiohttp.ClientSession, fid: str): + data = await session.get(f"https://signin.ea.com/p/originX/login?fid={fid}", allow_redirects=False) + location = data.headers["Location"] + i = 0 + jSessionId = "" + signInCookie = "" + for name, data in data.headers.items(): + if name == "Set-Cookie": + if i == 0: + jSessionId = re.search(r'=.+?;', data)[0].strip('=').strip(";") + i += 1 + else: + signInCookie = re.search(r'\".+\"', data)[0].strip('"') + return jSessionId, signInCookie, location + +async def random_char(y): + return ''.join(random.choice(string.ascii_letters) for x in range(y)) + +async def requestSecondFid(session: aiohttp.ClientSession, jSessionId: str, signInCookie: str, location: str, email: str, passw: str): + random = await random_char(5) + data = await session.post( + url = f"https://signin.ea.com{location}", + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Referer": f"https://signin.ea.com{location}", + "cookie": f"JSESSIONID={jSessionId};", + "cookie": f"signin-cookie={signInCookie}" + }, + data = f"email={urllib.parse.quote(email)}&password={passw}&_eventId=submit&cid={random*42}&showAgeUp=true&thirdPartyCaptchaResponse=&_rememberMe=on&rememberMe=on" + ) + html = await data.text() + htmlPart = re.search(r"window\.location(.+?)\n", html)[0] + redirect = re.search(r"\"(.+?)\"", htmlPart)[0] + return redirect.split("=")[-1] + +async def requestCookieAuth(session: aiohttp.ClientSession, secondFid: str): + data = await session.get(f"https://accounts.ea.com:443/connect/auth?display=originXWeb%2Flogin&response_type=code&release_type=prod&redirect_uri=https%3A%2F%2Fwww.origin.com%2Fviews%2Flogin.html&locale=zh_TW&client_id=ORIGIN_SPA_ID&fid={secondFid}", allow_redirects=False) + redirect = data.headers["Location"] + i = 0 + sid = "" + remid = "" + for name, data in data.headers.items(): + if name == "Set-Cookie": + if i == 0: + sid = re.search(r'=.+?;', data)[0].strip('=').strip(";") + i += 1 + else: + remid = re.search(r'=.+?;', data)[0].strip('=').strip(";") + access_code = urllib.parse.urlparse(redirect).query.split("=")[1] + return sid, remid, access_code \ No newline at end of file diff --git a/battlefield/api/redisDB.py b/battlefield/api/redisDB.py new file mode 100644 index 0000000..41fa954 --- /dev/null +++ b/battlefield/api/redisDB.py @@ -0,0 +1,39 @@ +import sys +from datetime import timedelta + +import redis +import os + +redishost = os.environ['redishost'] +redisport = os.environ['redisport'] +redispass = os.environ['redispass'] + +def redis_connect() -> redis.client.Redis: + try: + client = redis.RedisCluster( + host=redishost, + port=redisport, + password=redispass, + socket_timeout=5, + ) + ping = client.ping() + if ping is True: + return client + except redis.AuthenticationError: + print("AuthenticationError") + sys.exit(1) + +client = redis_connect() + +async def get_routes_from_cache(key: str) -> str: + """Data from redis.""" + + val = client.get(key) + return val + + +async def set_routes_to_cache(key: str, value: str, ttl: int = 600) -> bool: + """Data to redis, default == 10 minutes""" + + state = client.setex(key, timedelta(seconds=ttl), value=value) + return state diff --git a/battlefield/api/searchPlayer.py b/battlefield/api/searchPlayer.py new file mode 100644 index 0000000..4dd77ef --- /dev/null +++ b/battlefield/api/searchPlayer.py @@ -0,0 +1,311 @@ +# default bf1 api stuff +import aiohttp +import json +import re + +from pymongo.collation import Collation + +from ..api import constants +from .authSession import checkGatewaySession +from .redisDB import get_routes_from_cache, set_routes_to_cache +import requests +from ..mongo import SingletonClient + +access_token = "" + +async def getAccessToken(): + global access_token + headers = {"Cookie": f"sid={constants.COOKIE['sid']}; remid={constants.COOKIE['remid']};"} + x = requests.get("https://accounts.ea.com/connect/auth?client_id=ORIGIN_JS_SDK&response_type=token&redirect_uri=nucleus:rest&prompt=none&release_type=prod", headers=headers) + jsonaccess_token = x.json()["access_token"] + access_token = str(jsonaccess_token) + +async def bfTrackerPlatformSelect(message): + oldmessage = message.split() + current = "origin" + platforms = ["pc", "origin", "ps4", "psn", "xbox", "xbl", "one"] + for platform in platforms: + if platform in oldmessage: + if platform == "pc": + current = "origin" + elif platform == "ps4" or platform == "psn": + current = "psn" + elif platform == "xbox" or platform == "xbl" or platform == "one": + current = "xbl" + else: + current = platform + new_message = [string for string in oldmessage if string not in platforms] + # if new_message[0].lower() in ["me"]: + # new_message = [await checkMe(self, ctx)] + return {"current": current, "new_message": new_message} + +async def platformSelect(message): + """if platform is filled in, find platform. otherwise use default: origin (pc)""" + oldmessage = message.split() + current = "cem_ea_id" + platformid = 1 + platforms = ["pc", "origin", "ps4", "psn", "playstation", "ps", "ps3", "xboxone", "xbl", "one", "xbox", "360", "xbox360"] + for platform in platforms: + if platform in oldmessage: + if platform.lower() in ["pc", "origin"]: + current = "cem_ea_id" + platformid = 1 + elif platform.lower() in ["ps4", "psn", "playstation", "ps"]: + current = "ps4" + platformid = 32 + elif platform.lower() in ["ps3"]: + current = "ps3" + platformid = 4 + elif platform.lower() in ["xboxone", "xbl", "one", "xbox"]: + current = "xboxone" + platformid = 64 + elif platform.lower() in ["360", "xbox360"]: + current = "xbox360" + platformid = 2 + else: + current = platform + platformid = 1 + new_message = [string for string in oldmessage if string not in platforms] + + return {"current": current, "new_message": ' '.join(map(str, new_message)), "platformid": platformid} + +async def originID(session, message): + """get origin id""" + db = SingletonClient.get_data_base() + playerInfo = await db.playerList.find_one({'playerName': message}, collation=Collation(locale='en', strength=2, alternate="shifted")) + if playerInfo is not None: + if playerInfo["userId"] is not None: + return {"totalCount": 1, "userId": str(playerInfo["userId"]), "strpersonaId": str(playerInfo["_id"])} + + global access_token + url = f"https://api4.origin.com/xsearch/users?userId=2800753812&searchTerm={message}&start=0" + headers = {"authtoken": f"{access_token}"} + userId = {} + try: + async with session.get(url, headers=headers) as r: + userIdGet = await r.json() + userId["totalCount"] = userIdGet["totalCount"] + userId["userId"] = userIdGet["infoList"][0]["friendUserId"] + url = f"https://api2.origin.com/atom/users?userIds={userId['userId']}" + async with session.get(url, headers=headers) as r: + personaId = await r.text() + regexPersonaId = r'(?<=\).+?(?=\)' + userId["strpersonaId"] = re.findall(regexPersonaId, personaId)[0] + except: + playerInfo = await getPlayerFromProxy(message) + return {"totalCount": 1, "userId": str(playerInfo["originId"]), "strpersonaId": str(playerInfo["personaId"])} + return userId + +async def eaDesktopSearch(name): + async with aiohttp.ClientSession() as session: + db = SingletonClient.get_data_base() + accessToken = await db.globals.find_one({"_id": "deskopToken"}) + url = "https://service-aggregation-layer.juno.ea.com/graphql" + headers = { + "Host": "service-aggregation-layer.juno.ea.com", + "accept": "*/*", + "authorization": "Bearer " + accessToken["key"], + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.15.2 Chrome/83.0.4103.122 Safari/537.36", + "content-type": "application/json", "Origin": "https://pc.ea.com", "Referer": "https://pc.ea.com/zh-hans", + "Accept-Encoding": "gzip, deflate, br"} + + payload = {"operationName": "SearchPlayer", + "variables": {"searchText": name, "pageNumber": 1, "pageSize": 20, "locale": "zh-hans"}, + "extensions": {"persistedQuery": {"version": 1, + "sha256Hash": "83da6f3045ee524f6cb62a1c23eea908c9432f15e87b30dd33b89974ff83c657"}}, + "query": "query SearchPlayer($searchText: String!, $pageNumber: Int!, $pageSize: Int!) {\n players(searchText: $searchText, paging: {pageNumber: $pageNumber, pageSize: $pageSize}) {\n items {\n ...PlayerWithMutualFriendsCount\n __typename\n }\n __typename\n }\n}\n\nfragment PlayerWithMutualFriendsCount on Player {\n ...Player\n mutualFriends {\n totalCount\n __typename\n }\n __typename\n}\n\nfragment Player on Player {\n id: pd\n pd\n psd\n displayName\n uniqueName\n nickname\n avatar {\n ...Avatar\n __typename\n }\n relationship\n __typename\n}\n\nfragment Avatar on AvatarList {\n large {\n ...image\n __typename\n }\n medium {\n ...image\n __typename\n }\n small {\n ...image\n __typename\n }\n __typename\n}\n\nfragment image on Image {\n height\n width\n path\n __typename\n}\n"} + + async with session.post(url=url, headers=headers, data=json.dumps(payload)) as r: + result = await r.json() + player = result["data"]["players"]["items"][0] + return { + "personaId": player["psd"], + "personaName": player["displayName"], + "avatar": player["avatar"]["large"]["path"], + "battlelog": False + } + +async def searchNameBattlelog(player_name: str, game: str, platform: str): + async with aiohttp.ClientSession() as session: + # Get Battlelog platform id, defaulting to pc (1) + platform_id = constants.BATTLELOG_PLATFORM_NUMBERS.get(platform, 1) + # Get Battlelog game id (bfh: 8192, bf4: 2048, bf3: 2) + game_id = constants.BATTLELOG_GAME_NUMBERS.get(game) + payload = aiohttp.FormData() + payload.add_field("query", player_name) + url = "https://battlelog.battlefield.com/search/query/" + async with session.post(url, data=payload) as r: + results = await r.json() + if game not in ["tunguska", "casablanca"]: + for persona in results["data"]: + """ + Return persona which matches name exactly and owns current game on current platform, which is determined + based on Battlelog's enumeration flags using bitwise AND. + If a player owns only BF3, BF4 and BFH, on PC, their "games" attribute will be dict equal to: + {'1': '10242'} with '1' being the platform id (1 for pc) and 10242 being the sum of the game ids: + 2 (bf3) + 2048 (bf4) + 8192 (bfh). Using that, we can check if a player owns bf4 by checking if the game + id sum contains bf4's id like so: 10242 & 2048 (which is 2048) == 2048 + """ + if persona["personaName"].lower() == player_name.lower() and str(platform_id) in persona["games"] and \ + int(persona["games"][str(platform_id)]) & game_id == game_id: + return { + "personaId": persona["personaId"], + "personaName": persona["personaName"], + "platformid": platform_id, + "platformName": platform, + "avatar": f'https://secure.gravatar.com/avatar/{persona["user"]["gravatarMd5"]}?' + f's=204&d=https://eaassets-a.akamaihd.net/battlelog/defaultavatars/default-avatar-36.png', + "battlelog": True + } + else: + for persona in results["data"]: + if "cem_ea_id" in persona["namespace"] and persona["personaName"].lower() == player_name.lower(): + return { + "personaId": persona["personaId"], + "personaName": persona["personaName"], + "platformid": platform_id, + "platformName": platform, + "avatar": f'https://secure.gravatar.com/avatar/{persona["user"]["gravatarMd5"]}?' + f's=204&d=https://eaassets-a.akamaihd.net/battlelog/defaultavatars/default-avatar-36.png', + "battlelog": True + } + +async def getFromDb(message): + try: + db = SingletonClient.get_data_base() + playerInfo = await db.playerList.find_one({'playerName': message}, collation=Collation(locale='en', strength=2, alternate="shifted")) + if playerInfo is not None: + if playerInfo["userId"] is not None: + try: + async with aiohttp.ClientSession() as session: + global access_token + headers = {"authtoken": f"{access_token}"} + url = f"https://api3.origin.com/avatar/user/{playerInfo['userId']}/avatars?size=1" + async with session.get(url=url, headers={'Accept': 'application/json', **headers}) as r: + avatarJson = await r.json() + avatar = avatarJson['users'][0]['avatar']['link'] + except Exception as e: + print(f"getFromDb: {e}") + return None # if failed to get avatar + else: + return None + return { + "personaId": playerInfo["_id"], + "personaName": playerInfo["playerName"], + "platformid": 1, + "avatar": avatar, + "battlelog": False + } + except: + return None + +async def getPlayerFromProxy(message): + try: + async with aiohttp.ClientSession() as session: + global access_token + url = f"https://gateway.ea.com/proxy/identity/personas?displayName={message}&namespaceName=cem_ea_id" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0", + "Accept": "application/vnd.origin.v3+json; x-cache/force-write", + "Accept-Encoding": "gzip, deflate, br", + "X-ZuluTime-Format": "true", + "Origin": "https://www.origin.com", + "Referer": "https://www.origin.com/", + 'X-Expand-Results': 'true', + "Authorization": f"Bearer {access_token}", + } + async with session.get(url=url, headers=headers) as r: + playerInfo = await r.json() + user = playerInfo["personas"]["persona"][0] + try: + headers = {"authtoken": f"{access_token}"} + url = f"https://api3.origin.com/avatar/user/{user['pidId']}/avatars?size=1" + async with session.get(url=url, headers={'Accept': 'application/json', **headers}) as r: + avatarJson = await r.json() + return { + "personaName": user["displayName"], + "originId": user["pidId"], + "personaId": user["personaId"], + "avatar": avatarJson['users'][0]['avatar']['link'], + "battlelog": False, + "platformid": 1 + } + except Exception as e: + print(f"getAvatarSeachPlayer: {e}") + return { + "personaName": user["displayName"], + "originId": user["pidId"], + "personaId": user["personaId"], + "avatar": "https://eaassets-a.akamaihd.net/battlelog/defaultavatars/default-avatar-36.png", + "battlelog": False, + "platformid": 1 + } + except: + return None + +async def searchName(originId, name, session, game, platform="pc"): + # if name.lower() in ["me"]: + # name = await checkMe(self, ctx) + data = await get_routes_from_cache(key=f'names:{originId}:{name}:{game}:{platform}') + await checkGatewaySession(game, session) + if data is not None: + strpersonaId = json.loads(data) + return strpersonaId + else: + global access_token + # If game is a sparta game (BF1, BFV), try searching via Origin API + userId = {} + headers = {"authtoken": f"{access_token}"} + if platform == "pc" and originId is None: + try: + url = f"https://api1.origin.com/xsearch/users?userId=2800753812&searchTerm={name}&start=0" + async with session.get(url, headers=headers) as r: + userId = await r.json() + except: + pass + # If player was not found (or no search was attempted because game is not a sparta game), search via Battlelog + if userId.get("totalCount", 0) == 0 and originId is None: + try: + strpersonaId = await searchNameBattlelog(name, game, platform) + except Exception as e: + strpersonaId = None + if strpersonaId is None and game == "tunguska" and platform == "pc": + strpersonaId = await getFromDb(name) + if strpersonaId is None and game in constants.SPARTA_GAMES: + strpersonaId = await getPlayerFromProxy(name) + if strpersonaId is None and game in constants.SPARTA_GAMES: + strpersonaId = await eaDesktopSearch(name) + data = json.dumps(strpersonaId) + await set_routes_to_cache(key=f'names:{originId}:{name}:{game}:{platform}', value=data, ttl=604800) #1 week + return strpersonaId + else: + # get personaId based on userId + if originId is None: + strUserId = userId["infoList"][0]["friendUserId"] + else: + strUserId = originId + strpersonaId = {"personaId": None} + url = f"https://api2.origin.com/atom/users?userIds={strUserId}" + async with session.get(url, headers={'Accept': 'application/json', **headers}) as r: + userJson = await r.json() + strpersonaId = { + 'personaId': userJson['users'][0]['personaId'], + 'personaName': userJson['users'][0]['eaId'] + } + # get user avatar + url = f"https://api3.origin.com/avatar/user/{strUserId}/avatars?size=1" + async with session.get(url=url, headers={'Accept': 'application/json', **headers}) as r: + avatarJson = await r.json() + strpersonaId["avatar"] = avatarJson['users'][0]['avatar']['link'] + strpersonaId["battlelog"] = False + data = json.dumps(strpersonaId) + await set_routes_to_cache(key=f'names:{originId}:{name}:{game}:{platform}', value=data, ttl=604800) #1 week + return strpersonaId + +async def checkMe(self, ctx): + playerInfo = await self.bot.stickyPlayerName.find(ctx.author.id) + if playerInfo is not None: + return playerInfo["name"] + else: + await ctx.send("You haven't setup the \"me\" option (!bfsetme playername), trying with your nickname in this server..") + return re.sub(r'\[[^)]*\]', '', ctx.author.display_name) \ No newline at end of file diff --git a/battlefield/bftracker/bf1.py b/battlefield/bftracker/bf1.py new file mode 100644 index 0000000..133d434 --- /dev/null +++ b/battlefield/bftracker/bf1.py @@ -0,0 +1,107 @@ +import aiohttp +from lxml import html +import re +from ..imageStats.main import imageStats +import urllib.parse +from aiogram import types +from ..commands import grapher + +async def bf1TrackerPlatformSelect(message: str): + oldmessage = message.split() + current = "pc" + platforms = ["pc", "origin", "ps4", "psn", "xbox", "xbl", "one"] + for platform in platforms: + if platform in oldmessage: + if platform == "pc": + current = "pc" + elif platform == "ps4" or platform == "psn": + current = "psn" + elif platform == "xbox" or platform == "xbl" or platform == "one": + current = "xbox" + else: + current = platform + new_message = [string for string in oldmessage if string not in platforms] + return {"current": current, "new_message": str(' '.join(new_message))} + +async def top10WeaponGraph(message, result): + """returns top weapons for bf1 graph""" + try: + async with aiohttp.ClientSession() as session: + url = f"https://battlefieldtracker.com/bf1/profile/{result['current']}/{urllib.parse.quote(result['new_message'])}/weapons" + async with session.get(url=url) as r: + tree = html.fromstring(await r.text()) + graphing = [] + i = 1 + while i != 20: + try: + weaponName = tree.xpath(f'/html/body/div[1]/div[1]/div[3]/div/div[2]/div[4]/table/tbody/tr[{i}]/td[1]/div[1]/text()')[0].strip() + kills = int(''.join(filter(str.isdigit, tree.xpath(f'/html/body/div[1]/div[1]/div[3]/div/div[2]/div[4]/table/tbody/tr[{i}]/td[2]/div[1]/text()')[0].strip()))) + try: + graphing.append({"name": weaponName, "kills": int(kills)}) + i+=1 + except: + pass + except: + break + stats = {"platformid": result['current']} + await grapher.main(message, graphing, stats, "bf1", "weapons") + except: + await message.reply("player not found") + +async def top10VehicleGraph(message, result): + """returns top weapons for bf1""" + try: + async with aiohttp.ClientSession() as session: + url = f"https://battlefieldtracker.com/bf1/profile/{result['current']}/{urllib.parse.quote(result['new_message'])}/vehicles" + async with session.get(url=url) as r: + tree = html.fromstring(await r.text()) + graphing = [] + i = 1 + while i != 20: + try: + weaponName = tree.xpath(f'/html/body/div[1]/div[1]/div[3]/div/div[1]/div[3]/table/tbody/tr[{i}]/td[1]/div/text()')[0].strip() + kills = tree.xpath(f'/html/body/div[1]/div[1]/div[3]/div/div[1]/div[3]/table/tbody/tr[{i}]/td[2]/div[1]/text()')[0].strip() + try: + graphing.append({"name": weaponName, "kills": int(kills)}) + i+=1 + except: + pass + except: + break + stats = {"platformid": result['current']} + await grapher.main(message, graphing, stats, "bf1", "vehicles") + except: + await message.reply("player not found") + + +async def bf1Tracker(message: types.Message, result): + """returns stats for bf4 and bf1""" + try: + async with aiohttp.ClientSession() as session: + url = f"https://battlefieldtracker.com/bf1/profile/{result['current']}/{urllib.parse.quote(result['new_message'])}" + async with session.get(url=url) as r: + tree = html.fromstring(await r.text()) + stats = { + "onlyName": str('%20'.join(result['new_message'])), + "name": str(result['new_message']), + "avatar": "https://eaassets-a.akamaihd.net/battlelog/defaultavatars/default-avatar-204.png?ssl=1", + "rank": re.findall(r'\d+',tree.xpath('/html/body/div[1]/div[1]/div[3]/div[1]/div[1]/div/span/text()')[0])[0], + "rankImg": "https://battlefieldtracker.com" + tree.xpath('/html/body/div[1]/div[1]/div[3]/div[1]/div[1]/img/@src')[0], + "0.0": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[2]/div/div[8]/div[2]/text()')[0].strip(), + "0.1": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[2]/div/div[3]/div[2]/text()')[0].strip(), + "0.2": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[6]/div/div[6]/div[2]/text()')[0].strip(), + "0.3": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[2]/div/div[5]/div[2]/text()')[0].strip(), + "0.4": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[16]/div/div[7]/div[2]/text()')[0].strip(), + "1.0": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[1]/div[2]/div[1]/div[3]/div[2]/text()')[0].strip(), + "1.1": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[16]/div/div[5]/div[2]/text()')[0].strip(), + "1.2": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[6]/div/div[7]/div[2]/text()')[0].strip(), + "1.3": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[2]/div/div[10]/div[2]/text()')[0].strip(), + "1.4": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[16]/div/div[6]/div[2]/text()')[0].strip(), + "2.0": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[12]/div/div[6]/div[2]/text()')[0].strip(), + "2.1": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[10]/div/div[1]/div[2]/text()')[0].strip(), + "2.2": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[10]/div/div[2]/div[2]/text()')[0].strip(), + "2.3": tree.xpath('/html/body/div[1]/div[1]/div[3]/div[2]/div[1]/div[2]/div/div[9]/div[2]/text()')[0].strip() + } + await imageStats.imageRender(message, stats, url, "bf1", result['current']) + except: + await message.reply("player not found or no playername given, usage:\n /bf1stat (platform) name") \ No newline at end of file diff --git a/battlefield/bftracker/bf5.py b/battlefield/bftracker/bf5.py new file mode 100644 index 0000000..b533e73 --- /dev/null +++ b/battlefield/bftracker/bf5.py @@ -0,0 +1,75 @@ + +from ..imageStats.main import imageStats +from ..api.searchPlayer import bfTrackerPlatformSelect +import urllib.parse +import aiohttp +from aiogram import types +from ..commands import grapher + +async def bf5stat(message: types.Message): + try: + result = await bfTrackerPlatformSelect(message.text) + async with aiohttp.ClientSession() as session: + gameUrl = f"https://battlefieldtracker.com/bfv/profile/{result['current']}/{urllib.parse.quote(' '.join(result['new_message']))}/overview" + url = f"https://api.tracker.gg/api/v2/bfv/standard/profile/{result['current']}/{urllib.parse.quote(' '.join(result['new_message']))}" + async with session.get(url) as r: + stats = await r.json() + mainStats = stats["data"]["segments"][0]["stats"] + try: + headshotPercent = str(round((mainStats["headshots"]["value"]/mainStats["kills"]["value"])*100,2))+"%" + except: + headshotPercent = "0%" + stats = { + "onlyName": stats["data"]["platformInfo"]["platformUserHandle"], + "name": stats["data"]["platformInfo"]["platformUserHandle"], + "avatar": stats["data"]["platformInfo"]["avatarUrl"], + "rank": mainStats["rank"]["displayValue"], + "rankImg": mainStats["rank"]["metadata"]["imageUrl"], + "0.0": mainStats["killsPerMinute"]["displayValue"], + "0.1": mainStats["scorePerMinute"]["displayValue"], + "0.2": mainStats["saviorKills"]["displayValue"], + "0.3": mainStats["kdRatio"]["displayValue"], + "0.4": mainStats["longestHeadshot"]["displayValue"], + "1.0": mainStats["wlPercentage"]["displayValue"], + "1.1": headshotPercent, + "1.2": mainStats["shotsAccuracy"]["displayValue"], + "1.3": mainStats["assists"]["displayValue"], + "1.4": mainStats["killStreak"]["displayValue"], + "2.0": mainStats["revives"]["displayValue"], + "2.1": mainStats["repairs"]["displayValue"], + "2.2": mainStats["resupplies"]["displayValue"], + "2.3": mainStats["timePlayed"]["displayValue"], + } + await imageStats.imageRender(message, stats, gameUrl, "bfv", result['current']) + except: + await message.reply("player not found") + +async def bf5weapongraph(message: types.Message): + try: + result = await bfTrackerPlatformSelect(message.text) + async with aiohttp.ClientSession() as session: + url = f"http://api.tracker.gg/api/v1/bfv/profile/{result['current']}/{urllib.parse.quote(' '.join(result['new_message']))}/weapons" + async with session.get(url) as r: + stats = await r.json() + weapons = [] + for weapon in stats["data"]["children"]: + weapons.append({"name": weapon["metadata"]["name"], "kills": weapon["stats"][0]["value"]}) + graphing = sorted(weapons, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, "bf5", "weapons") + except: + await message.reply("player not found") + +async def bf5vehiclegraph(message: types.Message): + try: + result = await bfTrackerPlatformSelect(message.text) + async with aiohttp.ClientSession() as session: + url = f"http://api.tracker.gg/api/v1/bfv/profile/{result['current']}/{urllib.parse.quote(' '.join(result['new_message']))}/vehicles" + async with session.get(url) as r: + stats = await r.json() + vehicles = [] + for vehicle in stats["data"]["children"]: + vehicles.append({"name": vehicle["metadata"]["name"], "kills": vehicle["stats"][0]["value"]}) + graphing = sorted(vehicles, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, "bf5", "vehicles") + except: + await message.reply("player not found") diff --git a/battlefield/commands/bfstats.py b/battlefield/commands/bfstats.py new file mode 100644 index 0000000..a69e5e6 --- /dev/null +++ b/battlefield/commands/bfstats.py @@ -0,0 +1,256 @@ +from aiogram import types +from ..api.api_requests import getBf2DataFromEa, getDataFromBattlelog, getMultipleFromEa +import datetime +import urllib.parse +from ..imageStats.main import imageStats +from aiogram import types + +async def bf2(message: types.Message): + try: + stats = (await getBf2DataFromEa(message)) + gameUrl = f"https://www.bf2hub.com/stats/{str(stats['pid'])}" + kits = {int(stats['kkl-0']): "Anti-Tank", int(stats['kkl-1']): "Assault", int(stats['kkl-2']): "Engineer", int(stats['kkl-3']): "Medic", int(stats['kkl-4']): "Spec-Ops", int(stats['kkl-5']): "Support", int(stats['kkl-6']): "Sniper"} + sort = {} + [sort.update({key: value}) for (key, value) in sorted(kits.items(), reverse=True)] + stats = { + "name": stats['nick'], + "rank": stats['rank'], + "rankImg": f"https://www.bf2hub.com/home/images/ranks/rank_{stats['rank']}.png", + "0.0": round(int(stats["kill"])/(int(stats['time'])/60),2), + "0.1": int(int(stats['scor'])/(int(stats['time'])/60)), + "0.2": round(int(stats["kill"])/int(stats["deth"]),2), + + "0.3": stats["kill"], + "0.4": stats["deth"], + + "1.0": str(round(float(stats['osaa']),2)) + "%", + "1.1": list(sort.values())[0], + "1.2": str(int(int(stats["wins"])/(int(stats["wins"])+int(stats["loss"]))*100)) + "%", + + "1.3": stats["wins"], + "1.4": stats["loss"], + + "2.0": str(datetime.timedelta(seconds=int(stats['time']))) + } + await imageStats.oldBfImageRender(message, stats, gameUrl, "bf2") + except: + await message.reply("player not found") + +async def bf3(message: types.Message): + try: + stats = (await getDataFromBattlelog("overviewPopulateStats", message.text, "bf3")) + if stats['platformName'] == "cem_ea_id": + platformName = "pc" + else: + platformName = stats['platformName'] + gameUrl = f"https://gametools.network/stats/{platformName}/name/{urllib.parse.quote(stats['personaName'])}?game=bf3" + + platform = stats["platformName"] + #get Best Class: + items = [] + kits = {} + sort = {} + [items.append(item) for item in stats["data"]["overviewStats"]["kitScores"]] + [kits.update({stats["data"]["overviewStats"]["kitScores"][i]: i}) for i in items] + [sort.update({key: value}) for (key, value) in sorted(kits.items(), reverse=True)] + try: + kpm = round(stats["data"]["overviewStats"]["kills"]/(stats["data"]["overviewStats"]["timePlayed"]/60),2) + except: + kpm = 0 + try: + killDeath = round(stats["data"]["overviewStats"]["kills"]/stats["data"]["overviewStats"]["deaths"],2) + except: + killDeath = 0 + try: + winPercent = str(int(stats["data"]["overviewStats"]["numWins"]/(stats["data"]["overviewStats"]["numWins"]+stats["data"]["overviewStats"]["numLosses"])*100)) + "%" + except: + winPercent = "0%" + stats = { + "onlyName": stats['personaName'], + "name": stats['personaName'], + "avatar": stats["avatar"], + "rank": stats["data"]["overviewStats"]["rank"], + "rankImg": f'https://cdn.gametools.network/bf3/{stats["data"]["currentRankNeeded"]["texture"].replace("UI/Art/Persistence/Ranks/Rank", "").replace("UI/art/Persistence/Ranks/Rank", "").replace("UI/Art/Persistence/Ranks/rank", "").replace("S0100", "S100")}.png', + "0.0": kpm, + "0.1": stats["data"]["overviewStats"]["scorePerMinute"], + "0.2": stats["data"]["overviewStats"]["elo"], + "0.3": killDeath, + "0.4": stats["data"]["overviewStats"]["longestHeadshot"], + "1.0": winPercent, + "1.1": stats["data"]["overviewStats"]["headshots"], + "1.2": str(round(stats["data"]["overviewStats"]["accuracy"],2))+"%", + "1.3": stats["data"]["kitMap"][list(sort.values())[0]]["name"].capitalize(), + "1.4": stats["data"]["overviewStats"]["killStreakBonus"], + "2.0": stats["data"]["overviewStats"]["revives"], + "2.1": stats["data"]["overviewStats"]["repairs"], + "2.2": stats["data"]["overviewStats"]["resupplies"], + "2.3": str(datetime.timedelta(seconds=stats["data"]["overviewStats"]["timePlayed"])) + } + await imageStats.imageRender(message, stats, gameUrl, "bf3", platform) + except: + await message.reply("player not found") + +async def bfh(message: types.Message): + try: + stats = (await getDataFromBattlelog("bfhoverviewpopulate", message.text, "bfh")) + if stats['platformName'] == "cem_ea_id": + platformName = "pc" + else: + platformName = stats['platformName'] + gameUrl = f"https://gametools.network/stats/{platformName}/name/{urllib.parse.quote(stats['personaName'])}?game=bfh" + platform = stats["platformName"] + #get Best Class: + classes = {"2048": "commander", "4096": "enforcer", "8192": "mechanic", "16384": "operator", "32768": "professional"} + items = [] + kits = {} + sort = {} + [items.append(item) for item in stats["data"]["overviewStats"]["kitScores"]] + [kits.update({int(stats["data"]["overviewStats"]["kitScores"][i]): i}) for i in items] + [sort.update({key: value}) for (key, value) in sorted(kits.items(), reverse=True)] + try: + kpm = round(stats["data"]["overviewStats"]["kills"]/(stats["data"]["overviewStats"]["timePlayed"]/60),2) + except: + kpm = 0 + try: + winPercent = str(int(stats["data"]["overviewStats"]["numWins"]/(stats["data"]["overviewStats"]["numWins"]+stats["data"]["overviewStats"]["numLosses"])*100)) + "%" + except: + winPercent = "0%" + stats = { + "onlyName": stats['personaName'], + "name": stats['personaName'], + "avatar": stats["avatar"], + "rank": stats["data"]["overviewStats"]["rank"], + "rankImg": f'https://cdn.gametools.network/bfh/{stats["data"]["overviewStats"]["rank"]}.png', + "0.0": kpm, + "0.1": stats["data"]["overviewStats"]["scorePerMinute"], + "0.2": winPercent, + "0.3": stats["data"]["overviewStats"]["kdRatio"], + "0.4": str(round(stats["data"]["overviewStats"]["accuracy"],2))+"%", + "1.0": stats["data"]["overviewStats"]["sc_enforcer"], + "1.1": stats["data"]["overviewStats"]["sc_mechanic"], + "1.2": stats["data"]["overviewStats"]["sc_operator"], + "1.3": stats["data"]["overviewStats"]["sc_professional"], + "1.4": stats["data"]["overviewStats"]["sc_hacker"], + "2.0": classes[list(sort.values())[0]][0:4].capitalize(), + "2.1": stats["data"]["overviewStats"]["cashPerMinute"], + "2.2": stats["data"]["overviewStats"]["killAssists"], + "2.3": str(datetime.timedelta(seconds=stats["data"]["overviewStats"]["timePlayed"])) + } + await imageStats.imageRender(message, stats, gameUrl, "bfh", platform) + except: + await message.reply("player not found") + + +async def bf4(message: types.Message): + try: + stats = (await getDataFromBattlelog("warsawdetailedstatspopulate", message.text, "bf4")) + if stats['platformName'] == "cem_ea_id": + platformName = "pc" + else: + platformName = stats['platformName'] + gameUrl = f"https://gametools.network/stats/{platformName}/name/{urllib.parse.quote(stats['personaName'])}?game=bf4" + platform = stats["platformName"] + #get Best Class: + classes = {"1":"Assault","2":"engineer","32":"support","8":"recon","2048":"commander"} + items = [] + kits = {} + sort = {} + [items.append(item) for item in stats["data"]["generalStats"]["kitScores"]] + [kits.update({int(stats["data"]["generalStats"]["kitScores"][i]): i}) for i in items] + [sort.update({key: value}) for (key, value) in sorted(kits.items(), reverse=True)] + try: + winPercent = str(int(int(stats["data"]["generalStats"]["numWins"])/(int(stats["data"]["generalStats"]["numWins"])+int(stats["data"]["generalStats"]["numLosses"]))*100)) + "%" + except: + winPercent = "0%" + stats = { + "onlyName": stats['personaName'], + "name": stats['personaName'], + "avatar": stats["avatar"], + "rank": stats["data"]["generalStats"]["rank"], + "rankImg": f'https://cdn.gametools.network/bf4/{stats["data"]["generalStats"]["rank"]}.png', + "0.0": stats["data"]["generalStats"]["killsPerMinute"], + "0.1": stats["data"]["generalStats"]["scorePerMinute"], + "0.2": stats["data"]["generalStats"]["skill"], + "0.3": stats["data"]["generalStats"]["kdRatio"], + "0.4": stats["data"]["generalStats"]["longestHeadshot"], + "1.0": winPercent, + "1.1": stats["data"]["generalStats"]["headshots"], + "1.2": str(round(stats["data"]["generalStats"]["accuracy"],2))+"%", + "1.3": classes[list(sort.values())[0]].capitalize(), + "1.4": stats["data"]["generalStats"]["killStreakBonus"], + "2.0": stats["data"]["generalStats"]["revives"], + "2.1": stats["data"]["generalStats"]["repairs"], + "2.2": stats["data"]["generalStats"]["resupplies"], + "2.3": str(datetime.timedelta(seconds=int(stats["data"]["generalStats"]["timePlayed"]))) + } + await imageStats.imageRender(message, stats, gameUrl, "bf4", platform) + except: + await message.reply("player not found") + +async def bf1(message: types.Message, name, game, rGameName): + """returns stats for bf4 and bf1""" + try: + stats = (await getMultipleFromEa(name, "Stats.detailedStatsByPersonaId", game)) + if game == "tunguska": + gameUrl = f"https://gametools.network/stats/pc/name/{urllib.parse.quote(stats['personaName'])}?game=bf1" + else: #bf4 + gameUrl = f"https://gametools.network/stats/pc/name/{urllib.parse.quote(stats['personaName'])}?game=bf4" + vehicleKills = 0 + vehicleKills = 0 + try: + for vehicle in stats["result"]["vehicleStats"]: + vehicleKills += vehicle["killsAs"] + except: + pass + try: + winLoseProcent = str(int(float(stats["result"]["basicStats"]["wins"])/(float(stats["result"]["basicStats"]["wins"])+float(stats["result"]["basicStats"]["losses"]))*100)) + "%" + except: + winLoseProcent = "0%" + try: + headshotProcent = str(round((stats["result"]["headShots"]/stats["result"]["basicStats"]["kills"])*100,2))+"%" + except: + headshotProcent = "0%" + try: + kd = round(float(stats["result"]["basicStats"]["kills"])/float(stats["result"]["basicStats"]["deaths"]),2) + infantryKD = round((float(stats["result"]["basicStats"]["kills"])-float(vehicleKills))/float(stats["result"]["basicStats"]["deaths"]),2) + infantryKPM = round((float(stats["result"]["basicStats"]["kills"])-float(vehicleKills))/float((stats["result"]["basicStats"]["timePlayed"])/60),2) + except: + kd = 0 + infantryKD = 0 + infantryKPM = 0 + try: + bestClass = stats["result"]["favoriteClass"].capitalize() + except: + bestClass = "" + try: + accuracy = str(round(stats["result"]["accuracyRatio"]*100,2))+"%" + except: + accuracy = "0%" + if stats['activePlatoon']["result"] == None: + name = stats['personaName'] + else: + name = f"[{stats['activePlatoon']['result']['tag']}]{stats['personaName']}" + stats = { + "onlyName": stats['personaName'], + "name": name, + "avatar": stats["avatar"], + "rank": "", + "rankImg": "", + "0.0": stats["result"]["basicStats"]["kpm"], + "0.1": stats["result"]["basicStats"]["spm"], + "0.2": stats["result"]["basicStats"]["skill"], + "0.3": kd, + "0.4": stats["result"]["longestHeadShot"], + "1.0": winLoseProcent, + "1.1": headshotProcent, + "1.2": accuracy, + "1.3": bestClass, + "1.4": stats["result"]["highestKillStreak"], + "2.0": stats["result"]["revives"], + "2.1": infantryKD, + "2.2": infantryKPM, + "2.3": datetime.timedelta(seconds=stats["result"]["basicStats"]["timePlayed"]) + } + await imageStats.imageRender(message, stats, gameUrl, rGameName, "pc") + except: + await message.reply("player not found") \ No newline at end of file diff --git a/battlefield/commands/grapher.py b/battlefield/commands/grapher.py new file mode 100644 index 0000000..5134e94 --- /dev/null +++ b/battlefield/commands/grapher.py @@ -0,0 +1,56 @@ +import urllib.parse +import io +from aiogram import types + +"""add plotting(graphs)""" +import matplotlib.pyplot as plt +#matplotlib inline +plt.style.use('ggplot') + +async def main(message: types.Message, graphing, stats, game, type): + """makes the graphs for all the bfgraph commands""" + try: + if game == "tunguska": + gameUrl = f"https://gametools.network/stats/pc/name/{urllib.parse.quote(stats['personaName'])}?game=bf1" + elif game == "bf1": # bftracker + gameUrl = f"https://battlefieldtracker.com/bf1/profile/{stats['platformid']}/{urllib.parse.quote(message.text)}/{type}" + elif game == "bf2": + gameUrl = f"https://www.bf2hub.com/stats/{urllib.parse.quote(stats['pid'])}" + elif game == "bf5": + gameUrl = f"https://battlefieldtracker.com/bfv/profile/origin/{urllib.parse.quote(message.text)}/{type}" + else: + pid = str(stats["personaId"]) + gameUrl = f"https://battlelog.battlefield.com/{game}/soldier/{urllib.parse.quote(message.text)}/{type}/{pid}/{stats['platformid']}/" + x1 = [] + y1 = [] + x = [] + y = [] + t = 0 + [ x1.append(key["name"]) for (key) in graphing ] + [ y1.append(key["kills"]) for (key) in graphing ] + for i in range(len(x1)): + if t != 20: + x.append(x1[i]) + y.append(y1[i]) + else: + break + t+=1 + x_pos = [i for i, _ in enumerate(x)] + with io.BytesIO() as data_stream: + plt.figure(facecolor="#151829") + plt.bar(x_pos, y, color='#00aaff') + plt.xticks(x_pos, x, color='#ffffff', rotation=45, horizontalalignment='right') + plt.yticks(color='#ffffff') + plt.grid(b=False) + ax=plt.gca() + ax.set_facecolor('#151829') + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0) + + plt.savefig(data_stream, format='png', transparent=False) + plt.close() + data_stream.seek(0) + await message.reply_photo(data_stream, caption=f"Top 20 {type}\n{gameUrl}") + except: + await message.reply("player not found") diff --git a/battlefield/commands/vehicles.py b/battlefield/commands/vehicles.py new file mode 100644 index 0000000..acb1488 --- /dev/null +++ b/battlefield/commands/vehicles.py @@ -0,0 +1,50 @@ +from aiogram import types +from ..api.api_requests import getBf2DataFromEa, getDataFromBattlelog, getDataFromEa, getMultipleFromEa +from . import grapher + +async def bf2Graph(message: types.Message): + try: + stats = (await getBf2DataFromEa(message.text)) + vehicleNames = ["Armor", "Jet", "Helicopter", "Transport", "Anti-Air", "Ground-Def."] + graphing = [] + for i in range(len(vehicleNames)): + try: + graphing.append({"name": vehicleNames[i], "kills": int(stats["vkl-"+str(i)])}) + except: + pass + await grapher.main(message, graphing, stats, "bf2", "vehicles") + except: + await message.reply("player not found") + +async def graph(message: types.Message, game): + """returns top vehicles for bf1 in a graph""" + try: + stats = (await getDataFromEa(message.text, "Progression.getVehiclesByPersonaId", game)) + vehicles = [] + for weapon in stats["result"]: + try: + vehicles.append({"name": weapon["name"].capitalize(), "kills": int(weapon["stats"]["values"]["kills"])}) + except: + pass + graphing = sorted(vehicles, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, game, "vehicles") + except: + await message.reply("player not found") + +async def battlelogGraph(message: types.Message, type, game): + """returns top vehicles for bfh, bf3 and bf4 as graph""" + try: + stats = (await getDataFromBattlelog(type, message.text, game)) + naming = "name" + if game in ["bfh", "bf4"]: + naming = "slug" + vehicles = [] + for weapon in stats["data"]["mainVehicleStats"]: + try: + vehicles.append({"name": weapon[naming], "kills": int(weapon["kills"])}) + except: + pass + graphing = sorted(vehicles, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, game, "vehicles") + except: + await message.reply("player not found") \ No newline at end of file diff --git a/battlefield/commands/weapons.py b/battlefield/commands/weapons.py new file mode 100644 index 0000000..382d71e --- /dev/null +++ b/battlefield/commands/weapons.py @@ -0,0 +1,52 @@ +from aiogram import types +from ..api.api_requests import getBf2DataFromEa, getDataFromBattlelog, getDataFromEa, getMultipleFromEa +from . import grapher + +async def graph(message: types.Message, game): + """returns top weapons for bf1 in a graph""" + try: + stats = (await getDataFromEa(message.text, "Progression.getWeaponsByPersonaId", game)) + weapons = [] + for weaponGroup in stats["result"]: + for weapon in weaponGroup["weapons"]: + try: + weapons.append({"name": weapon["name"], "kills": int(weapon["stats"]["values"]["kills"])}) + except: + pass + graphing = sorted(weapons, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, game, "weapons") + except: + await message.reply("player not found") + +async def battlelogGraph(message: types.Message, type, game): + """returns top weapons for bfh, bf3 and bf4 as graph""" + try: + stats = (await getDataFromBattlelog(type, message.text, game)) + weapons = [] + naming = "name" + if game in ["bfh", "bf4"]: + naming = "slug" + for weapon in stats["data"]["mainWeaponStats"]: + try: + weapons.append({"name": weapon[naming], "kills": int(weapon["kills"])}) + except: + pass + graphing = sorted(weapons, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, game, "weapons") + except: + await message.reply("player not found") + +async def bf2Graph(message: types.Message): + try: + stats = (await getBf2DataFromEa(message.text)) + weaponNames = ["Assault-Rifle", "Grenade-Launcher", "Carbine", "Light Machine Gun", "Sniper Rifle", "Pistol", "Anti-Tank", "Sub-Machine Gun", "Shotgun", "Knife", "Defibrillator", "Explosives", "Grenade"] + weapons = [] + for i in range(len(weaponNames)): + try: + weapons.append({"name": weaponNames[i], "kills": int(stats["wkl-"+str(i)])}) + except: + pass + graphing = sorted(weapons, key=lambda k: k['kills'], reverse=True) + await grapher.main(message, graphing, stats, "bf2", "weapons") + except: + await message.reply("player not found") diff --git a/battlefield/handlers/__init__.py b/battlefield/handlers/__init__.py new file mode 100644 index 0000000..ae21e83 --- /dev/null +++ b/battlefield/handlers/__init__.py @@ -0,0 +1,3 @@ +from .bfstats import * +from .weapons import * +from .vehicles import * \ No newline at end of file diff --git a/battlefield/handlers/bfstats.py b/battlefield/handlers/bfstats.py new file mode 100644 index 0000000..5582d3e --- /dev/null +++ b/battlefield/handlers/bfstats.py @@ -0,0 +1,126 @@ +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters import Text +from aiogram import types +from ..bftracker import bf1, bf5 +from ..commands import bfstats +import re +from app import StatState, dp + +@dp.message_handler(commands=['bf2', 'bf2stat', 'bf2stats']) +async def bf2stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf2.set() + await message.reply("Give me a playername (\"/cancel\" to cancel)") + else: + await bfstats.bf2(message) + +@dp.message_handler(state=StatState.bf2) +async def bf2stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bfstats.bf2(message) + await state.finish() + + +@dp.message_handler(commands=['bf3', 'bf3stat', 'bf3stats']) +async def bf3stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf3.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await bfstats.bf3(message) + +@dp.message_handler(state=StatState.bf3) +async def bf3stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bfstats.bf3(message) + await state.finish() + + +@dp.message_handler(commands=['bfh', 'bfhstat', 'bfhstats']) +async def bfhstats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bfh.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await bfstats.bfh(message) + +@dp.message_handler(state=StatState.bfh) +async def bfhstats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bfstats.bfh(message) + await state.finish() + + +@dp.message_handler(commands=['bf4', 'bf4stat', 'bf4stats']) +async def bf4stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf4.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await bfstats.bf4(message) + +@dp.message_handler(state=StatState.bf4) +async def bf4stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bfstats.bf4(message) + await state.finish() + + +@dp.message_handler(commands=['bf1', 'bf1stat', 'bf1stats']) +async def bf1stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf1.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + result = await bf1.bf1TrackerPlatformSelect(message.text) + if result["current"] == "pc": + await bfstats.bf1(message, result["new_message"], "tunguska", "bf1") + else: + await bf1.bf1Tracker(message, result) + +@dp.message_handler(state=StatState.bf1) +async def bf1stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + result = await bf1.bf1TrackerPlatformSelect(message.text) + if result["current"] == "pc": + await bfstats.bf1(message, result["new_message"], "tunguska", "bf1") + else: + await bf1.bf1Tracker(message, result) + await state.finish() + + +@dp.message_handler(commands=['bf5', 'bf5stat', 'bf5stats', 'bfv', 'bfvstat', 'bfvstats']) +async def bf5stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf5.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await bf5.bf5stat(message) + +@dp.message_handler(state=StatState.bf5) +async def bf5stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bf5.bf5stat(message) + await state.finish() + + +@dp.message_handler(state='*', commands='cancel') +@dp.message_handler(Text(equals='cancel', ignore_case=True), state='*') +async def cancel_handler(message: types.Message, state: FSMContext): + """ + Allow user to cancel any action + """ + current_state = await state.get_state() + if current_state is None: + return + + # Cancel state and inform user about it + await state.finish() + # And remove keyboard (just in case) + await message.reply('Cancelled action.') diff --git a/battlefield/handlers/vehicles.py b/battlefield/handlers/vehicles.py new file mode 100644 index 0000000..5c7a54e --- /dev/null +++ b/battlefield/handlers/vehicles.py @@ -0,0 +1,110 @@ +from aiogram.dispatcher import FSMContext +from aiogram import types +from ..commands import vehicles +import re +from app import StatState, dp +from ..bftracker import bf1, bf5 + +@dp.message_handler(commands=['bf2vehiclegraph']) +async def bf2stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf2vehiclegraph.set() + await message.reply("Give me a playername (\"/cancel\" to cancel)") + else: + await vehicles.bf2Graph(message) + +@dp.message_handler(state=StatState.bf2vehiclegraph) +async def bf2stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await vehicles.bf2Graph(message) + await state.finish() + + +@dp.message_handler(commands=['bf3vehiclegraph']) +async def bf3stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf3vehiclegraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await vehicles.battlelogGraph(message, "vehiclesPopulateStats", "bf3") + +@dp.message_handler(state=StatState.bf3vehiclegraph) +async def bf3stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await vehicles.battlelogGraph(message, "vehiclesPopulateStats", "bf3") + await state.finish() + + +@dp.message_handler(commands=['bfhvehiclegraph']) +async def bfhstats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bfhvehiclegraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await vehicles.battlelogGraph(message, "bfhvehiclesPopulateStats", "bfh") + +@dp.message_handler(state=StatState.bfhvehiclegraph) +async def bfhstats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await vehicles.battlelogGraph(message, "bfhvehiclesPopulateStats", "bfh") + await state.finish() + + +@dp.message_handler(commands=['bf4vehiclegraph']) +async def bf4stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf4vehiclegraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await vehicles.battlelogGraph(message, "warsawvehiclesPopulateStats", "bf4") + +@dp.message_handler(state=StatState.bf4vehiclegraph) +async def bf4stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await vehicles.battlelogGraph(message, "warsawvehiclesPopulateStats", "bf4") + await state.finish() + + +@dp.message_handler(commands=['bfvehiclegraph', 'bf1vehiclegraph']) +async def bf1stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf1.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + result = await bf1.bf1TrackerPlatformSelect(message.text) + if result["current"] == "pc": + message.text = result["new_message"] + await vehicles.graph(message, "tunguska") + else: + await bf1.top10VehicleGraph(message, result) + +@dp.message_handler(state=StatState.bf1vehiclegraph) +async def bf1stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + result = await bf1.bf1TrackerPlatformSelect(message.text) + if result["current"] == "pc": + message.text = result["new_message"] + await vehicles.graph(message, "tunguska") + else: + await bf1.top10VehicleGraph(message, result) + + +@dp.message_handler(commands=['bf5vehiclegraph', 'bfvvehiclegraph']) +async def bf5stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf5vehiclegraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await bf5.bf5vehiclegraph(message) + +@dp.message_handler(state=StatState.bf5vehiclegraph) +async def bf5stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bf5.bf5vehiclegraph(message) + await state.finish() diff --git a/battlefield/handlers/weapons.py b/battlefield/handlers/weapons.py new file mode 100644 index 0000000..2068a55 --- /dev/null +++ b/battlefield/handlers/weapons.py @@ -0,0 +1,110 @@ +from aiogram.dispatcher import FSMContext +from aiogram import types +from ..commands import weapons +import re +from app import StatState, dp +from ..bftracker import bf1, bf5 + +@dp.message_handler(commands=['bf2weapongraph']) +async def bf2stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf2weapongraph.set() + await message.reply("Give me a playername (\"/cancel\" to cancel)") + else: + await weapons.bf2Graph(message) + +@dp.message_handler(state=StatState.bf2weapongraph) +async def bf2stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await weapons.bf2Graph(message) + await state.finish() + + +@dp.message_handler(commands=['bf3weapongraph']) +async def bf3stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf3weapongraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await weapons.battlelogGraph(message, "weaponsPopulateStats", "bf3") + +@dp.message_handler(state=StatState.bf3weapongraph) +async def bf3stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await weapons.battlelogGraph(message, "weaponsPopulateStats", "bf3") + await state.finish() + + +@dp.message_handler(commands=['bfhweapongraph']) +async def bfhstats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bfhweapongraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await weapons.battlelogGraph(message, "BFHWeaponsPopulateStats", "bfh") + +@dp.message_handler(state=StatState.bfhweapongraph) +async def bfhstats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await weapons.battlelogGraph(message, "BFHWeaponsPopulateStats", "bfh") + await state.finish() + + +@dp.message_handler(commands=['bf4weapongraph']) +async def bf4stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf4weapongraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await weapons.battlelogGraph(message, "warsawWeaponsPopulateStats", "bf4") + +@dp.message_handler(state=StatState.bf4weapongraph) +async def bf4stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await weapons.battlelogGraph(message, "warsawWeaponsPopulateStats", "bf4") + await state.finish() + + +@dp.message_handler(commands=['bfweapongraph', 'bf1weapongraph']) +async def bf1stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf1.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + result = await bf1.bf1TrackerPlatformSelect(message.text) + if result["current"] == "pc": + message.text = result["new_message"] + await weapons.graph(message, "tunguska") + else: + await bf1.top10WeaponGraph(message, result) + +@dp.message_handler(state=StatState.bf1weapongraph) +async def bf1stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + result = await bf1.bf1TrackerPlatformSelect(message.text) + if result["current"] == "pc": + message.text = result["new_message"] + await weapons.graph(message, "tunguska") + else: + await bf1.top10WeaponGraph(message, result) + + +@dp.message_handler(commands=['bf5weapongraph', 'bfvweapongraph']) +async def bf5stats(message: types.Message): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + if message.text == "": + await StatState.bf5weapongraph.set() + await message.reply("Give me a platform and playername (pc is default platform if none given, \"/cancel\" to cancel)") + else: + await bf5.bf5weapongraph(message) + +@dp.message_handler(state=StatState.bf5weapongraph) +async def bf5stats_return(message: types.Message, state: FSMContext): + message.text = re.sub(r"^/[a-zA-Z0-9_.-@]*", "", message.text).lstrip() + await bf5.bf5weapongraph(message) + await state.finish() diff --git a/battlefield/imageStats/fonts/Futura.ttf b/battlefield/imageStats/fonts/Futura.ttf new file mode 100644 index 0000000..97eded1 Binary files /dev/null and b/battlefield/imageStats/fonts/Futura.ttf differ diff --git a/battlefield/imageStats/images/bf1-1.png b/battlefield/imageStats/images/bf1-1.png new file mode 100644 index 0000000..39b0907 Binary files /dev/null and b/battlefield/imageStats/images/bf1-1.png differ diff --git a/battlefield/imageStats/images/bf2-1.png b/battlefield/imageStats/images/bf2-1.png new file mode 100644 index 0000000..7a6f19c Binary files /dev/null and b/battlefield/imageStats/images/bf2-1.png differ diff --git a/battlefield/imageStats/images/bf3-1.png b/battlefield/imageStats/images/bf3-1.png new file mode 100644 index 0000000..e0cb6a5 Binary files /dev/null and b/battlefield/imageStats/images/bf3-1.png differ diff --git a/battlefield/imageStats/images/bf4-1.png b/battlefield/imageStats/images/bf4-1.png new file mode 100644 index 0000000..bcf53c1 Binary files /dev/null and b/battlefield/imageStats/images/bf4-1.png differ diff --git a/battlefield/imageStats/images/bfh-1.png b/battlefield/imageStats/images/bfh-1.png new file mode 100644 index 0000000..f4048f7 Binary files /dev/null and b/battlefield/imageStats/images/bfh-1.png differ diff --git a/battlefield/imageStats/images/bfv-1.png b/battlefield/imageStats/images/bfv-1.png new file mode 100644 index 0000000..d7fc2c9 Binary files /dev/null and b/battlefield/imageStats/images/bfv-1.png differ diff --git a/battlefield/imageStats/main.py b/battlefield/imageStats/main.py new file mode 100644 index 0000000..1709ab8 --- /dev/null +++ b/battlefield/imageStats/main.py @@ -0,0 +1,134 @@ +import os +import io +from PIL import Image, ImageFont, ImageDraw, ImageChops +import aiohttp +from aiogram import types +import cairosvg + + +class imageStats: + async def crop_to_circle(im): + bigsize = (im.size[0] * 3, im.size[1] * 3) + mask = Image.new('L', bigsize, 0) + ImageDraw.Draw(mask).ellipse((0, 0) + bigsize, fill=255) + mask = mask.resize(im.size, Image.ANTIALIAS) + mask = ImageChops.darker(mask, im.split()[-1]) + im.putalpha(mask) + + async def imageRender(message: types.Message, stats, gameUrl, game, platform): + statsFont = ImageFont.truetype( + f"{os.path.dirname(os.path.realpath(__file__))}/fonts/Futura.ttf", size=38, index=0) + smallFont = ImageFont.truetype( + f"{os.path.dirname(os.path.realpath(__file__))}/fonts/Futura.ttf", size=28, index=0) + img = Image.open( + f"{os.path.dirname(os.path.realpath(__file__))}/images/{game}-1.png").convert("RGBA") + + async with aiohttp.ClientSession() as session: + try: + async with session.get(url=stats["avatar"]) as r: + avatarByte = await r.read() + avatar = Image.open(io.BytesIO(avatarByte)).convert('RGBA') + except: + async with session.get(url="https://eaassets-a.akamaihd.net/battlelog/defaultavatars/default-avatar-204.png?ssl=1") as r: + avatarByte = await r.read() + avatar = Image.open(io.BytesIO(avatarByte)).convert('RGBA') + if stats["rankImg"] == "": + stats["rankImg"] = "https://cdn.gametools.network/transparent.png" + async with session.get(url=stats["rankImg"]) as r: + rankImgByte = await r.read() + + if stats["rankImg"].endswith(".svg"): + rankImgByte = cairosvg.svg2png(bytestring=rankImgByte) + + await imageStats.crop_to_circle(avatar) + avatar.thumbnail((130, 130), Image.ANTIALIAS) + img.paste(avatar, (30, 152), avatar) + + rankImg = Image.open(io.BytesIO(rankImgByte)).convert('RGBA') + rankImg.thumbnail((48, 48), Image.ANTIALIAS) + img.paste(rankImg, (182, 240), rankImg) + + if platform in ["origin", "cem_ea_id"]: + platform = "pc" + elif platform in ["xbl", "xbox360", "xboxone"]: + platform = "xbox" + elif platform in ["ps3", "ps4"]: + platform = "psn" + + rankImg = Image.open( + f"{os.path.dirname(os.path.realpath(__file__))}/platforms/{platform}.png").convert('RGBA') + img.paste(rankImg, (887, 203), rankImg) + + draw = ImageDraw.Draw(img) + draw.text((182, 193), str(stats["name"]), font=statsFont) + draw.text((319, 248), str(stats["rank"]), font=smallFont) + + draw.text((30, 375), str(stats["0.0"]), font=statsFont) + draw.text((200, 375), str(stats["0.1"]), font=statsFont) + draw.text((392, 375), str(stats["0.2"]), font=statsFont) + draw.text((587, 375), str(stats["0.3"]), font=statsFont) + draw.text((820, 375), str(stats["0.4"]), font=statsFont) + + draw.text((30, 521), str(stats["1.0"]), font=statsFont) + draw.text((200, 521), str(stats["1.1"]), font=statsFont) + draw.text((392, 521), str(stats["1.2"]), font=statsFont) + draw.text((587, 521), str(stats["1.3"]), font=statsFont) + draw.text((820, 521), str(stats["1.4"]), font=statsFont) + + draw.text((30, 667), str(stats["2.0"]), font=statsFont) + draw.text((200, 667), str(stats["2.1"]), font=statsFont) + draw.text((392, 667), str(stats["2.2"]), font=statsFont) + draw.text((587, 667), str(stats["2.3"]), font=statsFont) + + with io.BytesIO() as data_stream: + img.save(data_stream, format="png") + data_stream.seek(0) + await message.reply_photo(data_stream, caption=f"Full stats: {gameUrl}") + + # older than bf3 (bf2) + async def oldBfImageRender(message: types.Message, stats, gameUrl, game): + statsFont = ImageFont.truetype( + f"{os.path.dirname(os.path.realpath(__file__))}/fonts/Futura.ttf", size=38, index=0) + smallFont = ImageFont.truetype( + f"{os.path.dirname(os.path.realpath(__file__))}/fonts/Futura.ttf", size=28, index=0) + smallestFont = ImageFont.truetype( + f"{os.path.dirname(os.path.realpath(__file__))}/fonts/Futura.ttf", size=24, index=0) + img = Image.open( + f"{os.path.dirname(os.path.realpath(__file__))}/images/{game}-1.png").convert("RGBA") + + async with aiohttp.ClientSession() as session: + async with session.get(url=stats["rankImg"]) as r: + rankImgByte = await r.read() + + rankImg = Image.open(io.BytesIO(rankImgByte)).convert('RGBA') + rankImg.thumbnail((48, 48), Image.ANTIALIAS) + img.paste(rankImg, (250, 295), rankImg) + + draw = ImageDraw.Draw(img) + draw.text((250, 252), str(stats["name"]), font=statsFont) + draw.text((376, 304), str(stats["rank"]), font=smallFont) + + draw.text((44, 421), str(stats["0.0"]), font=statsFont) + draw.text((223, 421), str(stats["0.1"]), font=statsFont) + draw.text((434, 421), str(stats["0.2"]), font=statsFont) + + w, h = draw.textsize(stats["0.3"], font=statsFont) + draw.text((710-w, 368), str(stats["0.3"]), font=statsFont) + w, h = draw.textsize(stats["0.4"], font=smallestFont) + draw.text((710-w, 420), str(stats["0.4"]), font=smallestFont) + + draw.text((44, 538), str(stats["1.0"]), font=statsFont) + draw.text((223, 538), str(stats["1.1"]), font=statsFont) + draw.text((434, 538), str(stats["1.2"]), font=statsFont) + + w, h = draw.textsize(stats["1.3"], font=statsFont) + draw.text((710-w, 488), str(stats["1.3"]), font=statsFont) + w, h = draw.textsize(stats["1.4"], font=smallestFont) + draw.text((710-w, 538), str(stats["1.4"]), font=smallestFont) + + draw.text((223, 662), str(stats["2.0"]), font=statsFont) + + with io.BytesIO() as data_stream: + img.save(data_stream, format="png") + data_stream.seek(0) + await message.reply_photo(data_stream, caption=f"Full stats: {gameUrl}") diff --git a/battlefield/imageStats/platforms/pc.png b/battlefield/imageStats/platforms/pc.png new file mode 100644 index 0000000..b33c226 Binary files /dev/null and b/battlefield/imageStats/platforms/pc.png differ diff --git a/battlefield/imageStats/platforms/psn.png b/battlefield/imageStats/platforms/psn.png new file mode 100644 index 0000000..c04f682 Binary files /dev/null and b/battlefield/imageStats/platforms/psn.png differ diff --git a/battlefield/imageStats/platforms/xbox.png b/battlefield/imageStats/platforms/xbox.png new file mode 100644 index 0000000..053c41b Binary files /dev/null and b/battlefield/imageStats/platforms/xbox.png differ diff --git a/battlefield/mongo.py b/battlefield/mongo.py new file mode 100644 index 0000000..5c6f411 --- /dev/null +++ b/battlefield/mongo.py @@ -0,0 +1,25 @@ +import motor.motor_asyncio as m_m_a +import os + + +class SingletonClient: + client = None + db = None + + @staticmethod + def get_client(): + if SingletonClient.client is None: + + SingletonClient.client = m_m_a.AsyncIOMotorClient( + os.environ.get("DB_URL", "") + ) + + return SingletonClient.client + + @staticmethod + def get_data_base(): + if SingletonClient.db is None: + client = SingletonClient.get_client() + SingletonClient.db = client["serverManager"] + + return SingletonClient.db diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aed1926 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +lxml~=4.8.0 +aiohttp~=3.7.4.post0 +requests~=2.25.1 +urllib3~=1.25.8 +matplotlib~=3.5.1 +pytz~=2022.1 +pycountry~=22.3.5 +pandas~=1.4.1 +openpyxl~=3.0.9 +redis~=3.5.3 +pymongo~=3.11.2 +motor~=2.4.0 +dnspython~=2.1.0 +aiosqlite~=0.17.0 +influxdb-client~=1.17.0 +pymysql~=1.0.2 +Pillow~=9.0.1 +CairoSVG~=2.5.2 +tabulate~=0.8.9 +discordspy~=0.1.2 +aiogram~=2.15 \ No newline at end of file