Compare commits

...

1144 Commits
v1.3 ... master

Author SHA1 Message Date
krateng 39a42e915c Initial support for new Spotify export format, GH-215 2023-06-25 18:00:23 +02:00
krateng b8944b4954 Reorganized containerfile to allow caching 2023-03-31 15:47:00 +02:00
krateng 9d9f3b500e More convenient album saving for 3.2 upgrade 2023-03-30 16:27:40 +02:00
krateng 72c58509a1 Added cool tag list script 2023-03-28 22:47:46 +02:00
krateng 11a5cb7401 Fixed scrobbler 2023-03-28 00:06:59 +02:00
krateng b4c8a0d68b Updated settings.md 2023-03-27 19:09:44 +02:00
krateng 88403d2583
Fetch smaller image from musicbrainz, fix GH-206 2023-03-27 18:10:53 +02:00
krateng 866d4ccd9b
Merge pull request #205 from FoxxMD/lsio
Refactor container image to use linuxserverio alpine base
2023-03-21 19:14:11 +01:00
FoxxMD 3db51a94d6 Add permission check and docs for PUID/PGID usage 2023-03-17 11:51:11 -04:00
FoxxMD a9c29f158e Refactor containerfile to align with lsio python install
* Simplify project file copy
* Reduce system and project dependency installs into single layer
* Add default permission ENVs for backwards-compatibility
2023-03-17 11:44:16 -04:00
krateng ab8af32812
Merge pull request #204 from FoxxMD/imagePerf
Improve image rendering performance
2023-03-17 16:43:49 +01:00
FoxxMD 7bc2ba0237 Move image base to linuxserverio alpine base
krateng/maloja#96
2023-03-17 10:28:07 -04:00
FoxxMD b8371347b7 Add configuration boolean for rendering album/artist icons
If a user has a slow internet connection or is using a low-power device they may wish to not render icons at all to prevent additional cpu/network load. Defaults to `true` to preserve existing behavior.
2023-03-16 15:21:02 -04:00
FoxxMD 1e3c6597d4 Lazy load tile and entity background images
CSS 'background-image:url' causes the browser to synchronously load images which prevents DOM from fully loading.
Replace this with lazyload.js to make
   * js load style=background-image... after DOM is loaded and
   * only load images in viewport

