diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 90f01e7bd..b6eb244a7 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -12,7 +12,7 @@ jobs: dev: runs-on: ubuntu-latest steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 with: node-version: "lts/*" diff --git a/.github/workflows/build-prod.yml b/.github/workflows/build-prod.yml index 69f6387d8..12cfe1ffb 100644 --- a/.github/workflows/build-prod.yml +++ b/.github/workflows/build-prod.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout master (the latest release) - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 with: ref: master - name: Install jq to parse json @@ -33,7 +33,7 @@ jobs: - name: Save old Makefile version run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV - name: Checkout PR branch - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - name: Save new package.json version run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV - name: package.json version must be updated @@ -61,7 +61,7 @@ jobs: prod: runs-on: ubuntu-latest steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 with: node-version: "lts/*" diff --git a/.github/workflows/roku-analysis.yml b/.github/workflows/roku-analysis.yml index 0965214fc..73122243a 100644 --- a/.github/workflows/roku-analysis.yml +++ b/.github/workflows/roku-analysis.yml @@ -11,7 +11,7 @@ jobs: static: runs-on: ubuntu-latest steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3 with: node-version: "lts/*" diff --git a/components/ItemGrid/GridItem.brs b/components/ItemGrid/GridItem.brs index ea386b3b5..ff624e7d5 100644 --- a/components/ItemGrid/GridItem.brs +++ b/components/ItemGrid/GridItem.brs @@ -53,6 +53,9 @@ sub itemContentChanged() if itemData.json.UserData.UnplayedItemCount > 0 m.unplayedCount.visible = true m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount + else + m.unplayedCount.visible = false + m.unplayedEpisodeCount.text = "" end if end if end if diff --git a/components/data/SceneManager.brs b/components/data/SceneManager.brs index e876957f5..51191bb8d 100755 --- a/components/data/SceneManager.brs +++ b/components/data/SceneManager.brs @@ -77,16 +77,17 @@ end sub sub popScene() group = m.groups.pop() if group <> invalid - if group.isSubType("JFGroup") + groupType = group.subtype() + if groupType = "JFGroup" unregisterOverhangData(group) - else if group.isSubType("JFVideo") + else if groupType = "JFVideo" ' Stop video to make sure app communicates stop playstate to server group.control = "stop" end if group.visible = false - if group.isSubType("JFScreen") + if groupType = "JFScreen" group.callFunc("OnScreenHidden") end if else diff --git a/components/data/UserData.brs b/components/data/UserData.brs index 564848a6f..446abbf38 100644 --- a/components/data/UserData.brs +++ b/components/data/UserData.brs @@ -20,9 +20,6 @@ sub loadFromRegistry(id as string) end sub sub saveToRegistry() - set_user_setting("username", m.top.username) - set_user_setting("token", m.top.token) - users = parseJson(get_setting("available_users", "[]")) this_user = invalid for each user in users @@ -57,7 +54,9 @@ function setPreference(key as string, value as string) end function sub setActive() - set_setting("active_user", m.top.id) + if m.global.session.user.settings["global.rememberme"] + set_setting("active_user", m.top.id) + end if end sub sub setServer(hostname as string) diff --git a/components/movies/MovieDetails.brs b/components/movies/MovieDetails.brs index 4f72b4783..913ccd796 100644 --- a/components/movies/MovieDetails.brs +++ b/components/movies/MovieDetails.brs @@ -38,11 +38,13 @@ end sub sub trailerAvailableChanged() if m.top.trailerAvailable ' add trailor button to button group - trailerButton = CreateObject("roSGNode", "JFButton") + trailerButton = CreateObject("roSGNode", "Button") trailerButton.id = "trailer-button" trailerButton.text = tr("Play Trailer") + trailerButton.iconUri = "" + trailerButton.focusedIconUri = "" trailerButton.maxWidth = "300" - trailerButton.minWidth = "300" + trailerButton.minWidth = "280" m.buttonGrp.appendChild(trailerButton) else ' remove trailor button from button group diff --git a/components/settings/settings.brs b/components/settings/settings.brs index 5809353f4..d4354e065 100644 --- a/components/settings/settings.brs +++ b/components/settings/settings.brs @@ -1,10 +1,10 @@ import "pkg:/source/utils/config.brs" import "pkg:/source/utils/misc.brs" import "pkg:/source/roku_modules/log/LogMixin.brs" +import "pkg:/source/api/sdk.bs" sub init() m.log = log.Logger("Settings") - m.top.overhangTitle = tr("Settings") m.top.optionsAvailable = false m.userLocation = [] @@ -12,7 +12,6 @@ sub init() m.settingsMenu = m.top.findNode("settingsMenu") m.settingDetail = m.top.findNode("settingDetail") m.settingDesc = m.top.findNode("settingDesc") - m.settingTitle = m.top.findNode("settingTitle") m.path = m.top.findNode("path") m.boolSetting = m.top.findNode("boolSetting") @@ -72,7 +71,7 @@ sub LoadMenu(configSection) end if ' Set Path display - m.path.text = "" + m.path.text = tr("Settings") for each level in m.userLocation if level.title <> invalid then m.path.text += " / " + tr(level.title) end for @@ -82,7 +81,7 @@ sub settingFocused() selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused] m.settingDesc.text = tr(selectedSetting.Description) - m.settingTitle.text = tr(selectedSetting.Title) + m.top.overhangTitle = tr(selectedSetting.Title) ' Hide Settings m.boolSetting.visible = false @@ -160,14 +159,40 @@ end sub sub boolSettingChanged() - if m.boolSetting.focusedChild = invalid then return selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused] if m.boolSetting.checkedItem - set_user_setting(selectedSetting.settingName, "true") + session.user.settings.Save(selectedSetting.settingName, "true") + if Left(selectedSetting.settingName, 7) = "global." + ' global user setting + ' save to main registry block + set_setting(selectedSetting.settingName, "true") + ' setting specific triggers + if selectedSetting.settingName = "global.rememberme" + print "m.global.session.user.id=", m.global.session.user.id + set_setting("active_user", m.global.session.user.id) + end if + else + ' regular user setting + ' save to user specific registry block + set_user_setting(selectedSetting.settingName, "true") + end if else - set_user_setting(selectedSetting.settingName, "false") + session.user.settings.Save(selectedSetting.settingName, "false") + if Left(selectedSetting.settingName, 7) = "global." + ' global user setting + ' save to main registry block + set_setting(selectedSetting.settingName, "false") + ' setting specific triggers + if selectedSetting.settingName = "global.rememberme" + unset_setting("active_user") + end if + else + ' regular user setting + ' save to user specific registry block + set_user_setting(selectedSetting.settingName, "false") + end if end if end sub diff --git a/components/settings/settings.xml b/components/settings/settings.xml index 707bda79d..f35a6aa02 100644 --- a/components/settings/settings.xml +++ b/components/settings/settings.xml @@ -7,32 +7,35 @@ + itemSpacing="[0,9]" + textVertAlign="center" /> - + + - - + + + + + + + + \ No newline at end of file diff --git a/components/video/VideoPlayerView.brs b/components/video/VideoPlayerView.brs index d3c7bd6cd..b4f479484 100644 --- a/components/video/VideoPlayerView.brs +++ b/components/video/VideoPlayerView.brs @@ -23,6 +23,9 @@ sub init() m.top.observeField("content", "onContentChange") m.top.observeField("selectedSubtitle", "onSubtitleChange") + ' Custom Caption Function + m.top.observeField("allowCaptions", "onAllowCaptionsChange") + m.playbackTimer.observeField("fire", "ReportPlayback") m.bufferPercentage = 0 ' Track whether content is being loaded m.playReported = false @@ -51,6 +54,51 @@ sub init() m.top.trickPlayBar.filledBarBlendColor = m.global.constants.colors.blue end sub +' Only setup captain items if captions are allowed +sub onAllowCaptionsChange() + if not m.top.allowCaptions then return + + m.captionGroup = m.top.findNode("captionGroup") + m.captionGroup.createchildren(9, "LayoutGroup") + m.captionTask = createObject("roSGNode", "captionTask") + m.captionTask.observeField("currentCaption", "updateCaption") + m.captionTask.observeField("useThis", "checkCaptionMode") + m.top.observeField("subtitleTrack", "loadCaption") + m.top.observeField("globalCaptionMode", "toggleCaption") + + if m.global.session.user.settings["playback.subs.custom"] + m.top.suppressCaptions = true + toggleCaption() + else + m.top.suppressCaptions = false + end if +end sub + +' Set caption url to server subtitle track +sub loadCaption() + if m.top.suppressCaptions + m.captionTask.url = m.top.subtitleTrack + end if +end sub + +' Toggles visibility of custom subtitles and sets captionTask's player state +sub toggleCaption() + m.captionTask.playerState = m.top.state + m.top.globalCaptionMode + if LCase(m.top.globalCaptionMode) = "on" + m.captionTask.playerState = m.top.state + m.top.globalCaptionMode + "w" + m.captionGroup.visible = true + else + m.captionGroup.visible = false + end if +end sub + +' Removes old subtitle lines and adds new subtitle lines +sub updateCaption() + m.captionGroup.removeChildrenIndex(m.captionGroup.getChildCount(), 0) + m.captionGroup.appendChildren(m.captionTask.currentCaption) +end sub + +' Event handler for when selectedSubtitle changes sub onSubtitleChange() ' Save the current video position m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&) @@ -114,7 +162,11 @@ sub onVideoContentLoaded() m.top.transcodeParams = videoContent[0].transcodeparams if m.LoadMetaDataTask.isIntro + ' Disable trackplay bar for intro videos m.top.enableTrickPlay = false + else + ' Allow custom captions for non intro videos + m.top.allowCaptions = true end if if isValid(m.top.audioIndex) @@ -132,21 +184,12 @@ sub onContentChange() if not isValid(m.top.content) then return m.top.observeField("position", "onPositionChanged") - - ' If video content type is not episode, remove position observer - if m.top.content.contenttype <> 4 - m.top.unobserveField("position") - end if end sub sub onNextEpisodeDataLoaded() m.checkedForNextEpisode = true m.top.observeField("position", "onPositionChanged") - - if m.getNextEpisodeTask.nextEpisodeData.Items.count() <> 2 - m.top.unobserveField("position") - end if end sub ' @@ -189,16 +232,27 @@ end sub ' When Video Player state changes sub onPositionChanged() + if isValid(m.captionTask) + m.captionTask.currentPos = Int(m.top.position * 1000) + end if + ' Check if dialog is open m.dialog = m.top.getScene().findNode("dialogBackground") if not isValid(m.dialog) - checkTimeToDisplayNextEpisode() + ' Do not show Next Episode button for intro videos + if not m.LoadMetaDataTask.isIntro + checkTimeToDisplayNextEpisode() + end if end if end sub ' ' When Video Player state changes sub onState(msg) + if isValid(m.captionTask) + m.captionTask.playerState = m.top.state + m.top.globalCaptionMode + end if + ' When buffering, start timer to monitor buffering process if m.top.state = "buffering" and m.bufferCheckTimer <> invalid @@ -319,11 +373,17 @@ function onKeyEvent(key as string, press as boolean) as boolean if not press then return false if key = "down" - m.top.selectSubtitlePressed = true - return true + ' Do not show subtitle selection for intro videos + if not m.LoadMetaDataTask.isIntro + m.top.selectSubtitlePressed = true + return true + end if else if key = "up" - m.top.selectPlaybackInfoPressed = true - return true + ' Do not show playback info for intro videos + if not m.LoadMetaDataTask.isIntro + m.top.selectPlaybackInfoPressed = true + return true + end if else if key = "OK" ' OK will play/pause depending on current state ' return false to allow selection during seeking diff --git a/components/video/VideoPlayerView.xml b/components/video/VideoPlayerView.xml index df5230ba9..609cfc11a 100644 --- a/components/video/VideoPlayerView.xml +++ b/components/video/VideoPlayerView.xml @@ -7,7 +7,6 @@ - @@ -24,9 +23,11 @@ + + diff --git a/locale/de_DE/translations.ts b/locale/de_DE/translations.ts index ad7ce59f6..04df13cfd 100644 --- a/locale/de_DE/translations.ts +++ b/locale/de_DE/translations.ts @@ -11439,5 +11439,100 @@ Aktivieren oder Deaktivieren von Direct Play für optionale Codecs Settings Menu - Title for settings group related to codec support + + Error During Playback + Fehler während der Wiedergabe + Dialog title when error occurs during playback + + + Sign Out + Abmelden + + + There was an error retrieving the data for this item from the server. + Fehler beim Laden dieses Eintrags vom Server. + Dialog detail when unable to load Content from Server + + + Loading Channel Data + Lade Kanaldaten + + + Name or Title field of media item + TITLE + Name + + + Error loading Channel Data + Fehler beim Laden der Kanaldaten + + + On Now + Jetzt läuft + + + An error was encountered while playing this item. + Bei der Wiedergabe dieses Eintrags ist ein Fehler aufgetreten. + Dialog detail when error occurs during playback + + + Unable to load Channel Data from the server + Kanaldaten konnten nicht vom Server geladen werden + + + Delete Saved + Gespeicherte löschen + + + Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc) + NO_ITEMS + %1 enthält keine Einträge + + + Save Credentials? + Anmeldedaten speichern? + + + Change Server + Server wechseln + + + CRITIC_RATING + Kritikerbewertung + + + IMDB_RATING + IMDb-Bewertung + + + DATE_PLAYED + Abspieldatum + + + DATE_ADDED + Hinzugefügt am + + + Error Retrieving Content + Fehler beim Empfangen des Inhalts + Dialog title when unable to load Content from Server + + + Sign Out + Abmelden + + + On Now + Jetzt läuft + + + Change Server + Server wechseln + + + Error Retrieving Content + Fehler beim Empfangen des Inhalts + Dialog title when unable to load Content from Server + diff --git a/locale/en_US/translations.ts b/locale/en_US/translations.ts index e4a3d6b0b..2ea904966 100644 --- a/locale/en_US/translations.ts +++ b/locale/en_US/translations.ts @@ -1208,5 +1208,25 @@ Disable the HEVC codec on this device. This may improve playback for some devices (ultra). User Setting - Setting description + + Global + Global + User Setting - Setting title + + + Global settings that affect everyone that uses this Roku device. + Global settings that affect everyone that uses this Roku device. + User Setting - Setting description + + + Remember Me? + Remember Me? + User Setting - Setting title + + + Remember the currently logged in user and try to log them in again next time you start the Jellyfin app. + Remember the currently logged in user and try to log them in again next time you start the Jellyfin app. + User Setting - Setting description + \ No newline at end of file diff --git a/locale/es_ES/translations.ts b/locale/es_ES/translations.ts index be1c7d257..47ed8b0d5 100644 --- a/locale/es_ES/translations.ts +++ b/locale/es_ES/translations.ts @@ -4418,5 +4418,1200 @@ Loading Channel Data Cargando datos de canales + + The requested content does not exist on the server + El contenido solicitado no existe en el servidor + Content of message box when the requested content is not found on the server + + + An error was encountered while playing this item. Server did not provide required transcoding data. + Un error ha ocurrido reproduciendo este elemento. El servidor no entrego los datos de transcodificación requeridos + Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url + + + Title of Tab for options to filter library content + TAB_FILTER + Filtro + + + TV Shows + Programas de TV + + + OFFICIAL_RATING + Calificación parental + + + today + hoy + Current day + + + Name or Title field of media item + TITLE + Nombre + + + Enabled + Habilitado + + + An error was encountered while playing this item. + Ha ocurrido un error reproduciendo este elemento. + Dialog detail when error occurs during playback + + + Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc) + NO_ITEMS + Este %1 no contiene elementos + + + Thursday + Jueves + Day of Week + + + Started at + Empezó a las + (Past Tense) For defining time when a program started today (e.g. Started at 08:00) + + + Live + En vivo + If TV Show is being broadcast live (not pre-recorded) + + + Repeat + Repetir + If TV Shows has previously been broadcasted + + + Unknown + Desconocido + Title for a cast member for which we have no information for + + + Friday + Viernes + Day of Week + + + Close + Cerrar + + + Tuesday + Martes + Day of Week + + + Saturday + Sábado + Day of Week + + + DATE_PLAYED + Fecha reproducido + + + yesterday + ayer + Previous day + + + tomorrow + mañana + Next day + + + Sunday + Domingo + Day of Week + + + Starts + Empieza + (Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) + + + Ended at + Terminó a las + (Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) + + + Channels + Canales + Menu option for showing Live TV Channel List + + + TV Guide + Guía TV + Menu option for showing Live TV Guide / Schedule + + + Cancel Recording + Cancelar grabación + + + IMDB_RATING + Calificación IMDb + + + DATE_ADDED + Fecha añadido + + + Age + Edad + + + More Like This + Más como este + + + Blur Unwatched Episodes + Desenfocar Episodios sin ver + Option Title in user setting screen + + + Hide Clock + Ocultar reloj + Option Title in user setting screen + + + Next episode + Siguiente episodio + + + Record Series + Grabar serie + + + Cancel Series Recording + Cancelar grabación de serie + + + Item Count + Contador de elementos + UI -> Media Grid -> Item Count in user setting screen. + + + Show item count in the library and index of selected item. + Mostrar contador de elementos en la librería y el indice del elemento seleccionado + Description for option in Setting Screen + + + Go to series + Ir a series + Continue Watching Popup Menu - Navigate to the Series Detail Page + + + Go to season + Ir a temporada + Continue Watching Popup Menu - Navigate to the Season Page + + + Go to episode + Ir a episodio + Continue Watching Popup Menu - Navigate to the Episode Detail Page + + + Search now + Buscar ahora + Help text in search Box + + + %1 of %2 + %1 de %2 + Item position and count. %1 = current item. %2 = total number of items + + + (Dialog will close automatically) + (Dialogo se cerrará automáticamente) + + + Go directly to the episode list if a TV series has only one season. + Ir directamente a lista de episodios si la serie de TV tiene solo una temporada. + Settings Menu - Description for option + + + Blur images of unwatched episodes. + Desenfocar imágenes de episodios sin ver. + + + Ends at + Terminó + (Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) + + + View Channel + Ver canal + + + Record + Grabar + + + Version + Versión + + + User Interface + Interfaz de usuario + Title for User Interface section in user setting screen. + + + Play Trailer + Reproducir adelanto + + + Codec Support + Soporte de codificador + Settings Menu - Title for settings group related to codec support + + + MPEG-4 + MPEG-4 + Name of codec used in settings menu + + + AV1 + AV1 + Name of a setting - should we try to direct play experimental av1 codec + + + Return to Top + Volver arriba + UI -> Media Grid -> Item Title in user setting screen. + + + Cinema Mode + Modo cine + Settings Menu - Title for option + + + H.264 + H.264 + Name of codec used in settings menu + + + HEVC + HEVC + Name of codec used in settings menu + + + Here is your Quick Connect code: + Este es tu código de conexión rápida: + + + There was an error authenticating via Quick Connect. + Hubo un error autenticando por medio de Conexión rápida. + + + Networks + Redes + + + Studios + Estudios + + + You can search for Titles, People, Live TV Channels and more + Puedes buscar por Títulos, Personas, Canales en vivo y más + Help text in search results + + + ...or enter server URL manually: + Si no hay servidores en la lista anterior, también puedes ingresar la URL manualmente + Instructions on initial app launch when the user is asked to manually enter a server URL + + + Disabled + Deshabilitado + + + On Now + Transmitiendo Ahora + + + Error loading Channel Data + Error cargando datos del canal + + + PLAY_COUNT + Contador de reproducciones + + + RELEASE_DATE + Fecha de estreno + + + Title of Tab for options to sort library content + TAB_SORT + Ordenar + + + Movies + Películas + + + Unable to load Channel Data from the server + Imposible cargar datos del servidor + + + Monday + Lunes + Day of Week + + + Wednesday + Miércoles + Day of Week + + + Started + Empezó + (Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) + + + Starts at + Empieza a las + (Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) + + + Connecting to Server + Conectando al servidor + Message to display to user while client is attempting to connect to the server + + + Not found + No encontrado + Title of message box when the requested content is not found on the server + + + Enter the server name or IP address + Ingresa nombre o dirección IP del servidor + Title of KeyboardDialog when manually entering a server URL + + + Pick a Jellyfin server from the local network + Selecciona un servidor Jellyfin disponible en tu red local: + Instructions on initial app launch when the user is asked to pick a server from a list + + + Enable or disable Direct Play for optional codecs + Habilita o deshabilita reproducción directa para codificadores opcionales + Settings Menu - Title for settings group related to codec support + + + MPEG-2 + MPEG-2 + Name of codec used in settings menu + + + Quick Connect + Conexión rápida + + + An error was encountered while playing this item. + Ha ocurrido un error reproduciendo este elemento. + Dialog detail when error occurs during playback + + + Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc) + NO_ITEMS + Este %1 no contiene elementos + + + CRITIC_RATING + Puntuación de los Críticos + + + OFFICIAL_RATING + Calificación Parental + + + RUNTIME + Duración + + + Monday + Lunes + Day of Week + + + Thursday + Jueves + Day of Week + + + Starts at + Empieza a las + (Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) + + + Ended at + Terminó a las + (Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) + + + Repeat + Repetir + If TV Shows has previously been broadcasted + + + Connecting to Server + Conectando al Servidor + Message to display to user while client is attempting to connect to the server + + + The requested content does not exist on the server + El contenido solicitado no existe en el servidor + Content of message box when the requested content is not found on the server + + + Pick a Jellyfin server from the local network + Selecciona un servidor Jellyfin disponible en tu red local: + Instructions on initial app launch when the user is asked to pick a server from a list + + + An error was encountered while playing this item. Server did not provide required transcoding data. + Un error ha ocurrido reproduciendo este elemento. El servidor no entregó los datos de transcodificación requeridos. + Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url + + + Title of Tab for options to filter library content + TAB_FILTER + Filtrar + + + TV Shows + Programas de TV + + + today + hoy + Current day + + + Name or Title field of media item + TITLE + Nombre + + + Wednesday + Miércoles + Day of Week + + + Born + Nacido en + + + Additional Parts + Partes Adicionales + Additional parts of a video + + + Movies + Películas + + + Started at + Empezó a las + (Past Tense) For defining time when a program started today (e.g. Started at 08:00) + + + Started + Empezó + (Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) + + + Live + En vivo + If TV Show is being broadcast live (not pre-recorded) + + + Unknown + Desconocido + Title for a cast member for which we have no information for + + + MPEG-2 + MPEG-2 + Name of codec used in settings menu + + + Friday + Viernes + Day of Week + + + Close + Cerrar + + + Tuesday + Martes + Day of Week + + + Saturday + Sábado + Day of Week + + + DATE_PLAYED + Reproducido en Fecha + + + Died + Murió en + + + Press 'OK' to Close + Presiona 'OK' para Cerrar + + + Special Features + Características Especiales + + + yesterday + ayer + Previous day + + + tomorrow + mañana + Next day + + + Sunday + Domingo + Day of Week + + + Starts + Empieza + (Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) + + + Channels + Canales + Menu option for showing Live TV Channel List + + + TV Guide + Guía de TV + Menu option for showing Live TV Guide / Schedule + + + Cancel Recording + Cancelar Grabación + + + Title of Tab for switching "views" when looking at a library + TAB_VIEW + Ver + + + IMDB_RATING + Calificación IMDb + + + DATE_ADDED + Añadido en Fecha + + + Age + Edad + + + Cast & Crew + Elenco y Equipo + + + More Like This + Más como este + + + Save Credentials? + ¿Guardar Credenciales? + + + Movies (Grid) + Películas (Cuadrícula) + Movie library view option + + + Record Series + Grabar Serie + + + Cancel Series Recording + Cancelar Grabación de Serie + + + Ends at + Terminó + (Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) + + + View Channel + Ver Canal + + + Record + Grabar + + + Version + Versión + + + Playback + Reproducción + Title for Playback section in user setting screen. + + + Codec Support + Soporte de Codificador + Settings Menu - Title for settings group related to codec support + + + MPEG-4 + MPEG-4 + Name of codec used in settings menu + + + ...or enter server URL manually: + Si no hay servidores en la lista anterior, también puedes ingresar la URL manualmente: + Instructions on initial app launch when the user is asked to manually enter a server URL + + + Change Server + Cambiar de Servidor + + + On Now + Transmitiendo Ahora + + + Error Retrieving Content + Error Al Recuperar Contenido + Dialog title when unable to load Content from Server + + + Error During Playback + Error Al Reproducir + Dialog title when error occurs during playback + + + There was an error retrieving the data for this item from the server. + Ha ocurrido un error al obtener este elemento desde el servidor. + Dialog detail when unable to load Content from Server + + + Sign Out + Cerrar Sesión + + + Delete Saved + Borrar Guardados + + + Loading Channel Data + Cargando Datos del Canal + + + Error loading Channel Data + Error al Cargar Datos del Canal + + + PLAY_COUNT + Veces Reproducido + + + RELEASE_DATE + Fecha de Estreno + + + Title of Tab for options to sort library content + TAB_SORT + Ordenar + + + Unable to load Channel Data from the server + Imposible cargar Datos del Canal desde el servidor + + + Movies (Presentation) + Películas (Modo Presentación) + Movie library view option + + + Not found + No Encontrado + Title of message box when the requested content is not found on the server + + + Enter the server name or IP address + Ingresa nombre o dirección IP del servidor + Title of KeyboardDialog when manually entering a server URL + + + Error Getting Playback Information + Error al Obtener la Información de Reproducción + Dialog Title: Received error from server when trying to get information about the selected item for playback + + + Enable or disable Direct Play for optional codecs + Habilita o deshabilita la Reproducción Directa para codificadores opcionales + Settings Menu - Title for settings group related to codec support + + + Media Grid + Cuadrícula de multimedia + UI -> Media Grid section in user setting screen. + + + Show item count in the library and index of selected item. + Mostrar cantidad de elementos en la biblioteca y el índice del elemento seleccionado. + Description for option in Setting Screen + + + Hide Taglines + Ocultar Eslogan + Option Title in user setting screen + + + Blur images of unwatched episodes. + Desenfocar imágenes de episodios sin ver. + + + Custom Subtitles + Subtitulos Personalizados + Name of a setting - custom subtitles that support CJK fonts + + + Enabled + Habilitado + + + Codec + Códec + + + Quick Connect + Conexión Rápida + + + Design Elements + Elementos de Diseño + + + Blur Unwatched Episodes + Desenfocar Episodios No Vistos + Option Title in user setting screen + + + Skip Details for Single Seasons + Saltar Detalles para Temporadas Únicas + Settings Menu - Title for option + + + Hide Clock + Ocultar Reloj + Option Title in user setting screen + + + Next episode + Siguiente episodio + + + Item Count + Cantidad de Elementos + UI -> Media Grid -> Item Count in user setting screen. + + + Set Favorite + Agregar a Favoritos + Button Text - When pressed, sets item as Favorite + + + Set Watched + Marcar como Visto + Button Text - When pressed, marks item as Warched + + + Go to series + Ir a series + Continue Watching Popup Menu - Navigate to the Series Detail Page + + + Go to season + Ir a temporada + Continue Watching Popup Menu - Navigate to the Season Page + + + Go to episode + Ir a episodio + Continue Watching Popup Menu - Navigate to the Episode Detail Page + + + Search now + Buscar ahora + Help text in search Box + + + %1 of %2 + %1 de %2 + Item position and count. %1 = current item. %2 = total number of items + + + (Dialog will close automatically) + (El Diálogo se cerrará automáticamente) + + + Hides tagline text on details pages. + Ocultar texto de eslogan en páginas de detalles. + + + Options for TV Shows. + Opciones para Series de TV. + Description for TV Shows user settings. + + + Go directly to the episode list if a TV series has only one season. + Ir directamente a lista de episodios si la serie de TV tiene solo una temporada. + Settings Menu - Description for option + + + Audio Codec + Códec de Audio + + + Video range type + Tipo de rango de video + + + Video Codec + Códec de Video + + + Playback Information + Información de Reproducción + + + Total Bitrate + Tasa de Bits Total + + + Size + Tamaño + Video size + + + Bit Rate + Tasa de Bits + Video streaming bit rate + + + Pixel format + Formato de pixeles + Video pixel format + + + Unable to find any albums or songs belonging to this artist + No se pudieron encontrar álbumes o canciones relacionadas a este artista + Popup message when we find no audio data for an artist + + + WxH + WxH + Video width x height + + + Text Subtitles Only + Subtítulos de Texto Únicamente + Name of a setting - should we hide subtitles that might transcode + + + User Interface + Interfaz de usuario + Title for User Interface section in user setting screen. + + + Media Grid options. + Opciones de la Cuadrícula de Multimedia. + + + Play Trailer + Reproducir Trailer + + + direct + directo + + + Aired + Publicado + Aired date label + + + AV1 + AV1 + Name of a setting - should we try to direct play experimental av1 codec + + + Return to Top + Volver Arriba + UI -> Media Grid -> Item Title in user setting screen. + + + Options that alter the design of Jellyfin. + Opciones que alteran el diseño de Jellyfin. + Description for Design Elements user settings. + + + Use Splashscreen as Home Background + Usar Pantalla de Bienvenida como Fondo de Inicio + Option Title in user setting screen + + + Cinema Mode + Modo Cine + Settings Menu - Title for option + + + H.264 + H.264 + Name of codec used in settings menu + + + HEVC + HEVC + Name of codec used in settings menu + + + Settings relating to how the application looks. + Configuración relacionada a como se ve la aplicación. + + + Reason + Razón + + + Transcoding Information + Información de Transcodificación + + + Codec Tag + Tag del Códec + + + Level + Nivel + Video profile level + + + Audio Channels + Canales de Audio + + + Stream Information + Información de la Transmisión + + + Container + Contenedor + Video streaming container + + + Only display text subtitles to minimize transcoding. + Solo mostrar subtítulos de texto para minimizar la transcodificación. + Description of a setting - should we hide subtitles that might transcode + + + all + todo + all will reset the searchTerm so all data will be availible + + + Here is your Quick Connect code: + Este es tu código de Conexión Rápida: + + + There was an error authenticating via Quick Connect. + Hubo un error autenticando por medio de Conexión Rápida. + + + Networks + Redes + + + Studios + Estudios + + + Shows + Series + + + You can search for Titles, People, Live TV Channels and more + Puedes buscar por Títulos, Personas, Canales en vivo y más + Help text in search results + + + Disabled + Deshabilitado + + + ** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it. + **EXPERIMENTAL** Soporte de Reproducción Directa para contenido AV1 si este dispositivo Roku es compatible. + Description of a setting - should we try to direct play experimental av1 codec + + + Use voice remote to search + Utilizar la búsqueda por voz + Help text in search voice text box + + + Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth. + Soporte de reproducción directa para contenido MPEG-2 (ej., televisión en vivo). Esto previene la transcodificación de contenido MPEG-2, pero a mayor uso de ancho de banda. + Settings Menu - Description for option + + + Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files. + Soporte de reproducción directa para contenido MPEG-4. Esto podría requerir ser deshabilitado para poder reproducir los archivos de video con encodificación DIVX. + Settings Menu - Description for option + + + Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately). + Use el botón de reproducción para animar lentamente al primer elemento de la carpeta. (Si está deshabilitado, la carpeta se restablecerá al primer elemento inmediatamente). + Description for option in Setting Screen + + + Monday + Lunes + Day of Week + + + Tuesday + Martes + Day of Week + + + Thursday + Jueves + Day of Week + + + Sunday + Domingo + Day of Week + + + Press 'OK' to Close + Presiona 'OK' para Cerrar + + + today + hoy + Current day + + + yesterday + ayer + Previous day + + + Friday + Viernes + Day of Week + + + TV Guide + Guía de TV + Menu option for showing Live TV Guide / Schedule + + + Wednesday + Miércoles + Day of Week + + + Unknown + Desconocido + Title for a cast member for which we have no information for + + + Saturday + Sábado + Day of Week + + + tomorrow + mañana + Next day + + + Channels + Canales + Menu option for showing Live TV Channel List + + + IMDB_RATING + Puntuación IMDb + + + Record + Grabar + + + Error Retrieving Content + Error Obteniendo Contenido + Dialog title when unable to load Content from Server + + + An error was encountered while playing this item. + Se produjo un error durante la reproducción de este elemento. + Dialog detail when error occurs during playback + + + Save Credentials? + ¿Guardar Credenciales? + + + Change Server + Cambiar Servidor + + + On Now + Ahora + + + Error During Playback + Error Durante la Reproducción + Dialog title when error occurs during playback + + + There was an error retrieving the data for this item from the server. + Se produjo un error al recuperar información para este elemento desde el servidor. + Dialog detail when unable to load Content from Server + + + Sign Out + Cerrar Sesión + + + Delete Saved + Eliminar Guardado + + + Loading Channel Data + Cargando información del canal + diff --git a/locale/fr/translations.ts b/locale/fr/translations.ts index 793d0b36b..243e10f51 100644 --- a/locale/fr/translations.ts +++ b/locale/fr/translations.ts @@ -26,7 +26,7 @@ Favorite - Favoris + Favori Loading... @@ -50,7 +50,7 @@ Please sign in - Se connecter + Connectez-vous s'il vous plaît Search @@ -118,7 +118,7 @@ Enter a password - Entrer le mot de passe + Entrez le mot de passe Enter a value... @@ -142,7 +142,7 @@ Sort Order - Trie par ordre + Ordre de tri Descending @@ -166,7 +166,7 @@ Director - Direction + Réalisateur Video @@ -174,7 +174,7 @@ Audio - Musiques + Audio Server @@ -7426,5 +7426,455 @@ Nombre d'éléments UI -> Media Grid -> Item Count in user setting screen. + + Error Retrieving Content + Erreur lors de la récupération du contenu + Dialog title when unable to load Content from Server + + + There was an error retrieving the data for this item from the server. + Une erreur s'est produite lors de la récupération des données de cet élément sur le serveur. + Dialog detail when unable to load Content from Server + + + Error loading Channel Data + Erreur lors du chargement des données de la chaîne + + + Unable to load Channel Data from the server + Impossible de charger les données de la chaîne depuis le serveur + + + Press 'OK' to Close + Appuyez sur «OK» pour fermer + + + Monday + Lundi + Day of Week + + + Starts at + Commence à + (Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) + + + Cancel Recording + Annuler l'enregistrement + + + Connecting to Server + Connexion au serveur en cours + Message to display to user while client is attempting to connect to the server + + + Enter the server name or IP address + Entrez le nom du serveur ou son adresse IP + Title of KeyboardDialog when manually entering a server URL + + + Pick a Jellyfin server from the local network + Sélectionnez un serveur Jellyfin disponible sur votre réseau local : + Instructions on initial app launch when the user is asked to pick a server from a list + + + An error was encountered while playing this item. Server did not provide required transcoding data. + Une erreur a été rencontrée lors de la lecture de cet élément. Le serveur n'a pas communiqué les informations nécessaires au transcodage. + Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url + + + Enable or disable Direct Play for optional codecs + Activer ou désactiver DirectPlay pour les codecs optionnels + Settings Menu - Title for settings group related to codec support + + + Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth. + Utiliser DirectPlay pour les contenus en MPEG-2 (par ex. TV en direct). Cela évitera la conversion des contenus en MPEG-2 mais utilisera beaucoup plus de bande passante. + Settings Menu - Description for option + + + Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files. + Utiliser DirectPlay pour les contenus en MPEG-4. Désactiver ce paramètres pourrait être nécessaire pour la lecture de fichiers vidéo encodés en DivX. + Settings Menu - Description for option + + + ** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it. + ** EXPERIMENTAL** Utiliser DirectPlay pour les contenus en AV1 si cet équipement Roku le supporte. + Description of a setting - should we try to direct play experimental av1 codec + + + HEVC + HEVC + Name of codec used in settings menu + + + H.264 + H.264 + Name of codec used in settings menu + + + PLAY_COUNT + Nombre de vues + + + Saturday + Samedi + Day of Week + + + Record Series + Enregistrer la série + + + ...or enter server URL manually: + Si aucun serveur n'est dans la liste ci-dessus, vous pouvez aussi saisir l'URL du serveur manuellement : + Instructions on initial app launch when the user is asked to manually enter a server URL + + + Disabled + Désactivé + + + Close + Fermer + + + Channels + Chaines + Menu option for showing Live TV Channel List + + + An error was encountered while playing this item. + Une erreur s'est produite lors de la lecture de cet élément. + Dialog detail when error occurs during playback + + + Error During Playback + Erreur lors de la lecture + Dialog title when error occurs during playback + + + Save Credentials? + Enregistrer les identifiants ? + + + Delete Saved + Suppression enregistrée + + + On Now + En ce moment + + + TV Shows + Séries TV + + + Friday + Vendredi + Day of Week + + + Cast & Crew + Distribution + + + Title of Tab for options to filter library content + TAB_FILTER + Filtre + + + yesterday + hier + Previous day + + + Additional Parts + Contenu additionnel + Additional parts of a video + + + Movies + Films + + + Ends at + Fini + (Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) + + + View Channel + Voir la chaîne + + + Died + Décès + + + TV Guide + Guide des programmes + Menu option for showing Live TV Guide / Schedule + + + Movies (Grid) + Films (Grille) + Movie library view option + + + Codec Support + Codecs supportés + Settings Menu - Title for settings group related to codec support + + + User Interface + Interface utilisateur + Title for User Interface section in user setting screen. + + + Set Favorite + Marquer comme favori + Button Text - When pressed, sets item as Favorite + + + Go to series + Aller à la série + Continue Watching Popup Menu - Navigate to the Series Detail Page + + + CRITIC_RATING + Notation des critiques + + + OFFICIAL_RATING + Classification parentale + + + Born + Date de naissance + + + Title of Tab for options to sort library content + TAB_SORT + Trier + + + Movies (Presentation) + Films (Présentation) + Movie library view option + + + Started at + A commencé à + (Past Tense) For defining time when a program started today (e.g. Started at 08:00) + + + Started + A commencé + (Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) + + + Repeat + Rediffusion + If TV Shows has previously been broadcasted + + + Cancel Series Recording + Annuler l'enregistrement de la série + + + Media Grid + Grille de médias + UI -> Media Grid section in user setting screen. + + + Set Watched + Marquer comme vu + Button Text - When pressed, marks item as Warched + + + Special Features + Caractéristiques spécifiques + + + Sunday + Dimanche + Day of Week + + + Tuesday + Mardi + Day of Week + + + Wednesday + Mercredi + Day of Week + + + Thursday + Jeudi + Day of Week + + + Starts + Commence + (Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) + + + Record + Enregistrer + + + Not found + Introuvable + Title of message box when the requested content is not found on the server + + + The requested content does not exist on the server + Le contenu demandé n'existe pas sur le serveur + Content of message box when the requested content is not found on the server + + + Error Getting Playback Information + Erreur lors de la récupération des informations de lecture + Dialog Title: Received error from server when trying to get information about the selected item for playback + + + Go to season + Aller à la saison + Continue Watching Popup Menu - Navigate to the Season Page + + + Search now + Chercher + Help text in search Box + + + Studios + Studios + + + MPEG-2 + MPEG-2 + Name of codec used in settings menu + + + MPEG-4 + MPEG-4 + Name of codec used in settings menu + + + AV1 + AV1 + Name of a setting - should we try to direct play experimental av1 codec + + + Networks + Réseaux + + + Version + Version + + + Use voice remote to search + Utilisez la télécommande vocale pour chercher + Help text in search voice text box + + + Loading Channel Data + Chargement des données de la chaîne + + + Name or Title field of media item + TITLE + Titre + + + IMDB_RATING + Notation IMDb + + + DATE_ADDED + Date d'ajout + + + DATE_PLAYED + Date de lecture + + + RUNTIME + Durée + + + Title of Tab for switching "views" when looking at a library + TAB_VIEW + Vue + + + More Like This + Contenu similaire + + + today + aujourd'hui + Current day + + + Ended at + A fini à + (Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) + + + Live + En direct + If TV Show is being broadcast live (not pre-recorded) + + + RELEASE_DATE + Date de sortie + + + Unknown + Inconnu + Title for a cast member for which we have no information for + + + Playback + Lecture + Title for Playback section in user setting screen. + + + Enabled + Activé + + + Age + Age + + + tomorrow + demain + Next day + + + Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc) + NO_ITEMS + Ce(tte) %1 ne contient pas d'éléments + + + Item Count + Nombre d'éléments + UI -> Media Grid -> Item Count in user setting screen. + + + Go to episode + Aller à l'épisode + Continue Watching Popup Menu - Navigate to the Episode Detail Page + + + Shows + Séries + diff --git a/locale/hu/translations.ts b/locale/hu/translations.ts index 47529ceaa..c2ad7fc3e 100644 --- a/locale/hu/translations.ts +++ b/locale/hu/translations.ts @@ -11268,5 +11268,10 @@ Direct playing Közvetlen lejátszás + + Name or Title field of media item + TITLE + Név + diff --git a/package-lock.json b/package-lock.json index 3f0ef5340..1463d15c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,25 +12,121 @@ "dependencies": { "@rokucommunity/bslib": "0.1.1", "bgv": "npm:button-group-vert@1.0.2", - "brighterscript-formatter": "1.6.32", + "brighterscript-formatter": "1.6.33", "intKeyboard": "npm:integer-keyboard@1.0.12", "log": "npm:roku-log@0.11.1", "sob": "npm:slide-out-button@1.0.1" }, "devDependencies": { "@rokucommunity/bslint": "0.8.10", - "brighterscript": "0.65.5", + "brighterscript": "0.65.7", "jshint": "2.13.6", - "markdownlint-cli2": "0.9.2", - "rimraf": "5.0.1", + "markdownlint-cli2": "0.10.0", + "rimraf": "5.0.5", "roku-deploy": "3.10.3", "roku-log-bsc-plugin": "0.8.1", "rooibos-roku": "5.7.0", - "ropm": "0.10.16", + "ropm": "0.10.17", "spellchecker-cli": "6.1.1", "undent": "0.1.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -533,9 +629,9 @@ } }, "node_modules/brighterscript": { - "version": "0.65.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.5.tgz", - "integrity": "sha512-xDkWIZhjTLhp6dVZ6lX7zWRyNvjdiAwZncJRnErSbqRhteNJFL7ic2UDJew9zCOYTQDrG7B85lpPpXc/1JlV+Q==", + "version": "0.65.7", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.7.tgz", + "integrity": "sha512-cmV0JzlYuKIKkMsbHR2FyaD2YugHeHESdMmUOtK61EsEUS895Y6yXftDVnFUwij7beLiZR6s1oRpMfiS7MJ40g==", "dependencies": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -575,11 +671,11 @@ } }, "node_modules/brighterscript-formatter": { - "version": "1.6.32", - "resolved": "https://registry.npmjs.org/brighterscript-formatter/-/brighterscript-formatter-1.6.32.tgz", - "integrity": "sha512-7rbNmSsj2v8iv+iSWXczSg4hC7L1zxMEwo+jZcDaMDPu0TBt4zmmCpaT0WvvvYcE8fD9I1tJ4dlTaj61QnH0QQ==", + "version": "1.6.33", + "resolved": "https://registry.npmjs.org/brighterscript-formatter/-/brighterscript-formatter-1.6.33.tgz", + "integrity": "sha512-XmirSAOBBHsprJvLOuh8nzXNiPQJXQa2NJ581OZdQrofFdBNPGH+yFhV6XXUZ+pA6fVLSxMxXA0toF83jtKjrA==", "dependencies": { - "brighterscript": "^0.65.5", + "brighterscript": "^0.65.7", "glob-all": "^3.3.0", "jsonc-parser": "^3.0.0", "source-map": "^0.7.3", @@ -590,16 +686,6 @@ "bsfmt": "dist/cli.js" } }, - "node_modules/brighterscript-formatter/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, "node_modules/brighterscript-formatter/node_modules/glob-all": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.3.1.tgz", @@ -612,6 +698,21 @@ "glob-all": "bin/glob-all" } }, + "node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, "node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -633,6 +734,18 @@ "node": ">=8" } }, + "node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/brighterscript-formatter/node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", @@ -651,23 +764,6 @@ "node": ">=8" } }, - "node_modules/brighterscript-formatter/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/brighterscript-formatter/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/brighterscript/node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -1264,6 +1360,12 @@ "domelementtype": "1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1990,12 +2092,12 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "node_modules/jackspeak": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.0.tgz", - "integrity": "sha512-DiEwVPqsieUzZBNxQ2cxznmFzfg/AMgJUjYw5xl6rSmCxAQXECcbSdwcLM6Ds6T09+SBfSNCGPhYUoQ96P4h7A==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.5.tgz", + "integrity": "sha512-Ratx+B8WeXLAtRJn26hrhY8S1+Jz6pxPMrkrdkgb/NstTNiqMhX0/oFVu5wX+g5n6JlEu2LPsDJmY8nRP4+alw==", "dev": true, "dependencies": { - "cliui": "^7.0.4" + "@isaacs/cliui": "^8.0.2" }, "engines": { "node": ">=14" @@ -2316,9 +2418,9 @@ } }, "node_modules/markdownlint": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.30.0.tgz", - "integrity": "sha512-nInuFvI/rEzanAOArW5490Ez4EYpB5ODqVM0mcDYCPx9DKJWCQqCgejjiCvbSeE7sjbDscVtZmwr665qpF5xGA==", + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.31.1.tgz", + "integrity": "sha512-CKMR2hgcIBrYlIUccDCOvi966PZ0kJExDrUi1R+oF9PvqQmCrTqjOsgIvf2403OmJ+CWomuzDoylr6KbuMyvHA==", "dev": true, "dependencies": { "markdown-it": "13.0.1", @@ -2329,17 +2431,17 @@ } }, "node_modules/markdownlint-cli2": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.9.2.tgz", - "integrity": "sha512-ndijEHIOikcs29W8068exHXlfkFviGFT/mPhREia7zSfQzHvTDkL2s+tWizvELjLHiKRO4KGTkkJyR3oeR8A5g==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.10.0.tgz", + "integrity": "sha512-kVxjPyKFC+eW7iqcxiNI50RDzwugpXkEX5eQlDso/0IUs9M73jXYguLFHDzgi5KatcxU/57Fu8KoGtkFft9lfA==", "dev": true, "dependencies": { "globby": "13.2.2", - "markdownlint": "0.30.0", + "markdownlint": "0.31.1", "markdownlint-cli2-formatter-default": "0.0.4", "micromatch": "4.0.5", "strip-json-comments": "5.0.1", - "yaml": "2.3.1" + "yaml": "2.3.2" }, "bin": { "markdownlint-cli2": "markdownlint-cli2.js", @@ -3482,13 +3584,13 @@ } }, "node_modules/path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "dev": true, "dependencies": { - "lru-cache": "^9.0.0", - "minipass": "^5.0.0" + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4219,15 +4321,15 @@ } }, "node_modules/rimraf": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", - "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dev": true, "dependencies": { - "glob": "^10.2.5" + "glob": "^10.3.7" }, "bin": { - "rimraf": "dist/cjs/src/bin.js" + "rimraf": "dist/esm/bin.mjs" }, "engines": { "node": ">=14" @@ -4246,19 +4348,19 @@ } }, "node_modules/rimraf/node_modules/glob": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.5.tgz", - "integrity": "sha512-Gj+dFYPZ5hc5dazjXzB0iHg2jKWJZYMjITXYPBRQ/xc2Buw7H0BINknRTwURJ6IC6MEFpYbLvtgVb3qD+DwyuA==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.0", - "minipass": "^5.0.0 || ^6.0.2", - "path-scurry": "^1.7.0" + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" }, "bin": { - "glob": "dist/cjs/src/bin.js" + "glob": "dist/esm/bin.mjs" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4268,9 +4370,9 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -4419,14 +4521,14 @@ "dev": true }, "node_modules/ropm": { - "version": "0.10.16", - "resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.16.tgz", - "integrity": "sha512-YZ49ie+dSRkyOz7RbXpku+neujhk6WfrFKUpda844W2kB0Xk/p4XtwirIdorVNtefBqEeQMXswwrVaWLNaL+vQ==", + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.17.tgz", + "integrity": "sha512-JFq/PAzrC3xVweRdTt4zAYCWWdSBzMg5MP9i302L+vi4eXLp4jDYGeYKZwSucZswWNGxVpSK3gW1paHpv7GVEw==", "dev": true, "dependencies": { "@xml-tools/ast": "^5.0.5", "@xml-tools/parser": "1.0.10", - "brighterscript": "^0.65.4", + "brighterscript": "^0.65.5", "del": "6.0.0", "fs-extra": "9.1.0", "glob-all": "3.2.1", @@ -4476,7 +4578,7 @@ "node": ">=10" } }, - "node_modules/ropm/node_modules/fs-extra/node_modules/jsonfile": { + "node_modules/ropm/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", @@ -4488,7 +4590,7 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/ropm/node_modules/fs-extra/node_modules/universalify": { + "node_modules/ropm/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", @@ -4497,45 +4599,6 @@ "node": ">= 10.0.0" } }, - "node_modules/ropm/node_modules/roku-deploy": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.3.tgz", - "integrity": "sha512-COJSQ638QklcM+8AN1nujFuzT04rTZLFuLSww35edm8w/y0l60oF/Iu7TQ46m75DwoGFzGFfomLEmA1ltQk9mA==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "dateformat": "^3.0.3", - "dayjs": "^1.11.0", - "fast-glob": "^3.2.12", - "fs-extra": "^7.0.1", - "is-glob": "^4.0.3", - "jsonc-parser": "^2.3.0", - "jszip": "^3.6.0", - "micromatch": "^4.0.4", - "moment": "^2.29.1", - "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", - "temp-dir": "^2.0.0", - "xml2js": "^0.5.0" - }, - "bin": { - "roku-deploy": "dist/cli.js" - } - }, - "node_modules/ropm/node_modules/roku-deploy/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, "node_modules/ropm/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -4834,6 +4897,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", @@ -4859,6 +4937,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -5452,6 +5543,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5492,9 +5601,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", "dev": true, "engines": { "node": ">= 14" diff --git a/package.json b/package.json index 1412e72b6..781e37854 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,21 @@ "dependencies": { "@rokucommunity/bslib": "0.1.1", "bgv": "npm:button-group-vert@1.0.2", - "brighterscript-formatter": "1.6.32", + "brighterscript-formatter": "1.6.33", "intKeyboard": "npm:integer-keyboard@1.0.12", "log": "npm:roku-log@0.11.1", "sob": "npm:slide-out-button@1.0.1" }, "devDependencies": { "@rokucommunity/bslint": "0.8.10", - "brighterscript": "0.65.5", + "brighterscript": "0.65.7", "jshint": "2.13.6", - "markdownlint-cli2": "0.9.2", - "rimraf": "5.0.1", + "markdownlint-cli2": "0.10.0", + "rimraf": "5.0.5", "roku-deploy": "3.10.3", "roku-log-bsc-plugin": "0.8.1", "rooibos-roku": "5.7.0", - "ropm": "0.10.16", + "ropm": "0.10.17", "spellchecker-cli": "6.1.1", "undent": "0.1.0" }, diff --git a/settings/settings.json b/settings/settings.json index f5240e70f..422174741 100644 --- a/settings/settings.json +++ b/settings/settings.json @@ -1,4 +1,18 @@ [ + { + "title": "Global", + "description": "Global settings that affect everyone that uses this Roku device.", + "children": [ + { + "title": "Remember Me?", + "description": "Remember the currently logged in user and try to log them in again next time you start the Jellyfin app.", + "settingName": "global.rememberme", + "type": "bool", + "default": "false" + } + + ] + }, { "title": "Playback", "description": "Settings relating to playback and supported codec and media types.", diff --git a/source/Main.brs b/source/Main.brs index 298650bd2..8a86ac075 100644 --- a/source/Main.brs +++ b/source/Main.brs @@ -62,7 +62,8 @@ sub Main (args as dynamic) as void end if ' Only show the Whats New popup the first time a user runs a new client version. - if m.global.app.version <> get_setting("LastRunVersion") + appLastRunVersion = get_setting("LastRunVersion") + if m.global.app.version <> appLastRunVersion ' Ensure the user hasn't disabled Whats New popups if m.global.session.user.settings["load.allowwhatsnew"] = true set_setting("LastRunVersion", m.global.app.version) @@ -72,6 +73,34 @@ sub Main (args as dynamic) as void end if end if + ' Registry migrations + if isValid(appLastRunVersion) and not versionChecker(appLastRunVersion, "1.7.0") + ' last app version used less than 1.7.0 + ' no longer saving raw password to registry + ' auth token and username are now stored in user settings and not global settings + print "Running 1.7.0 registry migrations" + ' remove global settings + unset_setting("token") + unset_setting("username") + unset_setting("password") + ' remove user settings + unset_user_setting("password") + ' remove saved credentials from saved_servers + saved = get_setting("saved_servers") + if isValid(saved) + savedServers = ParseJson(saved) + if isValid(savedServers.serverList) and savedServers.serverList.Count() > 0 + newServers = { serverList: [] } + for each item in savedServers.serverList + item.Delete("username") + item.Delete("password") + newServers.serverList.Push(item) + end for + set_setting("saved_servers", FormatJson(newServers)) + end if + end if + end if + ' Handle input messages input = CreateObject("roInput") input.SetMessagePort(m.port) @@ -197,25 +226,16 @@ sub Main (args as dynamic) as void selectedItem.selectedAudioStreamIndex = audio_stream_idx - ' If we are playing a playlist, always start at the beginning - if m.global.queueManager.callFunc("getCount") > 1 - selectedItem.startingPoint = 0 + ' Display playback options dialog + if selectedItem.json.userdata.PlaybackPositionTicks > 0 + m.global.queueManager.callFunc("hold", selectedItem) + playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json) + else m.global.queueManager.callFunc("clear") m.global.queueManager.callFunc("push", selectedItem) m.global.queueManager.callFunc("playQueue") - else - ' Display playback options dialog - if selectedItem.json.userdata.PlaybackPositionTicks > 0 - m.global.queueManager.callFunc("hold", selectedItem) - playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json) - else - m.global.queueManager.callFunc("clear") - m.global.queueManager.callFunc("push", selectedItem) - m.global.queueManager.callFunc("playQueue") - end if end if - else if selectedItemType = "Series" group = CreateSeriesDetailsGroup(selectedItem.json.id) else if selectedItemType = "Season" @@ -520,7 +540,11 @@ sub Main (args as dynamic) as void group.findNode("SearchBox").findNode("search_Key").active = true else if button.id = "change_server" unset_setting("server") - unset_setting("port") + session.server.Delete() + SignOut(false) + sceneManager.callFunc("clearScenes") + goto app_start + else if button.id = "change_user" SignOut(false) sceneManager.callFunc("clearScenes") goto app_start diff --git a/source/ShowScenes.brs b/source/ShowScenes.brs index a07715a4e..48a1853db 100644 --- a/source/ShowScenes.brs +++ b/source/ShowScenes.brs @@ -1,4 +1,4 @@ -function LoginFlow(startOver = false as boolean) +function LoginFlow() 'Collect Jellyfin server and user information start_login: @@ -41,9 +41,11 @@ function LoginFlow(startOver = false as boolean) activeUser = get_setting("active_user") if activeUser = invalid print "No active user found in registry" + user_select: SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting publicUsers = GetPublicUsers() - if publicUsers.count() + numPubUsers = publicUsers.count() + if numPubUsers > 0 publicUsersNodes = [] for each item in publicUsers user = CreateObject("roSGNode", "PublicUserData") @@ -55,18 +57,57 @@ function LoginFlow(startOver = false as boolean) publicUsersNodes.push(user) end for userSelected = CreateUserSelectGroup(publicUsersNodes) + + SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed if userSelected = "backPressed" - SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed - return LoginFlow(true) + session.server.Delete() + unset_setting("server") + goto start_login else + print "A public user was selected with username=" + userSelected + session.user.Update("name", userSelected) + regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "") + session.user.Update("friendlyName", regex.ReplaceAll(userSelected, "")) + ' save userid to session + for each user in publicUsersNodes + if user.name = userSelected + session.user.Update("id", user.id) + exit for + end if + end for + ' try to login with token from registry + myToken = get_user_setting("token") + if myToken <> invalid + ' check if token is valid + print "Auth token found in registry for selected user" + session.user.Update("authToken", myToken) + print "Attempting to use API with auth token" + currentUser = AboutMe() + if currentUser = invalid + print "Auth token is no longer valid - deleting token" + unset_user_setting("token") + unset_user_setting("username") + else + print "Success! Auth token is still valid" + session.user.Login(currentUser) + LoadUserPreferences() + LoadUserAbilities() + return true + end if + else + print "No auth token found in registry for selected user" + end if 'Try to login without password. If the token is valid, we're done + print "Attempting to login with no password" userData = get_token(userSelected, "") if isValid(userData) + print "login success!" session.user.Login(userData) LoadUserPreferences() LoadUserAbilities() - SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed return true + else + print "Auth failed. Password required" end if end if else @@ -75,65 +116,52 @@ function LoginFlow(startOver = false as boolean) passwordEntry = CreateSigninGroup(userSelected) SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed if passwordEntry = "backPressed" - m.global.sceneManager.callFunc("clearScenes") - return LoginFlow(true) + if numPubUsers > 0 + goto user_select + else + session.server.Delete() + unset_setting("server") + goto start_login + end if end if else print "Active user found in registry" session.user.Update("id", activeUser) + myUsername = get_user_setting("username") myAuthToken = get_user_setting("token") - if isValid(myAuthToken) + if isValid(myAuthToken) and isValid(myUsername) print "Auth token found in registry" session.user.Update("authToken", myAuthToken) + session.user.Update("name", myUsername) + regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "") + session.user.Update("friendlyName", regex.ReplaceAll(myUsername, "")) print "Attempting to use API with auth token" currentUser = AboutMe() if currentUser = invalid - print "Auth token is no longer valid - restart login flow" - unset_user_setting("token") - unset_setting("active_user") - session.user.Logout() - goto start_login + print "Auth token is no longer valid" + 'Try to login without password. If the token is valid, we're done + print "Attempting to login with no password" + userData = get_token(userSelected, "") + if isValid(userData) + print "login success!" + session.user.Login(userData) + LoadUserPreferences() + LoadUserAbilities() + return true + else + print "Auth failed. Password required" + print "delete token and restart login flow" + unset_user_setting("token") + unset_user_setting("username") + goto start_login + end if else print "Success! Auth token is still valid" session.user.Login(currentUser) end if else print "No auth token found in registry" - myUsername = get_setting("username") - myPassword = get_setting("password") - userData = invalid - - if isValid(myUsername) and isValid(myPassword) - if myUsername <> "" - print "Username and password found in registry. Attempting to login" - userData = get_token(myUsername, myPassword) - else - print "Username in registry is an empty string" - unset_setting("username") - unset_setting("password") - end if - else if isValid(myUsername) and not isValid(myPassword) - print "Username found in registry but no password" - if myUsername <> "" - print "Attempting to login with no password" - userData = get_token(myUsername, "") - else - print "Username in registry is an empty string" - unset_setting("username") - end if - - else if not isValid(myUsername) and not isValid(myPassword) - print "Neither username nor password found in registry - restart login flow" - unset_setting("active_user") - session.user.Logout() - goto start_login - end if - - if isValid(userData) - print "login success!" - session.user.Login(userData) - end if end if end if @@ -254,11 +282,6 @@ function CreateServerGroup() m.scene.dialog = dialog serverUrl = standardize_jellyfin_url(screen.serverUrl) - 'If this is a different server from what we know, reset username/password setting - if m.global.session.server.url <> serverUrl - set_setting("username", "") - set_setting("password", "") - end if set_setting("server", serverUrl) isConnected = session.server.UpdateURL(serverUrl) @@ -362,25 +385,6 @@ function CreateSigninGroup(user = "") group.findNode("prompt").text = tr("Sign In") - 'Load in any saved server data and see if we can just log them in... - server = m.global.session.server.url - if isValid(server) - server = LCase(server)'Saved server data is always lowercase - end if - saved = get_setting("saved_servers") - if isValid(saved) - savedServers = ParseJson(saved) - for each item in savedServers.serverList - if item.baseUrl = server and isValid(item.username) and isValid(item.password) - userData = get_token(item.username, item.password) - if isValid(userData) - session.user.Login(userData) - return "true" - end if - end if - end for - end if - config = group.findNode("configOptions") username_field = CreateObject("roSGNode", "ConfigData") username_field.label = tr("Username") @@ -447,11 +451,10 @@ function CreateSigninGroup(user = "") activeUser = get_token(username.value, password.value) if isValid(activeUser) session.user.Login(activeUser) - set_setting("username", username.value) - set_setting("password", password.value) + ' save credentials if checkbox.checkedState[0] = true - 'Update our saved server list, so next time the user can just click and go - UpdateSavedServerList() + set_user_setting("token", activeUser.token) + set_user_setting("username", username.value) end if return "true" end if @@ -515,6 +518,7 @@ function CreateHomeGroup() new_options = [] options_buttons = [ { "title": "Search", "id": "goto_search" }, + { "title": "Change user", "id": "change_user" }, { "title": "Change server", "id": "change_server" }, { "title": "Sign out", "id": "sign_out" } ] @@ -862,34 +866,6 @@ function CreatePersonView(personData as object) as dynamic return person end function -sub UpdateSavedServerList() - server = m.global.session.server.url - username = get_setting("username") - password = get_setting("password") - - if server = invalid or username = invalid or password = invalid - return - end if - - server = LCase(server)'Saved server data is always lowercase - - saved = get_setting("saved_servers") - if isValid(saved) - savedServers = ParseJson(saved) - if isValid(savedServers.serverList) and savedServers.serverList.Count() > 0 - newServers = { serverList: [] } - for each item in savedServers.serverList - if item.baseUrl = server - item.username = username - item.password = password - end if - newServers.serverList.Push(item) - end for - set_setting("saved_servers", FormatJson(newServers)) - end if - end if -end sub - 'Opens dialog asking user if they want to resume video or start playback over only on the home screen sub playbackOptionDialog(time as longinteger, meta as object) diff --git a/source/api/baserequest.brs b/source/api/baserequest.brs index fde121ac1..103461339 100644 --- a/source/api/baserequest.brs +++ b/source/api/baserequest.brs @@ -203,12 +203,16 @@ function authRequest(request as object) as object if m.global.session.user.id <> invalid auth = auth + ", UserId=" + QUOTE + m.global.session.user.id + QUOTE - auth = auth + ", DeviceId=" + QUOTE + m.global.device.id + QUOTE - if m.global.session.user.authToken <> invalid - auth = auth + ", Token=" + QUOTE + m.global.session.user.authToken + QUOTE - end if + end if + + if m.global.session.user <> invalid and m.global.session.user.friendlyName <> invalid + auth = auth + ", DeviceId=" + QUOTE + m.global.device.id + m.global.session.user.friendlyName + QUOTE else - auth = auth + ", DeviceId=" + QUOTE + m.global.device.uuid + QUOTE + auth = auth + ", DeviceId=" + QUOTE + m.global.device.id + QUOTE + end if + + if m.global.session.user.authToken <> invalid + auth = auth + ", Token=" + QUOTE + m.global.session.user.authToken + QUOTE end if request.AddHeader("Authorization", auth) diff --git a/source/api/userauth.brs b/source/api/userauth.brs index 657af4931..ce00984b9 100644 --- a/source/api/userauth.brs +++ b/source/api/userauth.brs @@ -32,28 +32,9 @@ function AboutMe(id = "" as string) end function sub SignOut(deleteSavedEntry = true as boolean) - if m.global.session.user.id <> invalid + if m.global.session.user.id <> invalid and deleteSavedEntry = true unset_user_setting("token") - unset_setting("username") - unset_setting("password") - if deleteSavedEntry = true - 'Also delete any credentials in the "saved servers" list - saved = get_setting("saved_servers") - server = m.global.session.server.url - if server <> invalid - server = LCase(server) - savedServers = ParseJson(saved) - newServers = { serverList: [] } - for each item in savedServers.serverList - if item.baseUrl = server - item.username = "" - item.password = "" - end if - newServers.serverList.Push(item) - end for - set_setting("saved_servers", FormatJson(newServers)) - end if - end if + unset_user_setting("username") end if unset_setting("active_user") session.user.Logout() diff --git a/source/utils/deviceCapabilities.brs b/source/utils/deviceCapabilities.brs index 902e9805f..46353fbd3 100644 --- a/source/utils/deviceCapabilities.brs +++ b/source/utils/deviceCapabilities.brs @@ -12,7 +12,7 @@ function getDeviceCapabilities() as object "Photo" ], "SupportedCommands": [], - "SupportsPersistentIdentifier": false, + "SupportsPersistentIdentifier": true, "SupportsMediaControl": false, "SupportsContentUploading": false, "SupportsSync": false, diff --git a/source/utils/session.bs b/source/utils/session.bs index 072eb5d19..72059547c 100644 --- a/source/utils/session.bs +++ b/source/utils/session.bs @@ -156,6 +156,10 @@ namespace session tmpSession.AddReplace("user", userData.json.User) tmpSession.user.AddReplace("authToken", userData.json.AccessToken) end if + ' remove special characters from name + regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "") + friendlyName = regex.ReplaceAll(tmpSession.user.name, "") + tmpSession.user.AddReplace("friendlyName", friendlyName) tmpSession.user.AddReplace("settings", oldUserSettings) ' update global user session @@ -174,9 +178,10 @@ namespace session print "m.global.session.user.settings = ", m.global.session.user.settings end if - ' ensure registry is updated - set_user_setting("username", tmpSession.user.name) - set_user_setting("token", tmpSession.user.authToken) + if m.global.session.user.settings["global.rememberme"] + set_user_setting("token", tmpSession.user.authToken) + set_user_setting("username", tmpSession.user.name) + end if end sub ' Empty the global user session array and reload defaults @@ -254,6 +259,20 @@ namespace session end for end if end for + + ' load globals + session.user.settings.LoadGlobals() + end sub + + ' Grab global vars from registry and overwrite defaults + sub LoadGlobals() + ' search main registry block for all keys that start with "global." + jfRegistry = RegistryReadAll("Jellyfin") + for each item in jfRegistry + if Left(item, 7) = "global." + session.user.settings.Save(item, get_setting(item)) + end if + end for end sub ' Saves the user setting to the global session.