Skip to content

Commit ed1dd81

Browse files
committed
Move away from Steam client to full Stratz client
- A stratz token is necessary for the app to work - GSI is necessary for the app to work - Get game info from Stratz live graphql - Get player info from Stratz player graphql - Detect last game with Stratz instead of Opendota - Added player account level and country code
1 parent 96c8ce9 commit ed1dd81

298 files changed

Lines changed: 12983 additions & 1844 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ __pycache__
33

44
src/.wvenv/
55
src/.venv/
6-
src/dota_notes/data/sqlite.db
6+
src/dota_notes/data/models/sqlite.db
77

88
src/build
99
src/dist

src/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ unix-clean:
5454

5555
# Generate UI from Qt Designer files
5656
unix-gen-ui:
57-
./.venv/Scripts/pyside6-uic ./d2notes/ui/ui_main_window.ui -o ./d2notes/ui/ui_main_window.py --absolute-imports -python-paths .
57+
./.venv/bin/pyside6-uic ./d2notes/ui/ui_main_window.ui -o ./d2notes/ui/ui_main_window.py --absolute-imports -python-paths .
5858

5959
# Generate embedded resources from Qt resource file
6060
unix-gen-resources:
61-
./.venv/Scripts/pyside6-rcc ./d2notes/ui/resources.qrc -o ./d2notes/ui/resources_rc.py
61+
./.venv/bin/pyside6-rcc ./d2notes/ui/resources.qrc -o ./d2notes/ui/resources_rc.py
6262

6363
## Generate a production build
6464
unix-build:
65-
./.venv/Script/pyinstaller --noconfirm ./DotaNotes.spec
65+
./.venv/bin/pyinstaller --noconfirm ./DotaNotes.spec
6666

src/dota_notes/app_dota.py

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/dota_notes/app_flask.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from flask import Flask, request, jsonify
22

3+
from dota_notes.data.messages import MessageGSI
34

4-
def flask_process(port, match_info_queue):
5+
6+
def flask_process(port, message_queue_qt):
57
"""Flask app spawner"""
6-
flask_app = FlaskApp(port, match_info_queue)
8+
flask_app = FlaskApp(port, message_queue_qt)
79
flask_app.run()
810

911

@@ -12,36 +14,24 @@ class FlaskApp:
1214
1315
Args:
1416
port: port to listen to
15-
match_info_queue: Queue to transmit the match information
17+
message_queue_qt: Queue to transmit the match information to the qt app
1618
"""
17-
def __init__(self, port, match_info_queue):
19+
def __init__(self, port, message_queue_qt):
1820
self.app = Flask(__name__)
1921
self.port = port
20-
self.match_info_queue = match_info_queue
22+
self.message_queue_qt = message_queue_qt
2123
self.last_match_id_sent = 0
2224

2325
@self.app.route('/', methods=['POST'])
2426
def gsi_endpoint():
2527
payload = request.get_json()
26-
if ('map' in payload and "matchid" in payload["map"] and
27-
"player" in payload and
28-
"team2" in payload["player"] and
29-
len(payload["player"]["team2"]) > 1):
30-
info = {
31-
"match_id": int(payload["map"]["matchid"]),
32-
"players": []}
33-
if self.last_match_id_sent == info["match_id"] or info["match_id"] == 0:
28+
if 'map' in payload and "matchid" in payload["map"] and payload["map"]["matchid"] is not None:
29+
info = MessageGSI(int(payload["map"]["matchid"]))
30+
if self.last_match_id_sent == info.match_id or info.match_id == 0:
3431
return jsonify({})
35-
else:
36-
self.last_match_id_sent = info["match_id"]
37-
38-
for team in payload["player"].values():
39-
for player in team.values():
40-
if "accountid" not in player or "name" not in player:
41-
continue
42-
info["players"].append({"accountid": int(player["accountid"]), "name": player["name"]})
4332

44-
self.match_info_queue.put(info)
33+
self.last_match_id_sent = info.match_id
34+
self.message_queue_qt.put(info)
4535
return jsonify({})
4636

4737
def run(self):

src/dota_notes/data/messages.py

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,6 @@
1-
class MessageConnect:
2-
"""Ask the client to connect with specific credentials
1+
class MessageGSI:
2+
"""MatchId send by the GSI"""
3+
match_id: str
34

