- name: Home columns: - size: small widgets: - type: calendar first-day-of-week: sunday - type: clock # Added Clock widget hour-format: 12h # Assuming 12h based on weather widget preference timezones: - timezone: America/Los_Angeles # San Francisco uses this timezone label: San Francisco - timezone: America/New_York label: New York - timezone: Europe/London label: London - timezone: Europe/Amsterdam label: Amsterdam - timezone: Asia/Kolkata # New Delhi uses this timezone label: New Delhi - timezone: Asia/Tokyo label: Tokyo - type: custom-api title: Astronomy Picture of the Day cache: 1d url: https://api.nasa.gov/planetary/apod?api_key=${NASA_API_KEY} headers: Accept: application/json template: | {{- if eq (.JSON.String "media_type") "image" -}}

{{ .JSON.String "title" }}

{{ .JSON.String
Show Explanation

{{ .JSON.String "explanation" }}

{{- else -}}

No image available today.

{{- end }} - size: full widgets: - type: search search-engine: google bangs: # Added bangs section - title: YouTube shortcut: "!yt" url: https://www.youtube.com/results?search_query={QUERY} - title: Wikipedia shortcut: "!w" url: https://en.wikipedia.org/wiki/Special:Search?search={QUERY} - title: Amazon shortcut: "!a" url: https://www.amazon.com/s?k={QUERY} - title: Reddit shortcut: "!r" url: https://www.reddit.com/search/?q={QUERY} - title: GitHub shortcut: "!gh" url: https://github.com/search?q={QUERY} - type: bookmarks # Rewritten Quick Links widget title: Quick Links groups: - links: - title: Google url: https://www.google.com/ icon: si:google - title: GMail url: https://mail.google.com/ icon: si:gmail - title: GDrive url: https://drive.google.com/ icon: si:googledrive - links: - title: GCal url: https://calendar.google.com/ icon: si:googlecalendar - title: Youtube url: https://www.youtube.com/ icon: si:youtube - title: Amazon url: https://www.amazon.com/ icon: si:amazon - links: - title: Github url: https://github.com/ icon: si:github - title: LinkedIn url: https://www.linkedin.com/ icon: si:linkedin - title: Plex url: "${PLEX_URL}/web/index.html#!/" icon: si:plex - links: - title: MCSManager url: http://panel.sirblob.co/ icon: mdi:server - title: Gitea url: https://git.sirblob.co/ icon: si:gitea - title: Coder url: "${CODER_APP_URL}" icon: mdi:code-braces - title: Yamtrack url: https://tbd.sirblob.co/ icon: mdi:bug - links: - title: qBittorrent url: https://torr.sirblob.co/ icon: si:qbittorrent - title: Excalidraw url: https://draw.sirblob.co/ icon: mdi:draw - title: Immich url: https://immich.sirblob.co/ icon: si:immich - links: - title: Canvas url: https://canvas.gmu.edu/ icon: si:instructure # Instructure is the company behind Canvas - title: Mason360 url: https://mason360.gmu.edu/ icon: mdi:school # Using a generic school icon - title: Nginx Proxy Manager # Added NPM url: https://npm.sirblob.co/ icon: si:nginxproxymanager - title: OpenWeb (AI Chat) url: https://openweb.sirblob.co/ icon: mdi:chat - title: Sea (Anime) url: https://sea.sirblob.co/ icon: mdi:television - type: monitor cache: 1m title: My Services sites: - title: Plex url: "${PLEX_URL}/web/index.html#!/" icon: si:plex - title: Immich url: https://immich.sirblob.co icon: si:immich - title: MCSManager url: http://panel.sirblob.co/ icon: mdi:server - title: Coder url: "${CODER_APP_URL}" icon: mdi:code-braces - title: Yamtrack url: https://tbd.sirblob.co/ icon: mdi:bug - title: Excalidraw url: https://draw.sirblob.co/ icon: mdi:draw - title: qBittorrent url: https://torr.sirblob.co/ icon: si:qbittorrent - title: Gitea url: https://git.sirblob.co/ icon: si:gitea - title: Nginx Proxy Manager # Added NPM url: https://npm.sirblob.co/ icon: si:nginxproxymanager - title: OpenWeb (AI Chat) url: https://openweb.sirblob.co/ icon: mdi:chat - title: Sea (Anime) url: https://sea.sirblob.co/ icon: mdi:television - type: group widgets: - type: custom-api title: mason.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/mason.sirblob.co template: &minecraft-widget-template |
{{ if .JSON.Bool "online" }} {{ else }} {{ end }}
{{ .JSON.String "host" }} {{ if .JSON.Bool "online" }} {{ else }} {{ end }}
- type: custom-api title: cobble.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/cobble.sirblob.co template: *minecraft-widget-template - type: custom-api title: cgc.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/cgc.sirblob.co template: *minecraft-widget-template - type: custom-api title: presto.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/presto.sirblob.co template: *minecraft-widget-template - type: custom-api title: event.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/event.sirblob.co template: *minecraft-widget-template - type: custom-api title: LeetCode Daily Question cache: 6h url: https://leetcode.com/graphql method: POST headers: Accept: application/json body-type: json body: query: | query questionOfToday { activeDailyCodingChallengeQuestion { link question { questionId title difficulty topicTags { name slug } } } } operationName: questionOfToday template: |

{{ .JSON.String "data.activeDailyCodingChallengeQuestion.question.questionId" }} - {{ .JSON.String "data.activeDailyCodingChallengeQuestion.question.title" }}

Difficulty: {{ .JSON.String "data.activeDailyCodingChallengeQuestion.question.difficulty" }}

Topics:

{{ if .JSON.Exists "data.activeDailyCodingChallengeQuestion.question.topicTags" }} {{ range .JSON.Array "data.activeDailyCodingChallengeQuestion.question.topicTags" }} {{ .String "name" }} {{ end }} {{ else }} None {{ end }}
{{ if .JSON.Bool "data.activeDailyCodingChallengeQuestion.question.paidOnly" }}

This is a Premium question

{{ end }}
- size: small widgets: - type: weather location: Great Falls, Virginia, United States units: imperial hour-format: 12h - type: custom-api title: Air Quality cache: 10m url: https://api.waqi.info/feed/geo:38.9979;-77.2894/?token=${AIR_TOKEN} template: | {{ $aqi := printf "%03s" (.JSON.String "data.aqi") }} {{ $aqiraw := .JSON.String "data.aqi" }} {{ $updated := .JSON.String "data.time.iso" }} {{ $humidity := .JSON.String "data.iaqi.h.v" }} {{ $ozone := .JSON.String "data.iaqi.o3.v" }} {{ $pm25 := .JSON.String "data.iaqi.pm25.v" }} {{ $pressure := .JSON.String "data.iaqi.p.v" }}
{{ if le $aqi "050" }}
Good air quality
{{ else if le $aqi "100" }}
Moderate air quality
{{ else }}
Bad air quality
{{ end }}
AQI: {{ $aqiraw }}
{{ $humidity }}%
HUMIDITY
{{ $ozone }} μg/m³
OZONE
{{ $pm25 }} μg/m³
PM2.5
{{ $pressure }} hPa
PRESSURE
Last Updated at {{ slice $updated 11 16 }}
- type: custom-api title: GitHub Repositories url: https://api.github.com/users/SirBlobby/repos cache: 30m parameters: affiliation: owner sort: updated visibility: all headers: Accept: application/vnd.github.v3+json User-Agent: Glance-Dashboard template: |
- name: Media columns: - size: small # Added new small column on the left widgets: - type: hacker-news # Added Hacker News widget limit: 15 collapse-after: 5 - size: full # Existing full column is now second widgets: - type: custom-api title: Epic Games cache: 1h url: https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions?locale=en&country=US&allowCountries=US template: |
{{ if eq .Response.StatusCode 200 }}
{{ range .JSON.Array "data.Catalog.searchStore.elements" }} {{ $price := .String "price.totalPrice.discountPrice" }} {{ $hasPromo := gt (len (.Array "promotions.promotionalOffers")) 0 }} {{ if and $hasPromo (eq $price "0") }} {{ $gamePage := .String "productSlug" }} {{ if gt (len (.Array "offerMappings")) 0 }} {{ $gamePage = .String "offerMappings.0.pageSlug" }} {{end }} {{ $title := .String "title" }} {{ range .Array "keyImages" }} {{ if eq (.String "type") "OfferImageWide" }} {{ $title }} {{ end }} {{ end }}
{{ $title }}
{{ if $hasPromo }} {{ $promotions := .Array "promotionalOffers" }} {{ if gt (len $promotions) 0 }} {{ $firstPromo := index $promotions 0 }} {{ $offers := $firstPromo.Array "promotionalOffers" }} {{ if gt (len $offers) 0 }} {{ $firstOffer := index $offers 0 }} Free until {{ slice ($firstOffer.String "endDate") 0 10 }} {{ else }} Free this week! {{ end }} {{ else }} Free this week! {{ end }} {{ end }}
{{ end }} {{ end }}
{{ else }}

Error fetching Epic Games data.

{{ end }}
- type: videos title: For You style: grid-cards collapse-after-rows: 2 channels: - UCwWhs_6x42TyRM4Wstoq8HA - UCLuYADJ6hESLHX87JnsGbjA - UC-gW4TeZAlKm7UATp24JsWQ - UCETqJYEne9Tks-eZYIrFZvg - UCOT2iLov0V7Re7ku_3UBtcQ - UCFe2Kq8Hg15UomoVYdmRg_Q - type: videos title: Engineering style: grid-cards collapse-after-rows: 2 channels: - UCUyeluBRhGPCW4rPe_UvBZQ - UCFhXFikryT4aFcLkLw2LBLA - UCsBjURrPoezykLs9EqgamOA - UCHnyfMqiRRG1u-2MsSQLbXA - UC6biysICWOJ-C3P4Tyeggzg - name: Services columns: - size: full widgets: - type: monitor cache: 1m title: My Services sites: - title: Plex url: "${PLEX_URL}/web/index.html#!/" icon: si:plex - title: Immich url: https://immich.sirblob.co icon: si:immich - title: MCSManager url: http://panel.sirblob.co/ icon: mdi:server - title: Coder url: "${CODER_APP_URL}" icon: mdi:code-braces - title: Excalidraw url: https://draw.sirblob.co/ icon: mdi:draw - title: qBittorrent url: https://torr.sirblob.co/ icon: si:qbittorrent - title: Gitea url: https://git.sirblob.co/ icon: si:gitea - title: Nginx Proxy Manager # Added NPM url: https://npm.sirblob.co/ icon: si:nginxproxymanager - type: custom-api title: Immich stats cache: 10m url: https://immich.sirblob.co/api/server/statistics headers: x-api-key: ${IMMICH_API_KEY} Accept: application/json template: |
{{ .JSON.Int "photos" | formatNumber }}
PHOTOS
{{ .JSON.Int "videos" | formatNumber }}
VIDEOS
{{ div (.JSON.Int "usage" | toFloat) 1073741824 | toInt | formatNumber }}GB
USAGE
- type: group widgets: - type: custom-api title: mason.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/mason.sirblob.co template: *minecraft-widget-template - type: custom-api title: cobble.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/cobble.sirblob.co template: *minecraft-widget-template - type: custom-api title: cgc.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/cgc.sirblob.co template: *minecraft-widget-template - type: custom-api title: presto.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/presto.sirblob.co template: *minecraft-widget-template - type: custom-api title: create.sirblob.co cache: 30s url: https://api.mcstatus.io/v2/status/java/create.sirblob.co template: *minecraft-widget-template - type: custom-api title: qBittorrent cache: 10s options: view: "detailed" # "basic" or "detailed" mode: "default" # "default" or "upload" subrequests: transfer: url: http://${QBITTORRENT_IP}/api/v2/transfer/info seeding: url: http://${QBITTORRENT_IP}/api/v2/torrents/info parameters: filter: seeding leeching: url: http://${QBITTORRENT_IP}/api/v2/torrents/info parameters: filter: downloading template: | {{ $transfer := .Subrequest "transfer" }} {{ $seeding := .Subrequest "seeding" }} {{ $leeching := .Subrequest "leeching" }} {{ if and (eq $transfer.Response.StatusCode 200) (eq $seeding.Response.StatusCode 200) (eq $leeching.Response.StatusCode 200) }} {{ $isDetailed := eq (.Options.StringOr "view" "detailed") "detailed" }} {{ $mode := .Options.StringOr "mode" "default" }} {{ if $isDetailed }}
{{ $dlSpeed := $transfer.JSON.Float "dl_info_speed" }} {{ if eq $mode "upload" }}
{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}
{{ else }} {{ if lt $dlSpeed 1048576.0 }}
{{ printf "%.0f KiB/s" (div $dlSpeed 1024.0) }}
{{ else }}
{{ printf "%.1f MiB/s" (div $dlSpeed 1048576.0) }}
{{ end }} {{ end }}
DOWNLOADING
{{ if eq $mode "upload" }}
{{ $ulSpeed := $transfer.JSON.Float "up_info_speed" }}
{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}
UPLOADING
{{ end }}
{{ len ($seeding.JSON.Array "") }}
SEEDING
{{ if eq $mode "default" }}
{{ len ($leeching.JSON.Array "") }}
LEECHING
{{ end }}
{{ $downloadingTorrents := $leeching.JSON.Array "" }} {{ if gt (len $downloadingTorrents) 0 }}
{{ end }} {{ if eq $mode "upload" }} {{ $seedingTorrents := $seeding.JSON.Array "" }} {{ if gt (len $seedingTorrents) 0 }}
{{ end }} {{ end }}
{{ else }}
{{ $dlSpeed := $transfer.JSON.Float "dl_info_speed" }}
{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}
DOWNLOADING
{{ if eq $mode "upload" }}
{{ $ulSpeed := $transfer.JSON.Float "up_info_speed" }}
{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}
UPLOADING
{{ end }}
{{ len ($seeding.JSON.Array "") }}
SEEDING
{{ if eq $mode "default" }}
{{ len ($leeching.JSON.Array "") }}
LEECHING
{{ end }}
{{ end }} {{ else }}

Error fetching qBittorrent data.

Check URL and authentication bypass settings.

{{ end }} - type: custom-api title: Plex Now Playing cache: 1m options: media-server: "plex" base-url: ${PLEX_URL} api-key: ${PLEX_TOKEN} small-column: false compact: false play-state: "indicator" show-thumbnail: true full-thumbnail: false show-paused: false show-progress-bar: false show-progress-info: true time-format: "15:04" template: | {{ $mediaServer := .Options.StringOr "media-server" "" }} {{ $baseURL := .Options.StringOr "base-url" "" }} {{ $apiKey := .Options.StringOr "api-key" "" }} {{ define "errorMsg" }}
ERROR

{{ . }}

{{ end }} {{ if or (eq $mediaServer "") (eq $baseURL "") (eq $apiKey "") }} {{ template "errorMsg" "Some required options are not set" }} {{ else }} {{ $isSmallColumn := .Options.BoolOr "small-column" false }} {{ $isCompact := .Options.BoolOr "compact" true }} {{ $playState := .Options.StringOr "play-state" "indicator" }} {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }} {{ $fullThumbnail := .Options.BoolOr "full-thumbnail" false }} {{ $showPaused := .Options.BoolOr "show-paused" false }} {{ $showProgressBar := .Options.BoolOr "show-progress-bar" false }} {{ $showProgressInfo := .Options.BoolOr "show-progress-info" true }} {{ $timeFormat := .Options.StringOr "time-format" "15:04" }} {{ $userID := "" }} {{ $sessionsRequestURL := "" }} {{ $sessionsCall := "" }} {{ $sessions := "" }} {{ $activeSessions := 0 }} {{ if eq $mediaServer "plex" }} {{ $sessionsRequestURL = concat $baseURL "/status/sessions" }} {{ $sessionsCall = newRequest $sessionsRequestURL | withHeader "Accept" "application/json" | withHeader "X-Plex-Token" $apiKey | getResponse }} {{ if $sessionsCall.JSON.Exists "MediaContainer" }} {{ $sessions = $sessionsCall.JSON.Array "MediaContainer.Metadata" }} {{ $activeSessions = len $sessions }} {{ else }} {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }} {{ end }} {{ else if eq $mediaServer "tautulli" }} {{ $sessionsRequestURL = concat $baseURL "/api/v2" }} {{ $sessionsCall = newRequest $sessionsRequestURL | withParameter "apikey" $apiKey | withParameter "cmd" "get_activity" | withHeader "Accept" "application/json" | getResponse }} {{ if eq $sessionsCall.Response.StatusCode 200 }} {{ $sessions = $sessionsCall.JSON.Array "response.data.sessions" }} {{ $activeSessions = len $sessions }} {{ else }} {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }} {{ end }} {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }} {{ $sessionsRequestURL = concat $baseURL "/Sessions" }} {{ $sessionsCall = newRequest $sessionsRequestURL | withParameter "api_key" $apiKey | withParameter "activeWithinSeconds" "30" | withHeader "Accept" "application/json" | getResponse }} {{ if eq $sessionsCall.Response.StatusCode 200 }} {{ $sessions = $sessionsCall.JSON.Array "" }} {{ if eq $mediaServer "emby" }} {{ range $session := $sessions }} {{ if $session.Bool "PlayState.CanSeek" }} {{ $activeSessions = 1 }} {{ break }} {{ end }} {{ end }} {{ else }} {{ $activeSessions = len $sessions }} {{ end }} {{ else }} {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }} {{ end }} {{ end }} {{ if and (eq $sessionsCall.Response.StatusCode 200) (eq $activeSessions 0) }}

