From a75e466c38ea52f9f32e4c69c349c3e6a1686270 Mon Sep 17 00:00:00 2001 From: GamerBoss101 Date: Sun, 26 Oct 2025 16:54:51 -0400 Subject: [PATCH] Initial Config --- assets/dev.png | Bin 0 -> 7660 bytes assets/user.css | 13 + config/backup.yml | 1562 +++++++++++++++++++++++++++++++++++++++++++++ config/glance.yml | 23 + config/home.yml | 1562 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 3160 insertions(+) create mode 100644 assets/dev.png create mode 100644 assets/user.css create mode 100644 config/backup.yml create mode 100644 config/glance.yml create mode 100644 config/home.yml diff --git a/assets/dev.png b/assets/dev.png new file mode 100644 index 0000000000000000000000000000000000000000..952cbba18034ff68859d3d076604b001c275cbe2 GIT binary patch literal 7660 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 oIIg|@=e + +

+ + {{ .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: https://app.plex.tv/ + 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: https://mcbugj8flucdg.pit-1.try.coder.app + icon: mdi:code-braces + - 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 + + - type: monitor + cache: 1m + title: My Services + sites: + - title: Plex + url: https://plex1.sirblob.co/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: https://mcbugj8flucdg.pit-1.try.coder.app + 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: 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 }} + +
    +
  • + {{ if .JSON.Bool "online" }} + {{ .JSON.String "version.name_clean" }} + {{ else }} + Offline + {{ end }} +
  • + {{ if .JSON.Bool "online" }} +
  • +
    + {{ range .JSON.Array "players.list" }}{{ .String "name_clean" }}
    {{ end }} +
    +

    + + {{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} players +

    +
  • + {{ else }} +
  • +

    + + 0 players +

    +
  • + {{ 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: | +
+
    + {{ range .JSON.Array "" }} +
  • + {{ .String "full_name" }} +

    +

    {{ .String "description" }}

    +
      +
    • Last Update
      {{ formatTime "DateOnly" ( parseTime "RFC3339" (.String "updated_at") ) }}
    • +
    • Visibility
      {{ .String "visibility" }}
    • +
    • Stars
      {{ .Int "stargazers_count" }}✩
    • + {{ if .String "language" }} +
    • Language
      {{ .String "language" }}
    • + {{ end }} +
    +
  • + {{ end }} +
+
+ +- 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: https://plex1.sirblob.co/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: https://mcbugj8flucdg.pit-1.try.coder.app + 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 }} +
+
    + {{ range $t := $downloadingTorrents }} + {{ $state := $t.String "state" }} + {{ $icon := "?" }} + {{ if ge ($t.Int "completed") ($t.Int "size") }}{{ $icon = "✔" }} + {{ else if eq $state "downloading" "forcedDL" }}{{ $icon = "↓" }} + {{ else if eq $state "pausedDL" "stoppedDL" "pausedUP" "stalledDL" "stalledUP" "queuedDL" "queuedUP" }}{{ $icon = "❚❚" }} + {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }} + {{ else if eq $state "checkingDL" "checkingUP" "allocating" }}{{ $icon = "…" }} + {{ else if eq $state "checkingResumeData" }}{{ $icon = "⟳" }} + {{ end }} + +
  • +
    {{ $icon }}
    +
    +
    {{ $t.String "name" }}
    +
    +
    +
    +
    +
    + {{ $dlSpeed := $t.Float "dlspeed" }} +
    + {{ if eq $mode "upload" }} + {{ if lt $dlSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}{{ end }} + {{ else }} + {{ if lt $dlSpeed 1024.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 }} +
    + {{ $eta := $t.Int "eta" }} +
    + {{ if eq $eta 8640000 }}∞ + {{ else if gt $eta 3600 }}{{ printf "%dh %dm" (div $eta 3600) (mod (div $eta 60) 60) }} + {{ else if gt $eta 0 }}{{ printf "%dm" (div $eta 60) }} + {{ else }}--{{ end }} +
    +
    +
  • + {{ end }} +
+
+ {{ end }} + + + {{ if eq $mode "upload" }} + {{ $seedingTorrents := $seeding.JSON.Array "" }} + {{ if gt (len $seedingTorrents) 0 }} +
+
    + {{ range $t := $seedingTorrents }} + {{ $state := $t.String "state" }} + {{ $icon := "↑" }} + {{ if eq $state "pausedUP" "stoppedUP" "stalledUP" "queuedUP" }}{{ $icon = "❚❚" }} + {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }} + {{ else if eq $state "checkingUP" }}{{ $icon = "…" }} + {{ else if eq $state "checkingResumeData" }}{{ $icon = "⟳" }} + {{ end }} + +
  • +
    {{ $icon }}
    +
    +
    {{ $t.String "name" }}
    +
    + Ratio: {{ printf "%.2f" ($t.Float "ratio") }} | + Size: {{ printf "%.1f GB" (div ($t.Float "size") 1073741824.0) }} +
    +
    +
    + {{ $ulSpeed := $t.Float "upspeed" }} +
    + {{ if lt $ulSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}{{ end }} +
    +
    Upload
    +
    +
  • + {{ end }} +
+
+ {{ 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: https://plex1.sirblob.co + 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 "https://plex1.sirblob.co" $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: https://plex1.sirblob.co + 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 }} + diff --git a/config/glance.yml b/config/glance.yml new file mode 100644 index 0000000..31b80e8 --- /dev/null +++ b/config/glance.yml @@ -0,0 +1,23 @@ +server: + assets-path: /app/assets + +branding: + app-name: "Sir Blob's Dash" # PWA/Tab Name + app-background-color: "#22232e" # PWA Background + +theme: + contrast-multiplier: 1.5 + background-color: 0 0 13 + primary-color: 319 23 50 # Original lightness 81 + negative-color: 78 13 70 # Original lightness 59 + + disable-picker: true + + # Note: assets are cached by the browser, changes to the CSS file + # will not be reflected until the browser cache is cleared (Ctrl+F5) + custom-css-file: /assets/user.css + +pages: + # It's not necessary to create a new file for each page and include it, you can simply + # put its contents here, though multiple pages are easier to manage when separated + - $include: home.yml diff --git a/config/home.yml b/config/home.yml new file mode 100644 index 0000000..e7b5de8 --- /dev/null +++ b/config/home.yml @@ -0,0 +1,1562 @@ +- 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: https://app.plex.tv/ + 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: https://mcbugj8flucdg.pit-1.try.coder.app + icon: mdi:code-braces + - 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 + + - type: monitor + cache: 1m + title: My Services + sites: + - title: Plex + url: https://plex1.sirblob.co/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: https://mcbugj8flucdg.pit-1.try.coder.app + 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: 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 }} + +
    +
  • + {{ if .JSON.Bool "online" }} + {{ .JSON.String "version.name_clean" }} + {{ else }} + Offline + {{ end }} +
  • + {{ if .JSON.Bool "online" }} +
  • +
    + {{ range .JSON.Array "players.list" }}{{ .String "name_clean" }}
    {{ end }} +
    +

    + + {{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} players +

    +
  • + {{ else }} +
  • +

    + + 0 players +

    +
  • + {{ 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: | +
+
    + {{ range .JSON.Array "" }} +
  • + {{ .String "full_name" }} +

    +

    {{ .String "description" }}

    +
      +
    • Last Update
      {{ formatTime "DateOnly" ( parseTime "RFC3339" (.String "updated_at") ) }}
    • +
    • Visibility
      {{ .String "visibility" }}
    • +
    • Stars
      {{ .Int "stargazers_count" }}✩
    • + {{ if .String "language" }} +
    • Language
      {{ .String "language" }}
    • + {{ end }} +
    +
  • + {{ end }} +
+
+ +- 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: https://plex1.sirblob.co/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: https://mcbugj8flucdg.pit-1.try.coder.app + 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 }} +
+
    + {{ range $t := $downloadingTorrents }} + {{ $state := $t.String "state" }} + {{ $icon := "?" }} + {{ if ge ($t.Int "completed") ($t.Int "size") }}{{ $icon = "✔" }} + {{ else if eq $state "downloading" "forcedDL" }}{{ $icon = "↓" }} + {{ else if eq $state "pausedDL" "stoppedDL" "pausedUP" "stalledDL" "stalledUP" "queuedDL" "queuedUP" }}{{ $icon = "❚❚" }} + {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }} + {{ else if eq $state "checkingDL" "checkingUP" "allocating" }}{{ $icon = "…" }} + {{ else if eq $state "checkingResumeData" }}{{ $icon = "⟳" }} + {{ end }} + +
  • +
    {{ $icon }}
    +
    +
    {{ $t.String "name" }}
    +
    +
    +
    +
    +
    + {{ $dlSpeed := $t.Float "dlspeed" }} +
    + {{ if eq $mode "upload" }} + {{ if lt $dlSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}{{ end }} + {{ else }} + {{ if lt $dlSpeed 1024.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 }} +
    + {{ $eta := $t.Int "eta" }} +
    + {{ if eq $eta 8640000 }}∞ + {{ else if gt $eta 3600 }}{{ printf "%dh %dm" (div $eta 3600) (mod (div $eta 60) 60) }} + {{ else if gt $eta 0 }}{{ printf "%dm" (div $eta 60) }} + {{ else }}--{{ end }} +
    +
    +
  • + {{ end }} +
+
+ {{ end }} + + + {{ if eq $mode "upload" }} + {{ $seedingTorrents := $seeding.JSON.Array "" }} + {{ if gt (len $seedingTorrents) 0 }} +
+
    + {{ range $t := $seedingTorrents }} + {{ $state := $t.String "state" }} + {{ $icon := "↑" }} + {{ if eq $state "pausedUP" "stoppedUP" "stalledUP" "queuedUP" }}{{ $icon = "❚❚" }} + {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }} + {{ else if eq $state "checkingUP" }}{{ $icon = "…" }} + {{ else if eq $state "checkingResumeData" }}{{ $icon = "⟳" }} + {{ end }} + +
  • +
    {{ $icon }}
    +
    +
    {{ $t.String "name" }}
    +
    + Ratio: {{ printf "%.2f" ($t.Float "ratio") }} | + Size: {{ printf "%.1f GB" (div ($t.Float "size") 1073741824.0) }} +
    +
    +
    + {{ $ulSpeed := $t.Float "upspeed" }} +
    + {{ if lt $ulSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}{{ end }} +
    +
    Upload
    +
    +
  • + {{ end }} +
+
+ {{ 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: https://plex1.sirblob.co + 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 "https://plex1.sirblob.co" $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: https://plex1.sirblob.co + 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 }} +