Compare commits

..

15 Commits

27 changed files with 110 additions and 23 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.vscode/
__pycache__/
db.sqlite3 db.sqlite3
videos/ videos/
channel_archiver/yt-dlp-archive.txt channel_archiver/yt-dlp-archive.txt

View File

@ -1,6 +0,0 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.python"
},
"python.formatting.provider": "none"
}

24
api/README.md Normal file
View File

@ -0,0 +1,24 @@
# VideoAPI
## Search
`GET /api/?param=value`
### Parameters
| Param | Value |
|-|-|
| `q` | The search query string. |
| `limit` | A limit on the number of objects to be returned. Default is 6. |
### Response
```
[
{
"id": 1,
"name": "Video 1"
},
{...}
]
```
### Examples
> `GET /api/?q=foo&limit=3` will return the first 3 videos with "foo" in their name.

0
api/__init__.py Normal file
View File

7
api/urls.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import path, include
from . import views
urlpatterns = [
path('search/', views.searchAPI),
path('', include('sage_stream.api.urls')),
]

17
api/views.py Normal file
View File

@ -0,0 +1,17 @@
from django.db.models import Q
from rest_framework.response import Response
from rest_framework.decorators import api_view
from core.models import Video
from core.serializers import VideoSerializer
@api_view(['GET'])
def searchAPI(request):
q = request.GET.get('q', '')
l = request.GET.get('limit', '6')
try:
l = int(l)
videos = Video.objects.filter(Q(id__contains=q) | Q(name__contains=q))[:l]
except:
videos = Video.objects.filter(Q(id__contains=q) | Q(name__contains=q))
serializer = VideoSerializer(videos, many=True)
return Response(serializer.data)

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,4 @@
from django.contrib import admin from django.contrib import admin
from .models import Video from .models import Video
@admin.register(Video) @admin.register(Video)

7
core/serializers.py Normal file
View File

@ -0,0 +1,7 @@
from rest_framework import serializers
from .models import Video
class VideoSerializer(serializers.ModelSerializer):
class Meta:
model = Video
fields = ['id', 'name']

View File

@ -12,8 +12,7 @@
<link rel="stylesheet" href="{% static 'dist/css/adminlte.css' %}"> <link rel="stylesheet" href="{% static 'dist/css/adminlte.css' %}">
<link rel="stylesheet" <link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback"> href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
<link rel="stylesheet" <link rel="stylesheet" href="{% static 'plugins/select2/css/select2.min.css' %}">
href="{% static 'plugins/fontawesome-free/css/all.min.css' %}">
</head> </head>
<body class="layout-top-nav" style="height: auto;"> <body class="layout-top-nav" style="height: auto;">
<div class="wrapper"> <div class="wrapper">
@ -51,12 +50,18 @@
</li> </li>
</ul> </ul>
<div class="float-right" style="width:100%"> <div class="float-right" style="width:100%">
<form class="form-inline ml-0 ml-md-3 float-right"> <form class="form-inline ml-0 ml-md-3 float-right">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input class="form-control form-control-navbar" <input class="form-control form-control-navbar" data-toggle="dropdown"
type="search" type="search"
placeholder="Search" placeholder="Search"
aria-label="Search"> aria-label="Search"
autocomplete="off"
id="search">
<ul class="dropdown-menu" id="searchdropdown">
<li><span class="dropdown-item-text">Nothing</span></li>
</ul>
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-navbar" type="submit"> <button class="btn btn-navbar" type="submit">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
@ -64,6 +69,7 @@
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
@ -123,5 +129,6 @@
<script src="{% static 'plugins/jquery/jquery.min.js' %}"></script> <script src="{% static 'plugins/jquery/jquery.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.bundle.min.js' %}"></script> <script src="{% static 'plugins/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'dist/js/adminlte.min.js' %}"></script> <script src="{% static 'dist/js/adminlte.min.js' %}"></script>
<script src="{% static 'dist/js/searchsuggestions.js' %}"></script>
</body> </body>
</html> </html>

View File

@ -1,7 +1,6 @@
from django.urls import path from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('', views.core, name='core'), path('', views.core, name='core'),
path('status/', views.status, name='status'), path('status/', views.status, name='status'),

View File

@ -1,9 +1,6 @@
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from core.models import Video from core.models import Video
import random
# Create your views here. # Create your views here.
@ -11,10 +8,9 @@ def core(request):
videos = Video.objects.all() videos = Video.objects.all()
return render(request, 'base.html', {'videos': videos[::-1]}) return render(request, 'base.html', {'videos': videos[::-1]})
def random(request): def random(request):
videos = Video.objects.all().order_by("?") videos = Video.objects.all().order_by("?")
ran = videos.first(); ran = videos.first()
return redirect('/view/'+str(ran.id)) return redirect('/view/'+str(ran.id))
def status(request): def status(request):

View File

@ -22,9 +22,7 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('auth/', include('django.contrib.auth.urls')), path('auth/', include('django.contrib.auth.urls')),
path('', include('core.urls')), path('', include('core.urls')),
path('/', include('core.urls')), path('api/', include('api.urls')),
path('status/', include('core.urls')),
path('api/', include('sage_stream.api.urls')),
] ]
if settings.DEBUG: if settings.DEBUG:

38
static/dist/js/searchsuggestions.js vendored Normal file
View File

@ -0,0 +1,38 @@
const searchinput = document.querySelector("#search");
const searchsuggestions = document.querySelector("#searchdropdown");
const li = (c) => document.createElement("li").appendChild(c);
searchinput.addEventListener("input", (e) => {
q(e.target.value).then((d) => {
if (d.length < 1) searchsuggestions.replaceChildren(nothing());
else searchsuggestions.replaceChildren(...suggestions(d));
})
});
async function q(s) {
if (s.length > 0) {
try {
const response = await fetch("/api/search/?q=" + s);
const result = await response.json();
return result;
} catch(error) {
console.log("Error: " + error);
}
}
return [];
}
function nothing() {
const span = document.createElement("span");
span.setAttribute("class", "dropdown-item-text");
span.insertAdjacentText("afterbegin", "Nothing");
return li(span);
}
function suggestions(d) {
const items = [];
for (const vid of d) {
const a = document.createElement("a");
a.setAttribute("class", "dropdown-item")
a.setAttribute("href", "/view/" + vid.id);
a.insertAdjacentText("afterbegin", vid.name + " " + vid.id);
items.push(li(a));
}
return items;
}