mpdas-0.4.5/0000755000175000017500000000000013202046101012443 5ustar dogslegdogslegmpdas-0.4.5/mpd.h0000644000175000017500000000211213202046101013370 0ustar dogslegdogsleg#ifndef _MPD_H #define _MPD_H class Song { public: Song() {}; Song(struct mpd_song *song); Song(std::string artist, std::string title, std::string album, int duration) { this->artist = artist; this->title = title; this->album = album; this->duration = duration; } std::string getArtist() const { return artist; } std::string getTitle() const { return title; } std::string getAlbum() const { return album; } std::string getAlbumArtist() const { return albumartist; } int getDuration() const { return duration; } private: std::string albumartist, artist, title, album; int duration; }; class CMPD { public: CMPD(CConfig *cfg); ~CMPD(); bool Connect(); void Update(); void SetSong(const Song *song); void CheckSubmit(int curplaytime); Song GetSong() const { return _song; }; inline bool isConnected() { return _connected; } private: void GotNewSong(struct mpd_song *song); CConfig *_cfg; mpd_connection *_conn; int _songid; int _songpos; Song _song; bool _gotsong; int _start; time_t _starttime; bool _connected; bool _cached; }; extern CMPD* MPD; #endif mpdas-0.4.5/README0000644000175000017500000000345213202046101013327 0ustar dogslegdogsleg================================================ _ _ __ ___ _ __ __| | __ _ ___ | '_ ` _ \| '_ \ / _` |/ _` / __| | | | | | | |_) | (_| | (_| \__ \ |_| |_| |_| .__/ \__,_|\__,_|___/ |_| ================================================ mpdas is a MPD AudioScrobbler client supporting the 2.0 protocol specs. It is written in C++ and uses libmpdclient to retrieve the song data from MPD and libcurl to post it to Last.fm ================================================ NOTE: There is a manpage available as well: man mpdas To configure mpdas, simply create a file called .mpdasrc in your home directory or in $PREFIX/mpdasrc. To change $PREFIX, modify the Makefile. Syntax is easy. Example: username = lastfmuser password = password NOTE: In the past the password had to be an MD5 hash. As of v0.4.3 this has changed due to Last.fm deprecating the previous authentication method. Please supply the password in plain-text from now on. Configuration options: username: Last.FM username password: Last.FM password (plain-text) host: MPD Host mpdpassword: MPD Password port: MPD Port runas: Change the user mpdas runs as debug: Print debug information service: Will scrobble to Libre.fm if set to "librefm" ================================================ Features: - Sets now-playing status - Scrobbling (obviously) - Caching - Config files - User switching - "Love" tracks on Last.fm (e.g. with 'mpc sendmessage mpdas love' and 'mpc sendmessage mpdas unlove' to revert) ================================================ Also checkout LICENSE ================================================ Contact: IRC: hrkfdn / irc.freenode.net Jabber: henrik@affekt.org WWW: http://www.50hz.ws ================================================ mpdas-0.4.5/main.cpp0000644000175000017500000000575513202046101014107 0ustar dogslegdogsleg#include "mpdas.h" bool running = true; void got_signal(int) { running = false; } void onclose() { iprintf("%s", "Closing mpdas."); if(MPD) delete MPD; if(AudioScrobbler) delete AudioScrobbler; if(Cache) delete Cache; } void setid(const char* username) { passwd* userinfo = 0; if(strlen(username) == 0) return; if(getuid() != 0) { eprintf("%s", "You are not root. Not changing user .."); return; } userinfo = getpwnam(username); if(!userinfo) { eprintf("%s", "The user you specified does not exist."); exit(EXIT_FAILURE); } if(setgid(userinfo->pw_gid) == -1 || setuid(userinfo->pw_uid) == -1) { eprintf("%s %s", "Could not switch to user", username); exit(EXIT_FAILURE); } setenv("HOME", userinfo->pw_dir, 1); } void printversion() { fprintf(stdout, "mpdas-" VERSION", (C) 2010-2017 Henrik Friedrichsen.\n"); fprintf(stdout, "Global config path is set to \"%s/mpdasrc\"\n", CONFDIR); } void printhelp() { fprintf(stderr, "\nusage: mpdas [-h] [-v] [-c config]\n"); fprintf(stderr, "\n\th: print this help"); fprintf(stderr, "\n\tv: print program version"); fprintf(stderr, "\n\tc: load specified config file"); fprintf(stderr, "\n"); } int main(int argc, char* argv[]) { int i; char* config = 0; bool go_daemon = false; if(argc >= 2) { for(i = 1; i <= argc-1; i++) { if(strstr(argv[i], "-h") == argv[i]) { printversion(); printhelp(); return EXIT_SUCCESS; } if(strstr(argv[i], "-v") == argv[i]) { printversion(); return EXIT_SUCCESS; } else if(strstr(argv[i], "-c") == argv[i]) { if(i >= argc-1) { fprintf(stderr, "mpdas: config path missing!\n"); printhelp(); return EXIT_FAILURE; } config = argv[i+1]; } else if(strstr(argv[i], "-d") == argv[i]) { go_daemon = true; } } } atexit(onclose); CConfig *cfg = new CConfig(config); setid(cfg->Get("runas").c_str()); // Load config in home dir as well (if possible) if(config == 0) { std::string home = getenv("HOME"); std::string xdgconfig = home + "/.config"; if(getenv("XDG_CONFIG_HOME")) { xdgconfig = std::string(getenv("XDG_CONFIG_HOME")); } std::string path = home + "/.mpdasrc"; std::string xdgpath = xdgconfig + "/mpdasrc"; cfg->LoadConfig(xdgpath); cfg->LoadConfig(path); } if(!cfg->gotNecessaryData()) { eprintf("%s", "AudioScrobbler username or password not set."); return EXIT_FAILURE; } iprintf("Using %s service URL", cfg->getService() == LastFm ? "Last.fm" : "Libre.fm"); if (go_daemon) { if (daemon(1, 0)) { perror("daemon"); return EXIT_FAILURE; } } MPD = new CMPD(cfg); if(!MPD->isConnected()) return EXIT_FAILURE; AudioScrobbler = new CAudioScrobbler(cfg); AudioScrobbler->Handshake(); Cache = new CCache(); Cache->LoadCache(); // catch sigint struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = got_signal; sigfillset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL); while(running) { MPD->Update(); Cache->WorkCache(); usleep(500000); } return EXIT_SUCCESS; } mpdas-0.4.5/config.h0000644000175000017500000000117313202046101014063 0ustar dogslegdogsleg#ifndef _CONFIG_H #define _CONFIG_H enum ScrobblingService { LastFm, LibreFm }; class CConfig { public: CConfig(char* cfg); ScrobblingService getService(); std::string Get(std::string name); bool GetBool(std::string name); int GetInt(std::string name); void Set(std::string name, std::string value) { _configuration[name] = value; }; bool gotNecessaryData() { if(!Get("username").size() || !Get("password").size()) return false; return true; } void LoadConfig(std::string path); private: void ParseLine(std::string line); std::map _configuration; }; extern CConfig* Config; #endif mpdas-0.4.5/md5.h0000644000175000017500000000650313202046101013305 0ustar dogslegdogsleg/* Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.h,v 1.1 2003/08/24 01:20:37 audhov Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.h is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Removed support for non-ANSI compilers; removed references to Ghostscript; clarified derivation from RFC 1321; now handles byte order either statically or dynamically. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); added conditionalization for C++ compilation from Martin Purschke . 1999-05-03 lpd Original version. */ #ifndef md5_INCLUDED # define md5_INCLUDED /* * This package supports both compile-time and run-time determination of CPU * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is * defined as non-zero, the code will be compiled to run only on big-endian * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to * run on either big- or little-endian CPUs, but will run slightly less * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. */ typedef unsigned char md5_byte_t; /* 8-bit byte */ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; #ifdef __cplusplus extern "C" { #endif /* Initialize the algorithm. */ void md5_init(md5_state_t *pms); /* Append a string to the message. */ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); #ifdef __cplusplus } /* end extern "C" */ #endif #endif /* md5_INCLUDED */ mpdas-0.4.5/LICENSE0000644000175000017500000000273013202046101013452 0ustar dogslegdogsleg* Copyright (c) <2008>, * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of the company nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mpdas-0.4.5/md5.cpp0000644000175000017500000003022513202046101013636 0ustar dogslegdogsleg/* Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.c,v 1.1 2003/08/24 01:20:37 audhov Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.c is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order either statically or dynamically; added missing #include in library. 2002-03-11 lpd Corrected argument list for main(), and added int return type, in test program and T value program. 2002-02-21 lpd Added missing #include in test program. 2000-07-03 lpd Patched to eliminate warnings about "constant is unsigned in ANSI C, signed in traditional"; made test program self-checking. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). 1999-05-03 lpd Original version. */ #include "md5.h" #include #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else # define BYTE_ORDER 0 #endif #define T_MASK ((md5_word_t)~0) #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) #define T3 0x242070db #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) #define T6 0x4787c62a #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) #define T9 0x698098d8 #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) #define T13 0x6b901122 #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) #define T16 0x49b40821 #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) #define T19 0x265e5a51 #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) #define T22 0x02441453 #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) #define T25 0x21e1cde6 #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) #define T28 0x455a14ed #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) #define T31 0x676f02d9 #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) #define T35 0x6d9d6122 #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) #define T38 0x4bdecfa9 #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) #define T41 0x289b7ec6 #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) #define T44 0x04881d05 #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) #define T47 0x1fa27cf8 #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) #define T50 0x432aff97 #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) #define T53 0x655b59c3 #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) #define T57 0x6fa87e4f #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) #define T60 0x4e0811a1 #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) #define T63 0x2ad7d2bb #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) { md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ md5_word_t xbuf[16]; const md5_word_t *X; #endif { #if BYTE_ORDER == 0 /* * Determine dynamically whether this is a big-endian or * little-endian machine, since we can use a more efficient * algorithm on the latter. */ static const int w = 1; if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ #endif #if BYTE_ORDER <= 0 /* little-endian */ { /* * On little-endian machines, we can process properly aligned * data without copying it. */ if (!((data - (const md5_byte_t *)0) & 3)) { /* data are properly aligned */ X = (const md5_word_t *)data; } else { /* not aligned */ memcpy(xbuf, data, 64); X = xbuf; } } #endif #if BYTE_ORDER == 0 else /* dynamic big-endian */ #endif #if BYTE_ORDER >= 0 /* big-endian */ { /* * On big-endian machines, we must arrange the bytes in the * right order. */ const md5_byte_t *xp = data; int i; # if BYTE_ORDER == 0 X = xbuf; /* (dynamic only) */ # else # define xbuf X /* (static only) */ # endif for (i = 0; i < 16; ++i, xp += 4) xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); } #endif } #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) /* Round 1. */ /* Let [abcd k s i] denote the operation a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + F(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 7, T1); SET(d, a, b, c, 1, 12, T2); SET(c, d, a, b, 2, 17, T3); SET(b, c, d, a, 3, 22, T4); SET(a, b, c, d, 4, 7, T5); SET(d, a, b, c, 5, 12, T6); SET(c, d, a, b, 6, 17, T7); SET(b, c, d, a, 7, 22, T8); SET(a, b, c, d, 8, 7, T9); SET(d, a, b, c, 9, 12, T10); SET(c, d, a, b, 10, 17, T11); SET(b, c, d, a, 11, 22, T12); SET(a, b, c, d, 12, 7, T13); SET(d, a, b, c, 13, 12, T14); SET(c, d, a, b, 14, 17, T15); SET(b, c, d, a, 15, 22, T16); #undef SET /* Round 2. */ /* Let [abcd k s i] denote the operation a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + G(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 1, 5, T17); SET(d, a, b, c, 6, 9, T18); SET(c, d, a, b, 11, 14, T19); SET(b, c, d, a, 0, 20, T20); SET(a, b, c, d, 5, 5, T21); SET(d, a, b, c, 10, 9, T22); SET(c, d, a, b, 15, 14, T23); SET(b, c, d, a, 4, 20, T24); SET(a, b, c, d, 9, 5, T25); SET(d, a, b, c, 14, 9, T26); SET(c, d, a, b, 3, 14, T27); SET(b, c, d, a, 8, 20, T28); SET(a, b, c, d, 13, 5, T29); SET(d, a, b, c, 2, 9, T30); SET(c, d, a, b, 7, 14, T31); SET(b, c, d, a, 12, 20, T32); #undef SET /* Round 3. */ /* Let [abcd k s t] denote the operation a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define H(x, y, z) ((x) ^ (y) ^ (z)) #define SET(a, b, c, d, k, s, Ti)\ t = a + H(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 5, 4, T33); SET(d, a, b, c, 8, 11, T34); SET(c, d, a, b, 11, 16, T35); SET(b, c, d, a, 14, 23, T36); SET(a, b, c, d, 1, 4, T37); SET(d, a, b, c, 4, 11, T38); SET(c, d, a, b, 7, 16, T39); SET(b, c, d, a, 10, 23, T40); SET(a, b, c, d, 13, 4, T41); SET(d, a, b, c, 0, 11, T42); SET(c, d, a, b, 3, 16, T43); SET(b, c, d, a, 6, 23, T44); SET(a, b, c, d, 9, 4, T45); SET(d, a, b, c, 12, 11, T46); SET(c, d, a, b, 15, 16, T47); SET(b, c, d, a, 2, 23, T48); #undef SET /* Round 4. */ /* Let [abcd k s t] denote the operation a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define I(x, y, z) ((y) ^ ((x) | ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + I(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 6, T49); SET(d, a, b, c, 7, 10, T50); SET(c, d, a, b, 14, 15, T51); SET(b, c, d, a, 5, 21, T52); SET(a, b, c, d, 12, 6, T53); SET(d, a, b, c, 3, 10, T54); SET(c, d, a, b, 10, 15, T55); SET(b, c, d, a, 1, 21, T56); SET(a, b, c, d, 8, 6, T57); SET(d, a, b, c, 15, 10, T58); SET(c, d, a, b, 6, 15, T59); SET(b, c, d, a, 13, 21, T60); SET(a, b, c, d, 4, 6, T61); SET(d, a, b, c, 11, 10, T62); SET(c, d, a, b, 2, 15, T63); SET(b, c, d, a, 9, 21, T64); #undef SET /* Then perform the following additions. (That is increment each of the four registers by the value it had before this block was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } void md5_init(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; pms->abcd[3] = 0x10325476; } void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) { const md5_byte_t *p = data; int left = nbytes; int offset = (pms->count[0] >> 3) & 63; md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) return; /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) pms->count[1]++; /* Process an initial partial block. */ if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) return; p += copy; left -= copy; md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) md5_process(pms, p); /* Process a final partial block. */ if (left) memcpy(pms->buf, p, left); } void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static const md5_byte_t pad[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } mpdas-0.4.5/utils.h0000644000175000017500000000032713202046101013756 0ustar dogslegdogsleg#ifndef _UTILS_H #define _UTILS_H bool fileexists(const char* file); void iprintf(const char* fmt, ...); void eprintf(const char* fmt, ...); std::string md5sum(const char* fmt, ...); std::string timestr(); #endif mpdas-0.4.5/TODO0000644000175000017500000000023513202046101013133 0ustar dogslegdogsleg- Rewrite. Maybe even in C? - Write test cases for cache, as it seems to contain duplicates in some cases.. - Pass instances around instead of using globals mpdas-0.4.5/mpdas.h0000644000175000017500000000112513202046101013717 0ustar dogslegdogsleg#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "md5.h" #include "ini.h" #include "config.h" #include "utils.h" #include "mpd.h" #include "cache.h" #include "audioscrobbler.h" mpdas-0.4.5/mpdas.10000644000175000017500000000254313202046101013635 0ustar dogslegdogsleg.\" mpdas.1 .TH mpdas 1 .SH "NAME" mpdas \- Music Player Daemon AudioScrobbler .SH "SYNOPSIS" .B mpdas [options] .SH "DESCRIPTION" mpdas is a MPD AudioScrobbler client using libmpd to retrieve the song data from MPD and libcurl to post it to Last.fm. .SH "OPTIONS" .TP .B \-c CONFIGFILE Read configuration from file. .TP .B \-d Run detached from the controlling terminal. .TP .B \-h Display help. .TP .B \-v Display version. .SH "CONFIGURATION" When mpdas starts it tries to read the configuration file $SYSCONFIGDIR/mpdasrc first, then the user configuration file at ~/.config/mpdasrc (taken from $XDG_CONFIG_HOME if set) or ~/.mpdasrc. .TP An example configuration file (mpdasrc.example) is usually provided with mpdas. .TP .B username = LASTFM USERNAME Set your Last.fm username. .TP .B password = LASTFM PASSWORD Set your Last.fm password .TP .B host = HOSTNAME Set the hostname used by MPD. .TP .B mpdpassword = MPD PASSWORD Set the password used by MPD. .TP .B port = MPD PORT Set the port used by MPD. .TP .B runas = USERNAME Changes the user mpdas runs as. .TP .B debug = (0|1) Print out debug information. .TP .B service = SERVICE NAME Will scrobble to Libre.fm if set to "librefm". Otherwise Last.fm is used (default). .SH "AUTHORS" \fBmpdas\fR was written by Henrik Friedrichsen . .PP The \fBmanpage\fR was written by Sebastian Mair . mpdas-0.4.5/cache.h0000644000175000017500000000140713202046101013661 0ustar dogslegdogsleg#ifndef _CACHE_H #define _CACHE_H class CacheEntry { public: CacheEntry() {}; CacheEntry(const Song& song, time_t starttime) { this->song = song; this->starttime = starttime; } // stream serialization friend std::ofstream& operator <<(std::ofstream& outstream, const CacheEntry& inobj); friend std::ifstream& operator >>(std::ifstream& instream, CacheEntry& outobj); Song getSong() const { return song; } time_t getStartTime() const { return starttime; } private: Song song; time_t starttime; }; class CCache { public: CCache() { _failtime = 0; } void AddToCache(const Song& song, time_t starttime); void WorkCache(); void SaveCache(); void LoadCache(); private: time_t _failtime; std::vector _entries; }; extern CCache* Cache; #endif mpdas-0.4.5/INSTALL.md0000644000175000017500000000071113202046101014072 0ustar dogslegdogsleg## Requirements The following packages are required in order to compile mpdas: * libcurl, this is generally provided by one of the following (depending which "flavour" you favour): * libcurl4-gnutls-dev (GnuTLS flavour) * libcurl4-nss-dev (NSS flavour) * libcurl4-openssl-dev (OpenSSL flavour) * libmpdclient-dev ## Building and Installing mpdas uses `make` for building so this is generally as simple as make sudo make install mpdas-0.4.5/utils.cpp0000644000175000017500000000375513202046101014321 0ustar dogslegdogsleg#include "mpdas.h" #define RED "\x1b[31;01m" #define RESET "\x1b[0m" #define GREEN "\x1b[32;01m" #define YELLOW "\x1b[33;01m" bool fileexists(const char* file) { if (access(file, F_OK) == 0) return true; else return false; } void error(std::string msg) { std::cerr << "(" << timestr() << ")" << "[" << RED << "ERROR" << RESET << "]" << msg << std::endl; } std::string md5sum(const char* fmt, ...) { va_list ap; char* abuf; md5_state_t state; unsigned char md5buf[16]; va_start(ap, fmt); if(vasprintf(&abuf, fmt, ap) == -1) { error("in md5sum: Could not allocate memory for vasprintf()"); va_end(ap); exit(EXIT_FAILURE); } va_end(ap); std::string buf(abuf); free(abuf); md5_init(&state); md5_append(&state, (const md5_byte_t*)buf.c_str(), buf.length()); md5_finish(&state, md5buf); std::ostringstream ret; for(unsigned int i = 0; i < 16; i++) ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)md5buf[i]; return ret.str(); } std::string timestr() { std::stringstream buf; time_t rawtime = time(NULL); struct tm* t = localtime(&rawtime); if(t->tm_hour < 10) buf << 0; buf << t->tm_hour << ":"; if(t->tm_min < 10) buf << "0"; buf << t->tm_min << ":"; if(t->tm_sec < 10) buf << "0"; buf << t->tm_sec; return buf.str(); } void eprintf(const char* fmt, ...) { char* abuf; va_list ap; va_start(ap, fmt); if(vasprintf(&abuf, fmt, ap) == -1) { error("in eprintf: Could not allocate memory for vasprintf()"); va_end(ap); return; } va_end(ap); std::string buf(abuf); free(abuf); std::cerr << "(" << timestr() << ") [" << RED << "ERROR" << RESET << "] " << buf << std::endl; } void iprintf(const char* fmt, ...) { char* abuf; va_list ap; va_start(ap, fmt); if(vasprintf(&abuf, fmt, ap) == -1) { error("in iprintf: could not allocate memory for vasprintf()"); va_end(ap); return; } va_end(ap); std::string buf(abuf); free(abuf); std::cout << "(" << timestr() << ") [" << YELLOW << "INFO" << RESET << "] " << buf << std::endl; } mpdas-0.4.5/cache.cpp0000644000175000017500000000427213202046101014217 0ustar dogslegdogsleg#include "mpdas.h" CCache* Cache = 0; void CCache::SaveCache() { std::string path = getenv("HOME"); path.append("/.mpdascache"); remove(path.c_str()); std::ofstream ofs(path.c_str()); if(!_entries.size()) { remove(path.c_str()); return; } for(unsigned int i = 0; i < _entries.size(); i++) { CacheEntry* entry = _entries[i]; ofs << *entry; if(i+1 == _entries.size()) ofs.flush(); else ofs << std::endl; } ofs.close(); } void CCache::LoadCache() { int length; std::string path = getenv("HOME"); path.append("/.mpdascache"); std::ifstream ifs(path.c_str(), std::ios::in|std::ios::binary); ifs.seekg (0, std::ios::end); length = ifs.tellg(); ifs.seekg (0, std::ios::beg); while(ifs.good()) { if(length == ifs.tellg()) break; CacheEntry *entry = new CacheEntry(); ifs >> *entry; _entries.push_back(entry); } ifs.close(); remove(path.c_str()); } void CCache::WorkCache() { if(_failtime && time(NULL) - _failtime < 300) { return; } _failtime = 0; while(_entries.size()) { if(AudioScrobbler->Scrobble(*_entries.front())) { delete _entries.front(); _entries.erase(_entries.begin()); } else { eprintf("%s", "Error scrobbling. Trying again in 5 minutes."); _failtime = time(NULL); AudioScrobbler->Failure(); break; } sleep(1); } SaveCache(); } void CCache::AddToCache(const Song& song, time_t starttime) { CacheEntry *entry = new CacheEntry(song, starttime); _entries.push_back(entry); SaveCache(); } std::ofstream& operator <<(std::ofstream& outstream, const CacheEntry& inobj) { Song song = inobj.getSong(); outstream << song.getArtist() << std::endl << song.getTitle() << std::endl << song.getAlbum() << std::endl << song.getDuration() << std::endl << inobj.getStartTime(); return outstream; } std::ifstream& operator >>(std::ifstream& instream, CacheEntry& outobj) { std::string artist, title, album; int duration; time_t starttime; getline(instream, artist); getline(instream, title); getline(instream, album); instream >> duration; instream.ignore(1); instream >> starttime; instream.ignore(1); Song song(artist, title, album, duration); outobj = CacheEntry(song, starttime); return instream; } mpdas-0.4.5/Makefile0000644000175000017500000000140713202046101014105 0ustar dogslegdogslegVERSION = 0.4.5 CXX ?= g++ OBJ = main.o md5.o utils.o mpd.o audioscrobbler.o cache.o config.o ini.o OUT = mpdas PREFIX ?= /usr/local MANPREFIX ?= ${PREFIX}/man/man1 CONFIG ?= $(PREFIX)/etc CXXFLAGS += `pkg-config --cflags libmpdclient libcurl` LIBS = `pkg-config --libs libmpdclient libcurl` CXXFLAGS += -DCONFDIR="\"$(CONFIG)\"" -DVERSION="\"$(VERSION)\"" all: $(OUT) .cpp.o: $(CXX) $(CXXFLAGS) -c -o $@ $< $(OUT): $(OBJ) $(CXX) $(LDFLAGS) $(OBJ) $(LIBS) -o $(OUT) clean: rm -rf $(OBJ) $(OUT) install: all install -d ${DESTDIR}${PREFIX}/bin install -d ${DESTDIR}${MANPREFIX} install -m 755 mpdas ${DESTDIR}${PREFIX}/bin install -m 644 mpdas.1 ${DESTDIR}${MANPREFIX}/mpdas.1 uninstall: -rm ${DESTDIR}${PREFIX}/bin/mpdas -rm ${DESTDIR}${MANPREFIX}/mpdas.1 mpdas-0.4.5/mpd.cpp0000644000175000017500000001023613202046101013731 0ustar dogslegdogsleg#include "mpdas.h" CMPD* MPD = 0; void CMPD::SetSong(const Song *song) { _cached = false; if(song && !song->getArtist().empty() && !song->getTitle().empty()) { _song = *song; _gotsong = true; iprintf("New song: %s - %s", _song.getArtist().c_str(), _song.getTitle().c_str()); AudioScrobbler->SendNowPlaying(*song); } else { _gotsong = false; } _starttime = time(NULL); } void CMPD::CheckSubmit(int curplaytime) { if(!_gotsong || _cached || (_song.getArtist().empty() || _song.getTitle().empty())) return; if(curplaytime - _start >= 240 || curplaytime - _start >= _song.getDuration()/2) { Cache->AddToCache(_song, _starttime); _cached = true; } } CMPD::CMPD(CConfig *cfg) { _cfg = cfg; _conn = NULL; _gotsong = false; _connected = false; _cached = false; _songid = -1; _songpos = -1; if(Connect()) iprintf("%s", "Connected to MPD."); else eprintf("%s", "Could not connect to MPD."); } CMPD::~CMPD() { if(_conn) mpd_connection_free(_conn); } bool CMPD::Connect() { if(_conn) mpd_connection_free(_conn); _conn = mpd_connection_new(_cfg->Get("host").c_str(), _cfg->GetInt("port"), 0); _connected = _conn && mpd_connection_get_error(_conn) == MPD_ERROR_SUCCESS; if(_connected && _cfg->Get("mpdpassword").size() > 0) { _connected &= mpd_run_password(_conn, _cfg->Get("mpdpassword").c_str()); } else if(!_connected) { eprintf("MPD connection error: %s", mpd_connection_get_error_message(_conn)); } if(_connected) mpd_run_subscribe(_conn, "mpdas"); return _connected; } void CMPD::GotNewSong(struct mpd_song *song) { Song *s = new Song(song); SetSong(s); delete s; } void CMPD::Update() { if(!_connected) { iprintf("Reconnecting in 10 seconds."); sleep(10); if(Connect()) iprintf("%s", "Reconnected!"); else { eprintf("%s", "Could not reconnect."); return; } } mpd_status *status = mpd_run_status(_conn); mpd_stats *stats = mpd_run_stats(_conn); if(status && stats) { int newsongid = mpd_status_get_song_id(status); int newsongpos = mpd_status_get_elapsed_time(status); int curplaytime = mpd_stats_get_play_time(stats); // new song if(newsongid != _songid) { _songid = newsongid; _songpos = newsongpos; _start = curplaytime; mpd_song *song = mpd_run_current_song(_conn); if(song) { GotNewSong(song); mpd_song_free(song); } } // song playing if(newsongpos != _songpos) { _songpos = newsongpos; CheckSubmit(curplaytime); } // check for client-to-client messages if(mpd_send_read_messages(_conn)) { mpd_message *msg; while((msg = mpd_recv_message(_conn)) != NULL) { const char *text = mpd_message_get_text(msg); if(_gotsong && text) { if(!strncmp(text, "love", 4)) { AudioScrobbler->LoveTrack(_song); } else if(!strncmp(text, "unlove", 6)) { AudioScrobbler->LoveTrack(_song, true); } } mpd_message_free(msg); } mpd_response_finish(_conn); } mpd_status_free(status); mpd_stats_free(stats); } else { // we have most likely lost our connection eprintf("Could not query MPD server: %s", mpd_connection_get_error_message(_conn)); _connected = false; } } Song::Song(struct mpd_song *song) { const char* temp; temp = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); artist = temp ? temp : ""; temp = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); title = temp ? temp : ""; temp = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); album = temp ? temp : ""; temp = mpd_song_get_tag(song, MPD_TAG_ALBUM_ARTIST, 0); albumartist = temp ? temp : ""; duration = mpd_song_get_duration(song); } mpdas-0.4.5/audioscrobbler.h0000644000175000017500000000230113202046101015607 0ustar dogslegdogsleg#ifndef _AUDIOSCROBBLER_H #define _AUDIOSCROBBLER_H class CAudioScrobbler { public: CAudioScrobbler(CConfig *cfg); ~CAudioScrobbler(); void Handshake(); std::string CreateScrobbleMessage(int index, const CacheEntry& entry); bool Scrobble(const CacheEntry& entry); void ReportResponse(char* buf, size_t size); bool LoveTrack(const Song& song, bool unlove = false); bool SendNowPlaying(const Song& song); void Failure(); private: std::string GetServiceURL(); void OpenURL(std::string url, const char* postfields, char* errbuf); bool CheckFailure(std::string response); CConfig *_cfg; CURL* _handle; std::string _password; std::string _response; std::string _sessionid; bool _authed; int _failcount; }; class CLastFMMessage { public: CLastFMMessage(CURL *curl_handle) { this->curl_handle = curl_handle; } void AddField(std::string name, std::string value) { valueMap[name] = value; } void AddField(std::string name, int value) { std::ostringstream str; str << value; AddField(name, str.str()); } std::string GetMessage(); private: std::map valueMap; std::string GetSignatureHash(); CURL *curl_handle; }; extern CAudioScrobbler* AudioScrobbler; #endif mpdas-0.4.5/config.cpp0000644000175000017500000000264613202046101014424 0ustar dogslegdogsleg#include "mpdas.h" int IniHandler(void* param, const char* section, const char* name, const char* value) { CConfig* config = (CConfig*)param; std::string val = std::string(value); // strip quotes if they exist to allow passwords to begin with a whitespace if(val.length() >= 2 && val[0] == '\"' && val[val.length()-1] == '\"') { val.erase(0, 1); val.erase(val.length() - 1); } config->Set(name, val); return 1; } void CConfig::LoadConfig(std::string path) { if(ini_parse(path.c_str(), &IniHandler, this) < 0) { iprintf("Cannot parse config file (%s).", path.c_str()); return; } } std::string CConfig::Get(std::string name) { if(_configuration.find(name) == _configuration.end()) { return ""; } return _configuration.find(name)->second; } bool CConfig::GetBool(std::string name) { std::string value = Get(name); return value == "1" || value == "true"; } int CConfig::GetInt(std::string name) { return atoi(Get(name).c_str()); } ScrobblingService CConfig::getService() { return Get("service") == "librefm" ? LibreFm : LastFm; } CConfig::CConfig(char* cfg) { /* Set optional settings to default */ Set("host", "localhost"); Set("port", "6600"); Set("debug", "false"); Set("service", "lastfm"); std::string path = ""; if(!cfg) { path = CONFDIR; path.append("/mpdasrc"); } else { path = cfg; } LoadConfig(path); } mpdas-0.4.5/audioscrobbler.cpp0000644000175000017500000002111413202046101016145 0ustar dogslegdogsleg#include "mpdas.h" #define APIKEY "a0ed2629d3d28606f67d7214c916788d" #define SECRET "295f31c5d28215215b1503fb0327cc01" #define CURL_MAX_RETRIES 3 #define CURL_RETRY_DELAY 3 // Seconds CAudioScrobbler* AudioScrobbler = 0; #define CLEANUP() _response.clear() size_t writecb(void* ptr, size_t size, size_t nmemb, void *stream) { AudioScrobbler->ReportResponse((char*)ptr, size*nmemb); return size*nmemb; } CAudioScrobbler::CAudioScrobbler(CConfig *cfg) { _cfg = cfg; _failcount = 0; _authed = false; _response = ""; _handle = curl_easy_init(); if(!_handle) { eprintf("%s", "Could not initialize CURL."); exit(EXIT_FAILURE); } } CAudioScrobbler::~CAudioScrobbler() { curl_easy_cleanup(_handle); curl_global_cleanup(); } std::string CAudioScrobbler::GetServiceURL() { if(_cfg->getService() == LibreFm) { return "https://libre.fm/2.0/"; } return "https://ws.audioscrobbler.com/2.0/"; } void CAudioScrobbler::OpenURL(std::string url, const char* postfields = 0, char* errbuf = 0) { curl_easy_setopt(_handle, CURLOPT_DNS_CACHE_TIMEOUT, 0); curl_easy_setopt(_handle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(_handle, CURLOPT_WRITEFUNCTION, writecb); curl_easy_setopt(_handle, CURLOPT_TIMEOUT, 10); if(postfields) { curl_easy_setopt(_handle, CURLOPT_POST, 1); curl_easy_setopt(_handle, CURLOPT_POSTFIELDS, postfields); } else curl_easy_setopt(_handle, CURLOPT_POST, 0); if(errbuf) curl_easy_setopt(_handle, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(_handle, CURLOPT_URL, url.c_str()); CURLcode res = curl_easy_perform(_handle); // Sometimes last.fm likes to just timeout for no reason, leaving us hanging. // If this happens, retry a few times with a small delay. if (res != CURLE_OK) { eprintf("libcurl error (%d): %s", res, curl_easy_strerror(res)); eprintf("Will retry %d times with a %d second delay.", CURL_MAX_RETRIES, CURL_RETRY_DELAY); int retries = 0; do { sleep(CURL_RETRY_DELAY); retries++; eprintf("Retry %d/%d", retries, CURL_MAX_RETRIES); res = curl_easy_perform(_handle); if (res != CURLE_OK) { eprintf("Failed: %s", curl_easy_strerror(res)); } } while (res != CURLE_OK && retries < CURL_MAX_RETRIES); } } void CAudioScrobbler::ReportResponse(char* buf, size_t size) { _response.append(buf); } std::string CAudioScrobbler::CreateScrobbleMessage(int index, const CacheEntry& entry) { const Song& song = entry.getSong(); CLastFMMessage msg(_handle); msg.AddField("method", "track.Scrobble"); msg.AddField("artist", song.getArtist()); msg.AddField("track", song.getTitle()); msg.AddField("duration", song.getDuration()); msg.AddField("timestamp", entry.getStartTime()); msg.AddField("sk", _sessionid); msg.AddField("api_key", APIKEY); if(!song.getAlbum().empty()) { msg.AddField("album", song.getAlbum()); } if(!song.getAlbumArtist().empty()) { msg.AddField("albumArtist", song.getAlbumArtist()); } return msg.GetMessage(); } void CAudioScrobbler::Failure() { _failcount += 1; if(_failcount >= 3) { eprintf("%s", "Re-Handshaking!"); _failcount = 0; Handshake(); } } bool CAudioScrobbler::CheckFailure(std::string response) { bool retval = false; size_t start, end; start = _response.find("", start)-1; std::string errorcode = _response.substr(start, end-start); int code = strtol(errorcode.c_str(), 0, 10); eprintf("%s%i", "Code: ", code); switch(code) { case 3: eprintf("Invalid Method. This should not happen."); retval = true; break; case 4: eprintf("Authentication failed. Please check your login data."); exit(EXIT_FAILURE); case 9: eprintf("Invalid session key. Re-authenticating."); retval = true; _failcount = 3; break; case 10: eprintf("Invalid API-Key. Let's bugger off."); exit(EXIT_FAILURE); case 13: eprintf("Invalid method signature."); exit(EXIT_FAILURE); case 16: eprintf("The service is temporarily unavailable, we will try again later.."); retval = true; break; case 26: eprintf("Uh oh. Suspended API key - Access for your account has been suspended, please contact Last.fm"); exit(EXIT_FAILURE); } return retval; } bool CAudioScrobbler::Scrobble(const CacheEntry& entry) { bool retval = false; if(!_authed) { eprintf("Handshake hasn't been done yet."); Handshake(); return retval; } iprintf("Scrobbling: %s - %s", entry.getSong().getArtist().c_str(), entry.getSong().getTitle().c_str()); OpenURL(GetServiceURL(), CreateScrobbleMessage(0, entry).c_str()); if(_response.find("") != std::string::npos) { iprintf("%s", "Scrobbled successfully."); retval = true; } else if(_response.find("") != std::string::npos) { eprintf("%s%s", "Last.fm returned an error while scrobbling:\n", _response.c_str()); if(CheckFailure(_response)) Failure(); } CLEANUP(); return retval; } bool CAudioScrobbler::LoveTrack(const Song& song, bool unlove) { bool retval = false; CLastFMMessage msg(_handle); msg.AddField("method", unlove ? "track.unlove" : "track.love"); msg.AddField("artist", song.getArtist()); msg.AddField("track", song.getTitle()); msg.AddField("api_key", APIKEY); msg.AddField("sk", _sessionid); OpenURL(GetServiceURL(), msg.GetMessage().c_str()); if(_response.find("") != std::string::npos) { iprintf("%s", "(Un)loved track successfully."); retval = true; } else if(_response.find("") != std::string::npos) { eprintf("%s%s", "Last.fm returned an error while (un)loving the currently playing track:\n", _response.c_str()); if(CheckFailure(_response)) Failure(); } CLEANUP(); return retval; } bool CAudioScrobbler::SendNowPlaying(const Song& song) { bool retval = false; CLastFMMessage msg(_handle); msg.AddField("method", "track.updateNowPlaying"); msg.AddField("artist", song.getArtist()); msg.AddField("track", song.getTitle()); msg.AddField("duration", song.getDuration()); msg.AddField("sk", _sessionid); msg.AddField("api_key", APIKEY); if(!song.getAlbum().empty()) { msg.AddField("album", song.getAlbum()); } if(!song.getAlbumArtist().empty()) { msg.AddField("albumArtist", song.getAlbumArtist()); } OpenURL(GetServiceURL(), msg.GetMessage().c_str()); if(_response.find("") != std::string::npos) { iprintf("%s", "Updated \"Now Playing\" status successfully."); retval = true; } else if(_response.find("") != std::string::npos) { eprintf("%s%s", "Last.fm returned an error while updating the currently playing track:\n", _response.c_str()); if(CheckFailure(_response)) Failure(); } CLEANUP(); return retval; } void CAudioScrobbler::Handshake() { std::string username = ""; for(unsigned int i = 0; i < _cfg->Get("username").length(); i++) { username.append(1, tolower(_cfg->Get("username").c_str()[i])); } std::string password = _cfg->Get("password"); CLastFMMessage msg(_handle); msg.AddField("method", "auth.getMobileSession"); msg.AddField("username", username); if(_cfg->getService() == LastFm) { msg.AddField("password", password); } else { std::string password_hashed(md5sum((char*)"%s", password.c_str())); std::string authtoken(md5sum((char*)"%s%s", username.c_str(), password_hashed.c_str())); msg.AddField("authToken", authtoken); msg.AddField("password", password_hashed); } msg.AddField("api_key", APIKEY); OpenURL(GetServiceURL(), msg.GetMessage().c_str()); if(_response.find("") != std::string::npos) { size_t start, end; start = _response.find("") + 5; end = _response.find(""); _sessionid = _response.substr(start, end-start); iprintf("%s%s", "Last.fm handshake successful. SessionID: ", _sessionid.c_str()); _authed = true; } else if(_response.find("") != std::string::npos) { CheckFailure(_response); exit(EXIT_FAILURE); } CLEANUP(); } std::string CLastFMMessage::GetMessage() { std::ostringstream strstream; for(std::map::iterator it = valueMap.begin(); it != valueMap.end(); ++it) { if(it != valueMap.begin()) { strstream << "&"; } char* escaped = curl_easy_escape(curl_handle, it->second.c_str(), it->second.length()); strstream << it->first << "=" << escaped; curl_free(escaped); } // append signature hash strstream << "&api_sig=" << GetSignatureHash(); return strstream.str(); } std::string CLastFMMessage::GetSignatureHash() { std::ostringstream strstream; for(std::map::iterator it = valueMap.begin(); it != valueMap.end(); ++it) { strstream << it->first << it->second; } // append secret key strstream << SECRET; return std::string(md5sum((char*)"%s", strstream.str().c_str())); } mpdas-0.4.5/mpdasrc.example0000644000175000017500000000007213202046101015450 0ustar dogslegdogslegusername = fhen password = password runas = mpd debug = 1 mpdas-0.4.5/ini.h0000644000175000017500000000720613202046101013400 0ustar dogslegdogsleg/* inih -- simple .INI file parser inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #ifndef __INI_H__ #define __INI_H__ /* Make this header file easier to include in C++ code */ #ifdef __cplusplus extern "C" { #endif #include /* Nonzero if ini_handler callback should accept lineno parameter. */ #ifndef INI_HANDLER_LINENO #define INI_HANDLER_LINENO 0 #endif /* Typedef for prototype of handler function. */ #if INI_HANDLER_LINENO typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value, int lineno); #else typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value); #endif /* Typedef for prototype of fgets-style reader function. */ typedef char* (*ini_reader)(char* str, int num, void* stream); /* Parse given INI-style file. May have [section]s, name=value pairs (whitespace stripped), and comments starting with ';' (semicolon). Section is "" if name=value pair parsed before any section heading. name:value pairs are also supported as a concession to Python's configparser. For each name=value pair parsed, call handler function with given user pointer as well as section, name, and value (data only valid for duration of handler call). Handler should return nonzero on success, zero on error. Returns 0 on success, line number of first error on parse error (doesn't stop on first error), -1 on file open error, or -2 on memory allocation error (only when INI_USE_STACK is zero). */ int ini_parse(const char* filename, ini_handler handler, void* user); /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't close the file when it's finished -- the caller must do that. */ int ini_parse_file(FILE* file, ini_handler handler, void* user); /* Same as ini_parse(), but takes an ini_reader function pointer instead of filename. Used for implementing custom or string-based I/O (see also ini_parse_string). */ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user); /* Same as ini_parse(), but takes a zero-terminated string with the INI data instead of a file. Useful for parsing INI data from a network socket or already in memory. */ int ini_parse_string(const char* string, ini_handler handler, void* user); /* Nonzero to allow multi-line value parsing, in the style of Python's configparser. If allowed, ini_parse() will call the handler with the same name for each subsequent line parsed. */ #ifndef INI_ALLOW_MULTILINE #define INI_ALLOW_MULTILINE 1 #endif /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of the file. See http://code.google.com/p/inih/issues/detail?id=21 */ #ifndef INI_ALLOW_BOM #define INI_ALLOW_BOM 1 #endif /* Nonzero to allow inline comments (with valid inline comment characters specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match Python 3.2+ configparser behaviour. */ #ifndef INI_ALLOW_INLINE_COMMENTS #define INI_ALLOW_INLINE_COMMENTS 1 #endif #ifndef INI_INLINE_COMMENT_PREFIXES #define INI_INLINE_COMMENT_PREFIXES ";" #endif /* Nonzero to use stack, zero to use heap (malloc/free). */ #ifndef INI_USE_STACK #define INI_USE_STACK 1 #endif /* Stop parsing on first error (default is to keep parsing). */ #ifndef INI_STOP_ON_FIRST_ERROR #define INI_STOP_ON_FIRST_ERROR 0 #endif /* Maximum line length for any line in INI file. */ #ifndef INI_MAX_LINE #define INI_MAX_LINE 200 #endif #ifdef __cplusplus } #endif #endif /* __INI_H__ */ mpdas-0.4.5/ini.cpp0000644000175000017500000001460213202046101013731 0ustar dogslegdogsleg/* inih -- simple .INI file parser inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include "ini.h" #if !INI_USE_STACK #include #endif #define MAX_SECTION 50 #define MAX_NAME 50 /* Used by ini_parse_string() to keep track of string parsing state. */ typedef struct { const char* ptr; size_t num_left; } ini_parse_string_ctx; /* Strip whitespace chars off end of given string, in place. Return s. */ static char* rstrip(char* s) { char* p = s + strlen(s); while (p > s && isspace((unsigned char)(*--p))) *p = '\0'; return s; } /* Return pointer to first non-whitespace char in given string. */ static char* lskip(const char* s) { while (*s && isspace((unsigned char)(*s))) s++; return (char*)s; } /* Return pointer to first char (of chars) or inline comment in given string, or pointer to null at end of string if neither found. Inline comment must be prefixed by a whitespace character to register as a comment. */ static char* find_chars_or_comment(const char* s, const char* chars) { #if INI_ALLOW_INLINE_COMMENTS int was_space = 0; while (*s && (!chars || !strchr(chars, *s)) && !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { was_space = isspace((unsigned char)(*s)); s++; } #else while (*s && (!chars || !strchr(chars, *s))) { s++; } #endif return (char*)s; } /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ static char* strncpy0(char* dest, const char* src, size_t size) { strncpy(dest, src, size); dest[size - 1] = '\0'; return dest; } /* See documentation in header file. */ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user) { /* Uses a fair bit of stack (use heap instead if you need to) */ #if INI_USE_STACK char line[INI_MAX_LINE]; #else char* line; #endif char section[MAX_SECTION] = ""; char prev_name[MAX_NAME] = ""; char* start; char* end; char* name; char* value; int lineno = 0; int error = 0; #if !INI_USE_STACK line = (char*)malloc(INI_MAX_LINE); if (!line) { return -2; } #endif #if INI_HANDLER_LINENO #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) #else #define HANDLER(u, s, n, v) handler(u, s, n, v) #endif /* Scan through stream line by line */ while (reader(line, INI_MAX_LINE, stream) != NULL) { lineno++; start = line; #if INI_ALLOW_BOM if (lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB && (unsigned char)start[2] == 0xBF) { start += 3; } #endif start = lskip(rstrip(start)); if (*start == ';' || *start == '#') { /* Per Python configparser, allow both ; and # comments at the start of a line */ } #if INI_ALLOW_MULTILINE else if (*prev_name && *start && start > line) { /* Non-blank line with leading whitespace, treat as continuation of previous name's value (as per Python configparser). */ if (!HANDLER(user, section, prev_name, start) && !error) error = lineno; } #endif else if (*start == '[') { /* A "[section]" line */ end = find_chars_or_comment(start + 1, "]"); if (*end == ']') { *end = '\0'; strncpy0(section, start + 1, sizeof(section)); *prev_name = '\0'; } else if (!error) { /* No ']' found on section line */ error = lineno; } } else if (*start) { /* Not a comment, must be a name[=:]value pair */ end = find_chars_or_comment(start, "=:"); if (*end == '=' || *end == ':') { *end = '\0'; name = rstrip(start); value = end + 1; #if INI_ALLOW_INLINE_COMMENTS end = find_chars_or_comment(value, NULL); if (*end) *end = '\0'; #endif value = lskip(value); rstrip(value); /* Valid name[=:]value pair found, call handler */ strncpy0(prev_name, name, sizeof(prev_name)); if (!HANDLER(user, section, name, value) && !error) error = lineno; } else if (!error) { /* No '=' or ':' found on name[=:]value line */ error = lineno; } } #if INI_STOP_ON_FIRST_ERROR if (error) break; #endif } #if !INI_USE_STACK free(line); #endif return error; } /* See documentation in header file. */ int ini_parse_file(FILE* file, ini_handler handler, void* user) { return ini_parse_stream((ini_reader)fgets, file, handler, user); } /* See documentation in header file. */ int ini_parse(const char* filename, ini_handler handler, void* user) { FILE* file; int error; file = fopen(filename, "r"); if (!file) return -1; error = ini_parse_file(file, handler, user); fclose(file); return error; } /* An ini_reader function to read the next line from a string buffer. This is the fgets() equivalent used by ini_parse_string(). */ static char* ini_reader_string(char* str, int num, void* stream) { ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; const char* ctx_ptr = ctx->ptr; size_t ctx_num_left = ctx->num_left; char* strp = str; char c; if (ctx_num_left == 0 || num < 2) return NULL; while (num > 1 && ctx_num_left != 0) { c = *ctx_ptr++; ctx_num_left--; *strp++ = c; if (c == '\n') break; num--; } *strp = '\0'; ctx->ptr = ctx_ptr; ctx->num_left = ctx_num_left; return str; } /* See documentation in header file. */ int ini_parse_string(const char* string, ini_handler handler, void* user) { ini_parse_string_ctx ctx; ctx.ptr = string; ctx.num_left = strlen(string); return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, user); }