diff --git a/assets/dev.png b/assets/dev.png new file mode 100644 index 0000000..952cbba Binary files /dev/null and b/assets/dev.png differ diff --git a/assets/user.css b/assets/user.css new file mode 100644 index 0000000..4d0608e --- /dev/null +++ b/assets/user.css @@ -0,0 +1,13 @@ +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap'); + +body, html { + font-family: 'Open Sans', sans-serif !important; /* Use !important cautiously if needed to override defaults */ +background: #3A5F5F; +background: radial-gradient(circle, rgba(58, 95, 95, 1) 0%, rgba(33, 33, 33, 1) 100%); + color: #e7ebeb; +} + +/* You might need more specific selectors depending on what you want to change */ +.widget-content { + font-family: 'Open Sans', sans-serif !important; +} \ No newline at end of file diff --git a/config/backup.yml b/config/backup.yml new file mode 100644 index 0000000..e7b5de8 --- /dev/null +++ b/config/backup.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 }} + + +
+
+ - 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: 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 }} +
+ +
+ {{ 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: 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 }} + + +
+
+ - 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: 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 }} +
+ +
+ {{ 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: 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 }} +