mirror of
https://github.com/tubearchivist/tubearchivist-frontend.git
synced 2024-11-22 11:50:14 +00:00
Merge api app from branch 'testing'
This commit is contained in:
commit
51f63f7b71
59
tubearchivist/api/README.md
Normal file
59
tubearchivist/api/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# TubeArchivist API
|
||||
Documentation of available API endpoints.
|
||||
**Note: This is very early alpha and will change!**
|
||||
|
||||
## Authentication
|
||||
API token will get automatically created, accessible on the settings page. Token needs to be passed as an authorization header with every request. Additionally session based authentication is enabled too: When you are logged into your TubeArchivist instance, you'll have access to the api in the browser for testing.
|
||||
|
||||
Curl example:
|
||||
```shell
|
||||
curl -v /api/video/<video-id>/ \
|
||||
-H "Authorization: Token xxxxxxxxxx"
|
||||
```
|
||||
|
||||
Python requests example:
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "/api/video/<video-id>/"
|
||||
headers = {"Authorization": "Token xxxxxxxxxx"}
|
||||
response = requests.get(url, headers=headers)
|
||||
```
|
||||
|
||||
## Video Item View
|
||||
/api/video/\<video_id>/
|
||||
|
||||
## Channel List View
|
||||
/api/channel/
|
||||
|
||||
### Subscribe to a list of channels
|
||||
POST /api/channel/
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{"channel_id": "UC9-y-6csu5WGm29I7JiwpnA", "channel_subscribed": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Channel Item View
|
||||
/api/channel/\<channel_id>/
|
||||
|
||||
## Playlists Item View
|
||||
/api/playlist/\<playlist_id>/
|
||||
|
||||
## Download Queue List View
|
||||
/api/download/
|
||||
|
||||
### Add list of videos to download queue
|
||||
POST /api/download/
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{"youtube_id": "NYj3DnI81AQ", "status": "pending"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Download Queue Item View
|
||||
/api/download/\<video_id>/
|
0
tubearchivist/api/__init__.py
Normal file
0
tubearchivist/api/__init__.py
Normal file
3
tubearchivist/api/admin.py
Normal file
3
tubearchivist/api/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin # noqa: F401
|
||||
|
||||
# Register your models here.
|
10
tubearchivist/api/apps.py
Normal file
10
tubearchivist/api/apps.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""apps file for api package"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
"""app config"""
|
||||
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "api"
|
0
tubearchivist/api/migrations/__init__.py
Normal file
0
tubearchivist/api/migrations/__init__.py
Normal file
3
tubearchivist/api/models.py
Normal file
3
tubearchivist/api/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""api models"""
|
||||
|
||||
# from django.db import models
|
0
tubearchivist/api/serializers.py
Normal file
0
tubearchivist/api/serializers.py
Normal file
3
tubearchivist/api/tests.py
Normal file
3
tubearchivist/api/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase # noqa: F401
|
||||
|
||||
# Create your tests here.
|
44
tubearchivist/api/urls.py
Normal file
44
tubearchivist/api/urls.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""all api urls"""
|
||||
|
||||
from api.views import (
|
||||
ChannelApiListView,
|
||||
ChannelApiView,
|
||||
DownloadApiListView,
|
||||
DownloadApiView,
|
||||
PlaylistApiView,
|
||||
VideoApiView,
|
||||
)
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"video/<slug:video_id>/",
|
||||
VideoApiView.as_view(),
|
||||
name="api-video",
|
||||
),
|
||||
path(
|
||||
"channel/",
|
||||
ChannelApiListView.as_view(),
|
||||
name="api-channel-list",
|
||||
),
|
||||
path(
|
||||
"channel/<slug:channel_id>/",
|
||||
ChannelApiView.as_view(),
|
||||
name="api-channel",
|
||||
),
|
||||
path(
|
||||
"playlist/<slug:playlist_id>/",
|
||||
PlaylistApiView.as_view(),
|
||||
name="api-playlist",
|
||||
),
|
||||
path(
|
||||
"download/",
|
||||
DownloadApiListView.as_view(),
|
||||
name="api-download-list",
|
||||
),
|
||||
path(
|
||||
"download/<slug:video_id>/",
|
||||
DownloadApiView.as_view(),
|
||||
name="api-download",
|
||||
),
|
||||
]
|
200
tubearchivist/api/views.py
Normal file
200
tubearchivist/api/views.py
Normal file
@ -0,0 +1,200 @@
|
||||
"""all API views"""
|
||||
|
||||
import requests
|
||||
from home.src.config import AppConfig
|
||||
from home.src.helper import UrlListParser
|
||||
from home.tasks import extrac_dl, subscribe_to
|
||||
from rest_framework.authentication import (
|
||||
SessionAuthentication,
|
||||
TokenAuthentication,
|
||||
)
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
||||
class ApiBaseView(APIView):
|
||||
"""base view to inherit from"""
|
||||
|
||||
authentication_classes = [SessionAuthentication, TokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
search_base = False
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.response = {"data": False}
|
||||
self.status_code = False
|
||||
self.context = False
|
||||
|
||||
def config_builder(self):
|
||||
"""build confic context"""
|
||||
default_conf = AppConfig().config
|
||||
self.context = {
|
||||
"es_url": default_conf["application"]["es_url"],
|
||||
"es_auth": default_conf["application"]["es_auth"],
|
||||
}
|
||||
|
||||
def get_document(self, document_id):
|
||||
"""get single document from es"""
|
||||
es_url = self.context["es_url"]
|
||||
url = f"{es_url}{self.search_base}{document_id}"
|
||||
print(url)
|
||||
response = requests.get(url, auth=self.context["es_auth"])
|
||||
try:
|
||||
self.response["data"] = response.json()["_source"]
|
||||
except KeyError:
|
||||
print(f"item not found: {document_id}")
|
||||
self.response["data"] = False
|
||||
self.status_code = response.status_code
|
||||
|
||||
def get_paginate(self):
|
||||
"""add pagination detail to response"""
|
||||
self.response["paginate"] = False
|
||||
|
||||
def get_document_list(self, data):
|
||||
"""get a list of results"""
|
||||
es_url = self.context["es_url"]
|
||||
url = f"{es_url}{self.search_base}"
|
||||
print(url)
|
||||
response = requests.get(url, json=data, auth=self.context["es_auth"])
|
||||
all_hits = response.json()["hits"]["hits"]
|
||||
self.response["data"] = [i["_source"] for i in all_hits]
|
||||
self.status_code = response.status_code
|
||||
|
||||
|
||||
class VideoApiView(ApiBaseView):
|
||||
"""resolves to /api/video/<video_id>/
|
||||
GET: returns metadata dict of video
|
||||
"""
|
||||
|
||||
search_base = "/ta_video/_doc/"
|
||||
|
||||
def get(self, request, video_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
self.config_builder()
|
||||
self.get_document(video_id)
|
||||
return Response(self.response, status=self.status_code)
|
||||
|
||||
|
||||
class ChannelApiView(ApiBaseView):
|
||||
"""resolves to /api/channel/<channel_id>/
|
||||
GET: returns metadata dict of channel
|
||||
"""
|
||||
|
||||
search_base = "/ta_channel/_doc/"
|
||||
|
||||
def get(self, request, channel_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
self.config_builder()
|
||||
self.get_document(channel_id)
|
||||
return Response(self.response, status=self.status_code)
|
||||
|
||||
|
||||
class ChannelApiListView(ApiBaseView):
|
||||
"""resolves to /api/channel/
|
||||
GET: returns list of channels
|
||||
POST: edit a list of channels
|
||||
"""
|
||||
|
||||
search_base = "/ta_channel/_search/"
|
||||
|
||||
def get(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
data = {"query": {"match_all": {}}}
|
||||
self.config_builder()
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
|
||||
return Response(self.response)
|
||||
|
||||
@staticmethod
|
||||
def post(request):
|
||||
"""subscribe to list of channels"""
|
||||
data = request.data
|
||||
try:
|
||||
to_add = data["data"]
|
||||
except KeyError:
|
||||
message = "missing expected data key"
|
||||
print(message)
|
||||
return Response({"message": message}, status=400)
|
||||
|
||||
pending = [i["channel_id"] for i in to_add if i["channel_subscribed"]]
|
||||
url_str = " ".join(pending)
|
||||
subscribe_to.delay(url_str)
|
||||
|
||||
return Response(data)
|
||||
|
||||
|
||||
class PlaylistApiView(ApiBaseView):
|
||||
"""resolves to /api/playlist/<playlist_id>/
|
||||
GET: returns metadata dict of playlist
|
||||
"""
|
||||
|
||||
search_base = "/ta_playlist/_doc/"
|
||||
|
||||
def get(self, request, playlist_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
self.config_builder()
|
||||
self.get_document(playlist_id)
|
||||
return Response(self.response, status=self.status_code)
|
||||
|
||||
|
||||
class DownloadApiView(ApiBaseView):
|
||||
"""resolves to /api/download/<video_id>/
|
||||
GET: returns metadata dict of an item in the download queue
|
||||
"""
|
||||
|
||||
search_base = "/ta_download/_doc/"
|
||||
|
||||
def get(self, request, video_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
self.config_builder()
|
||||
self.get_document(video_id)
|
||||
return Response(self.response, status=self.status_code)
|
||||
|
||||
|
||||
class DownloadApiListView(ApiBaseView):
|
||||
"""resolves to /api/download/
|
||||
GET: returns latest videos in the download queue
|
||||
POST: add a list of videos to download queue
|
||||
"""
|
||||
|
||||
search_base = "/ta_download/_search/"
|
||||
|
||||
def get(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
data = {"query": {"match_all": {}}}
|
||||
self.config_builder()
|
||||
self.get_document_list(data)
|
||||
self.get_paginate()
|
||||
return Response(self.response)
|
||||
|
||||
@staticmethod
|
||||
def post(request):
|
||||
"""add list of videos to download queue"""
|
||||
data = request.data
|
||||
try:
|
||||
to_add = data["data"]
|
||||
except KeyError:
|
||||
message = "missing expected data key"
|
||||
print(message)
|
||||
return Response({"message": message}, status=400)
|
||||
|
||||
pending = [i["youtube_id"] for i in to_add if i["status"] == "pending"]
|
||||
url_str = " ".join(pending)
|
||||
try:
|
||||
youtube_ids = UrlListParser(url_str).process_list()
|
||||
except ValueError:
|
||||
message = f"failed to parse: {url_str}"
|
||||
print(message)
|
||||
return Response({"message": message}, status=400)
|
||||
|
||||
extrac_dl.delay(youtube_ids)
|
||||
|
||||
return Response(data)
|
@ -44,6 +44,9 @@ INSTALLED_APPS = [
|
||||
"whitenoise.runserver_nostatic",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.humanize",
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
"api",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -18,5 +18,6 @@ from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path("", include("home.urls")),
|
||||
path("api/", include("api.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
|
@ -97,6 +97,10 @@
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h2 id="integrations">Integrations</h2>
|
||||
<div class="settings-item">
|
||||
<p>API token:</p>
|
||||
<p>{{ api_token }}</p>
|
||||
</div>
|
||||
<div class="settings-item">
|
||||
<p>Integrate with <a href="https://returnyoutubedislike.com/">returnyoutubedislike.com</a> to get dislikes and average ratings back: <span class="settings-current">{{ config.downloads.integrate_ryd }}</span></p>
|
||||
<i>Before activating that, make sure you have a scraping sleep interval of at least 3 secs set to avoid ratelimiting issues.</i><br>
|
||||
|
@ -31,6 +31,7 @@ from home.src.index import YoutubePlaylist
|
||||
from home.src.index_management import get_available_backups
|
||||
from home.src.searching import Pagination, SearchHandler
|
||||
from home.tasks import extrac_dl, subscribe_to
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
|
||||
class ArchivistViewConfig(View):
|
||||
@ -683,8 +684,7 @@ class SettingsView(View):
|
||||
take post request from the form to update settings
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get(request):
|
||||
def get(self, request):
|
||||
"""read and display current settings"""
|
||||
config_handler = AppConfig(request.user.id)
|
||||
colors = config_handler.colors
|
||||
@ -693,10 +693,12 @@ class SettingsView(View):
|
||||
user_form = UserSettingsForm()
|
||||
app_form = ApplicationSettingsForm()
|
||||
scheduler_form = SchedulerSettingsForm()
|
||||
token = self.get_token(request)
|
||||
|
||||
context = {
|
||||
"title": "Settings",
|
||||
"config": config_handler.config,
|
||||
"api_token": token,
|
||||
"colors": colors,
|
||||
"available_backups": available_backups,
|
||||
"user_form": user_form,
|
||||
@ -706,6 +708,14 @@ class SettingsView(View):
|
||||
|
||||
return render(request, "home/settings.html", context)
|
||||
|
||||
@staticmethod
|
||||
def get_token(request):
|
||||
"""get existing or create new token of user"""
|
||||
# pylint: disable=no-member
|
||||
token = Token.objects.get_or_create(user=request.user)[0]
|
||||
print(token)
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def post(request):
|
||||
"""handle form post to update settings"""
|
||||
|
@ -1,6 +1,7 @@
|
||||
beautifulsoup4==4.10.0
|
||||
celery==5.2.3
|
||||
Django==4.0.1
|
||||
djangorestframework==3.13.1
|
||||
Pillow==9.0.0
|
||||
redis==4.1.0
|
||||
requests==2.27.1
|
||||
|
Loading…
Reference in New Issue
Block a user