4-
Attributes:
5-
user: steam username
6-
password: steam password
7-
"""
8-
user: str
9-
password: str
10-
11-
def __init__(self, user: str, password: str):
12-
self.user = user
13-
self.password = password
14-
15-
16-
class MessageConnectionStatus:
17-
"""Report the status of the connections of steam/dota clients
18-
19-
Attributes:
20-
steam: Steam connection status
21-
dota: Dota connection status
22-
"""
23-
steam: str
24-
dota: str
25-
26-
def __init__(self, steam: str, dota: str):
27-
self.steam = steam
28-
self.dota = dota
29-
30-
31-
class MessageServerIdRequest:
32-
"""Request the server ID a specific account is playing on"""
33-
account_id: str
34-
35-
def __init__(self, account_id: str):
36-
self.account_id = account_id
37-
38-
39-
class MessageServerIdResponse:
40-
"""Server ID where a specific player is playing on"""
41-
server_id: int
42-
43-
def __init__(self, server_id: int):
44-
self.server_id = server_id
5+
def __init__(self, match_id):
6+
self.match_id = match_id

src/dota_notes/data/models/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from sqlalchemy.orm import DeclarativeBase
2+
3+
4+
class BaseEntity(DeclarativeBase):
5+
"""Database model base class"""
6+
pass
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
import sys
3+
4+
from sqlalchemy import create_engine
5+
from sqlalchemy.orm import Session
6+
7+
from dota_notes.data.models.base_entity import BaseEntity
8+
from dota_notes.data.models.settings_entity import SettingEntity
9+
10+
11+
class Database:
12+
"""Singleton defining database URI and unique ressources.
13+
14+
Attributes:
15+
_instance: Singleton instance
16+
uri: database location
17+
engine: database connection used for session generation
18+
"""
19+
20+
_instance = None
21+
22+
def __new__(cls, *args, **kwargs):
23+
"""New overload to create a singleton."""
24+
if not isinstance(cls._instance, cls):
25+
cls._instance = object.__new__(cls)
26+
return cls._instance
27+
28+
def __init__(self):
29+
"""Defines all necessary ressources (URI & engine) and create database if necessary."""
30+
if getattr(sys, 'frozen', False):
31+
file_uri = os.path.dirname(sys.executable)
32+
elif __file__:
33+
file_uri = os.path.dirname(__file__)
34+
self.uri = f"sqlite+pysqlite:///{file_uri}/sqlite.db"
35+
self.engine = create_engine(self.uri, echo=False)
36+
BaseEntity.metadata.create_all(self.engine)
37+
38+
with Session(self.engine) as session:
39+
if session.get(SettingEntity, "version") is None:
40+
session.add(SettingEntity("version", "1"))
41+
session.commit()

src/dota_notes/data/models.py renamed to src/dota_notes/data/models/player_entity.py

Lines changed: 16 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,9 @@
1-
import os
2-
import sys
31
from typing import Optional
42

5-
from sqlalchemy import create_engine, String
6-
from sqlalchemy.orm import Session, DeclarativeBase, mapped_column, Mapped
3+
from sqlalchemy import String
4+
from sqlalchemy.orm import Mapped, mapped_column
75

8-
9-
class Database(object):
10-
"""Singleton defining database URI and unique ressources.
11-
12-
Attributes:
13-
_instance: Singleton instance
14-
uri: database location
15-
engine: database connection used for session generation
16-
"""
17-
18-
_instance = None
19-
20-
def __new__(cls, *args, **kwargs):
21-
"""New overload to create a singleton."""
22-
if not isinstance(cls._instance, cls):
23-
cls._instance = object.__new__(cls)
24-
return cls._instance
25-
26-
def __init__(self):
27-
"""Defines all necessary ressources (URI & engine) and create database if necessary."""
28-
if getattr(sys, 'frozen', False):
29-
file_uri = os.path.dirname(sys.executable)
30-
elif __file__:
31-
file_uri = os.path.dirname(__file__)
32-
self.uri = 'sqlite+pysqlite:///{0}/sqlite.db'.format(file_uri)
33-
self.engine = create_engine(self.uri, echo=False)
34-
BaseEntity.metadata.create_all(self.engine)
35-
36-
with Session(self.engine) as session:
37-
if session.get(SettingEntity, "version") is None:
38-
session.add(SettingEntity("version", "1"))
39-
session.commit()
40-
41-
42-
class BaseEntity(DeclarativeBase):
43-
"""Database model base class"""
44-
pass
45-
46-
47-
class SettingEntity(BaseEntity):
48-
"""An application setting.
49-
50-
Attributes:
51-
key: unique string defining a setting
52-
value: value of the setting
53-
"""
54-
__tablename__ = 'settings'
55-
56-
key: Mapped[str] = mapped_column(primary_key=True)
57-
value: Mapped[str] = mapped_column()
58-
59-
def __init__(self, key, value):
60-
self.key = key
61-
self.value = value
62-
63-
def __repr__(self) -> str:
64-
return f"Setting(key={self.key!r}, value={self.value!r})"
6+
from dota_notes.data.models.base_entity import BaseEntity
657

668

679
class PlayerEntity(BaseEntity):
@@ -72,7 +14,9 @@ class PlayerEntity(BaseEntity):
7214
name: last seen name
7315
pro_name: pro name (if fetched from the API)
7416
custom_name: user set name
17+
match_count: number of games played by the user (set by stratz)
7518
smurf: user defined smurf indicator
19+
smurf_stratz: smurf flag (set by stratz)
7620
is_racist: flag
7721
is_sexist: flag
7822
is_toxic: flag
@@ -87,7 +31,9 @@ class PlayerEntity(BaseEntity):
8731
name: Mapped[str] = mapped_column()
8832
pro_name: Mapped[Optional[str]] = mapped_column()
8933
custom_name: Mapped[str] = mapped_column()
34+
match_count: Mapped[Optional[int]] = mapped_column()
9035
smurf: Mapped[str] = mapped_column()
36+
smurf_stratz: Mapped[Optional[int]] = mapped_column()
9137
is_racist: Mapped[bool] = mapped_column()
9238
is_sexist: Mapped[bool] = mapped_column()
9339
is_toxic: Mapped[bool] = mapped_column()
@@ -96,13 +42,16 @@ class PlayerEntity(BaseEntity):
9642
destroys_items: Mapped[bool] = mapped_column()
9743
note: Mapped[str] = mapped_column(String(500))
9844

99-
def __init__(self, steam_id, name, pro_name=None, custom_name="", smurf="", is_racist=False, is_sexist=False,
100-
is_toxic=False, is_feeder=False, gives_up=False, destroys_items=False, note=""):
45+
def __init__(self, steam_id, name, pro_name=None, custom_name="", match_count=None, smurf="", smurf_stratz=None,
46+
is_racist=False, is_sexist=False, is_toxic=False, is_feeder=False, gives_up=False,
47+
destroys_items=False, note=""):
10148
self.steam_id = steam_id
10249
self.name = name
10350
self.pro_name = pro_name
10451
self.custom_name = custom_name
52+
self.match_count = match_count
10553
self.smurf = smurf
54+
self.smurf_stratz = smurf_stratz
10655
self.is_racist = is_racist
10756
self.is_sexist = is_sexist
10857
self.is_toxic = is_toxic
@@ -123,7 +72,9 @@ def make_from_state(player_state):
12372
player_state.name,
12473
player_state.pro_name if player_state.pro_name != "" else None,
12574
player_state.custom_name,
75+
player_state.match_count,
12676
player_state.smurf,
77+
player_state.smurf_stratz,
12778
player_state.is_racist,
12879
player_state.is_sexist,
12980
player_state.is_toxic,
@@ -141,7 +92,9 @@ def import_export(from_object, to_object):
14192
"""
14293
to_object.pro_name = from_object.pro_name
14394
to_object.custom_name = from_object.custom_name
95+
to_object.match_count = from_object.match_count
14496
to_object.smurf = from_object.smurf
97+
to_object.smurf_stratz = from_object.smurf_stratz
14598
to_object.is_racist = from_object.is_racist
14699
to_object.is_sexist = from_object.is_sexist
147100
to_object.is_toxic = from_object.is_toxic

0 commit comments

Comments
 (0)