├── maloja ├── proccontrol │ ├── __init__.py │ └── tasks │ │ ├── __init__.py │ │ ├── export.py │ │ └── backup.py ├── data_files │ ├── cache │ │ ├── images │ │ │ └── dummy │ │ └── .maloja_cache_sentinel │ ├── logs │ │ ├── dbfix │ │ │ └── dummy │ │ └── .maloja_logs_sentinel │ ├── state │ │ ├── auth │ │ │ └── dummy │ │ ├── backups │ │ │ └── dummy │ │ ├── import │ │ │ └── dummy │ │ ├── images │ │ │ ├── albums │ │ │ │ └── dummy │ │ │ ├── tracks │ │ │ │ └── dummy │ │ │ ├── artists │ │ │ │ └── dummy │ │ │ └── images.info │ │ └── .maloja_state_sentinel │ └── config │ │ ├── .maloja_config_sentinel │ │ ├── rules │ │ ├── predefined │ │ │ ├── .gitignore │ │ │ ├── krateng_kpop.tsv │ │ │ ├── krateng_memes.tsv │ │ │ ├── krateng_classical.tsv │ │ │ ├── krateng_dach.tsv │ │ │ ├── tdemin_kpop.tsv │ │ │ ├── krateng_specialsymbols.tsv │ │ │ ├── krateng_redcliff.tsv │ │ │ ├── predefined.info │ │ │ ├── krateng_monstercat.tsv │ │ │ ├── krateng_cpop.tsv │ │ │ ├── krateng_jeremysoule.tsv │ │ │ ├── krateng_jpop.tsv │ │ │ ├── krateng_firefly-soundtrack.tsv │ │ │ ├── krateng_threelions.tsv │ │ │ ├── krateng_artistsingroups.tsv │ │ │ ├── krateng_masseffect.tsv │ │ │ └── krateng_lotr-soundtrack.tsv │ │ └── rules.info │ │ └── custom_css │ │ └── customcss.info ├── web │ ├── static │ │ ├── css │ │ │ ├── themes │ │ │ │ ├── maloja.css │ │ │ │ ├── kda.css │ │ │ │ └── constantinople.css │ │ │ ├── grisonsfont.css │ │ │ └── startpage.css │ │ ├── txt │ │ │ └── robots.txt │ │ ├── png │ │ │ ├── star.png │ │ │ ├── favicon.png │ │ │ ├── star_alt.png │ │ │ ├── chartpos_gold.png │ │ │ ├── favicon_large.png │ │ │ ├── favicon_old.png │ │ │ ├── chartpos_bronze.png │ │ │ ├── chartpos_normal.png │ │ │ └── chartpos_silver.png │ │ ├── ico │ │ │ ├── favicon.ico │ │ │ └── favicon_old.ico │ │ ├── ttf │ │ │ ├── Ubuntu-Bold.ttf │ │ │ ├── Ubuntu-Italic.ttf │ │ │ ├── Ubuntu-Light.ttf │ │ │ ├── Ubuntu-Medium.ttf │ │ │ ├── Ubuntu-Regular.ttf │ │ │ ├── Ubuntu-BoldItalic.ttf │ │ │ ├── Ubuntu-LightItalic.ttf │ │ │ └── Ubuntu-MediumItalic.ttf │ │ ├── js │ │ │ ├── upload.js │ │ │ ├── datechange.js │ │ │ ├── notifications.js │ │ │ └── statselect.js │ │ └── svg │ │ │ ├── placeholder_album.svg │ │ │ ├── placeholder_artist.svg │ │ │ ├── placeholder_track.svg │ │ │ └── LICENSE │ └── jinja │ │ ├── admin_settings.jinja │ │ ├── icons │ │ ├── delete.jinja │ │ ├── nodata.jinja │ │ ├── add_artist_confirm.jinja │ │ ├── add_album_confirm.jinja │ │ ├── reparse.jinja │ │ ├── merge.jinja │ │ ├── merge_cancel.jinja │ │ ├── association_cancel.jinja │ │ ├── add_artist.jinja │ │ ├── remove_artist.jinja │ │ ├── edit.jinja │ │ ├── merge_mark.jinja │ │ ├── merge_unmark.jinja │ │ ├── remove_album.jinja │ │ ├── association_mark.jinja │ │ ├── add_album.jinja │ │ ├── disassociate.jinja │ │ ├── association_unmark.jinja │ │ ├── LICENSE-octicons │ │ ├── settings.jinja │ │ ├── cert_album.jinja │ │ └── cert_track.jinja │ │ ├── admin_apikeys.jinja │ │ ├── admin_albumless.jinja │ │ ├── startpage_modules │ │ ├── lastscrobbles.jinja │ │ ├── pulse.jinja │ │ ├── charts_albums.jinja │ │ ├── charts_tracks.jinja │ │ ├── charts_artists.jinja │ │ └── featured.jinja │ │ ├── snippets │ │ ├── filterdescription.jinja │ │ ├── pagination.jinja │ │ ├── entityrow.jinja │ │ └── timeselection.jinja │ │ ├── start.jinja │ │ ├── partials │ │ ├── list_tracks.jinja │ │ ├── performance.jinja │ │ ├── charts_artists_tiles.jinja │ │ ├── charts_tracks_tiles.jinja │ │ ├── top_albums.jinja │ │ ├── top_tracks.jinja │ │ ├── charts_albums_tiles.jinja │ │ ├── pulse.jinja │ │ ├── awards_track.jinja │ │ ├── scrobbles.jinja │ │ ├── top_artists.jinja │ │ ├── info_album.jinja │ │ ├── info_track.jinja │ │ ├── album_showcase.jinja │ │ ├── info_artist.jinja │ │ ├── awards_album.jinja │ │ ├── charts_tracks.jinja │ │ ├── charts_albums.jinja │ │ ├── charts_artists.jinja │ │ └── awards_artist.jinja │ │ ├── admin_import.jinja │ │ ├── top_albums.jinja │ │ ├── top_tracks.jinja │ │ ├── top_artists.jinja │ │ ├── error.jinja │ │ ├── wait.jinja │ │ ├── abstracts │ │ └── admin.jinja │ │ ├── pulse.jinja │ │ ├── performance.jinja │ │ ├── scrobbles.jinja │ │ ├── charts_tracks.jinja │ │ ├── charts_artists.jinja │ │ ├── charts_albums.jinja │ │ ├── about.jinja │ │ ├── admin_manual.jinja │ │ └── admin_issues.jinja ├── dev │ ├── __init__.py │ ├── apidebug.py │ ├── profiler.py │ └── generate.py ├── __init__.py ├── apis │ ├── _exceptions.py │ ├── _apikeys.py │ └── __init__.py ├── __pkginfo__.py ├── pkg_global │ └── monkey.py ├── thirdparty │ ├── audiodb.py │ ├── maloja.py │ ├── deezer.py │ ├── spotify.py │ └── lastfm.py ├── database │ ├── jinjaview.py │ ├── exceptions.py │ └── associated.py └── jinjaenv │ └── filters.py ├── requirements_extra.txt ├── dev ├── list_tags.sh ├── clear_testdata.sh ├── templates │ ├── requirements.txt.jinja │ └── requirements_extra.txt.jinja ├── releases │ ├── 1.0.yml │ ├── 1.1.yml │ ├── 1.3.yml │ ├── 1.5.yml │ ├── 1.2.yml │ ├── 1.4.yml │ ├── 2.11.yml │ ├── 2.1.yml │ ├── 2.0.yml │ ├── 2.5.yml │ ├── 2.2.yml │ ├── 2.13.yml │ ├── 2.8.yml │ ├── 2.10.yml │ ├── 2.3.yml │ ├── 2.6.yml │ ├── 2.9.yml │ ├── 2.14.yml │ ├── 2.7.yml │ ├── 2.4.yml │ ├── 2.12.yml │ ├── 3.1.yml │ ├── 3.0.yml │ └── 3.2.yml ├── docker-compose.yml ├── update_scrobbler.sh ├── update_dist_files.py ├── testing │ └── stresstest.py └── write_tags.py ├── container └── root │ └── etc │ └── s6-overlay │ └── s6-rc.d │ ├── svc-python │ ├── type │ ├── dependencies.d │ │ └── init-services │ └── run │ ├── user │ └── contents.d │ │ ├── svc-python │ │ └── init-permission-check │ └── init-permission-check │ ├── type │ ├── dependencies.d │ └── init-config │ ├── up │ └── run ├── screenshot.png ├── .github ├── FUNDING.yml └── workflows │ ├── auxil.scrobbler.upload.yml │ ├── pypi.yml │ ├── auxil.library.pypi.yml │ └── docker.yml ├── auxiliary ├── chromium_scrobbler │ ├── .gitignore │ ├── maloja-scrobbler.zip │ └── maloja-scrobbler │ │ ├── icon48.png │ │ ├── icon128.png │ │ ├── icon256.png │ │ ├── sites │ │ ├── spotify.js │ │ ├── plex.js │ │ ├── navidrome.js │ │ ├── soundcloud.js │ │ ├── bandcamp.js │ │ └── ytmusic.js │ │ ├── manifest.json │ │ ├── settings.html │ │ └── sitescript.js └── malojalib │ ├── README.md │ ├── malojalib │ └── __init__.py │ └── pyproject.toml ├── .dockerignore ├── .gitignore ├── requirements.txt ├── example-compose.yml ├── DEVELOPMENT.md ├── FUTURE.md ├── pylintrc ├── pyproject.toml └── Containerfile /maloja/proccontrol/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/cache/images/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/logs/dbfix/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/auth/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/backups/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/import/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/images/albums/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/images/tracks/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/web/static/css/themes/maloja.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/cache/.maloja_cache_sentinel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/config/.maloja_config_sentinel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/logs/.maloja_logs_sentinel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/.maloja_state_sentinel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/data_files/state/images/artists/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements_extra.txt: -------------------------------------------------------------------------------- 1 | pyvips==2.2.* 2 | 3 | -------------------------------------------------------------------------------- /dev/list_tags.sh: -------------------------------------------------------------------------------- 1 | git tag -l '*.0' -n1 --sort=v:refname 2 | -------------------------------------------------------------------------------- /maloja/data_files/config/rules/predefined/.gitignore: -------------------------------------------------------------------------------- 1 | !*.tsv 2 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/svc-python/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-python: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maloja/web/static/txt/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/init-permission-check/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/svc-python/dependencies.d/init-services: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-permission-check: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/screenshot.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://paypal.me/krateng"] 2 | patreon: krateng 3 | -------------------------------------------------------------------------------- /auxiliary/chromium_scrobbler/.gitignore: -------------------------------------------------------------------------------- 1 | screenshot.png 2 | tile.png 3 | !*.sh 4 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/init-permission-check/dependencies.d/init-config: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/clear_testdata.sh: -------------------------------------------------------------------------------- 1 | sudo rm -r ./testdata 2 | mkdir ./testdata 3 | chmod 777 ./testdata -------------------------------------------------------------------------------- /dev/templates/requirements.txt.jinja: -------------------------------------------------------------------------------- 1 | {% for dep in project.dependencies -%} 2 | {{ dep }} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /maloja/web/static/png/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/star.png -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/init-permission-check/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init-permission-check/run 2 | -------------------------------------------------------------------------------- /maloja/web/static/ico/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ico/favicon.ico -------------------------------------------------------------------------------- /maloja/web/static/png/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/favicon.png -------------------------------------------------------------------------------- /maloja/web/static/png/star_alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/star_alt.png -------------------------------------------------------------------------------- /dev/releases/1.0.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Yura" 2 | '1.0': 3 | commit: "1fac2ca965fdbe40c85a88559d5b736f4829e7b0" 4 | -------------------------------------------------------------------------------- /dev/releases/1.1.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Solar" 2 | '1.1': 3 | commit: "5603ca9eb137516e604e9e3e83e273a70ef32f65" 4 | -------------------------------------------------------------------------------- /dev/releases/1.3.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "IU" 2 | '1.3': 3 | commit: "0bf1790a7cc0174b84f8c25dade6b221b13d65e9" 4 | -------------------------------------------------------------------------------- /dev/releases/1.5.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Seulgi" 2 | '1.5': 3 | commit: "e282789153ec3df133474a56e8d922a73795b72a" 4 | -------------------------------------------------------------------------------- /dev/releases/1.2.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Jeonghwa" 2 | '1.2': 3 | commit: "d46d2be2bf27ef40ddd9f0c077f86dcf0214adbb" 4 | -------------------------------------------------------------------------------- /dev/releases/1.4.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Chungha" 2 | '1.4': 3 | commit: "981c0e4ae2ad1bff5a0778b6fa34916b0c4d4f4a" 4 | -------------------------------------------------------------------------------- /dev/releases/2.11.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Akali" 2 | 2.11.0: 3 | commit: "218313f80c160f90b28d99236a062ef62db7260d" 4 | -------------------------------------------------------------------------------- /maloja/web/static/ico/favicon_old.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ico/favicon_old.ico -------------------------------------------------------------------------------- /maloja/web/static/png/chartpos_gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/chartpos_gold.png -------------------------------------------------------------------------------- /maloja/web/static/png/favicon_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/favicon_large.png -------------------------------------------------------------------------------- /maloja/web/static/png/favicon_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/favicon_old.png -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-Italic.ttf -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-Light.ttf -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-Medium.ttf -------------------------------------------------------------------------------- /dev/templates/requirements_extra.txt.jinja: -------------------------------------------------------------------------------- 1 | {% for dep in project['optional-dependencies'].full -%} 2 | {{ dep }} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /maloja/web/static/png/chartpos_bronze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/chartpos_bronze.png -------------------------------------------------------------------------------- /maloja/web/static/png/chartpos_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/chartpos_normal.png -------------------------------------------------------------------------------- /maloja/web/static/png/chartpos_silver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/png/chartpos_silver.png -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !maloja 3 | !container 4 | !Containerfile 5 | !requirements.txt 6 | !pyproject.toml 7 | !README.md 8 | !LICENSE 9 | -------------------------------------------------------------------------------- /maloja/data_files/config/custom_css/customcss.info: -------------------------------------------------------------------------------- 1 | In this folder, you can place any number of CSS files to change Maloja's style. 2 | -------------------------------------------------------------------------------- /maloja/dev/__init__.py: -------------------------------------------------------------------------------- 1 | ### Subpackage that takes care of all things that concern the server process itself, 2 | ### e.g. analytics 3 | -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-BoldItalic.ttf -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-LightItalic.ttf -------------------------------------------------------------------------------- /maloja/web/static/ttf/Ubuntu-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/maloja/web/static/ttf/Ubuntu-MediumItalic.ttf -------------------------------------------------------------------------------- /auxiliary/chromium_scrobbler/maloja-scrobbler.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/auxiliary/chromium_scrobbler/maloja-scrobbler.zip -------------------------------------------------------------------------------- /maloja/__init__.py: -------------------------------------------------------------------------------- 1 | # monkey patching 2 | from .pkg_global import monkey 3 | # configuration before all else 4 | from .pkg_global import conf 5 | -------------------------------------------------------------------------------- /auxiliary/chromium_scrobbler/maloja-scrobbler/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/auxiliary/chromium_scrobbler/maloja-scrobbler/icon48.png -------------------------------------------------------------------------------- /maloja/data_files/config/rules/predefined/krateng_kpop.tsv: -------------------------------------------------------------------------------- 1 | # NAME: K-Pop 2 | # DESC: Some romanizations and fixes 3 | 4 | replaceartist 강남스타일 Gangnam Style 5 | -------------------------------------------------------------------------------- /maloja/web/static/js/upload.js: -------------------------------------------------------------------------------- 1 | function upload(encodedentity,b64) { 2 | neo.xhttprequest("/apis/mlj_1/addpicture?" + encodedentity,{"b64":b64},"POST") 3 | } 4 | -------------------------------------------------------------------------------- /auxiliary/chromium_scrobbler/maloja-scrobbler/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/auxiliary/chromium_scrobbler/maloja-scrobbler/icon128.png -------------------------------------------------------------------------------- /auxiliary/chromium_scrobbler/maloja-scrobbler/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krateng/maloja/HEAD/auxiliary/chromium_scrobbler/maloja-scrobbler/icon256.png -------------------------------------------------------------------------------- /dev/releases/2.1.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Jennie" 2 | 2.1.0: 3 | commit: "b87379ed986640788201f1ff52826413067e5ffb" 4 | 2.1.4: 5 | commit: "c95ce17451cb19b4775a819f82a532d3a3a6231b" 6 | -------------------------------------------------------------------------------- /dev/releases/2.0.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Irene" 2 | '2.0': 3 | commit: "55621ef4efdf61c3092d42565e897dfbaa0244c8" 4 | notes: 5 | - "[Architecture] Refactored into Python Package" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # temporary / generated files 2 | *.pyc 3 | 4 | # environments / builds 5 | .venv/* 6 | /dist 7 | /build 8 | /*.egg-info 9 | 10 | # dev files 11 | *.xcf 12 | *.note 13 | *-old 14 | -------------------------------------------------------------------------------- /maloja/data_files/config/rules/predefined/krateng_memes.tsv: -------------------------------------------------------------------------------- 1 | # NAME: Meme Music 2 | # DESC: Some fixes for internet artists 3 | 4 | replaceartist Weebl's Stuff Mr Weebl 5 | replaceartist Mr. Weebl Mr Weebl 6 | -------------------------------------------------------------------------------- /maloja/proccontrol/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | from .import_scrobbles import import_scrobbles 2 | from .backup import backup 3 | from .export import export # read that line out loud 4 | from .parse_albums import parse_albums 5 | -------------------------------------------------------------------------------- /maloja/web/jinja/admin_settings.jinja: -------------------------------------------------------------------------------- 1 | {% set page ='admin_settings' %} 2 | {% extends "abstracts/admin.jinja" %} 3 | {% block title %}Maloja - Settings{% endblock %} 4 | 5 | 6 | {% block maincontent %} 7 | {{ settings.html() }} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /maloja/web/static/css/grisonsfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: 'Ubuntu'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Ubuntu Regular'), local('Ubuntu-Regular'), url('/static/ttf/Ubuntu-Regular.ttf') format('woff2'); 7 | } 8 | -------------------------------------------------------------------------------- /maloja/apis/_exceptions.py: -------------------------------------------------------------------------------- 1 | class BadAuthException(Exception): pass 2 | class InvalidAuthException(Exception): pass 3 | class InvalidMethodException(Exception): pass 4 | class InvalidSessionKey(Exception): pass 5 | class MalformedJSONException(Exception): pass 6 | 7 | -------------------------------------------------------------------------------- /container/root/etc/s6-overlay/s6-rc.d/svc-python/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | 3 | # used https://github.com/linuxserver/docker-wikijs/blob/master/root/etc/s6-overlay/s6-rc.d/svc-wikijs/run as a template 4 | 5 | echo -e "\nMaloja is starting!" 6 | exec \ 7 | s6-setuidgid abc /venv/bin/python -m maloja run -------------------------------------------------------------------------------- /maloja/data_files/config/rules/predefined/krateng_classical.tsv: -------------------------------------------------------------------------------- 1 | # NAME: Classical Music 2 | # DESC: Various standardizations for Classical Music 3 | 4 | replacetitle Tochter Zion freue dich Tochter Zion, freue dich 5 | replacetitle Zadok the Priest (Coronation Anthem No.1 HWV 258) Zadok the Priest 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bottle==0.13.* 2 | waitress==3.0.* 3 | doreah==2.0.* 4 | nimrodel==0.8.* 5 | setproctitle==1.3.* 6 | jinja2==3.1.* 7 | lru-dict==1.3.* 8 | psutil==5.9.* 9 | sqlalchemy==2.0 10 | python-datauri==3.0.* 11 | python-magic==0.4.* 12 | requests==2.32.* 13 | toml==0.10.* 14 | PyYAML==6.0.* 15 | 16 | -------------------------------------------------------------------------------- /dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | maloja: 3 | build: 4 | context: .. 5 | dockerfile: ./Containerfile 6 | ports: 7 | - "42010:42010" 8 | volumes: 9 | - "./testdata:/data" 10 | environment: 11 | - "MALOJA_DATA_DIRECTORY=/data" 12 | - "PUID=1000" 13 | - "PGID=1000" 14 | -------------------------------------------------------------------------------- /maloja/data_files/config/rules/predefined/krateng_dach.tsv: -------------------------------------------------------------------------------- 1 | # NAME: DACH Artists 2 | # DESC: Some fixes for artists in Switzerland, Imperial Switzerland and Big Switzerland 3 | 4 | replaceartist Erste Allgemeine Verunsicherung EAV 5 | replaceartist E.A.V. EAV 6 | replaceartist E.A.V. (Erste Allgemeine Verunsicherung) EAV 7 | -------------------------------------------------------------------------------- /dev/releases/2.5.yml: -------------------------------------------------------------------------------- 1 | minor_release_name: "Seungeun" 2 | 2.5.0: 3 | commit: "990131f546876d1461bac745e5cab3e60c78d038" 4 | 2.5.1: 5 | commit: "0918444ab6ff934ba83393e294a135b1fc25bd0c" 6 | 2.5.2: 7 | commit: "0918444ab6ff934ba83393e294a135b1fc25bd0c" 8 | 2.5.3: 9 | commit: "0918444ab6ff934ba83393e294a135b1fc25bd0c" 10 | -------------------------------------------------------------------------------- /maloja/web/jinja/icons/delete.jinja: -------------------------------------------------------------------------------- 1 |
| {{ thisrange.desc() }} | 15 |16 | {{ links.link_rank(filterkeys,specialkeys,thisrange,rank=t.rank) }} 17 | | 18 |19 | {% set prct = ((minrank+1-t.rank)*100/minrank if t.rank is not none else 0) %} 20 | {{ links.link_rank(filterkeys,specialkeys,thisrange,percent=prct,rank=t.rank) }} 21 | | 22 | 23 | 24 | 25 |
| 17 | 18 | | 19 |
20 | #1 Albums21 | {{ filterdesc.desc(filterkeys,limitkeys) }} 22 | 23 | 24 | {% include 'snippets/timeselection.jinja' %} 25 | |
26 |
| 17 | 18 | | 19 |
20 | #1 Tracks21 | {{ filterdesc.desc(filterkeys,limitkeys) }} 22 | 23 | 24 | {% include 'snippets/timeselection.jinja' %} 25 | |
26 |
| 17 | 18 | | 19 |
20 | #1 Artists21 | {{ filterdesc.desc(filterkeys,limitkeys) }} 22 | 23 | 24 | {% with artistchart = True %} 25 | {% include 'snippets/timeselection.jinja' %} 26 | {% endwith %} 27 | |
28 |
| 8 | 9 | | 10 |
11 | {{ error_desc | e }}12 | {{ error_full_desc | e }} 13 | 14 | |
15 |
| {{ thisrange.desc() }} | 16 | 17 | {% if album is none %} 18 |19 | | n/a | 20 |0 | 21 |22 | {% else %} 23 | {{ entityrow.row(album) }} 24 | | {{ links.link_scrobbles([{'album':album,'timerange':thisrange}],amount=e.scrobbles) }} | 25 |{{ links.link_scrobbles([{'album':album,'timerange':thisrange}],percent=e.scrobbles*100/maxbar) }} | 26 | {% endif %} 27 | 28 |
| {{ thisrange.desc() }} | 16 | 17 | {% if track is none %} 18 |19 | | n/a | 20 |0 | 21 |22 | {% else %} 23 | {{ entityrow.row(track) }} 24 | | {{ links.link_scrobbles([{'track':track,'timerange':thisrange}],amount=e.scrobbles) }} | 25 |{{ links.link_scrobbles([{'track':track,'timerange':thisrange}],percent=e.scrobbles*100/maxbar) }} | 26 | {% endif %} 27 | 28 |
| 13 | 14 | | 15 |
16 | Admin Panel17 |18 | 19 | {% for tab_url,tab_name in [ 20 | ['overview','Overview'], 21 | ['setup','Server Setup'], 22 | ['settings','Settings'], 23 | ['apikeys','API Keys'], 24 | ['manual','Manual Scrobbling'], 25 | ['albumless','Tracks without Albums'] 26 | 27 | ] %} 28 | {# ['import','Scrobble Import'], 29 | ['issues','Database Maintenance'] 30 | hide for now #} 31 | {% if page=='admin_' + tab_url %} 32 | {{ tab_name }} 33 | {% else %} 34 | {{ tab_name }} 35 | {% endif %} {%- if not loop.last %}|{% endif %} 36 | {% endfor %} 37 | 38 | 39 | 40 | 41 | 42 | |
43 |
| 18 | 19 | | 20 |
21 | {{ malojatime.delimit_desc_p(delimitkeys) }} Pulse22 | {% if filterkeys != {} %} 23 | View Rankings 24 | {% endif %} 25 |26 | {{ filterdesc.desc(filterkeys,limitkeys,prefix='of') }} 27 | 28 | {% include 'snippets/timeselection.jinja' %} 29 | 30 | |
31 |
| {{ thisrange.desc() }} | 14 |15 | {{ links.link_scrobbles([filterkeys,{'timerange':thisrange}],amount=t.scrobbles) }} 16 | | 17 |18 | {% if 'artist' in filterkeys and filterkeys.get('associated') %} 19 | {{ links.link_scrobbles([{'artist':filterkeys.artist,'associated':False,'timerange':thisrange}],percent=t.real_scrobbles*100/maxbar) }} 20 | {%- if t.real_scrobbles != t.scrobbles -%} 21 | {{ links.link_scrobbles([{'artist':filterkeys.artist,'associated':True,'timerange':thisrange}],percent=(t.scrobbles-t.real_scrobbles)*100/maxbar) }} 22 | {%- endif %} 23 | {% else %} 24 | {{ links.link_scrobbles([filterkeys,{'timerange':thisrange}],percent=t.scrobbles*100/maxbar) }} 25 | {% endif %} 26 | | 27 |
| 18 | 19 | | 20 |
21 | {{ malojatime.delimit_desc_p(delimitkeys) }} Performance22 | {% if limitkeys != {} %} 23 | View Pulse 24 | {% endif %} 25 |26 | {{ filterdesc.desc(filterkeys,limitkeys,prefix='of') }} 27 | 28 | {% with artistchart = (filterkeys.get('artist') is not none) %} 29 | {% include 'snippets/timeselection.jinja' %} 30 | {% endwith %} 31 | 32 | |
33 |
{{ info.topweeks }}
42 |
43 | {% endif %}
44 |
45 |
46 | {% if info.topweeks > 0 %}
47 |
48 | {% endif %}
49 |
50 | {%- endmacro %}
51 |
52 |
53 |
--------------------------------------------------------------------------------
/maloja/web/jinja/partials/scrobbles.jinja:
--------------------------------------------------------------------------------
1 | {% set scrobbles = dbc.get_scrobbles(filterkeys,limitkeys,amountkeys) %}
2 |
3 | {% set firstindex = amountkeys.page * amountkeys.perpage %}
4 | {% set lastindex = firstindex + amountkeys.perpage %}
5 |
6 | {% import 'snippets/entityrow.jinja' as entityrow %}
7 |
8 |
9 | | {{ malojatime.timestamp_desc(s["time"],short=shortTimeDesc) }} | 13 | {{ entityrow.row(s.track) }} 14 | {% if adminmode %} 15 | 16 |17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% include 'icons/reparse.jinja' %} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% include 'icons/delete.jinja' %} 37 | 38 | 39 | 40 | | 41 | {% endif %} 42 |
| {{ thisrange.desc() }} | 16 | 17 | {% if artist is none %} 18 |19 | | n/a | 20 |0 | 21 |22 | {% else %} 23 | {{ entityrow.row(artist,counting=([] if specialkeys.separate else e.associated_artists)) }} 24 | | {{ links.link_scrobbles([{'artist':artist,'associated':(not specialkeys.separate),'timerange':thisrange}],amount=e.scrobbles) }} | 25 |26 | {{ links.link_scrobbles([{'artist':e['artist'],'associated':False,'timerange':e.range}],percent=e['real_scrobbles']*100/maxbar) }} 27 | {%- if e['real_scrobbles'] != e['scrobbles'] -%} 28 | {{ links.link_scrobbles([{'artist':e['artist'],'associated':True,'timerange':e.range}],percent=(e['scrobbles']-e['real_scrobbles'])*100/maxbar) }} 29 | {%- endif %} 30 | | 31 | {% endif %} 32 | 33 |
|
12 | {% if adminmode %}
13 |
18 | {% else %}
19 |
20 |
21 | {% endif %}
22 | |
23 |
24 | {{ links.links(album.artists) }} 25 | {% if condensed %}{% endif %} 26 | {{ info.album.albumtitle | e }}27 | {%- if condensed -%}{% endif %} 28 | #{{ info.position }} 29 |30 | 31 | 32 | {{ info['scrobbles'] }} Scrobbles 33 | 34 | 35 | 36 | 37 | 38 | 39 | {{ awards.medals(info) }} 40 | {{ awards.topweeks(info) }} 41 | {{ awards.subcerts(info) }} 42 | 43 | |
44 |
| 27 | 28 | | 29 |
30 | Scrobbles31 | {{ filterdesc.desc(filterkeys,limitkeys) }} 32 | 33 | {{ totalscrobbles }} Scrobbles 34 |35 | {% with delimitkeys = {} %} 36 | {% include 'snippets/timeselection.jinja' %} 37 | {% endwith %} 38 | 39 | |
40 |
| 26 | 27 | | 28 |
29 | Track ChartsView #1 Tracks30 | {{ filterdesc.desc(filterkeys,limitkeys) }} 31 | 32 | {% with delimitkeys = {} %} 33 | {% include 'snippets/timeselection.jinja' %} 34 | {% endwith %} 35 | 36 | |
37 |
| 29 | 30 | | 31 |
32 | Artist ChartsView #1 Artists33 | {{ filterdesc.desc(filterkeys,limitkeys) }} 34 | 35 | {% with delimitkeys = {}, artistchart=True %} 36 | {% include 'snippets/timeselection.jinja' %} 37 | {% endwith %} 38 | 39 | |
40 |
| 26 | 27 | | 28 |
29 | Album ChartsView #1 Albums30 | {{ filterdesc.desc(filterkeys,limitkeys) }} 31 | 32 | {% with delimitkeys = {} %} 33 | {% include 'snippets/timeselection.jinja' %} 34 | {% endwith %} 35 | 36 | |
37 |
|
12 | {% if adminmode %}
13 |
18 | {% else %}
19 |
20 |
21 | {% endif %}
22 | |
23 |
24 | {{ links.links(track.artists) }} 25 | {% if condensed %}{% endif %} 26 | {{ info.track.title | e }}27 | {%- if condensed -%}{% endif %} 28 | #{{ info.position }} 29 |30 | {% if info.track.album %} 31 | from {{ links.link(info.track.album) }} 32 | {% endif %} 33 | 34 | 35 | {% if adminmode %}{% endif %} 36 | {{ info['scrobbles'] }} Scrobbles 37 | 38 | 39 | 40 | 41 | 42 | 43 | {{ awards.medals(info) }} 44 | {{ awards.topweeks(info) }} 45 | 46 | 47 | |
48 |
|
8 |
9 |
10 |
11 | |
12 |
13 | Maloja14 |15 | Version {{ pkginfo.VERSION }} 16 | {# {% if adminmode %} 17 | Python {{ platform.sys.version }} 18 | {{ platform.system() }} {{ platform.release() }} ({{ platform.machine() }}) 19 | 20 | {% set pid = psutil.os.getpid() %} 21 | {% set proc = psutil.Process(pid) %} 22 | CPU: 23 | {{ proc.cpu_percent() | int }}% Maloja, 24 | {{ (psutil.getloadavg()[2]/psutil.os.cpu_count() * 100) | int }}% System 25 | 26 | RAM: 27 | {{ (proc.memory_info().rss / (1024*1024)) | int }}MB Maloja ({{ proc.memory_percent() | int }}%), 28 | {{ (psutil.virtual_memory().used / (1024*1024)) | int }}MB System ({{ psutil.virtual_memory().percent | int }}%) 29 | {% endif %} #} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | |
38 |
|   |
| 17 | 18 | 19 | 20 | |
|
22 | {{ links.links(entry.album.artists) }} 23 | {{ links.link(entry.album) }} 24 | |
| Appears on |
| 42 | 43 | 44 | 45 | |
|
47 | {{ links.links(entry.album.artists) }} 48 | {{ links.link(entry.album) }} 49 | |
|
23 | {% if adminmode %}
24 |
29 | {% else %}
30 |
31 |
32 | {% endif %}
33 | |
34 |
35 | {% if condensed %}{% endif %}
36 | {{ info.artist | e }}37 | {%- if condensed -%}{% endif %} 38 | {% if competes and info['scrobbles']>0 %}#{{ info.position }}{% endif %} 39 |40 | {% if competes and included and (not condensed) %} 41 | associated: {{ links.links(included) }} 42 | {% elif not competes %} 43 | Competing under {{ links.link(credited) }} (#{{ info.position }}) 44 | {% endif %} 45 | 46 | 47 | {{ info['scrobbles'] }} Scrobbles 48 | 49 | 50 | 51 | 52 | 53 | {% if competes %} 54 | {{ awards.medals(info) }} 55 | {{ awards.topweeks(info) }} 56 | {% endif %} 57 | {{ awards.subcerts(artist) }} 58 | 59 | 60 | |
61 |
{{ info.topweeks }}
44 | {%- endif %}
45 |
46 |
47 | {% if info.topweeks > 0 %}
48 |
49 | {% endif %}
50 |
51 | {%- endmacro %}
52 |
53 |
54 |
55 | {% macro subcerts(album) %}
56 |
57 |
58 |
59 | {% set charts = dbc.get_charts_tracks({'album':album.album,'timerange':malojatime.alltime()}) %}
60 | {% for e in charts -%}
61 | {%- if e.scrobbles >= settings.scrobbles_gold -%}{% set cert = 'gold' %}{%- endif -%}
62 | {%- if e.scrobbles >= settings.scrobbles_platinum -%}{% set cert = 'platinum' %}{%- endif -%}
63 | {%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%}
64 |
65 | {%- if cert -%}
66 |
67 | {% include 'icons/cert_track.jinja' %}
68 |
69 | {%- endif %}
70 |
71 | {%- endfor %}
72 |
73 | {%- endmacro %}
--------------------------------------------------------------------------------
/dev/releases/3.0.yml:
--------------------------------------------------------------------------------
1 | minor_release_name: "Yeonhee"
2 | 3.0.0:
3 | commit: "f31c95228eb2dc01e661be928ffd881c063377da"
4 | notes:
5 | - "[Architecture] Switched to SQLite for main database"
6 | - "[Architecture] Switched to SQLite for artwork cache"
7 | - "[Feature] Added scrobble deletion from web interface"
8 | 3.0.1:
9 | commit: "700b81217cb585df631d6f069243c56074cd1b71"
10 | notes:
11 | - "[Bugfix] Fixed upgrading imported scrobbles"
12 | 3.0.2:
13 | commit: "4a8221f7a08f679b21c1fb619f03e5f922a1dc2b"
14 | notes:
15 | - "[Logging] Cleaned up output for waitress warnings"
16 | - "[Bugfix] Fixed exception in native API"
17 | 3.0.3:
18 | commit: "1d9247fc724d7410b6e50d2cbfaa8f375d5e70af"
19 | notes:
20 | - "[Documentation] Added descriptions for native API endpoints"
21 | - "[Code Health] Made arguments for native API scrobbling explicit"
22 | - "[Bugfix] Fixed faulty entity type recognition for artists including the string 'artists'"
23 | - "[Bugfix] Fixed OS return codes"
24 | 3.0.4:
25 | commit: "206ebd58ea204e0008f2c9bf72d76dd9918fec53"
26 | notes:
27 | - "[Feature] Enabled dual stack for web server"
28 | - "[Feature] Added better feedback to native API endpoints"
29 | - "[Bugfix] Fixed native API receiving superfluous keywords"
30 | - "[Bugfix] Fixed crash when importing scrobbles with artists with similar names"
31 | 3.0.5:
32 | commit: "fe21894c5ecf3a53c9c5c00453abfc7f41c6a83e"
33 | notes:
34 | - "[Feature] Added notification system for web interface"
35 | - "[Bugfix] Fixed crash when encountering error in Lastfm import"
36 | 3.0.6:
37 | commit: "b3d4cb7a153845d1f5a5eef67a6508754e338f2f"
38 | notes:
39 | - "[Performance] Implemented search in database"
40 | - "[Bugfix] Better parsing of featuring artists"
41 | - "[Bugfix] Fixed buffered output in Docker"
42 | - "[Bugfix] Fixed importing a Spotify file without path"
43 | - "[Bugfix] No longer releasing database lock during scrobble creation"
44 | - "[Distribution] Experimental arm64 image"
45 | 3.0.7:
46 | commit: "62abc319303a6cb6463f7c27b6ef09b76fc67f86"
47 | notes:
48 | - "[Bugix] Improved signal handling"
49 | - "[Bugix] Fixed constant re-caching of all-time stats, significantly increasing page load speed"
50 | - "[Logging] Disabled cache information when cache is not used"
51 | - "[Distribution] Experimental arm/v7 image"
52 |
--------------------------------------------------------------------------------
/Containerfile:
--------------------------------------------------------------------------------
1 | FROM lsiobase/alpine:3.21 AS base
2 |
3 | WORKDIR /usr/src/app
4 |
5 |
6 |
7 | COPY --chown=abc:abc ./requirements.txt ./requirements.txt
8 |
9 | # based on https://github.com/linuxserver/docker-pyload-ng/blob/main/Dockerfile
10 | # everything but the app installation is run in one command so we can purge
11 | # all build dependencies and cache in the same layer
12 | # it may be possible to decrease image size slightly by using build stage and
13 | # copying all site-packages to runtime stage but the image is already pretty small
14 | RUN \
15 | echo "" && \
16 | echo "**** install build packages ****" && \
17 | apk add --no-cache --virtual=build-deps \
18 | gcc \
19 | g++ \
20 | python3-dev \
21 | libxml2-dev \
22 | libxslt-dev \
23 | libffi-dev \
24 | libc-dev \
25 | py3-pip \
26 | linux-headers && \
27 | echo "" && \
28 | echo "**** install runtime packages ****" && \
29 | apk add --no-cache \
30 | python3 \
31 | py3-lxml \
32 | libmagic \
33 | tzdata && \
34 | echo "" && \
35 | echo "**** install pip dependencies ****" && \
36 | python3 -m venv /venv && \
37 | . /venv/bin/activate && \
38 | python3 -m ensurepip && \
39 | pip install -U --no-cache-dir \
40 | pip \
41 | wheel && \
42 | echo "" && \
43 | echo "**** install maloja requirements ****" && \
44 | pip install --no-cache-dir -r requirements.txt && \
45 | echo "" && \
46 | echo "**** cleanup ****" && \
47 | apk del --purge \
48 | build-deps && \
49 | rm -rf \
50 | /tmp/* \
51 | ${HOME}/.cache
52 |
53 | # actual installation in extra layer so we can cache the stuff above
54 |
55 | COPY --chown=abc:abc . .
56 |
57 | RUN \
58 | echo "" && \
59 | echo "**** install maloja ****" && \
60 | apk add --no-cache --virtual=install-deps \
61 | py3-pip && \
62 | python3 -m venv /venv && \
63 | . /venv/bin/activate && \
64 | pip3 install /usr/src/app && \
65 | apk del --purge \
66 | install-deps && \
67 | rm -rf \
68 | /tmp/* \
69 | ${HOME}/.cache
70 |
71 |
72 |
73 | COPY container/root/ /
74 |
75 | ENV \
76 | # Docker-specific configuration
77 | MALOJA_SKIP_SETUP=yes \
78 | MALOJA_CONTAINER=yes \
79 | PYTHONUNBUFFERED=1 \
80 | # Prevents breaking change for previous container that ran maloja as root
81 | # On linux hosts (non-podman rootless) these variables should be set to the
82 | # host user that should own the host folder bound to MALOJA_DATA_DIRECTORY
83 | PUID=0 \
84 | PGID=0
85 |
86 | EXPOSE 42010
87 |
--------------------------------------------------------------------------------
/maloja/web/jinja/partials/charts_tracks.jinja:
--------------------------------------------------------------------------------
1 | {% import 'snippets/links.jinja' as links %}
2 | {% import 'snippets/entityrow.jinja' as entityrow %}
3 |
4 | {% if charts is undefined %}
5 | {% set charts = dbc.get_charts_tracks(filterkeys,limitkeys) %}
6 | {% endif %}
7 | {% if compare %}
8 | {% if compare is true %}
9 | {% set compare = limitkeys.timerange.next(step=-1) %}
10 | {% if compare is none %}{% set compare = False %}{% endif %}
11 | {% endif %}
12 | {% if compare %}
13 | {% set prevtracks = dbc.get_charts_tracks(filterkeys,{'timerange':compare}) %}
14 |
15 | {% set lastranks = {} %}
16 | {% for t in prevtracks %}
17 | {% if lastranks.update({"|".join(t.track.artists)+"||"+t.track.title:t.rank}) %}{% endif %}
18 | {% endfor %}
19 |
20 | {% for t in charts %}
21 | {% if "|".join(t.track.artists)+"||"+t.track.title in lastranks %}
22 | {% if t.update({'last_rank':lastranks["|".join(t.track.artists)+"||"+t.track.title]}) %}{% endif %}
23 | {% endif %}
24 | {% endfor %}
25 | {% endif %}
26 | {% endif %}
27 |
28 | {% set firstindex = amountkeys.page * amountkeys.perpage %}
29 | {% set lastindex = firstindex + amountkeys.perpage %}
30 |
31 |
32 | {% set maxbar = charts[0]['scrobbles'] if charts != [] else 0 %}
33 | | {%if loop.changed(e.scrobbles) %}#{{ e.rank }}{% endif %} | 39 | 40 | {% if compare %} 41 | {% if e.last_rank is undefined %}🆕 | 42 | {% elif e.last_rank < e.rank %}↘ | 43 | {% elif e.last_rank > e.rank %}↗ | 44 | {% elif e.last_rank == e.rank %}➡ | 45 | {% endif %} 46 | {% endif %} 47 | 48 | 49 | {{ entityrow.row(e['track'],adminmode=adminmode) }} 50 | 51 | 52 |{{ links.link_scrobbles([{'track':e.track,'timerange':limitkeys.timerange}],amount=e['scrobbles']) }} | 53 |{{ links.link_scrobbles([{'track':e.track,'timerange':limitkeys.timerange}],percent=e['scrobbles']*100/maxbar) }} | 54 |
| {%if loop.changed(e.scrobbles) %}#{{ e.rank }}{% endif %} | 39 | 40 | {% if compare %} 41 | {% if e.last_rank is undefined %}🆕 | 42 | {% elif e.last_rank < e.rank %}↘ | 43 | {% elif e.last_rank > e.rank %}↗ | 44 | {% elif e.last_rank == e.rank %}➡ | 45 | {% endif %} 46 | {% endif %} 47 | 48 | 49 | {{ entityrow.row(e['album'],adminmode=adminmode) }} 50 | 51 | 52 |{{ links.link_scrobbles([{'album':e.album,'timerange':limitkeys.timerange}],amount=e['scrobbles']) }} | 53 |{{ links.link_scrobbles([{'album':e.album,'timerange':limitkeys.timerange}],percent=e['scrobbles']*100/maxbar) }} | 54 |
| 25 | Artists: 26 | | 27 | 28 | | 29 |
| 32 | Title: 33 | | 34 | 35 | | 36 |
| 39 | Album artists (Optional): 40 | | 41 | 42 | | 43 |
| 46 | Album (Optional): 47 | | 48 | 49 | | 50 |
| 53 | 54 | Custom Time: 55 | | 56 |57 | 58 | | 59 |
Maloja can identify possible problems with consistency or redundancy in your library. After making any changes, you should rebuild your library.
42 | 43 || The current database wasn't built with all current rules in effect. Any problem below might be a false alarm and fixing it could create redundant rules. | 48 |49 | | |
| {{ links.link(issue[0]) }} is a possible duplicate of {{ links.link(issue[1]) }} | 55 |56 | 57 | | 58 |59 | 60 | | 61 |
| {{ links.link(issue[0]) }} sounds like the combination of {{ issue[1].__len__() }} artists: 67 | {{ issue[1]|join(", ") }} 68 | | 69 |70 | 71 | | 72 ||
| Is '{{ issue[0] }}' in '{{ links.link(issue[1]) }}' an artist? | 78 |79 | 80 | | 81 |
| {%if loop.changed(e.scrobbles) %}#{{ e.rank }}{% endif %} | 42 | 43 | {% if compare %} 44 | {% if e.last_rank is undefined %}🆕 | 45 | {% elif e.last_rank < e.rank %}↘ | 46 | {% elif e.last_rank > e.rank %}↗ | 47 | {% elif e.last_rank == e.rank %}➡ | 48 | {% endif %} 49 | {% endif %} 50 | 51 | 52 | {{ entityrow.row(e['artist'],adminmode=adminmode,counting=([] if specialkeys.separate else e.associated_artists)) }} 53 | 54 | 55 |{{ links.link_scrobbles([{'artist':e['artist'],'associated':(not specialkeys.separate),'timerange':limitkeys.timerange}],amount=e['scrobbles']) }} | 56 |57 | {{ links.link_scrobbles([{'artist':e['artist'],'associated':False,'timerange':limitkeys.timerange}],percent=e['real_scrobbles']*100/maxbar) }} 58 | {%- if e['real_scrobbles'] != e['scrobbles'] -%} 59 | {{ links.link_scrobbles([{'artist':e['artist'],'associated':True,'timerange':limitkeys.timerange}],percent=(e['scrobbles']-e['real_scrobbles'])*100/maxbar) }} 60 | {%- endif %} 61 | | 62 |
{{ info.topweeks }}
46 | {%- endif %}
47 |
48 |
49 | {% if info.topweeks > 0 %}
50 |
51 | {% endif %}
52 |
53 | {%- endmacro %}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {% macro subcerts(artist) %}
64 |
65 |
66 |
67 |
68 | {% set albumcharts = dbc.get_charts_albums({'artist':artist,'timerange':malojatime.alltime(),'resolve_ids':True,'only_own_albums':True}) %}
69 | {% for e in albumcharts -%}
70 | {%- if e.scrobbles >= settings.scrobbles_gold_album -%}{% set cert = 'gold' %}{%- endif -%}
71 | {%- if e.scrobbles >= settings.scrobbles_platinum_album -%}{% set cert = 'platinum' %}{%- endif -%}
72 | {%- if e.scrobbles >= settings.scrobbles_diamond_album -%}{% set cert = 'diamond' %}{%- endif -%}
73 |
74 | {%- if cert -%}
75 |
76 | {% include 'icons/cert_album.jinja' %}
77 |
78 | {%- endif %}
79 |
80 | {%- endfor %}
81 |
82 |
83 | {% set charts = dbc.get_charts_tracks({'artist':artist,'timerange':malojatime.alltime()}) %}
84 | {% for e in charts -%}
85 | {%- if e.scrobbles >= settings.scrobbles_gold -%}{% set cert = 'gold' %}{%- endif -%}
86 | {%- if e.scrobbles >= settings.scrobbles_platinum -%}{% set cert = 'platinum' %}{%- endif -%}
87 | {%- if e.scrobbles >= settings.scrobbles_diamond -%}{% set cert = 'diamond' %}{%- endif -%}
88 |
89 | {%- if cert -%}
90 |
91 | {% include 'icons/cert_track.jinja' %}
92 |
93 | {%- endif %}
94 |
95 | {%- endfor %}
96 |
97 |
98 |
99 | {%- endmacro %}
100 |
--------------------------------------------------------------------------------
/auxiliary/chromium_scrobbler/maloja-scrobbler/sitescript.js:
--------------------------------------------------------------------------------
1 | function getxpath(path,type) {
2 | result = document.evaluate(path, this, null, type, null);
3 |
4 | if (type == XPathResult.FIRST_ORDERED_NODE_TYPE) {
5 | return result.singleNodeValue;
6 | }
7 | else if (type == XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
8 | resultarray = [];
9 | while(node = result.iterateNext()) {
10 | resultarray.push(node);
11 | }
12 |
13 | return resultarray;
14 | }
15 | else if (type == XPathResult.STRING_TYPE) {
16 | return result.stringValue;
17 | }
18 |
19 | // if (path.split("/").slice(-1)[0].startsWith("text()") || path.split("/").slice(-1)[0].startsWith("@")) {
20 | // result = document.evaluate(path, this, null, XPathResult.STRING_TYPE, null);
21 | // return result.stringValue;
22 | // }
23 | // else {
24 | // result = document.evaluate(path, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
25 | // return result.singleNodeValue;
26 | // }
27 |
28 |
29 | }
30 | Node.prototype.xpath = getxpath;
31 |
32 |
33 | bar = document.xpath(maloja_scrobbler_selector_playbar, XPathResult.FIRST_ORDERED_NODE_TYPE);
34 | if (bar == null) {
35 | console.log("[Maloja Scrobbler] Nothing playing right now!");
36 | chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:"",title:""});
37 | }
38 | else {
39 | metadata = bar.xpath(maloja_scrobbler_selector_metadata, XPathResult.FIRST_ORDERED_NODE_TYPE);
40 | duration = bar.xpath(maloja_scrobbler_selector_duration, XPathResult.STRING_TYPE);
41 | duration = duration + '';
42 |
43 |
44 | title = metadata.xpath(maloja_scrobbler_selector_title, XPathResult.STRING_TYPE);
45 | if (typeof maloja_scrobbler_selector_artists !== "undefined") {
46 | artistnodes = metadata.xpath(maloja_scrobbler_selector_artists, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
47 | artists = artistnodes.map(x => x.xpath(maloja_scrobbler_selector_artist, XPathResult.STRING_TYPE));
48 | artist = artists.join(";");
49 | }
50 | else {
51 | artist = metadata.xpath(maloja_scrobbler_selector_artist, XPathResult.STRING_TYPE);
52 | }
53 |
54 |
55 | if (typeof duration_needs_split !== "undefined" && duration_needs_split) {
56 | duration = duration.split("/").slice(-1)[0].trim();
57 | }
58 |
59 | if (duration.split(":").length == 2) {
60 | durationSeconds = parseInt(duration.split(":")[0]) * 60 + parseInt(duration.split(":")[1]);
61 | }
62 | else {
63 | durationSeconds = parseInt(duration.split(":")[0]) * 60 * 60 + parseInt(duration.split(":")[1]) * 60 + parseInt(duration.split(":")[2]);
64 | }
65 |
66 |
67 | control = bar.xpath(maloja_scrobbler_selector_control, XPathResult.STRING_TYPE);
68 | try {
69 | label_playing = maloja_scrobbler_label_playing
70 | }
71 | catch {
72 | label_playing = "Pause"
73 | }
74 | try {
75 | label_paused = maloja_scrobbler_label_paused
76 | }
77 | catch {
78 | label_paused = "Play"
79 | }
80 | if (control == label_paused) {
81 | console.log("[Maloja Scrobbler] Not playing right now");
82 | chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:artist,title:title});
83 | //stopPlayback()
84 | }
85 | else if (control == label_playing) {
86 | console.log("[Maloja Scrobbler] Playing " + artist + " - " + title + " (" + durationSeconds + " sec)");
87 | chrome.runtime.sendMessage({type:"startPlayback",time:Math.floor(Date.now()),artist:artist,title:title,duration:durationSeconds});
88 | //startPlayback(artist,title,durationSeconds)
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/maloja/thirdparty/lastfm.py:
--------------------------------------------------------------------------------
1 | from . import MetadataInterface, ProxyScrobbleInterface, utf
2 | import hashlib
3 | import requests
4 | import xml.etree.ElementTree as ElementTree
5 | from doreah.logging import log
6 |
7 | class LastFM(MetadataInterface, ProxyScrobbleInterface):
8 | name = "LastFM"
9 | identifier = "lastfm"
10 |
11 | settings = {
12 | "apikey":"LASTFM_API_KEY",
13 | "sk":"LASTFM_API_SK",
14 | "secret":"LASTFM_API_SECRET",
15 | "username":"LASTFM_USERNAME",
16 | "password":"LASTFM_PASSWORD"
17 | }
18 |
19 | proxyscrobble = {
20 | "scrobbleurl": "http://ws.audioscrobbler.com/2.0/",
21 | "response_type":"xml",
22 | "required_settings": ["apikey","sk","secret"],
23 | "activated_setting": "SCROBBLE_LASTFM"
24 | }
25 | metadata = {
26 | #"artisturl": "https://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist={artist}&api_key={apikey}&format=json"
27 | "trackurl": "https://ws.audioscrobbler.com/2.0/?method=track.getinfo&track={title}&artist={artist}&api_key={apikey}&format=json",
28 | "albumurl": "https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={apikey}&artist={artist}&album={title}&format=json",
29 | "response_type":"json",
30 | "response_parse_tree_track": ["track","album","image",-1,"#text"],
31 | # technically just the album artwork, but we use it for now
32 | #"response_parse_tree_artist": ["artist","image",-1,"#text"],
33 | "response_parse_tree_album": ["album","image",-1,"#text"],
34 | "required_settings": ["apikey"],
35 | "enabled_entity_types": ["track","album"]
36 | }
37 |
38 | def get_image_artist(self,artist):
39 | return None
40 | # lastfm still provides that endpoint with data,
41 | # but doesn't provide actual images
42 |
43 |
44 | def proxyscrobble_parse_response(self,data):
45 | return data.attrib.get("status") == "ok" and data.find("scrobbles").attrib.get("ignored") == "0"
46 |
47 | def proxyscrobble_postdata(self,artists,title,timestamp):
48 | return self.query_compose({
49 | "method":"track.scrobble",
50 | "artist[0]":", ".join(artists),
51 | "track[0]":title,
52 | "timestamp":timestamp,
53 | "api_key":self.settings["apikey"],
54 | "sk":self.settings["sk"]
55 | })
56 |
57 | def authorize(self):
58 | if all(self.settings[key] not in [None,"ASK",False] for key in ["username","password","apikey","secret"]):
59 | try:
60 | response = requests.post(
61 | url=self.proxyscrobble['scrobbleurl'],
62 | params=self.query_compose({
63 | "method":"auth.getMobileSession",
64 | "username":self.settings["username"],
65 | "password":self.settings["password"],
66 | "api_key":self.settings["apikey"]
67 | }),
68 | headers={
69 | "User-Agent":self.useragent
70 | }
71 | )
72 |
73 | data = ElementTree.fromstring(response.text)
74 | self.settings["sk"] = data.find("session").findtext("key")
75 | except Exception as e:
76 | log("Error while authenticating with LastFM: " + repr(e))
77 |
78 |
79 | # creates signature and returns full query
80 | def query_compose(self,parameters):
81 | m = hashlib.md5()
82 | keys = sorted(str(k) for k in parameters)
83 | m.update(utf("".join(str(k) + str(parameters[k]) for k in keys)))
84 | m.update(utf(self.settings["secret"]))
85 | sig = m.hexdigest()
86 | return {**parameters,"api_sig":sig}
87 |
88 | def handle_json_result_error(self,result):
89 | if "track" in result and not result.get("track").get('album',{}):
90 | return True
91 |
92 | if "error" in result and result.get("error") == 6:
93 | return True
94 |
--------------------------------------------------------------------------------
/maloja/web/jinja/snippets/timeselection.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% set allkeys = [filterkeys,limitkeys,delimitkeys,amountkeys,specialkeys] | combine_dicts %}
5 |
6 |
7 |
8 | {% if limitkeys != {} %}
9 |
10 |