summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config/config.example.yml45
-rw-r--r--locales/es.json8
-rw-r--r--locales/ko.json93
-rw-r--r--locales/lt.json40
-rw-r--r--locales/vi.json9
-rw-r--r--src/invidious.cr2
-rw-r--r--src/invidious/config.cr4
-rw-r--r--src/invidious/database/nonces.cr11
-rw-r--r--src/invidious/database/videos.cr9
-rw-r--r--src/invidious/jobs.cr27
-rw-r--r--src/invidious/jobs/base_job.cr30
-rw-r--r--src/invidious/jobs/clear_expired_items_job.cr27
-rw-r--r--src/invidious/views/watch.ecr2
13 files changed, 248 insertions, 59 deletions
diff --git a/config/config.example.yml b/config/config.example.yml
index 160a2750..264a5bea 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -304,10 +304,8 @@ https_only: false
## Number of threads to use when crawling channel videos (during
## subscriptions update).
##
-## Notes:
-## - Setting this to 0 will disable the channel videos crawl job.
-## - This setting is overridden if "-c THREADS" or
-## "--channel-threads=THREADS" are passed on the command line.
+## Notes: This setting is overridden if either "-c THREADS" or
+## "--channel-threads=THREADS" is passed on the command line.
##
## Accepted values: a positive integer
## Default: 1
@@ -335,10 +333,8 @@ full_refresh: false
##
## Number of threads to use when updating RSS feeds.
##
-## Notes:
-## - Setting this to 0 will disable the channel videos crawl job.
-## - This setting is overridden if "-f THREADS" or
-## "--feed-threads=THREADS" are passed on the command line.
+## Notes: This setting is overridden if either "-f THREADS" or
+## "--feed-threads=THREADS" is passed on the command line.
##
## Accepted values: a positive integer
## Default: 1
@@ -361,6 +357,39 @@ feed_threads: 1
#decrypt_polling: false
+jobs:
+
+ ## Options for the database cleaning job
+ clear_expired_items:
+
+ ## Enable/Disable job
+ ##
+ ## Accepted values: true, false
+ ## Default: true
+ ##
+ enable: true
+
+ ## Options for the channels updater job
+ refresh_channels:
+
+ ## Enable/Disable job
+ ##
+ ## Accepted values: true, false
+ ## Default: true
+ ##
+ enable: true
+
+ ## Options for the RSS feeds updater job
+ refresh_feeds:
+
+ ## Enable/Disable job
+ ##
+ ## Accepted values: true, false
+ ## Default: true
+ ##
+ enable: true
+
+
# -----------------------------
# Captcha API
# -----------------------------
diff --git a/locales/es.json b/locales/es.json
index c427e81a..8603e9fe 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -114,7 +114,7 @@
"Save preferences": "Guardar las preferencias",
"Subscription manager": "Gestor de suscripciones",
"Token manager": "Gestor de tokens",
- "Token": "Token",
+ "Token": "Ficha",
"Import/export": "Importar/Exportar",
"unsubscribe": "Desuscribirse",
"revoke": "revocar",
@@ -355,7 +355,7 @@
"search_filters_features_option_location": "ubicación",
"search_filters_features_option_hdr": "hdr",
"Current version: ": "Versión actual: ",
- "next_steps_error_message": "Después de lo cual deberías intentar: ",
+ "next_steps_error_message": "Después de lo cual debes intentar: ",
"next_steps_error_message_refresh": "Recargar la página",
"next_steps_error_message_go_to_youtube": "Ir a YouTube",
"search_filters_duration_option_short": "Corto (< 4 minutos)",
@@ -467,8 +467,8 @@
"search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180",
"search_filters_apply_button": "Aplicar filtros seleccionados",
- "tokens_count": "{{count}} token",
- "tokens_count_plural": "{{count}} tokens",
+ "tokens_count": "{{count}} ficha",
+ "tokens_count_plural": "{{count}} fichas",
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
"search_filters_duration_option_medium": "Medio (4 - 20 minutes)",
"Popular enabled: ": "¿Habilitar la sección popular? ",
diff --git a/locales/ko.json b/locales/ko.json
index 26412d0c..127a500b 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -12,14 +12,14 @@
"Dark mode: ": "다크 모드: ",
"preferences_player_style_label": "플레이어 스타일: ",
"preferences_category_visual": "시각 설정",
- "preferences_vr_mode_label": "360도 비디오와 상호작용하기(WebGL를 요구함): ",
+ "preferences_vr_mode_label": "VR 영상 활성화(WebGL 필요): ",
"preferences_extend_desc_label": "자동으로 비디오 설명을 확장: ",
- "preferences_annotations_label": "기본적으로 주석 표시: ",
+ "preferences_annotations_label": "기본으로 주석 표시: ",
"preferences_related_videos_label": "관련 동영상 보기: ",
"Fallback captions: ": "대체 자막: ",
"preferences_captions_label": "기본 자막: ",
- "reddit": "Reddit",
- "youtube": "YouTube",
+ "reddit": "레딧",
+ "youtube": "유튜브",
"preferences_comments_label": "기본 댓글: ",
"preferences_volume_label": "플레이어 볼륨: ",
"preferences_quality_label": "선호하는 비디오 품질: ",
@@ -46,8 +46,8 @@
"Log in/register": "로그인/회원가입",
"Log in": "로그인",
"source": "출처",
- "JavaScript license information": "JavaScript 라이선스 정보",
- "An alternative front-end to YouTube": "YouTube의 대안 프론트엔드",
+ "JavaScript license information": "자바스크립트 라이센스 정보",
+ "An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
"History": "역사",
"Delete account?": "계정을 삭제 하시겠습니까?",
"Export data as JSON": "데이터를 JSON으로 내보내기",
@@ -57,7 +57,7 @@
"Import NewPipe data (.zip)": "NewPipe 데이터 가져오기 (.zip)",
"Import NewPipe subscriptions (.json)": "NewPipe 구독을 가져오기 (.json)",
"Import FreeTube subscriptions (.db)": "FreeTube 구독 가져오기 (.db)",
- "Import YouTube subscriptions": "YouTube 구독 가져오기",
+ "Import YouTube subscriptions": "유튜브 구독 가져오기",
"Import Invidious data": "Invidious JSON 데이터 가져오기",
"Import": "가져오기",
"Import and Export Data": "데이터 가져오기 및 내보내기",
@@ -65,7 +65,7 @@
"Yes": "예",
"Authorize token for `x`?": "`x` 에 대한 토큰을 승인하시겠습니까?",
"Authorize token?": "토큰을 승인하시겠습니까?",
- "Cannot change password for Google accounts": "Google 계정의 비밀번호를 변경할 수 없습니다",
+ "Cannot change password for Google accounts": "구글 계정의 비밀번호를 변경할 수 없습니다",
"New passwords must match": "새 비밀번호는 일치해야 합니다",
"New password": "새 비밀번호",
"Clear watch history?": "재생 기록을 삭제 하시겠습니까?",
@@ -76,8 +76,8 @@
"popular": "인기",
"oldest": "오래된순",
"newest": "최신순",
- "View playlist on YouTube": "YouTube에서 재생목록 보기",
- "View channel on YouTube": "YouTube에서 채널 보기",
+ "View playlist on YouTube": "유튜브에서 재생목록 보기",
+ "View channel on YouTube": "유튜브에서 채널 보기",
"Subscribe": "구독",
"Unsubscribe": "구독 취소",
"LIVE": "실시간",
@@ -116,11 +116,11 @@
"Show replies": "댓글 보기",
"Hide replies": "댓글 숨기기",
"Incorrect password": "잘못된 비밀번호",
- "License: ": "라이선스: ",
+ "License: ": "라이센스: ",
"Genre: ": "장르: ",
"Editing playlist `x`": "재생목록 `x` 수정하기",
"Playlist privacy": "재생목록 공개 범위",
- "Watch on YouTube": "YouTube에서 보기",
+ "Watch on YouTube": "유튜브에서 보기",
"Show less": "간략히",
"Show more": "더보기",
"Title": "제목",
@@ -129,13 +129,13 @@
"Delete playlist": "재생목록 삭제",
"Delete playlist `x`?": "재생목록 `x` 를 삭제 하시겠습니까?",
"Updated `x` ago": "`x` 전에 업데이트됨",
- "Released under the AGPLv3 on Github.": "GitHub에 AGPLv3 으로 배포됩니다.",
+ "Released under the AGPLv3 on Github.": "깃허브에 AGPLv3 으로 배포됩니다.",
"View all playlists": "모든 재생목록 보기",
"Private": "비공개",
"Unlisted": "목록에 없음",
"Public": "공개",
"View privacy policy.": "개인정보 처리방침 보기.",
- "View JavaScript license information.": "JavaScript 라이센스 정보 보기.",
+ "View JavaScript license information.": "자바스크립트 라이센스 정보 보기.",
"Source available here.": "소스는 여기에서 사용할 수 있습니다.",
"Log out": "로그아웃",
"search": "검색",
@@ -202,7 +202,7 @@
"search_filters_features_option_hdr": "HDR",
"Current version: ": "현재 버전: ",
"next_steps_error_message_refresh": "새로 고침",
- "next_steps_error_message_go_to_youtube": "YouTube로 가기",
+ "next_steps_error_message_go_to_youtube": "유튜브로 가기",
"search_filters_features_option_subtitles": "자막",
"`x` marked it with a ❤": "`x`님의 ❤",
"Download as: ": "다음으로 다운로드: ",
@@ -245,14 +245,14 @@
"Could not create mix.": "믹스를 생성할 수 없습니다.",
"`x` ago": "`x` 전",
"comments_view_x_replies_0": "답글 {{count}}개 보기",
- "View Reddit comments": "Reddit의 댓글 보기",
+ "View Reddit comments": "레딧 댓글 보기",
"Engagement: ": "약속: ",
"Wilson score: ": "Wilson Score: ",
- "Family friendly? ": "가족 친화적입니까? ",
+ "Family friendly? ": "전연령 영상입니까? ",
"Quota exceeded, try again in a few hours": "한도량을 초과했습니다. 몇 시간 후에 다시 시도하세요",
"View `x` comments": {
- "([^.,0-9]|^)1([^.,0-9]|$)": "`x` 개의 댓글 보기",
- "": "`x` 개의 댓글 보기"
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x`개의 댓글 보기",
+ "": "`x`개의 댓글 보기"
},
"Haitian Creole": "아이티 크레올어",
"Gujarati": "구자라트어",
@@ -273,16 +273,16 @@
"Bosnian": "보스니아어",
"Belarusian": "벨라루스어",
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "로그인할 수 없습니다. 이중 인증(Authenticator 또는 SMS)이 켜져 있는지 확인하세요.",
- "View more comments on Reddit": "Reddit에서 더 많은 댓글 보기",
- "View YouTube comments": "YouTube 댓글 보기",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "JavaScript가 꺼져 있는 것 같습니다! 댓글을 보려면 여기를 클릭하세요. 댓글을 로드하는 데 시간이 조금 더 걸릴 수 있습니다.",
- "Shared `x`": "공유된 `x`",
+ "View more comments on Reddit": "레딧에서 더 많은 댓글 보기",
+ "View YouTube comments": "유튜브 댓글 보기",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "자바스크립트가 꺼져 있는 것 같습니다! 댓글을 보려면 여기를 클릭하세요. 댓글을 로드하는 데 시간이 조금 더 걸릴 수 있습니다.",
+ "Shared `x`": "`x` 업로드",
"Whitelisted regions: ": "차단되지 않은 지역: ",
"search_filters_sort_option_views": "조회수",
"Please log in": "로그인하세요",
"Password cannot be longer than 55 characters": "비밀번호는 55자 이하여야 합니다",
"Password cannot be empty": "비밀번호는 비워둘 수 없습니다",
- "Please sign in using 'Log in with Google'": "'Google로 로그인'을 사용하여 로그인하세요",
+ "Please sign in using 'Log in with Google'": "'구글로 로그인'을 사용하여 로그인하세요",
"Wrong username or password": "잘못된 사용자 이름 또는 비밀번호",
"Password is a required field": "비밀번호는 필수 필드입니다",
"User ID is a required field": "사용자 ID는 필수 필드입니다",
@@ -312,13 +312,13 @@
"Fallback comments: ": "대체 댓글: ",
"Swahili": "스와힐리어",
"Sundanese": "순다어",
- "generic_count_years_0": "{{count}} 년",
- "generic_count_months_0": "{{count}} 개월",
- "generic_count_weeks_0": "{{count}} 주",
- "generic_count_days_0": "{{count}} 일",
- "generic_count_hours_0": "{{count}} 시간",
- "generic_count_minutes_0": "{{count}} 분",
- "generic_count_seconds_0": "{{count}} 초",
+ "generic_count_years_0": "{{count}}년",
+ "generic_count_months_0": "{{count}}개월",
+ "generic_count_weeks_0": "{{count}}주",
+ "generic_count_days_0": "{{count}}일",
+ "generic_count_hours_0": "{{count}}시간",
+ "generic_count_minutes_0": "{{count}}분",
+ "generic_count_seconds_0": "{{count}}초",
"Zulu": "줄루어",
"Yoruba": "요루바어",
"Yiddish": "이디시어",
@@ -339,7 +339,7 @@
"comments_points_count_0": "{{count}} 포인트",
"Invidious Private Feed for `x`": "`x` 에 대한 Invidious 비공개 피드",
"Premieres `x`": "최초 공개 `x`",
- "Premieres in `x`": "`x` 에 최초 공개",
+ "Premieres in `x`": "`x` 후 최초 공개",
"next_steps_error_message": "다음 방법을 시도해 보세요: ",
"search_filters_features_option_c_commons": "크리에이티브 커먼즈",
"search_filters_duration_label": "길이",
@@ -352,7 +352,7 @@
"Video mode": "비디오 모드",
"Audio mode": "오디오 모드",
"permalink": "퍼머링크",
- "YouTube comment permalink": "YouTube 댓글 퍼머링크",
+ "YouTube comment permalink": "유튜브 댓글 퍼머링크",
"(edited)": "(수정됨)",
"%A %B %-d, %Y": "%A %B %-d, %Y",
"Movies": "영화",
@@ -396,7 +396,7 @@
"search_filters_features_option_purchased": "구입한 항목",
"search_filters_apply_button": "선택한 필터 적용하기",
"preferences_quality_dash_option_240p": "240p",
- "preferences_region_label": "콘텐트 국가: ",
+ "preferences_region_label": "지역: ",
"preferences_quality_dash_option_1440p": "1440p",
"French (auto-generated)": "프랑스어 (자동 생성됨)",
"Indonesian (auto-generated)": "인도네시아어 (자동 생성됨)",
@@ -404,13 +404,13 @@
"Vietnamese (auto-generated)": "베트남어 (자동 생성됨)",
"preferences_quality_dash_option_2160p": "2160p",
"Italian (auto-generated)": "이탈리아어 (자동 생성됨)",
- "preferences_quality_option_medium": "중간",
+ "preferences_quality_option_medium": "보통",
"preferences_quality_dash_option_720p": "720p",
"search_filters_duration_option_medium": "중간 (4 - 20분)",
"preferences_quality_dash_option_best": "최고",
"Portuguese (auto-generated)": "포르투갈어 (자동 생성됨)",
"Spanish (Spain)": "스페인어 (스페인)",
- "preferences_quality_dash_label": "선호하시는 DASH 비디오 품질: ",
+ "preferences_quality_dash_label": "선호하는 DASH 비디오 품질: ",
"preferences_quality_option_hd720": "HD720",
"Spanish (auto-generated)": "스페인어 (자동 생성됨)",
"preferences_quality_dash_option_1080p": "1080p",
@@ -437,7 +437,24 @@
"Spanish (Mexico)": "스페인어 (멕시코)",
"search_filters_type_option_all": "모든 유형",
"footer_donate_page": "기부하기",
- "preferences_quality_option_dash": "DASH (적절한 화질)",
+ "preferences_quality_option_dash": "DASH (다양한 화질)",
"preferences_quality_dash_option_360p": "360p",
- "preferences_save_player_pos_label": "이어서 보기 활성화 "
+ "preferences_save_player_pos_label": "이어서 보기 활성화: ",
+ "none": "없음",
+ "videoinfo_started_streaming_x_ago": "'x' 전에 스트리밍을 시작했습니다",
+ "crash_page_you_found_a_bug": "Invidious에서 버그를 찾은 것 같습니다!",
+ "download_subtitles": "자막 - `x`(.vtt)",
+ "user_saved_playlists": "`x`개의 저장된 재생목록",
+ "crash_page_before_reporting": "버그를 보고하기 전에 다음 사항이 있는지 확인합니다:",
+ "crash_page_search_issue": "<a href=\"`x`\">깃허브에서 기존 이슈</a>를 검색했습니다",
+ "Video unavailable": "비디오를 사용할 수 없음",
+ "crash_page_refresh": "<a href=\"`x`\">페이지를 새로고침</a>하려고 했습니다",
+ "videoinfo_watch_on_youTube": "유튜브에서 보기",
+ "crash_page_switch_instance": "<a href=\"`x`\">다른 인스턴스를 사용</a>하려고 했습니다",
+ "crash_page_read_the_faq": "<a href=\"`x`\">자주 묻는 질문(FAQ)</a> 읽기",
+ "user_created_playlists": "`x`개의 생성된 재생목록",
+ "crash_page_report_issue": "위의 방법 중 어느 것도 도움이 되지 않았다면, <a href=\"`x`\">깃허브에서 새 이슈를 열고</a>(가능하면 영어로) 메시지에 다음 텍스트를 포함하세요(해당 텍스트를 번역하지 마십시오):",
+ "videoinfo_youTube_embed_link": "임베드",
+ "videoinfo_invidious_embed_link": "임베드 링크",
+ "error_video_not_in_playlist": "요청한 동영상이 이 재생목록에 없습니다. <a href=\"`x`\">재생목록 목록을 보려면 여기를 클릭하십시오.</a>"
}
diff --git a/locales/lt.json b/locales/lt.json
index 607b3705..b4a6da04 100644
--- a/locales/lt.json
+++ b/locales/lt.json
@@ -21,15 +21,15 @@
"No": "Ne",
"Import and Export Data": "Importuoti ir eksportuoti duomenis",
"Import": "Importuoti",
- "Import Invidious data": "Importuoti Invidious duomenis",
- "Import YouTube subscriptions": "Importuoti YouTube prenumeratas",
+ "Import Invidious data": "Importuoti Invidious JSON duomenis",
+ "Import YouTube subscriptions": "Importuoti YouTube/OPML prenumeratas",
"Import FreeTube subscriptions (.db)": "Importuoti FreeTube prenumeratas (.db)",
"Import NewPipe subscriptions (.json)": "Importuoti NewPipe prenumeratas (.json)",
"Import NewPipe data (.zip)": "Importuoti NewPipe duomenis (.zip)",
"Export": "Eksportuoti",
"Export subscriptions as OPML": "Eksportuoti prenumeratas kaip OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuoti prenumeratas kaip OPML (skirta NewPipe & FreeTube)",
- "Export data as JSON": "Eksportuoti duomenis kaip JSON",
+ "Export data as JSON": "Eksportuoti Invidious duomenis kaip JSON",
"Delete account?": "Ištrinti paskyrą?",
"History": "Istorija",
"An alternative front-end to YouTube": "Alternatyvus YouTube žiūrėjimo būdas",
@@ -66,7 +66,7 @@
"preferences_related_videos_label": "Rodyti susijusius vaizdo įrašus: ",
"preferences_annotations_label": "Rodyti anotacijas pagal nutylėjimą: ",
"preferences_extend_desc_label": "Automatiškai išplėsti vaizdo įrašo aprašymą: ",
- "preferences_vr_mode_label": "Interaktyvūs 360 laipsnių vaizdo įrašai: ",
+ "preferences_vr_mode_label": "Interaktyvūs 360 laipsnių vaizdo įrašai (reikalingas WebGL): ",
"preferences_category_visual": "Vizualinės nuostatos",
"preferences_player_style_label": "Vaizdo grotuvo stilius: ",
"Dark mode: ": "Tamsus rėžimas: ",
@@ -371,5 +371,35 @@
"preferences_quality_dash_option_best": "Geriausia",
"preferences_quality_dash_option_worst": "Blogiausia",
"preferences_quality_dash_option_auto": "Automatinis",
- "search_filters_title": "Filtras"
+ "search_filters_title": "Filtras",
+ "generic_videos_count_0": "{{count}} vaizdo įrašas",
+ "generic_videos_count_1": "{{count}} vaizdo įrašai",
+ "generic_videos_count_2": "{{count}} vaizdo įrašų",
+ "generic_subscribers_count_0": "{{count}} prenumeratorius",
+ "generic_subscribers_count_1": "{{count}} prenumeratoriai",
+ "generic_subscribers_count_2": "{{count}} prenumeratorių",
+ "generic_subscriptions_count_0": "{{count}} prenumerata",
+ "generic_subscriptions_count_1": "{{count}} prenumeratos",
+ "generic_subscriptions_count_2": "{{count}} prenumeratų",
+ "preferences_watch_history_label": "Įgalinti žiūrėjimo istoriją: ",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "invidious": "Invidious",
+ "preferences_quality_dash_option_720p": "720p",
+ "generic_playlists_count_0": "{{count}} grojaraštis",
+ "generic_playlists_count_1": "{{count}} grojaraščiai",
+ "generic_playlists_count_2": "{{count}} grojaraščių",
+ "preferences_quality_option_medium": "Vidutinė",
+ "preferences_quality_option_small": "Maža",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_144p": "144p",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_option_dash": "DASH (prisitaikanti kokybė)",
+ "generic_views_count_0": "{{count}} peržiūra",
+ "generic_views_count_1": "{{count}} peržiūros",
+ "generic_views_count_2": "{{count}} peržiūrų",
+ "preferences_quality_dash_option_480p": "480p",
+ "preferences_quality_dash_option_240p": "240p"
}
diff --git a/locales/vi.json b/locales/vi.json
index 709013a2..07fcf52f 100644
--- a/locales/vi.json
+++ b/locales/vi.json
@@ -177,7 +177,7 @@
"Not a playlist.": "Không phải danh sách phát.",
"Playlist does not exist.": "Danh sách phát không tồn tại.",
"Could not pull trending pages.": "Không thể kéo các trang thịnh hành.",
- "Hidden field \"challenge\" is a required field": "Trường ẩn \"challenge\" là trường bắt buộc",
+ "Hidden field \"challenge\" is a required field": "Trường ẩn \"challenge\" là trường bắt buộc",
"Hidden field \"token\" is a required field": "Trường ẩn \"token\" là trường bắt buộc",
"Erroneous challenge": "Thử thách sai",
"Erroneous token": "Mã thông báo bị lỗi",
@@ -341,5 +341,10 @@
"search_filters_features_option_location": "vị trí",
"search_filters_features_option_hdr": "hdr",
"Current version: ": "Phiên bản hiện tại: ",
- "search_filters_title": "bộ lọc"
+ "search_filters_title": "bộ lọc",
+ "generic_playlists_count": "{{count}} danh sách phát",
+ "generic_views_count": "{{count}} lượt xem",
+ "View `x` comments": {
+ "": "Xem `x` bình luận"
+ }
}
diff --git a/src/invidious.cr b/src/invidious.cr
index 0601d5b2..58adaa35 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -172,6 +172,8 @@ end
CONNECTION_CHANNEL = Channel({Bool, Channel(PQ::Notification)}).new(32)
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(CONNECTION_CHANNEL, CONFIG.database_url)
+Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
+
Invidious::Jobs.start_all
def popular_videos
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
index f0873df4..c9bf43a4 100644
--- a/src/invidious/config.cr
+++ b/src/invidious/config.cr
@@ -78,6 +78,10 @@ class Config
property decrypt_polling : Bool = false
# Used for crawling channels: threads should check all videos uploaded by a channel
property full_refresh : Bool = false
+
+ # Jobs config structure. See jobs.cr and jobs/base_job.cr
+ property jobs = Invidious::Jobs::JobsConfig.new
+
# Used to tell Invidious it is behind a proxy, so links to resources should be https://
property https_only : Bool?
# HMAC signing key for CSRF tokens and verifying pubsub subscriptions
diff --git a/src/invidious/database/nonces.cr b/src/invidious/database/nonces.cr
index 469fcbd8..b87c81ec 100644
--- a/src/invidious/database/nonces.cr
+++ b/src/invidious/database/nonces.cr
@@ -4,7 +4,7 @@ module Invidious::Database::Nonces
extend self
# -------------------
- # Insert
+ # Insert / Delete
# -------------------
def insert(nonce : String, expire : Time)
@@ -17,6 +17,15 @@ module Invidious::Database::Nonces
PG_DB.exec(request, nonce, expire)
end
+ def delete_expired
+ request = <<-SQL
+ DELETE FROM nonces *
+ WHERE expire < now()
+ SQL
+
+ PG_DB.exec(request)
+ end
+
# -------------------
# Update
# -------------------
diff --git a/src/invidious/database/videos.cr b/src/invidious/database/videos.cr
index e1fa01c3..695f5b33 100644
--- a/src/invidious/database/videos.cr
+++ b/src/invidious/database/videos.cr
@@ -22,6 +22,15 @@ module Invidious::Database::Videos
PG_DB.exec(request, id)
end
+ def delete_expired
+ request = <<-SQL
+ DELETE FROM videos *
+ WHERE updated < (now() - interval '6 hours')
+ SQL
+
+ PG_DB.exec(request)
+ end
+
def update(video : Video)
request = <<-SQL
UPDATE videos
diff --git a/src/invidious/jobs.cr b/src/invidious/jobs.cr
index ec0cad64..524a3624 100644
--- a/src/invidious/jobs.cr
+++ b/src/invidious/jobs.cr
@@ -1,12 +1,39 @@
module Invidious::Jobs
JOBS = [] of BaseJob
+ # Automatically generate a structure that wraps the various
+ # jobs' configs, so that the follwing YAML config can be used:
+ #
+ # jobs:
+ # job_name:
+ # enabled: true
+ # some_property: "value"
+ #
+ macro finished
+ struct JobsConfig
+ include YAML::Serializable
+
+ {% for sc in BaseJob.subclasses %}
+ # Voodoo macro to transform `Some::Module::CustomJob` to `custom`
+ {% class_name = sc.id.split("::").last.id.gsub(/Job$/, "").underscore %}
+
+ getter {{ class_name }} = {{ sc.name }}::Config.new
+ {% end %}
+
+ def initialize
+ end
+ end
+ end
+
def self.register(job : BaseJob)
JOBS << job
end
def self.start_all
JOBS.each do |job|
+ # Don't run the main rountine if the job is disabled by config
+ next if job.disabled?
+
spawn { job.begin }
end
end
diff --git a/src/invidious/jobs/base_job.cr b/src/invidious/jobs/base_job.cr
index 47e75864..f90f0bfe 100644
--- a/src/invidious/jobs/base_job.cr
+++ b/src/invidious/jobs/base_job.cr
@@ -1,3 +1,33 @@
abstract class Invidious::Jobs::BaseJob
abstract def begin
+
+ # When this base job class is inherited, make sure to define
+ # a basic "Config" structure, that contains the "enable" property,
+ # and to create the associated instance property.
+ #
+ macro inherited
+ macro finished
+ # This config structure can be expanded as required.
+ struct Config
+ include YAML::Serializable
+
+ property enable = true
+
+ def initialize
+ end
+ end
+
+ property cfg = Config.new
+
+ # Return true if job is enabled by config
+ protected def enabled? : Bool
+ return (@cfg.enable == true)
+ end
+
+ # Return true if job is disabled by config
+ protected def disabled? : Bool
+ return (@cfg.enable == false)
+ end
+ end
+ end
end
diff --git a/src/invidious/jobs/clear_expired_items_job.cr b/src/invidious/jobs/clear_expired_items_job.cr
new file mode 100644
index 00000000..17191aac
--- /dev/null
+++ b/src/invidious/jobs/clear_expired_items_job.cr
@@ -0,0 +1,27 @@
+class Invidious::Jobs::ClearExpiredItemsJob < Invidious::Jobs::BaseJob
+ # Remove items (videos, nonces, etc..) whose cache is outdated every hour.
+ # Removes the need for a cron job.
+ def begin
+ loop do
+ failed = false
+
+ LOGGER.info("jobs: running ClearExpiredItems job")
+
+ begin
+ Invidious::Database::Videos.delete_expired
+ Invidious::Database::Nonces.delete_expired
+ rescue DB::Error
+ failed = true
+ end
+
+ # Retry earlier than scheduled on DB error
+ if failed
+ LOGGER.info("jobs: ClearExpiredItems failed. Retrying in 10 minutes.")
+ sleep 10.minutes
+ else
+ LOGGER.info("jobs: ClearExpiredItems done.")
+ sleep 1.hour
+ end
+ end
+ end
+end
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 243ea3a4..ae478378 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -7,7 +7,7 @@
<meta name="thumbnail" content="<%= thumbnail %>">
<meta name="description" content="<%= HTML.escape(video.short_description) %>">
<meta name="keywords" content="<%= video.keywords.join(",") %>">
-<meta property="og:site_name" content="Invidious">
+<meta property="og:site_name" content="<%= author %> | Invidious">
<meta property="og:url" content="<%= HOST_URL %>/watch?v=<%= video.id %>">
<meta property="og:title" content="<%= title %>">
<meta property="og:image" content="/vi/<%= video.id %>/maxres.jpg">