The end result is much faster *apparent* page loads as all DOM is loaded before images and a reduction in load for both client/server as images are only loaded if they become visible.
2023-03-16 15:01:54 -04:00
krateng 37210995fa
Merge pull request #202 from christophernewton/master
Fixed search response failure for manual scrobbling
2023-03-07 16:54:51 +01:00
Chris Newton 94ae453133 Fixed search response failure for manual scrobbling 2023-03-07 11:04:53 +11:00
krateng 93bbaac0e3 Bumped doreah version, fix GH-200 2023-02-26 22:10:13 +01:00
krateng 00a564c54d Hardcoded screenshot url in readme 2023-02-26 16:46:36 +01:00
krateng 4330b0294b Version bump 2023-02-26 16:32:03 +01:00
krateng b53141f065 Renamed workflows, fix GH-181 2023-02-26 16:00:06 +01:00
krateng 3ae395f697 Removed explicit column selection, GH-196 2023-02-26 15:50:37 +01:00
krateng 5466b6c37e Added dependency versions to information output 2023-02-26 15:48:50 +01:00
krateng e85861fb79 Bandaid for entity editing in Firefox, fix GH-188, GH-175 2023-02-25 22:18:57 +01:00
krateng a611b78dbc Removed dead link, fix GH-189 2023-02-25 22:07:12 +01:00
krateng c3ed5f318d Narrowed chart bars a bit, fix GH-195 2023-02-25 21:58:50 +01:00
krateng 073448257a Fixed robots.txt 2023-01-13 06:23:22 +01:00
krateng d12229d8a5 Fixed small visual bug 2023-01-01 23:35:55 +01:00
krateng d8f53a56d2 Fixed info output for Dual Stack 2022-12-05 00:05:19 +01:00
krateng c8f9e9c391 Fix main page display on Safari, fix GH-172 2022-11-30 05:15:20 +01:00
krateng 185a5b3e87 Added scrobbler functionality to selectively enable sites 2022-11-24 00:10:57 +01:00
krateng 95eaf0a3d6 Added interface for picking services to scrobbler 2022-11-23 23:40:44 +01:00
krateng a7d286c90c Added error handling for image upload 2022-10-19 19:53:13 +02:00
krateng ddc78c5756 Made addpicture endpoint part of the external API, GH-169 2022-10-19 19:28:15 +02:00
krateng a12253dc29 Sanitize artists and tracks in lists, GH-167 2022-10-13 18:08:08 +02:00
krateng 9eaeffca7e Sanitize artists and tracks in search results, GH-167 2022-10-13 18:06:02 +02:00
krateng db8389e6c1 Rules 2022-10-13 15:35:21 +02:00
krateng ef06f22622 Version bump 2022-10-07 17:38:12 +02:00
krateng b333009684 Merge branch 'master' of github.com:krateng/maloja 2022-10-07 17:33:07 +02:00
krateng ebd78914f9 Sanitize artists and tracks, fix GH-167 2022-10-07 17:32:34 +02:00
krateng 36d0e7bb8a
Merge pull request #153 from badlandspray/master
Track more additional information
2022-09-11 21:02:55 +02:00
krateng 91750db8ac
Reduce stored extra info from Listenbrainz API 2022-09-11 21:01:21 +02:00
krateng d5f2c254f3
Fix field name for track length 2022-09-11 20:58:37 +02:00
krateng e3933e7dca
Merge pull request #163 from krkk/import_listenbrainz
Implement importing scrobbles from ListenBrainz
2022-08-20 16:55:32 +02:00
Karol Kosek 9b10ca4a5d Implement importing scrobbles from ListenBrainz
Closes: #162
2022-08-16 22:17:02 +02:00
Karol Kosek 2ce2e2f682 Import track lengths from own maloja format
It will also be used when importing ListenBrainz files.
2022-08-16 19:37:15 +02:00
krateng 9917210b66 More intuitive manual scrobbling, GH-160 2022-08-15 17:50:42 +02:00
krateng 5656f8b4c0 Some rules 2022-08-15 17:50:07 +02:00
badlandspray 9ae14da397
scrobble_duration key 2022-07-16 00:54:54 -07:00
badlandspray 3fd02c1675
Add release_artist_name and correct duration 2022-07-15 15:18:22 +00:00
badlandspray f7251c613c
Add more fields 2022-07-05 08:34:07 +00:00
badlandspray d57bf33969
Track more additional information 2022-06-09 17:54:20 +00:00
krateng a1b2261fa7
Merge pull request #151 from badlandspray/master
Store album name
2022-06-06 18:02:52 +02:00
krateng 260c587248
Allow minimal listenbrainz payload 2022-06-06 18:02:34 +02:00
badlandspray c1493255b7
Store album name 2022-06-04 07:31:18 +00:00
krateng 97fc38f919 Graceful handling of missing templates 2022-05-26 14:56:04 +02:00
krateng 397d5e7c13 API root path now returns JSON error, fix GH-150 2022-05-26 14:43:29 +02:00
krateng 1eaba888c7 Saving additional info for Listenbrainz API, fix GH-149 2022-05-17 16:34:09 +02:00
krateng 084c7d5a1e Merge branch 'master' of github.com:krateng/maloja 2022-05-17 15:32:51 +02:00
krateng 515fa69fce
Fixed Readme links 2022-05-15 21:04:50 +02:00
krateng ca30309450
Merge pull request #146 from badlandspray/master
Track album name and track length
2022-05-08 16:45:51 +02:00
badlandspray 705f4b4252
Track album name and track length 2022-05-08 13:26:42 +00:00
krateng ac498bde73 Refactored some scrobble parsing 2022-05-07 22:24:37 +02:00
krateng f3a04c79b1 Version bump 2022-05-07 15:24:19 +02:00
krateng f74d5679eb Properly passing flags argument for regex sub calls, fix GH-145 2022-05-07 15:17:29 +02:00
krateng 5eb838d5df Added favicon html tag, fix GH-143 2022-05-06 16:30:24 +02:00
krateng 96778709bd Design change for chart tiles 2022-05-05 17:38:11 +02:00
krateng a073930601 Version bump 2022-05-05 16:38:08 +02:00
krateng 81f4e35258 Added API debug feature 2022-05-01 22:39:16 +02:00
krateng c16919eb1e Added rules 2022-05-01 17:53:25 +02:00
krateng e116690640 Fixed leftover whitespaces when parsing titles 2022-04-30 20:19:45 +02:00
krateng 8cb332b9fc Removed underline from linked buttons 2022-04-29 16:35:56 +02:00
krateng 3ede71fc79 Made some parsing rules case insensitive 2022-04-28 06:08:51 +02:00
krateng 77a0a0a41b
Merge pull request #139 from alim4r/feature/parse-remix-artists
Add feature to parse remix artists
2022-04-28 04:43:29 +02:00
alim4r ec02672a2e Remove debug print... 2022-04-27 22:30:23 +02:00
alim4r 5941123c52 Set parse_remix_artists default to False 2022-04-27 22:24:45 +02:00
alim4r 91a7aeb50d Add feature to parse remix artists 2022-04-27 20:54:33 +02:00
krateng 20aae955b2 Version bump 2022-04-27 20:30:15 +02:00
krateng d83b44de6e Added tooltip for image upload, GH-138 2022-04-27 17:51:39 +02:00
krateng 8197548285 Improved cache memory output 2022-04-26 19:58:36 +02:00
krateng 6171d1d2e1 Restored custom CSS file functionality, fix GH-135 2022-04-26 19:43:35 +02:00
krateng 0c948561a8 Added more generalized support for static user files, GH-135 2022-04-26 19:41:23 +02:00
krateng 02c77a5e31 Updated commit information 2022-04-26 14:52:19 +02:00
krateng bfa553bed0 Version bump 2022-04-26 14:43:51 +02:00
krateng 3592571afd
Merge pull request #130 from krateng/feature-webedit
Version 3.1
2022-04-26 14:38:41 +02:00
krateng c77b7c952f Added status to more API endpoints 2022-04-25 22:54:53 +02:00
krateng 8a44d3def2 Fixed some CSS 2022-04-25 22:01:56 +02:00
krateng cf04583122 Added more connection passing 2022-04-25 22:00:32 +02:00
krateng 8845f931df Wrapped DB write operations in transactions to ensure integrity 2022-04-25 21:50:40 +02:00
krateng 9c6c91f594 Can now reparse without reloading 2022-04-25 20:48:22 +02:00
krateng 2c31df3c58 Fixed cache invalidation after merging 2022-04-25 18:37:19 +02:00
krateng 9c656ee90b Some improvements to process control, should fix GH-112 2022-04-25 18:36:11 +02:00
krateng 938947d06c Added indicator for empty tile stats, fix GH-134 2022-04-25 17:55:46 +02:00
krateng ac3ca0b5e9 Moved exceptions and added handling for more of them 2022-04-25 17:03:44 +02:00
krateng 64d4036f55 Merge branch 'master' into feature-webedit 2022-04-25 16:07:26 +02:00
krateng 6df363a763 Merge branch 'master' of github.com:krateng/maloja 2022-04-25 15:51:55 +02:00
krateng 7062c0b440
Update API.md 2022-04-25 15:51:01 +02:00
krateng ad50ee866c More cache organization 2022-04-25 15:36:15 +02:00
krateng 62abc31930 Version bump 2022-04-25 03:37:12 +02:00
krateng c55e12dd43 Re-enabled cache per default 2022-04-25 03:24:16 +02:00
krateng 3b156a73ff Merge branch 'master' into feature-webedit 2022-04-25 02:53:43 +02:00
krateng 5b48c33a79 Added stresstest and new screenshot 2022-04-25 02:48:01 +02:00
krateng 95f98370cf Updated branch notes 2022-04-25 02:47:03 +02:00
krateng e470e2e43f Potentially fixed nonsensical caching, GH-132 2022-04-25 02:42:07 +02:00
krateng 35f428ef69 Merge branch 'master' of github.com:krateng/maloja 2022-04-24 20:55:57 +02:00
krateng 342b8867d9 Ported cache cleanup from 3.1 2022-04-24 20:55:07 +02:00
krateng bfc83fdbb0 Ported signal handling fix from 3.1 2022-04-24 20:47:17 +02:00
krateng f359662cf3 No longer catching BaseExceptions 2022-04-24 19:41:55 +02:00
krateng de286b58b9
Merge pull request #133 from northys/build_rpi
Build image for raspberry pi 2 (arm/v7)
2022-04-24 17:10:52 +02:00
krateng d5f5b48d85 Removed previous ability, but this time clean and consistent 2022-04-24 16:14:24 +02:00
Jiri Travnicek 00b3e6fc57
actions: build image for linux/arm/v7 (raspberry pi) 2022-04-24 16:12:11 +02:00
Jiri Travnicek e1074ba259
actions: drop ghcr support 2022-04-24 16:11:58 +02:00
krateng 7c77474feb Implemented cache enabling and disabling at runtime 2022-04-24 15:53:30 +02:00
krateng 279499ad9f Fixed old css passed to auth 2022-04-24 15:12:49 +02:00
krateng dc1becd683 Removed release notes that have been moved to 3.0.6 2022-04-24 03:17:35 +02:00
krateng b3d4cb7a15 Version bump 2022-04-24 03:08:22 +02:00
krateng 4c1ba087ba
Merge pull request #131 from northys/build_arm64
Build arm64
2022-04-24 03:07:51 +02:00
krateng 0c94dc845b Updated release information 2022-04-24 03:06:44 +02:00
Jiri Travnicek 9589a6a5c9
use repository owner variable instead of hardcoding it 2022-04-23 21:03:40 +02:00
Jiri Travnicek d54f2f8d35
ci: use specific commit tag for github actions 2022-04-23 21:03:40 +02:00
Jiri Travnicek 082d11309b
build arm64 2022-04-23 21:03:39 +02:00
krateng 3cb72f46bc Didn't actually mean to reset this 2022-04-23 20:59:18 +02:00
krateng d81f8374c9 Simplified container build 2022-04-23 20:13:50 +02:00
krateng c86ae31ea9 Fixed sins of my youth 2022-04-23 19:22:32 +02:00
krateng c3bb8ad322 Version-bumped Python and dependencies 2022-04-23 17:59:39 +02:00
krateng 6c5f08aa5a Removed special handling of css 2022-04-23 17:32:05 +02:00
krateng 29a6a74c37 Altered the previous fix. Pray I don't alter it further. 2022-04-23 17:24:18 +02:00
krateng 1bbb600481 Fixed small content jumping issue 2022-04-23 17:05:47 +02:00
krateng df07307730 Prepare for release 2022-04-23 16:48:37 +02:00
krateng 74977b18cc Merge branch 'master' into feature-webedit 2022-04-23 16:38:50 +02:00
krateng 029d0464b4 Merge branch 'master' of github.com:krateng/maloja 2022-04-23 16:36:56 +02:00
krateng db8bf60aef Moved search functionality to database 2022-04-23 16:36:35 +02:00
krateng 52ee456b1f
Merge pull request #129 from northys/patch-1
README.md: docker import suggestion
2022-04-23 16:29:35 +02:00
northys 519c26b8d8
README.md: docker import suggestion [closes #125] [closes #126] [closes #127] 2022-04-23 16:17:37 +02:00
krateng e330678a05
Update API.md 2022-04-23 15:45:32 +02:00
krateng 0424fa7795
Update API.md 2022-04-23 15:25:42 +02:00
krateng 528d3565b7 Moved API information 2022-04-23 14:38:18 +02:00
krateng 56f7c18495
Create API.md 2022-04-23 05:59:47 +02:00
krateng 1dfda0086e Fixed merging of artists that already share tracks 2022-04-22 22:59:02 +02:00
krateng 7c9f6e9e2d Fixed bug in web interface for non-independent artists 2022-04-22 21:38:35 +02:00
krateng 529d0c8a5d Reload on reparse 2022-04-22 21:37:48 +02:00
krateng cf4b3cd68f Commit 1291 🇨🇭 2022-04-22 20:59:55 +02:00
krateng 9272c191d8 More UI changes 2022-04-22 20:34:46 +02:00
krateng d0ccf3d1ae Small fixes 2022-04-22 20:04:24 +02:00
krateng 10fef00592 Unified remaining icons 2022-04-22 20:04:13 +02:00
krateng 1ed4af10ac Small design changes 2022-04-22 19:06:54 +02:00
krateng 11bc92ee8f Unified icon style somewhat 2022-04-22 19:04:00 +02:00
krateng 98c791064d More interface fixing and notifications 2022-04-22 18:43:40 +02:00
krateng d208290956 Added descriptions to API return dicts 2022-04-22 18:36:06 +02:00
krateng 009d77a75e Reogranized scrobble action area in web interface 2022-04-22 18:29:09 +02:00
krateng e6992f1e90 Moved more icons to jinja 2022-04-22 18:28:40 +02:00
krateng c52ad81fc2 Fixed destructive updating with missing fields 2022-04-22 17:51:42 +02:00
krateng f5d1fbc576 Generalized scrobble updating 2022-04-22 17:43:14 +02:00
krateng a8f8d86ec1 Adjusted reparse additions to new branch changes 2022-04-22 17:25:58 +02:00
krateng e9189b8903
Merge pull request #122 from alim4r/feature/reparse-scrobble
Add reparse scrobble feature
2022-04-22 17:17:11 +02:00
krateng 01d52d7e36 Merge branch 'master' into feature-webedit 2022-04-22 17:16:26 +02:00
krateng 528c954de9 Added output for API-caught errors 2022-04-22 17:14:57 +02:00
krateng 7c0ecda8a2 Fixed duplicate tracks on artist merge 2022-04-22 17:00:07 +02:00
krateng 6e4e62755d Fixed global database lock release during scrobble creation, GH-126 2022-04-22 06:01:59 +02:00
krateng 646c290a37 Added separate output for importing zero files 2022-04-21 21:17:55 +02:00
krateng 28163348fa Fixed importing with direct filename, fix GH-124 2022-04-21 21:15:53 +02:00
alim4r 495627f3f7 Merge branch 'feature-webedit' into feature/reparse-scrobble 2022-04-21 19:04:32 +02:00
alim4r 6893fd745a Update get_scrobble parameters 2022-04-21 18:28:59 +02:00
krateng 91dae00851 Fixed renaming entities when new and old name are normalized the same 2022-04-21 18:19:33 +02:00
krateng c0ff50b064 Updated admin mode information 2022-04-21 18:08:15 +02:00
krateng 884e95dc58 Manual scrobbling now also uses new notification system 2022-04-21 18:04:01 +02:00
krateng 8023c2d51c Removed merge icon handling on pages that don't use them 2022-04-21 17:59:42 +02:00
krateng df6bbebe31 Removed now unnecessary default override for container 2022-04-21 17:51:16 +02:00
krateng de625cc3c2 Fixed missing output when running in Docker, fix GH-117 2022-04-21 17:46:25 +02:00
krateng 428d92a267 Updated release notes 2022-04-21 17:35:19 +02:00
krateng 20092df02c Only showing valid icons for merging 2022-04-21 17:02:10 +02:00
krateng 713dbc34bb Fixed renaming artist to existing artist 2022-04-21 16:00:29 +02:00
krateng 181406d339 Added exception handling for all native API endpoints 2022-04-21 15:46:29 +02:00
krateng 9b5eb6f723 Fixed notifications of errors 2022-04-21 15:43:11 +02:00
krateng 662923dd5e Fixed caching bug with updating track 2022-04-21 15:41:38 +02:00
krateng ff71a9c526 Fixed renaming track to existing track 2022-04-21 15:13:14 +02:00
krateng fbbd959295 Added exceptions to database 2022-04-21 15:12:48 +02:00
krateng ce495176c1 Fixed passing of dbconn to subfunctions 2022-04-21 15:11:55 +02:00
krateng afc78e75b0 Generalized exception handling for native API 2022-04-21 15:05:54 +02:00
alim4r 85bb1f36cc Ignore scrobbles without a rawscrobble 2022-04-20 21:48:41 +02:00
alim4r c457b58ab8 Quick fix for reparse confirmation & button placement 2022-04-20 20:18:28 +02:00
krateng 62208bf668 Merge branch 'feature-restructure' into feature-webedit 2022-04-20 19:10:41 +02:00
krateng 53bc856222 Merge branch 'master' into feature-restructure 2022-04-20 19:08:16 +02:00
alim4r b525252af1 Add reparse scrobble feature 2022-04-20 15:59:33 +02:00
krateng 43ec4c2c9e Reenabled bracket normalization for titles, GH-121 2022-04-19 22:13:51 +02:00
krateng 17be00f794 Improved parsing of featuring artists in square brackets, fix GH-121 2022-04-19 15:22:42 +02:00
krateng fe21894c5e Version bump 2022-04-19 02:41:39 +02:00
krateng 2bb3fa12b3
Merge pull request #119 from northys/patch-1
README.md: fix docker image name
2022-04-19 00:13:54 +02:00
krateng 32a900cf37 Fixed error for Lastfm import and added feedback, fix GH-118 2022-04-19 00:11:40 +02:00
krateng 397eaf668f Moved static areas together in jinja base template 2022-04-18 23:34:53 +02:00
northys 80ba4550c7
README.md: fix docker image name 2022-04-18 22:37:27 +02:00
krateng b31e778d95 Made incomplete merging process a bit less permanent 2022-04-17 20:23:49 +02:00
krateng 6e8cbe6a57 Added callback notifications to edit functions 2022-04-17 20:18:44 +02:00
krateng 45ea7499b2 Added some return values to database 2022-04-17 20:18:26 +02:00
krateng 77c4dac7be Merge branch 'master' into feature-webedit 2022-04-17 19:30:39 +02:00
krateng 61526fdc89 Added basic notification system to web interface 2022-04-17 19:30:27 +02:00
krateng ea6d70a650 Implemented experimental merging server-side 2022-04-17 17:38:38 +02:00
krateng 57e66fdafd Added client logic for merging 2022-04-17 17:24:23 +02:00
krateng 0d985ff706 Reorganized admin mode icons 2022-04-17 16:46:02 +02:00
krateng 27a9543da9 Added merge icons 2022-04-17 16:16:05 +02:00
krateng c9d2527a98 Added changelog 2022-04-17 15:37:48 +02:00
krateng 977385a700 Fixed editing with special characters 2022-04-17 15:37:08 +02:00
krateng c8522bd473 Updated changelog 2022-04-17 15:31:12 +02:00
krateng 83e3157ad1 Can now cancel editing 2022-04-17 15:15:29 +02:00
krateng 0525ff400b Merge branch 'feature-restructure' into feature-webedit 2022-04-17 04:45:51 +02:00
krateng 13856a2347 Merge branch 'master' into feature-restructure 2022-04-17 04:44:28 +02:00
krateng e9bf65da34 Completed old tag information 2022-04-16 22:08:54 +02:00
krateng 5bf66ab270 Added release documentation 2022-04-16 20:40:37 +02:00
krateng 206ebd58ea Version bump 2022-04-16 18:47:07 +02:00
krateng a642c274e3
Merge pull request #116 from ICTman1076/patch-4
Add artist to specialsymbols
2022-04-16 18:46:33 +02:00
ICTman1076 8ba973ed91
Add artist to specialsymbols 2022-04-16 16:35:21 +00:00
krateng ca726c774a Removed duplicate track artist entries, fix GH-115 2022-04-16 18:19:25 +02:00
krateng 33bbe61ece Small fixes 2022-04-16 16:21:24 +02:00
krateng 15f815ffe9 Improved native API error feedback 2022-04-16 15:59:42 +02:00
krateng fa2ce0c05f Reduced DB connections for cached stats 2022-04-16 04:37:50 +02:00
krateng b806be6e02 Cached stats now use IDs to survive renames 2022-04-16 03:10:51 +02:00
krateng 6601920f69 Fixed entrypoint 2022-04-16 02:17:43 +02:00
krateng f3f7dbd8ef Fixed double request when editing 2022-04-16 02:17:14 +02:00
krateng 263e7cd704 Merge branch 'feature-restructure' into feature-webedit 2022-04-16 02:04:43 +02:00
krateng 5b8e2debbc Merge branch 'master' into feature-restructure 2022-04-16 02:04:04 +02:00
krateng bccd88acd4 Implemented track title editing and refactored edit system 2022-04-15 19:41:44 +02:00
krateng 371e73ac99 Implemented artist name editing 2022-04-15 18:48:03 +02:00
krateng c33fcf1dc1 Added edit function to web interface 2022-04-15 18:16:54 +02:00
krateng 98e1926613 Moved svg icon to jinja snippet 2022-04-15 18:16:49 +02:00
krateng b255d424ee Native API accepts superfluous keywords, FoxxMD/multi-scrobbler#42 2022-04-15 17:49:13 +02:00
krateng 28d43d00cb Merge branch 'master' into feature-restructure 2022-04-14 20:55:29 +02:00
krateng 7f9aa125af Enabled dual stack web server 2022-04-14 20:49:40 +02:00
krateng 1d9247fc72 Version bump 2022-04-14 20:09:01 +02:00
krateng c91cae9de1 Added info about API endpoint return values, fix GH-114 2022-04-14 20:02:02 +02:00
krateng 1a977d9c0c Moved all native API endpoints to new auth handling 2022-04-14 19:36:50 +02:00
krateng 62a654bfbf Added more docstrings 2022-04-14 19:34:42 +02:00
krateng 16d8ed0575 Fixed nofix argument for scrobbling 2022-04-14 17:44:52 +02:00
krateng 7c1d45f4af Fixed mistake in API testing 2022-04-14 17:32:27 +02:00
krateng 65fd57dceb Explicit arguments for native scrobble endpoint 2022-04-14 17:29:10 +02:00
krateng 29f722e3d3 Added time format info to docstrings 2022-04-14 17:00:45 +02:00
krateng e6bb844ff9 Added some docstrings to native API endpoints, GH-114 2022-04-14 16:14:31 +02:00
krateng 4cffc9971d Merge branch 'master' into feature-restructure 2022-04-14 15:19:38 +02:00
krateng bcb1d36b4a Exit codes for main function, fix GH-113 2022-04-14 15:10:15 +02:00
krateng 9d8752d052 Fixed proper recognition of artist and track entities, fix GH-111 2022-04-14 14:49:59 +02:00
krateng 741246a7c1
Merge pull request #110 from da2x/patch-1
Set Referrer-Policy to same-origin
2022-04-14 14:35:55 +02:00
Daniel Aleksandersen c076518d76
Set Referrer-Policy to same-origin
Remove the Referer (sic) HTTP request header from external requests (e.g. to
the image CDNs).

The charset directive must be included in the first TCP packet. It should
be set at the very top of the document. Grouping document mode metas
and descriptive metadata in separate groups.
2022-04-14 13:02:21 +02:00
krateng 4a8221f7a0 Added waitress warning interception 2022-04-13 18:00:39 +02:00
krateng 42579ad1f0 Fixed missing import, GH-108 2022-04-13 15:42:45 +02:00
krateng ef312c6ba9 Updated admin mode info 2022-04-12 23:04:17 +02:00
krateng dad1365627 Renamed imported scrobble folder 2022-04-12 20:33:36 +02:00
krateng 1c2062c512 Reenabled site generation profiling 2022-04-12 19:04:22 +02:00
krateng 6b39ca8b19 Added duration warning to upgrade page 2022-04-12 19:03:55 +02:00
krateng 700b81217c Version bump 2022-04-12 17:59:32 +02:00
krateng 50cf592a75 Fixed upgrading early scrobbles, GH-106 2022-04-12 17:57:13 +02:00
krateng 0f39ecbf7e Fixed funding file 2022-04-12 16:39:44 +02:00
krateng d018a758c0 Merge branch 'master' into feature-restructure 2022-04-12 16:20:53 +02:00
krateng f31c95228e Ride now, ride now! Ride to Gondor! 2022-04-12 16:03:47 +02:00
krateng 2cf785faae
Merge pull request #105 from krateng/v3
Version 3
2022-04-12 16:02:52 +02:00
krateng 189dfb58bc Limited cache size 2022-04-12 16:02:30 +02:00
krateng cabfa298b9 Replaced cache trimming with full clear for now 2022-04-12 05:33:03 +02:00
krateng b8aa2a562e Bumped doreah requirement 2022-04-12 05:31:31 +02:00
krateng cc4d40ae3f Reenabled Dockerhub Readme update 2022-04-11 21:22:58 +02:00
krateng 5a2856a682 Added log output to cache trimming 2022-04-10 23:26:03 +02:00
krateng 2d2a7c2ee7 Updated Readme 2022-04-10 18:44:55 +02:00
krateng 6635a9ac50 Merge branch 'v3' into feature-restructure 2022-04-10 17:44:36 +02:00
krateng df5eb499af Added lint configuration and reorganized gitignore 2022-04-10 17:41:37 +02:00
krateng e52f35d65b Added example compose file 2022-04-10 17:22:38 +02:00
krateng 97e1eae386 Merge branch 'master' into v3 2022-04-10 16:08:41 +02:00
krateng e152a2edde Containerfile transition test update 2022-04-10 15:56:00 +02:00
krateng 871b3d289d Moved monkey patching and globalconf to subpackage 2022-04-09 21:39:04 +02:00
krateng abde7e72c4 Moved scrobble generation to dev package 2022-04-09 21:24:48 +02:00
krateng 24dfa41ad9 Moved profiler to new dev subpackage 2022-04-09 21:20:48 +02:00
krateng bceb0db09a Moved supervisor to __main__ 2022-04-09 21:11:06 +02:00
krateng 87f1250629 Moved setup to top level 2022-04-09 21:02:17 +02:00
krateng bb68afee12 Moved main process control to __main__ 2022-04-09 20:55:50 +02:00
krateng 233e49d087 Small setup fix 2022-04-09 17:05:54 +02:00
krateng fe727dedee Added handler for old tsv files to upgrade module 2022-04-09 16:52:20 +02:00
krateng 64f6836365 More Readme changes 2022-04-09 16:44:48 +02:00
krateng 96933d5f18 Added screenshot 2022-04-09 16:34:09 +02:00
krateng fba21b7128 Added proper logging to upgrade script 2022-04-08 21:17:17 +02:00
krateng 1207475e4d Removed absolute paths from dockerignore 2022-04-08 21:15:10 +02:00
krateng de5ae6408a Refactored imports to avoid DB startup for unrelated tasks 2022-04-08 19:10:20 +02:00
krateng 45d481b1ed Version bump 2022-04-08 19:04:07 +02:00
krateng 806f024f51 Removed unused stat sending prompt 2022-04-08 19:03:43 +02:00
krateng 5952b8de4d Fixed setup of completely new server 2022-04-08 19:03:12 +02:00
krateng 3115d0372b Merge branch 'master' into v3 2022-04-08 18:11:17 +02:00
krateng df996f7cb6 Version bump 2022-04-08 17:51:58 +02:00
krateng 42cde8b647 Updated gitignore 2022-04-08 17:50:33 +02:00
krateng 9e7bbb6c20 Color! 2022-04-08 17:31:30 +02:00
krateng 28ba7b6ad0 Improved Containerfile 2022-04-08 17:23:56 +02:00
krateng 0f59ffb288 And this one too 2022-04-08 16:30:04 +02:00
krateng 7864c9f897 Updated version in pkginfo as well 2022-04-08 16:25:45 +02:00
krateng 5524c0a70f Version and dependency bump 2022-04-08 16:22:39 +02:00
krateng dc192d7444 Removed remaining doreah tsv dependencies 2022-04-08 16:08:48 +02:00
krateng 4e33f808e4 Removed issues functionality for now 2022-04-08 06:10:29 +02:00
krateng 700d99c5ae Updated Readme 2022-04-08 06:07:15 +02:00
krateng 037f195803 More normalizing 2022-04-08 04:52:59 +02:00
krateng e9d8303763 Removed dependency on doreah's tsv module 2022-04-07 22:50:08 +02:00
krateng 387c40d18c Small improvements 2022-04-07 21:21:10 +02:00
krateng 40c0edb06f Improved DB cleanup 2022-04-07 21:21:00 +02:00
krateng 9f26cce34b Avoided ID pollution from loading and deleting artists 2022-04-07 21:20:35 +02:00
krateng a142804bfe Implemented scrobble deletion 2022-04-07 20:37:46 +02:00
krateng 8d111b6de7 Improved design of scrobble delete functionality 2022-04-07 20:23:03 +02:00
krateng 848f009774 Distinction between external and internal scrobble info 2022-04-07 20:00:26 +02:00
krateng c9fa9956bb Improved delete button 2022-04-07 20:00:09 +02:00
krateng 2deb5f0e36 Moved to doreah 1.8 2022-04-07 17:52:50 +02:00
krateng 2c73c81434 Version bump 2022-04-07 17:48:05 +02:00
krateng c378c9301d Minor fixes 2022-04-07 17:34:07 +02:00
krateng 74f6a931a4 Adjusted v3 scrobble upgrade 2022-04-07 06:09:07 +02:00
krateng c982cbd1c4 Pinned to doreah version 2022-04-07 05:52:43 +02:00
krateng 6b4f2f713b Adjusted image cache update 2022-04-07 05:52:22 +02:00
krateng 4682914b88 Ensured API consistency to v2 for scrobbling 2022-04-07 05:25:10 +02:00
krateng 781ed66357 Fixed audioscrobbler APIs 2022-04-06 22:51:14 +02:00
krateng 2720dc1be5 Removed old API key functions 2022-04-06 22:46:43 +02:00
krateng 34db81ccef Adapted audioscrobbler legacy API to new architecture 2022-04-06 22:45:56 +02:00
krateng 6ca18b4471 Better logging 2022-04-06 22:25:23 +02:00
krateng c676e0a5bf Adapted audioscrobbler API to new architecture 2022-04-06 22:22:18 +02:00
krateng 08bd352641 Adapted listenbrainz API to new architecture 2022-04-06 22:13:16 +02:00
krateng de18ecff26 More elegant client checking for scrobbles 2022-04-06 21:08:14 +02:00
krateng 36f7ab1670 Scrobbler now always scrobbles plays longer than 3 minutes 2022-04-06 18:27:09 +02:00
krateng 24c65d4acc Removed GET scrobbling 2022-04-06 17:44:59 +02:00
krateng 1257768e33 Reworked scrobble handling 2022-04-06 17:42:48 +02:00
krateng f4e42f9256 Added database feedback when trying to add duplicate scrobbles 2022-04-06 16:48:02 +02:00
krateng a16c24281e Minor database improvements 2022-04-06 16:47:00 +02:00
krateng bd29c1e1ba Implemented extra information field in DB 2022-04-05 20:51:14 +02:00
krateng 6fc3a9cbf8 Experimental database upgrade functionality 2022-04-05 18:30:17 +02:00
krateng abe658cc77 Merge branch 'master' into v3 2022-04-05 16:11:29 +02:00
krateng 447d31b44e Disabled dockerhub docs update, peter-evans/dockerhub-description#10 2022-04-05 16:08:13 +02:00
krateng ec5723d2b3 Version bump 2022-04-05 16:00:25 +02:00
krateng 8ff7acfc38 Readme and minor fixes 2022-04-05 05:48:23 +02:00
krateng 0ae9091889 Removed now unnecessary cache clearing 2022-04-04 18:31:33 +02:00
krateng e1ce80131a Added export to web interface 2022-04-04 18:31:04 +02:00
krateng b7781d27c3 Aligned export and backup 2022-04-04 18:30:51 +02:00
krateng b41203bac7 Fixed database inconsistencies introduced by overeager maintenance 2022-04-04 17:51:19 +02:00
krateng c647a57983 Implemented import from own export 2022-04-04 17:50:46 +02:00
krateng 1b087e92db Merge branch 'master' into v3 2022-04-04 16:56:51 +02:00
krateng 72b74eb27e Renamed import module to match v3 2022-04-04 16:25:21 +02:00
krateng 2748d0e360 Disabled ID reuse in database 2022-04-04 16:18:47 +02:00
krateng e0af117805 Added export functionality 2022-04-04 16:18:18 +02:00
krateng 153ab41ce7 Improved support for read-only config directory 2022-04-04 16:18:06 +02:00
krateng acc08693b3 Various fixes 2022-04-04 16:17:42 +02:00
krateng b510e52188 Implemented additional database maintenance 2022-04-03 17:51:27 +02:00
krateng ba5b0c8957 Fixed images in search results 2022-04-03 16:34:10 +02:00
krateng c8f678b600 Fixed removal of empty artists 2022-04-03 16:26:06 +02:00
krateng 31c6fe6243 Clearing entity cache after maintenance 2022-04-03 16:16:18 +02:00
krateng b96f0cfc08 Various fixes 2022-04-03 16:09:50 +02:00
krateng ca2596cfc9 Improved import feedback output logic 2022-04-01 19:43:33 +02:00
krateng c150a57090 Implemented importing from Spotify's one-year data export 2022-04-01 19:28:13 +02:00
krateng a833039ced Improved feedback of import 2022-04-01 18:19:21 +02:00
krateng d8821efeeb Implemented heuristics for Spotify import with inaccurate timestamps, GH-104 2022-04-01 17:53:36 +02:00
krateng 3389d6c5f5 Reworked import 2022-04-01 17:16:50 +02:00
krateng 8ed3923851 Fixed timestamp parsing for Spotify import, GH-104 2022-03-30 21:37:43 +02:00
krateng 608986b239 Added font preloading 2022-03-30 19:19:31 +02:00
krateng 2a1f188e37 Changed Spotify import to use all files and discard duplicates, GH-104 2022-03-30 17:38:56 +02:00
krateng 27cacbf658 Added ability to import multiple files, GH-104 2022-03-29 19:02:59 +02:00
krateng 3275e4ec5d Experimenting with more thread limitations 2022-03-29 18:47:59 +02:00
krateng 5d582d39aa Added confirmation prompt to random generation 2022-03-29 18:09:39 +02:00
krateng 3108b368ef Fixed continued scrobble import after error 2022-03-29 17:41:16 +02:00
krateng 38f2173bde Added handling for invalid Spotify scrobbles 2022-03-29 17:27:34 +02:00
krateng e611d05c34 Merge branch 'master' into v3 2022-03-29 04:50:15 +02:00
krateng 97aed7e73c Fixed inconsistent Dockerhub secrets 2022-03-29 04:46:06 +02:00
krateng 634df2ffaf Version bump 2022-03-29 04:34:40 +02:00
krateng eea2e917f5 Fixed password setup 2022-03-29 04:33:44 +02:00
krateng 04947cb97d Switched to explicit server execution 2022-03-27 22:03:46 +02:00
krateng a598ba96de Fixes 2022-03-27 22:02:50 +02:00
krateng 9f8e691924 Moved image handling to top level module 2022-03-27 22:02:24 +02:00
krateng be4ed055ff Small fixes 2022-03-27 19:52:51 +02:00
krateng e22ef4d268 Updated documentation 2022-03-27 05:57:58 +02:00
krateng c8ed894efb Updated tasks to new database architecture 2022-03-27 05:31:15 +02:00
krateng e31c0dce57 Reorganized tasks 2022-03-27 05:08:17 +02:00
krateng 3f098b6993 Merge branch 'master' into v3 2022-03-27 03:30:04 +02:00
krateng fa9fee758c Reworked import and added support for Spotify, GH-104 2022-03-27 03:10:54 +02:00
krateng c0bf8cb8ac Added identifying output to scrobbler sitescript 2022-03-27 01:02:42 +01:00
krateng e7663138c1 Fixed redirect loop 2022-03-26 06:01:05 +01:00
krateng 66bd69b49e Reworked image proxying / caching 2022-03-26 05:49:30 +01:00
krateng fec6686ccc Fixed docker publish action 2022-03-25 20:07:39 +01:00
krateng dad027677e More imports 2022-03-25 19:58:34 +01:00
krateng fce450fac3 Fixed another missing import 2022-03-25 19:55:58 +01:00
krateng 822895461e Fixed backup from web interface 2022-03-25 19:50:16 +01:00
krateng 66d703b623 Genericized containerfile 2022-03-24 03:38:06 +01:00
krateng 68fd6fe65f Fixed inconsistent Dockerhub secrets 2022-03-21 21:08:54 +01:00
krateng cc24d48e65 Added support for themes to procrastinate from actually needed work 2022-03-18 01:59:47 +01:00
krateng 1fff4eca6c Added more formal delimiters 2022-03-16 22:26:57 +01:00
krateng a443e6250e Added scrobble delete button 2022-03-16 22:26:39 +01:00
krateng c3e6dcd1eb More logging and documentation 2022-03-12 08:28:48 +01:00
krateng 634cb38dec Fixed separate caching for each combination of entity IDs 2022-03-11 05:30:45 +01:00
krateng c944a3d937 Reworked install scripts again 2022-03-10 21:46:35 +01:00
krateng cc2b984080 Updated requirements 2022-03-10 07:11:39 +01:00
krateng 34e0b0fd67 Reworked Dockerfile 2022-03-10 07:11:00 +01:00
krateng 4dd7cf69a7 Replaced pkg_resources with importlib 2022-03-10 05:31:41 +01:00
krateng 36b47368a3 Added Docker to dev instructions 2022-03-10 05:06:31 +01:00
krateng 30e973402b Adjusted development instructions to new system 2022-03-09 22:12:39 +01:00
krateng 4e1b099547 Fixed optional dependencies 2022-03-09 22:02:42 +01:00
krateng e3dc401ccf Automated all distribution files 2022-03-09 21:53:42 +01:00
krateng 7b89d227a3 Added experimental Alpine package 2022-03-09 20:10:50 +01:00
krateng c8e658af43 Potential performance improvements 2022-03-06 05:42:13 +01:00
krateng 2930d40685 Fixed search and image upload 2022-03-06 04:52:10 +01:00
krateng 02e3f17594 Hopefully fixed image proxying 2022-03-06 04:20:26 +01:00
krateng 57142bc327 Moved page load performance measuring to profiler module 2022-03-06 03:17:40 +01:00
krateng e398dd3ac1 A few fixes 2022-03-06 02:58:02 +01:00
krateng 48d88b208f Limited metadata requests 2022-03-06 02:30:29 +01:00
krateng 827b05da8f Added support for secrets, fix GH-101 2022-03-06 02:00:48 +01:00
krateng bdbb644d8e Complete reorganization of process control 2022-03-06 01:58:33 +01:00
krateng aff56c9069 Logging 2022-03-02 04:52:04 +01:00
krateng af57103300 Implemented request-local DB cache 2022-02-27 02:54:05 +01:00
krateng a1ef5a7791 Changed some default settings 2022-02-27 00:34:06 +01:00
krateng fe0d06af7e Consistency fixes 2022-02-27 00:33:55 +01:00
krateng 86c4261a96 Pinned github actions 2022-02-26 23:22:55 +01:00
krateng a4e06413d8 Cleanup 2022-02-26 22:47:41 +01:00
krateng b83eee559f Implemented full local image caching, fix GH-99 2022-02-26 22:36:55 +01:00
krateng 631fd941ec Cleanup 2022-02-26 21:51:33 +01:00
krateng c952fab440 I have committed various war crimes and cannot enter heaven as a result 2022-02-26 21:44:38 +01:00
krateng 65f3dac40a WHAT THE F*CK AM I DOING 2022-02-26 21:30:06 +01:00
krateng 349e0bb7ea Experimenting with DB connections in Jinja context 2022-02-26 21:07:48 +01:00
krateng 4cd16d73d3 Added profiler for testing, reorganized folders 2022-02-26 20:59:15 +01:00
krateng 2b75e1e50f Merge branch 'master' into v3 2022-02-23 05:48:17 +01:00
krateng 538daeb284 WIP automated scrobbler publishing 2022-02-23 05:46:06 +01:00
krateng d38cf8d4be Updated Readme 2022-02-23 05:45:28 +01:00
krateng 9f862dd89b Added support for navidrome scrobbling 2022-02-22 07:37:28 +01:00
krateng 54a73243cc Adjustments to dev testing 2022-02-21 04:42:20 +01:00
krateng d3258a7e63 Bugfixes and Docker dev test script 2022-02-20 05:18:05 +01:00
krateng 0f473599a7 Small refactor 2022-02-20 04:07:25 +01:00
krateng bde06deb4f Fixed cache trimming 2022-02-19 08:02:07 +01:00
krateng a367c7c573 Logging adjustments 2022-02-18 20:01:17 +01:00
krateng 9e4274f209 Another fix 2022-02-18 09:49:45 +01:00
krateng dba31867c6 Fixes 2022-02-18 09:26:06 +01:00
krateng 6b05dde7c0 Added hosted font 2022-02-18 09:25:58 +01:00
krateng 7b3e1bbaa6 More experimental DB caching 2022-02-18 08:26:28 +01:00
krateng 900ce51af0 Fixed database cleanup 2022-02-18 08:13:31 +01:00
krateng e980efa731 Small reorganization 2022-02-18 08:05:23 +01:00
krateng 42607cedb7 More DB cleanup and logging 2022-02-18 05:53:57 +01:00
krateng deb35ec042 Scrobbling fixes 2022-02-18 05:33:34 +01:00
krateng ef594c2546 Added regular database cleanup 2022-02-18 05:31:08 +01:00
krateng 7e62ddebf6 Fixed image uploading 2022-02-17 08:42:33 +01:00
krateng f645f73f1f Removed unused old image handling 2022-02-17 08:16:59 +01:00
krateng bfed3604c5 Fixes to image handling 2022-02-17 08:11:01 +01:00
krateng cf43a9221a Switched image caching to SQL 2022-02-17 07:35:05 +01:00
krateng 4c40fb0577 Changed About page 2022-02-15 09:00:38 +01:00
krateng 765ab493cb Added About page 2022-02-15 07:18:26 +01:00
krateng a1f8e96ae4 Reimplemented cache limitation 2022-02-15 05:52:44 +01:00
krateng cc060d650b Implemented caching 2022-02-15 05:20:27 +01:00
krateng d9f4021342 Fixed duplicate artist association rules 2022-02-15 04:45:42 +01:00
krateng b95d1e8b0c Even more fixes 2022-02-14 06:45:53 +01:00
krateng 73564eccc1 More fixes 2022-02-14 06:42:27 +01:00
krateng b53df53c40 Fixes 2022-02-14 06:39:18 +01:00
krateng fee94a88c5 Scrobble origin is now saved 2022-02-14 06:07:54 +01:00
krateng 78c50d24d9 Updated backup 2022-02-14 05:11:55 +01:00
krateng 055dca4b6d Reimplemented search function 2022-02-13 08:21:26 +01:00
krateng 8db87bdbc5 Various fixes 2022-02-13 07:45:22 +01:00
krateng a64d3610d3 Fixed convoluted old scrobbling functionality 2022-02-13 06:15:29 +01:00
krateng 034f8b32c7 Merge branch 'master' into v3 2022-02-13 05:57:53 +01:00
krateng 38e2a184af Added password confirmation prompt 2022-02-13 05:56:36 +01:00
krateng d3797d89fa Fixed Plex scrobbling again 2022-02-10 08:32:44 +01:00
krateng 4f7e1decd3 Added rules 2022-01-28 06:07:02 +01:00
krateng b3002b1578 Fixed Plex scrobbling (new web interface) 2022-01-26 07:34:17 +01:00
krateng efad4379be Added log output, GH-98 2022-01-16 16:49:17 +01:00
krateng b325fab698 Fixing and renaming 2022-01-10 05:05:54 +01:00
krateng eb9d29686b Ported reasonable changes from the eldritch branch 2022-01-10 04:51:58 +01:00
krateng b50afe70ea Minor stuff 2022-01-09 21:03:28 +01:00
krateng eb9cd4aba4 Reimplemented caching of yearly and weekly stats 2022-01-09 06:58:06 +01:00
krateng df07dd7b00 Feels good man 2022-01-09 01:19:13 +01:00
krateng 7021099e7b Removed compare functionality 2022-01-09 01:14:06 +01:00
krateng 1df51748b6 Implemented artist and track info, improved performance of artist page 2022-01-08 06:11:42 +01:00
krateng 632905a1c7 Implemented associated artists 2022-01-07 21:47:55 +01:00
krateng 65a076c249 Replaced old camelCase functions 2022-01-07 04:57:13 +01:00
krateng 6611ca8705 Implemented top artists and tracks 2022-01-07 04:53:35 +01:00
krateng c120850d42 Implemented pulse 2022-01-07 04:38:41 +01:00
krateng 02ddeb4dc0 Implemented artist track charts 2022-01-07 04:30:23 +01:00
krateng f68fe04760 Implemented track charts 2022-01-07 04:07:10 +01:00
krateng 11bebce807 Implemented associated artists for artist charts 2022-01-07 03:50:21 +01:00
krateng 1824a8e5dc Fixed orderings 2022-01-06 20:29:34 +01:00
krateng 8a96a2c144 Reorganized sql module, implemented artist charts 2022-01-06 20:07:55 +01:00
krateng 44a124e6ec More experimenting with database architecture 2022-01-06 09:28:34 +01:00
krateng 80acf6275f Moved API key checking to proper module 2022-01-06 05:19:56 +01:00
krateng 40e733a054 Implemented aggregating by artist 2022-01-05 08:16:55 +01:00
krateng 9fc838e4c8 Implemented aggregating by track 2022-01-05 04:58:58 +01:00
krateng 2f7f4c8567 Implemented getting scrobbles by artist and track, more refactoring 2022-01-04 22:14:27 +01:00
krateng 03186bc49f More refactoring 2022-01-04 20:45:15 +01:00
krateng f88852ee6a We got the first working webpage! 2022-01-04 08:08:38 +01:00
krateng 0dd6cd9dd5 I promise this is temporary code! 2022-01-04 07:55:07 +01:00
krateng 8ab42b844b Removed shutdown handling 2022-01-03 20:45:55 +01:00
krateng 03dd902e1b Split up DB module a bit 2022-01-03 08:01:49 +01:00
krateng c826b069e4 More work 2022-01-03 03:04:36 +01:00
krateng 0233adedec Implemented base scrobble functions 2022-01-03 02:46:19 +01:00
krateng 9eb8dc0b47 Initial work on SQLite 2022-01-03 02:08:02 +01:00
krateng 68a450672e Fixed version display in backend 2022-01-01 06:59:36 +01:00
krateng ccbb3d3a80 Version bump 2022-01-01 06:48:49 +01:00
krateng f16334aaac Fixes 2022-01-01 06:46:03 +01:00
krateng 9370e62a47 Restored API info, fix FoxxMD/multi-scrobbler#38 2022-01-01 06:43:02 +01:00
krateng 7d979e92fe Added lots of rulesets 2022-01-01 05:07:12 +01:00
krateng b5bf87bf6b Slightly changed import architecture 2022-01-01 04:30:19 +01:00
krateng 4c9f824bbd Added other Maloja instance as third party option 2021-12-31 23:13:22 +01:00
krateng e62c637aa0 Implemented initial support for importing scrobbles 2021-12-31 23:13:08 +01:00
krateng b8fc3db371 Removed duplicate jinja context 2021-12-31 23:12:18 +01:00
krateng 6ac86d3d19 Fixes 2021-12-31 21:48:42 +01:00
krateng 9ec52806c4 Made image resizing optional again to keep Docker image size down 2021-12-31 21:27:22 +01:00
krateng 3714aef878 Moved to slightly smaller image library 2021-12-31 20:21:14 +01:00
krateng eff806bd73 Potentially fixed pyproject-based build and upload 2021-12-31 07:06:42 +01:00
krateng 21d1643988 Aight if this doesn't work I'm switching to Rust 2021-12-31 06:10:47 +01:00
krateng 868b8396a0 Sorry to all the docker people for the constant pointless releases 2021-12-31 05:46:47 +01:00
krateng f806fb8ed2 -.- 2021-12-31 05:26:09 +01:00
krateng cd8e0ff90a More fixing 2021-12-31 05:24:04 +01:00
krateng fb2dff8add Temporary fix for divergent package and project name 2021-12-31 05:16:26 +01:00
krateng 1b0e3ffdb2 Version bump 2021-12-31 04:30:45 +01:00
krateng f71bd72825 Fixed imagemagick in Docker 2021-12-31 04:22:19 +01:00
krateng 1e3ea1fba9 Readme improvements and test fix 2021-12-30 07:29:55 +01:00
krateng 57c090bdcd Updated API tests 2021-12-27 21:27:14 +01:00
krateng fe1ed955cd Added username/pw authentication for last.fm 2021-12-27 20:18:56 +01:00
krateng 37bac06735 Fixed scrobble forwarding to Last.fm 2021-12-27 18:32:33 +01:00
krateng b704aa8092 Added folder for dev scripts 2021-12-26 22:50:07 +01:00
krateng 9b23c3bd57 Small fixes 2021-12-26 21:37:14 +01:00
krateng 83f1956256 Removed tuple-version use 2021-12-26 21:36:58 +01:00
krateng af569ae983 Added backup from web interface 2021-12-26 20:53:38 +01:00
krateng 03e741c0ae Removed old import 2021-12-26 20:26:14 +01:00
krateng f56e23db1e Reverted python upgrade for now 2021-12-26 20:22:17 +01:00
krateng 84e8ac0139 Updated predefined rules 2021-12-26 19:53:14 +01:00
krateng 6c7e94fc20 Updated scrobbler 2021-12-26 19:20:43 +01:00
krateng 0ec93d7d0f Reworked pkginfo again 2021-12-25 23:43:34 +01:00
krateng 5228a12e3f Added indicator that page is already dark
see https://github.com/darkreader/darkreader/issues/1285
2021-12-25 22:44:44 +01:00
krateng cdd762a07a Adjusted pkginfo 2021-12-25 22:02:28 +01:00
krateng 61b1271491 Finished basic dev instructions, close GH-57 2021-12-25 19:05:34 +01:00
krateng c82936ad4a Adjustments 2021-12-25 04:06:29 +01:00
krateng 6aee8c7a48 Implemented web interface for API key management, close GH-24 2021-12-25 03:30:39 +01:00
krateng 115be57651 More html fixing 2021-12-25 03:29:22 +01:00
krateng 898a9930bb Improved HTML semantics 2021-12-25 03:12:03 +01:00
krateng 2a642c5f80 Automated admin interface tabs 2021-12-25 02:51:26 +01:00
krateng 7f650e604e Moved to new api key handling 2021-12-25 02:22:57 +01:00
krateng a1ba5f58b8 Minor improvements 2021-12-24 21:23:02 +01:00
krateng 6fc2c1c889 Added more data to maloja info command 2021-12-24 09:41:41 +01:00
krateng 1be4e50b49 Fixes 2021-12-24 07:38:24 +01:00
krateng f2dd5862e2 Added workflow for library package 2021-12-24 07:15:59 +01:00
krateng 5651626c39 Moved extra paackages 2021-12-24 07:08:28 +01:00
krateng 73a6c18b17 Repurposed __pkginfo__ module 2021-12-24 06:30:19 +01:00
krateng c745d4a647 Created thin setup.py for compatibility 2021-12-24 05:57:18 +01:00
krateng c98fc592a1 Simplified install instructions 2021-12-23 18:13:36 +01:00
krateng e53588b402 Reworked everything again 2021-12-23 08:48:39 +01:00
krateng 9f4041de78 Fixed PyPI-based dockerfile 2021-12-23 08:30:24 +01:00
krateng 0f40ebbad9 Made Dockerfile installations one layer again 2021-12-23 08:20:31 +01:00
krateng 8661d9f7e4 Renamed pyproject file until ready for use 2021-12-23 07:59:51 +01:00
krateng 83ad648832 Updated development instructions 2021-12-23 07:51:08 +01:00
krateng 6798500398 Made dockerfiles use authorative information on packages 2021-12-23 07:48:22 +01:00
krateng 0d2599fb82 Factored out alpine installation steps 2021-12-23 07:44:04 +01:00
krateng b6551131ba Made psutil necessary requirement 2021-12-23 07:25:12 +01:00
krateng 06f178b58a Fixes 2021-12-23 07:24:24 +01:00
krateng 4aa1343cf1 Added experimental pyproject.toml 2021-12-23 06:51:17 +01:00
krateng 36c8f2654e Refactoring 2021-12-23 05:17:19 +01:00
krateng 1e70a523b2 Some minor linting and reorganizing 2021-12-22 20:37:14 +01:00
krateng 0ccd39ffd9 How tf did that even happen 2021-12-22 07:24:34 +01:00
krateng 2b55e3a7c9 Matched github actions 2021-12-22 07:16:31 +01:00
krateng 40648b66f3 Aaaaand more bugfixing 2021-12-22 05:53:41 +01:00
krateng 0f5ccd4645 Bugfixes 2021-12-22 05:45:26 +01:00
krateng cefed03bc9 Fixed inclusion of dotfiles 2021-12-22 05:38:14 +01:00
krateng 8555b28fbc Version bump 2021-12-22 05:25:32 +01:00
krateng e4b63bb570 Merge branch 'newsettings' 2021-12-21 23:12:05 +01:00
krateng 3ee68e75ac Ready to finally merge, fix GH-25 2021-12-21 23:05:36 +01:00
krateng 2fa9eeaa07 Adjusted dev instructions further 2021-12-21 22:39:07 +01:00
krateng 72941558d2 Small adjustments 2021-12-21 22:13:27 +01:00
krateng 14938d8fb5 Added simpler way to run server for development 2021-12-21 21:33:03 +01:00
krateng 5ba455dc92 Added initial basic dev instructions, GH-57 2021-12-21 20:11:51 +01:00
krateng ca78463989 Updated requirements 2021-12-21 19:58:20 +01:00
krateng 7299c2e07e Finishing touches 2021-12-21 07:30:38 +01:00
krateng 97e8d5b18d Removed thumbor 2021-12-21 07:11:24 +01:00
krateng e94607dc2c Finished globalconf rework (I hope?) 2021-12-21 07:09:25 +01:00
krateng e006a10f70 Removed old files 2021-12-21 06:28:50 +01:00
krateng ef04c98ea7 Added new sentinel files for future 2021-12-21 06:09:58 +01:00
krateng b212e6b921 Adjusted globalconfig to new configuration module 2021-12-21 06:09:46 +01:00
krateng 77667e7066 Visual adjustments 2021-12-20 02:31:54 +01:00
krateng 5a2ec61d66 Updated requirements 2021-12-20 00:55:31 +01:00
krateng ba96c8e14f More fixes 2021-12-20 00:15:22 +01:00
krateng 1ec283f885 Added descriptions, fixed various things 2021-12-19 23:40:30 +01:00
krateng df22a595b8 Replaced settings calls in jinja files 2021-12-19 22:25:01 +01:00
krateng 6ccbf68923 Replaced more settings calls, added missing settings 2021-12-19 22:18:42 +01:00
krateng 4186171b8f Replaced settings calls 2021-12-19 21:41:43 +01:00
krateng b39e2b889a Added description for some settings 2021-12-19 21:04:43 +01:00
krateng ed309dd36e
Merge pull request #95 from FoxxMD/dockerDoc
Improve docker run documentation
2021-12-17 04:39:31 +01:00
FoxxMD 27cc4171fe Improve docker run documentation
* Reword host advice to provide actual environment variable with example value
* Provide an example run command for IPv4 usage
2021-12-16 09:20:04 -05:00
krateng de05090d7d Removed automatic setup processes when importing only metadata module 2021-12-16 06:51:18 +01:00
krateng b5999dab1f More adjustments to PyPI workflow 2021-12-16 06:37:52 +01:00
krateng 54ad3f6d3b
Update pypi.yml 2021-12-16 06:28:53 +01:00
krateng 647e942a35
Update dockerhub.yml 2021-12-16 06:26:25 +01:00
krateng 7774d9a936 Fixed issue with first setup, GH-94 2021-12-16 06:19:26 +01:00
krateng 3d573b10f9 Automated Dockerhub Readme updates 2021-12-15 22:57:43 +01:00
krateng a24e26678a Updated Dockerhub workflow 2021-12-15 19:47:34 +01:00
krateng c0c474b473
Merge pull request #93 from FoxxMD/dockerFromSource
Improve docker builds and documentation
2021-12-15 18:40:49 +01:00
krateng 319519b7e2 Adjusted Dockerhub workflow 2021-12-15 18:36:00 +01:00
FoxxMD a988f0e6dc Improve docker builds and documentation
* Refactor Dockerfile to build from source instead of pypi
* Include an additional dockerfile for building from pypi
* Update readme documentation for docker to specify image sources and docker-specific run instructions
2021-12-15 09:53:33 -05:00
krateng 1303891a2d How have I never noticed this 2021-12-15 07:47:36 +01:00
krateng 1a689d3a1c Added action to publish to PyPI 2021-12-15 07:19:01 +01:00
krateng 8958eb1b54 Updated Readme 2021-12-14 21:43:34 +01:00
krateng e597ba8504 More work on new settings 2021-12-14 21:19:15 +01:00
krateng 975e57ea46
Merge pull request #92 from FoxxMD/apiKeysLoadOrder
Load api keys before building database for api accessibility
2021-12-14 18:51:42 +01:00
FoxxMD e582e5abe2 Load api keys before building database for api accessibility
Allows access to /mlj_1/test without database having to be built
2021-12-14 09:13:09 -05:00
krateng d65def2ddf
Merge pull request #90 from FoxxMD/ghDockerDeploy
Build and deploy docker image on master commit or tag
2021-12-13 16:58:35 +01:00
FoxxMD a71086c10d Build and deploy docker image on master commit or tag
Automate docker image deployment to dockerhub using repository triggers
2021-12-13 09:20:03 -05:00
krateng 09e484021c Began work on new settings implementation 2021-12-12 18:43:24 +01:00
krateng 383c24511f Added ability to add custom css files, fix GH-79 2021-12-12 13:53:02 +01:00
krateng 98ec5885e7 Minor improvements 2021-12-11 20:40:52 +01:00
krateng 86f8d5a3eb Cleanup 2021-12-11 04:56:53 +01:00
krateng a85ec372f2 Simplified timestamp desc further 2021-12-10 22:20:36 +01:00
krateng 05460f97b7 Refactored timestamp descriptions 2021-12-10 22:01:29 +01:00
krateng 3b8723790a Refactored time objects 2021-12-10 21:31:09 +01:00
krateng acace4d04a Cleaned up time range handling 2021-12-10 21:08:44 +01:00
krateng 59eaa2264a Minor cleanup of image functions 2021-12-09 22:58:43 +01:00
krateng a52c494e4b Moved endpoints back out of functions 2021-12-09 21:12:37 +01:00
krateng d23da91101 Fixed CSS errors 2021-12-09 20:22:45 +01:00
krateng 56eaa8a793 Error handling details 2021-12-09 19:49:53 +01:00
krateng e17002299b Cleaned up database 2021-12-09 06:35:43 +01:00
krateng e110ed765f Removed superfluous temp endpoint stuff 2021-12-09 06:33:37 +01:00
krateng e901ca719f Improved error handling and output 2021-12-09 06:26:06 +01:00
krateng 83063ba943 Added error for unbuilt database 2021-12-09 06:24:34 +01:00
krateng b4230c0ae6 Reorganized main loop and added temporary endpoints during DB build, GH-88 2021-12-09 05:49:25 +01:00
krateng 6d5b306d93 Updated setup of API secrets 2021-11-27 20:04:13 +01:00
krateng a41aa0962b Added Deezer and TheAudioDB as metadata providers 2021-11-27 19:19:58 +01:00
krateng 898dd9735c Fixed Spotify auth if not in use, fix GH-87 2021-11-21 17:14:22 +01:00
krateng 2101223440 More design changes 2021-11-21 17:06:20 +01:00
krateng be7780a0c0 Expanded scrobble generation 2021-11-18 06:47:03 +01:00
krateng ff8e5ec8ff Commenting and slight Spotify search improvement 2021-11-18 06:45:44 +01:00
krateng e236cce86c Design changes 2021-11-17 22:18:04 +01:00
krateng d9cd546952 Moved main loop to function 2021-11-17 19:10:49 +01:00
krateng 29f8c10167 Minor UI / output changes 2021-11-17 19:10:28 +01:00
krateng 26dfdfb569 Minor cleanup and better logging 2021-11-16 18:22:46 +01:00
krateng 817d98e467 Added new scrobble generation for testing 2021-11-14 18:08:09 +01:00
krateng 504bfb405e Some design changes 2021-11-14 18:07:49 +01:00
krateng 9b98b3db3c Listenbrainz API now returns user name 2021-11-12 17:23:55 +01:00
krateng 33af60ed2c Updated listenbrainz endpoint, should fix GH-86 2021-11-12 17:16:59 +01:00
krateng 5157ce825e Updated dependencies for Python 3.10, fix GH-85 2021-10-20 19:08:21 +02:00
Brian Pepple d1b598a32b
Refactoring (#83)
* Merge isinstance calls

* Inline variable that is immediately returned

* Replace set() with comprehension

* Replace assignment with augmented assignment

* Remove unnecessary else after guard condition

* Convert for loop into list comprehension

* Replace unused for index with underscore

* Merge nested if conditions

* Convert for loop into list comprehension

* Convert for loop into set comprehension

* Remove unnecessary else after guard condition

* Replace if statements with if expressions

* Simplify sequence comparison

* Replace multiple comparisons with in operator

* Merge isinstance calls

* Merge nested if conditions

* Add guard clause

* Merge duplicate blocks in conditional

* Replace unneeded comprehension with generator

* Inline variable that is immediately returned

* Remove unused imports

* Replace unneeded comprehension with generator

* Remove unused imports

* Remove unused import

* Inline variable that is immediately returned

* Swap if/else branches and remove unnecessary else

* Use str.join() instead of for loop

* Multiple refactors

- Remove redundant pass statement
- Hoist repeated code outside conditional statement
- Swap if/else to remove empty if body

* Inline variable that is immediately returned

* Simplify generator expression

* Replace if statement with if expression

* Multiple refactoring

- Replace range(0, x) with range(x)
- Swap if/else branches
- Remove unnecessary else after guard condition

* Use str.join() instead of for loop

* Hoist repeated code outside conditional statement

* Use str.join() instead of for loop

* Inline variables that are immediately returned

* Merge dictionary assignment with declaration

* Use items() to directly unpack dictionary values

* Extract dup code from methods into a new one
2021-10-19 14:58:24 +02:00
krateng c31770a34c Version bump due to faulty upload 2021-10-14 18:15:24 +02:00
krateng d3e45b138b Added ability to set own time format, GH-82 2021-10-14 18:14:03 +02:00
krateng 08cc32ac33 Various 2021-10-14 18:02:11 +02:00
krateng 4b49ffb3cb Updated scrobbler icons 2021-08-30 19:44:25 +02:00
krateng f50728d2a6 Merge branch 'master' of github.com:krateng/maloja 2021-08-30 19:21:57 +02:00
krateng a55343f442 Changed from LESS to CSS custom properties 2021-08-30 19:21:04 +02:00
krateng 01fbc8c3d3
Clarified settings, GH-77 2021-08-24 04:54:09 +02:00
krateng 9037403777 Updated favicon 2021-08-10 17:36:31 +02:00
krateng 49598e914f Version bump 2021-07-31 19:07:21 +02:00
krateng 738a2d84e5 Merge branch 'master' of github.com:krateng/maloja 2021-07-31 19:06:58 +02:00
krateng 79716debf8 New logo 2021-07-31 19:00:32 +02:00
krateng 947614ddcf
Merge pull request #76 from tdemin/master
Group LOONA subunits under a single group
2021-07-14 17:33:01 +02:00
Timur Demin ced5e54074
Group LOONA subunits under single group 2021-07-14 16:38:33 +05:00
krateng 8ebd27ab76 Added some output for errors in import and start 2021-05-24 15:04:22 +02:00
krateng 5455abd0d1 Fixed silly oversight 2021-05-23 23:40:49 +02:00
krateng a652a22a96 Rules 2021-05-23 16:00:46 +02:00
krateng e664a93ef0 Rules 2021-04-15 20:09:57 +02:00
krateng 1727e0c92d Added Readme for library 2021-03-18 19:11:02 +01:00
krateng 1aeb72fd8f Added Python library for music players / scrobblers 2021-03-18 19:01:38 +01:00
Krateng 0cfdc60111 Merge branch 'master' of github.com:krateng/maloja 2021-02-24 16:13:32 +01:00
Krateng 2903a88096 Scrobbler, rule, e-mail 2021-02-24 16:11:37 +01:00
krateng b75b643f6b
Update README.md 2021-02-06 04:52:35 +01:00
krateng 642cc7c00b
Update README.md 2021-01-31 17:35:33 +01:00
Krateng 6199fc8204 Why do these things happen 2021-01-24 01:44:04 +01:00
Krateng 23cc1ac341 Third party improvements 2021-01-21 17:38:37 +01:00
Krateng c2f8ecc2df Version bump 2021-01-17 04:20:59 +01:00
Krateng 6555eea887 Fixed another time bug 2021-01-17 03:09:48 +01:00
Krateng f67f900dae Fixed image uploading 2021-01-16 20:11:06 +01:00
Krateng 49b0a57581 Bumped Python version requirement to oldest supported 2021-01-16 17:25:52 +01:00
Krateng 06c32e205e This time for real tho 2021-01-15 20:09:34 +01:00
Krateng 2a5d9498d1 Restored functionality for older Python versions 2021-01-15 19:57:53 +01:00
Krateng 5006ad2bf1 Version bump 2021-01-15 18:45:42 +01:00
Krateng bcd62c16fa Fixed time issues 2021-01-15 18:39:52 +01:00
Krateng c4a9c6dc0f Fixed scrobbles view 2021-01-12 18:47:18 +01:00
Krateng a42ed56d2d Updated testing 2021-01-10 17:00:16 +01:00
Krateng 6efe4a48c7 Fixed handling of special characters in manual scrobbling, fixes GH-68 2021-01-10 16:12:41 +01:00
Krateng 5bfd1e49dd Updated manual scrobbling, GH-68 2021-01-10 15:45:59 +01:00
Krateng c86f3597fd Added proper multi-artist scrobbling via API 2021-01-10 15:30:11 +01:00
Krateng 723efcb8ba Made artist delimiters a setting, fix GH-66 2021-01-06 22:09:07 +01:00
Krateng adfd6d2fc2 Fixes 2021-01-06 21:53:43 +01:00
Krateng b53052c818 Housekeeping 2021-01-01 04:13:58 +01:00
Krateng 4d06a327e9 Happy New Year! 2021-01-01 03:10:36 +01:00
Krateng ac8abd4cb6 Updated Readme 2020-12-29 17:03:58 +01:00
Krateng f5f73d9223 Removed debug logging, fix GH-63 2020-12-25 17:11:09 +01:00
Krateng db80e1791a Fixed backup 2020-12-25 16:52:25 +01:00
Krateng f44c3fecb2 Fixed more complicated user file references 2020-12-25 05:24:59 +01:00
Krateng 0818dccac4 Fixed most references to user files 2020-12-25 04:52:05 +01:00
Krateng fe2b0dd7c8 Reworked initial determination of user folders, GH-63 2020-12-25 04:41:33 +01:00
Krateng ce17f77cfd Added setup.py, fix GH-27 2020-12-13 03:50:04 +01:00
Krateng 5fe4c5cd22 Added alternative to loading bar, fixes GH-54 2020-12-13 03:38:46 +01:00
Krateng 218313f80c Fixed requirement 2020-12-13 03:24:40 +01:00
Krateng 4e69a6119d Merge branch 'master' of github.com:krateng/maloja 2020-12-13 03:09:03 +01:00
krateng 8b5254e4c7
Merge pull request #58 from FoxxMD/displayLocalTimezone
Display full datetime using system timezone
2020-12-13 03:08:35 +01:00
Krateng 64bb235011 Fixed daily execution of tasks for different timezones 2020-12-13 02:18:46 +01:00
Krateng 772f207b7e Cleaned up some imports 2020-12-03 20:12:49 +01:00
Krateng f6b9f2b9fa Fix GH-61 2020-12-03 18:37:30 +01:00
Krateng 212fbf368e Database fixing now finally respects existing separation 2020-12-02 21:08:44 +01:00
Krateng f62fd254dd Version bump 2020-12-02 20:15:20 +01:00
Krateng 7a3ee26c87 Fixed wait page after database rebuild, closes GH-59 2020-12-02 20:09:19 +01:00
Krateng 3de4059f85 Fixed database maintenance, GH-59 2020-12-02 19:18:12 +01:00
FoxxMD 75d8251a29 Use system timezone as TIMEZONE setting default 2020-12-01 15:48:56 -05:00
Krateng b9feebb064 Merge branch 'master' of github.com:krateng/maloja 2020-12-01 20:32:38 +01:00
Krateng 74dc54862e Added progress bar to database build, GH-54 2020-12-01 20:32:29 +01:00
Krateng e6535eb6bc Adjusted predefined ranges to consider timezone 2020-12-01 19:19:01 +01:00
Krateng 951f6bb562 Timeranges should now consider timezone 2020-12-01 19:12:20 +01:00
Krateng e475b0c716 Added setting for timezone 2020-12-01 18:59:39 +01:00
Krateng e1c9d525f5 Updated Spotify scrobbler 2020-12-01 16:49:19 +01:00
FoxxMD c87dc32455 Display full datetime using system timezone
When displaying a full datetime to the user use the system timezone instead of hardcoded UTC
2020-11-30 14:02:03 -05:00
krateng 4304aab8b7
Merge pull request #55 from FoxxMD/multiScrobblerReadme
Update mulit-scrobbler description and repo name
2020-11-23 21:55:48 +01:00
FoxxMD 5fa1396ddc Update mulit-scrobbler description and repo name 2020-11-23 12:19:47 -05:00
krateng 58050513af
Update README.md 2020-11-16 19:05:29 +01:00
krateng 9cdc2cf103
Merge pull request #53 from FoxxMD/readmeDocker
Update dockerhub repository
2020-11-16 19:03:47 +01:00
FoxxMD bd2a3d46ae Update dockerhub repository
Use repository that builds changes automatically. FoxxMD repository is not automated and out of date.
2020-11-16 12:22:31 -05:00
Krateng 034bd064f1 Improved artist fixing (hopefully) 2020-11-15 20:19:29 +01:00
Krateng 755567549c Added makeshift pagination to api scrobbles list, fixes GH-52 2020-11-14 19:42:23 +01:00
Krateng b84712ef22 Updated another old API url, GH-51 2020-11-11 19:05:10 +01:00
Krateng 3cf0dd9767 Cleanup and version bump 2020-11-11 17:45:40 +01:00
Krateng cfec8e089b Updated API url, should fix GH-51 2020-11-11 17:44:57 +01:00
Krateng c216aa5f24 Added ability to skip metadata fixing with existing known tracks 2020-11-06 20:43:55 +01:00
Krateng b38267a09f Fixed scrobbles link in artist charts 2020-11-06 20:25:00 +01:00
Krateng df6ad4f073 Legacy audioscrobbler should now use correct scheme automatically 2020-11-01 22:14:55 +01:00
Krateng 5b7d1fd8e9 Added ability to use legacy audioscrobbler without SSL 2020-11-01 18:05:11 +01:00
Krateng 9a6617b4b1 Merge branch 'master' of github.com:krateng/maloja 2020-10-31 19:58:08 +01:00
Krateng e70cb3e037 API improvements 2020-10-31 19:57:49 +01:00
krateng 8a0d15d86e
Added --upgrade flag to docker installation, fix GH-46 2020-10-30 16:54:31 +01:00
Krateng ac29f9728e Prettified settings overview 2020-10-29 19:16:38 +01:00
Krateng 79d7af5025 Added settings overview 2020-10-29 19:04:23 +01:00
Krateng f555ee9d9f Version bump 2020-10-29 17:26:23 +01:00
Krateng 663c6a0372 Merge branch 'master' of github.com:krateng/maloja 2020-10-29 17:25:46 +01:00
krateng 1940f62260
Merge pull request #37 from ICTman1076/patch-3
Create legacy audioscrobbler API
2020-10-29 17:23:12 +01:00
Krateng 724bfd7164 Updated postman collection 2020-10-29 17:21:50 +01:00
Krateng e8c19a05e4 Implemented scrobbling 2020-10-29 17:14:22 +01:00
Krateng 8cf93c3100 Implemented handshake 2020-10-29 16:46:45 +01:00
Krateng 5c2928e13b Renaming and organization 2020-10-29 15:56:51 +01:00
krateng 27c65703bc
Update Dockerfile
Avoid caching, GH-41
2020-10-24 23:50:04 +02:00
krateng 0f10f278b5
Update README.md
GH-42
2020-10-20 17:58:21 +02:00
Krateng cba9d839a2 Version bump 2020-10-18 20:08:07 +02:00
Krateng ce9d882856 Listenbrainz artwork urls are now converted to https, fixes GH-41 2020-10-18 19:08:47 +02:00
Krateng c125cbc20d Fixes 2020-10-18 19:06:44 +02:00
Krateng 2705c524f9
Updated Readme 2020-10-09 17:40:12 +02:00
Krateng 54c3eb9cc0
Removed inline handlers in chromium extension settings, fixes GH-38 2020-10-08 17:08:52 +02:00
Krateng 9f63504633
Updated API URL in Readme 2020-10-08 16:45:01 +02:00
Krateng 9e03a01e70
Merge branch 'master' of github.com:krateng/maloja 2020-10-05 03:15:15 +02:00
Krateng 90d6e67e97
Added ability to discourage CPU-heavy statistics 2020-10-05 03:15:06 +02:00
krateng 21b27d17c5
Merge pull request #34 from p182/patch-1
Fixed double escaping when getting metadata from musicbrainz
2020-10-02 14:59:03 +02:00
krateng 38d4716dc5
Extracting host from request 2020-10-02 04:35:36 +02:00
krateng 05fca5c7c0
Merge pull request #36 from ICTman1076/patch-2
Add 3 new track search services
2020-10-02 04:14:56 +02:00
ICTman1076 a4722f9e55 Fix some bugs 2020-10-01 21:06:38 +01:00
ICTman1076 fcee13214b Create legacy audioscrobbler API 2020-10-01 21:01:41 +01:00
ICTman1076 258b9c22d6
Update default.ini with new search services 2020-09-30 20:22:47 +01:00
ICTman1076 30f10ba46e
Add services to links.jinja
Beatport, Bandcamp and Qobuz
2020-09-30 20:18:28 +01:00
Павло 951c192adb
Update musicbrainz.py
URLEncode already do URL Quoting, so we don't need to do it before
2020-09-30 13:28:29 +03:00
Krateng cbd93d2b33
Fixed api key unpacking 2020-09-27 02:03:35 +02:00
Krateng 22172d8b57
Fixed GET scrobbling 2020-09-25 17:06:45 +02:00
Krateng c312608f2d
Cleaned some JS code 2020-09-25 16:03:51 +02:00
Krateng f7861c44b4
Version bump 2020-09-25 15:52:14 +02:00
Krateng 85f19a8b72
Reorganized search provider link snippet 2020-09-25 05:38:01 +02:00
Krateng 13f2746171
Fixed minor things for track search provider 2020-09-25 04:48:24 +02:00
Krateng 616c24fb5b
Merge branch 'master' of github.com:krateng/maloja 2020-09-25 04:46:59 +02:00
krateng b9d9f0ff03
Merge pull request #33 from ZackBoe/search-providers-jinja
Reimplement track search provider in jinja templates
2020-09-25 04:46:15 +02:00
Krateng f0cbe33f6a
Fixed bug in pulse / performance display when no local preference set 2020-09-25 04:09:31 +02:00
Zack Boehm 026415c7d8 Reimplement track search provider in jinja templates
Remove GPM, add Apple Music, fix Spotify. Change icon from emoji to svg
2020-09-24 17:31:55 -04:00
Krateng 5e93eb15f4
Changed html minifier 2020-09-23 16:26:54 +02:00
Krateng 98830b0803
Moved to new default data folder 2020-09-22 17:24:19 +02:00
Krateng 8250c7b467
Improved API logging slightly 2020-09-18 18:56:09 +02:00
Krateng 6dd3fe5512
Fixed key unpacking in native API 2020-09-17 05:09:28 +02:00
Krateng eb9763eb65
Updated restart recommendation 2020-09-16 23:03:48 +02:00
Krateng 6466b48796
Removed 2.0 update warning 2020-09-16 21:58:41 +02:00
Krateng 336e36fb79
Changed Readme 2020-09-13 17:21:45 +02:00
Krateng e0b990578e
Removed linked whitespace from topweeks 2020-09-13 10:44:47 +02:00
Krateng 2da9f154be
Merge branch 'master' of github.com:krateng/maloja 2020-09-13 10:26:05 +02:00
Krateng 6316e45265
Changed timeselection snippet to include 2020-09-13 10:24:34 +02:00
Johannes Krattenmacher b4d224fb66 Fixed batch scrobbling with Listenbrainz API 2020-09-07 20:26:23 +02:00
Krateng 60a06efad8
Fixed design of records on artist page 2020-09-06 18:54:31 +02:00
Krateng a462faf2bf
Changed redirect to more appropriate HTTP code 2020-09-06 18:19:48 +02:00
Krateng 160e393a00
Added postman data for testing APIs 2020-09-06 17:45:07 +02:00
Krateng f4a563f080
Fixed redirect from old API endpoints, GH-31 2020-09-06 16:56:22 +02:00
Krateng c1a4d5a4ee
Fixed typo 2020-09-05 17:05:09 +02:00
Krateng 9fb352cc6f
Listenbrainz API now nods and smiles on 'playing_now' requests 2020-09-05 16:59:25 +02:00
Krateng 09d3f10383
Added some debug output 2020-09-05 16:44:25 +02:00
Krateng a0a8e5bcc8
Updated Readme 2020-09-05 16:02:11 +02:00
Krateng e8a87cb8a5
Fixed bug in readable uri timerange keys 2020-09-04 18:24:55 +02:00
Krateng 7c8b0dd7cb
Updated scrobbler 2020-09-04 16:36:35 +02:00
Krateng e8c316f199
Fixed more bugs 2020-09-04 15:33:16 +02:00
Krateng 5cf7ca2e9b
Fixed issue with API redirect 2020-09-04 15:23:08 +02:00
Krateng 52a9faae90
Fixed scrobble bug 2020-09-04 14:32:11 +02:00
Krateng 8b4e9609e9
Version bump 2020-09-04 14:13:26 +02:00
Krateng 9d3ffae45c
Merge branch 'api_modularization' 2020-09-04 14:07:21 +02:00
Krateng 35616212ff
Various fixes 2020-09-04 13:59:04 +02:00
Krateng e4139369fe
Various small issues 2020-09-04 12:18:34 +02:00
Krateng 19c928773d
Improved scrobbler slightly 2020-09-04 04:45:38 +02:00
Krateng 03c3202cdf
Significantly reworked maintenance 2020-09-04 03:48:24 +02:00
Krateng 79544044be
Slightly reworked maintenance 2020-09-04 02:58:17 +02:00
Krateng 5a548f1979
Moved utilities to submodule 2020-09-04 02:42:01 +02:00
Krateng 785e6cfa17 Refactored charts into includes 2020-09-04 00:28:48 +02:00
Krateng 0b54999a1c Updated Readme 2020-09-04 00:15:53 +02:00
Krateng dad75dbbc2 Implemented aliases 2020-09-04 00:08:24 +02:00
Krateng 879b3cf170 Audioscrobbler protocol should now work as well 2020-09-03 23:42:27 +02:00
Krateng a4812a66da First working version for new api achitecture 2020-09-03 20:04:16 +02:00
Krateng f61804b095 Added redirects for backwards compatibility 2020-09-02 22:24:35 +02:00
Krateng 8acf2ef503 Began factoring out API 2020-09-02 20:18:46 +02:00
Krateng a6088ec7b7 Merged remaining references to two uri modules 2020-09-02 15:22:53 +02:00
Krateng ed1c595e20 Fixed time-dependent jinja context 2020-09-02 14:10:47 +02:00
Krateng 26f6a1af58 Turned tiled charts into include 2020-09-02 14:03:03 +02:00
Krateng 7bc70ed0bd Updated scrobbler 2020-09-01 15:22:57 +02:00
Krateng f7f1b1225e Fixed performance view on artist page for uncredited 2020-09-01 14:26:00 +02:00
Krateng 10f636e7ed Removed rulestate consistency system 2020-09-01 01:19:10 +02:00
Krateng 01e555172f Added full html minification 2020-09-01 01:18:46 +02:00
Krateng 6acab324db Fixed dev mode css bug 2020-09-01 00:38:30 +02:00
Krateng e27a83bdc9 Reduced excessive whitespaces in jinja output 2020-09-01 00:35:14 +02:00
Krateng 1321fcb45e Fixed bug with json scrobbling 2020-08-31 23:04:39 +02:00
Krateng 25661f82af Version Bump 2020-08-31 22:59:29 +02:00
Krateng 4a811932ac Removed htmlgenerators.py 2020-08-31 22:33:37 +02:00
Krateng 6ca88685bf Replaced remaining htmlgenerator calls 2020-08-31 22:30:58 +02:00
Krateng f7ca7b0dc9 Replaced some old htmlgenerator calls with jinja macros 2020-08-31 21:11:57 +02:00
Krateng 363c431b93 Improved error page 2020-08-31 20:12:44 +02:00
krateng d85d13ce5a
Merge pull request #30 from ICTman1076/patch-1
Fix typo in wait.jinja
2020-08-31 18:00:32 +02:00
Krateng 316ad48ae6 Fixed scrobbling via JSON payload, fixes GH-31 2020-08-31 17:57:42 +02:00
ICTman1076 d788b14190
Fix typo in wait.jinja 2020-08-31 10:05:22 +01:00
Krateng b4f84625bc Added descriptions for HTTP errors 2020-08-31 04:36:39 +02:00
Krateng e5536ba384 Fixed error pages 2020-08-31 00:08:55 +02:00
Krateng d3c3c1fc4c Removed htmlmodules.py 2020-08-30 23:52:55 +02:00
Krateng e55975514f Refactored macros into includes and removed remaining htmlmodules 2020-08-30 23:51:24 +02:00
Krateng 22ee6bf751 Moved jinja handling to submodule 2020-08-30 23:49:14 +02:00
Krateng a5edc113c8 Removed non-jinja support 2020-08-30 19:02:48 +02:00
Krateng 65861d4c41 Implemented compare in jinja 2020-08-30 18:53:02 +02:00
Krateng 44a2739a3b Implemented pulse and performance in jinja 2020-08-30 03:08:45 +02:00
Krateng c6adc90d4b Implemented scrobbles in jinja 2020-08-30 02:05:54 +02:00
Krateng a45696ab62 Implemented top artists in jinja 2020-08-27 23:10:11 +02:00
Krateng 0abf2aae39 Implemented top tracks in jinja 2020-08-27 22:26:51 +02:00
Krateng 7fc879f778 Implemented start page in jinja 2020-08-27 18:17:08 +02:00
Krateng bc5f11d499 Implemented tiled charts for jinja 2020-08-27 17:26:56 +02:00
Krateng fddbfb6a41 Removed PYHP data 2020-08-27 16:08:52 +02:00
Krateng fb04dd507c Fixed bug in database maintenance page, should fix GH-29 2020-08-27 15:53:33 +02:00
Krateng 75bd823ad0 Fixed scrobbling with POST request 2020-08-26 05:27:01 +02:00
Krateng 3a4769cfb2 Some cleanup 2020-08-23 03:53:03 +02:00
Krateng 379ee49f1c Fixed bug with artist charts 2020-08-23 01:41:51 +02:00
Krateng 47087b4288 Fixed bug for trend indicators with unlimited time ranges 2020-08-23 01:38:56 +02:00
Krateng fa05c40660 Fixed bug with chart bars 2020-08-23 01:30:50 +02:00
Krateng 439d12d87f Implemented chart trend indicators 2020-08-23 00:30:55 +02:00
Krateng 3e6bcc45d5 Implemented pagination for jinja 2020-08-22 22:26:28 +02:00
Krateng 7693ba3a20 Removed top weeks indicator when it would be 0 2020-08-22 05:28:17 +02:00
Krateng 1563a15abd Improved jinja pages 2020-08-21 19:32:58 +02:00
Krateng 4113d1761e Aliases, debug info and robots 2020-08-21 18:06:16 +02:00
Krateng 0127a25ce5 Added password information to readme 2020-08-18 16:38:28 +02:00
Krateng 6885fbdecc Updated requirements 2020-08-18 16:31:07 +02:00
Krateng 8d7fb9a2c8 Version bump 2020-08-18 05:16:17 +02:00
Krateng 833048440c Merge branch 'backend' 2020-08-18 05:04:34 +02:00
Krateng 7f3b7031ac Updated Readme 2020-08-18 04:59:34 +02:00
Krateng 2484015261 Added setup step for password 2020-08-17 23:04:55 +02:00
Krateng 94794bff5b Removed on-the-fly database analysis 2020-08-17 18:28:31 +02:00
Krateng 6050e26f7a Added basic top info structure to base template 2020-08-17 18:16:09 +02:00
Krateng ae0da83a9c Moved abstract templates to own folder 2020-08-17 17:23:17 +02:00
Krateng 9a1bc8be03 Fixed error page 2020-08-17 17:14:38 +02:00
Krateng a88afe40ec Added ability to quickly prefill manual scrobble form with last scrobble 2020-08-17 16:52:30 +02:00
Krateng a103c360d3 Minor fixes 2020-08-16 20:08:56 +02:00
Krateng 3e1331b0e3 Reorganized backend 2020-08-16 19:48:35 +02:00
Krateng cb7a6d2241 Listenbrainz protocol should now properly work with batch scrobbles from Pano 2020-08-06 18:57:23 +02:00
Krateng bdfb2a4a0b Fixed token validation endpoint for Listenbrainz 2020-08-06 17:27:15 +02:00
Krateng ddbdc7ec56 Added FUTURE.md 2020-08-06 16:08:12 +02:00
Krateng 0bdc4654bf Added validate token endpoint to ListenBrainz API 2020-08-05 20:35:42 +02:00
Krateng 0ee6e761da Forcing SSL on third party images 2020-08-05 19:31:12 +02:00
Krateng 87cdb9987e Improved some logging 2020-08-05 15:45:52 +02:00
Krateng 0fdd7669cc Fixed error when missing Spotify key, fixes GH-26 2020-08-04 18:49:40 +02:00
Krateng 02e41ccbe0 Updated Readme 2020-07-30 15:38:43 +02:00
Krateng 7a4df06090 Spotify now re-authenticates 2020-07-30 15:15:09 +02:00
Krateng c44e14d0a6 Updated admin mode 2020-07-29 21:19:29 +02:00
Krateng ba701a2317 Removed unnecessary cross origin resource 2020-07-29 20:20:15 +02:00
Krateng b5b09c4052 Updated some methods to new authentication method 2020-07-29 20:11:51 +02:00
Krateng 0ddb5a4dd9 Updated server setup page 2020-07-29 17:49:55 +02:00
Krateng a0a8ba4052 Updated manual scrobbling page 2020-07-29 17:15:03 +02:00
Krateng 2c754c75ce Updated admin panel and issues page 2020-07-29 17:04:38 +02:00
Krateng 56cc06d905 Implemented proper authentication for backend 2020-07-29 15:52:01 +02:00
Krateng 5f8e73e6c7 Apparently ** glob doesn't work 2020-07-28 20:36:25 +02:00
Krateng 27f3ff6d08 Improved sorting of metadata providers 2020-07-28 20:33:26 +02:00
Krateng dd3c83920b Enabled custom sorting of metadata providers, GH-23 2020-07-28 20:12:50 +02:00
Krateng 1eae55e3bb Fixed race condition in search 2020-07-26 18:09:41 +02:00
Krateng b161da1c1a Cleanup 2020-07-26 13:00:38 +02:00
Krateng 331374e35c Updated default settings 2020-07-26 03:40:55 +02:00
Krateng 29f88539b4 Added MusicBrainz service for metadata, resolves GH-22 2020-07-26 03:27:10 +02:00
Krateng fe106a3227 Implemented artist metadata interface 2020-07-25 19:34:41 +02:00
Krateng b6a66ff2ed Added Spotify metadata service 2020-07-25 18:38:56 +02:00
Krateng 4c30ff5fa2 Implemented track image fetcher interface 2020-07-25 17:54:01 +02:00
Krateng a097d34f10 Merge branch 'master' into thirdparty_rework 2020-07-25 05:45:23 +02:00
Krateng f89dcf0599 Cleanup 2020-07-25 05:29:23 +02:00
Krateng abef221435 Simplified structure for third party services 2020-07-25 05:09:58 +02:00
Krateng d911a7a8c4 Added rule and bumped doreah version 2020-07-23 16:27:13 +02:00
Krateng 8659f98935 Fixed external link 2020-06-20 21:00:49 +02:00
Krateng bda279d01d Fixed some issues 2020-06-20 20:56:51 +02:00
Krateng 990131f546 Version bump 2020-06-20 20:27:59 +02:00
Krateng 895fcfd8c3 Merge branch 'processcontrolrework' 2020-06-20 20:27:38 +02:00
Krateng 00be885847 Updated requirements.txt 2020-06-20 18:24:30 +02:00
Krateng 4d10276cc1 Added some log output 2020-06-20 18:15:02 +02:00
Krateng 5f29cea6ad Merge branch 'master' into processcontrolrework 2020-06-20 18:10:13 +02:00
Krateng daa256fc3b Fixed db fix from main process 2020-06-20 18:06:08 +02:00
Krateng 398b737781 Fixed relative file hierarchy for backups 2020-06-20 18:04:20 +02:00
Krateng be79dc1888 Reorganized tasks 2020-06-20 17:52:26 +02:00
Krateng d48ffc964d Removed unused settings 2020-06-20 17:34:40 +02:00
Krateng 6c2eac550b Updated console script 2020-06-20 17:33:23 +02:00
Krateng 1835243678 Reorganized process control 2020-06-20 17:31:39 +02:00
Krateng 57403a89ab Updated database rebuild, should fix GH-16 2020-06-18 15:16:24 +02:00
Krateng 4c6b40e42f Added some sanity checks to cache reduction 2020-06-13 17:42:59 +02:00
Krateng 6658165bae Added setting for file logging (GH-19) 2020-06-13 17:34:30 +02:00
Krateng d551513733 Update install scripts 2020-06-07 15:01:05 +02:00
Krateng 5c6a901f51 Lowered recommended restart frequency 2020-06-06 16:47:59 +02:00
Krateng 92e6fea00f Merge branch 'master' of github.com:krateng/maloja 2020-06-06 16:46:39 +02:00
Krateng 1828bd35bb Can now use custom data directory with environment variable, close GH-18 2020-06-06 16:46:25 +02:00
Krateng e73e047af9 Reduced disk access for cache settings 2020-06-06 16:38:45 +02:00
krateng 7f2a8f3df3
Merge pull request #17 from Derkades/master 2020-06-06 16:06:36 +02:00
Robin e531dc4007
Remove pip3 and dependencies after installing 2020-06-06 11:53:42 +02:00
Krateng 08fe4695f6 High RAM usage affects all caches 2020-06-05 13:20:54 +02:00
krateng ef2a2c817e
Update README.md 2020-06-01 18:22:16 +02:00
Krateng 813dee8400 More incomplete jinja templates 2020-05-31 18:11:28 +02:00
Krateng 31585ec646 Updated Spotify scrobbler 2020-05-30 18:10:17 +02:00
Krateng 2f67f427f2 Various 2020-05-29 22:03:59 +02:00
Krateng 2df77f89e9 Added psutil to dockerfile 2020-05-29 17:43:34 +02:00
Krateng b21b27bb6e Made psutil optional 2020-05-29 17:39:19 +02:00
Krateng 98c1527f77 Added cache dump for high memory usage 2020-05-29 17:33:42 +02:00
Krateng c166620d5f Properly implemented cache debug logging 2020-05-29 16:45:41 +02:00
Krateng 65f9c88da4 Added missing dependency 2020-05-29 04:51:51 +02:00
Krateng 9b787fa3b1 Replaced DB caches with LRU dicts, hope this improves memory problems 2020-05-29 04:46:53 +02:00
Krateng d989134e65 Fixed some CSS 2020-05-28 19:45:30 +02:00
Krateng b117e6f7ec Fixed missing file inclusion 2020-05-28 19:37:19 +02:00
Krateng 6aa65bf1ce Added settings for caching 2020-05-28 19:32:41 +02:00
Krateng b8de507a4f Updated wrong file reference (GH-16) 2020-05-28 17:26:41 +02:00
Krateng bf6c00fde0 Merge branch 'master' of github.com:krateng/maloja 2020-05-17 14:20:55 +02:00
Krateng 471a61f788 Initial concept 2020-05-17 14:18:37 +02:00
Krateng 2cd5472751 Completed artist and track jinja templates 2020-05-17 14:16:14 +02:00
krateng 85f03e443e
Added Docker installation to readme 2020-05-17 11:46:41 +02:00
Krateng b3f4fc1246 Created first experimental Jinja templates 2020-05-17 01:58:24 +02:00
Krateng 1a64641fe6 Organized PYHP files 2020-05-13 22:57:55 +02:00
krateng 5103625078
Merge pull request #10 from MarcoBuster/docker
Docker support

Co-authored-by: kinduff <abarcadabra@gmail.com>
Co-authored-by: Matt Foxx <FoxxMD@users.noreply.github.com>
2020-04-28 21:24:56 +02:00
Krateng afe01c8341 Fixed db fixing with new file structure 2020-04-21 18:20:40 +02:00
Krateng b17060184b Fixing DB now creates individual patch files 2020-04-21 18:11:16 +02:00
Krateng 0ceb70b27e Updated Spotify scrobbling 2020-03-23 17:24:58 +01:00
Krateng b611387011 Server setup is now executed even when running directly 2020-03-21 17:49:52 +01:00
krateng fb882a1af4
Adjusted entrypoint command 2020-03-19 18:45:35 +01:00
Krateng a4f13f6923 Added alias for console-attached execution 2020-03-19 18:41:45 +01:00
Krateng eb82282e58 Fixed stopping main server from console 2020-03-08 01:27:55 +01:00
Krateng 9cf1fb3ed8 Temporary fix for supervisor issues 2020-03-08 01:18:31 +01:00
Krateng 5a08fd78c6 Added pkginfo 2020-03-06 17:06:19 +01:00
Krateng fb51f61597 Updated Readme 2020-03-02 06:02:51 +01:00
Krateng 7c6e2ad60f Added setting for non-interactive server start GH-10 2020-02-28 17:26:26 +01:00
Krateng 8793b149f5 Reworked supervisor 2020-02-28 16:29:56 +01:00
Krateng 55c68b21cd Various fixes 2020-02-28 16:23:59 +01:00
Krateng 83f73758c4 Can now load settings from environment variables GH-10 2020-02-28 16:22:57 +01:00
krateng a97a5e2fbf
Update Dockerfile
Updated to python package
2020-02-26 15:09:53 +01:00
Krateng d4b66ec673 Now saving duration and album information for later use 2020-01-31 23:03:40 +01:00
Krateng c6deb15437 Fixed bug with image upload 2020-01-25 05:10:36 +01:00
Krateng 23e076f93a Fixed Readme formatting error 2020-01-23 20:56:06 +01:00
Krateng 779436bc18 Expanded Readme 2020-01-23 20:52:53 +01:00
Krateng e397e644cb Reorganized Readme 2020-01-21 22:44:49 +01:00
Krateng 75aa8f86bb Added ToC 2020-01-21 22:25:33 +01:00
Krateng c285e0b024 Merge branch 'version2' 2020-01-21 22:08:34 +01:00
Krateng 218c7eb2fd Various 2020-01-21 22:00:42 +01:00
Marco Aceti 2004f7690f
More logic Dockerfile ordering 2020-01-21 15:27:27 +01:00
Marco Aceti 98d80d3d11
Add Dockerfile support 2020-01-21 15:09:52 +01:00
Krateng a2cc27ddd4 Fixed requirements 2020-01-10 23:02:51 +01:00
Krateng c518627670 Removed some log output 2020-01-10 22:51:50 +01:00
Krateng fbce600c4e Various 2020-01-10 04:06:34 +01:00
Krateng 95534f0f4a Removed logfile output for subprocess calling to avoid zombification 2020-01-10 04:02:43 +01:00
Krateng 2ae293a889 Added pyhp partials for tile charts 2019-12-31 15:58:44 +01:00
Krateng 33cea26a79 More PYHP conversion 2019-12-30 17:45:46 +01:00
Krateng a6724b9455 Added fixartists rule 2019-12-29 20:00:17 +01:00
Krateng 137da60ab9 Fixed faulty import of predefined rules 2019-12-29 19:29:11 +01:00
Krateng a4abf383a6 Moved range preferences from cookies to localstorage 2019-12-29 16:16:28 +01:00
Krateng ec914d1b40 Removed outdated gitignore files 2019-12-22 22:50:13 +01:00
Krateng e2f627a1a0 Minor improvements 2019-12-22 22:01:01 +01:00
Krateng f9672a918d Improved and refactored name normalization 2019-12-22 15:13:51 +01:00
Krateng 94a20a8818 Added pyhp partials 2019-12-19 20:05:50 +01:00
Krateng 582c4c8814 Added pyhp page for artist charts 2019-12-19 15:30:13 +01:00
Krateng d68393f91d Fixed local images 2019-12-15 15:51:19 +01:00
Krateng e85049af64 Fixed oversights 2019-12-15 15:43:56 +01:00
Krateng e50cce28fa Finally refactored lastfm import 2019-12-15 15:35:11 +01:00
Krateng dbc23ca73c Replaced directory changing with fully generated file paths 2019-12-15 15:18:33 +01:00
Krateng 2029e5d522 Added log output when starting server via controller 2019-12-14 16:18:20 +01:00
Krateng e856130e19 Added more title cleanup 2019-12-14 16:05:34 +01:00
Krateng ab28d00052 Improved pyhp for artist and track page 2019-12-14 15:36:53 +01:00
Krateng af2bd35d74 Moved data directory to globalconf 2019-12-14 13:46:02 +01:00
Krateng 148b3d83f8 Moved backup to own file 2019-12-14 13:43:01 +01:00
Krateng 6369cbbeb8 Added first partial pyhp modules 2019-12-13 21:25:47 +01:00
Krateng c95ce17451 Added github tagging 2019-12-13 17:37:22 +01:00
Krateng a06149df15 Renamed scrobbler folder 2019-12-13 17:29:32 +01:00
Krateng c0ecbfb28f Added thumbor support 2019-12-12 21:24:13 +01:00
Krateng 091c86a7ce Added optional stat report 2019-12-12 16:46:55 +01:00
Krateng 68000067bc Bumped nimrodel requirement 2019-12-11 16:07:17 +01:00
Krateng 8cf39adc91 Version now compares to latest pypi release 2019-12-11 15:54:59 +01:00
Krateng 905c6e8e02 Switched error page to pyhp 2019-12-11 15:49:03 +01:00
Krateng b87379ed98 Added automatic backups 2019-12-11 15:27:21 +01:00
Krateng 14fb4b4023 Database fixing from web interface should now work again 2019-12-11 15:17:49 +01:00
Krateng 592e26f1ca Updated database fix 2019-12-11 15:10:28 +01:00
Krateng 5d066d9b26 Added artistintitle rules 2019-12-11 14:25:09 +01:00
Krateng a6b1a8a144 Added update command 2019-12-10 22:55:36 +01:00
Krateng 414f530035 Fixed inclusion of predefined rules 2019-12-10 21:20:11 +01:00
Krateng 246f61ee14 Fixed search 2019-12-10 21:15:50 +01:00
Krateng 01bf88f83d Added backup command 2019-12-10 20:05:19 +01:00
Krateng c99ccd0586 Added ubuntu install script 2019-12-10 18:59:21 +01:00
Krateng e2c7e57918 More shields 2019-12-10 17:52:19 +01:00
Krateng 2ef5e88299 API returns versionstring 2019-12-10 16:07:23 +01:00
Krateng 661473b482 Fixed old import 2019-12-10 15:08:09 +01:00
Krateng fb55944147 Adjusted update script 2019-12-10 14:31:42 +01:00
Krateng 24267eb31d Updated gitignore 2019-12-10 13:56:43 +01:00
Krateng 0f4c7b04f7 Changed distribution name 2019-12-10 13:53:05 +01:00
Krateng 3382c20cb4 Cleaned up and added logging to image fetching 2019-12-10 13:51:51 +01:00
Krateng 11eb57ed2f Added auto-update 2019-12-06 18:34:29 +01:00
Krateng d11a1ea9c2 Some preparations 2019-12-06 18:25:36 +01:00
Krateng 1316026999 Added initial prompt for name 2019-12-05 23:23:48 +01:00
Krateng 0bf6e80e07 Added transition updater to version 2 2019-12-04 20:49:32 +01:00
Krateng fd0033e1c0 Adjusted structure to be closer to albula 2019-12-04 20:41:53 +01:00
Krateng c74cb7010f Better css generation 2019-12-04 19:35:14 +01:00
Krateng 2e69ef7df9 More refactoring 2019-12-03 16:23:01 +01:00
Krateng 47eeb38edf Moved doreah settings 2019-12-03 16:14:55 +01:00
Krateng 75ef7a4b0c Fixed wrong tooltip and link for track medals 2019-11-30 13:25:21 +01:00
Krateng 20908dfbaa Renaming 2019-11-29 21:36:27 +01:00
Krateng 2e4e206695 Removed symlink 2019-11-29 20:12:22 +01:00
Krateng f942979e21 Improved search performance 2019-11-29 18:26:29 +01:00
Krateng 8f53839db8 Some refining 2019-11-25 02:32:59 +01:00
Krateng 55621ef4ef Refactored into Python Package 2019-11-24 21:47:24 +01:00
Krateng 3c12462d36 Can now upload artist / track images from web 2019-11-21 23:15:10 +01:00
Krateng 3ebde4a187 Misc 2019-11-21 19:29:46 +01:00
Krateng 1c9fdedbed Added debug output to evaluate PyHP performance 2019-11-21 06:31:23 +01:00
Krateng ab7ca8f192 Artist and track pyhp pages now fully equivalent to non-pyhp versions 2019-11-21 05:39:04 +01:00
Krateng 6372d2d8f2 Can now scrobble from track page 2019-11-21 05:12:52 +01:00
Krateng 899f1ea121 Factored out button css 2019-11-20 22:46:20 +01:00
Krateng aafbfa747a API and frontend now consistent in regards to multiartist querystring 2019-11-20 22:45:59 +01:00
Krateng a5b407d92f Fixed various issues with pyhp files 2019-11-20 22:14:49 +01:00
Krateng e1eacbb272 More concise medal code 2019-11-20 21:11:10 +01:00
Krateng f540862881 Updated javascript 2019-11-20 21:10:50 +01:00
Krateng 9b7bc5e38e Implemented admin mode 2019-11-20 05:24:59 +01:00
Krateng 204712e240 Added link to admin page 2019-11-19 21:51:05 +01:00
Krateng e6b282a1f2 Fixed warning if ahead of reference version 2019-11-19 21:34:38 +01:00
Krateng f064dd602e Reverted default use of pyhp until parity achieved 2019-11-19 21:29:29 +01:00
Krateng a61208a2f8 Added versioning and admin panel 2019-11-19 21:20:42 +01:00
Krateng 36754ade85 Merge branch 'pyhp' 2019-11-19 20:06:51 +01:00
Krateng 306834cfc0 Added invalid artist for Spotify advertisements 2019-11-19 20:06:39 +01:00
Krateng 83137433bd Fixed cookie for weekly top tracks not being set 2019-11-07 06:01:46 +01:00
Krateng 3746111ee8 Updated to new pyhp 2019-10-24 20:51:49 +02:00
Krateng b955777637 Merge branch 'master' into pyhp 2019-10-24 15:57:41 +02:00
Krateng 748964c8ef Fixed syncing for manual scrobbling 2019-10-24 15:46:38 +02:00
Krateng 580350c1d6 Added addartists rule type 2019-10-24 04:03:44 +02:00
Krateng a8a15826c3 Made update script more portable 2019-10-16 15:24:07 +02:00
Krateng 4ffeb9eae2 Added script tp update requirements 2019-10-16 15:17:55 +02:00
Krateng fe953d07b1 Added log output to supervisor 2019-10-16 15:09:15 +02:00
Krateng 2170350315 Server-side less compilation 2019-10-11 05:17:15 +02:00
Krateng f4a3fff848 Switched to less 2019-10-08 18:55:37 +02:00
Krateng 96d1b8d77b Fixed some DB API endpoints 2019-10-03 05:24:47 +02:00
Krateng 243362d7d6 Updated Spotify scrobbling 2019-10-03 05:10:31 +02:00
Krateng 1f90b34d95 Factored out common style 2019-10-02 05:35:33 +02:00
Krateng 5342003fa4 Updated doreah version 2019-09-30 17:18:03 +02:00
Krateng 4f9579b8d2 Updated readme 2019-09-28 05:20:01 +02:00
Krateng af90e2633c Fixed time ranges again 2019-09-26 18:24:42 +02:00
Krateng 59e38e24ee Reorganized charts code 2019-09-21 20:51:49 +02:00
Krateng ed62cbfc0b Merge branch 'master' of github.com:krateng/maloja 2019-09-20 18:53:23 +02:00
Krateng 1320bf4f5b Fixed small issue with ranges 2019-09-20 18:53:00 +02:00
krateng d8db927c8e
Merge pull request #7 from ZackBoe/fix-tiles-artist-charts
Don't render tiles on artist track chart pages
2019-09-14 14:47:09 +02:00
Zack Boehm 8ad625ef72 Don't render tiles on artist track chart pages 2019-09-11 20:42:13 -04:00
Krateng 852795cecd Added default picture to charts when tiles are enabled 2019-09-05 14:53:09 +02:00
krateng 731653b964
Merge pull request #6 from ZackBoe/feature-tiles
Optionally display tiles in artist & track chart pages
2019-09-05 14:47:04 +02:00
Krateng 2efe2967f6 Added ignoreartist rule 2019-09-05 14:46:34 +02:00
Krateng 390118cdd8 Bugfix 2019-09-05 03:48:45 +02:00
Zack Boehm 864e7bc46d Optionally display tiles in artist + track chart pages
This allows for viewing top tiles for any specific timerange
2019-09-04 01:58:28 -04:00
Krateng fdbeed40f0 Added supervisor to restart on crash, very experimental 2019-09-02 01:24:32 +02:00
Krateng 974574da98 Added basic filter to reject wrong scrobbles 2019-08-30 15:57:54 +02:00
Krateng 5d9ecc0557 Disabled search provider per default and moved around some things 2019-08-22 22:49:58 +02:00
krateng 2101b56b8c
Merge pull request #5 from ZackBoe/feature-track-search
Add link to track search feature
2019-08-22 21:55:03 +02:00
Krateng 08d478e202 Merge branch 'master' of github.com:krateng/maloja 2019-08-22 21:36:16 +02:00
Krateng a5feec7234 Added temporary debug logging 2019-08-22 21:35:58 +02:00
Krateng 564f276496 Removed some excessive logging 2019-08-22 20:51:32 +02:00
Zack Boehm cc3f9f7fef Add track search feature
Adds a link to the search results for each track on a provider of choice
2019-08-21 16:19:01 -04:00
krateng 6b566e28df
Update README.md 2019-08-21 16:47:11 +02:00
Krateng 725d2308b1 Fixed issue with missing consistency check files 2019-08-17 18:21:36 +02:00
Krateng 7c80256c7d Merge branch 'master' of github.com:krateng/maloja 2019-08-17 16:40:44 +02:00
Krateng 6644863ca1 Fixed error when no scrobbles present 2019-08-17 16:40:23 +02:00
krateng 793fbe4769
Update README.md 2019-08-16 21:25:50 +02:00
Krateng 3ae30a5226 Ensured idempotence of scrobbles with timestamp 2019-08-15 15:51:29 +02:00
Krateng 89881af8fe Added predefined rule 2019-08-02 17:31:23 +02:00
Krateng 6818a976a5 Switching time range or similar now resets to first page 2019-07-25 17:35:03 +02:00
Krateng 40573ba4f7 Added some log output and exception catching 2019-07-09 20:27:36 +02:00
Krateng 4323d0dc67 Added setting for custom host (closes GH-4) 2019-06-30 19:32:16 +02:00
Krateng 289a1dc076 API key cookie no longer expires on session end 2019-06-28 22:14:05 +02:00
Krateng ffb12045f5 Fixed weekly #1 for ties 2019-06-27 11:25:11 +02:00
Krateng cfa0734c0a Enabled caching of weekly #1 2019-06-27 11:04:45 +02:00
Krateng 495b00803d Added weeks on #1 stat to artist and track pages 2019-06-27 10:40:38 +02:00
Krateng ae5b0fc90c Beautification 2019-06-26 19:13:02 +02:00
Krateng c3c10170bc Further DRYed selectors 2019-06-26 17:59:23 +02:00
Krateng 728c4fed54 Fixed description text for performance / pulse 2019-06-26 17:17:15 +02:00
Krateng 92edca3521 Refactored selectors for easier extensibility 2019-06-26 17:11:46 +02:00
Krateng 3d78d124ef Added soundcloud to scrobbler 2019-06-25 16:55:12 +02:00
Krateng 2cbe67f149 Added bandcamp to scrobbler 2019-06-25 16:05:36 +02:00
Krateng da76b6105e Fixed typo 2019-06-24 18:15:24 +02:00
Krateng 305999cc14 Fixed encoding for proxy scrobbling 2019-06-24 17:23:27 +02:00
Krateng 63c2214913 Added log output for proxy scrobbling 2019-06-24 16:09:25 +02:00
Krateng b9ae163132 Added highly experimental last.fm proxy scrobbling 2019-06-24 15:43:38 +02:00
Krateng 8cbc7a8e56 Added simpler redirect directly from page script 2019-06-24 15:43:04 +02:00
Krateng 55f0a2ceaf Factored out API key insertion 2019-06-24 15:02:38 +02:00
Krateng 66c7d9e711 Added URL shortcuts 2019-06-24 12:48:45 +02:00
Krateng 35a44f7d06 Cleaned up namespaces 2019-06-24 12:22:47 +02:00
Krateng e282789153 Release 1.5 2019-06-24 12:16:10 +02:00
Krateng 2f05b83390 Added sanity check to compare 2019-06-24 12:15:56 +02:00
Krateng a59381d0bf Expanded compare feature slightly 2019-06-18 14:11:03 +02:00
Krateng 8a80c265cd Added bare-bones experimental compare feature 2019-06-18 12:34:10 +02:00
Krateng aeaaf3a8e9 Fixed bug with setting cookie 2019-06-18 10:38:31 +02:00
Krateng 90b510bc0a Added database call for basic information 2019-06-17 16:57:20 +02:00
Krateng 47f13028c4 Fixed use of custom page sizes and more links 2019-06-17 15:45:00 +02:00
Krateng a83f2c3cf0 Fixed faulty links 2019-06-17 15:24:11 +02:00
Krateng 6958196adb Fixed pagination on pulse and performance views 2019-06-17 15:09:21 +02:00
Krateng 5fedde5e9f Added graphical pagination 2019-06-17 14:59:30 +02:00
Krateng a5d73f8fe9 Added basic pagination 2019-06-17 14:28:16 +02:00
Krateng f484c768ab Links to full pages are now adjusted by choices in onpage selector 2019-06-17 13:02:06 +02:00
Krateng e6aa0a25c8 Range selections are now saved in cookies 2019-06-17 12:19:56 +02:00
Krateng 649075286c Fixed display bug 2019-06-13 17:41:42 +02:00
Krateng 09b1fd446b Added info about record certifications to artist page 2019-06-13 16:56:41 +02:00
Krateng 00a8f1101c Implemented certification display from master 2019-06-13 12:28:57 +02:00
Krateng 95b4fc7426 Merged master changes 2019-06-13 12:22:02 +02:00
Krateng b4a6036c0e Added record certification icon on web interface 2019-06-13 12:12:47 +02:00
Krateng 746475390f Fixed API bug and added track certifications 2019-06-13 11:37:42 +02:00
Krateng 060cb7ea23 Decluttered namespace 2019-06-13 10:51:25 +02:00
Krateng f358b856dd Fixed GH-3 2019-06-12 09:51:00 +02:00
Krateng 49024b957e Adjusted design 2019-06-11 10:17:54 +02:00
Krateng 23fb808d70 Fixed caching bug 2019-06-11 10:08:55 +02:00
Krateng 65afef8489 Showing artists and title in one column 2019-06-10 17:42:08 +02:00
Krateng 1eee88482c Updated packaged scrobbler 2019-06-09 15:56:17 +02:00
Krateng f479cbeea0 The Yogpod Update 2019-06-04 12:14:03 +02:00
Krateng 424271d5df Scrobbler sends more accurate times when loop-listening to a track 2019-05-31 16:02:39 +02:00
Krateng 5ea7605bbf Added robots.txt 2019-05-26 18:35:24 +02:00
Krateng 01fe8771bd Updated Nimrodel version to fix api probe 2019-05-26 10:53:33 +02:00
Krateng 52ab0311d6 Updated nimrodel version to fix listenbrainz scrobbling 2019-05-26 10:28:59 +02:00
Krateng f096754139 Added some rules 2019-05-24 22:05:41 +02:00
Krateng 15970ff2c6 Switched track page to pyhp 2019-05-23 16:39:30 +02:00
Krateng 452e443c21 Bugfix 2019-05-23 14:27:10 +02:00
Krateng d20d31b200 Fixed some API endpoints 2019-05-23 14:25:35 +02:00
Krateng fc24d19da5 Fixed requirements 2019-05-23 14:16:44 +02:00
Krateng dbba4ed671 Added requirements.txt and fixed error when not using API keys 2019-05-23 14:07:37 +02:00
Krateng 410eee4a4d Switched to using nimrodel API 2019-05-23 13:13:42 +02:00
Krateng 7b3c72d211 Added artist hyperlinks 2019-05-21 18:03:57 +02:00
Krateng 452b84af5c Some more rules 2019-05-21 17:59:10 +02:00
Krateng ab648db336 Experimental use of pyhp for artist page 2019-05-21 17:29:07 +02:00
Krateng 2ae78bcec2 Various Stuff 2019-05-20 17:38:05 +02:00
Krateng 29171bda0b Chrome extension now shows number of handled tabs 2019-05-19 11:09:35 +02:00
Krateng 2944830df6 Fixed multi-artist recognition for Spotify 2019-05-17 18:02:08 +02:00
Krateng 69c25411b9 Added Spotify module to scrobbler 2019-05-17 17:56:29 +02:00
Krateng 604f897571 Minor visual improvements 2019-05-17 15:08:02 +02:00
Krateng 981c0e4ae2 Release 1.4 2019-05-17 14:51:30 +02:00
Krateng baa7552478 Further adjustments to compliant API 2019-05-15 10:11:41 +02:00
Krateng 9f5a705504 Made caching times a bit more reasonable 2019-05-14 21:59:33 +02:00
Krateng 3b0b9eaa78 Removed default URI keys from pages where they aren't relevant 2019-05-14 18:06:34 +02:00
Krateng 53778ce644 Reorganized compliant APIs somewhat, possibly for the worse 2019-05-14 17:18:28 +02:00
Krateng 3c6cfc81c5 Removed outdated scrobblers 2019-05-14 14:43:46 +02:00
Krateng 80c47d4916 Documentation 2019-05-14 13:04:05 +02:00
Krateng 8b0904e589 Added page to manually scrobble 2019-05-14 12:50:31 +02:00
Krateng 9b8e7c65f1 API now gives some feedback when scrobbling 2019-05-14 12:31:24 +02:00
Krateng 8701a1b39d Search API returns more information 2019-05-14 12:07:47 +02:00
Krateng 75aa341b9a Resized image files no longer go in the pool of possible images 2019-05-13 18:36:06 +02:00
Krateng b99cf8b77d Updated Chromium scrobbler to be more modularized 2019-05-13 17:16:35 +02:00
Krateng 5aa92a1ab8 Experimental support for Listenbrainz scrobblers 2019-05-13 10:46:23 +02:00
Krateng 042f4f12fb Updated readme 2019-05-12 18:41:00 +02:00
Krateng e9cde843e1 Integrated API server into main server 2019-05-12 18:39:46 +02:00
Krateng 3ba7e4cfef Fixed old url references 2019-05-12 11:58:51 +02:00
Krateng 374fc6d885 Updated information on setup page and readme 2019-05-12 11:51:08 +02:00
Krateng dea356d591 Merge branch 'master' of github.com:krateng/maloja 2019-05-12 11:46:43 +02:00
Krateng 144c11c5c6 Experimental support for third party GNUFM scrobblers 2019-05-12 11:46:25 +02:00
Krateng 3fce682c00 Changed API url 2019-05-12 10:20:57 +02:00
krateng 7f4e2d8b68
Update README.md 2019-05-10 14:02:34 +02:00
330 changed files with 13718 additions and 6959 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
*
!maloja
!container
!Containerfile
!requirements.txt
!pyproject.toml
!README.md
!LICENSE

View File

@ -1,4 +0,0 @@
logging.logfolder = logs
settings.files = [ "settings/default.ini" , "settings/settings.ini" ]
caching.folder = "cache/"
regular.autostart = false

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ["https://flattr.com/@Krateng", "https://paypal.me/krateng"]

View File

@ -0,0 +1,36 @@
name: Publish library to PyPI
on:
push:
paths:
- 'auxiliary/malojalib/pyproject.toml'
# When the version updates, this file changes
# False positives only result in a failed push
jobs:
publish_to_pypi:
name: Push Library to PyPI
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Set up Python
uses: actions/setup-python@7f80679172b057fc5e90d70d197929d454754a5a
with:
python-version: '3.x'
- name: Install dependencies
run: pip install build
- name: Change directory
run: cd auxiliary/malojalib
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@717ba43cfbb0387f6ce311b169a825772f54d295
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

View File

@ -0,0 +1,21 @@
name: Publish Chromium scrobbler to web store
on:
push:
paths:
- 'auxiliary/chromium_scrobbler/maloja-scobbler/manifest.json'
# When the version updates, this file changes
jobs:
publish_to_pypi:
name: Build and publish extension
runs-on: ubuntu-latest
steps:
- name: Push Extension to Web Store
uses: Klemensas/chrome-extension-upload-action@1e8ede84548583abf1a2a495f4242c4c51539337
with:
refresh-token: '${{ secrets.GOOGLE_REFRESHTOKEN }}'
client-id: '${{ secrets.GOOGLE_CLIENTID }}'
file-name: './auxiliary/chromium_scrobbler/maloja-scrobbler.zip'
app-id: 'cfnbifdmgbnaalphodcbandoopgbfeeh'
publish: true

76
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: Build and release docker image
on:
push:
tags:
- 'v*'
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@f2a13332ac1ce8c0a71aeac48a150dbb1838ab67
with:
images: |
${{ github.repository_owner }}/maloja
# generate Docker tags based on the following events/attributes
tags: |
type=semver,pattern={{version}}
flavor: |
latest=true
- name: Set up QEMU
uses: docker/setup-qemu-action@27d0a4f181a40b142cce983c5393082c365d1480
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@94ab11c41e45d028884a99163086648e898eed25
- name: Cache Docker layers
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push Docker image
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
with:
context: .
file: Containerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
# Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
- name: Update Readme and short description
uses: peter-evans/dockerhub-description@836d7e6aa8f6f32dce26f5a1dd46d3dc24997eae
continue-on-error: true
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}
repository: krateng/maloja
short-description: ${{ github.event.repository.description }}

31
.github/workflows/pypi.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Publish to PyPI
on:
push:
tags:
- 'v*'
jobs:
publish_to_pypi:
name: Push Package to PyPI
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Set up Python
uses: actions/setup-python@7f80679172b057fc5e90d70d197929d454754a5a
with:
python-version: '3.x'
- name: Install dependencies
run: pip install build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@717ba43cfbb0387f6ce311b169a825772f54d295
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

29
.gitignore vendored
View File

@ -1,19 +1,14 @@
# generic temporary / dev files
# temporary / generated files
*.pyc
*.sh
*.txt
# environments / builds
.venv/*
testdata*
/dist
/build
/*.egg-info
# dev files
*.xcf
nohup.out
/.dev
# user files
*.tsv
*.rulestate
*.log
# currently not using
/screenshot*.png
/proxyscrobble.py
# only for development, normally external
/doreah
*.note
*-old

173
API.md Normal file
View File

@ -0,0 +1,173 @@
# Scrobbling
Scrobbling can be done with the native API, see [below](#submitting-a-scrobble).
In order to scrobble from a wide selection of clients, you can also use Maloja's standard-compliant APIs with the following settings:
GNU FM | &nbsp;
------ | ---------
Gnukebox URL | Your Maloja URL followed by `/apis/audioscrobbler`
Username | Doesn't matter
Password | Any of your API keys
ListenBrainz | &nbsp;
------ | ---------
API URL | Your Maloja URL followed by `/apis/listenbrainz`
Username | Doesn't matter
Auth Token | Any of your API keys
Audioscrobbler v1.2 | &nbsp;
------ | ---------
Server URL | Your Maloja URL followed by `/apis/audioscrobbler_legacy`
Username | Doesn't matter
Password | Any of your API keys
| :warning: | Note that these are the base URLs - some scrobblers ask you for the full endpoint instead. |
|---------------|:------------------------|
## Scrobbling Guideline
Maloja makes no assumptions about scrobbling behaviour. The clients should decide when and whether a play is scrobbled - the server will accept it as long as it contains all necessary data. However, a general guideline is:
* As soon as a track has been played for 50% of its length or 4 minutes, it should be counted as a scrobble
* That scrobble should be submitted when the play has ended in order to know its duration
* If the total play duration is enough to count as a scrobble, but not longer than the total track length + enough for a second scrobble, it should be submitted as a scrobble with the according duration
* If the duration exceeds this value, the first scrobble should be submitted as a scrobble with the duration of the full track length, while the second scrobble is queued up following the above suggestions in regards to remaining time
<table>
<tr><td>:memo: Example </td><tr>
<tr><td>
The user starts playing '(Fine Layers of) Slaysenflite', which is exactly 3:00 minutes long.
* If the user ends the play after 1:22, no scrobble is submitted
* If the user ends the play after 2:06, a scrobble with `"duration":126` is submitted
* If the user jumps back several times and ends the play after 3:57, a scrobble with `"duration":237` is submitted
* If the user jumps back several times and ends the play after 4:49, two scrobbles with `"duration":180` and `"duration":109` are submitted
</td></tr>
<table>
# API Documentation
The native Maloja API is reachable at `/apis/mlj_1`. Endpoints are listed on `/api_explorer`.
All endpoints return JSON data. POST request can be made with query string or form data arguments, but this is discouraged - JSON should be used whenever possible.
No application should ever rely on the non-existence of fields in the JSON data - i.e., additional fields can be added at any time without this being considered a breaking change. Existing fields should usually not be removed or changed, but it is always a good idea to add basic handling for missing fields.
## Submitting a Scrobble
The POST endpoint `/newscrobble` is used to submit new scrobbles. These use a flat JSON structure with the following fields:
| Key | Type | Description |
| --- | --- | --- |
| `artists` | List(String) | Track artists |
| `title` | String | Track title |
| `album` | String | Name of the album (Optional) |
| `albumartists` | List(String) | Album artists (Optional) |
| `duration` | Integer | How long the song was listened to in seconds (Optional) |
| `length` | Integer | Actual length of the full song in seconds (Optional) |
| `time` | Integer | Timestamp of the listen if it was not at the time of submitting (Optional) |
| `nofix` | Boolean | Skip server-side metadata fixing (Optional) |
## General Structure
The API is not fully consistent in order to ensure backwards-compatibility. Refer to the individual endpoints.
Generally, most endpoints follow this structure:
| Key | Type | Description |
| --- | --- | --- |
| `status` | String | Status of the request. Can be `success`, `ok`, `error`, `failure`, `no_operation` |
| `error` | Mapping | Details about the error if one occured. |
| `warnings` | List | Any warnings that did not result in failure, but should be noted. Field is omitted if there are no warnings! |
| `desc` | String | Human-readable feedback. This can be shown directly to the user if desired. |
| `list` | List | List of returned [entities](#entity-structure) |
Both errors and warnings have the following structure:
| Key | Type | Description |
| --- | --- | --- |
| `type` | String | Name of the error or warning type |
| `value` | varies | Specific data for this error or warning instance |
| `desc` | String | Human-readable error or warning description. This can be shown directly to the user if desired. |
## Entity Structure
Whenever a list of entities is returned, they have the following fields:
### Scrobble
| Key | Type | Description |
| --- | --- | --- |
| `time` | Integer | Timestamp of the Scrobble in UTC |
| `track` | Mapping | The [track](#track) being scrobbled |
| `duration` | Integer | How long the track was played for in seconds |
| `origin` | String | Client that submitted the scrobble, or import source |
<table>
<tr><td>:memo: Example </td><tr>
<tr><td>
```json
{
"time": 1650684324,
"track": {
"artists": ["Jennie Kim","HyunA","LE","SunMi"],
"title": "Wow Thing",
"length":200
},
"duration": 196,
"origin": "client:navidrome_desktop"
}
```
</tr></td>
</table>
### Track
| Key | Type | Description |
| --- | --- | --- |
| `artists` | List | The [artists](#artist) credited with the track |
| `title` | String | The title of the track |
| `length` | Integer | The full length of the track in seconds |
<table>
<tr><td>:memo: Example </td><tr>
<tr><td>
```json
{
"artists": ["Blackpink","Chou Tzuyu"],
"title": "MORE",
"length": 171
}
```
</tr></td>
</table>
### Artist
Artists are just represented as raw Strings.
**Example**
<table>
<tr><td>:memo: Example </td><tr>
<tr><td>
```json
"Red Velvet"
```
</tr></td>
</table>

36
APKBUILD Normal file
View File

@ -0,0 +1,36 @@
# Contributor: Johannes Krattenmacher <maloja@dev.krateng.ch>
# Maintainer: Johannes Krattenmacher <maloja@dev.krateng.ch>
pkgname=maloja
pkgver=3.0.0-dev
pkgrel=0
pkgdesc="Self-hosted music scrobble database"
url="https://github.com/krateng/maloja"
arch="noarch"
license="GPL-3.0"
depends="python3 tzdata"
pkgusers=$pkgname
pkggroups=$pkgname
depends_dev="gcc g++ python3-dev libxml2-dev libxslt-dev libffi-dev libc-dev py3-pip linux-headers"
makedepends="$depends_dev"
source="
$pkgname-$pkgver.tar.gz::https://github.com/krateng/maloja/archive/refs/tags/v$pkgver.tar.gz
"
builddir="$srcdir"/$pkgname-$pkgver
build() {
cd $builddir
python3 -m build .
pip3 install dist/*.tar.gz
}
package() {
mkdir -p /etc/$pkgname || return 1
mkdir -p /var/lib/$pkgname || return 1
mkdir -p /var/cache/$pkgname || return 1
mkdir -p /var/logs/$pkgname || return 1
}
# TODO
sha512sums="a674eaaaa248fc2b315514d79f9a7a0bac6aa1582fe29554d9176e8b551e8aa3aa75abeebdd7713e9e98cc987e7bd57dc7a5e9a2fb85af98b9c18cb54de47bf7 $pkgname-${pkgver}.tar.gz"

74
Containerfile Normal file
View File

@ -0,0 +1,74 @@
FROM lsiobase/alpine:3.17 as base
WORKDIR /usr/src/app
COPY --chown=abc:abc ./requirements.txt ./requirements.txt
# based on https://github.com/linuxserver/docker-pyload-ng/blob/main/Dockerfile
# everything but the app installation is run in one command so we can purge
# all build dependencies and cache in the same layer
# it may be possible to decrease image size slightly by using build stage and
# copying all site-packages to runtime stage but the image is already pretty small
RUN \
echo "**** install build packages ****" && \
apk add --no-cache --virtual=build-deps \
gcc \
g++ \
python3-dev \
libxml2-dev \
libxslt-dev \
libffi-dev \
libc-dev \
py3-pip \
linux-headers && \
echo "**** install runtime packages ****" && \
apk add --no-cache \
python3 \
py3-lxml \
tzdata && \
echo "**** install pip dependencies ****" && \
python3 -m ensurepip && \
pip3 install -U --no-cache-dir \
pip \
wheel && \
echo "**** install maloja requirements ****" && \
pip3 install --no-cache-dir -r requirements.txt && \
echo "**** cleanup ****" && \
apk del --purge \
build-deps && \
rm -rf \
/tmp/* \
${HOME}/.cache
# actual installation in extra layer so we can cache the stuff above
COPY --chown=abc:abc . .
RUN \
echo "**** install maloja ****" && \
apk add --no-cache --virtual=install-deps \
py3-pip && \
pip3 install /usr/src/app && \
apk del --purge \
install-deps && \
rm -rf \
/tmp/* \
${HOME}/.cache
COPY container/root/ /
ENV \
# Docker-specific configuration
MALOJA_SKIP_SETUP=yes \
PYTHONUNBUFFERED=1 \
# Prevents breaking change for previous container that ran maloja as root
# On linux hosts (non-podman rootless) these variables should be set to the
# host user that should own the host folder bound to MALOJA_DATA_DIRECTORY
PUID=0 \
PGID=0
EXPOSE 42010

58
DEVELOPMENT.md Normal file
View File

@ -0,0 +1,58 @@
# Development
Clone the repository and enter it.
```console
git clone https://github.com/krateng/maloja
cd maloja
```
## Environment
To avoid cluttering your system, consider using a [virtual environment](https://docs.python.org/3/tutorial/venv.html).
Your system needs several packages installed. For supported distributions, this can be done with e.g.
```console
sh ./install/install_dependencies_alpine.sh
```
For other distros, try to find the equivalents of the packages listed or simply check your error output.
Then install all Python dependencies with
```console
pip install -r requirements.txt
```
## Running the server
For development, you might not want to install maloja files all over your filesystem. Use the environment variable `MALOJA_DATA_DIRECTORY` to force all user files into one central directory - this way, you can also quickly change between multiple configurations.
You can quickly run the server with all your local changes with
```console
python3 -m maloja run
```
You can also build the package with
```console
pip install .
```
## Docker
You can also always build and run the server with
```console
sh ./dev/run_docker.sh
```
This will use the directory `testdata`.
## Further help
Feel free to [ask](https://github.com/krateng/maloja/discussions) if you need some help!

11
FUTURE.md Normal file
View File

@ -0,0 +1,11 @@
Если вы обнаружили этот репозиторий как часть GitHub Arctic Code Vault, я хотел бы искренне извиниться за то, что вы посвятили свой взгляд этому ужасному коду.
如果您已将该存储库作为GitHub Arctic Code Vault的一部分发现我谨诚挚地道歉因为您将目光投向了这一可怕的代码。
Hoc si repositum partem GitHub arcticum Codicis Buy nudatus, subdens excusare velim simpliciter gravissimum intueri codice.
Εάν έχετε αποκαλύψει αυτό το αποθετήριο ως μέρος του GitHub Arctic Code Vault, θα ήθελα ειλικρινά να ζητήσω συγνώμη για την υποβολή των ματιών σας σε αυτόν τον τρομερό κώδικα.
Եթե դուք հայտնաբերել եք այս պահեստը որպես GitHub Arctic Code Vault- ի մաս, ապա ես կցանկանայի անկեղծորեն ներողություն խնդրել ձեր աչքերը այս սարսափելի կոդին ենթարկելու համար:
If you have uncovered this repository as part of the GitHub Arctic Code Vault, I would like to sincerely apologize for subjecting your eyes to this terrible code.

201
README.md
View File

@ -1,69 +1,192 @@
# Maloja
[![](https://img.shields.io/github/v/tag/krateng/maloja?label=GitHub&style=for-the-badge&logo=github&logoColor=white)](https://github.com/krateng/maloja)
[![](https://img.shields.io/pypi/v/malojaserver?label=PyPI&style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/malojaserver/)
[![](https://img.shields.io/docker/v/krateng/maloja?label=Dockerhub&style=for-the-badge&logo=docker&logoColor=white)](https://hub.docker.com/r/krateng/maloja)
[![](https://img.shields.io/pypi/l/malojaserver?style=for-the-badge)](https://github.com/krateng/maloja/blob/master/LICENSE)
[![](https://img.shields.io/codeclimate/maintainability/krateng/maloja?style=for-the-badge)](https://codeclimate.com/github/krateng/maloja)
Simple self-hosted music scrobble database to create personal listening statistics. No recommendations, no social network, no nonsense.
You can check [my own Maloja page](https://maloja.krateng.ch) to see what it looks like.
![screenshot](https://raw.githubusercontent.com/krateng/maloja/master/screenshot.png)
## Never Asked Questions
You can check [my own Maloja page](https://maloja.krateng.ch) as an example instance.
### Why not Last.fm / Libre.fm / GNU FM?
Maloja is **self-hosted**. You will always be able to access your data in an easily-parseable format. Your library is not synced with any public or official music database, so you can **follow your own tagging schema** or even **group associated artists together** in your charts.
## Table of Contents
* [Features](#features)
* [How to install](#how-to-install)
* [Requirements](#requirements)
* [PyPI](#pypi)
* [From Source](#from-source)
* [Docker / Podman](#docker--podman)
* [Extras](#extras)
* [How to use](#how-to-use)
* [Basic control](#basic-control)
* [Data](#data)
* [Customization](#customization)
* [How to scrobble](#how-to-scrobble)
* [How to extend](#how-to-extend)
Maloja also gets **rid of all the extra stuff**: social networking, radios, recommendations, etc. It only keeps track of your listening history and lets you analyze it.
## Features
Maloja's database has one big advantage: It supports **multiple artists per track**. This means artists who are often just "featuring" in the track title get a place in your charts, and **collaborations between several artists finally get credited to all participants**. This allows you to get an actual idea of your artist preferences over time.
* **Self-hosted**: You will always be able to access your data in an easily-parseable format. Your library is not synced with any public or official music database, so you can follow your own tagging schema.
* **Associated Artists**: Compare different artists' popularity in your listening habits including subunits, collaboration projects or solo performances by their members. Change these associations at any time without losing any information.
* **Multi-Artist Tracks**: Some artists often collaborate with others or are listed under "featuring" in the track title. Instead of tracking each combination of artists, each individual artist competes in your charts.
* **Custom Images**: Don't rely on the community to select the best pictures for your favorite artists. Upload your own so that your start page looks like you want it to look.
* **Proxy Scrobble**: No need to fully commit or set up every client twice - you can configure your Maloja server to forward your scrobbles to other services.
* **Standard-compliant API**: Use existing, mature apps or extensions to scrobble to your Maloja server.
* **Manual Scrobbling**: Listening to vinyl or elevator background music? Simply submit a scrobble with the web interface.
* **Keep it Simple**: Unlike Last.fm and similar alternatives, Maloja doesn't have social networking, radios, recommendations or any other gimmicks. It's a tool to keep track of your listening habits over time - and nothing more.
Also neat: You can use your **custom artist or track images**.
## Requirements
* [python3](https://www.python.org/) - [GitHub](https://github.com/python/cpython)
* [bottle.py](https://bottlepy.org/) - [GitHub](https://github.com/bottlepy/bottle)
* [waitress](https://docs.pylonsproject.org/projects/waitress/) - [GitHub](https://github.com/Pylons/waitress)
* [doreah](https://pypi.org/project/doreah/) - [GitHub](https://github.com/krateng/doreah) (at least Version 0.6.1)
* If you'd like to display images, you will need API keys for [Last.fm](https://www.last.fm/api/account/create) and [Fanart.tv](https://fanart.tv/get-an-api-key/). These are free of charge!
## How to install
1) Either install Maloja with a package, or download the repository to some arbitrary location. If you pick the manual installation, every command needs to be executed from the Maloja directory and led with `./`. You can also only download the file `maloja` instead of the whole repository and fetch the rest with
### Requirements
./maloja install
Maloja should run on any x86 or ARM machine that runs Python.
2) Start the server with
I can support you with issues best if you use **Alpine Linux**.
maloja start
Your CPU should have a single core passmark score of at the very least 1500. 500 MB RAM should give you a decent experience, but performance will benefit greatly from up to 2 GB.
If you're missing packages, the console output will tell you so. Install them.
### PyPI
2) (Recommended) Put your server behind a reverse proxy for SSL encryption. Configure that proxy to rewrite /db/ requests to the database port. In nginx this would look as follows:
You can install Maloja with
location / {
proxy_pass http://yoururl:42010;
}
```console
pip install malojaserver
```
To make sure all dependencies are installed, you can also use one of the included scripts in the `install` folder.
### From Source
Clone this repository and enter the directory with
```console
git clone https://github.com/krateng/maloja
cd maloja
```
Then install all the requirements and build the package, e.g.:
```console
sh ./install/install_dependencies_alpine.sh
pip install -r requirements.txt
pip install .
```
### Docker / Podman
Pull the [latest image](https://hub.docker.com/r/krateng/maloja) or check out the repository and use the included Containerfile.
Of note are these settings which should be passed as environmental variables to the container:
* `MALOJA_DATA_DIRECTORY` -- Set the directory in the container where configuration folders/files should be located
* Mount a [volume](https://docs.docker.com/engine/reference/builder/#volume) to the specified directory to access these files outside the container (and to make them persistent)
* `MALOJA_FORCE_PASSWORD` -- Set an admin password for maloja
You must publish a port on your host machine to bind to the container's web port (default 42010). The container uses IPv4 per default.
An example of a minimum run configuration to access maloja via `localhost:42010`:
```console
docker run -p 42010:42010 -v $PWD/malojadata:/mljdata -e MALOJA_DATA_DIRECTORY=/mljdata krateng/maloja
```
#### Linux Host
**NOTE:** If you are using [rootless containers with Podman](https://developers.redhat.com/blog/2020/09/25/rootless-containers-with-podman-the-basics#why_podman_) this DOES NOT apply to you.
If you are running Docker on a **Linux Host** you should specify `user:group` ids of the user who owns the folder on the host machine bound to `MALOJA_DATA_DIRECTORY` in order to avoid [docker file permission problems.](https://ikriv.com/blog/?p=4698) These can be specified using the [environmental variables **PUID** and **PGID**.](https://docs.linuxserver.io/general/understanding-puid-and-pgid)
To get the UID and GID for the current user run these commands from a terminal:
* `id -u` -- prints UID (EX `1000`)
* `id -g` -- prints GID (EX `1001`)
The modified run command with these variables would look like:
```console
docker run -e PUID=1000 -e PGID=1001 -p 42010:42010 -v $PWD/malojadata:/mljdata -e MALOJA_DATA_DIRECTORY=/mljdata krateng/maloja
```
### Extras
* If you'd like to display images, you will need API keys for [Last.fm](https://www.last.fm/api/account/create) and [Spotify](https://developer.spotify.com/dashboard/applications). These are free of charge!
* Put your server behind a reverse proxy for SSL encryption. Make sure that you're proxying to the IPv6 or IPv4 address according to your settings.
* You can set up a cronjob to start your server on system boot, and potentially restart it on a regular basis:
```
@reboot sleep 15 && maloja start
42 0 7 * * maloja restart
```
location /db {
rewrite ^/db(.*)$ $1 break;
proxy_pass http://yoururl:42011;
}
## How to use
If you didn't install Maloja from the package (and therefore don't have it in `/opt/maloja`), every command needs to be executed from the Maloja directory and led with `./`. Otherwise, all commands work in any location and without the prefix.
### Basic control
1) In order to scrobble your music from Plex Web or YouTube Music, install the included Chrome extension. Make sure to enter the random key Maloja generates on first startup in the extension. If you use another music player, Maloja has a very simple API to create your own scrobbler.
Start and stop the server in the background with
2) If you would like to import all your previous last.fm scrobbles, use [benfoxall's website](https://benjaminbenben.com/lastfm-to-csv/) ([GitHub page](https://github.com/benfoxall/lastfm-to-csv)). Use the command
```console
maloja start
maloja stop
maloja restart
```
maloja import *filename*
If you need to run the server in the foreground, use
to import the downloaded file into Maloja.
```console
maloja run
```
3) You can interact with the server at any time with the commands
maloja stop
maloja restart
maloja start
maloja update
### Data
The `update` command will always fetch the latest version, while packages are only offered for release versions.
If you would like to import your previous scrobbles, use the command `maloja import *filename*`. This works on:
* a Last.fm export generated by [benfoxall's website](https://benjaminbenben.com/lastfm-to-csv/) ([GitHub page](https://github.com/benfoxall/lastfm-to-csv))
* an official [Spotify data export file](https://www.spotify.com/us/account/privacy/)
* an official [ListenBrainz export file](https://listenbrainz.org/profile/export/)
* the export of another Maloja instance
⚠️ Never import your data while maloja is running. When you need to do import inside docker container start it in shell mode instead and perform import before starting the container as mentioned above.
```console
docker run -it --entrypoint sh -v $PWD/malojadata:/mljdata -e MALOJA_DATA_DIRECTORY=/mljdata krateng/maloja
cd /mljdata
maloja import my_last_fm_export.csv
```
---
To backup your data, run `maloja backup`, optional with `--include_images`.
### Customization
* Have a look at the [available settings](settings.md) and specifiy your choices in `/etc/maloja/settings.ini`. You can also set each of these settings as an environment variable with the prefix `MALOJA_` (e.g. `MALOJA_SKIP_SETUP`).
* If you have activated admin mode in your web interface, you can upload custom images for artists or tracks by simply dragging them onto the existing image on the artist or track page. You can also manage custom images directly in the file system - consult `images.info` in the `/var/lib/maloja/images` folder.
* To specify custom rules, consult the `rules.info` file in `/etc/maloja/rules`. You can also apply some predefined rules on the `/admin_setup` page of your server.
## How to scrobble
You can set up any amount of API keys in the file `apikeys.yml` in your configuration folder (or via the web interface). It is recommended to define a different API key for every scrobbler you use.
Some scrobbler clients support Maloja's native API. You can also use any scrobbler that allows you to set a custom Listenbrainz or GNUFM server. See [API.md](API.md) for details.
If you're the maintainer of a music player or server and would like to implement native Maloja scrobbling, feel free to reach out!
If you can't automatically scrobble your music, you can always do it manually on the `/admin_manual` page of your Maloja server.
## How to extend
If you'd like to implement anything on top of Maloja, visit `/api_explorer`.

View File

@ -0,0 +1,3 @@
screenshot.png
tile.png
!*.sh

Binary file not shown.

View File

@ -1,5 +1,4 @@
chrome.tabs.onUpdated.addListener(onTabUpdated);
chrome.tabs.onRemoved.addListener(onTabRemoved);
//chrome.tabs.onActivated.addListener(onTabChanged);
@ -7,8 +6,13 @@ chrome.runtime.onMessage.addListener(onInternalMessage);
tabManagers = {}
const ALWAYS_SCROBBLE_SECONDS = 60*3;
// Longer songs are always scrobbled when playing at least 2 minutes
pages = {
"Plex Web":{
"plex":{
"name":"Plex",
"patterns":[
"https://app.plex.tv",
"http://app.plex.tv",
@ -17,16 +21,52 @@ pages = {
],
"script":"plex.js"
},
"YouTube Music":{
"ytmusic":{
"name":"YouTube Music",
"patterns":[
"https://music.youtube.com",
"http://music.youtube.com"
"https://music.youtube.com"
],
"script":"ytmusic.js"
},
"spotify":{
"name":"Spotify",
"patterns":[
"https://open.spotify.com"
],
"script":"spotify.js"
},
"bandcamp":{
"name":"Bandcamp",
"patterns":[
"bandcamp.com"
],
"script":"bandcamp.js"
},
"soundcloud":{
"name":"Soundcloud",
"patterns":[
"https://soundcloud.com"
],
"script":"soundcloud.js"
},
"navidrome":{
"name":"Navidrome",
"patterns":[
"https://navidrome.",
"http://navidrome."
],
"script":"navidrome.js"
}
}
function updateTabNum() {
var amount = Object.keys(tabManagers).length;
chrome.browserAction.setBadgeText({"text":amount.toString()});
chrome.browserAction.setBadgeBackgroundColor({"color":"#333337"});
}
function onTabUpdated(tabId, changeInfo, tab) {
@ -35,33 +75,49 @@ function onTabUpdated(tabId, changeInfo, tab) {
//console.log("Update to tab " + tabId + "!")
if (tabManagers.hasOwnProperty(tabId)) {
//console.log("Yes!")
page = tabManagers[tabId].page
patterns = pages[page]["patterns"]
page = tabManagers[tabId].page;
patterns = pages[page]["patterns"];
//console.log("Page was managed by a " + page + " manager")
for (var i=0;i<patterns.length;i++) {
if (tab.url.startsWith(patterns[i])) {
if (tab.url.includes(patterns[i])) {
//console.log("Still on same page!")
//tabManagers[tabId].update()
window.setTimeout(tabManagers[tabId].update(),1000);
tabManagers[tabId].update();
// check if the setting for this page is still active
chrome.storage.local.get(["service_active_" + page],function(result){
if (!result["service_active_" + page]) {
delete tabManagers[tabId];
}
});
return
}
}
console.log("Page on tab " + tabId + " changed, removing old " + page + " manager!")
delete tabManagers[tabId]
console.log("Page on tab " + tabId + " changed, removing old " + page + " manager!");
delete tabManagers[tabId];
}
//check if pattern matches
for (var key in pages) {
if (pages.hasOwnProperty(key)) {
patterns = pages[key]["patterns"]
patterns = pages[key]["patterns"];
for (var i=0;i<patterns.length;i++) {
if (tab.url.startsWith(patterns[i])) {
console.log("New page on tab " + tabId + " will be handled by new " + key + " manager!")
tabManagers[tabId] = new Controller(tabId,key)
return
//chrome.tabs.executeScript(tab.id,{"file":"sitescripts/" + pages[key]["script"]})
if (tab.url.includes(patterns[i])) {
// check if we even like that page
chrome.storage.local.get(["service_active_" + key],function(result){
if (result["service_active_" + key]) {
console.log("New page on tab " + tabId + " will be handled by new " + key + " manager!");
tabManagers[tabId] = new Controller(tabId,key);
updateTabNum();
//chrome.tabs.executeScript(tab.id,{"file":"sitescripts/" + pages[key]["script"]})
}
else {
console.log("New page on tab " + tabId + " is " + key + ", not enabled!");
}
});
return;
}
}
}
@ -73,10 +129,11 @@ function onTabUpdated(tabId, changeInfo, tab) {
function onTabRemoved(tabId,removeInfo) {
if (tabManagers.hasOwnProperty(tabId)) {
page = tabManagers[tabId].page
console.log("closed tab was " + page + ", now removing manager")
tabManagers[tabId].stopPlayback("","") //in case we want to scrobble the playing track
delete tabManagers[tabId]
page = tabManagers[tabId].page;
console.log("closed tab was " + page + ", now removing manager");
tabManagers[tabId].stopPlayback("",""); //in case we want to scrobble the playing track
delete tabManagers[tabId];
updateTabNum();
}
}
@ -91,10 +148,10 @@ function onInternalMessage(request,sender) {
for (tabId in tabManagers) {
manager = tabManagers[tabId]
if (manager.currentlyPlaying) {
answer.push([manager.page,manager.currentArtist,manager.currentTitle])
answer.push([pages[manager.page]['name'],manager.currentArtist,manager.currentTitle]);
}
else {
answer.push([manager.page,null])
answer.push([pages[manager.page]['name'],null]);
}
}
@ -107,7 +164,7 @@ function onInternalMessage(request,sender) {
//console.log("Message was sent from tab id " + tabId)
if (tabManagers.hasOwnProperty(tabId)) {
//console.log("This is managed! Seems to be " + tabManagers[tabId].page)
tabManagers[tabId].playbackUpdate(request)
tabManagers[tabId].playbackUpdate(request);
}
}
@ -132,14 +189,35 @@ class Controller {
this.messageID = 0;
this.lastMessage = 0;
this.update()
this.alreadyQueued = false;
// we reject update requests when we're already planning to run an update!
this.update();
}
// the tab has been updated, we need to run the script
update() {
if (this.alreadyQueued) {
}
else {
this.alreadyQueued = true;
setTimeout(() => { this.actuallyupdate(); },800);
//this.actuallyupdate();
}
}
actuallyupdate() {
this.messageID++;
//console.log("Update! Our page is " + this.page + ", our tab id " + this.tabId)
chrome.tabs.executeScript(this.tabId,{"file":"sitescripts/" + pages[this.page]["script"]})
try {
chrome.tabs.executeScript(this.tabId,{"file":"sites/" + pages[this.page]["script"]});
chrome.tabs.executeScript(this.tabId,{"file":"sitescript.js"});
}
catch (e) {
console.log("Could not run site script. Tab probably closed or something idk.")
}
this.alreadyQueued = false;
}
// an actual update message from the script has arrived
@ -160,6 +238,14 @@ class Controller {
}
}
backlog_scrobble() {
while (this.alreadyPlayed > this.currentLength) {
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
var secondsago = this.alreadyPlayed
scrobble(this.currentArtist,this.currentTitle,this.currentLength,secondsago)
}
}
@ -169,11 +255,8 @@ class Controller {
if (artist == this.currentArtist && title == this.currentTitle && !this.currentlyPlaying) {
console.log("Resuming playback of " + this.currentTitle)
// Already played full song
while (this.alreadyPlayed > this.currentLength) {
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
scrobble(this.currentArtist,this.currentTitle,this.currentLength)
}
// Already played full song?
this.backlog_scrobble()
this.setUpdate()
this.currentlyPlaying = true
@ -184,16 +267,22 @@ class Controller {
else if (artist != this.currentArtist || title != this.currentTitle) {
//first inform ourselves that the previous track has now been stopped for good
this.stopPlayback(artist,title)
this.stopPlayback(artist,title);
//then initialize new playback
console.log("New track")
this.setUpdate()
this.alreadyPlayed = 0
this.currentTitle = title
this.currentArtist = artist
this.currentLength = seconds
console.log(artist + " - " + title + " is playing!")
this.currentlyPlaying = true
console.log("New track");
this.setUpdate();
this.alreadyPlayed = 0;
this.currentTitle = title;
this.currentArtist = artist;
if (Number.isInteger(seconds)) {
this.currentLength = seconds;
}
else {
this.currentLength = 300;
// avoid excessive scrobbling when the selector breaks
}
console.log(artist + " - " + title + " is playing! (" + this.currentLength + " seconds)");
this.currentlyPlaying = true;
}
}
@ -209,11 +298,8 @@ class Controller {
}
// Already played full song
while (this.alreadyPlayed > this.currentLength) {
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
scrobble(this.currentArtist,this.currentTitle,this.currentLength)
}
// Already played full song?
this.backlog_scrobble()
this.currentlyPlaying = false
@ -221,7 +307,7 @@ class Controller {
//ONLY CASE 2: Playback ended
if (artist != this.currentArtist || title != this.currentTitle) {
if (this.alreadyPlayed > this.currentLength / 2) {
if ((this.alreadyPlayed > this.currentLength / 2) || (this.alreadyPlayed > ALWAYS_SCROBBLE_SECONDS)) {
scrobble(this.currentArtist,this.currentTitle,this.alreadyPlayed)
this.alreadyPlayed = 0
}
@ -246,18 +332,27 @@ class Controller {
function scrobble(artist,title,seconds) {
console.log("Scrobbling " + artist + " - " + title + "; " + seconds + " seconds playtime")
artiststring = encodeURIComponent(artist)
titlestring = encodeURIComponent(title)
chrome.storage.local.get("apikey",function(result) {
APIKEY = result["apikey"]
chrome.storage.local.get("serverurl",function(result) {
URL = result["serverurl"]
var xhttp = new XMLHttpRequest();
xhttp.open("POST",URL + "/db/newscrobble",true);
xhttp.send("artist=" + artiststring + "&title=" + titlestring + "&duration=" + seconds + "&key=" + APIKEY)
});
function scrobble(artist,title,seconds,secondsago=0) {
console.log("Scrobbling " + artist + " - " + title + "; " + seconds + " seconds playtime, " + secondsago + " seconds ago")
var d = new Date()
var time = Math.floor(d.getTime()/1000) - secondsago
//console.log("Time: " + time)
var payload = {
"artist":artist,
"title":title,
"duration":seconds,
"time":time
}
chrome.storage.local.get(["serverurl","apikey"],function(result) {
payload["key"] = result["apikey"];
var xhttp = new XMLHttpRequest();
xhttp.open("POST",result["serverurl"] + "/apis/mlj_1/newscrobble",true);
xhttp.setRequestHeader("Content-Type", "application/json");
//xhttp.send(requestbody + "&key=" + APIKEY)
var body = JSON.stringify(payload);
xhttp.send(body)
//console.log("Sent: " + body)
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,15 +1,13 @@
{
"name": "Maloja Scrobbler",
"version": "1.0",
"version": "1.13",
"description": "Scrobbles tracks from various sites to your Maloja server",
"manifest_version": 2,
"permissions": ["activeTab",
"declarativeContent",
"permissions": [
"tabs",
"storage",
"http://*/",
"https://*/",
"<all_urls>"
"https://*/"
],
"background":
{
@ -28,7 +26,7 @@
"48":"icon48.png"
},
"default_popup": "settings.html",
"default_title": "Settings"
"default_title": "Maloja Scrobbler"
},
"icons":
{

View File

@ -14,7 +14,7 @@
color:beige;
font-family:'Ubuntu';
}
input {
input[type=text] {
width:270px;
font-family:'Ubuntu';
outline:none;
@ -29,14 +29,18 @@
<body>
<div id="wat">
<span id="checkmark_url"></span> <span>Server:</span><br />
<input type="text" id="serverurl" value="http://localhost:42010" />
<input type="text" id="serverurl" />
<br /><br />
<span id="checkmark_key"></span> <span>API key:</span><br />
<input type="text" id="apikey" />
<br/><br/>
<hr/>
<span>Tabs:</span>
<list id="playinglist">
</list>
<hr/>
<span>Services:</span>
<list id="sitelist">
</list>
</div>
</body>
</html>

View File

@ -1,7 +1,44 @@
// duplicate this info for now, don't know if there is a better way than sending messages
var pages = {
"plex":"Plex",
"ytmusic":"YouTube Music",
"spotify":"Spotify",
"bandcamp":"Bandcamp",
"soundcloud":"Soundcloud",
"navidrome":"Navidrome"
}
var config_defaults = {
serverurl:"http://localhost:42010",
apikey:"BlackPinkInYourArea"
}
for (var key in pages) {
config_defaults["service_active_" + key] = true;
}
document.addEventListener("DOMContentLoaded",function() {
document.getElementById("serverurl").addEventListener("input",updateServer);
document.getElementById("apikey").addEventListener("input",updateAPIKey);
var sitelist = document.getElementById("sitelist");
for (var identifier in pages) {
sitelist.append(document.createElement('br'));
var checkbox = document.createElement('input');
checkbox.type = "checkbox";
checkbox.id = "service_active_" + identifier;
var label = document.createElement('label');
label.for = checkbox.id;
label.textContent = pages[identifier];
sitelist.appendChild(checkbox);
sitelist.appendChild(label);
checkbox.addEventListener("change",toggleSite);
}
document.getElementById("serverurl").addEventListener("change",checkServer);
document.getElementById("apikey").addEventListener("change",checkServer);
@ -9,18 +46,29 @@ document.addEventListener("DOMContentLoaded",function() {
document.getElementById("serverurl").addEventListener("focusout",checkServer);
document.getElementById("apikey").addEventListener("focusout",checkServer);
document.getElementById("serverurl").addEventListener("input",saveServer);
document.getElementById("apikey").addEventListener("input",saveServer);
chrome.runtime.onMessage.addListener(onInternalMessage);
chrome.storage.local.get(config_defaults,function(result){
console.log(result);
for (var key in result) {
chrome.storage.local.get({"serverurl":"http://localhost:42010"},function(result) {
document.getElementById("serverurl").value = result["serverurl"]
checkServerMaybe()
});
chrome.storage.local.get({"apikey":"BlackPinkInYourArea"},function(result) {
document.getElementById("apikey").value = result["apikey"]
checkServerMaybe()
});
// booleans
if (result[key] == true || result[key] == false) {
document.getElementById(key).checked = result[key];
}
// text
else{
document.getElementById(key).value = result[key];
}
}
checkServer();
})
chrome.runtime.sendMessage({"type":"query"})
@ -28,18 +76,12 @@ document.addEventListener("DOMContentLoaded",function() {
});
//this makes sure only the second call actually makes a request (the first request is pointless
//when the other element isn't filled yet and might actually overwrite the correct result because
//of a race condition)
var done = 0
function checkServerMaybe() {
done++;
if (done == 2) {
checkServer()
}
function toggleSite(evt) {
var element = evt.target;
chrome.storage.local.set({ [element.id]: element.checked });
}
function onInternalMessage(request,sender) {
if (request.type == "response") {
players = request.content
@ -58,21 +100,15 @@ function onInternalMessage(request,sender) {
function updateServer() {
text = document.getElementById("serverurl").value
chrome.storage.local.set({"serverurl":text})
}
function updateAPIKey() {
text = document.getElementById("apikey").value
chrome.storage.local.set({"apikey":text})
function saveServer() {
for (var key of ["serverurl","apikey"]) {
var value = document.getElementById(key).value;
chrome.storage.local.set({ [key]: value });
}
}
function checkServer() {
url = document.getElementById("serverurl").value + "/db/test?key=" + document.getElementById("apikey").value
url = document.getElementById("serverurl").value + "/api/test?key=" + document.getElementById("apikey").value
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = createCheckmarks;
@ -91,7 +127,7 @@ function checkServer() {
function createCheckmarks() {
if (this.readyState == 4) {
if ((this.status == 204) || (this.status == 205)) {
if ((this.status >= 200) && (this.status < 300)) {
//document.getElementById("checkmark_url").innerHTML = "✔️"
//document.getElementById("checkmark_key").innerHTML = "✔️"
document.getElementById("serverurl").style.backgroundColor = "lawngreen"

View File

@ -0,0 +1,15 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'trackView')]"
maloja_scrobbler_selector_metadata = "."
// need to select everything as bar / metadata block because artist isn't shown in the inline player
maloja_scrobbler_selector_title = ".//span[@class='title']/text()"
maloja_scrobbler_selector_artist = ".//span[contains(@itemprop,'byArtist')]/a/text()"
maloja_scrobbler_selector_duration = ".//span[@class='time_total']/text()"
maloja_scrobbler_selector_control = ".//td[@class='play_cell']/a[@role='button']/div[contains(@class,'playbutton')]/@class"
maloja_scrobbler_label_playing = "playbutton playing"
maloja_scrobbler_label_paused = "playbutton"

View File

@ -0,0 +1,14 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'music-player-panel')]"
maloja_scrobbler_selector_metadata = ".//span[contains(@class,'audio-title')]"
maloja_scrobbler_selector_title = ".//span[contains(@class,'songTitle')]/text()"
maloja_scrobbler_selector_artist = ".//span[contains(@class,'songArtist')]/text()"
maloja_scrobbler_selector_duration = ".//span[contains(@class,'duration')]/text()"
maloja_scrobbler_selector_control = ".//span[contains(@class,'group play-btn')]/@title"
maloja_scrobbler_label_playing = "Click to pause"
maloja_scrobbler_label_paused = "Click to play"

View File

@ -0,0 +1,11 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'PlayerControls')]"
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'PlayerControlsMetadata-container')]"
maloja_scrobbler_selector_title = ".//a[@data-testid='metadataTitleLink']/@title"
maloja_scrobbler_selector_artist = ".//span[contains(@class,'MetadataPosterTitle-title')]/a[1]/@title"
maloja_scrobbler_selector_duration = ".//button[@data-testid='mediaDuration']/text()[3]"
maloja_scrobbler_selector_control = ".//div[contains(@class,'PlayerControls-buttonGroupCenter')]/button[2]/@title"

View File

@ -0,0 +1,14 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'playControls')]"
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'playControls__soundBadge')]//div[contains(@class,'playbackSoundBadge__titleContextContainer')]"
maloja_scrobbler_selector_title = ".//div/a/@title"
maloja_scrobbler_selector_artist = ".//a/text()"
maloja_scrobbler_selector_duration = ".//div[contains(@class,'playbackTimeline__duration')]//span[@aria-hidden='true']/text()"
maloja_scrobbler_selector_control = ".//button[contains(@class,'playControl')]/@title"
maloja_scrobbler_label_playing = "Pause current"
maloja_scrobbler_label_paused = "Play current"

View File

@ -0,0 +1,12 @@
maloja_scrobbler_selector_playbar = "//footer[@data-testid='now-playing-bar']"
maloja_scrobbler_selector_metadata = ".//div[@data-testid='now-playing-widget']"
maloja_scrobbler_selector_title = ".//a[@data-testid='context-item-link']/text()"
maloja_scrobbler_selector_artists = ".//a[contains(@href,'/artist/')]"
maloja_scrobbler_selector_artist = "./text()"
maloja_scrobbler_selector_duration = ".//div[@data-testid='playback-duration']/text()"
maloja_scrobbler_selector_control = ".//button[@data-testid='control-button-playpause']/@aria-label"

View File

@ -0,0 +1,13 @@
maloja_scrobbler_selector_playbar = "//ytmusic-player-bar"
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'middle-controls')]/div[contains(@class,'content-info-wrapper')]"
maloja_scrobbler_selector_title = ".//yt-formatted-string[contains(@class,'title')]/@title"
maloja_scrobbler_selector_artists = ".//span/span[contains(@class,'subtitle')]/yt-formatted-string/a[position()<last()]"
maloja_scrobbler_selector_artist = "./text()"
maloja_scrobbler_selector_duration = ".//div[contains(@class,'left-controls')]/span[contains(@class,'time-info')]/text()"
duration_needs_split = true
maloja_scrobbler_selector_control = ".//div[contains(@class,'left-controls')]/div/paper-icon-button[contains(@class,'play-pause-button')]/@title"

View File

@ -0,0 +1,91 @@
function getxpath(path,type) {
result = document.evaluate(path, this, null, type, null);
if (type == XPathResult.FIRST_ORDERED_NODE_TYPE) {
return result.singleNodeValue;
}
else if (type == XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
resultarray = [];
while(node = result.iterateNext()) {
resultarray.push(node);
}
return resultarray;
}
else if (type == XPathResult.STRING_TYPE) {
return result.stringValue;
}
// if (path.split("/").slice(-1)[0].startsWith("text()") || path.split("/").slice(-1)[0].startsWith("@")) {
// result = document.evaluate(path, this, null, XPathResult.STRING_TYPE, null);
// return result.stringValue;
// }
// else {
// result = document.evaluate(path, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
// return result.singleNodeValue;
// }
}
Node.prototype.xpath = getxpath;
bar = document.xpath(maloja_scrobbler_selector_playbar, XPathResult.FIRST_ORDERED_NODE_TYPE);
if (bar == null) {
console.log("[Maloja Scrobbler] Nothing playing right now!");
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:"",title:""});
}
else {
metadata = bar.xpath(maloja_scrobbler_selector_metadata, XPathResult.FIRST_ORDERED_NODE_TYPE);
duration = bar.xpath(maloja_scrobbler_selector_duration, XPathResult.STRING_TYPE);
duration = duration + '';
title = metadata.xpath(maloja_scrobbler_selector_title, XPathResult.STRING_TYPE);
if (typeof maloja_scrobbler_selector_artists !== "undefined") {
artistnodes = metadata.xpath(maloja_scrobbler_selector_artists, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
artists = artistnodes.map(x => x.xpath(maloja_scrobbler_selector_artist, XPathResult.STRING_TYPE));
artist = artists.join(";");
}
else {
artist = metadata.xpath(maloja_scrobbler_selector_artist, XPathResult.STRING_TYPE);
}
if (typeof duration_needs_split !== "undefined" && duration_needs_split) {
duration = duration.split("/").slice(-1)[0].trim();
}
if (duration.split(":").length == 2) {
durationSeconds = parseInt(duration.split(":")[0]) * 60 + parseInt(duration.split(":")[1]);
}
else {
durationSeconds = parseInt(duration.split(":")[0]) * 60 * 60 + parseInt(duration.split(":")[1]) * 60 + parseInt(duration.split(":")[2]);
}
control = bar.xpath(maloja_scrobbler_selector_control, XPathResult.STRING_TYPE);
try {
label_playing = maloja_scrobbler_label_playing
}
catch {
label_playing = "Pause"
}
try {
label_paused = maloja_scrobbler_label_paused
}
catch {
label_paused = "Play"
}
if (control == label_paused) {
console.log("[Maloja Scrobbler] Not playing right now");
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:artist,title:title});
//stopPlayback()
}
else if (control == label_playing) {
console.log("[Maloja Scrobbler] Playing " + artist + " - " + title + " (" + durationSeconds + " sec)");
chrome.runtime.sendMessage({type:"startPlayback",time:Math.floor(Date.now()),artist:artist,title:title,duration:durationSeconds});
//startPlayback(artist,title,durationSeconds)
}
}

