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",
|
"whitenoise.runserver_nostatic",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django.contrib.humanize",
|
"django.contrib.humanize",
|
||||||
|
"rest_framework",
|
||||||
|
"rest_framework.authtoken",
|
||||||
|
"api",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -18,5 +18,6 @@ from django.urls import include, path
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("home.urls")),
|
path("", include("home.urls")),
|
||||||
|
path("api/", include("api.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
]
|
]
|
||||||
|
@ -97,6 +97,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<h2 id="integrations">Integrations</h2>
|
<h2 id="integrations">Integrations</h2>
|
||||||
|
<div class="settings-item">
|
||||||
|
<p>API token:</p>
|
||||||
|
<p>{{ api_token }}</p>
|
||||||
|
</div>
|
||||||
<div class="settings-item">
|
<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>
|
<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>
|
<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.index_management import get_available_backups
|
||||||
from home.src.searching import Pagination, SearchHandler
|
from home.src.searching import Pagination, SearchHandler
|
||||||
from home.tasks import extrac_dl, subscribe_to
|
from home.tasks import extrac_dl, subscribe_to
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
class ArchivistViewConfig(View):
|
class ArchivistViewConfig(View):
|
||||||
@ -683,8 +684,7 @@ class SettingsView(View):
|
|||||||
take post request from the form to update settings
|
take post request from the form to update settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
def get(self, request):
|
||||||
def get(request):
|
|
||||||
"""read and display current settings"""
|
"""read and display current settings"""
|
||||||
config_handler = AppConfig(request.user.id)
|
config_handler = AppConfig(request.user.id)
|
||||||
colors = config_handler.colors
|
colors = config_handler.colors
|
||||||
@ -693,10 +693,12 @@ class SettingsView(View):
|
|||||||
user_form = UserSettingsForm()
|
user_form = UserSettingsForm()
|
||||||
app_form = ApplicationSettingsForm()
|
app_form = ApplicationSettingsForm()
|
||||||
scheduler_form = SchedulerSettingsForm()
|
scheduler_form = SchedulerSettingsForm()
|
||||||
|
token = self.get_token(request)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"config": config_handler.config,
|
"config": config_handler.config,
|
||||||
|
"api_token": token,
|
||||||
"colors": colors,
|
"colors": colors,
|
||||||
"available_backups": available_backups,
|
"available_backups": available_backups,
|
||||||
"user_form": user_form,
|
"user_form": user_form,
|
||||||
@ -706,6 +708,14 @@ class SettingsView(View):
|
|||||||
|
|
||||||
return render(request, "home/settings.html", context)
|
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
|
@staticmethod
|
||||||
def post(request):
|
def post(request):
|
||||||
"""handle form post to update settings"""
|
"""handle form post to update settings"""
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
beautifulsoup4==4.10.0
|
beautifulsoup4==4.10.0
|
||||||
celery==5.2.3
|
celery==5.2.3
|
||||||
Django==4.0.1
|
Django==4.0.1
|
||||||
|
djangorestframework==3.13.1
|
||||||
Pillow==9.0.0
|
Pillow==9.0.0
|
||||||
redis==4.1.0
|
redis==4.1.0
|
||||||
requests==2.27.1
|
requests==2.27.1
|
||||||
|
Loading…
Reference in New Issue
Block a user