diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..94f970c --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,19 @@ +name: CI +on: [push, pull_request] + +jobs: + Test: + runs-on: cimaster-latest + steps: + - name: ๐Ÿ“ฆ Checkout repository + uses: actions/checkout@v3 + - name: ๐Ÿฆ€ Setup Rust cache + uses: https://github.com/Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + + - name: ๐Ÿ“Ž Clippy + run: cargo clippy --all -- -D warnings + + - name: ๐Ÿงช Test + run: cargo test --workspace diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..fa7432d --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,38 @@ +name: Release +on: + push: + tags: + - "*/v*.*.*" + +jobs: + Release: + runs-on: cimaster-latest + steps: + - name: ๐Ÿ“ฆ Checkout repository + uses: actions/checkout@v3 + + - name: Get variables + run: | + git fetch --tags --force #the checkout action does not load the tag message + + echo "CRATE=$(echo '${{ github.ref_name }}' | awk 'BEGIN{RS="/"} NR==1{print}')" >> "$GITHUB_ENV" + echo "CRATE_VERSION=$(echo '${{ github.ref_name }}' | awk 'BEGIN{RS="/"} NR==2{print}')" >> "$GITHUB_ENV" + { + echo 'CHANGELOG<> "$GITHUB_ENV" + + - name: ๐Ÿ“ค Publish crate on code.thetadev.de + run: | + mkdir -p ~/.cargo + printf '[registries.thetadev]\nindex = "https://code.thetadev.de/ThetaDev/_cargo-index.git"\ntoken = "Bearer ${{ secrets.TOKEN_GITEA }}"\n' >> ~/.cargo/config.toml + sed -i "s/^musixmatch-.*=\s*{/\0 registry = \"thetadev\",/g" Cargo.toml + cargo publish --registry thetadev --package "${{ env.CRATE }}" --allow-dirty + git restore Cargo.toml + + - name: ๐ŸŽ‰ Publish release + uses: https://gitea.com/actions/release-action@main + with: + title: "${{ env.CRATE }} ${{ env.CRATE_VERSION }}" + body: "${{ env.CHANGELOG }}" diff --git a/.woodpecker.yml b/.woodpecker.yml deleted file mode 100644 index a091c3d..0000000 --- a/.woodpecker.yml +++ /dev/null @@ -1,11 +0,0 @@ -steps: - test: - image: rust:latest - secrets: - - musixmatch_email - - musixmatch_password - commands: - - rustup component add rustfmt clippy - - cargo fmt --all --check - - cargo clippy --all -- -D warnings - - cargo test --workspace diff --git a/Cargo.toml b/Cargo.toml index 2323866..8c998bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ include = ["/src", "README.md", "LICENSE"] [workspace] members = [".", "cli"] +[workspace.dependencies] +musixmatch-inofficial = { version = "0.1.0", path = ".", default-features = false } + [features] default = ["default-tls"] @@ -24,7 +27,7 @@ rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"] rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] [dependencies] -reqwest = { version = "0.11.11", default-features = false, features = [ +reqwest = { version = "0.12.0", default-features = false, features = [ "json", "gzip", ] } @@ -42,7 +45,7 @@ time = { version = "0.3.15", features = [ hmac = "0.12.1" sha1 = "0.10.5" rand = "0.8.5" -base64 = "0.21.0" +base64 = "0.22.0" [dev-dependencies] ctor = "0.2.0" diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..e3b19b5 --- /dev/null +++ b/Justfile @@ -0,0 +1,44 @@ +test: + cargo test + +release crate="musixmatch-inofficial": + #!/usr/bin/env bash + set -e + + CRATE="{{crate}}" + INCLUDES='--include-path README.md --include-path LICENSE --include-path Cargo.toml' + CHANGELOG="CHANGELOG.md" + + if [ "$CRATE" = "musixmatch-inofficial" ]; then + INCLUDES="$INCLUDES --include-path src/** --include-path tests/** --include-path testfiles/**" + else + if [ ! -d "$CRATE" ]; then + echo "$CRATE does not exist."; exit 1 + fi + INCLUDES="$INCLUDES --include-path $CRATE/**" + CHANGELOG="$CRATE/$CHANGELOG" + CRATE="musixmatch-$CRATE" # Add crate name prefix + fi + + VERSION=$(cargo pkgid --package "$CRATE" | tr '#@' '\n' | tail -n 1) + TAG="${CRATE}/v${VERSION}" + echo "Releasing $TAG:" + + if git rev-parse "$TAG" >/dev/null 2>&1; then echo "version tag $TAG already exists"; exit 1; fi + + CLIFF_ARGS="--tag v${VERSION} --tag-pattern ${CRATE}/* --unreleased $INCLUDES" + echo "git-cliff $CLIFF_ARGS" + if [ -f "$CHANGELOG" ]; then + git-cliff $CLIFF_ARGS --prepend "$CHANGELOG" + else + git-cliff $CLIFF_ARGS --output "$CHANGELOG" + fi + + editor "$CHANGELOG" + + git add "$CHANGELOG" + git commit -m "chore(release): release $CRATE v$VERSION" + + awk 'BEGIN{RS="(^|\n)## [^\n]+\n*"} NR==2 { print }' "$CHANGELOG" | git tag -as -F - --cleanup whitespace "$TAG" + + echo "๐Ÿš€ Run 'git push origin $TAG' to publish" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index be21b09..4279f8c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -18,7 +18,7 @@ rustls-tls-webpki-roots = ["musixmatch-inofficial/rustls-tls-webpki-roots"] rustls-tls-native-roots = ["musixmatch-inofficial/rustls-tls-native-roots"] [dependencies] -musixmatch-inofficial = { path = "../" } +musixmatch-inofficial.workspace = true tokio = { version = "1.20.0", features = ["macros", "rt-multi-thread"] } id3 = "1.3.0" mp3-duration = "0.1.10" diff --git a/cli/src/main.rs b/cli/src/main.rs index b80813f..c0fae2c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -28,6 +28,20 @@ enum Commands { #[command(subcommand)] command: FileCommands, }, + #[group(required = true)] + Search { + /// Track name + #[clap(short, long)] + name: Option, + /// Artist + #[clap(short, long)] + artist: Option, + /// Lyrics + #[clap(short, long)] + lyrics: Option, + /// Search query + query: Option>, + }, } #[derive(Subcommand)] @@ -319,6 +333,42 @@ async fn run(cli: Cli) -> Result<()> { println!("{}", subtitles.subtitle_body); } }, + Commands::Search { + query, + name, + artist, + lyrics, + } => { + let mut sb = mxm + .track_search() + .s_track_rating(musixmatch_inofficial::models::SortOrder::Desc); + let querystr; + if let Some(q) = &query { + querystr = q.join(" "); + sb = sb.q(&querystr); + } + if let Some(n) = &name { + sb = sb.q_track(n); + } + if let Some(a) = &artist { + sb = sb.q_artist(a); + } + if let Some(l) = &lyrics { + sb = sb.q_lyrics(l); + } + + let tracks = sb.send(20, 0).await?; + for t in tracks { + println!( + "{} - {} ({}) ISRC'{}' ", + t.track_name, + t.artist_name, + t.first_release_date.map(|d| d.year()).unwrap_or_default(), + t.track_isrc.unwrap_or_default(), + t.commontrack_vanity_id + ); + } + } }; Ok(()) } diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..32b679a --- /dev/null +++ b/cliff.toml @@ -0,0 +1,99 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% set repo_url = "https://code.thetadev.de/ThetaDev/rustypipe" %}\ +{% if version %}\ + {%if previous.version %}\ + ## [{{ version }}]({{ repo_url }}/compare/{{ previous.version }}..{{ version }})\ + {% else %}\ + ## {{ version }}\ + {% endif %} - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% if previous.version %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }} - \ + ([{{ commit.id | truncate(length=7, end="") }}]({{ repo_url }}/commit/{{ commit.id }}))\ + {% endfor %} +{% endfor %}\ +{% else %} +Initial release +{% endif %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing s +trim = true +# postprocessors +postprocessors = [ + # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL +] + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # Replace issue numbers + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit with https://github.com/crate-ci/typos + # If the spelling is incorrect, it will be automatically fixed. + #{ pattern = '.*', replace_command = 'typos --write-changes -' }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "๐Ÿš€ Features" }, + { message = "^fix", group = "๐Ÿ› Bug Fixes" }, + { message = "^doc", group = "๐Ÿ“š Documentation" }, + { message = "^perf", group = "โšก Performance" }, + { message = "^refactor", group = "๐Ÿšœ Refactor" }, + { message = "^style", group = "๐ŸŽจ Styling" }, + { message = "^test", group = "๐Ÿงช Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore", group = "โš™๏ธ Miscellaneous Tasks" }, + { message = "^ci", skip = true }, + { body = ".*security", group = "๐Ÿ›ก๏ธ Security" }, + { message = "^revert", group = "โ—€๏ธ Revert" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# regex for matching git tags +# tag_pattern = "v[0-9].*" +# regex for skipping tags +# skip_tags = "" +# regex for ignoring tags +# ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" +# limit the number of commits included in the changelog. +# limit_commits = 42 diff --git a/tests/tests.rs b/tests/tests.rs index 829e3d1..203100c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -89,18 +89,9 @@ mod album { ); assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single"); assert!(album.updated_time > datetime!(2022-6-3 0:00 UTC)); - assert_eq!( - album.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045.jpg" - ); - assert_eq!( - album.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_350_350.jpg" - ); - assert_eq!( - album.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_500_500.jpg" - ); + assert_imgurl(&album.album_coverart_100x100, "/26544045.jpg"); + assert_imgurl(&album.album_coverart_350x350, "/26544045_350_350.jpg"); + assert_imgurl(&album.album_coverart_500x500, "/26544045_500_500.jpg"); } #[tokio::test] @@ -333,18 +324,9 @@ mod track { "650e7db6-b795-4eb5-a702-5ea2fc46c848" ); assert_eq!(track.artist_name, "Lady Gaga"); - assert_eq!( - track.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" - ); - assert_eq!( - track.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" - ); - assert_eq!( - track.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" - ); + assert_imgurl(&track.album_coverart_100x100, "/26319636.jpg"); + assert_imgurl(&track.album_coverart_350x350, "/26319636_350_350.jpg"); + assert_imgurl(&track.album_coverart_500x500, "/26319636_500_500.jpg"); assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); let first_release = track.first_release_date.unwrap(); assert_eq!(first_release.date(), date!(2008 - 1 - 1)); @@ -414,18 +396,9 @@ mod track { assert_eq!(track.album_name, "Black Mamba"); assert_eq!(track.artist_id, 46970441); assert_eq!(track.artist_name, "aespa"); - assert_eq!( - track.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg" - ); - assert_eq!( - track.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg" - ); - assert_eq!( - track.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg" - ); + assert_imgurl(&track.album_coverart_100x100, "/52156772.jpg"); + assert_imgurl(&track.album_coverart_350x350, "/52156772_350_350.jpg"); + assert_imgurl(&track.album_coverart_500x500, "/52156772_500_500.jpg"); assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba"); let release_date = track.first_release_date.unwrap(); @@ -491,18 +464,9 @@ mod track { "650e7db6-b795-4eb5-a702-5ea2fc46c848" ); assert_eq!(track.artist_name, "Lady Gaga"); - assert_eq!( - track.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" - ); - assert_eq!( - track.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" - ); - assert_eq!( - track.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" - ); + assert_imgurl(&track.album_coverart_100x100, "/26319636.jpg"); + assert_imgurl(&track.album_coverart_350x350, "/26319636_350_350.jpg"); + assert_imgurl(&track.album_coverart_500x500, "/26319636_500_500.jpg"); assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); let first_release = track.first_release_date.unwrap(); assert_eq!(first_release.date(), date!(2008 - 1 - 1)); @@ -702,16 +666,20 @@ mod lyrics { // dbg!(&lyrics); - assert_eq!(lyrics.lyrics_id, 25947036); + assert_eq!(lyrics.lyrics_id, 34583240); assert!(!lyrics.instrumental); assert!(!lyrics.explicit); - assert!(lyrics - .lyrics_body - .starts_with("Eyes, in the sky, gazing far into the night\n")); + assert!( + lyrics + .lyrics_body + .starts_with("Eyes in the sky gazing far into the night\n"), + "got: {}", + lyrics.lyrics_body + ); assert_eq!(lyrics.lyrics_language.unwrap(), "en"); assert_eq!(lyrics.lyrics_language_description.unwrap(), "English"); let copyright = lyrics.lyrics_copyright.unwrap(); - assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); + assert!(copyright.contains("Jesse Warren"), "copyright: {copyright}",); assert!(lyrics.updated_time > datetime!(2021-6-3 0:00 UTC)); } @@ -851,12 +819,12 @@ mod subtitles { // dbg!(&subtitle); - assert_eq!(subtitle.subtitle_id, 36913312); + assert_eq!(subtitle.subtitle_id, 35340319); assert_eq!(subtitle.subtitle_language.unwrap(), "en"); assert_eq!(subtitle.subtitle_language_description.unwrap(), "English"); let copyright = subtitle.lyrics_copyright.unwrap(); - assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); - assert_eq!(subtitle.subtitle_length, 315); + assert!(copyright.contains("Jesse Warren"), "copyright: {copyright}",); + assert_eq!(subtitle.subtitle_length, 316); assert!(subtitle.updated_time > datetime!(2021-6-30 0:00 UTC)); } @@ -993,3 +961,15 @@ mod translation { assert_eq!(subtitles_trans.to_ttml().trim(), expected_ttml.trim()); } } + +#[track_caller] +fn assert_imgurl(url: &Option, ends_with: &str) { + assert!( + url.as_deref().is_some_and( + |url| url.starts_with("https://s.mxmcdn.net/images-storage/") + && url.ends_with(ends_with) + ), + "expected url ending with {ends_with}\ngot {:?}", + url + ); +}