Initial commit
This commit is contained in:
0
scrobbles/__init__.py
Normal file
0
scrobbles/__init__.py
Normal file
13
scrobbles/admin.py
Normal file
13
scrobbles/admin.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from scrobbles.models import Scrobble
|
||||
|
||||
|
||||
class ScrobbleAdmin(admin.ModelAdmin):
|
||||
date_hierarchy = "timestamp"
|
||||
list_display = ("video", "timestamp", "source", "playback_position")
|
||||
list_filter = ("video",)
|
||||
ordering = ("-timestamp",)
|
||||
|
||||
|
||||
admin.site.register(Scrobble, ScrobbleAdmin)
|
||||
5
scrobbles/apps.py
Normal file
5
scrobbles/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ScrobblesConfig(AppConfig):
|
||||
name = 'scrobbles'
|
||||
81
scrobbles/migrations/0001_initial.py
Normal file
81
scrobbles/migrations/0001_initial.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-04 21:33
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('videos', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Scrobble',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'created',
|
||||
django_extensions.db.fields.CreationDateTimeField(
|
||||
auto_now_add=True, verbose_name='created'
|
||||
),
|
||||
),
|
||||
(
|
||||
'modified',
|
||||
django_extensions.db.fields.ModificationDateTimeField(
|
||||
auto_now=True, verbose_name='modified'
|
||||
),
|
||||
),
|
||||
('timestamp', models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
'playback_position_ticks',
|
||||
models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
(
|
||||
'playback_position',
|
||||
models.CharField(blank=True, max_length=8, null=True),
|
||||
),
|
||||
('is_paused', models.BooleanField(default=False)),
|
||||
('played_to_completion', models.BooleanField(default=False)),
|
||||
(
|
||||
'source',
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
('source_id', models.TextField(blank=True, null=True)),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
'video',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
to='videos.video',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'modified',
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
scrobbles/migrations/__init__.py
Normal file
0
scrobbles/migrations/__init__.py
Normal file
25
scrobbles/models.py
Normal file
25
scrobbles/models.py
Normal file
@ -0,0 +1,25 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
|
||||
from videos.models import Video
|
||||
|
||||
User = get_user_model()
|
||||
BNULL = {"blank": True, "null": True}
|
||||
|
||||
|
||||
class Scrobble(TimeStampedModel):
|
||||
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING)
|
||||
user = models.ForeignKey(
|
||||
User, blank=True, null=True, on_delete=models.DO_NOTHING
|
||||
)
|
||||
timestamp = models.DateTimeField(**BNULL)
|
||||
playback_position_ticks = models.PositiveIntegerField(**BNULL)
|
||||
playback_position = models.CharField(max_length=8, **BNULL)
|
||||
is_paused = models.BooleanField(default=False)
|
||||
played_to_completion = models.BooleanField(default=False)
|
||||
source = models.CharField(max_length=255, **BNULL)
|
||||
source_id = models.TextField(**BNULL)
|
||||
|
||||
def percent_played(self):
|
||||
return int((self.playback_position_ticks / video.run_time_ticks) * 100)
|
||||
8
scrobbles/serializers.py
Normal file
8
scrobbles/serializers.py
Normal file
@ -0,0 +1,8 @@
|
||||
from rest_framework import serializers
|
||||
from scrobbles.models import Scrobble
|
||||
|
||||
|
||||
class ScrobbleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Scrobble
|
||||
fields = "__all__"
|
||||
25
scrobbles/templates/scrobbles/scrobble_list.html
Normal file
25
scrobbles/templates/scrobbles/scrobble_list.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Untitled</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<!-- Place favicon.ico in the root directory -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lt IE 8]>
|
||||
<p class="browserupgrade">
|
||||
You are using an <strong>outdated</strong> browser. Please
|
||||
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
|
||||
your experience.
|
||||
</p>
|
||||
<![endif]-->
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
8
scrobbles/urls.py
Normal file
8
scrobbles/urls.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django.urls import path
|
||||
from scrobbles import views
|
||||
|
||||
app_name = 'scrobbles'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.scrobble_list, name='scrobble-list'),
|
||||
]
|
||||
98
scrobbles/views.py
Normal file
98
scrobbles/views.py
Normal file
@ -0,0 +1,98 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
import dateutil
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic.list import ListView
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
from scrobbles.models import Scrobble
|
||||
from scrobbles.serializers import ScrobbleSerializer
|
||||
from videos.models import Series, Video
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRUTHY_VALUES = [
|
||||
'true',
|
||||
'1',
|
||||
't',
|
||||
'y',
|
||||
'yes',
|
||||
'yeah',
|
||||
'yup',
|
||||
'certainly',
|
||||
'uh-huh',
|
||||
]
|
||||
|
||||
|
||||
class RecentScrobbleList(ListView):
|
||||
model = Scrobble
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@api_view(['GET', 'POST'])
|
||||
def scrobble_list(request):
|
||||
"""List all Scrobbles, or create a new Scrobble"""
|
||||
if request.method == 'GET':
|
||||
scrobble = Scrobble.objects.all()
|
||||
serializer = ScrobbleSerializer(scrobble, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
elif request.method == 'POST':
|
||||
data_dict = json.loads(request.data["_content"])
|
||||
media_type = data_dict["ItemType"]
|
||||
# Check if it's a TV Episode
|
||||
video_dict = {
|
||||
"title": data_dict["Name"],
|
||||
"imdb_id": data_dict["Provider_imdb"],
|
||||
"video_type": Video.VideoType.MOVIE,
|
||||
}
|
||||
if media_type == 'Episode':
|
||||
series_name = data_dict["SeriesName"]
|
||||
series = Series.objects.get_or_create(name=series_name)
|
||||
|
||||
video_dict['video_type'] = Video.VideoType.TV_EPISODE
|
||||
video_dict["tv_series_id"] = series.id
|
||||
video_dict["episode_num"] = data_dict["EpisodeNumber"]
|
||||
video_dict["season_num"] = data_dict["SeasonNumber"]
|
||||
video_dict["tvdb_id"] = data_dict["Provider_tvdb"]
|
||||
video_dict["tvrage_id"] = data_dict["Provider_tvrage"]
|
||||
|
||||
video, _created = Video.objects.get_or_create(**video_dict)
|
||||
|
||||
video.year = data_dict["Year"]
|
||||
video.overview = data_dict["Overview"]
|
||||
video.tagline = data_dict["Tagline"]
|
||||
video.run_time_ticks = data_dict["RunTimeTicks"]
|
||||
video.run_time = data_dict["RunTime"]
|
||||
video.save()
|
||||
|
||||
# Now we run off a scrobble
|
||||
scrobble_dict = {
|
||||
'video_id': video.id,
|
||||
'user_id': request.user.id,
|
||||
}
|
||||
scrobble, scrobble_created = Scrobble.objects.get_or_create(
|
||||
**scrobble_dict
|
||||
)
|
||||
|
||||
if scrobble_created:
|
||||
scrobble.source = data_dict['ClientName']
|
||||
scrobble.source_id = data_dict['MediaSourceId']
|
||||
|
||||
# Update a found scrobble with new position and timestamp
|
||||
scrobble.playback_position_ticks = data_dict["PlaybackPositionTicks"]
|
||||
scrobble.playback_position = data_dict["PlaybackPosition"]
|
||||
scrobble.timestamp = dateutil.parser.parse(data_dict["UtcTimestamp"])
|
||||
scrobble.is_paused = data_dict["IsPaused"] in TRUTHY_VALUES
|
||||
scrobble.played_to_completion = (
|
||||
data_dict["PlayedToCompletion"] in TRUTHY_VALUES
|
||||
)
|
||||
scrobble.save()
|
||||
|
||||
logger.info(f"You are {scrobble.percent_played}% through {video}")
|
||||
|
||||
return Response(video_dict, status=status.HTTP_201_CREATED)
|
||||
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
Reference in New Issue
Block a user