Compare commits
2 Commits
c3b5d7633a
...
59183e7021
| Author | SHA1 | Date | |
|---|---|---|---|
|
59183e7021
|
|||
|
9242b02957
|
@@ -2,3 +2,5 @@
|
|||||||
|
|
||||||
- Updated Jellyfin item fetching logic to skip items that are not `Audio`, `Episode`, or `Movie` types, preventing errors when unsupported media types are encountered
|
- Updated Jellyfin item fetching logic to skip items that are not `Audio`, `Episode`, or `Movie` types, preventing errors when unsupported media types are encountered
|
||||||
- Updated Jellyfin image fetching logic to use `ParentId` for episodes and music tracks to ensure correct artwork is displayed in Discord Rich Presence
|
- Updated Jellyfin image fetching logic to use `ParentId` for episodes and music tracks to ensure correct artwork is displayed in Discord Rich Presence
|
||||||
|
- Added `coloredlogs` dependency for improved logging output
|
||||||
|
- Added a formatting script (`scripts/format.sh`) that uses `autopep8` to automatically format the codebase for better readability and consistency
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pypresence.types import ActivityType
|
from pypresence.types import ActivityType
|
||||||
|
|
||||||
|
|
||||||
class DiscordRPCUpdatePayload(BaseModel):
|
class DiscordRPCUpdatePayload(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
title: str
|
title: str
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from settings import settings
|
|||||||
from discord.models import DiscordRPCUpdatePayload
|
from discord.models import DiscordRPCUpdatePayload
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class DiscordRPC:
|
class DiscordRPC:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = logging.getLogger('DiscordRPC')
|
self.logger = logging.getLogger('DiscordRPC')
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class JellyfinApiClient:
|
class JellyfinApiClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
machine_name = os.uname().nodename
|
machine_name = os.uname().nodename
|
||||||
@@ -50,7 +51,8 @@ class JellyfinApiClient:
|
|||||||
session_id = session.get('Id')
|
session_id = session.get('Id')
|
||||||
current_item = self.client.jellyfin.get_now_playing(session_id)
|
current_item = self.client.jellyfin.get_now_playing(session_id)
|
||||||
|
|
||||||
if current_item and current_item.get('Type') in ['Audio', 'Episode', 'Movie']:
|
if current_item and current_item.get(
|
||||||
|
'Type') in ['Audio', 'Episode', 'Movie']:
|
||||||
self.logger.info("Current playback information fetched.")
|
self.logger.info("Current playback information fetched.")
|
||||||
return self.to_model(current_item)
|
return self.to_model(current_item)
|
||||||
|
|
||||||
@@ -89,11 +91,11 @@ class JellyfinApiClient:
|
|||||||
media_id = item.get('Id')
|
media_id = item.get('Id')
|
||||||
parent_id = item.get('ParentId')
|
parent_id = item.get('ParentId')
|
||||||
premiere_date = item.get('PremiereDate')
|
premiere_date = item.get('PremiereDate')
|
||||||
premiere_year = datetime.fromisoformat(premiere_date).year if premiere_date else None
|
premiere_year = datetime.fromisoformat(
|
||||||
|
premiere_date).year if premiere_date else None
|
||||||
|
|
||||||
(start, end) = self.get_playback_info(item)
|
(start, end) = self.get_playback_info(item)
|
||||||
|
|
||||||
|
|
||||||
return JellyfinMediaItem(
|
return JellyfinMediaItem(
|
||||||
id=media_id,
|
id=media_id,
|
||||||
name=item.get('Name'),
|
name=item.get('Name'),
|
||||||
@@ -103,9 +105,8 @@ class JellyfinApiClient:
|
|||||||
end=end,
|
end=end,
|
||||||
metadata={
|
metadata={
|
||||||
'artist': item.get('AlbumArtist'),
|
'artist': item.get('AlbumArtist'),
|
||||||
'album': f"{item.get('Album')} ({premiere_year})" if premiere_date else item.get('Album')
|
'album': f"{
|
||||||
}
|
item.get('Album')} ({premiere_year})" if premiere_date else item.get('Album')})
|
||||||
)
|
|
||||||
|
|
||||||
def to_movie_model(self, item: dict) -> JellyfinMediaItem:
|
def to_movie_model(self, item: dict) -> JellyfinMediaItem:
|
||||||
media_id = item.get('Id')
|
media_id = item.get('Id')
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class JellyfinMediaType(str, Enum):
|
class JellyfinMediaType(str, Enum):
|
||||||
AUDIO = 'Audio'
|
AUDIO = 'Audio'
|
||||||
MOVIE = 'Movie'
|
MOVIE = 'Movie'
|
||||||
EPISODE = 'Episode'
|
EPISODE = 'Episode'
|
||||||
|
|
||||||
|
|
||||||
class JellyfinMediaItem(BaseModel):
|
class JellyfinMediaItem(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -2,18 +2,24 @@ from discord.models import DiscordRPCUpdatePayload
|
|||||||
from jellyfin.models import JellyfinMediaItem, JellyfinMediaType
|
from jellyfin.models import JellyfinMediaItem, JellyfinMediaType
|
||||||
from pypresence.types import ActivityType
|
from pypresence.types import ActivityType
|
||||||
|
|
||||||
|
|
||||||
def to_rpc_payload(media_item: JellyfinMediaItem) -> DiscordRPCUpdatePayload:
|
def to_rpc_payload(media_item: JellyfinMediaItem) -> DiscordRPCUpdatePayload:
|
||||||
if media_item.type == JellyfinMediaType.AUDIO:
|
if media_item.type == JellyfinMediaType.AUDIO:
|
||||||
return DiscordRPCUpdatePayload(
|
return DiscordRPCUpdatePayload(
|
||||||
id=media_item.id,
|
id=media_item.id,
|
||||||
title=f"Listening to {media_item.name}",
|
title=f"Listening to {
|
||||||
subtitle=f"by {media_item.metadata.get('artist', 'Unknown Artist')}",
|
media_item.name}",
|
||||||
|
subtitle=f"by {
|
||||||
|
media_item.metadata.get(
|
||||||
|
'artist',
|
||||||
|
'Unknown Artist')}",
|
||||||
image_url=media_item.image_url,
|
image_url=media_item.image_url,
|
||||||
details=media_item.metadata.get('album', 'Unknown Album'),
|
details=media_item.metadata.get(
|
||||||
|
'album',
|
||||||
|
'Unknown Album'),
|
||||||
activity_type=ActivityType.LISTENING,
|
activity_type=ActivityType.LISTENING,
|
||||||
start=media_item.start,
|
start=media_item.start,
|
||||||
end=media_item.end
|
end=media_item.end)
|
||||||
)
|
|
||||||
elif media_item.type == JellyfinMediaType.MOVIE:
|
elif media_item.type == JellyfinMediaType.MOVIE:
|
||||||
return DiscordRPCUpdatePayload(
|
return DiscordRPCUpdatePayload(
|
||||||
id=media_item.id,
|
id=media_item.id,
|
||||||
|
|||||||
7
main.py
7
main.py
@@ -2,16 +2,18 @@ from discord.rpc import DiscordRPC
|
|||||||
from jellyfin.api_client import JellyfinApiClient
|
from jellyfin.api_client import JellyfinApiClient
|
||||||
from jellyfin.utils import to_rpc_payload
|
from jellyfin.utils import to_rpc_payload
|
||||||
from settings import settings
|
from settings import settings
|
||||||
|
import coloredlogs
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
logging.basicConfig(
|
coloredlogs.install(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
discordRPC = DiscordRPC()
|
discordRPC = DiscordRPC()
|
||||||
jellyfinApiClient = JellyfinApiClient()
|
jellyfinApiClient = JellyfinApiClient()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -28,5 +30,6 @@ def main():
|
|||||||
discordRPC.clear()
|
discordRPC.clear()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ getmac==0.9.5
|
|||||||
pypresence==4.6.1
|
pypresence==4.6.1
|
||||||
pydantic==2.12.5
|
pydantic==2.12.5
|
||||||
pydantic-settings==2.12.0
|
pydantic-settings==2.12.0
|
||||||
|
coloredlogs==15.0.1
|
||||||
|
autopep8==2.3.2
|
||||||
|
|||||||
6
scripts/format.sh
Executable file
6
scripts/format.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
which autopep8 &> /dev/null || { echo "autopep8 not found, please install it."; exit 1; }
|
||||||
|
|
||||||
|
autopep8 --in-place --aggressive --aggressive --recursive --exclude bin,lib,include,venv,.git,__pycache__ .
|
||||||
|
|
||||||
|
echo "Code formatted with autopep8."
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
jellyfin_server_url: str = Field(..., env="JELLYFIN_SERVER_URL")
|
jellyfin_server_url: str = Field(..., env="JELLYFIN_SERVER_URL")
|
||||||
jellyfin_username: str = Field(..., env="JELLYFIN_USERNAME")
|
jellyfin_username: str = Field(..., env="JELLYFIN_USERNAME")
|
||||||
jellyfin_password: str = Field(..., env="JELLYFIN_PASSWORD")
|
jellyfin_password: str = Field(..., env="JELLYFIN_PASSWORD")
|
||||||
jellyfin_auth_timeout: int = Field(10 * 60, env="JELLYFIN_AUTH_TIMEOUT") # default 10 minutes
|
jellyfin_auth_timeout: int = Field(
|
||||||
|
10 * 60, env="JELLYFIN_AUTH_TIMEOUT") # default 10 minutes
|
||||||
|
|
||||||
discord_app_id: str = Field(..., env="DISCORD_APP_ID")
|
discord_app_id: str = Field(..., env="DISCORD_APP_ID")
|
||||||
|
|
||||||
poll_interval: int = Field(15, env="POLL_INTERVAL") # default 15 seconds
|
poll_interval: int = Field(15, env="POLL_INTERVAL") # default 15 seconds
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env", env_file_encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
Reference in New Issue
Block a user