Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
w4rum committed Mar 20, 2021
0 parents commit b1267e1
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.idea
__pycache__
config.json
build
dist
venv
*.spec
5 changes: 5 additions & 0 deletions config-sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"player_name": "REPLACE_ME",
"eft_install_dir": "REPLACE_ME",
"webhook_url": "REPLACE_ME"
}
200 changes: 200 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import asyncio
import logging
import msvcrt
import os
import re
import sys
from dataclasses import dataclass
from typing import TextIO, AsyncIterator, Tuple

import aiohttp
import discord
import win32file
from aiohttp_requests import requests
from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Config:
player_name: str
eft_install_dir: str
webhook_url: str


config: Config
logger: logging.Logger


def setup_logging(*, debug_on_stdout=False) -> None:
formatter = logging.Formatter(
"[%(asctime)s][%(levelname)s][%(name)s][%(funcName)s] %(message)s")

stdout_level = logging.DEBUG if debug_on_stdout else logging.INFO
log_stdout = logging.StreamHandler(stream=sys.stdout)
log_stdout.setLevel(stdout_level)
log_stdout.setFormatter(formatter)

log_stderr = logging.StreamHandler(stream=sys.stderr)
log_stderr.setLevel(logging.WARNING)
log_stderr.setFormatter(formatter)

# filter discord and websocket on stdout
def filter_discord(record):
return not (record.name.startswith("discord.")
or record.name.startswith("websockets."))

# filter WARNING and above on stdout
def filter_above_info(record):
return record.levelno <= logging.INFO

log_stdout.addFilter(filter_discord)
log_stderr.addFilter(filter_discord)
log_stdout.addFilter(filter_above_info)

logging.basicConfig(level=logging.NOTSET,
handlers=[log_stdout, log_stderr])

global logger
logger = logging.getLogger(__name__)


def get_newest_log_filename() -> str:
# get newest log directory
newest_entry = None
newest_time = -1
for entry in os.scandir(f"{config.eft_install_dir}\\Logs\\"):
entry: os.DirEntry
cur_time = entry.stat().st_ctime
if cur_time > newest_time:
newest_time = cur_time
newest_entry = entry

assert newest_entry is not None, "no log directories, start EFT before starting this script"

# get date prefix of the log filename
_, _, log_filename_prefix = newest_entry.name.partition("log_")

return f"{config.eft_install_dir}\\Logs\\{newest_entry.name}\\{log_filename_prefix} application.log"


def open_log_file() -> Tuple[TextIO, str]:
log_filename = get_newest_log_filename()

logger.debug(f"Opening log file {log_filename}")
# source:
# https://www.thepythoncorner.com/2016/10/python-how-to-open-a-file-on-windows-without-locking-it/
# get a handle using win32 API, specifying SHARED access!
handle = win32file.CreateFile(log_filename,
win32file.GENERIC_READ,
win32file.FILE_SHARE_DELETE |
win32file.FILE_SHARE_READ |
win32file.FILE_SHARE_WRITE,
None,
win32file.OPEN_EXISTING,
0,
None)
# detach the handle
detached_handle = handle.Detach()
# get a file descriptor associated to the handle
file_descriptor = msvcrt.open_osfhandle(
detached_handle, os.O_RDONLY)
# open the file descriptor
f = open(file_descriptor, encoding="UTF-8")
# seek to end
f.seek(0, os.SEEK_END)

logger.debug(f"Opened log file {log_filename}")
return f, log_filename


async def log_follow() -> AsyncIterator[str]:
f, f_name = open_log_file()
no_new_lines_counter = 0
# read indefinitely
while True:
# read until end of file
while True:
try:
line = f.readline()
except UnicodeDecodeError:
sys.stderr.write(
"[WARN] Skipped line because of decode error\n")
line = "DECODE_ERROR"
logger.debug(f"DECODE_ERROR")
# check if we're at EOF
if not line:
no_new_lines_counter += 1
break
else:
no_new_lines_counter = 0
logger.debug(f"[READ]{line}")
yield line

# if we've seen no new lines for 30 iterations, check if there is a new log file
if no_new_lines_counter > 30:
no_new_lines_counter = 0
f_new, f_new_name = open_log_file()
# If it's a different file, replace old handle. Otherwise, discard new handle.
if f_new_name != f_name:
logger.debug("new log file")
f.close()
f = f_new
else:
logger.debug("no new log file")
f_new.close()

logger.debug(f"GOING_TO_SLEEP")
await asyncio.sleep(1)
logger.debug(f"WOKE_UP")


async def parse_line(line):
match = re.search(r"Status: Busy, Ip: ([^,]+).*shortId: (....)", line)
if match is None:
return
ip, lobby_id = match.group(1, 2)

response = await requests.get(f"http://ip-api.com/json/{ip}")
response_json = await response.json()

if not response_json["status"] == "success":
logger.error(f"unsuccessfull ip location query: {response_json}")
return

await post_location(lobby_id, response_json["country"])


async def post_location(lobby_id: str, country: str) -> None:
logger.info(f"Posting: lobby_id {lobby_id}, county {country}")
embed = discord.Embed(title=config.player_name)
embed.add_field(name="Lobby ID", value=lobby_id)
embed.add_field(name="Country", value=country)

# Send message via webhook
async with aiohttp.ClientSession() as session:
webhook = discord.Webhook.from_url(config.webhook_url,
adapter=discord.AsyncWebhookAdapter(session))
await webhook.send(embed=embed)


async def main() -> None:
async for line in log_follow():
await parse_line(line)


if __name__ == "__main__":
setup_logging(debug_on_stdout=False)
logger = logging.getLogger(__name__)
logger.info("Reading config...")

if not os.path.isfile("config.json"):
logger.error("No config found. Make sure you have a config.json next to this script. Press any key to exit...")
input()
sys.exit(1)

with open("config.json", "r") as config_file:
config = Config.from_json(config_file.read())

logger.info("Started. Keep this script open.")
asyncio.run(main())
1 change: 1 addition & 0 deletions package.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.\venv\Scripts\pyinstaller.exe -F main.py
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
aiohttp
aiohttp-requests
dataclasses-json
discord.py
pyinstaller
pywin32

0 comments on commit b1267e1

Please sign in to comment.