steam sync
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-04-02 21:11:54 +03:00
parent 457b8c8443
commit f820e86277
5 changed files with 88 additions and 1 deletions

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('backlogger', '0009_userprofile'),
]
operations = [
migrations.AddField(
model_name='item',
name='steam_appid',
field=models.IntegerField(blank=True, null=True),
),
]

View File

@@ -60,6 +60,9 @@ class Item(models.Model):
watched = models.BooleanField(null=True, blank=True) watched = models.BooleanField(null=True, blank=True)
duration_minutes = models.IntegerField(null=True, blank=True) duration_minutes = models.IntegerField(null=True, blank=True)
# Steam
steam_appid = models.IntegerField(null=True, blank=True)
# HowLongToBeat estimates (games only) # HowLongToBeat estimates (games only)
hltb_main = models.FloatField(null=True, blank=True) hltb_main = models.FloatField(null=True, blank=True)
hltb_extra = models.FloatField(null=True, blank=True) hltb_extra = models.FloatField(null=True, blank=True)

View File

@@ -202,7 +202,20 @@
{% if request.GET.imported %} {% if request.GET.imported %}
<span style="font-size:0.82rem;color:#34d399">&#10003; {{ request.GET.imported }} game{{ request.GET.imported|pluralize }} imported</span> <span style="font-size:0.82rem;color:#34d399">&#10003; {{ request.GET.imported }} game{{ request.GET.imported|pluralize }} imported</span>
{% endif %} {% endif %}
<a href="{% url 'backlogger:steam_login' %}" class="btn btn-outline" style="font-size:0.82rem">&#9654; Steam</a> {% if request.GET.synced %}
<span style="font-size:0.82rem;color:#34d399">&#10003; {{ request.GET.synced }} game{{ request.GET.synced|pluralize }} synced</span>
{% endif %}
{% if request.GET.sync_error %}
<span style="font-size:0.82rem;color:#f87171">Steam sync failed</span>
{% endif %}
<a href="{% url 'backlogger:steam_sync_login' %}" class="btn btn-outline" style="font-size:0.82rem" title="Sync hours played from Steam">&#8635; Sync</a>
<a href="{% url 'backlogger:steam_login' %}" class="btn btn-outline" style="font-size:0.82rem">&#9654; Import</a>
{% if debug %}
<form method="post" action="{% url 'backlogger:debug_delete_all' %}" onsubmit="return confirm('Delete ALL items?')">
{% csrf_token %}
<button type="submit" class="btn btn-danger" style="font-size:0.82rem">&#128465; Delete all</button>
</form>
{% endif %}
<a href="{% url 'backlogger:add' %}" class="btn btn-primary">+ Add item</a> <a href="{% url 'backlogger:add' %}" class="btn btn-primary">+ Add item</a>
</div> </div>
</div> </div>
@@ -229,6 +242,7 @@
<option value="newest" {% if sort == 'newest' %}selected{% endif %}>Newest first</option> <option value="newest" {% if sort == 'newest' %}selected{% endif %}>Newest first</option>
<option value="oldest" {% if sort == 'oldest' %}selected{% endif %}>Oldest first</option> <option value="oldest" {% if sort == 'oldest' %}selected{% endif %}>Oldest first</option>
<option value="progress" {% if sort == 'progress' %}selected{% endif %}>Most complete</option> <option value="progress" {% if sort == 'progress' %}selected{% endif %}>Most complete</option>
<option value="updated" {% if sort == 'updated' %}selected{% endif %}>Recently updated</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -12,4 +12,7 @@ urlpatterns = [
path('steam/login/', views.steam_login, name='steam_login'), path('steam/login/', views.steam_login, name='steam_login'),
path('steam/callback/', views.steam_callback, name='steam_callback'), path('steam/callback/', views.steam_callback, name='steam_callback'),
path('steam/import/', views.steam_import, name='steam_import'), path('steam/import/', views.steam_import, name='steam_import'),
path('debug/delete-all/', views.debug_delete_all, name='debug_delete_all'),
path('steam/sync/', views.steam_sync_login, name='steam_sync_login'),
path('steam/sync/callback/', views.steam_sync_callback, name='steam_sync_callback'),
] ]

View File

@@ -28,6 +28,7 @@ SORT_MAP = {
'newest': ['-created_at'], 'newest': ['-created_at'],
'oldest': ['created_at'], 'oldest': ['created_at'],
'progress': ['-progress_percent'], 'progress': ['-progress_percent'],
'updated': ['-updated_at'],
} }
@@ -65,6 +66,7 @@ def item_list(request):
'sort': sort, 'sort': sort,
'shelf': shelf, 'shelf': shelf,
'categories': Item.CATEGORY_CHOICES, 'categories': Item.CATEGORY_CHOICES,
'debug': settings.DEBUG,
}) })
@@ -180,9 +182,58 @@ def steam_import(request):
name=game['name'], name=game['name'],
hours_played=hours, hours_played=hours,
progress_percent=progress, progress_percent=progress,
steam_appid=game['appid'],
) )
hltb_api.apply_to_item(item) hltb_api.apply_to_item(item)
imported += 1 imported += 1
del request.session['steam_games'] del request.session['steam_games']
return redirect(f"{reverse('backlogger:list')}?category=games&imported={imported}") return redirect(f"{reverse('backlogger:list')}?category=games&imported={imported}")
@login_required
def debug_delete_all(request):
if not settings.DEBUG:
return redirect('backlogger:list')
if request.method == 'POST':
Item.objects.filter(user=request.user).delete()
return redirect('backlogger:list')
@login_required
def steam_sync_login(request):
callback = request.build_absolute_uri(reverse('backlogger:steam_sync_callback'))
realm = f"{request.scheme}://{request.get_host()}"
return redirect(steam_api.build_auth_url(callback, realm))
@login_required
def steam_sync_callback(request):
steam_id = steam_api.verify_and_get_steam_id(request.GET.dict())
if not steam_id:
return redirect(f"{reverse('backlogger:list')}?sync_error=1")
api_key = getattr(settings, 'STEAM_API_KEY', '')
if not api_key:
return redirect(f"{reverse('backlogger:list')}?sync_error=1")
try:
games = steam_api.get_owned_games(api_key, steam_id)
except Exception:
return redirect(f"{reverse('backlogger:list')}?sync_error=1")
hours_by_appid = {
g['appid']: round(g.get('playtime_forever', 0) / 60, 1)
for g in games
}
steam_items = Item.objects.filter(user=request.user, steam_appid__isnull=False)
synced = 0
for item in steam_items:
new_hours = hours_by_appid.get(item.steam_appid)
if new_hours is not None and new_hours != item.hours_played:
item.hours_played = new_hours
item.save(update_fields=['hours_played', 'updated_at'])
synced += 1
return redirect(f"{reverse('backlogger:list')}?category=games&synced={synced}")