- add support for different activity types

- switch from process naming matching to app user model id matching on windows
- updated example settings file
- added exception handling to itunes api
This commit is contained in:
EinTim23 2025-05-10 12:27:15 +02:00
parent cd5656de63
commit 90607d2f5d
4 changed files with 47 additions and 70 deletions

View File

@ -5,13 +5,34 @@
"client_id": "1245257414715113573", "client_id": "1245257414715113573",
"enabled": true, "enabled": true,
"name": "Spotify", "name": "Spotify",
"type": 2,
"process_names": [ "process_names": [
"org.mpris.MediaPlayer2.spotify", "org.mpris.MediaPlayer2.spotify",
"com.spotify.client", "com.spotify.client",
"Spotify.exe" "Spotify.exe"
], ],
"search_endpoint": "https://open.spotify.com/search/" "search_endpoint": "https://open.spotify.com/search/"
},
{
"client_id": "1337188104829665340",
"enabled": true,
"name": "Apple Music",
"type": 2,
"process_names": [
"com.apple.Music",
"AppleInc.AppleMusicWin_nzyj5cx40ttqa!APP",
"AppleMusic.exe"
],
"search_endpoint": "https://open.spotify.com/search/"
} }
], ],
"lastfm": {
"api_key": "",
"api_secret": "",
"enabled": false,
"password": "",
"username": ""
},
"odesli": true,
"autostart": false "autostart": false
} }

View File