Nothing is playing right now.

{{ else if $sessionsCall.JSON.Exists "MediaContainer" }}
{{ range $i, $session := $sessions }} {{ $isClient := true }} {{ $isPlaying := false }} {{ $state := "playing" }} {{ $isMovie := false }} {{ $isShows := false }} {{ $isMusic := false }} {{ $userName := "" }} {{ $title := "" }} {{ $showTitle := "" }} {{ $showSeason := "" }} {{ $showEpisode := "" }} {{ $artist := "" }} {{ $albumTitle := "" }} {{ $thumbURL := "" }} {{ $now := now | formatTime "15:04:05" | parseLocalTime "15:04:05" }} {{ $duration := 0 }} {{ $offset := 0 }} {{ $remainingSeconds := 0 }} {{ if eq $mediaServer "plex" }} {{ $isPlaying = eq ($session.String "Player.state") "playing" }} {{ if not $isPlaying }} {{ $state = "paused"}} {{ end }} {{ $mediaType := $session.String "type" }} {{ $isMovie = eq $mediaType "movie" }} {{ $isShows = eq $mediaType "episode" }} {{ $isMusic = eq $mediaType "track" }} {{ $userName = $session.String "User.title" }} {{ $title = $session.String "title" }} {{ $showTitle = $session.String "grandparentTitle" }} {{ $showSeason = $session.String "parentIndex" }} {{ $showEpisode = $session.String "index" }} {{ $artist = $session.String "grandparentTitle" }} {{ $albumTitle = $session.String "parentTitle" }} {{ $thumbID := $session.String "thumb" }} {{ if or $isShows $isMusic }} {{ $thumbID = $session.String "parentThumb" }} {{ end }} {{ $thumbURL = concat $baseURL $thumbID "?X-Plex-Token=" $apiKey }} {{ $duration = $session.Float "duration" }} {{ $offset = $session.Float "viewOffset" }} {{ $remainingSeconds = div (sub $duration $offset) 1000 | toInt }} {{ else if eq $mediaServer "tautulli" }} {{ $isPlaying = eq ($session.String "state") "playing" }} {{ if not $isPlaying }} {{ $state = "paused"}} {{ end }} {{ $mediaType := $session.String "media_type" }} {{ $isMovie = eq $mediaType "movie" }} {{ $isShows = eq $mediaType "episode" }} {{ $isMusic = eq $mediaType "track" }} {{ $userName = $session.String "user" }} {{ $title = $session.String "title" }} {{ $showTitle = $session.String "grandparent_title" }} {{ $showSeason = $session.String "parent_media_index" }} {{ $showEpisode = $session.String "media_index" }} {{ $artist = $session.String "grandparent_title" }} {{ $albumTitle = $session.String "parent_title" }} {{ $thumbID := $session.String "thumb" }} {{ if or $isShows $isMusic }} {{ $thumbID = $session.String "parent_thumb" }} {{ end }} {{ $thumbURL = concat $baseURL "/api/v2?apikey=" $apiKey "&cmd=pms_image_proxy&img=" $thumbID }} {{ $duration = $session.Float "duration" }} {{ $offset = $session.Float "view_offset" }} {{ $remainingSeconds = div (sub $duration $offset) 1000 | toInt }} {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }} {{ if eq $mediaServer "emby" }} {{ $isClient = $session.Bool "PlayState.CanSeek" }} {{ end }} {{ $isPlaying = and ($session.Exists "NowPlayingItem") (not ($session.Bool "PlayState.IsPaused")) }} {{ if not $isPlaying }} {{ $state = "paused"}} {{ end }} {{ $mediaType := $session.String "NowPlayingItem.Type" }} {{ $isMovie = eq $mediaType "Movie" }} {{ $isShows = eq $mediaType "Episode" }} {{ $isMusic = eq $mediaType "Audio" }} {{ $userName = $session.String "UserName" }} {{ $title = $session.String "NowPlayingItem.Name" }} {{ $showTitle = $session.String "NowPlayingItem.SeriesName" }} {{ $showSeason = $session.String "NowPlayingItem.ParentIndexNumber" }} {{ $showEpisode = $session.String "NowPlayingItem.IndexNumber" }} {{ $artist = $session.String "NowPlayingItem.AlbumArtist" }} {{ $albumTitle = $session.String "NowPlayingItem.Album" }} {{ $thumbID := $session.String "NowPlayingItem.Id" }} {{ if $isShows }} {{ $thumbID = $session.String "NowPlayingItem.ParentId" }} {{ end }} {{ $thumbURL = concat $baseURL "/Items/" $thumbID "/Images/Primary?api_key=" $apiKey }} {{ $duration = $session.Float "NowPlayingItem.RunTimeTicks" }} {{ $offset = $session.Float "PlayState.PositionTicks" }} {{ $remainingSeconds = div (sub $duration $offset) 10000000 | toInt }} {{ end }} {{ $progress := mul 100 (div $offset $duration) | toInt }} {{ $endTime := $now.Add (duration (printf "%ds" $remainingSeconds)) | formatTime $timeFormat }} {{ $showInfoFormat := concat "Season " $showSeason " Episode " $showEpisode}} {{ if $isCompact }} {{ $showInfoFormat = concat "S" $showSeason "E" $showEpisode}} {{ end }} {{ if and $isClient (or $isPlaying $showPaused) }}
{{ $userName }} {{ if eq $playState "text" }} [{{ $state }}] {{ else if eq $playState "indicator" }}
{{ end }}

