fluncle 0.67.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +80 -5
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -423,7 +423,7 @@ function parseVersion(version) {
423
423
  var currentVersion;
424
424
  var init_version = __esm(() => {
425
425
  init_output();
426
- currentVersion = "0.67.0".trim() ? "0.67.0".trim() : "0.1.0";
426
+ currentVersion = "0.68.0".trim() ? "0.68.0".trim() : "0.1.0";
427
427
  });
428
428
 
429
429
  // src/update-notifier.ts
@@ -2138,7 +2138,7 @@ function parseVersion2(version) {
2138
2138
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2139
2139
  var init_version2 = __esm(() => {
2140
2140
  init_output();
2141
- currentVersion2 = "0.67.0".trim() ? "0.67.0".trim() : "0.1.0";
2141
+ currentVersion2 = "0.68.0".trim() ? "0.68.0".trim() : "0.1.0";
2142
2142
  });
2143
2143
 
2144
2144
  // ../../packages/registry/src/index.ts
@@ -3162,6 +3162,73 @@ var init_admin_tracks = __esm(() => {
3162
3162
  init_recent2();
3163
3163
  });
3164
3164
 
3165
+ // src/version-match.ts
3166
+ function isRemix(title) {
3167
+ return REMIX_MARKER.test(title);
3168
+ }
3169
+ function stripVersionSuffix(title) {
3170
+ const parts = title.split(/\s+-\s+/);
3171
+ if (parts.length > 1 && VERSION_MARKER.test(parts[parts.length - 1] ?? "")) {
3172
+ return parts.slice(0, -1).join(" - ").trim();
3173
+ }
3174
+ return title.trim();
3175
+ }
3176
+ function tokenize(value) {
3177
+ return value.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter(Boolean);
3178
+ }
3179
+ function versionTokens(title) {
3180
+ const parts = title.split(/\s+-\s+/);
3181
+ const tail = parts.length > 1 ? parts[parts.length - 1] ?? "" : "";
3182
+ if (parts.length > 1 && VERSION_MARKER.test(tail)) {
3183
+ return new Set(tokenize(tail));
3184
+ }
3185
+ const bracketed = /[([]([^)\]]*?)[)\]]/.exec(title);
3186
+ if (bracketed?.[1] && VERSION_MARKER.test(bracketed[1])) {
3187
+ return new Set(tokenize(bracketed[1]));
3188
+ }
3189
+ return new Set;
3190
+ }
3191
+ function versionMatches(findingTitle, candidateTitle) {
3192
+ const findingIsRemix = isRemix(findingTitle);
3193
+ const candidateIsRemix = isRemix(candidateTitle);
3194
+ if (findingIsRemix) {
3195
+ if (!candidateIsRemix) {
3196
+ return false;
3197
+ }
3198
+ const want = [...versionTokens(findingTitle)].filter((t) => !VERSION_STOPWORDS.has(t));
3199
+ if (want.length === 0) {
3200
+ return true;
3201
+ }
3202
+ const have = versionTokens(candidateTitle);
3203
+ for (const token of want) {
3204
+ if (!have.has(token)) {
3205
+ return false;
3206
+ }
3207
+ }
3208
+ return true;
3209
+ }
3210
+ return !candidateIsRemix;
3211
+ }
3212
+ function baseTitleMatches(findingTitle, candidateTitle) {
3213
+ const want = new Set(tokenize(stripVersionSuffix(findingTitle)));
3214
+ const have = new Set(tokenize(stripVersionSuffix(candidateTitle)));
3215
+ if (want.size === 0) {
3216
+ return false;
3217
+ }
3218
+ for (const token of want) {
3219
+ if (!have.has(token)) {
3220
+ return false;
3221
+ }
3222
+ }
3223
+ return true;
3224
+ }
3225
+ var VERSION_MARKER, REMIX_MARKER, VERSION_STOPWORDS;
3226
+ var init_version_match = __esm(() => {
3227
+ VERSION_MARKER = /\b(mix|edit|version|remix|dub|vip|bootleg|rework|re-?edit|flip|refix|remaster(?:ed)?|instrumental)\b/i;
3228
+ REMIX_MARKER = /\b(remix|bootleg|vip|rework|re-?edit|flip|refix)\b/i;
3229
+ VERSION_STOPWORDS = new Set(["mix", "the", "and", "feat", "ft", "edit", "version", "remix"]);
3230
+ });
3231
+
3165
3232
  // src/commands/preview-archive.ts
3166
3233
  var exports_preview_archive = {};
3167
3234
  __export(exports_preview_archive, {
@@ -3265,16 +3332,22 @@ async function resolveDeezerByIsrc(isrc) {
3265
3332
  return;
3266
3333
  }
3267
3334
  }
3335
+ function durationAgrees(findingMs, candidateSeconds) {
3336
+ if (!findingMs || !candidateSeconds) {
3337
+ return true;
3338
+ }
3339
+ return Math.abs(candidateSeconds - findingMs / 1000) <= DURATION_TOLERANCE_S;
3340
+ }
3268
3341
  async function resolveDeezerSearch(track) {
3269
3342
  const artist = track.artists[0]?.trim();
3270
3343
  if (!artist || !track.title.trim()) {
3271
3344
  return;
3272
3345
  }
3273
3346
  try {
3274
- const query = `artist:"${artist}" track:"${track.title.trim()}"`;
3347
+ const query = `artist:"${artist}" track:"${stripVersionSuffix(track.title.trim())}"`;
3275
3348
  const response = await fetch(`https://api.deezer.com/search?q=${encodeURIComponent(query)}`);
3276
3349
  const body = await response.json();
3277
- const hit = (body.data ?? []).find((item) => item.preview);
3350
+ const hit = (body.data ?? []).find((item) => item.preview && versionMatches(track.title, item.title ?? "") && baseTitleMatches(track.title, item.title ?? "") && durationAgrees(track.durationMs, item.duration));
3278
3351
  return downloadPreview(hit?.preview, "deezer:search");
3279
3352
  } catch {
3280
3353
  return;
@@ -3288,7 +3361,7 @@ async function resolveItunes(track) {
3288
3361
  try {
3289
3362
  const response = await fetch(`https://itunes.apple.com/search?term=${encodeURIComponent(`${artist} ${track.title}`)}&media=music&limit=10`);
3290
3363
  const body = await response.json();
3291
- const hit = (body.results ?? []).find((item) => item.previewUrl && normalize(item.artistName ?? "").includes(normalize(artist)));
3364
+ const hit = (body.results ?? []).find((item) => item.previewUrl && normalize(item.artistName ?? "").includes(normalize(artist)) && versionMatches(track.title, item.trackName ?? "") && baseTitleMatches(track.title, item.trackName ?? "") && durationAgrees(track.durationMs, item.trackTimeMillis ? item.trackTimeMillis / 1000 : undefined));
3292
3365
  return downloadPreview(hit?.previewUrl, "itunes");
3293
3366
  } catch {
3294
3367
  return;
@@ -3350,8 +3423,10 @@ function extensionForMime(mime) {
3350
3423
  function normalize(value) {
3351
3424
  return value.toLowerCase().replace(/[^a-z0-9]/g, "");
3352
3425
  }
3426
+ var DURATION_TOLERANCE_S = 5;
3353
3427
  var init_preview_archive = __esm(() => {
3354
3428
  init_api();
3429
+ init_version_match();
3355
3430
  });
3356
3431
 
3357
3432
  // src/commands/mixtape-youtube.ts
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.67.0"
34
+ "version": "0.68.0"
35
35
  }