@ -35,59 +35,6 @@ std::string toStdString(winrt::hstring& in) {
return result; return result;
} }
std::string getAppModelIdOfProcess(HANDLE hProc) {
UINT32 length = 0;
LONG rc = GetApplicationUserModelId(hProc, &length, NULL);
if (rc != ERROR_INSUFFICIENT_BUFFER)
return "";
PWSTR fullName = (PWSTR)malloc(length * sizeof(*fullName));
if (!fullName)
return "";
rc = GetApplicationUserModelId(hProc, &length, fullName);
if (rc != ERROR_SUCCESS) {
free(fullName);
return "";
}
winrt::hstring wideName = fullName;
std::string name = toStdString(wideName);
free(fullName);
return name;
}
std::string getProcessNameFromAppModelId(std::string appModelId) {
DWORD processes[1024];
DWORD cbNeeded;
if (!EnumProcesses(processes, sizeof(processes), &cbNeeded))
return "";
unsigned int processCount = cbNeeded / sizeof(DWORD);
for (DWORD i = 0; i < processCount; i++) {
DWORD processID = processes[i];
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processID);
if (hProcess) {
std::string modelid = getAppModelIdOfProcess(hProcess);
if (modelid != appModelId) {
CloseHandle(hProcess);
continue;
}
char exeName[MAX_PATH]{};
DWORD size = MAX_PATH;
QueryFullProcessImageNameA(hProcess, 0, exeName, &size);
std::filesystem::path exePath = exeName;
CloseHandle(hProcess);
return exePath.filename().string();
}
}
return "";
}
bool CreateShortcut(std::string source, std::string target) { bool CreateShortcut(std::string source, std::string target) {
CoInitialize(nullptr); CoInitialize(nullptr);
WCHAR src[MAX_PATH]; WCHAR src[MAX_PATH];
@ -185,13 +132,6 @@ std::shared_ptr<MediaInfo> backend::getMediaInformation() {
std::string modelId = toStdString(currentSession.SourceAppUserModelId()); std::string modelId = toStdString(currentSession.SourceAppUserModelId());
// I do know that this is disgusting, but for some reason microsoft decided to switch out the exe name with the
// ApplicationUserModelId in some version of windows 11. So we check if it's an exe name, if not we are on some
// newer windows version and need to get the exe name from the model id. We cannot directly work with the model id
// because it's unique per machine and therefore would mess up configs with preconfigured apps.
if (modelId.find(".exe") == std::string::npos)
modelId = getProcessNameFromAppModelId(modelId);
return std::make_shared<MediaInfo>( return std::make_shared<MediaInfo>(
playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused, playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused,
toStdString(mediaProperties.Title()), std::move(artist), std::move(albumName), std::move(modelId), toStdString(mediaProperties.Title()), std::move(artist), std::move(albumName), std::move(modelId),

View File

@ -110,7 +110,7 @@ void handleMediaTasks() {
std::string activityState = "by " + mediaInformation->songArtist; std::string activityState = "by " + mediaInformation->songArtist;
DiscordRichPresence activity{}; DiscordRichPresence activity{};
activity.type = ActivityType::LISTENING; activity.type = app.type;
activity.details = mediaInformation->songTitle.c_str(); activity.details = mediaInformation->songTitle.c_str();
activity.state = activityState.c_str(); activity.state = activityState.c_str();
activity.smallImageText = serviceName.c_str(); activity.smallImageText = serviceName.c_str();

View File

@ -21,6 +21,7 @@
namespace utils { namespace utils {
struct App { struct App {
bool enabled; bool enabled;
int type;
std::string appName; std::string appName;
std::string clientId; std::string clientId;
std::string searchEndpoint; std::string searchEndpoint;
@ -68,6 +69,15 @@ namespace utils {
return wxNullIcon; return wxNullIcon;
} }
inline std::string toLower(const std::string& str) {
std::string lowerStr = str;
std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(),
[](unsigned char c) { return std::tolower(c); });
return lowerStr;
}
inline bool caseInsensitiveMatch(const std::string& a, const std::string& b) { return toLower(a) == toLower(b); }
inline std::string ltrim(std::string& s) { inline std::string ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
return s; return s;
@ -144,6 +154,7 @@ namespace utils {
SongInfo ret{}; SongInfo ret{};
std::string response = std::string response =
httpRequest("https://itunes.apple.com/search?media=music&entity=song&term=" + urlEncode(query)); httpRequest("https://itunes.apple.com/search?media=music&entity=song&term=" + urlEncode(query));
try {
nlohmann::json j = nlohmann::json::parse(response); nlohmann::json j = nlohmann::json::parse(response);
auto results = j["results"]; auto results = j["results"];
if (results.size() > 0) { if (results.size() > 0) {
@ -151,6 +162,9 @@ namespace utils {
ret.trackId = results[0]["trackId"].get<int64_t>(); ret.trackId = results[0]["trackId"].get<int64_t>();
} }
return ret; return ret;
} catch (...) {
return ret;
}
} }
inline std::string getOdesliURL(SongInfo& song) { inline std::string getOdesliURL(SongInfo& song) {
@ -224,7 +238,7 @@ namespace utils {
appJson["client_id"] = app.clientId; appJson["client_id"] = app.clientId;
appJson["search_endpoint"] = app.searchEndpoint; appJson["search_endpoint"] = app.searchEndpoint;
appJson["enabled"] = app.enabled; appJson["enabled"] = app.enabled;
appJson["type"] = app.type;
for (const auto& processName : app.processNames) appJson["process_names"].push_back(processName); for (const auto& processName : app.processNames) appJson["process_names"].push_back(processName);
j["apps"].push_back(appJson); j["apps"].push_back(appJson);
@ -269,6 +283,7 @@ namespace utils {
a.clientId = app.value("client_id", ""); a.clientId = app.value("client_id", "");
a.searchEndpoint = app.value("search_endpoint", ""); a.searchEndpoint = app.value("search_endpoint", "");
a.enabled = app.value("enabled", false); a.enabled = app.value("enabled", false);
a.type = app.value("type", 2);
for (const auto& process : app["process_names"]) a.processNames.push_back(process.get<std::string>()); for (const auto& process : app["process_names"]) a.processNames.push_back(process.get<std::string>());
@ -283,7 +298,7 @@ namespace utils {
auto settings = getSettings(); auto settings = getSettings();
for (auto app : settings.apps) { for (auto app : settings.apps) {
for (auto procName : app.processNames) { for (auto procName : app.processNames) {
if (procName == processName) if (caseInsensitiveMatch(procName, processName))
return app; return app;
} }
} }
@ -291,6 +306,7 @@ namespace utils {
a.clientId = DEFAULT_CLIENT_ID; a.clientId = DEFAULT_CLIENT_ID;
a.appName = DEFAULT_APP_NAME; a.appName = DEFAULT_APP_NAME;
a.enabled = settings.anyOtherEnabled; a.enabled = settings.anyOtherEnabled;
a.type = 2; // Default to listening
a.searchEndpoint = ""; a.searchEndpoint = "";
return a; return a;
} }