{{ if $showThumbnail }} {{ $title }} thumbnail {{ end }}
    {{ if $isCompact }} {{ if $isShows }}
    • {{ concat "S" $showSeason "E" $showEpisode }}
    • {{ $showTitle }}
    {{ else if $isMusic }}
    • {{ $artist }}
    • {{ $albumTitle }}
    {{ end }}
  • {{ $title }}
  • {{ else }} {{ if $isShows }}
  • {{ $showTitle }}
  • {{ concat "S" $showSeason "E" $showEpisode }}
  • {{ else if $isMusic }}
  • {{ $artist }}
  • {{ $albumTitle }}
  • {{ end }}
  • {{ $title }}
  • {{ end }}
  • {{ if and $isPlaying $showProgressBar }}
    {{ if $showProgressInfo }}

    {{ if and (not $isCompact) (not $isSmallColumn) }} ends at {{ end }} {{ $endTime }}

    {{ end }}
    {{ end }}
{{ end }} {{ end }}
{{ end }} {{ end }} - type: custom-api title: Media Server History frameless: true cache: 5m options: media-server: "plex" base-url: ${PLEX_URL} api-key: ${PLEX_TOKEN} history-length: "10" small-column: false compact: true show-thumbnail: true thumbnail-aspect-ratio: "default" show-user: true time-absolute: false time-format: "Jan 02 15:04" template: | {{ $mediaServer := .Options.StringOr "media-server" "" }} {{ $baseURL := .Options.StringOr "base-url" "" }} {{ $apiKey := .Options.StringOr "api-key" "" }} {{ $userName := .Options.StringOr "user-name" "" }} {{ define "errorMsg" }}
ERROR

