summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/default.css6
-rw-r--r--locales/el.json4
-rw-r--r--locales/en-US.json1
-rw-r--r--locales/es.json106
-rw-r--r--locales/ja.json2
-rw-r--r--locales/pt-BR.json2
-rw-r--r--src/invidious/channels/community.cr53
-rw-r--r--src/invidious/comments.cr38
-rw-r--r--src/invidious/routes/api/v1/authenticated.cr59
-rw-r--r--src/invidious/routing.cr5
10 files changed, 205 insertions, 71 deletions
diff --git a/assets/css/default.css b/assets/css/default.css
index 3deaebe1..f8b1c9f7 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -515,7 +515,7 @@ hr {
#descexpansionbutton ~ div {
overflow: hidden;
- height: 8.3em;
+ max-height: 8.3em;
}
#descexpansionbutton:checked ~ div {
@@ -583,3 +583,7 @@ p,
/* Wider settings name to less word wrap */
.pure-form-aligned .pure-control-group label { width: 19em; }
+
+.channel-emoji {
+ margin: 0 2px;
+}
diff --git a/locales/el.json b/locales/el.json
index 3448a4dc..8d0c84dd 100644
--- a/locales/el.json
+++ b/locales/el.json
@@ -366,7 +366,7 @@
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Μεσαία",
"preferences_quality_option_small": "Μικρό",
- "preferences_quality_option_dash": "DASH (προσαρμοστική ποιότητα)",
+ "preferences_quality_option_dash": "DASH (προσαρμόσιμη ποιότητα)",
"preferences_quality_dash_option_4320p": "4320p",
"preferences_quality_dash_option_720p": "720p",
"invidious": "Invidious",
@@ -450,5 +450,5 @@
"search_filters_type_option_show": "Μπάρα προόδου διαβάσματος",
"preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ",
"search_filters_title": "Φίλτρο",
- "search_message_no_results": "Δεν"
+ "search_message_no_results": "Δε βρέθηκαν αποτελέσματα."
}
diff --git a/locales/en-US.json b/locales/en-US.json
index 65a81ab7..fee0b037 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -406,6 +406,7 @@
"YouTube comment permalink": "YouTube comment permalink",
"permalink": "permalink",
"`x` marked it with a ❤": "`x` marked it with a ❤",
+ "Channel Sponsor": "Channel Sponsor",
"Audio mode": "Audio mode",
"Video mode": "Video mode",
"Playlists": "Playlists",
diff --git a/locales/es.json b/locales/es.json
index 6cf721f3..a0d16325 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -52,21 +52,21 @@
"preferences_video_loop_label": "Repetir siempre: ",
"preferences_autoplay_label": "Reproducción automática: ",
"preferences_continue_label": "Reproducir siguiente por defecto: ",
- "preferences_continue_autoplay_label": "Reproducir automáticamente el vídeo siguiente: ",
+ "preferences_continue_autoplay_label": "Reproducir automáticamente el video siguiente: ",
"preferences_listen_label": "Activar el sonido por defecto: ",
- "preferences_local_label": "¿Usar un proxy para los vídeos? ",
+ "preferences_local_label": "¿Usar un proxy para los videos? ",
"preferences_speed_label": "Velocidad por defecto: ",
- "preferences_quality_label": "Calidad de vídeo preferida: ",
+ "preferences_quality_label": "Calidad de video preferida: ",
"preferences_volume_label": "Volumen del reproductor: ",
"preferences_comments_label": "Comentarios por defecto: ",
"youtube": "YouTube",
"reddit": "Reddit",
"preferences_captions_label": "Subtítulos por defecto: ",
"Fallback captions: ": "Subtítulos alternativos: ",
- "preferences_related_videos_label": "¿Mostrar vídeos relacionados? ",
+ "preferences_related_videos_label": "¿Mostrar videos relacionados? ",
"preferences_annotations_label": "¿Mostrar anotaciones por defecto? ",
- "preferences_extend_desc_label": "Extender automáticamente la descripción del vídeo: ",
- "preferences_vr_mode_label": "Vídeos interactivos de 360 grados (necesita WebGL): ",
+ "preferences_extend_desc_label": "Extender automáticamente la descripción del video: ",
+ "preferences_vr_mode_label": "Videos interactivos de 360 grados (necesita WebGL): ",
"preferences_category_visual": "Preferencias visuales",
"preferences_player_style_label": "Estilo de reproductor: ",
"Dark mode: ": "Modo oscuro: ",
@@ -79,16 +79,16 @@
"preferences_category_subscription": "Preferencias de la suscripción",
"preferences_annotations_subscribed_label": "¿Mostrar anotaciones por defecto para los canales suscritos? ",
"Redirect homepage to feed: ": "Redirigir la página de inicio a la fuente: ",
- "preferences_max_results_label": "Número de vídeos mostrados en la fuente: ",
- "preferences_sort_label": "Ordenar los vídeos por: ",
+ "preferences_max_results_label": "Número de videos mostrados en la fuente: ",
+ "preferences_sort_label": "Ordenar los videos por: ",
"published": "fecha de publicación",
"published - reverse": "fecha de publicación: orden inverso",
"alphabetically": "alfabéticamente",
"alphabetically - reverse": "alfabéticamente: orden inverso",
"channel name": "nombre del canal",
"channel name - reverse": "nombre del canal: orden inverso",
- "Only show latest video from channel: ": "Mostrar solo el último vídeo del canal: ",
- "Only show latest unwatched video from channel: ": "Mostrar solo el último vídeo sin ver del canal: ",
+ "Only show latest video from channel: ": "Mostrar solo el último video del canal: ",
+ "Only show latest unwatched video from channel: ": "Mostrar solo el último video sin ver del canal: ",
"preferences_unseen_only_label": "Mostrar solo los no vistos: ",
"preferences_notifications_only_label": "Mostrar solo notificaciones (si hay alguna): ",
"Enable web notifications": "Habilitar notificaciones web",
@@ -139,7 +139,7 @@
"Editing playlist `x`": "Editando la lista de reproducción 'x'",
"Show more": "Mostrar más",
"Show less": "Mostrar menos",
- "Watch on YouTube": "Ver el vídeo en YouTube",
+ "Watch on YouTube": "Ver en YouTube",
"Switch Invidious Instance": "Cambiar Instancia de Invidious",
"Hide annotations": "Ocultar anotaciones",
"Show annotations": "Mostrar anotaciones",
@@ -153,7 +153,7 @@
"Shared `x`": "Compartido `x`",
"Premieres in `x`": "Se estrena en `x`",
"Premieres `x`": "Estrenos `x`",
- "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "¡Hola! Parece que tienes JavaScript desactivado. Haz clic aquí para ver los comentarios, pero tengas en cuenta que pueden tardar un poco más en cargarse.",
"View YouTube comments": "Ver los comentarios de YouTube",
"View more comments on Reddit": "Ver más comentarios en Reddit",
"View `x` comments": {
@@ -164,7 +164,7 @@
"Hide replies": "Ocultar las respuestas",
"Show replies": "Mostrar las respuestas",
"Incorrect password": "Contraseña incorrecta",
- "Quota exceeded, try again in a few hours": "Cuota excedida, pruebe otra vez en unas horas",
+ "Quota exceeded, try again in a few hours": "Cuota excedida, prueba otra vez en unas horas",
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.",
"Invalid TFA code": "Código TFA no válido",
"Login failed. This may be because two-factor authentication is not turned on for your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.",
@@ -176,7 +176,7 @@
"Wrong username or password": "Nombre o contraseña incorrecto",
"Please sign in using 'Log in with Google'": "Inicie sesión con «Iniciar sesión con Google»",
"Password cannot be empty": "La contraseña no puede estar en blanco",
- "Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres",
+ "Password cannot be longer than 55 characters": "La contraseña no debe tener más de 55 caracteres",
"Please log in": "Inicie sesión, por favor",
"Invidious Private Feed for `x`": "Fuente privada de Invidious para `x`",
"channel:`x`": "canal: `x`",
@@ -198,7 +198,7 @@
"No such user": "Usuario no existe",
"Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo",
"English": "Inglés",
- "English (auto-generated)": "Inglés (generados automáticamente)",
+ "English (auto-generated)": "Inglés (generado automáticamente)",
"Afrikaans": "Afrikáans",
"Albanian": "Albanés",
"Amharic": "Amárico",
@@ -324,50 +324,51 @@
"permalink": "enlace permanente",
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
"Audio mode": "Modo de audio",
- "Video mode": "Modo de vídeo",
- "channel_tab_videos_label": "Vídeos",
+ "Video mode": "Modo de video",
+ "channel_tab_videos_label": "Videos",
"Playlists": "Listas de reproducción",
"channel_tab_community_label": "Comunidad",
- "search_filters_sort_option_relevance": "relevancia",
- "search_filters_sort_option_rating": "valoración",
- "search_filters_sort_option_date": "fecha",
- "search_filters_sort_option_views": "visualizaciones",
- "search_filters_type_label": "content_type",
+ "search_filters_sort_option_relevance": "Relevancia",
+ "search_filters_sort_option_rating": "Valoración",
+ "search_filters_sort_option_date": "Fecha de subida",
+ "search_filters_sort_option_views": "Visualizaciones",
+ "search_filters_type_label": "tipo de contenido",
"search_filters_duration_label": "duración",
"search_filters_features_label": "funcionalidades",
"search_filters_sort_label": "ordenar",
- "search_filters_date_option_hour": "hora",
- "search_filters_date_option_today": "hoy",
- "search_filters_date_option_week": "semana",
- "search_filters_date_option_month": "mes",
- "search_filters_date_option_year": "año",
- "search_filters_type_option_video": "vídeo",
- "search_filters_type_option_channel": "canal",
- "search_filters_type_option_playlist": "lista de reproducción",
- "search_filters_type_option_movie": "película",
- "search_filters_type_option_show": "programa",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "subtítulos",
- "search_filters_features_option_c_commons": "creative_commons",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "directo",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "ubicación",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_date_option_hour": "Última hora",
+ "search_filters_date_option_today": "Hoy",
+ "search_filters_date_option_week": "Esta semana",
+ "search_filters_date_option_month": "Este mes",
+ "search_filters_date_option_year": "Este año",
+ "search_filters_type_option_video": "Video",
+ "search_filters_type_option_channel": "Canal",
+ "search_filters_type_option_playlist": "Lista de reproducción",
+ "search_filters_type_option_movie": "Película",
+ "search_filters_type_option_show": "Programa",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Subtítulos",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "En directo",
+ "search_filters_features_option_four_k": "4K",
+ "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 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)",
- "search_filters_duration_option_long": "Largo (> 20 minutos)",
+ "search_filters_duration_option_short": "Menos de 4 minutos",
+ "search_filters_duration_option_medium": "De 4 a 20 minutos",
+ "search_filters_duration_option_long": "Más de 20 minutos",
"footer_documentation": "Documentación",
"footer_original_source_code": "Código fuente original",
- "adminprefs_modified_source_code_url_label": "URL al repositorio de código fuente modificado",
+ "adminprefs_modified_source_code_url_label": "Enlace al repositorio de código fuente modificado",
"footer_source_code": "Código fuente",
"footer_modfied_source_code": "Código fuente modificado",
"footer_donate_page": "Donar",
"preferences_region_label": "País del contenido: ",
- "preferences_quality_dash_label": "Calidad de vídeo DASH preferida: ",
+ "preferences_quality_dash_label": "Calidad de video DASH preferida: ",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Intermedia",
"preferences_quality_dash_option_auto": "Automática",
@@ -376,7 +377,7 @@
"download_subtitles": "Subtítulos- `x` (.vtt)",
"user_created_playlists": "`x` listas de reproducción creadas",
"user_saved_playlists": "`x` listas de reproducción guardadas",
- "Video unavailable": "Vídeo no disponible",
+ "Video unavailable": "Video no disponible",
"videoinfo_youTube_embed_link": "Insertar",
"preferences_quality_dash_option_2160p": "2160p",
"preferences_quality_dash_option_4320p": "4320p",
@@ -413,8 +414,8 @@
"generic_count_weeks_plural": "{{count}} semanas",
"generic_playlists_count": "{{count}} lista de reproducción",
"generic_playlists_count_plural": "{{count}} listas de reproducción",
- "generic_videos_count": "{{count}} vídeo",
- "generic_videos_count_plural": "{{count}} vídeos",
+ "generic_videos_count": "{{count}} video",
+ "generic_videos_count_plural": "{{count}} videos",
"generic_count_months": "{{count}} mes",
"generic_count_months_plural": "{{count}} meses",
"comments_points_count": "{{count}} punto",
@@ -433,7 +434,7 @@
"crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en GitHub</a>",
"crash_page_you_found_a_bug": "¡Parece que has encontrado un error en Invidious!",
"crash_page_refresh": "probado a <a href=\"`x`\">recargar la página</a>",
- "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye el siguiente texto en tu mensaje (NO traduzcas este texto):",
+ "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye verbatim el siguiente texto en tu mensaje:",
"English (United States)": "Inglés (Estados Unidos)",
"Cantonese (Hong Kong)": "Cantonés (Hong Kong)",
"Dutch (auto-generated)": "Neerlandés (generados automáticamente)",
@@ -461,23 +462,22 @@
"search_message_no_results": "No se han encontrado resultados.",
"search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.",
"search_filters_title": "Filtros",
- "search_filters_date_label": "Fecha de subida",
+ "search_filters_date_label": "fecha de subida",
"search_filters_date_option_none": "Cualquier fecha",
"search_filters_type_option_all": "Cualquier tipo",
"search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180",
- "search_filters_apply_button": "Aplicar filtros seleccionados",
+ "search_filters_apply_button": "Aplicar filtros",
"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? ",
- "error_video_not_in_playlist": "El vídeo solicitado no existe en esta lista de reproducción. <a href=\"`x`\">Haga clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
+ "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
"channel_tab_streams_label": "Directos",
"channel_tab_channels_label": "Canales",
"channel_tab_shorts_label": "Cortos",
"channel_tab_playlists_label": "Listas de reproducción",
- "Music in this video": "Música en este vídeo",
+ "Music in this video": "Música en este video",
"Artist: ": "Artista: ",
"Album: ": "Álbum: "
}
diff --git a/locales/ja.json b/locales/ja.json
index d08413ea..4d2ed5a0 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -366,7 +366,7 @@
"search_filters_features_option_subtitles": "字幕",
"search_filters_features_option_c_commons": "クリエイティブ・コモンズ",
"search_filters_features_option_three_d": "3D",
- "search_filters_features_option_live": "生配信",
+ "search_filters_features_option_live": "ライブ",
"search_filters_features_option_four_k": "4K",
"search_filters_features_option_location": "場所",
"search_filters_features_option_hdr": "HDR",
diff --git a/locales/pt-BR.json b/locales/pt-BR.json
index 079c4ea1..ec00d46e 100644
--- a/locales/pt-BR.json
+++ b/locales/pt-BR.json
@@ -381,7 +381,7 @@
"footer_documentation": "Documentação",
"footer_source_code": "Código fonte",
"footer_original_source_code": "Código fonte original",
- "footer_modfied_source_code": "Código Fonte Modificado",
+ "footer_modfied_source_code": "Código-fonte modificado",
"preferences_quality_dash_label": "Qualidade de vídeo do painel preferida: ",
"preferences_region_label": "País do conteúdo: ",
"preferences_quality_dash_option_4320p": "4320p",
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 13af2d8b..da8be6ea 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -1,3 +1,5 @@
+private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
+
# TODO: Add "sort_by"
def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en")
@@ -75,10 +77,9 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
json.field "author", author
json.field "authorThumbnails" do
json.array do
- qualities = {32, 48, 76, 100, 176, 512}
author_thumbnail = post["authorThumbnail"]["thumbnails"].as_a[0]["url"].as_s
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", author_thumbnail.gsub(/s\d+-/, "s#{quality}-")
json.field "width", quality
@@ -108,6 +109,8 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
like_count = post["actionButtons"]["commentActionButtonsRenderer"]["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"]
.try &.as_s.gsub(/\D/, "").to_i? || 0
+ reply_count = short_text_to_number(post.dig?("actionButtons", "commentActionButtonsRenderer", "replyButton", "buttonRenderer", "text", "simpleText").try &.as_s || "0")
+
json.field "content", html_to_content(content_html)
json.field "contentHtml", content_html
@@ -115,6 +118,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
json.field "likeCount", like_count
+ json.field "replyCount", reply_count
json.field "commentId", post["postId"]? || post["commentId"]? || ""
json.field "authorIsChannelOwner", post["authorEndpoint"]["browseEndpoint"]["browseId"] == ucid
@@ -174,9 +178,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
aspect_ratio = (width.to_f / height.to_f)
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
- qualities = {320, 560, 640, 1280, 2000}
-
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
@@ -185,10 +187,39 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
end
end
end
- # TODO
- # when .has_key?("pollRenderer")
- # attachment = attachment["pollRenderer"]
- # json.field "type", "poll"
+ when .has_key?("pollRenderer")
+ attachment = attachment["pollRenderer"]
+ json.field "type", "poll"
+ json.field "totalVotes", short_text_to_number(attachment["totalVotes"]["simpleText"].as_s.split(" ")[0])
+ json.field "choices" do
+ json.array do
+ attachment["choices"].as_a.each do |choice|
+ json.object do
+ json.field "text", choice.dig("text", "runs", 0, "text").as_s
+ # A choice can have an image associated with it.
+ # Ex post: https://www.youtube.com/post/UgkxD4XavXUD4NQiddJXXdohbwOwcVqrH9Re
+ if choice["image"]?
+ thumbnail = choice["image"]["thumbnails"][0].as_h
+ width = thumbnail["width"].as_i
+ height = thumbnail["height"].as_i
+ aspect_ratio = (width.to_f / height.to_f)
+ url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
+ json.field "image" do
+ json.array do
+ IMAGE_QUALITIES.each do |quality|
+ json.object do
+ json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
+ json.field "width", quality
+ json.field "height", (quality / aspect_ratio).ceil.to_i
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
when .has_key?("postMultiImageRenderer")
attachment = attachment["postMultiImageRenderer"]
json.field "type", "multiImage"
@@ -202,9 +233,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
aspect_ratio = (width.to_f / height.to_f)
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
- qualities = {320, 560, 640, 1280, 2000}
-
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index 357a461c..b15d63d4 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -182,7 +182,11 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
json.field "contentHtml", content_html
json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil)
-
+ json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil)
+ if node_comment["sponsorCommentBadge"]?
+ # Sponsor icon thumbnails always have one object and there's only ever the url property in it
+ json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s
+ end
json.field "published", published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
@@ -324,11 +328,21 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
end
author_name = HTML.escape(child["author"].as_s)
+ sponsor_icon = ""
if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool
author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark-circle\"></i>"
elsif child["verified"]?.try &.as_bool
author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark\"></i>"
end
+
+ if child["isSponsor"]?.try &.as_bool
+ sponsor_icon = String.build do |str|
+ str << %(<img alt="" )
+ str << %(src="/ggpht) << URI.parse(child["sponsorIconUrl"].as_s).request_target << "\" "
+ str << %(title=") << translate(locale, "Channel Sponsor") << "\" "
+ str << %(width="16" height="16" />)
+ end
+ end
html << <<-END_HTML
<div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
@@ -339,6 +353,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
<b>
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
</b>
+ #{sponsor_icon}
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
END_HTML
@@ -675,6 +690,27 @@ def content_to_comment_html(content, video_id : String? = "")
text = "<s>#{text}</s>" if run["strikethrough"]?
text = "<i>#{text}</i>" if run["italics"]?
+ # check for custom emojis
+ if run["emoji"]?
+ if run["emoji"]["isCustomEmoji"]?.try &.as_bool
+ if emojiImage = run.dig?("emoji", "image")
+ emojiAlt = emojiImage.dig?("accessibility", "accessibilityData", "label").try &.as_s || text
+ emojiThumb = emojiImage["thumbnails"][0]
+ text = String.build do |str|
+ str << %(<img alt=") << emojiAlt << "\" "
+ str << %(src="/ggpht) << URI.parse(emojiThumb["url"].as_s).request_target << "\" "
+ str << %(title=") << emojiAlt << "\" "
+ str << %(width=") << emojiThumb["width"] << "\" "
+ str << %(height=") << emojiThumb["height"] << "\" "
+ str << %(class="channel-emoji"/>)
+ end
+ else
+ # Hide deleted channel emoji
+ text = ""
+ end
+ end
+ end
+
text
end
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index 6b935312..ce2ee812 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -54,6 +54,65 @@ module Invidious::Routes::API::V1::Authenticated
env.response.status_code = 204
end
+ def self.get_history(env)
+ env.response.content_type = "application/json"
+ user = env.get("user").as(User)
+
+ page = env.params.query["page"]?.try &.to_i?.try &.clamp(0, Int32::MAX)
+ page ||= 1
+
+ max_results = env.params.query["max_results"]?.try &.to_i?.try &.clamp(0, MAX_ITEMS_PER_PAGE)
+ max_results ||= user.preferences.max_results
+ max_results ||= CONFIG.default_user_preferences.max_results
+
+ start_index = (page - 1) * max_results
+ if user.watched[start_index]?
+ watched = user.watched.reverse[start_index, max_results]
+ end
+ watched ||= [] of String
+
+ return watched.to_json
+ end
+
+ def self.mark_watched(env)
+ user = env.get("user").as(User)
+
+ if !user.preferences.watch_history
+ return error_json(409, "Watch history is disabled in preferences.")
+ end
+
+ id = env.params.url["id"]
+ if !id.match(/^[a-zA-Z0-9_-]{11}$/)
+ return error_json(400, "Invalid video id.")
+ end
+
+ Invidious::Database::Users.mark_watched(user, id)
+ env.response.status_code = 204
+ end
+
+ def self.mark_unwatched(env)
+ user = env.get("user").as(User)
+
+ if !user.preferences.watch_history
+ return error_json(409, "Watch history is disabled in preferences.")
+ end
+
+ id = env.params.url["id"]
+ if !id.match(/^[a-zA-Z0-9_-]{11}$/)
+ return error_json(400, "Invalid video id.")
+ end
+
+ Invidious::Database::Users.mark_unwatched(user, id)
+ env.response.status_code = 204
+ end
+
+ def self.clear_history(env)
+ user = env.get("user").as(User)
+
+ Invidious::Database::Users.clear_watch_history(user)
+ env.response.status_code = 204
+ end
+
def self.feed(env)
env.response.content_type = "application/json"
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index dca2f117..9e2ade3d 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -257,6 +257,11 @@ module Invidious::Routing
get "/api/v1/auth/export/invidious", {{namespace}}::Authenticated, :export_invidious
post "/api/v1/auth/import/invidious", {{namespace}}::Authenticated, :import_invidious
+ get "/api/v1/auth/history", {{namespace}}::Authenticated, :get_history
+ post "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_watched
+ delete "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_unwatched
+ delete "/api/v1/auth/history", {{namespace}}::Authenticated, :clear_history
+
get "/api/v1/auth/feed", {{namespace}}::Authenticated, :feed
get "/api/v1/auth/subscriptions", {{namespace}}::Authenticated, :get_subscriptions