[boardgames] Add lichess importing
This commit is contained in:
93
poetry.lock
generated
93
poetry.lock
generated
@ -294,6 +294,24 @@ charset-normalizer = ["charset-normalizer"]
|
||||
html5lib = ["html5lib"]
|
||||
lxml = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "berserk"
|
||||
version = "0.13.2"
|
||||
description = "Python client for the lichess API"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "berserk-0.13.2-py3-none-any.whl", hash = "sha256:0f7fc40f152370924cb05a77c3f1c357a91e8ff0db60d23c14f0f16216b632a8"},
|
||||
{file = "berserk-0.13.2.tar.gz", hash = "sha256:96c3ff3a10407842019e5e6bf3233080030419e4eba333bbd4234a86b4eff86f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
deprecated = ">=1.2.14,<2.0.0"
|
||||
ndjson = ">=0.3.1,<0.4.0"
|
||||
python-dateutil = ">=2.8.2,<3.0.0"
|
||||
requests = ">=2.28.2,<3.0.0"
|
||||
typing-extensions = ">=4.7.1,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "billiard"
|
||||
version = "4.2.1"
|
||||
@ -360,17 +378,17 @@ css = ["tinycss2 (>=1.1.0,<1.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.36.3"
|
||||
version = "1.36.8"
|
||||
description = "The AWS SDK for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "boto3-1.36.3-py3-none-any.whl", hash = "sha256:f9843a5d06f501d66ada06f5a5417f671823af2cf319e36ceefa1bafaaaaa953"},
|
||||
{file = "boto3-1.36.3.tar.gz", hash = "sha256:53a5307f6a3526ee2f8590e3c45efa504a3ea4532c1bfe4926c0c19bf188d141"},
|
||||
{file = "boto3-1.36.8-py3-none-any.whl", hash = "sha256:7f61c9d0ea64f484a17c1e3115fdf90fd7b17ab6771e07cb4549f42b9fd28fb9"},
|
||||
{file = "boto3-1.36.8.tar.gz", hash = "sha256:ac47215d320b0c2534340db58d6d5284cb1860b7bff172b4dd6eee2dee1d5779"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.36.3,<1.37.0"
|
||||
botocore = ">=1.36.8,<1.37.0"
|
||||
jmespath = ">=0.7.1,<2.0.0"
|
||||
s3transfer = ">=0.11.0,<0.12.0"
|
||||
|
||||
@ -379,13 +397,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.36.3"
|
||||
version = "1.36.8"
|
||||
description = "Low-level, data-driven core of boto 3."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "botocore-1.36.3-py3-none-any.whl", hash = "sha256:536ab828e6f90dbb000e3702ac45fd76642113ae2db1b7b1373ad24104e89255"},
|
||||
{file = "botocore-1.36.3.tar.gz", hash = "sha256:775b835e979da5c96548ed1a0b798101a145aec3cd46541d62e27dda5a94d7f8"},
|
||||
{file = "botocore-1.36.8-py3-none-any.whl", hash = "sha256:59d3fdfbae6d916b046e973bebcbeb70a102f9e570ca86d5ba512f1854b78fc2"},
|
||||
{file = "botocore-1.36.8.tar.gz", hash = "sha256:81c88e5566cf018e1411a68304dc1fb9e4156ca2b50a3a0f0befc274299e67fa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -963,6 +981,23 @@ files = [
|
||||
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deprecated"
|
||||
version = "1.2.18"
|
||||
description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
||||
files = [
|
||||
{file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"},
|
||||
{file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
wrapt = ">=1.10,<2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "dj-database-url"
|
||||
version = "0.5.0"
|
||||
@ -2270,6 +2305,17 @@ files = [
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndjson"
|
||||
version = "0.3.1"
|
||||
description = "JsonDecoder for ndjson"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ndjson-0.3.1-py2.py3-none-any.whl", hash = "sha256:839c22275e6baa3040077b83c005ac24199b94973309a8a1809be962c753a410"},
|
||||
{file = "ndjson-0.3.1.tar.gz", hash = "sha256:bf9746cb6bb1cb53d172cda7f154c07c786d665ff28341e4e689b796b229e5d6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.2.2"
|
||||
@ -3792,20 +3838,6 @@ files = [
|
||||
[package.dependencies]
|
||||
types-urllib3 = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.0.20241016"
|
||||
description = "Typing stubs for requests"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"},
|
||||
{file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
urllib3 = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "types-urllib3"
|
||||
version = "1.26.25.14"
|
||||
@ -3872,23 +3904,6 @@ brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotl
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.3.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
|
||||
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "vine"
|
||||
version = "5.1.0"
|
||||
@ -4168,4 +4183,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<4.0"
|
||||
content-hash = "47922140929eccdcdf8eabb43e4b34af9ce0c5d395948385e7a861036c43ab3c"
|
||||
content-hash = "1b3b34d6e5e5db0f5192c5d2f3054cf2b747f2833a4304167d55c6e1c8e0de00"
|
||||
|
||||
@ -47,8 +47,10 @@ thefuzz = "^0.22.1"
|
||||
dataclass-wizard = "0.22.0"
|
||||
webdavclient3 = "^3.14.6"
|
||||
boto3 = "^1.35.37"
|
||||
urllib3 = "<2"
|
||||
django-oauth-toolkit = "^3.0.1"
|
||||
meta-yt = "^0.1.9"
|
||||
berserk = "^0.13.2"
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
@ -24,6 +24,7 @@ VROBBLER_COMICVINE_API_KEY="<key>"
|
||||
VROBBLER_TODOIST_CLIENT_ID="<id>"
|
||||
VROBBLER_TODOIST_CLIENT_SECRET="<key>"
|
||||
VROBBLER_GOOGLE_API_KEY="<key>"
|
||||
VROBBLER_LICHESS_API_KEY = "<key>"
|
||||
|
||||
# Storages
|
||||
# VROBBLER_DATABASE_URL="postgres://USER:PASSWORD@HOST:PORT/NAME"
|
||||
|
||||
119
vrobbler/apps/boardgames/sources/lichess.py
Normal file
119
vrobbler/apps/boardgames/sources/lichess.py
Normal file
@ -0,0 +1,119 @@
|
||||
import berserk
|
||||
from django.conf import settings
|
||||
|
||||
from boardgames.models import BoardGame
|
||||
from scrobbles.models import Scrobble
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def import_chess_games_for_all_users():
|
||||
client = berserk.Client(
|
||||
session=berserk.TokenSession(settings.LICHESS_API_KEY)
|
||||
)
|
||||
|
||||
scrobbles_to_create = []
|
||||
for user in User.objects.filter(profile__lichess_username__isnull=False):
|
||||
games = client.games.export_by_player(user.profile.lichess_username)
|
||||
for game_dict in games:
|
||||
chess, created = BoardGame.objects.get_or_create(title="Chess")
|
||||
if created:
|
||||
chess.run_time_seconds = 1800
|
||||
chess.bggeek_id = 171
|
||||
chess.save(update_fields=["run_time_seconds", "bggeek_id"])
|
||||
scrobble = Scrobble.objects.filter(
|
||||
user_id=user.id,
|
||||
timestamp=game_dict.get("createdAt"),
|
||||
board_game_id=chess.id,
|
||||
).first()
|
||||
|
||||
if scrobble:
|
||||
continue
|
||||
|
||||
log_data = {
|
||||
"variant": game_dict.get("variant"),
|
||||
"lichess_id": game_dict.get("id"),
|
||||
"rated": game_dict.get("rated"),
|
||||
"speed": game_dict.get("speed"),
|
||||
"moves": game_dict.get("moves"),
|
||||
"players": [],
|
||||
}
|
||||
|
||||
chess_status = game_dict.get("status")
|
||||
chess_source = game_dict.get("source")
|
||||
|
||||
winner = game_dict.get("winner")
|
||||
black_player = game_dict.get("players", {}).get("black", {})
|
||||
white_player = game_dict.get("players", {}).get("white", {})
|
||||
|
||||
user_player = {
|
||||
"user_id": user.profile.lichess_username,
|
||||
"color": "",
|
||||
"win": False,
|
||||
}
|
||||
other_player = {"name_str": "", "color": "", "win": False}
|
||||
|
||||
if (
|
||||
black_player.get("user", {}).get("name", "")
|
||||
== user.profile.lichess_username
|
||||
):
|
||||
user_player["color"] = "black"
|
||||
if "aiLevel" in white_player.keys():
|
||||
other_player["name_str"] = "aiLevel_" + str(
|
||||
white_player.get("aiLevel", "")
|
||||
)
|
||||
else:
|
||||
other_player["name_str"] = white_player.get(
|
||||
"user", {}
|
||||
).get("name", "")
|
||||
|
||||
other_player["color"] = "white"
|
||||
if winner == "black":
|
||||
user_player["win"] = True
|
||||
else:
|
||||
other_player["win"] = True
|
||||
if (
|
||||
white_player.get("user", {}).get("name", "")
|
||||
== user.profile.lichess_username
|
||||
):
|
||||
user_player["color"] = "white"
|
||||
if "aiLevel" in black_player.keys():
|
||||
other_player["name_str"] = "aiLevel_" + str(
|
||||
black_player.get("aiLevel", "")
|
||||
)
|
||||
else:
|
||||
other_player["name_str"] = white_player.get(
|
||||
"user", {}
|
||||
).get("name", "")
|
||||
other_player["color"] = "black"
|
||||
if winner == "white":
|
||||
user_player["win"] = True
|
||||
else:
|
||||
other_player["win"] = True
|
||||
|
||||
log_data["players"].append(user_player)
|
||||
log_data["players"].append(other_player)
|
||||
|
||||
scrobble_dict = {
|
||||
"user_id": user.id,
|
||||
"timestamp": game_dict.get("createdAt"),
|
||||
"stop_timestamp": game_dict.get("lastMoveAt"),
|
||||
"board_game_id": chess.id,
|
||||
"log": log_data,
|
||||
}
|
||||
scrobbles_to_create.append(Scrobble(**scrobble_dict))
|
||||
|
||||
if scrobbles_to_create:
|
||||
Scrobble.objects.bulk_create(scrobbles_to_create)
|
||||
return scrobbles_to_create
|
||||
|
||||
|
||||
# 'players': {
|
||||
# 'white': {'aiLevel': 1},
|
||||
# 'black': {'user': {'name': 'secstate', 'id': 'secstate'},
|
||||
# 'rating': 1500,
|
||||
# 'provisional': True}
|
||||
# },
|
||||
# 'fullId': '4T8CinfXdI95',
|
||||
# 'winner': 'black',
|
||||
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.18 on 2025-01-29 04:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"profiles",
|
||||
"0020_userprofile_ntfy_enabled_userprofile_ntfy_url_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="lichess_username",
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@ -30,6 +30,7 @@ class UserProfile(TimeStampedModel):
|
||||
archivebox_url = models.CharField(max_length=255, **BNULL)
|
||||
|
||||
bgg_username = models.CharField(max_length=255, **BNULL)
|
||||
lichess_username = models.CharField(max_length=255, **BNULL)
|
||||
|
||||
todoist_auth_key = EncryptedField(**BNULL)
|
||||
todoist_state = EncryptedField(**BNULL)
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from vrobbler.apps.boardgames.sources.lichess import (
|
||||
import_chess_games_for_all_users,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
count = len(import_chess_games_for_all_users())
|
||||
print(f"Imported {count} Lichess games")
|
||||
@ -58,6 +58,12 @@ LASTFM_SECRET_KEY = os.getenv("VROBBLER_LASTFM_SECRET_KEY")
|
||||
IGDB_CLIENT_ID = os.getenv("VROBBLER_IGDB_CLIENT_ID")
|
||||
IGDB_CLIENT_SECRET = os.getenv("VROBBLER_IGDB_CLIENT_SECRET")
|
||||
|
||||
TODOIST_CLIENT_ID = os.getenv("VROBBLER_TODOIST_CLIENT_ID", "")
|
||||
TODOIST_CLIENT_SECRET = os.getenv("VROBBLER_TODOIST_CLIENT_SECRET", "")
|
||||
|
||||
GOOGLE_API_KEY = os.getenv("VROBBLER_GOOGLE_API_KEY", "")
|
||||
LICHESS_API_KEY = os.getenv("VROBBLER_LICHESS_API_KEY", "")
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "US/Eastern")
|
||||
|
||||
@ -75,6 +75,7 @@ TODOIST_CLIENT_ID = os.getenv("VROBBLER_TODOIST_CLIENT_ID", "")
|
||||
TODOIST_CLIENT_SECRET = os.getenv("VROBBLER_TODOIST_CLIENT_SECRET", "")
|
||||
|
||||
GOOGLE_API_KEY = os.getenv("VROBBLER_GOOGLE_API_KEY", "")
|
||||
LICHESS_API_KEY = os.getenv("VROBBLER_LICHESS_API_KEY", "")
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user