{{ . }}

{{ end }} {{ if or (eq $mediaServer "") (eq $baseURL "") (eq $apiKey "") (and (eq $mediaServer "jellyfin") (eq $userName "")) }} {{ template "errorMsg" "Some required options are not set" }} {{ else }} {{ $historyLength := .Options.StringOr "history-length" "10" }} {{ $mediaTypes := .Options.StringOr "media-types" "" }} {{ if eq $mediaServer "tautulli" }} {{ $mediaTypes = .Options.StringOr "media-types" "movie,episode,track" }} {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }} {{ $mediaTypes = .Options.StringOr "media-types" "Movie,Episode,Audio" }} {{ end }} {{ $isSmallColumn := .Options.BoolOr "small-column" false }} {{ $isCompact := .Options.BoolOr "compact" true }} {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }} {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }} {{ $showUser := .Options.BoolOr "show-user" true }} {{ $timeAbsolute := .Options.BoolOr "time-absolute" false }} {{ $timeFormat := .Options.StringOr "time-format" "Jan 02 15:04" }} {{ $userID := "" }} {{ $historyRequestURL := "" }} {{ $usersRequestURL := "" }} {{ $historyCall := "" }} {{ $usersCall := "" }} {{ $history := "" }} {{ $users := "" }} {{ if eq $mediaServer "plex" }} {{ $historyRequestURL = concat $baseURL "/status/sessions/history/all" }} {{ $historyCall = newRequest $historyRequestURL | withParameter "limit" $historyLength | withParameter "sort" "viewedAt:desc" | withHeader "Accept" "application/json" | withHeader "X-Plex-Token" $apiKey | getResponse }} {{ if $historyCall.JSON.Exists "MediaContainer" }} {{ $history = $historyCall.JSON.Array "MediaContainer.Metadata" }} {{ else }} {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }} {{ end }} {{ $usersRequestURL = concat $baseURL "/accounts" }} {{ $usersCall = newRequest $usersRequestURL | withHeader "Accept" "application/json" | withHeader "X-Plex-Token" $apiKey | getResponse }} {{ $users = $usersCall.JSON.Array "MediaContainer.Account" }} {{ else if eq $mediaServer "tautulli" }} {{ $historyRequestURL = concat $baseURL "/api/v2" }} {{ $historyCall = newRequest $historyRequestURL | withParameter "apikey" $apiKey | withParameter "cmd" "get_history" | withParameter "length" $historyLength | withParameter "media_type" $mediaTypes | withHeader "Accept" "application/json" | getResponse }} {{ if eq $historyCall.Response.StatusCode 200 }} {{ $history = $historyCall.JSON.Array "response.data.data" }} {{ else }} {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }} {{ end }} {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }} {{ $usersRequestURL = concat $baseURL "/Users" }} {{ $usersCall = newRequest $usersRequestURL | withParameter "api_key" $apiKey | withHeader "Accept" "application/json" | getResponse }} {{ $usersList := $usersCall.JSON.Array "" }} {{ range $i, $user := $usersList }} {{ if eq ($user.String "Name") $userName }} {{ $userID = $user.String "Id" }} {{ break }} {{ end }} {{ end }} {{ if eq $userID "" }} {{ template "errorMsg" (concat "User '" $userName "' not found.") }} {{ end }} {{ $historyRequestURL = concat $baseURL "/Users/" $userID "/Items" }} {{ $historyCall = newRequest $historyRequestURL | withParameter "api_key" $apiKey | withParameter "Limit" $historyLength | withParameter "IncludeItemTypes" $mediaTypes | withParameter "Recursive" "true" | withParameter "isPlayed" "true" | withParameter "sortBy" "DatePlayed" | withParameter "sortOrder" "Descending" | withParameter "Fields" "UserDataLastPlayedDate" | withHeader "Accept" "application/json" | getResponse }} {{ $history = $historyCall.JSON.Array "Items" }} {{ end }} {{ if and (eq $historyCall.Response.StatusCode 200) (eq (len $history) 0) }}

Nothing has been played. Start streaming something!

{{ else if $historyCall.JSON.Exists "MediaContainer" }} {{ end }} {{ end }}