[API] add watched state endpoints
This commit is contained in:
parent
a2e2fd1b89
commit
6ed2308f99
|
@ -41,6 +41,7 @@ Note:
|
||||||
- [Refresh](#refresh-view)
|
- [Refresh](#refresh-view)
|
||||||
- [Cookie](#cookie-view)
|
- [Cookie](#cookie-view)
|
||||||
- [Search](#search-view)
|
- [Search](#search-view)
|
||||||
|
- [Watched](#watched-view)
|
||||||
- [Ping](#ping-view)
|
- [Ping](#ping-view)
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
@ -404,6 +405,19 @@ GET /api/search/?query=\<query>
|
||||||
|
|
||||||
Returns search results from your 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
|
## Ping View
|
||||||
Validate your connection with the API
|
Validate your connection with the API
|
||||||
GET /api/ping/
|
GET /api/ping/
|
||||||
|
|
|
@ -23,6 +23,7 @@ from api.views import (
|
||||||
VideoProgressView,
|
VideoProgressView,
|
||||||
VideoSimilarView,
|
VideoSimilarView,
|
||||||
VideoSponsorView,
|
VideoSponsorView,
|
||||||
|
WatchedView,
|
||||||
)
|
)
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
@ -124,6 +125,11 @@ urlpatterns = [
|
||||||
CookieView.as_view(),
|
CookieView.as_view(),
|
||||||
name="api-cookie",
|
name="api-cookie",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"watched/",
|
||||||
|
WatchedView.as_view(),
|
||||||
|
name="api-watched",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"search/",
|
"search/",
|
||||||
SearchView.as_view(),
|
SearchView.as_view(),
|
||||||
|
|
|
@ -7,6 +7,7 @@ from home.src.download.yt_dlp_base import CookieHandler
|
||||||
from home.src.es.connect import ElasticWrap
|
from home.src.es.connect import ElasticWrap
|
||||||
from home.src.es.snapshot import ElasticSnapshot
|
from home.src.es.snapshot import ElasticSnapshot
|
||||||
from home.src.frontend.searching import SearchForm
|
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.channel import YoutubeChannel
|
||||||
from home.src.index.generic import Pagination
|
from home.src.index.generic import Pagination
|
||||||
from home.src.index.reindex import ReindexProgress
|
from home.src.index.reindex import ReindexProgress
|
||||||
|
@ -703,6 +704,24 @@ class CookieView(ApiBaseView):
|
||||||
return Response(message)
|
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):
|
class SearchView(ApiBaseView):
|
||||||
"""resolves to /api/search/
|
"""resolves to /api/search/
|
||||||
GET: run a search with the string in the ?query parameter
|
GET: run a search with the string in the ?query parameter
|
||||||
|
|
|
@ -74,12 +74,12 @@ class PostData:
|
||||||
|
|
||||||
def _watched(self):
|
def _watched(self):
|
||||||
"""mark as watched"""
|
"""mark as watched"""
|
||||||
WatchState(self.exec_val).mark_as_watched()
|
WatchState(self.exec_val, is_watched=True).change()
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|
||||||
def _un_watched(self):
|
def _un_watched(self):
|
||||||
"""mark as unwatched"""
|
"""mark as unwatched"""
|
||||||
WatchState(self.exec_val).mark_as_unwatched()
|
WatchState(self.exec_val, is_watched=False).change()
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
|
|
||||||
def _change_view(self):
|
def _change_view(self):
|
||||||
|
|
|
@ -12,95 +12,94 @@ from home.src.ta.helper import UrlListParser
|
||||||
class WatchState:
|
class WatchState:
|
||||||
"""handle watched checkbox for videos and channels"""
|
"""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.youtube_id = youtube_id
|
||||||
|
self.is_watched = is_watched
|
||||||
self.stamp = int(datetime.now().timestamp())
|
self.stamp = int(datetime.now().timestamp())
|
||||||
|
self.pipeline = f"_ingest/pipeline/watch_{youtube_id}"
|
||||||
|
|
||||||
def mark_as_watched(self):
|
def change(self):
|
||||||
"""update es with new watched value"""
|
"""change watched state of item(s)"""
|
||||||
url_type = self.dedect_type()
|
url_type = self._dedect_type()
|
||||||
if url_type == "video":
|
if url_type == "video":
|
||||||
self.mark_vid_watched()
|
self.change_vid_state()
|
||||||
elif url_type == "channel":
|
return
|
||||||
self.mark_channel_watched()
|
|
||||||
elif url_type == "playlist":
|
|
||||||
self.mark_playlist_watched()
|
|
||||||
|
|
||||||
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):
|
def _dedect_type(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):
|
|
||||||
"""find youtube id type"""
|
"""find youtube id type"""
|
||||||
print(self.youtube_id)
|
print(self.youtube_id)
|
||||||
url_process = UrlListParser(self.youtube_id).process_list()
|
url_process = UrlListParser(self.youtube_id).process_list()
|
||||||
url_type = url_process[0]["type"]
|
url_type = url_process[0]["type"]
|
||||||
return url_type
|
return url_type
|
||||||
|
|
||||||
def mark_vid_watched(self, revert=False):
|
def change_vid_state(self):
|
||||||
"""change watched status of single video"""
|
"""change watched state of video"""
|
||||||
path = f"ta_video/_update/{self.youtube_id}"
|
path = f"ta_video/_update/{self.youtube_id}"
|
||||||
data = {
|
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)
|
response, status_code = ElasticWrap(path).post(data=data)
|
||||||
if status_code != 200:
|
if status_code != 200:
|
||||||
print(response)
|
print(response)
|
||||||
raise ValueError("failed to mark video as watched")
|
raise ValueError("failed to mark video as watched")
|
||||||
|
|
||||||
def _get_source(self):
|
def _build_update_data(self, url_type):
|
||||||
"""build source line for update_by_query script"""
|
"""build update by query data based on url_type"""
|
||||||
source = [
|
term_key_map = {
|
||||||
"ctx._source.player['watched'] = true",
|
"channel": "channel.channel_id",
|
||||||
f"ctx._source.player['watched_date'] = {self.stamp}",
|
"playlist": "playlist.keyword",
|
||||||
]
|
}
|
||||||
return "; ".join(source)
|
term_key = term_key_map.get(url_type)
|
||||||
|
|
||||||
def mark_channel_watched(self):
|
return {
|
||||||
"""change watched status of every video in channel"""
|
"query": {
|
||||||
path = "ta_video/_update_by_query"
|
"bool": {
|
||||||
must_list = [
|
"must": [
|
||||||
{"term": {"channel.channel_id": {"value": self.youtube_id}}},
|
{"term": {term_key: {"value": self.youtube_id}}},
|
||||||
{"term": {"player.watched": {"value": False}}},
|
{
|
||||||
]
|
"term": {
|
||||||
data = {
|
"player.watched": {
|
||||||
"query": {"bool": {"must": must_list}},
|
"value": not self.is_watched
|
||||||
"script": {
|
}
|
||||||
"source": self._get_source(),
|
}
|
||||||
"lang": "painless",
|
},
|
||||||
},
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, status_code = ElasticWrap(path).post(data=data)
|
def _add_pipeline(self):
|
||||||
if status_code != 200:
|
"""add ingest pipeline"""
|
||||||
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}}},
|
|
||||||
]
|
|
||||||
data = {
|
data = {
|
||||||
"query": {"bool": {"must": must_list}},
|
"description": f"{self.youtube_id}: watched {self.is_watched}",
|
||||||
"script": {
|
"processors": [
|
||||||
"source": self._get_source(),
|
{
|
||||||
"lang": "painless",
|
"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)
|
def _delete_pipeline(self):
|
||||||
if status_code != 200:
|
"""delete pipeline"""
|
||||||
print(response)
|
ElasticWrap(self.pipeline).delete()
|
||||||
raise ValueError("failed mark playlist as watched")
|
|
||||||
|
|
|
@ -24,14 +24,13 @@ function updateVideoWatchStatus(input1, videoCurrentWatchStatus) {
|
||||||
removeProgressBar(videoId);
|
removeProgressBar(videoId);
|
||||||
|
|
||||||
let watchStatusIndicator, payload;
|
let watchStatusIndicator, payload;
|
||||||
|
let apiEndpoint = '/api/watched/'
|
||||||
if (videoCurrentWatchStatus === 'watched') {
|
if (videoCurrentWatchStatus === 'watched') {
|
||||||
watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched');
|
watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched');
|
||||||
payload = JSON.stringify({ un_watched: videoId });
|
apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": false})
|
||||||
sendPost(payload);
|
|
||||||
} else if (videoCurrentWatchStatus === 'unwatched') {
|
} else if (videoCurrentWatchStatus === 'unwatched') {
|
||||||
watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched');
|
watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched');
|
||||||
payload = JSON.stringify({ watched: videoId });
|
apiRequest(apiEndpoint, 'POST', {id: videoId, "is_watched": true})
|
||||||
sendPost(payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let watchButtons = document.getElementsByClassName('watch-button');
|
let watchButtons = document.getElementsByClassName('watch-button');
|
||||||
|
@ -76,9 +75,10 @@ function removeProgressBar(videoId) {
|
||||||
|
|
||||||
function isWatchedButton(button) {
|
function isWatchedButton(button) {
|
||||||
let youtube_id = button.getAttribute('data-id');
|
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();
|
button.remove();
|
||||||
sendPost(payload);
|
apiRequest(apiEndpoint, 'POST', data);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
location.reload();
|
location.reload();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
Loading…
Reference in New Issue