View File

@ -0,0 +1,12 @@
# maloja-lib
Library for Python music players to allow users to scrobble to [Maloja](https://github.com/krateng/maloja) servers.
```
from malojalib import MalojaInstance
instance = MalojaInstance(user_supplied_url,user_supplied_key)
instance.scrobble(artists=['K/DA','Howard Shore','Blackbeard's Tea Party],title='Grüezi Wohl Frau Stirnimaa')
```

View File

@ -0,0 +1,27 @@
import requests
class MalojaInstance:
def __init__(self,base_url,key):
self.base_url = base_url
self.key = key
def test(self):
url = self.base_url + '/apis/mlj_1/test'
response = requests.get(url,{'key':self.key})
return (response.status_code == 200)
def scrobble(self,artists,title,timestamp=None,album=None,duration=None):
payload = {
'key':self.key,
'artists':artists,
'title':title,
'time':timestamp,
'album':album,
'duration':duration
}
url = self.base_url + '/apis/mlj_1/newscrobble'
response = requests.post(url,payload)
return response.json()

View File

@ -0,0 +1,27 @@
[project]
name = "maloja-lib"
version = "1.0.0"
description = "Utilities to interact with Maloja servers"
readme = "./README.md"
requires-python = ">=3.6"
license = { file="../../LICENSE" }
authors = [ { name="Johannes Krattenmacher", email="maloja@dev.krateng.ch" } ]
urls.repository = "https://github.com/krateng/maloja"
urls.documentation = "https://github.com/krateng/maloja"
keywords = ["scrobbling", "music", "library", "api"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent"
]
dependencies = [
"requests"
]
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

3
cache/.gitignore vendored
View File

@ -1,3 +0,0 @@
*
!.gitignore
!*.info

View File

@ -1,201 +0,0 @@
import re
import utilities
from doreah import tsv
# need to do this as a class so it can retain loaded settings from file
# apparently this is not true
# I'm dumb
class CleanerAgent:
def __init__(self):
self.updateRules()
def updateRules(self):
raw = tsv.parse_all("rules","string","string","string")
self.rules_belongtogether = [b for [a,b,c] in raw if a=="belongtogether"]
self.rules_notanartist = [b for [a,b,c] in raw if a=="notanartist"]
self.rules_replacetitle = {b.lower():c for [a,b,c] in raw if a=="replacetitle"}
self.rules_replaceartist = {b.lower():c for [a,b,c] in raw if a=="replaceartist"}
# we always need to be able to tell if our current database is made with the current rules
self.checksums = utilities.checksumTSV("rules")
def fullclean(self,artist,title):
artists = self.parseArtists(self.removespecial(artist))
title = self.parseTitle(self.removespecial(title))
(title,moreartists) = self.parseTitleForArtists(title)
artists += moreartists
artists = list(set(artists))
artists.sort()
return (artists,title)
def removespecial(self,s):
s = s.replace("\t","").replace("","").replace("\n","")
s = re.sub(" +"," ",s)
return s
# if an artist appears in any created rule, we can assume that artist is meant to exist and be spelled like that
def confirmedReal(self,a):
confirmed = self.rules_belongtogether + [self.rules_replaceartist[r] for r in self.rules_replaceartist]
return (a in confirmed)
#Delimiters used for extra artists, even when in the title field
delimiters_feat = ["ft.","ft","feat.","feat","featuring","Ft.","Ft","Feat.","Feat","Featuring"]
#Delimiters in informal artist strings, spaces expected around them
delimiters = ["vs.","vs","&"]
#Delimiters used specifically to tag multiple artists when only one tag field is available, no spaces used
delimiters_formal = ["; ",";","/"]
def parseArtists(self,a):
if a.strip() == "":
return []
if a.strip() in self.rules_notanartist:
return []
if " performing " in a.lower():
return self.parseArtists(re.split(" [Pp]erforming",a)[0])
if a.strip() in self.rules_belongtogether:
return [a.strip()]
if a.strip().lower() in self.rules_replaceartist:
return self.rules_replaceartist[a.strip().lower()].split("")
for d in self.delimiters_feat:
if re.match(r"(.*) \(" + d + " (.*)\)",a) is not None:
return self.parseArtists(re.sub(r"(.*) \(" + d + " (.*)\)",r"\1",a)) + \
self.parseArtists(re.sub(r"(.*) \(" + d + " (.*)\)",r"\2",a))
for d in self.delimiters_formal:
if (d in a):
ls = []
for i in a.split(d):
ls += self.parseArtists(i)
return ls
for d in (self.delimiters_feat + self.delimiters):
if ((" " + d + " ") in a):
ls = []
for i in a.split(" " + d + " "):
ls += self.parseArtists(i)
return ls
return [a.strip()]
def parseTitle(self,t):
if t.strip().lower() in self.rules_replacetitle:
return self.rules_replacetitle[t.strip().lower()]
t = t.replace("[","(").replace("]",")")
t = re.sub(r" \(as made famous by .*?\)","",t)
t = re.sub(r" \(originally by .*?\)","",t)
t = re.sub(r" \(.*?Remaster.*?\)","",t)
return t.strip()
def parseTitleForArtists(self,t):
for d in self.delimiters_feat:
if re.match(r"(.*) \(" + d + " (.*?)\)",t) is not None:
(title,artists) = self.parseTitleForArtists(re.sub(r"(.*) \(" + d + " (.*?)\)",r"\1",t))
artists += self.parseArtists(re.sub(r"(.*) \(" + d + " (.*?)\).*",r"\2",t))
return (title,artists)
if re.match(r"(.*) - " + d + " (.*)",t) is not None:
(title,artists) = self.parseTitleForArtists(re.sub(r"(.*) - " + d + " (.*)",r"\1",t))
artists += self.parseArtists(re.sub(r"(.*) - " + d + " (.*).*",r"\2",t))
return (title,artists)
if re.match(r"(.*) " + d + " (.*)",t) is not None:
(title,artists) = self.parseTitleForArtists(re.sub(r"(.*) " + d + " (.*)",r"\1",t))
artists += self.parseArtists(re.sub(r"(.*) " + d + " (.*).*",r"\2",t))
return (title,artists)
return (t,[])
#this is for all the runtime changes (counting Trouble Maker as HyunA for charts etc)
class CollectorAgent:
def __init__(self):
self.updateRules()
# rules_countas dict: real artist -> credited artist
# rules_countas_id dict: real artist ID -> credited artist ID
# rules_include dict: credited artist -> all real artists
def updateRules(self):
raw = tsv.parse_all("rules","string","string","string")
self.rules_countas = {b:c for [a,b,c] in raw if a=="countas"}
self.rules_countas_id = {}
self.rules_include = {} #Twice the memory, double the performance!
# (Yes, we're saving redundant information here, but it's not unelegant if it's within a closed object!)
for a in self.rules_countas:
self.rules_include[self.rules_countas[a]] = self.rules_include.setdefault(self.rules_countas[a],[]) + [a]
# this agent needs to be aware of the current id assignment in the main program
# unelegant, but the best way i can think of
def updateIDs(self,artistlist):
self.rules_countas_id = {artistlist.index(a):artistlist.index(self.rules_countas[a]) for a in self.rules_countas if a in artistlist}
#self.rules_include_id = {artistlist.index(a):artistlist.index(self.rules_include[a]) for a in self.rules_include}
#this needs to take lists into account
# get who is credited for this artist
def getCredited(self,artist):
if artist in self.rules_countas:
return self.rules_countas[artist]
if artist in self.rules_countas_id:
return self.rules_countas_id[artist]
else:
return artist
# get all credited artists for the artists given
def getCreditedList(self,artists):
updatedArtists = []
for artist in artists:
updatedArtists.append(self.getCredited(artist))
return list(set(updatedArtists))
# get artists who the given artist is given credit for
def getAllAssociated(self,artist):
return self.rules_include.get(artist,[])
# this function is there to check for artists that we should include in the
# database even though they never have any scrobble.
def getAllArtists(self):
return list(set([self.rules_countas[a] for a in self.rules_countas]))
# artists that count can be nonexisting (counting HyunA as 4Minute even
# though 4Minute has never been listened to)
# but artists that are counted as someone else are only relevant if they
# exist (so we can preemptively declare lots of rules just in case)
#return list(set([a for a in self.rules_countas] + [self.rules_countas[a] for a in self.rules_countas]))
def flatten(lis):
newlist = []
for l in lis:
if isinstance(l, str):
newlist.append(l)
else:
newlist = newlist + l
return list(set(newlist))

1
clients/.gitignore vendored
View File

@ -1 +0,0 @@
!example_file.tsv

View File

@ -1,2 +0,0 @@
# Only the entries in authenticated_machines.tsv are used, this is an example file
YDzcmp8JpYHCcvJbDOVT7nEDoyCEND6K Example Machine
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -0,0 +1,10 @@
#!/usr/bin/with-contenv bash
if [ "$(s6-setuidgid abc id -u)" = "0" ]; then
echo "-------------------------------------"
echo "WARN: Running as root! If you meant to do this than this message can be ignored."
echo "If you are running this container on a *linux* host and are not using podman rootless you SHOULD"
echo "change the ENVs PUID and PGID for this container to ensure correct permissions on your config folder."
echo -e "See: https://github.com/krateng/maloja#linux-host\n"
echo -e "-------------------------------------\n"
fi

View File

@ -0,0 +1 @@
oneshot

View File

@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-permission-check/run

View File

@ -0,0 +1,7 @@
#!/usr/bin/with-contenv bash
# used https://github.com/linuxserver/docker-wikijs/blob/master/root/etc/s6-overlay/s6-rc.d/svc-wikijs/run as a template
echo -e "\nMaloja is starting!"
exec \
s6-setuidgid abc python -m maloja run

View File

@ -0,0 +1 @@
longrun

File diff suppressed because it is too large Load Diff

1
dev/list_tags.sh Normal file
View File

@ -0,0 +1 @@
git tag -l '*.0' -n1 --sort=v:refname

21
dev/package.py Normal file
View File

@ -0,0 +1,21 @@
import toml
import os
with open("pyproject.toml") as filed:
data = toml.load(filed)
info = {
'name':data['project']['name'],
'license':"GPLv3",
'version':data['project']['version'],
'architecture':'all',
'description':'"' + data['project']['description'] + '"',
'url':'"' + data['project']['urls']['homepage'] + '"',
'maintainer':f"\"{data['project']['authors'][0]['name']} <{data['project']['authors'][0]['email']}>\"",
}
for target in ["apk","deb"]:
lcmd = f"fpm {' '.join(f'--{key} {info[key]}' for key in info)} -s python -t {target} . "
print(lcmd)
os.system(lcmd)

3
dev/releases/1.0.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "Yura"
'1.0':
commit: "1fac2ca965fdbe40c85a88559d5b736f4829e7b0"

3
dev/releases/1.1.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "Solar"
'1.1':
commit: "5603ca9eb137516e604e9e3e83e273a70ef32f65"

3
dev/releases/1.2.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "Jeonghwa"
'1.2':
commit: "d46d2be2bf27ef40ddd9f0c077f86dcf0214adbb"

3
dev/releases/1.3.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "IU"
'1.3':
commit: "0bf1790a7cc0174b84f8c25dade6b221b13d65e9"

3
dev/releases/1.4.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "Chungha"
'1.4':
commit: "981c0e4ae2ad1bff5a0778b6fa34916b0c4d4f4a"

3
dev/releases/1.5.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "Seulgi"
'1.5':
commit: "e282789153ec3df133474a56e8d922a73795b72a"

5
dev/releases/2.0.yml Normal file
View File

@ -0,0 +1,5 @@
minor_release_name: "Irene"
'2.0':
commit: "55621ef4efdf61c3092d42565e897dfbaa0244c8"
notes:
- "[Architecture] Refactored into Python Package"

5
dev/releases/2.1.yml Normal file
View File

@ -0,0 +1,5 @@
minor_release_name: "Jennie"
2.1.0:
commit: "b87379ed986640788201f1ff52826413067e5ffb"
2.1.4:
commit: "c95ce17451cb19b4775a819f82a532d3a3a6231b"

17
dev/releases/2.10.yml Normal file
View File

@ -0,0 +1,17 @@
minor_release_name: "Yeri"
2.10.0:
commit: "ce9d882856be8f6caca14ab7e5b9f13d6c31940b"
2.10.1:
commit: "f555ee9d9fc485c6a241f4a8fa88bd68527ed2e2"
2.10.2:
commit: "9a6617b4b1117a6e53d818aadcda886c831e16db"
2.10.3:
commit: "5b7d1fd8e9f70a8bf9c5bfbe4aca5b796578e114"
2.10.4:
commit: "3cf0dd9767fe62702b2f5c4f0267a234338a972b"
2.10.5:
commit: "034bd064f11ef18ebbfcb25bd8acac8eacce1324"
2.10.6:
commit: "f62fd254dd44deca50a860f3a966390ae9c3662c"
2.10.7:
commit: "212fbf368e38281f45a0d8dd63dc051dbd8cd8cf"

3
dev/releases/2.11.yml Normal file
View File

@ -0,0 +1,3 @@
minor_release_name: "Akali"
2.11.0:
commit: "218313f80c160f90b28d99236a062ef62db7260d"

41
dev/releases/2.12.yml Normal file
View File

@ -0,0 +1,41 @@
minor_release_name: "Tzuyu"
2.12.0:
commit: "723efcb8ba12f7bda9acc21d81d9930265881c15"
2.12.1:
commit: "a42ed56d2de47f88f873737f0f1374e99be895bf"
2.12.2:
commit: "5006ad2bf1a6d5132ed07d28a7f6f0a9a454d5a7"
2.12.3:
commit: "2a5d9498d1dd7bb6ac62a27d518a87542ec3f344"
2.12.4:
commit: "06c32e205e95df8d3d1e27876887b7da7aa2bdf4"
2.12.5:
commit: "c2f8ecc2dfa1ac4febde228fce150e08fb47be38"
2.12.6:
commit: "a652a22a96ab693a4e7c3271520e6ad79fa025af"
2.12.7:
commit: "5455abd0d1c9ecc8e000d04257f721babacb18e9"
2.12.8:
commit: "8ebd27ab76ad2dcaf1dfef0cc171900fa20d5ee5"
2.12.9:
commit: "49598e914f4e2d7895959bea954238db8a6cee78"
2.12.10:
commit: "9037403777faa089092cf103181027febf6f0340"
2.12.12:
commit: "eaaa0f3b53aa102ba1eb709c1803e94752017d86"
2.12.13:
commit: "c31770a34c96cecc87af78f861819cc49fe98dda"
2.12.14:
commit: "5157ce825eea9bf7b74123cb02dd28e25c6a0767"
2.12.15:
commit: "33af60ed2c8c980f17338827a9cb96e7f2fd2572"
2.12.16:
commit: "26dfdfb569d0beaf4ba8c6c67a9e2295d1362eed"
2.12.17:
commit: "21012234409c01fec3cb5c506f0b4ba74b735b0b"
2.12.18:
commit: "59eaa2264aefa6c9ed5f38e8490f77150bcae27b"
2.12.19:
commit: "8958eb1b547f07d5d063c46cbe59ec57e000ecae"
2.12.20:
commit: "7774d9a9361db986092e143e1bc397ce7a7524dd"

11
dev/releases/2.13.yml Normal file
View File

@ -0,0 +1,11 @@
minor_release_name: "Aqua"
2.13.0:
commit: "8555b28fbc9a220577260014b7f71f433263cb9f"
2.13.1:
commit: "cefed03bc95dd5641b918f79b6ed14b2bfc9898d"
2.13.2:
commit: "0f5ccd4645ead8d1ad48a532752d401424edb236"
2.13.3:
commit: "40648b66f36894a297633c650e570ac77555d143"
2.13.4:
commit: "0ccd39ffd99b19e4cd1b1a14f97bfb4385662eeb"

23
dev/releases/2.14.yml Normal file
View File

@ -0,0 +1,23 @@
minor_release_name: "Mina"
2.14.0:
commit: "1b0e3ffdb2389ae6ca484c78840756d0b7e5c0be"
2.14.1:
commit: "fb2dff8addc7eaf740c5e30cbcd6791aab882c56"
2.14.2:
commit: "cd8e0ff90abf7d01761b0576c4168254b9b1f7c1"
2.14.3:
commit: "f806fb8ed24dd1474b80e7b1a9a7637cdbd35905"
2.14.4:
commit: "868b8396a0a4ff0f687e651772d746af6d9dfab1"
2.14.5:
commit: "21d1643988e40a02531bcc708f43925789d854d1"
2.14.6:
commit: "ccbb3d3a807fd77a1481f9d44f311c7f8df659c7"
2.14.7:
commit: "634df2ffafdfa00b6caf981108d333e30bf160f8"
2.14.8:
commit: "ec5723d2b3122faaa5b76c5a1b156c9a915af9d6"
2.14.9:
commit: "2c73c81434e1a591685a4b1d267a9eb6dbd57174"
2.14.10:
commit: "e152a2edde836f8fb30427d13eb1e9e0d591a00b"

11
dev/releases/2.2.yml Normal file
View File

@ -0,0 +1,11 @@
minor_release_name: "Rosé"
2.2.0:
commit: "33cea26a791e224625aa9bc523e2cf90e39c8a50"
2.2.1:
commit: "fbce600c4edd2b530e6673b89513b1a26b068b64"
2.2.2:
commit: "c518627670f5614a2b9931471337a1a6b2ee344f"
2.2.3:
commit: "a2cc27ddd46c7cf9959f33478eac396e18f90055"
2.2.4:
commit: "c6deb1543779ce8b09af6bcbdc35e7668af86010"

19
dev/releases/2.3.yml Normal file
View File

@ -0,0 +1,19 @@
minor_release_name: "Nancy"
2.3.0:
commit: "8793b149f501fe5f3e237d7ae0fcd23c8f4e5e9d"
2.3.1:
commit: "7c6e2ad60f15d8c4ac85a0808a0abd07549a4a2b"
2.3.2:
commit: "5a08fd78c69c4047b82ff9c394ea23d25356758e"
2.3.3:
commit: "9cf1fb3ed83817168dfe2ac30a42dcadb080c043"
2.3.4:
commit: "eb82282e58259b243958e7590506bd26f8e92db0"
2.3.5:
commit: "a4f13f6923b7783509462944f1abb235b4a068d0"
2.3.6:
commit: "b611387011e4cbd274e210d0c21c83d15302281c"
2.3.7:
commit: "b17060184b6897b18cf8af28a3817c9989aac96f"
2.3.8:
commit: "afe01c8341acd4cf9f4b84fbba85aab6777fd230"

29
dev/releases/2.4.yml Normal file
View File

@ -0,0 +1,29 @@
minor_release_name: "Songhee"
2.4.0:
commit: "6aa65bf1ce273d9fd36d44f6e24439981b2228a3"
2.4.1:
commit: "b117e6f7ec80afc6210314ce97bac087d5ab7b54"
2.4.2:
commit: "d989134e65c20ab33b0ea8e4a132655074057757"
2.4.3:
commit: "9b787fa3b13d77a9cfbe21061f519defac7fafd0"
2.4.4:
commit: "948772b1c26070d7814871824b970fb60fc6976d"
2.4.5:
commit: "2da5ab83b3a410b02af48e70b298069218a7e2a3"
2.4.6:
commit: "65f9c88da4d56df37e4a3f974d7f660502c7a310"
2.4.7:
commit: "c166620d5f9706e54f9cd67044d42bf8583575d8"
2.4.8:
commit: "98c1527f778958b1a3322a4f026cfe2c421388aa"
2.4.9:
commit: "b21b27bb6e230901281bb524f84e177c937b48fd"
2.4.10:
commit: "08fe4695f6d5ef09789688481db478d0decbd5df"
2.4.11:
commit: "5c6a901f5118be54ae44affbd6881b14bc30e04a"
2.4.12:
commit: "6658165baedeee3939084ba4500de3de06bbc045"
2.4.13:
commit: "57403a89ab1d679523341d6a607d0b03e495ff35"

9
dev/releases/2.5.yml Normal file
View File

@ -0,0 +1,9 @@
minor_release_name: "Seungeun"
2.5.0:
commit: "990131f546876d1461bac745e5cab3e60c78d038"
2.5.1:
commit: "0918444ab6ff934ba83393e294a135b1fc25bd0c"
2.5.2:
commit: "0918444ab6ff934ba83393e294a135b1fc25bd0c"
2.5.3:
commit: "0918444ab6ff934ba83393e294a135b1fc25bd0c"

21
dev/releases/2.6.yml Normal file
View File

@ -0,0 +1,21 @@
minor_release_name: "HyunA"
2.6.0:
commit: "b161da1c1a1632725a44e998ff0d1872b3d5d184"
2.6.1:
commit: "1eae55e3bba335d41da0d21dfc383b838d9f0d03"
2.6.2:
commit: "dd3c83920b668466f2c053434bfd6be93bf32942"
2.6.3:
commit: "27f3ff6d085f42bdb67385f967db904022339d1d"
2.6.4:
commit: "5f8e73e6c714e9ca94a66f48d1b72fe516bbb0da"
2.6.5:
commit: "0fdd7669cced6c2b47f657e510bda03a053ee7ae"
2.6.6:
commit: "87cdb9987efe08b466f99f9ccb8b808131f9fbcd"
2.6.7:
commit: "0bdc4654bfb0f42d838e15c3d36dab0b4472db00"
2.6.8:
commit: "bdfb2a4a0b48362aabda7bb735296d83a02b932d"
2.6.9:
commit: "cb7a6d224152048176e6187ede6d60625961ab39"

25
dev/releases/2.7.yml Normal file
View File

@ -0,0 +1,25 @@
minor_release_name: "Shanshan"
2.7.0:
commit: "8d7fb9a2c8be3f813ee5994be1818f9f81088faa"
2.7.1:
commit: "6885fbdeccb8b690fa0af59d8fd341e44803798f"
2.7.2:
commit: "4113d1761e28a7ee3b3cdabe4404cf3876f1fc84"
2.7.3:
commit: "1563a15abde175022b50fa085c6b9b19a6021c31"
2.7.4:
commit: "3e6bcc45d55446c6607664e407768391b47c5421"
2.7.5:
commit: "fa05c406606e269fb4153465611caeb71c12b486"
2.7.6:
commit: "47087b4288cbfa6000ca019a000f27ee5846d161"
2.7.7:
commit: "379ee49f1c61df9720346d3d021dea040587d54d"
2.7.8:
commit: "75bd823ad0cc24efecd1de193436a28dfaecd4f3"
2.7.9:
commit: "fb04dd507cee42092b889fe72cdf9975ea48e3b1"
2.7.10:
commit: "7fc879f77818371721e21c13e9df98796cf632de"
2.7.11:
commit: "44a2739a3b6e58cb90b7f7dfca2197834cf30464"

13
dev/releases/2.8.yml Normal file
View File

@ -0,0 +1,13 @@
minor_release_name: "Haeun"
2.8.0:
commit: "25661f82af9338a024aae429cdafec7c86692aa5"
2.8.1:
commit: "1321fcb45ebe0291c9fd47ff2eb8cc329035acf3"
2.8.2:
commit: "e27a83bdc99a06a207c67c6f0034bc0a554c89af"
2.8.3:
commit: "6acab324dbd3594dcfbf944bfdfb5c8fe173354b"
2.8.4:
commit: "f7f1b1225e64b54d8962467182bddcc1de237f51"
2.8.5:
commit: "1dbc0b7fca05830d654076c74a91b6b74f470d5b"

23
dev/releases/2.9.yml Normal file
View File

@ -0,0 +1,23 @@
minor_release_name: "Yaorenmao"
2.9.0:
commit: "8b4e9609e994d74506fd91471bd5a622b75b2f08"
2.9.1:
commit: "52a9faae90175841b2c259dd4677697e513e12f9"
2.9.2:
commit: "5cf7ca2e9bbf66082c4afb76b4033ff17c9cf8c8"
2.9.3:
commit: "e8c316f1992c3e5f171891272f32d959bb1fa4f0"
2.9.4:
commit: "e8a87cb8a5e2f63850ff3c02ed5aa8ee388460ed"
2.9.5:
commit: "09d3f103832bb7e26949a8f2df60c25851886bdc"
2.9.6:
commit: "9fb352cc6fe2bc41c56304e5ba941035fc1ac82d"
2.9.7:
commit: "f4a563f080f7dba336034feb1c0c42057f8d8d8c"
2.9.8:
commit: "2da9f154be240b8648d68a7eb2a3291738cfc93c"
2.9.9:
commit: "f7861c44b4a44b0cdd34e9f3f62530b8bf2837e3"
2.9.10:
commit: "22172d8b57df2ad1282f8d835183be45843fdd6a"

51
dev/releases/3.0.yml Normal file
View File

@ -0,0 +1,51 @@
minor_release_name: "Yeonhee"
3.0.0:
commit: "f31c95228eb2dc01e661be928ffd881c063377da"
notes:
- "[Architecture] Switched to SQLite for main database"
- "[Architecture] Switched to SQLite for artwork cache"
- "[Feature] Added scrobble deletion from web interface"
3.0.1:
commit: "700b81217cb585df631d6f069243c56074cd1b71"
notes:
- "[Bugfix] Fixed upgrading imported scrobbles"
3.0.2:
commit: "4a8221f7a08f679b21c1fb619f03e5f922a1dc2b"
notes:
- "[Logging] Cleaned up output for waitress warnings"
- "[Bugfix] Fixed exception in native API"
3.0.3:
commit: "1d9247fc724d7410b6e50d2cbfaa8f375d5e70af"
notes:
- "[Documentation] Added descriptions for native API endpoints"
- "[Code Health] Made arguments for native API scrobbling explicit"
- "[Bugfix] Fixed faulty entity type recognition for artists including the string 'artists'"
- "[Bugfix] Fixed OS return codes"
3.0.4:
commit: "206ebd58ea204e0008f2c9bf72d76dd9918fec53"
notes:
- "[Feature] Enabled dual stack for web server"
- "[Feature] Added better feedback to native API endpoints"
- "[Bugfix] Fixed native API receiving superfluous keywords"
- "[Bugfix] Fixed crash when importing scrobbles with artists with similar names"
3.0.5:
commit: "fe21894c5ecf3a53c9c5c00453abfc7f41c6a83e"
notes:
- "[Feature] Added notification system for web interface"
- "[Bugfix] Fixed crash when encountering error in Lastfm import"
3.0.6:
commit: "b3d4cb7a153845d1f5a5eef67a6508754e338f2f"
notes:
- "[Performance] Implemented search in database"
- "[Bugfix] Better parsing of featuring artists"
- "[Bugfix] Fixed buffered output in Docker"
- "[Bugfix] Fixed importing a Spotify file without path"
- "[Bugfix] No longer releasing database lock during scrobble creation"
- "[Distribution] Experimental arm64 image"
3.0.7:
commit: "62abc319303a6cb6463f7c27b6ef09b76fc67f86"
notes:
- "[Bugix] Improved signal handling"
- "[Bugix] Fixed constant re-caching of all-time stats, significantly increasing page load speed"
- "[Logging] Disabled cache information when cache is not used"
- "[Distribution] Experimental arm/v7 image"

46
dev/releases/3.1.yml Normal file
View File

@ -0,0 +1,46 @@
minor_release_name: "Soyeon"
3.1.0:
commit: "bfa553bed05d7dba33f611a44485d6cf460ba308"
notes:
- "[Architecture] Cleaned up legacy process control"
- "[Architecture] Added proper exception framework to native API"
- "[Feature] Implemented track title and artist name editing from web interface"
- "[Feature] Implemented track and artist merging from web interface"
- "[Feature] Implemented scrobble reparsing from web interface"
- "[Performance] Adjusted cache sizes"
- "[Logging] Added cache memory use information"
- "[Technical] Bumped Python Version and various dependencies"
3.1.1:
commit: "20aae955b2263be07c56bafe4794f622117116ef"
notes:
- "[Bugfix] Fixed inclusion of custom css files"
- "[Bugfix] Fixed list values in configuration"
3.1.2:
commit: "a0739306013cd9661f028fb5b2620cfa2d298aa4"
notes:
- "[Feature] Added remix artist parsing"
- "[Feature] Added API debug mode"
- "[Bugfix] Fixed leftover whitespaces when parsing titles"
- "[Bugfix] Fixed handling of fallthrough values in config file"
3.1.3:
commit: "f3a04c79b1c37597cdf3cafcd95e3c923cd6a53f"
notes:
- "[Bugfix] Fixed infinite recursion with capitalized featuring delimiters"
- "[Bugfix] Fixed favicon display"
3.1.4:
commit: "ef06f2262205c903e7c3060e2d2d52397f8ffc9d"
notes:
- "[Feature] Expanded information saved from Listenbrainz API"
- "[Feature] Added import for Listenbrainz exports"
- "[Bugfix] Sanitized artists and tracks with html-like structure"
3.1.5:
commit: "4330b0294bc0a01cdb841e2e3db370108da901db"
notes:
- "[Feature] Made image upload part of regular API"
- "[Bugfix] Additional entity name sanitization"
- "[Bugfix] Fixed image display on Safari"
- "[Bugfix] Fixed entity editing on Firefox"
- "[Bugfix] Made compatibile with SQLAlchemy 2.0"
upcoming:
notes:
- "[Bugfix] Fixed configuration of time format"

2
dev/run_docker.sh Normal file
View File

@ -0,0 +1,2 @@
docker build -t maloja . -f Containerfile
docker run --rm -p 42010:42010 -v $PWD/testdata:/mlj -e MALOJA_DATA_DIRECTORY=/mlj maloja

2
dev/run_podman.sh Normal file
View File

@ -0,0 +1,2 @@
podman build -t maloja . -f Containerfile
podman run --rm -p 42010:42010 -v $PWD/testdata:/mlj -e MALOJA_DATA_DIRECTORY=/mlj maloja

View File

@ -0,0 +1,36 @@
# Contributor: Johannes Krattenmacher <maloja@dev.krateng.ch>
# Maintainer: Johannes Krattenmacher <maloja@dev.krateng.ch>
pkgname={{ tool.flit.module.name }}
pkgver={{ project.version }}
pkgrel=0
pkgdesc="{{ project.description }}"
url="{{ project.urls.homepage }}"
arch="noarch"
license="GPL-3.0"
depends="{{ tool.osreqs.alpine.run | join(' ') }}"
pkgusers=$pkgname
pkggroups=$pkgname
depends_dev="{{ tool.osreqs.alpine.build | join(' ') }}"
makedepends="$depends_dev"
source="
$pkgname-$pkgver.tar.gz::{{ project.urls.repository }}/archive/refs/tags/v$pkgver.tar.gz
"
builddir="$srcdir"/$pkgname-$pkgver
build() {
cd $builddir
python3 -m build .
pip3 install dist/*.tar.gz
}
package() {
mkdir -p /etc/$pkgname || return 1
mkdir -p /var/lib/$pkgname || return 1
mkdir -p /var/cache/$pkgname || return 1
mkdir -p /var/logs/$pkgname || return 1
}
# TODO
sha512sums="a674eaaaa248fc2b315514d79f9a7a0bac6aa1582fe29554d9176e8b551e8aa3aa75abeebdd7713e9e98cc987e7bd57dc7a5e9a2fb85af98b9c18cb54de47bf7 $pkgname-${pkgver}.tar.gz"

View File

@ -0,0 +1,40 @@
FROM alpine:3.15
# Python image includes two Python versions, so use base Alpine
# Based on the work of Jonathan Boeckel <jonathanboeckel1996@gmail.com>
WORKDIR /usr/src/app
# Install run dependencies first
RUN apk add --no-cache {{ tool.osreqs.alpine.run | join(' ') }}
# system pip could be removed after build, but apk then decides to also remove all its
# python dependencies, even if they are explicitly installed as python packages
# whut
RUN \
apk add py3-pip && \
pip install wheel
COPY ./requirements.txt ./requirements.txt
RUN \
apk add --no-cache --virtual .build-deps {{ tool.osreqs.alpine.build | join(' ') }} && \
pip install --no-cache-dir -r requirements.txt && \
apk del .build-deps
# no chance for caching below here
COPY . .
RUN pip install /usr/src/app
# Docker-specific configuration
# defaulting to IPv4 is no longer necessary (default host is dual stack)
ENV MALOJA_SKIP_SETUP=yes
ENV PYTHONUNBUFFERED=1
EXPOSE 42010
# use exec form for better signal handling https://docs.docker.com/engine/reference/builder/#entrypoint
ENTRYPOINT ["maloja", "run"]

View File

@ -0,0 +1,4 @@
{% include 'install/install_dependencies_alpine.sh.jinja' %}
apk add py3-pip
pip install wheel
pip install malojaserver

View File

@ -0,0 +1,4 @@
{% include 'install/install_dependencies_debian.sh.jinja' %}
apt install python3-pip
pip install wheel
pip install malojaserver

View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
apk update
apk add \
{{ (tool.osreqs.alpine.build + tool.osreqs.alpine.run + tool.osreqs.alpine.opt) | join(' \\\n\t') }}

View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
apt update
apt install \
{{ (tool.osreqs.debian.build + tool.osreqs.debian.run + tool.osreqs.debian.opt) | join(' \\\n\t') }}

View File

@ -0,0 +1,3 @@
{% for dep in project.dependencies -%}
{{ dep }}
{% endfor %}

View File

@ -0,0 +1,3 @@
{% for dep in project['optional-dependencies'].full -%}
{{ dep }}
{% endfor %}

View File

@ -0,0 +1,907 @@
{
"info": {
"_postman_id": "71b19d5e-78a1-4a4a-98b5-12cbf2f13fe6",
"name": "Maloja",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Scrobbling",
"item": [
{
"name": "Scrobble Native",
"item": [
{
"name": "Query String",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{url}}/apis/mlj_1/newscrobble?key={{api_key}}&artist={{data.artist1}}&title={{data.title1}}&artist={{data.artist2}}",
"host": [
"{{url}}"
],
"path": [
"apis",
"mlj_1",
"newscrobble"
],
"query": [
{
"key": "key",
"value": "{{api_key}}"
},
{
"key": "artist",
"value": "{{data.artist1}}"
},
{
"key": "title",
"value": "{{data.title1}}"
},
{
"key": "artist",
"value": "{{data.artist2}}"
}
]
}
},
"response": []
},
{
"name": "Query String Redirect",
"request": {
"method": "POST",
"header": [],
"url": {
"raw": "{{url}}/api/newscrobble?key={{api_key}}&artist={{data.artist1}}&title={{data.title1}}&artist={{data.artist2}}",
"host": [
"{{url}}"
],
"path": [
"api",
"newscrobble"
],
"query": [
{
"key": "key",
"value": "{{api_key}}"
},
{
"key": "artist",
"value": "{{data.artist1}}"
},
{
"key": "title",
"value": "{{data.title1}}"
},
{
"key": "artist",
"value": "{{data.artist2}}"
}
]
}
},
"response": []
},
{
"name": "Formdata",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "key",
"value": "{{api_key}}",
"type": "text"
},
{
"key": "artist",
"value": "{{data.artist1}}",
"type": "text"
},
{
"key": "title",
"value": "{{data.title1}}",
"type": "text"
},
{
"key": "artist",
"value": "{{data.artist2}}",
"type": "text"
}
]
},
"url": {
"raw": "{{url}}/apis/mlj_1/newscrobble",
"host": [
"{{url}}"
],
"path": [
"apis",
"mlj_1",
"newscrobble"
]
}
},
"response": []
},
{
"name": "Formdata Redirect",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "key",
"value": "{{api_key}}",
"type": "text"
},
{
"key": "artist",
"value": "{{data.artist1}}",
"type": "text"
},
{
"key": "title",
"value": "{{data.title1}}",
"type": "text"
},
{
"key": "artist",
"value": "{{data.artist2}}",
"type": "text"
}
]
},
"url": {
"raw": "{{url}}/api/newscrobble",
"host": [
"{{url}}"
],
"path": [
"api",
"newscrobble"
]
}
},
"response": []
},
{
"name": "JSON",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"key\": \"{{api_key}}\",\n \"artist\": \"{{data.artist1}}\",\n \"title\": \"{{data.title1}}\"\n}"
},
"url": {
"raw": "{{url}}/apis/mlj_1/newscrobble",
"host": [
"{{url}}"
],
"path": [
"apis",
"mlj_1",
"newscrobble"
]
}
},
"response": []
},
{
"name": "JSON Multiartist",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"key\": \"{{api_key}}\",\n \"artists\": [\"{{data.artist1}}\",\"{{data.artist2}}\"],\n \"title\": \"{{data.title1}}\"\n}"
},
"url": {
"raw": "{{url}}/apis/mlj_1/newscrobble",
"host": [
"{{url}}"
],
"path": [
"apis",
"mlj_1",
"newscrobble"
]
}
},
"response": []
},
{
"name": "JSON Redirect",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"key\": \"{{api_key}}\",\n \"artist\": \"{{data.artist1}}\",\n \"title\": \"{{data.title1}}\"\n}"
},
"url": {
"raw": "{{url}}/api/newscrobble",
"host": [
"{{url}}"
],
"path": [
"api",
"newscrobble"
]
}
},
"response": []
}
]
},
{
"name": "Scrobble Listenbrainz",
"item": [
{
"name": "JSON",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
},
{
"key": "Authorization",
"value": "token {{api_key}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"listen_type\":\"single\",\n\t\"payload\":[\n\t\t{\n\t\t\t\"track_metadata\":{\n\t\t\t\t\"artist_name\":\"{{data.artist1}}\",\n\t\t\t\t\"track_name\":\"{{data.title1}}\"\n\t\t\t}\n\t\t}\n\t]\n}"
},
"url": {
"raw": "{{url}}/apis/listenbrainz/1/submit-listens",
"host": [
"{{url}}"
],
"path": [
"apis",
"listenbrainz",
"1",
"submit-listens"
]
}
},
"response": []
},
{
"name": "JSON Redirect",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
},
{
"key": "Authorization",
"type": "text",
"value": "token {{api_key}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"listen_type\":\"single\",\n\t\"payload\":[\n\t\t{\n\t\t\t\"track_metadata\":{\n\t\t\t\t\"artist_name\":\"{{data.artist1}}\",\n\t\t\t\t\"track_name\":\"{{data.title1}}\"\n\t\t\t}\n\t\t}\n\t]\n}"
},
"url": {
"raw": "{{url}}/api/s/listenbrainz/1/submit-listens",
"host": [
"{{url}}"
],
"path": [
"api",
"s",
"listenbrainz",
"1",
"submit-listens"
]
}
},
"response": []
}
]
},
{
"name": "Scrobble Audioscrobbler",
"item": [
{
"name": "JSON",
"event": [
{
"listen": "test",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
},
{
"listen": "prerequest",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"method\":\"track.scrobble\",\n\t\"artist\":\"{{data.artist1}}\",\n\t\"track\":\"{{data.title1}}\",\n\t\"sk\":\"{{session_key}}\"\n}"
},
"url": {
"raw": "{{url}}/apis/audioscrobbler/2.0/",
"host": [
"{{url}}"
],
"path": [
"apis",
"audioscrobbler",
"2.0",
""
]
}
},
"response": []
},
{
"name": "JSON Redirect",
"event": [
{
"listen": "test",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"method\":\"track.scrobble\",\n\t\"artist\":\"{{data.artist1}}\",\n\t\"track\":\"{{data.title1}}\",\n\t\"sk\":\"{{session_key}}\"\n}"
},
"url": {
"raw": "{{url}}/api/s/audioscrobbler/2.0/",
"host": [
"{{url}}"
],
"path": [
"api",
"s",
"audioscrobbler",
"2.0",
""
]
}
},
"response": []
},
{
"name": "Authorize",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var data = JSON.parse(responseBody);",
"pm.environment.set(\"session_key\", data.session.key);",
"tests[\"gotkey\"] = true;"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"method\":\"auth.getMobileSession\",\n\t\"authToken\":\"abc\",\n\t\"username\":\"someguy\",\n\t\"password\":\"{{api_key}}\"\n}"
},
"url": {
"raw": "{{url}}/apis/audioscrobbler/2.0/",
"host": [
"{{url}}"
],
"path": [
"apis",
"audioscrobbler",
"2.0",
""
]
}
},
"response": []
}
]
},
{
"name": "Scrobble Audioscrobbler Legacy",
"item": [
{
"name": "Authorize",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var data = responseBody.split(\"\\n\");",
"",
"pm.environment.set(\"session_key\", data[1]);",
"pm.environment.set(\"scrobble_url\", data[3]);"
],
"type": "text/javascript"
}
},
{
"listen": "prerequest",
"script": {
"exec": [
"apikey = pm.variables.get(\"api_key\");",
"ts = pm.variables.get(\"data.timestamp1\");",
"",
"token = CryptoJS.MD5(CryptoJS.MD5(apikey) + ts).toString()",
"pm.environment.set(\"legacy_token\", token);"
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{url}}/apis/audioscrobbler_legacy/?hs=true&t={{data.timestamp1}}&a={{legacy_token}}",
"host": [
"{{url}}"
],
"path": [
"apis",
"audioscrobbler_legacy",
""
],
"query": [
{
"key": "hs",
"value": "true"
},
{
"key": "t",
"value": "{{data.timestamp1}}"
},
{
"key": "a",
"value": "{{legacy_token}}"
}
]
}
},
"response": []
},
{
"name": "Scrobble",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{scrobble_url}}?s={{session_key}}&a[0]={{data.artist1}}&t[0]={{data.title1}}&a[1]={{data.artist2}}&t[1]={{data.title2}}&i[0]={{data.timestamp1}}&i[1]={{data.timestamp2}}",
"host": [
"{{scrobble_url}}"
],
"query": [
{
"key": "s",
"value": "{{session_key}}"
},
{
"key": "a[0]",
"value": "{{data.artist1}}"
},
{
"key": "t[0]",
"value": "{{data.title1}}"
},
{
"key": "a[1]",
"value": "{{data.artist2}}"
},
{
"key": "t[1]",
"value": "{{data.title2}}"
},
{
"key": "i[0]",
"value": "{{data.timestamp1}}"
},
{
"key": "i[1]",
"value": "{{data.timestamp2}}"
}
]
}
},
"response": []
}
]
}
]
},
{
"name": "Metadata",
"item": [
{
"name": "Spotify",
"item": [
{
"name": "Authorize",
"event": [
{
"listen": "prerequest",
"script": {
"exec": [
"apiid = pm.collectionVariables.get(\"external.spotify.api_id\");",
"apisecret = pm.collectionVariables.get(\"external.spotify.api_secret\");",
"",
"authb64 = new Buffer(apiid + ':' + apisecret).toString('base64');",
"pm.environment.set(\"authb64\", authb64);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"exec": [
"var data = JSON.parse(responseBody);",
"pm.collectionVariables.set(\"external.spotify.access_token\", data.access_token);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Basic {{authb64}}",
"type": "text"
}
],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "grant_type",
"value": "client_credentials",
"type": "text"
}
]
},
"url": {
"raw": "https://accounts.spotify.com/api/token",
"protocol": "https",
"host": [
"accounts",
"spotify",
"com"
],
"path": [
"api",
"token"
]
}
},
"response": []
},
{
"name": "Track Info Old",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://api.spotify.com/v1/search?type=track&access_token={{external.spotify.access_token}}&q=artist:{{data.artist3}} track:{{data.title3}}",
"protocol": "https",
"host": [
"api",
"spotify",
"com"
],
"path": [
"v1",
"search"
],
"query": [
{
"key": "type",
"value": "track"
},
{
"key": "access_token",
"value": "{{external.spotify.access_token}}"
},
{
"key": "q",
"value": "artist:{{data.artist3}} track:{{data.title3}}"
}
]
}
},
"response": []
},
{
"name": "Track Info New",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{external.spotify.access_token}}",
"type": "text"
}
],
"url": {
"raw": "https://api.spotify.com/v1/search?type=track&q=artist:{{data.artist3}}%20track:{{data.title3}}",
"protocol": "https",
"host": [
"api",
"spotify",
"com"
],
"path": [
"v1",
"search"
],
"query": [
{
"key": "type",
"value": "track"
},
{
"key": "q",
"value": "artist:{{data.artist3}}%20track:{{data.title3}}"
}
]
}
},
"response": []
}
]
}
]
},
{
"name": "Scrobbleforward",
"item": [
{
"name": "Last.fm",
"item": [
{
"name": "Authorize",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "urlencoded",
"urlencoded": []
},
"url": {
"raw": "https://ws.audioscrobbler.com/2.0/?method=auth.getMobileSession&username={{external.lastfm.username}}&password={{external.lastfm.password}}&api_key={{external.lastfm.api_key}}&api_sig=TODO",
"protocol": "https",
"host": [
"ws",
"audioscrobbler",
"com"
],
"path": [
"2.0",
""
],
"query": [
{
"key": "method",
"value": "auth.getMobileSession"
},
{
"key": "username",
"value": "{{external.lastfm.username}}"
},
{
"key": "password",
"value": "{{external.lastfm.password}}"
},
{
"key": "api_key",
"value": "{{external.lastfm.api_key}}"
},
{
"key": "api_sig",
"value": "TODO"
}
]
}
},
"response": []
}
]
}
]
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
"var current_timestamp = Math.floor(Date.now() / 1000);",
"pm.collectionVariables.set(\"data.timestamp1\", current_timestamp);",
"pm.collectionVariables.set(\"data.timestamp2\", current_timestamp - 200);"
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "url",
"value": "http://localhost:42010"
},
{
"key": "api_key",
"value": "localdevtestkey"
},
{
"key": "session_key",
"value": ""
},
{
"key": "data.artist1",
"value": "EXID ft. Jeremy Soule"
},
{
"key": "data.artist2",
"value": "BLACKPINK ft. Tzuyu"
},
{
"key": "data.artist3",
"value": "TWICE"
},
{
"key": "data.title1",
"value": "Why is the Rum gone?"
},
{
"key": "data.title2",
"value": "POP/STARS"
},
{
"key": "data.title3",
"value": "One in a Million"
},
{
"key": "data.timestamp1",
"value": ""
},
{
"key": "data.timestamp2",
"value": ""
},
{
"key": "external.spotify.api_id",
"value": ""
},
{
"key": "external.spotify.api_secret",
"value": ""
},
{
"key": "external.spotify.access_token",
"value": ""
},
{
"key": "external.lastfm.username",
"value": ""
},
{
"key": "external.lastfm.password",
"value": ""
},
{
"key": "external.lastfm.api_key",
"value": ""
},
{
"key": "external.lastfm.secret",
"value": ""
}
]
}

43
dev/testing/stresstest.py Normal file
View File

@ -0,0 +1,43 @@
import threading
import subprocess
import time
import requests
import os
ACTIVE = True
build_cmd = ["docker","build","-t","maloja",".","-f","Containerfile"]
subprocess.run(build_cmd)
common_prc = (
["docker","run","--rm","-v",f"{os.path.abspath('./testdata')}:/mlj","-e","MALOJA_DATA_DIRECTORY=/mlj"],
["maloja"]
)
servers = [
{'port': 42010},
{'port': 42011, 'extraargs':["--memory=1g"]},
{'port': 42012, 'extraargs':["--memory=500m"]}
]
for s in servers:
cmd = common_prc[0] + ["-p",f"{s['port']}:42010"] + s.get('extraargs',[]) + common_prc[1]
print(cmd)
t = threading.Thread(target=subprocess.run,args=(cmd,))
s['thread'] = t
t.daemon = True
t.start()
time.sleep(5)
time.sleep(5)
while ACTIVE:
time.sleep(1)
try:
for s in servers:
requests.get(f"http://localhost:{s['port']}")
except KeyboardInterrupt:
ACTIVE = False
except Exception:
pass
for s in servers:
s['thread'].join()

33
dev/update_dist_files.py Normal file
View File

@ -0,0 +1,33 @@
import toml
import os
import jinja2
env = jinja2.Environment(
loader=jinja2.FileSystemLoader('dev/templates'),
autoescape=jinja2.select_autoescape(['html', 'xml']),
keep_trailing_newline=True
)
with open("pyproject.toml") as filed:
data = toml.load(filed)
templatedir = "./dev/templates"
for root,dirs,files in os.walk(templatedir):
reldirpath = os.path.relpath(root,start=templatedir)
for f in files:
relfilepath = os.path.join(reldirpath,f)
if not f.endswith('.jinja'): continue
srcfile = os.path.join(root,f)
trgfile = os.path.join(reldirpath,f.replace(".jinja",""))
template = env.get_template(relfilepath)
result = template.render(**data)
with open(trgfile,"w") as filed:
filed.write(result)

9
dev/update_scrobbler.sh Normal file
View File

@ -0,0 +1,9 @@
ICON_DIR=./maloja/web/static/png;
SCROBBLER_DIR=./auxiliary/chromium_scrobbler;
convert $ICON_DIR/favicon_large.png -resize 256 $SCROBBLER_DIR/maloja-scrobbler/icon256.png
convert $ICON_DIR/favicon_large.png -resize 128 $SCROBBLER_DIR/maloja-scrobbler/icon128.png
convert $ICON_DIR/favicon_large.png -resize 48 $SCROBBLER_DIR/maloja-scrobbler/icon48.png
convert $ICON_DIR/favicon_large.png -background none -resize 280 -gravity center -extent 440x280 -background "#232327" -flatten $SCROBBLER_DIR/tile.png
rm $SCROBBLER_DIR/maloja-scrobbler.zip
zip $SCROBBLER_DIR/maloja-scrobbler.zip $SCROBBLER_DIR/maloja-scrobbler/* $SCROBBLER_DIR/maloja-scrobbler/*/*

53
dev/write_tags.py Normal file
View File

@ -0,0 +1,53 @@
import os
import subprocess as sp
import yaml
FOLDER = "dev/releases"
releases = {}
for f in os.listdir(FOLDER):
if f == "branch.yml": continue
#maj,min = (int(i) for i in f.split('.')[:2])
with open(os.path.join(FOLDER,f)) as fd:
data = yaml.safe_load(fd)
name = data.pop('minor_release_name')
for tag in data:
tagtup = tuple(int(i) for i in tag.split('.'))
releases[tagtup] = data[tag]
# this is a bit dirty, works on our data
if len(tagtup)<3 or tagtup[2] == 0: releases[tagtup]['name'] = name
for version in releases:
info = releases[version]
version = '.'.join(str(v) for v in version)
msg = [
f"Version {version}" + (f" '{info.get('name')}'" if info.get('name') else ''),
*([""] if info.get('notes') else []),
*[f"* {n}" for n in info.get('notes',[])]
]
cmd = [
'git','tag','--force',
'-a',f'v{version}',
'-m',
'\n'.join(msg),
info['commit']
]
try:
prev_tag = sp.check_output(["git","show",f'v{maj}.{min}.{hot}']).decode()
prev_tag_commit = prev_tag.split('\n')[6].split(" ")[1]
except Exception:
pass
else:
assert prev_tag_commit == info['commit']
print(cmd)
sp.run(cmd)

20
example-compose.yml Normal file
View File

@ -0,0 +1,20 @@
services:
maloja:
# from dockerhub
image: "krateng/maloja:latest"
# or built locally
#build:
# context: .
# dockerfile: ./Containerfile
ports:
- "42010:42010"
# different directories for configuration, state and logs
volumes:
- "$PWD/config:/etc/maloja"
- "$PWD/data:/var/lib/maloja"
- "$PWD/logs:/var/log/maloja"
#you can also have everything together instead:
#volumes:
#- "$PWD/data:/data"
#environment:
#- "MALOJA_DATA_DIRECTORY=/data"

View File

@ -1,125 +0,0 @@
import urllib.parse, urllib.request
import json
import base64
from doreah.settings import get_settings
from doreah.logging import log
apis_artists = [
{
"name":"LastFM + Fanart.tv",
"check":get_settings("LASTFM_API_KEY") not in [None,"ASK"] and get_settings("FANARTTV_API_KEY") not in [None,"ASK"],
"steps":[
("get","http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist={artiststring}&api_key=" + get_settings("LASTFM_API_KEY") + "&format=json"),
("parse",["artist","mbid"]),
("get","http://webservice.fanart.tv/v3/music/{var}?api_key=" + get_settings("FANARTTV_API_KEY")),
("parse",["artistthumb",0,"url"])
]
},
{
"name":"Spotify",
"check":get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"],
"steps":[
("post","https://accounts.spotify.com/api/token",{"Authorization":"Basic " + base64.b64encode(bytes(get_settings("SPOTIFY_API_ID") + ":" + get_settings("SPOTIFY_API_SECRET"),encoding="utf-8")).decode("utf-8")},{"grant_type":"client_credentials"}),
("parse",["access_token"]),
("get","https://api.spotify.com/v1/search?q={artiststring}&type=artist&access_token={var}"),
("parse",["artists","items",0,"images",0,"url"])
]
}
]
apis_tracks = [
{
"name":"LastFM",
"check":get_settings("LASTFM_API_KEY") not in [None,"ASK"],
"steps":[
("get","https://ws.audioscrobbler.com/2.0/?method=track.getinfo&track={titlestring}&artist={artiststring}&api_key=" + get_settings("LASTFM_API_KEY") + "&format=json"),
("parse",["track","album","image",3,"#text"])
]
},
{
"name":"Spotify",
"check":get_settings("SPOTIFY_API_ID") not in [None,"ASK"] and get_settings("SPOTIFY_API_SECRET") not in [None,"ASK"],
"steps":[
("post","https://accounts.spotify.com/api/token",{"Authorization":"Basic " + base64.b64encode(bytes(get_settings("SPOTIFY_API_ID") + ":" + get_settings("SPOTIFY_API_SECRET"),encoding="utf-8")).decode("utf-8")},{"grant_type":"client_credentials"}),
("parse",["access_token"]),
("get","https://api.spotify.com/v1/search?q={artiststring}%20{titlestring}&type=track&access_token={var}"),
("parse",["tracks","items",0,"album","images",0,"url"])
]
}
]
def api_request_artist(artist):
for api in apis_artists:
if api["check"]:
log("API: " + api["name"] + "; Image request: " + artist,module="external")
try:
artiststring = urllib.parse.quote(artist)
var = artiststring
for step in api["steps"]:
if step[0] == "get":
response = urllib.request.urlopen(step[1].format(artiststring=artiststring,var=var))
var = json.loads(response.read())
elif step[0] == "post":
keys = {
"url":step[1].format(artiststring=artiststring,var=var),
"method":"POST",
"headers":step[2],
"data":bytes(urllib.parse.urlencode(step[3]),encoding="utf-8")
}
req = urllib.request.Request(**keys)
response = urllib.request.urlopen(req)
var = json.loads(response.read())
elif step[0] == "parse":
for node in step[1]:
var = var[node]
assert isinstance(var,str) and var != ""
except:
continue
return var
else:
pass
return None
def api_request_track(track):
artists, title = track
for api in apis_tracks:
if api["check"]:
log("API: " + api["name"] + "; Image request: " + "/".join(artists) + " - " + title,module="external")
try:
artiststring = urllib.parse.quote(", ".join(artists))
titlestring = urllib.parse.quote(title)
var = artiststring + titlestring
for step in api["steps"]:
if step[0] == "get":
response = urllib.request.urlopen(step[1].format(artiststring=artiststring,titlestring=titlestring,var=var))
var = json.loads(response.read())
elif step[0] == "post":
keys = {
"url":step[1].format(artiststring=artiststring,titlestring=titlestring,var=var),
"method":"POST",
"headers":step[2],
"data":bytes(urllib.parse.urlencode(step[3]),encoding="utf-8")
}
req = urllib.request.Request(**keys)
response = urllib.request.urlopen(req)
var = json.loads(response.read())
elif step[0] == "parse":
for node in step[1]:
var = var[node]
assert isinstance(var,str) and var != ""
except:
if len(artists) != 1:
# try the same track with every single artist
for a in artists:
result = api_request_track(([a],title))
if result is not None:
return result
continue
return var
else:
pass
return None

View File

@ -1,46 +0,0 @@
import os
import re
from cleanup import CleanerAgent
from doreah.logging import log
import difflib
wendigo = CleanerAgent()
exp = r"([0-9]*)(\t+)([^\t]+?)(\t+)([^\t]+)(\t*)([^\t]*)\n"
for fn in os.listdir("scrobbles/"):
if fn.endswith(".tsv"):
f = open("scrobbles/" + fn)
fnew = open("scrobbles/" + fn + "_new","w")
for l in f:
a,t = re.sub(exp,r"\3",l), re.sub(exp,r"\5",l)
r1,r2,r3 = re.sub(exp,r"\1\2",l),re.sub(exp,r"\4",l),re.sub(exp,r"\6\7",l)
a = a.replace("",";")
(al,t) = wendigo.fullclean(a,t)
a = "".join(al)
fnew.write(r1 + a + r2 + t + r3 + "\n")
#print("Artists: " + a)
#print("Title: " + t)
#print("1: " + r1)
#print("2: " + r2)
#print("3: " + r3)
f.close()
fnew.close()
#os.system("diff " + "scrobbles/" + fn + "_new" + " " + "scrobbles/" + fn)
with open("scrobbles/" + fn + "_new","r") as newfile:
with open("scrobbles/" + fn,"r") as oldfile:
diff = difflib.unified_diff(oldfile.read().split("\n"),newfile.read().split("\n"),lineterm="")
diff = list(diff)[2:]
log("Diff for scrobbles/" + fn + "".join("\n\t" + d for d in diff),module="fixer")
os.rename("scrobbles/" + fn + "_new","scrobbles/" + fn)
checkfile = open("scrobbles/" + fn + ".rulestate","w")
checkfile.write(wendigo.checksums)
checkfile.close()

View File

@ -1,132 +0,0 @@
import urllib
from bottle import FormsDict
import datetime
from urihandler import compose_querystring
# returns the proper column(s) for an artist or track
def entity_column(element,counting=[],image=None):
html = ""
if image is not None:
html += """<td class='icon'><div style="background-image:url('""" + image + """')"></div></td>"""
if "artists" in element:
# track
html += "<td class='artists'>" + html_links(element["artists"]) + "</td>"
html += "<td class='title'>" + html_link(element) + "</td>"
else:
# artist
html += "<td class='artist'>" + html_link(element)
if (counting != []):
html += " <span class='extra'>incl. " + html_links(counting) + "</span>"
html += "</td>"
return html
def uri_query(entity):
if "artists" in entity:
#track
return "title=" + urllib.parse.quote(entity["title"]) \
+ "&" + "&".join(["artist=" + urllib.parse.quote(a) for a in entity["artists"]])
else:
#artist
return "artist=" + urllib.parse.quote(entity)
# returns the address of the track / artist page
def link_address(entity):
if "artists" in entity:
#track
return "/track?" + uri_query(entity)
else:
#artist
return "/artist?" + uri_query(entity)
#returns linked name
def html_link(entity):
if "artists" in entity:
#track
name = entity["title"]
else:
#artist
name = entity
return "<a href='" + link_address(entity) + "'>" + name + "</a>"
def html_links(entities):
return ", ".join([html_link(e) for e in entities])
# DEPRECATED
def artistLink(name):
return html_link(name)
#return "<a href='/artist?artist=" + urllib.parse.quote(name) + "'>" + name + "</a>"
# DEPRECATED
def artistLinks(artists):
return ", ".join([artistLink(a) for a in artists])
#def trackLink(artists,title):
# DEPRECATED
def trackLink(track):
return html_link(track)
#artists,title = track["artists"],track["title"]
#return "<a href='/track?title=" + urllib.parse.quote(title) + "&" + "&".join(["artist=" + urllib.parse.quote(a) for a in artists]) + "'>" + title + "</a>"
#def scrobblesTrackLink(artists,title,timekeys,amount=None,pixels=None):
def scrobblesTrackLink(track,timekeys,amount=None,percent=None):
artists,title = track["artists"],track["title"]
inner = str(amount) if amount is not None else "<div style='width:" + str(percent) + "%;'></div>"
return "<a href='/scrobbles?" + uri_query(track) + "&" + compose_querystring(timekeys) + "'>" + inner + "</a>"
def scrobblesArtistLink(artist,timekeys,amount=None,percent=None,associated=False):
inner = str(amount) if amount is not None else "<div style='width:" + str(percent) + "%;'></div>"
askey = "&associated" if associated else ""
return "<a href='/scrobbles?" + uri_query(artist) + "&" + compose_querystring(timekeys) + askey + "'>" + inner + "</a>"
def scrobblesLink(timekeys,amount=None,percent=None,artist=None,track=None,associated=False):
if track is not None: return scrobblesTrackLink(track,timekeys,amount,percent)
if artist is not None: return scrobblesArtistLink(artist,timekeys,amount,percent,associated)
inner = str(amount) if amount is not None else "<div style='width:" + str(percent) + "%;'></div>"
return "<a href='/scrobbles?" + compose_querystring(timekeys) + "'>" + inner + "</a>"
def rankTrackLink(track,timekeys,rank=None,percent=None,medal=None):
cl = ""
if medal == 1: cl = "class='gold'"
if medal == 2: cl = "class='silver'"
if medal == 3: cl = "class='bronze'"
inner = str(rank) if rank is not None else "<div " + cl + " style='width:" + str(percent) + "%;'></div>"
return "<a href='/charts_tracks?" + compose_querystring(timekeys) + "'>" + inner + "</a>"
def rankArtistLink(artist,timekeys,rank=None,percent=None,medal=None):
cl = ""
if medal == 1: cl = "class='gold'"
if medal == 2: cl = "class='silver'"
if medal == 3: cl = "class='bronze'"
inner = str(rank) if rank is not None else "<div " + cl + " style='width:" + str(percent) + "%;'></div>"
return "<a href='/charts_artists?" + compose_querystring(timekeys) + "'>" + inner + "</a>"
def rankLink(timekeys,rank=None,percent=None,artist=None,track=None,medal=None):
if track is not None: return rankTrackLink(track,timekeys,rank,percent,medal)
if artist is not None: return rankArtistLink(artist,timekeys,rank,percent,medal)
# limit a multidict to only the specified keys
# would be a simple constructor expression, but multidicts apparently don't let me do that
def pickKeys(d,*keys):
if isinstance(d,dict):
return {k:d.get(k) for k in d if k in keys}
else:
# create a normal dictionary of lists
newd = {k:d.getall(k) for k in d if k in keys}
# one by one add the list entries to the formsdict
finald = FormsDict()
for k in newd:
for v in newd.get(k):
finald.append(k,v)
return finald

View File

@ -1,648 +0,0 @@
from htmlgenerators import *
import database
from utilities import getArtistImage, getTrackImage
from malojatime import *
from urihandler import compose_querystring, internal_to_uri, uri_to_internal
import urllib
import datetime
import math
#def getpictures(ls,result,tracks=False):
# from utilities import getArtistsInfo, getTracksInfo
# if tracks:
# for element in getTracksInfo(ls):
# result.append(element.get("image"))
# else:
# for element in getArtistsInfo(ls):
# result.append(element.get("image"))
# artist=None,track=None,since=None,to=None,within=None,associated=False,max_=None,pictures=False
def module_scrobblelist(max_=None,pictures=False,shortTimeDesc=False,earlystop=False,**kwargs):
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
# if earlystop, we don't care about the actual amount and only request as many from the db
# without, we request everything and filter on site
maxkey = {"max_":max_} if earlystop else {}
scrobbles = database.get_scrobbles(**kwargs_time,**kwargs_filter,**maxkey)
if pictures:
scrobbleswithpictures = scrobbles if max_ is None else scrobbles[:max_]
#scrobbleimages = [e.get("image") for e in getTracksInfo(scrobbleswithpictures)] #will still work with scrobble objects as they are a technically a subset of track objects
#scrobbleimages = ["/image?title=" + urllib.parse.quote(t["title"]) + "&" + "&".join(["artist=" + urllib.parse.quote(a) for a in t["artists"]]) for t in scrobbleswithpictures]
scrobbleimages = [getTrackImage(t["artists"],t["title"],fast=True) for t in scrobbleswithpictures]
representative = scrobbles[0] if len(scrobbles) is not 0 else None
# build list
i = 0
html = "<table class='list'>"
for s in scrobbles:
html += "<tr>"
html += "<td class='time'>" + timestamp_desc(s["time"],short=shortTimeDesc) + "</td>"
if pictures:
img = scrobbleimages[i]
else: img = None
html += entity_column(s,image=img)
# Alternative way: Do it in one cell
#html += "<td class='title'><span>" + artistLinks(s["artists"]) + "</span> — " + trackLink({"artists":s["artists"],"title":s["title"]}) + "</td>"
html += "</tr>"
i += 1
if max_ is not None and i>=max_:
break
html += "</table>"
return (html,len(scrobbles),representative)
def module_pulse(max_=None,**kwargs):
from doreah.timing import clock, clockp
kwargs_filter = pickKeys(kwargs,"artist","track","associated")
kwargs_time = pickKeys(kwargs,"since","to","within","timerange","step","stepn","trail")
ranges = database.get_pulse(**kwargs_time,**kwargs_filter)
if max_ is not None: ranges = ranges[:max_]
# if time range not explicitly specified, only show from first appearance
# if "since" not in kwargs:
# while ranges[0]["scrobbles"] == 0:
# del ranges[0]
maxbar = max([t["scrobbles"] for t in ranges])
maxbar = max(maxbar,1)
#build list
html = "<table class='list'>"
for t in ranges:
range = t["range"]
html += "<tr>"
html += "<td>" + range.desc() + "</td>"
html += "<td class='amount'>" + scrobblesLink(range.urikeys(),amount=t["scrobbles"],**kwargs_filter) + "</td>"
html += "<td class='bar'>" + scrobblesLink(range.urikeys(),percent=t["scrobbles"]*100/maxbar,**kwargs_filter) + "</td>"
html += "</tr>"
html += "</table>"
return html
def module_performance(max_=None,**kwargs):
kwargs_filter = pickKeys(kwargs,"artist","track")
kwargs_time = pickKeys(kwargs,"since","to","within","timerange","step","stepn","trail")
ranges = database.get_performance(**kwargs_time,**kwargs_filter)
if max_ is not None: ranges = ranges[:max_]
# if time range not explicitly specified, only show from first appearance
# if "since" not in kwargs:
# while ranges[0]["scrobbles"] == 0:
# del ranges[0]
minrank = 80
for t in ranges:
if t["rank"] is not None and t["rank"]+20 > minrank: minrank = t["rank"]+20
#build list
html = "<table class='list'>"
for t in ranges:
range = t["range"]
html += "<tr>"
html += "<td>" + range.desc() + "</td>"
html += "<td class='rank'>" + ("#" + str(t["rank"]) if t["rank"] is not None else "No scrobbles") + "</td>"
prct = (minrank+1-t["rank"])*100/minrank if t["rank"] is not None else 0
html += "<td class='chart'>" + rankLink(range.urikeys(),percent=prct,**kwargs_filter,medal=t["rank"]) + "</td>"
html += "</tr>"
html += "</table>"
return html
def module_trackcharts(max_=None,**kwargs):
kwargs_filter = pickKeys(kwargs,"artist","associated")
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
tracks = database.get_charts_tracks(**kwargs_filter,**kwargs_time)
# last time range (to compare)
try:
trackslast = database.get_charts_tracks(**kwargs_filter,timerange=kwargs_time["timerange"].next(step=-1))
# create rank association
lastrank = {}
for tl in trackslast:
lastrank[(*tl["track"]["artists"],tl["track"]["title"])] = tl["rank"]
for t in tracks:
try:
t["delta"] = lastrank[(*t["track"]["artists"],t["track"]["title"])] - t["rank"]
except:
t["delta"] = math.inf
except:
pass
if tracks != []:
maxbar = tracks[0]["scrobbles"]
representative = tracks[0]["track"]
else:
representative = None
i = 0
html = "<table class='list'>"
for e in tracks:
i += 1
if max_ is not None and i>max_:
break
html += "<tr>"
# rank
if i == 1 or e["scrobbles"] < prev["scrobbles"]:
html += "<td class='rank'>#" + str(i) + "</td>"
else:
html += "<td class='rank'></td>"
# rank change
if e.get("delta") is None:
pass
elif e["delta"] is math.inf:
html += "<td class='rankup' title='New'>🆕</td>"
elif e["delta"] > 0:
html += "<td class='rankup' title='up from #" + str(e["rank"]+e["delta"]) + "'>↗</td>"
elif e["delta"] < 0:
html += "<td class='rankdown' title='down from #" + str(e["rank"]+e["delta"]) + "'>↘</td>"
else:
html += "<td class='ranksame' title='Unchanged'>➡</td>"
# track
html += entity_column(e["track"])
# scrobbles
html += "<td class='amount'>" + scrobblesTrackLink(e["track"],internal_to_uri(kwargs_time),amount=e["scrobbles"]) + "</td>"
html += "<td class='bar'>" + scrobblesTrackLink(e["track"],internal_to_uri(kwargs_time),percent=e["scrobbles"]*100/maxbar) + "</td>"
html += "</tr>"
prev = e
html += "</table>"
return (html,representative)
def module_artistcharts(max_=None,**kwargs):
kwargs_filter = pickKeys(kwargs,"associated") #not used right now
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
artists = database.get_charts_artists(**kwargs_filter,**kwargs_time)
# last time range (to compare)
try:
#from malojatime import _get_next
artistslast = database.get_charts_artists(**kwargs_filter,timerange=kwargs_time["timerange"].next(step=-1))
# create rank association
lastrank = {}
for al in artistslast:
lastrank[al["artist"]] = al["rank"]
for a in artists:
try:
a["delta"] = lastrank[a["artist"]] - a["rank"]
except:
a["delta"] = math.inf
except:
pass
if artists != []:
maxbar = artists[0]["scrobbles"]
representative = artists[0]["artist"]
else:
representative = None
i = 0
html = "<table class='list'>"
for e in artists:
i += 1
if max_ is not None and i>max_:
break
html += "<tr>"
# rank
if i == 1 or e["scrobbles"] < prev["scrobbles"]:
html += "<td class='rank'>#" + str(i) + "</td>"
else:
html += "<td class='rank'></td>"
# rank change
#if "within" not in kwargs_time: pass
if e.get("delta") is None:
pass
elif e["delta"] is math.inf:
html += "<td class='rankup' title='New'>🆕</td>"
elif e["delta"] > 0:
html += "<td class='rankup' title='up from #" + str(e["rank"]+e["delta"]) + "'>↗</td>"
elif e["delta"] < 0:
html += "<td class='rankdown' title='down from #" + str(e["rank"]+e["delta"]) + "'>↘</td>"
else:
html += "<td class='ranksame' title='Unchanged'>➡</td>"
# artist
html += entity_column(e["artist"],counting=e["counting"])
# scrobbles
html += "<td class='amount'>" + scrobblesArtistLink(e["artist"],internal_to_uri(kwargs_time),amount=e["scrobbles"],associated=True) + "</td>"
html += "<td class='bar'>" + scrobblesArtistLink(e["artist"],internal_to_uri(kwargs_time),percent=e["scrobbles"]*100/maxbar,associated=True) + "</td>"
html += "</tr>"
prev = e
html += "</table>"
return (html, representative)
def module_toptracks(pictures=True,**kwargs):
kwargs_filter = pickKeys(kwargs,"artist","associated")
kwargs_time = pickKeys(kwargs,"timerange","since","to","within","step","stepn","trail")
tracks = database.get_top_tracks(**kwargs_filter,**kwargs_time)
if tracks != []:
maxbar = max(t["scrobbles"] for t in tracks)
# track with most #1 positions
max_appear = 0
representatives = list(t["track"] for t in tracks if t["track"] is not None)
for t in representatives:
max_appear = max(max_appear,representatives.count(t))
#representatives.sort(key=lambda reftrack:len([t for t in tracks if t["track"] == reftrack["track"] and t["track"] is not None]))
representatives = [t for t in tracks if representatives.count(t["track"]) == max_appear]
# of these, track with highest scrobbles in its #1 range
representatives.sort(key=lambda t: t["scrobbles"])
representative = representatives[-1]["track"]
else:
representative = None
i = 0
html = "<table class='list'>"
for e in tracks:
#fromstr = "/".join([str(p) for p in e["from"]])
#tostr = "/".join([str(p) for p in e["to"]])
range = e["range"]
i += 1
html += "<tr>"
html += "<td>" + range.desc() + "</td>"
if e["track"] is None:
if pictures:
html += "<td><div></div></td>"
html += "<td class='stats'>" + "No scrobbles" + "</td>"
html += "<td>" + "" + "</td>"
html += "<td class='amount'>" + "0" + "</td>"
html += "<td class='bar'>" + "" + "</td>"
else:
if pictures:
img = getTrackImage(e["track"]["artists"],e["track"]["title"],fast=True)
else: img = None
html += entity_column(e["track"],image=img)
html += "<td class='amount'>" + scrobblesTrackLink(e["track"],range.urikeys(),amount=e["scrobbles"]) + "</td>"
html += "<td class='bar'>" + scrobblesTrackLink(e["track"],range.urikeys(),percent=e["scrobbles"]*100/maxbar) + "</td>"
html += "</tr>"
prev = e
html += "</table>"
return (html,representative)
def module_topartists(pictures=True,**kwargs):
kwargs_time = pickKeys(kwargs,"timerange","since","to","within","step","stepn","trail")
artists = database.get_top_artists(**kwargs_time)
if artists != []:
maxbar = max(a["scrobbles"] for a in artists)
# artists with most #1 positions
max_appear = 0
representatives = list(a["artist"] for a in artists if a["artist"] is not None)
for a in representatives:
max_appear = max(max_appear,representatives.count(a))
representatives = [a for a in artists if representatives.count(a["artist"]) == max_appear]
# of these, artist with highest scrobbles in their #1 range
representatives.sort(key=lambda a: a["scrobbles"])
representative = representatives[-1]["artist"]
else:
representative = None
i = 0
html = "<table class='list'>"
for e in artists:
#fromstr = "/".join([str(p) for p in e["from"]])
#tostr = "/".join([str(p) for p in e["to"]])
range = e["range"]
i += 1
html += "<tr>"
html += "<td>" + range.desc() + "</td>"
if e["artist"] is None:
if pictures:
html += "<td><div></div></td>"
html += "<td class='stats'>" + "No scrobbles" + "</td>"
html += "<td class='amount'>" + "0" + "</td>"
html += "<td class='bar'>" + "" + "</td>"
else:
if pictures:
img = getArtistImage(e["artist"],fast=True)
else: img = None
html += entity_column(e["artist"],image=img)
html += "<td class='amount'>" + scrobblesArtistLink(e["artist"],range.urikeys(),amount=e["scrobbles"],associated=True) + "</td>"
html += "<td class='bar'>" + scrobblesArtistLink(e["artist"],range.urikeys(),percent=e["scrobbles"]*100/maxbar,associated=True) + "</td>"
html += "</tr>"
prev = e
html += "</table>"
return (html,representative)
def module_artistcharts_tiles(**kwargs):
kwargs_filter = pickKeys(kwargs,"associated") #not used right now
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
artists = database.get_charts_artists(**kwargs_filter,**kwargs_time)[:14]
while len(artists)<14: artists.append(None)
i = 1
bigpart = [0,1,2,6,15]
smallpart = [0,1,2,4,6,9,12,15]
#rnk = (0,0) #temporary store so entries with the same scrobble amount get the same rank
html = """<table class="tiles_top"><tr>"""
for e in artists:
if i in bigpart:
n = bigpart.index(i)
html += """<td><table class="tiles_""" + str(n) + """x""" + str(n) + """ tiles_sub">"""
if i in smallpart:
html += "<tr>"
if e is not None:
html += "<td onclick='window.location.href=\"" \
+ link_address(e["artist"]) \
+ "\"' style='cursor:pointer;background-image:url(\"" + getArtistImage(e["artist"],fast=True) + "\");'>" \
+ "<span class='stats'>" + "#" + str(e["rank"]) + "</span> <span>" + html_link(e["artist"]) + "</span></td>"
else:
html += "<td><span class='stats'></span> <span></span></td>"
i += 1
if i in smallpart:
html += "</tr>"
if i in bigpart:
html += "</table></td>"
html += """</tr></table>"""
return html
def module_trackcharts_tiles(**kwargs):
kwargs_filter = pickKeys(kwargs,"artist","associated")
kwargs_time = pickKeys(kwargs,"timerange","since","to","within")
tracks = database.get_charts_tracks(**kwargs_filter,**kwargs_time)[:14]
while len(tracks)<14: tracks.append(None) #{"track":{"title":"","artists":[]}}
i = 1
bigpart = [0,1,2,6,15]
smallpart = [0,1,2,4,6,9,12,15]
#rnk = (0,0) #temporary store so entries with the same scrobble amount get the same rank
html = """<table class="tiles_top"><tr>"""
for e in tracks:
if i in bigpart:
n = bigpart.index(i)
html += """<td><table class="tiles_""" + str(n) + """x""" + str(n) + """ tiles_sub">"""
if i in smallpart:
html += "<tr>"
if e is not None:
html += "<td onclick='window.location.href=\"" \
+ link_address(e["track"]) \
+ "\"' style='cursor:pointer;background-image:url(\"" + getTrackImage(e["track"]["artists"],e["track"]["title"],fast=True) + "\");'>" \
+ "<span class='stats'>" + "#" + str(e["rank"]) + "</span> <span>" + html_link(e["track"]) + "</span></td>"
else:
html += "<td><span class='stats'></span> <span></span></td>"
i += 1
if i in smallpart:
html += "</tr>"
if i in bigpart:
html += "</table></td>"
html += """</tr></table>"""
return html
# THIS FUNCTION USES THE ORIGINAL URI KEYS!!!
def module_filterselection(keys,time=True,delimit=False):
filterkeys, timekeys, delimitkeys, extrakeys = uri_to_internal(keys)
html = ""
if time:
# all other keys that will not be changed by clicking another filter
#keystr = "?" + compose_querystring(keys,exclude=["since","to","in"])
unchangedkeys = internal_to_uri({**filterkeys,**delimitkeys,**extrakeys})
# wonky selector for precise date range
# fromdate = start_of_scrobbling()
# todate = end_of_scrobbling()
# if keys.get("since") is not None: fromdate = keys.get("since")
# if keys.get("to") is not None: todate = keys.get("to")
# if keys.get("in") is not None: fromdate, todate = keys.get("in"), keys.get("in")
# fromdate = time_fix(fromdate)
# todate = time_fix(todate)
# fromdate, todate = time_pad(fromdate,todate,full=True)
# fromdate = [str(e) if e>9 else "0" + str(e) for e in fromdate]
# todate = [str(e) if e>9 else "0" + str(e) for e in todate]
#
# html += "<div>"
# html += "from <input id='dateselect_from' onchange='datechange()' type='date' value='" + "-".join(fromdate) + "'/> "
# html += "to <input id='dateselect_to' onchange='datechange()' type='date' value='" + "-".join(todate) + "'/>"
# html += "</div>"
from malojatime import today, thisweek, thismonth, thisyear
### temp!!! this will not allow weekly rank changes
# weekday = ((now.isoweekday()) % 7)
# weekbegin = now - datetime.timedelta(days=weekday)
# weekend = weekbegin + datetime.timedelta(days=6)
# weekbegin = [weekbegin.year,weekbegin.month,weekbegin.day]
# weekend = [weekend.year,weekend.month,weekend.day]
# weekbeginstr = "/".join((str(num) for num in weekbegin))
# weekendstr = "/".join((str(num) for num in weekend))
# relative to current range
html += "<div>"
# if timekeys.get("timerange").next(-1) is not None:
# html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":timekeys.get("timerange").next(-1)})) + "'><span class='stat_selector'>«</span></a>"
# if timekeys.get("timerange").next(-1) is not None or timekeys.get("timerange").next(1) is not None:
# html += " " + timekeys.get("timerange").desc() + " "
# if timekeys.get("timerange").next(1) is not None:
# html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":timekeys.get("timerange").next(1)})) + "'><span class='stat_selector'>»</span></a>"
if timekeys.get("timerange").next(-1) is not None:
prevrange = timekeys.get("timerange").next(-1)
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":prevrange})) + "'><span class='stat_selector'>" + prevrange.desc() + "</span></a>"
html += " « "
if timekeys.get("timerange").next(-1) is not None or timekeys.get("timerange").next(1) is not None:
html += "<span class='stat_selector' style='opacity:0.5;'>" + timekeys.get("timerange").desc() + "</span>"
if timekeys.get("timerange").next(1) is not None:
html += " » "
nextrange = timekeys.get("timerange").next(1)
html += "<a href='?" + compose_querystring(unchangedkeys,internal_to_uri({"timerange":nextrange})) + "'><span class='stat_selector'>" + nextrange.desc() + "</span></a>"
html += "</div>"
# predefined ranges
html += "<div>"
if timekeys.get("timerange") == today():
html += "<span class='stat_selector' style='opacity:0.5;'>Today</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"today"}) + "'><span class='stat_selector'>Today</span></a>"
html += " | "
if timekeys.get("timerange") == thisweek():
html += "<span class='stat_selector' style='opacity:0.5;'>This Week</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"week"}) + "'><span class='stat_selector'>This Week</span></a>"
html += " | "
if timekeys.get("timerange") == thismonth():
html += "<span class='stat_selector' style='opacity:0.5;'>This Month</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"month"}) + "'><span class='stat_selector'>This Month</span></a>"
html += " | "
if timekeys.get("timerange") == thisyear():
html += "<span class='stat_selector' style='opacity:0.5;'>This Year</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,{"in":"year"}) + "'><span class='stat_selector'>This Year</span></a>"
html += " | "
if timekeys.get("timerange") is None or timekeys.get("timerange").unlimited():
html += "<span class='stat_selector' style='opacity:0.5;'>All Time</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys) + "'><span class='stat_selector'>All Time</span></a>"
html += "</div>"
if delimit:
#keystr = "?" + compose_querystring(keys,exclude=["step","stepn"])
unchangedkeys = internal_to_uri({**filterkeys,**timekeys,**extrakeys})
# only for this element (delimit selector consists of more than one)
unchangedkeys_sub = internal_to_uri({k:delimitkeys[k] for k in delimitkeys if k not in ["step","stepn"]})
html += "<div>"
if delimitkeys.get("step") == "day" and delimitkeys.get("stepn") == 1:
html += "<span class='stat_selector' style='opacity:0.5;'>Daily</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"day"}) + "'><span class='stat_selector'>Daily</span></a>"
html += " | "
if delimitkeys.get("step") == "week" and delimitkeys.get("stepn") == 1:
html += "<span class='stat_selector' style='opacity:0.5;'>Weekly</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"week"}) + "'><span class='stat_selector'>Weekly</span></a>"
html += " | "
if delimitkeys.get("step") == "month" and delimitkeys.get("stepn") == 1:
html += "<span class='stat_selector' style='opacity:0.5;'>Monthly</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"month"}) + "'><span class='stat_selector'>Monthly</span></a>"
html += " | "
if delimitkeys.get("step") == "year" and delimitkeys.get("stepn") == 1:
html += "<span class='stat_selector' style='opacity:0.5;'>Yearly</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"step":"year"}) + "'><span class='stat_selector'>Yearly</span></a>"
html += "</div>"
unchangedkeys_sub = internal_to_uri({k:delimitkeys[k] for k in delimitkeys if k != "trail"})
html += "<div>"
if delimitkeys.get("trail") == 1:
html += "<span class='stat_selector' style='opacity:0.5;'>Standard</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"1"}) + "'><span class='stat_selector'>Standard</span></a>"
html += " | "
if delimitkeys.get("trail") == 2:
html += "<span class='stat_selector' style='opacity:0.5;'>Trailing</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"2"}) + "'><span class='stat_selector'>Trailing</span></a>"
html += " | "
if delimitkeys.get("trail") == 3:
html += "<span class='stat_selector' style='opacity:0.5;'>Long Trailing</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"trail":"3"}) + "'><span class='stat_selector'>Long Trailing</span></a>"
html += " | "
if delimitkeys.get("trail") == math.inf:
html += "<span class='stat_selector' style='opacity:0.5;'>Cumulative</span>"
else:
html += "<a href='?" + compose_querystring(unchangedkeys,unchangedkeys_sub,{"cumulative":"yes"}) + "'><span class='stat_selector'>Cumulative</span></a>"
html += "</div>"
return html

3
images/.gitignore vendored
View File

@ -1,3 +0,0 @@
*
!.gitignore
!*.info

20
install/install_alpine.sh Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env sh
apk update
apk add \
gcc \
g++ \
python3-dev \
libxml2-dev \
libxslt-dev \
libffi-dev \
libc-dev \
py3-pip \
linux-headers \
python3 \
py3-lxml \
tzdata \
vips
apk add py3-pip
pip install wheel
pip install malojaserver

View File

@ -0,0 +1,9 @@
#!/usr/bin/env sh
apt update
apt install \
python3-pip \
python3
apt install python3-pip
pip install wheel
pip install malojaserver

View File

@ -0,0 +1,16 @@
#!/usr/bin/env sh
apk update
apk add \
gcc \
g++ \
python3-dev \
libxml2-dev \
libxslt-dev \
libffi-dev \
libc-dev \
py3-pip \
linux-headers \
python3 \
py3-lxml \
tzdata \
vips

View File

@ -0,0 +1,5 @@
#!/usr/bin/env sh
apt update
apt install \
python3-pip \
python3

View File

@ -1,63 +0,0 @@
import sys, os, datetime, re, cleanup
from cleanup import *
from utilities import *
log = open(sys.argv[1])
outputlog = open(sys.argv[2],"w")
checksumfile = open(sys.argv[2] + ".rulestate","w") #this file stores an identifier for all rules that were in place when the corresponding file was created
c = CleanerAgent()
stamps = [99999999999999]
for l in log:
l = l.replace("\n","")
data = l.split(",")
artist = data[0]
album = data[1]
title = data[2]
time = data[3]
(artists,title) = c.fullclean(artist,title)
artistsstr = "".join(artists)
timeparts = time.split(" ")
(h,m) = timeparts[3].split(":")
months = {"Jan":1,"Feb":2,"Mar":3,"Apr":4,"May":5,"Jun":6,"Jul":7,"Aug":8,"Sep":9,"Oct":10,"Nov":11,"Dec":12}
timestamp = int(datetime.datetime(int(timeparts[2]),months[timeparts[1]],int(timeparts[0]),int(h),int(m)).timestamp())
## We prevent double timestamps in the database creation, so we technically don't need them in the files
## however since the conversion from lastfm to maloja is a one-time thing, we should take any effort to make the file as good as possible
if (timestamp < stamps[-1]):
pass
elif (timestamp == stamps[-1]):
timestamp -= 1
else:
while(timestamp in stamps):
timestamp -= 1
if (timestamp < stamps[-1]):
stamps.append(timestamp)
else:
stamps.insert(0,timestamp)
entry = "\t".join([str(timestamp),artistsstr,title,album])
entry = entry.replace("#",r"\num")
outputlog.write(entry)
outputlog.write("\n")
checksumfile.write(c.checksums)
log.close()
outputlog.close()
checksumfile.close()

331
maloja
View File

@ -1,331 +0,0 @@
#!/usr/bin/env python3
import subprocess
import sys
import signal
import os
import stat
import pathlib
neededmodules = [
"bottle",
"waitress",
"setproctitle",
"doreah"
]
recommendedmodules = [
"wand"
]
SOURCE_URL = "https://github.com/krateng/maloja/archive/master.zip"
def blue(txt): return "\033[94m" + txt + "\033[0m"
def green(txt): return "\033[92m" + txt + "\033[0m"
def yellow(txt): return "\033[93m" + txt + "\033[0m"
## GOTODIR goes to directory that seems to have a maloja install
## SETUP assumes correct directory. sets settings and key
## INSTALL ignores local files, just installs prerequisites
## START INSTALL - GOTODIR - SETUP - starts process
## RESTART STOP - START
## STOP Stops process
## UPDATE GOTODIR - updates from repo
## LOADLASTFM GOTODIR - imports csv data
## INSTALLHERE makes this directory valid - UPDATE - INSTALL - SETUP
def gotodir():
if os.path.exists("./server.py"):
return True
elif os.path.exists("/opt/maloja/server.py"):
os.chdir("/opt/maloja/")
return True
print("Maloja installation could not be found.")
return False
def setup():
from doreah import settings
# EXTERNAL API KEYS
apikeys = {
"LASTFM_API_KEY":"Last.fm API Key",
"FANARTTV_API_KEY":"Fanart.tv API Key",
"SPOTIFY_API_ID":"Spotify Client ID",
"SPOTIFY_API_SECRET":"Spotify Client Secret"
}
print("Various external services can be used to display images. If not enough of them are set up, only local images will be used.")
for k in apikeys:
key = settings.get_settings(k)
if key is None:
print("Currently not using a " + apikeys[k] + " for image display.")
elif key == "ASK":
print("Please enter your " + apikeys[k] + ". If you do not want to use one at this moment, simply leave this empty and press Enter.")
key = input()
if key == "": key = None
settings.update_settings("settings/settings.ini",{k:key},create_new=True)
else:
print(apikeys[k] + " found.")
# OWN API KEY
if os.path.exists("./clients/authenticated_machines.tsv"):
pass
else:
print("Do you want to set up a key to enable scrobbling? Your scrobble extension needs that key so that only you can scrobble tracks to your database. [Y/n]")
answer = input()
if answer.lower() in ["y","yes","yea","1","positive","true",""]:
import random
key = ""
for i in range(64):
key += str(random.choice(list(range(10)) + list("abcdefghijklmnopqrstuvwxyz") + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")))
print("Your API Key: " + yellow(key))
with open("./clients/authenticated_machines.tsv","w") as keyfile:
keyfile.write(key + "\t" + "Default Generated Key")
elif answer.lower() in ["n","no","nay","0","negative","false"]:
pass
def install():
toinstall = []
toinstallr = []
for m in neededmodules:
try:
exec("import " + m) #I'm sorry
except:
toinstall.append(m)
for m in recommendedmodules:
try:
exec("import " + m)
except:
toinstallr.append(m)
if toinstall != []:
print("The following python modules need to be installed:")
for m in toinstall:
print("\t" + yellow(m))
if toinstallr != []:
print("The following python modules are highly recommended, some features will not work without them:")
for m in toinstallr:
print("\t" + yellow(m))
if toinstall != [] or toinstallr != []:
if os.geteuid() != 0:
print("Installing python modules should be fairly straight-forward, but Maloja can try to install them automatically. For this, you need to run this script as a root user.")
return False
else:
print("Installing python modules should be fairly straight-forward, but Maloja can try to install them automatically, This might or might not work / bloat your system / cause a nuclear war.")
fail = False
if toinstall != []:
print("Attempt to install required modules? [Y/n]")
answer = input()
if answer.lower() in ["y","yes","yea","1","positive","true",""]:
for m in toinstall:
try:
print("Installing " + m + " with pip...")
from pip._internal import main as pipmain
#os.system("pip3 install " + m)
pipmain(["install",m])
print("Success!")
except:
print("Failure!")
fail = True
elif answer.lower() in ["n","no","nay","0","negative","false"]:
return False #if you dont want to auto install required, you probably dont want to install recommended
else:
print("What?")
return False
if toinstallr != []:
print("Attempt to install recommended modules? [Y/n]")
answer = input()
if answer.lower() in ["y","yes","yea","1","positive","true",""]:
for m in toinstallr:
try:
print("Installing " + m + " with pip...")
from pip._internal import main as pipmain
#os.system("pip3 install " + m)
pipmain(["install",m])
print("Success!")
except:
print("Failure!")
fail = True
elif answer.lower() in ["n","no","nay","0","negative","false"]:
return False
else:
print("What?")
return False
if fail: return False
print("All modules successfully installed!")
print("Run the script again (without root) to start Maloja.")
return False
else:
print("All necessary modules seem to be installed.")
return True
def getInstance():
try:
output = subprocess.check_output(["pidof","Maloja"])
pid = int(output)
return pid
except:
return None
def start():
if install():
if gotodir():
setup()
p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
print(green("Maloja started!") + " PID: " + str(p.pid))
from doreah import settings
port = settings.get_settings("WEB_PORT")
print("Visit your server address (Port " + str(port) + ") to see your web interface. Visit /setup to get started.")
print("If you're installing this on your local machine, these links should get you there:")
print("\t" + blue("http://localhost:" + str(port)))
print("\t" + blue("http://localhost:" + str(port) + "/setup"))
return True
#else:
# os.chdir("/opt/maloja/")
# p = subprocess.Popen(["python3","server.py"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
# print("Maloja started! PID: " + str(p.pid))
# return True
print("Error while starting Maloja.")
return False
def restart():
#pid = getInstance()
#if pid == None:
# print("Server is not running.")
#else:
# stop()
#start()
wasrunning = stop()
start()
return wasrunning
def stop():
pid = getInstance()
if pid == None:
print("Server is not running")
return False
else:
os.kill(pid,signal.SIGTERM)
print("Maloja stopped! PID: " + str(pid))
return True
def update():
import urllib.request
import shutil
#import tempfile
import zipfile
import distutils.dir_util
if not gotodir(): return False
if os.path.exists("./.dev"):
print("Better not overwrite the development server!")
return
print("Updating Maloja...")
#with urllib.request.urlopen(SOURCE_URL) as response:
# with tempfile.NamedTemporaryFile(delete=True) as tmpfile:
# shutil.copyfileobj(response,tmpfile)
#
# with zipfile.ZipFile(tmpfile.name,"r") as z:
#
# for f in z.namelist():
# #print("extracting " + f)
# z.extract(f)
os.system("wget " + SOURCE_URL)
with zipfile.ZipFile("./master.zip","r") as z:
# if we ever have a separate directory for the code
# (the calling update script is not the same version as the current
# remote repository, so we better add this check just in case)
if "source/" in z.namelist():
for f in z.namelist():
if f.startswith("source/"):
z.extract(f)
for dir,_,files in os.walk("source"):
for f in files:
origfile = os.path.join(dir,f)
newfile = ps.path.join(dir[7:],f)
os.renames(origfile,newfile) #also prunes empty directory
else:
for f in z.namelist():
z.extract(f)
os.remove("./master.zip")
distutils.dir_util.copy_tree("./maloja-master/","./",verbose=2)
shutil.rmtree("./maloja-master")
print("Done!")
os.chmod("./maloja",os.stat("./maloja").st_mode | stat.S_IXUSR)
print("Make sure to install the latest version of doreah! (" + yellow("pip3 install --upgrade --no-cache-dir doreah") + ")")
if stop(): start() #stop returns whether it was running before, in which case we restart it
def loadlastfm():
try:
filename = sys.argv[2]
filename = os.path.abspath(filename)
except:
print("Please specify a file!")
return
if gotodir():
if os.path.exists("./scrobbles/lastfmimport.tsv"):
print("Already imported Last.FM data. Overwrite? [y/N]")
if input().lower() in ["y","yes","yea","1","positive","true"]:
pass
else:
return
print("Please wait...")
os.system("python3 ./lastfmconverter.py " + filename + " ./scrobbles/lastfmimport.tsv")
print("Successfully imported your Last.FM scrobbles!")
def installhere():
if len(os.listdir()) > 1:
print("You should install Maloja in an empty directory.")
return False
else:
open("server.py","w").close()
# if it's cheese, but it works, it ain't cheese
update()
install()
setup()
print("Maloja installed! Start with " + yellow("./maloja start"))
if __name__ == "__main__":
if sys.argv[1] == "start": restart()
elif sys.argv[1] == "restart": restart()
elif sys.argv[1] == "stop": stop()
elif sys.argv[1] == "update": update()
elif sys.argv[1] == "import": loadlastfm()
elif sys.argv[1] == "install": installhere()
else: print("Valid commands: start restart stop update import install")

4
maloja/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# monkey patching
from .pkg_global import monkey
# configuration before all else
from .pkg_global import conf

Some files were not shown because too many files have changed in this diff Show More