[API] add watched state endpoints

This commit is contained in:
simon 2022-12-23 20:39:14 +07:00
parent a2e2fd1b89
commit 6ed2308f99
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
6 changed files with 112 additions and 74 deletions

View File

@ -41,6 +41,7 @@ Note:
- [Refresh](#refresh-view)
- [Cookie](#cookie-view)
- [Search](#search-view)
- [Watched](#watched-view)
- [Ping](#ping-view)
## Authentication
@ -404,6 +405,19 @@ GET /api/search/?query=\<query>
Returns search results from your query.
## Watched View
POST /api/watched/
Change watched state, where the `id` can be a single video, or channel/playlist to change all videos belonging to that channel/playlist.
```json
{
"id": "xxxxxxx",
"is_watched": True
}
```
## Ping View
Validate your connection with the API
GET /api/ping/

View File

@ -23,6 +23,7 @@ from api.views import (
VideoProgressView,
VideoSimilarView,
VideoSponsorView,
WatchedView,
)
from django.urls import path
@ -124,6 +125,11 @@ urlpatterns = [
CookieView.as_view(),
name="api-cookie",
),
path(
"watched/",
WatchedView.as_view(),
name="api-watched",
),
path(
"search/",
SearchView.as_view(),

View File

@ -7,6 +7,7 @@ from home.src.download.yt_dlp_base import CookieHandler
from home.src.es.connect import ElasticWrap
from home.src.es.snapshot import ElasticSnapshot
from home.src.frontend.searching import SearchForm
from home.src.frontend.watched import WatchState
from home.src.index.channel import YoutubeChannel
from home.src.index.generic import Pagination
from home.src.index.reindex import ReindexProgress
@ -703,6 +704,24 @@ class CookieView(ApiBaseView):
return Response(message)
class WatchedView(ApiBaseView):
"""resolves to /api/watched/
POST: change watched state of video, channel or playlist
"""
def post(self, request):
"""change watched state"""
youtube_id = request.data.get("id")
is_watched = request.data.get("is_watched")
if not youtube_id or is_watched is None:
message = {"message": "missing id or is_watched"}
return Response(message, status=400)
WatchState(youtube_id, is_watched).change()
return Response({"message": "success"}, status=200)
class SearchView(ApiBaseView):
"""resolves to /api/search/
GET: run a search with the string in the ?query parameter

View File

@ -74,12 +74,12 @@ class PostData:
def _watched(self):
"""mark as watched"""
WatchState(self.exec_val).mark_as_watched()
WatchState(self.exec_val, is_watched=True).change()
return {"success": True}
def _un_watched(self):
"""mark as unwatched"""
WatchState(self.exec_val).mark_as_unwatched()
WatchState(self.exec_val, is_watched=False).change()
return {"success": True}
def _change_view(self):

View File

@ -12,95 +12,94 @@ from home.src.ta.helper import UrlListParser
class WatchState:
"""handle watched checkbox for videos and channels"""
def __init__(self, youtube_id):
def __init__(self, youtube_id, is_watched):
self.youtube_id = youtube_id
self.is_watched = is_watched
self.stamp = int(datetime.now().timestamp())
self.pipeline = f"_ingest/pipeline/watch_{youtube_id}"
def mark_as_watched(self):
"""update es with new watched value"""
url_type = self.dedect_type()
def change(self):
"""change watched state of item(s)"""
url_type = self._dedect_type()
if url_type == "video":
self.mark_vid_watched()
elif url_type == "channel":
self.mark_channel_watched()
elif url_type == "playlist":
self.mark_playlist_watched()
self.change_vid_state()
return
print(f"{self.youtube_id}: marked as watched")
self._add_pipeline()
path = f"ta_video/_update_by_query?pipeline=watch_{self.youtube_id}"
data = self._build_update_data(url_type)
_, _ = ElasticWrap(path).post(data)
self._delete_pipeline()
def mark_as_unwatched(self):
"""revert watched state to false"""
url_type = self.dedect_type()
if url_type == "video":
self.mark_vid_watched(revert=True)
print(f"{self.youtube_id}: revert as unwatched")
def dedect_type(self):
def _dedect_type(self):
"""find youtube id type"""
print(self.youtube_id)
url_process = UrlListParser(self.youtube_id).process_list()
url_type = url_process[0]["type"]
return url_type
def mark_vid_watched(self, revert=False):
"""change watched status of single video"""
def change_vid_state(self):
"""change watched state of video"""
path = f"ta_video/_update/{self.youtube_id}"
data = {
"doc": {"player": {"watched": True, "watched_date": self.stamp}}
"doc": {
"player": {
"watched": self.is_watched,
"watched_date": self.stamp,
}
}
}
if revert:
data["doc"]["player"]["watched"] = False
response, status_code = ElasticWrap(path).post(data=data)
if status_code != 200:
print(response)
raise ValueError("failed to mark video as watched")
def _get_source(self):
"""build source line for update_by_query script"""
source = [
"ctx._source.player['watched'] = true",
f"ctx._source.player['watched_date'] = {self.stamp}",
]
return "; ".join(source)
def _build_update_data(self, url_type):
"""build update by query data based on url_type"""
term_key_map = {
"channel": "channel.channel_id",
"playlist": "playlist.keyword",
}
term_key = term_key_map.get(url_type)
def mark_channel_watched(self):
"""change watched status of every video in channel"""
path = "ta_video/_update_by_query"
must_list = [
{"term": {"channel.channel_id": {"value": self.youtube_id}}},
{"term": {"player.watched": {"value": False}}},
]
data = {
"query": {"bool": {"must": must_list}},
"script": {
"source": self._get_source(),
"lang": "painless",
},
return {
"query": {
"bool": {
"must": [
{"term": {term_key: {"value": self.youtube_id}}},
{
"term": {
"player.watched": {
"value": not self.is_watched
}
}
},
],
}
}
}
response, status_code = ElasticWrap(path).post(data=data)
if status_code != 200:
print(response)
raise ValueError("failed mark channel as watched")
def mark_playlist_watched(self):
"""change watched state of all videos in playlist"""
path = "ta_video/_update_by_query"
must_list = [
{"term": {"playlist.keyword": {"value": self.youtube_id}}},
{"term": {"player.watched": {"value": False}}},
]
def _add_pipeline(self):
"""add ingest pipeline"""
data = {
"query": {"bool": {"must": must_list}},
"script": {
"source": self._get_source(),
"lang": "painless",
},
"description": f"{self.youtube_id}: watched {self.is_watched}",
"processors": [
{
"set": {
"field": "player.watched",
"value": self.is_watched,
}
},
{
"set": {
"field": "player.watched_date",
"value": self.stamp,
}
},
],
}
_, _ = ElasticWrap(self.pipeline).put(data)
response, status_code = ElasticWrap(path).post(data=data)
if status_code != 200:
print(response)
raise ValueError("failed mark playlist as watched")
def _delete_pipeline(self):
"""delete pipeline"""
ElasticWrap(self.pipeline).delete()

View File

@ -24,14 +24,13 @@ function updateVideoWatchStatus(input1, videoCurrentWatchStatus) {
removeProgressBar(videoId);
let watchStatusIndicator, payload;
let apiEndpoint = '/api/watched/'
if (videoCurrentWatchStatus === 'watched') {
watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched');
payload = JSON.stringify({ un_watched: videoId });
sendPost(payload);
apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": false})
} else if (videoCurrentWatchStatus === 'unwatched') {
watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched');
payload = JSON.stringify({ watched: videoId });
sendPost(payload);
apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": true})
}
let watchButtons = document.getElementsByClassName('watch-button');
@ -76,9 +75,10 @@ function removeProgressBar(videoId) {
function isWatchedButton(button) {
let youtube_id = button.getAttribute('data-id');
let payload = JSON.stringify({ watched: youtube_id });
let apiEndpoint = '/api/watched/';
let data = {id: youtube_id, is_watched: true}
button.remove();
sendPost(payload);
apiRequest(apiEndpoint, 'POST', data);
setTimeout(function () {
location.reload();
}, 1000);