Like Current Song — like the song you're hearing on Spotify or YouTube Music without touching your phone
Heard a song you love while your phone is in your pocket with the screen off? Pause and resume it with your headphone button (e.g. pause → play), and the track is saved to your Spotify Liked Songs or your YouTube Music likes. You don't unlock the phone, look at the screen, or open the music app.
At your computer, a global keyboard shortcut does the same thing: press Ctrl+Shift+Alt+W (configurable) while your music plays in the background, and the current song is liked without switching away from the app you're working in.
- Android: works with the screen off and the phone locked. It reacts to the pause/play state of whatever is playing, so anything that pauses and resumes playback can trigger it: wired or Bluetooth headphones, earbud taps, a smartwatch, or a car stereo. The pattern is configurable, and a short sound confirms the like.
- Windows: a tray app with a global hotkey to like the current track, plus a second hotkey that discards it: out of your playlists, and a dislike on YouTube Music. Spotify, or YouTube Music in beta.
- macOS / Linux: a
like-current-song like-oncecommand you can bind to any shortcut — in your OS settings, Raycast, skhd, or similar. - Two music services, neither required: Spotify and YouTube Music. On Android, YouTube Music needs no Google sign-in; on Windows it is in beta. If you use both, Android can send each like to whichever one is actually playing.
- Beyond "like" (optional rules): remove the track from a Discover Weekly archive playlist, promote it to a "best" playlist after you like it N times across devices, and auto-follow an artist after N liked tracks. The counts live in a Google Sheet you own, so your phone and computer see the same numbers.
Open source (MIT). It talks to your music service through that service's official API, using a developer app you create yourself. There's no UI scraping, and your tokens stay on your devices.
For developers: the desktop side is a pluggable framework with five extension points (Trigger, MusicProvider, Storage, PreLikeAction, PostLikeAction). Nine extensions ship across those five seams, plus one skeleton, each a folder under like_spotify/extensions/ with a manifest.json describing it. Hosts discover them from those manifests at startup, so adding one is a new folder, with no edit to the host code (a wheel install also needs its manifest listed in pyproject.toml). See CONTRIBUTING.md.
- Trigger — pause-play your headset (Android) or press a hotkey (desktop)
- Like — the current track is saved: Liked Songs on Spotify, a thumbs-up on YouTube Music — or a playlist of your own instead, or both (Trigger configuration → Where likes go on Android). The playlist is matched by name and created if it isn't there yet; on YouTube Music it is the only way to keep a songs-only list, since the native like mixes songs in with every video you have ever liked
- Archive cleanup — if the track is in your archive playlist, it gets removed
- Best promotion — like a track 3 times across devices and it's added to your best playlist
- Artist follow — like 5+ tracks from an artist and they get auto-followed
Steps 3–5 are optional and off until you set them up. On Android they live under Trigger configuration → Extra actions. Steps 4 and 5 count likes, so they also need the counter from Cross-device counters; step 3 works without one.
Several desktop hotkey tools can like the current Spotify song. We haven't found another open-source project that does it from a phone with the screen off, or one that covers phone and desktop with shared rules. If you only need a Windows hotkey, the smaller tools below may fit you better.
| Project | One-press like | Headset trigger (phone) | Hotkey trigger (desktop) | Rule engine (archive/best/follow) | Cross-device counters | Pluggable | Use theirs when |
|---|---|---|---|---|---|---|---|
| Like Current Song (this) | ✓ | ✓ Android | ✓ Windows tray + mac/linux CLI | ✓ | ✓ a Google Sheet you own | ✓ 5 typed seams, 9 extensions | n/a |
| Pano Scrobbler | partial (love via UI) | — (notification scrape) | — | — (scrobble target only) | — | provider seam only (write target) | you want scrobbling history to last.fm/listenbrainz/librefm/pleroma. Pano is the right answer for "where did my listens go" — we don't try to replace it. |
| BeatBind | ✓ (save / remove) | — | ✓ Windows tray (.NET) | — | — | — | you want a polished Windows-only global-hotkey app for full playback control (play/pause, skip, volume, seek) as well as saving tracks. |
| Spotikey | ✓ | — | ✓ Windows (Ctrl+Alt+L) |
— | — | — | you want only a like hotkey, as a single small executable. |
| SpotiLike-GUI | ✓ (to a playlist) | — | ✓ desktop (PyQt) | — | — | — | you want one hotkey per target playlist and a GUI to manage them. |
| SpotifyHotKeys.ahk | ✓ (like / unlike) | — | ✓ Windows only (AutoHotKey) | — | — | — | you already live in AutoHotKey and want a small single-file script you can paste & edit. We're heavier, but cross-platform and rule-capable. |
| Music Assistant | partial (per-provider) | — | via Home Assistant | extensive (queue / library / sync) | — (per-instance) | ✓ ~60 providers | you want Home Assistant-grade music orchestration — multi-provider library merging, multi-room sync, queue scripting. We don't try to be your music server; we sit next to your existing Spotify client. |
| n8n / Zapier / IFTTT | only via polling | — | — | yes (general workflows) | yes (workflow vars) | ✓ generic | you want a generic workflow engine with a UI and 400+ integrations. We're the inverse — narrow to "like + post-like rules", but one button press and ~30 ms latency vs minutes of polling. |
Only if you use Spotify. For YouTube Music, skip this step — on Android it needs no sign-in at all (details), and on Windows it uses a Google OAuth client instead (details).
You run this against your own Spotify app, which stays in Spotify's development mode. Since the February 2026 changes (in force for existing integrations from 9 March 2026), that mode has three limits worth knowing before you start — see quota modes:
- The owner needs Spotify Premium. "The app owner must have a Spotify Premium account for apps in development mode to function." On a free account your own app does not work at all.
- Five users, allowlisted by hand. "Up to 5 authenticated Spotify users can use an app that is in development mode" (it was 25). Everyone counts, you included, and each one is added manually in the dashboard.
- One development-mode app per developer. "Developers will be limited to one Development Mode Client ID", so a single client ID has to serve your phone and your computer — which is how this project uses it anyway.
Extended quota mode is not an escape hatch for a project like this one: since 15 May 2025 Spotify only accepts applications from organizations, not individuals, and expects at least 250k monthly active users.
- Go to developer.spotify.com/dashboard
- Create an app
- Add redirect URIs:
likespotify://auth-callback(Android)http://127.0.0.1:8793/callback(Desktop)
- Open the app's Settings → User Management and add every Spotify account that will use it — your own included — by display name and the email on the account
- Copy your Client ID
If likes fail with 403 right after a successful login, that is the allowlist, not a bug here. An account that is not under User Management can still complete the whole OAuth flow and look connected; the limit is enforced on the API calls afterwards. Add the account, then reconnect in the app.
Download app-release.apk from the
latest release
and install it. Android will warn you about an app from outside the Play Store;
that is expected for a sideloaded APK.
Then open Connected services and paste the Client ID from step 1 into Spotify credentials → Save client ID. The redirect URI to add in the dashboard is shown right there, with a copy button. Connect Spotify turns on once the ID is saved.
Then grant notification access — the trigger does not work without it. Open Permissions → Open notification access (required) and switch the app on. Android gives the headset button to the music app, not to us, so the only way a pause-play reaches the app is by reading the player's own playback state, and that is what notification access is for. Without it the service starts, says so, and nothing ever happens. Finally, switch the service on from the main screen.
- Some phones stop the app once you swipe it away, unless it is exempt from battery optimization. Vendor ROMs with aggressive battery management (Xiaomi HyperOS and MIUI, Samsung One UI, Huawei, OnePlus) can stop delivering playback events to an app whose task was removed from Recents, even though its service and notification are still there. The pattern then does nothing until you open the app again. On the HyperOS phone where this was seen, the exemption was enough: with the app gone from Recents it kept picking up presses and liking tracks (#199). So open Battery optimization status in the app's menu and tap Request exemption. If presses still stop after a swipe, on Xiaomi also allow autostart and set the battery saver to No restrictions (the same screen shows both steps), or just leave the app in Recents. dontkillmyapp.com lists the settings for each vendor.
- Android only. There is no iOS app: iOS doesn't let third-party apps observe another app's playback in the background.
When a press seems to do nothing, open Logs / debug from the menu. It
lists what the app saw and did, including what happened while the app was
closed — those entries are kept and shown the next time you open it, with the
time they actually happened. With a cable, adb logcat -s LikeCurrentSong:*
shows the same lines live.
The client ID lives in the app's encrypted storage, so it survives updates and a disconnect — you type it once, not once per build. The published APK carries no credentials of anyone else's.
Building it yourself instead.
git clone https://github.com/Osasuwu/like-current-song.git
cd like-current-song
flutter pub get
flutter build apk --releaseA build with no keystore of your own is signed with your machine's debug key. That is fine for your own phone, but it cannot be upgraded in place by the released APK — you would have to uninstall first. See CONTRIBUTING.md for signing with a real key.
Building with your credentials baked in (optional). If you flash the app often, or hand builds to the four other people on your allowlist, you can seed the credentials at build time instead:
cp .env.example .env
# Edit .env — fill in only the keys you use
flutter build apk --release --dart-define-from-file=.env.env.example documents all six, and every one of them is optional:
| Key | What it seeds |
|---|---|
SPOTIFY_CLIENT_ID |
Spotify sign-in |
YTMUSIC_CLIENT_ID, YTMUSIC_CLIENT_SECRET |
the optional YouTube Music sign-in |
COUNTER_SPREADSHEET_ID |
which sheet the shared counter writes to |
COUNTER_GOOGLE_CLIENT_ID, COUNTER_GOOGLE_CLIENT_SECRET |
the counter's own Google sign-in |
Those values only ever fill a field the app has never been told about. Anything saved in Connected services wins from then on, and clearing a field keeps it clear — a rebuild will not put the old value back.
Pick YouTube Music under Connected services. The trigger then gives the playing song a thumbs-up through the YT Music app's own media session, so it works with the screen off and needs no Google sign-in at all — only the notification access the listener already uses.
Automatic (optional). If you use both services, Connected services also offers Automatic, which sends the like to whichever connected service is currently playing instead of to one service you picked. It resolves, in order: the one connected service with a playing media session; otherwise the last one that was playing; otherwise the service still shown in the picker. Automatic is off until you choose it — upgrading never changes where your likes go — and it is only offered while notification access is granted and at least two services are connected, since that is what it takes to tell them apart. When it isn't offered, the picker says which of the two is missing. Every automatic like logs which service it went to and why, on the Logs / debug screen.
Signing in is optional, and the rest of this section is only about that. It buys two things: a YouTube Data API fallback for when the session rating doesn't take, and likes that count in the shared counter (the same one the desktop app uses, keyed by your Google account, and only once the shared like counter is set up). It costs setting up a Google Cloud project, and — if you leave that project in Testing — a re-sign-in about once a week; see step 3, which is also where that weekly reconnect is avoided. If the trade isn't worth it to you, stop here; the thumbs-up keeps working.
Counting looks a song up through the Data API the first time it is liked.
Since 1 June 2026 that lookup has its own budget: Google grants a project
100 search.list calls a day, separate from the 10,000 units a day
shared by every other endpoint
(quota details).
So counting stops after about 100 new songs in a day while the 10,000-unit
pool is still almost untouched — the two run out independently. Repeats of a
song already looked up cost nothing. Past the limit, likes still work, they
just stop counting until the buckets reset at midnight Pacific time.
To sign in, you use your own Google OAuth client. Nothing goes in
.env: you enter the client in the app. It takes about 5 minutes, once.
-
Open console.cloud.google.com and create a project. You can reuse the desktop one.
-
Go to APIs & Services → Library, find YouTube Data API v3 and click Enable.
-
Go to APIs & Services → OAuth consent screen and choose External. Fill in the app name and your email. You now pick one of two states, and the choice is worth a minute because one of them makes you reconnect every week.
Testing — the quick way to try it. Add your own Google account under Test users and carry on. The cost: Google expires the refresh token after 7 days, so you repeat the sign-in (steps 6–7) roughly once a week. There is no way around that for the
youtubescope — the 7-day limit is waived only for the name/email/profile scopes. An account that is not on the Test users list gets "Access blocked: … has not completed the Google verification process" instead of a consent screen, so add it there first.In production — click Publish app, and the 7-day expiry goes away. Publishing is not the same as being verified by Google, and it does not require a review. What it does require is a Branding page with a home page, a privacy policy and a terms-of-service link, each on a domain you have verified in Search Console: "These links are required for all external production apps" (source). Without them Publish only answers "To publish your app, you must complete your configuration on the Branding page". This repo ships those pages — they are the GitHub Pages site under
docs/, served fromosasuwu.github.io, which is a domain you can verify if you fork. What publishing does not remove: the "Google hasn't verified this app" interstitial on every sign-in (step 7), becauseyoutubeis a sensitive scope, and the 100-user lifetime cap. Both need full verification, which is a demo video and a review.The project this repo is developed against runs in production, unverified.
-
Go to APIs & Services → Credentials → Create credentials → OAuth client ID, and pick application type TVs and Limited Input devices. Other types (Android, Desktop app, Web) are rejected by the sign-in the app uses. Copy the client secret straight away — it is shown only once, when the client is created, and cannot be downloaded again. If you lose it, open Google Auth Platform → Clients → your client → Add Secret and use the new secret instead.
-
In the app, open Connected services, pick YouTube Music, paste the client ID and secret, and tap Save credentials.
-
Tap Connect YouTube Music. The app shows a code and a link (
google.com/device). Open the link on the phone or any other device, enter the code, and pick your Google account. -
Google warns that the app isn't verified, because it is your own unreviewed project. Click Advanced → Go to app name (unsafe) and allow access. The app notices on its own and shows the account as connected.
Requested scopes: youtube (to rate videos) and openid (the account id shown
in the app). Tokens stay on the phone in encrypted storage, apart from
Spotify's, so switching services keeps both signed in. Disconnect signs out
of YouTube Music only and keeps the client ID and secret.
The opt-in Extra actions work under YouTube Music too: archive clean-up,
promote-to-best and follow-artist act on your ordinary YouTube playlists and
channel subscriptions. Each one spends about 50 units of the 10,000-unit daily
pool — the pool the song lookup above does not draw on — so a like with all
three enabled costs a couple of hundred units out of 10,000, and you would need
hundreds of likes in a day to exhaust it. A plain like costs none — but
pointing Where likes go at a playlist makes every like a playlist write,
at the same ~50 units. The youtube scope above already covers all of this,
so there is nothing more to authorise.
The desktop side ships as a pluggable Python package (like_spotify/) —
a tray host + global hotkey on Windows, a CLI fallback (like-once) on
macOS / Linux, providers for Spotify and YouTube Music (beta), and
an optional like counter kept in a Google Sheet you own.
Windows: download the exe. No Python, no terminal. Download
LikeCurrentSong.exe from the
latest release
and double-click it. The first launch opens the Settings window (described
below): pick the music service, sign in, and save. The tray icon and hotkey
start right after. Keep the exe wherever you like; if you move it, relaunch
it once so the autostart entry points at the new location.
The exe isn't code-signed, so Windows SmartScreen warns about an unrecognized app on first launch. Click More info → Run anyway — the same kind of warning the APK gets from Android. To check that the file is the one the release published, compare its hash with the SHA-256 in the release notes:
Get-FileHash .\LikeCurrentSong.exe -Algorithm SHA256The exe ships without credentials: everyone brings their own Spotify or Google
app, entered in Settings. Config and tokens live in ~/.like_spotify/, the
same place the Python package uses, so you can switch between the two. The
exe takes the same commands as like-current-song (see After install
below) and never shows a console, so an external hotkey tool can call
LikeCurrentSong.exe like-once.
From source (pipx). For macOS / Linux, or to run the Python package on Windows. Run from a fresh clone:
# Windows (PowerShell)
git clone https://github.com/Osasuwu/like-current-song.git
cd like-current-song
.\install.ps1# macOS / Linux
git clone https://github.com/Osasuwu/like-current-song.git
cd like-current-song
./install.shThe installer checks for Python 3.11+, installs pipx if missing,
installs the like-current-song package, then walks you through the
interactive setup wizard. It opens by asking which music service you
want — spotify or ytmusic — and that answer decides what step 1 asks
for. Then five numbered steps:
- Sign in to the service you picked.
- Spotify: paste a Client ID from
developer.spotify.com/dashboard
(redirect URI:
http://127.0.0.1:8793/callback); a browser opens for PKCE OAuth. - YouTube Music: paste your own Google OAuth client — see YouTube Music (beta, Windows) below.
- Spotify: paste a Client ID from
developer.spotify.com/dashboard
(redirect URI:
- Where a like goes —
native(the default) likes the track on the music service itself, which is what feeds its recommendations;playlistadds it to a playlist of yours instead;bothdoes the two. On YouTube Music the native like shares one bucket with liked videos, so a playlist is the only song-only list you can keep. Pickingplaylistorbothasks for the playlist name next; the playlist is created on first use if it doesn't exist yet, so a typo makes a new playlist rather than an error. - Storage —
sheetscounts your likes into a Google Sheet you own;noneskips counting. Likes work either way; without a counter you lose cross-device aggregation and the two rules that read it (promote-to-best, follow-artist). The default isnone, so nothing is set up behind your back. - Playlist clean-up (optional) — the archive playlist that a like should remove the track from, plus the best playlist. Leave blank to skip.
- Autostart — Windows: toggle the
HKCU\…\Runentry. macOS / Linux: instructions for a Launch Agent /.desktopfile are printed (no auto-config — too platform-fragmented).
The wizard is re-runnable; existing tokens are kept unless you pass
--reauth (or -Reauth on PowerShell).
After install:
like-current-song # Windows: tray host with the hotkey (default Ctrl+Shift+Alt+W)
like-current-song like-once # any OS: like the currently-playing track and exit
like-current-song discard-once # any OS: dislike the current track and take it out of your playlists (no like)
like-current-song --config # print config + token paths
like-current-song --settings # open the settings windowUpgrading from like-spotify. The package and commands used to be
called like-spotify / like-spotify-gui. Re-run the installer: it
removes the old pipx package and installs like-current-song. Your config
and tokens in ~/.like_spotify/ stay where they are. The old command names
still work for now (the console one prints a short note), but they will be
removed in a future release, so update any scripts or hotkey tools. On
Windows, an autostart entry from the old version is moved to the new
launcher the next time the tray starts, or when --setup asks about
autostart.
Settings window. Everything the wizard asks, in one window instead of a
terminal: the music service and the account sign-in (the same browser flow
as --setup), Where a like goes (the same three choices as the wizard,
with the playlist name greyed out while likes only go to the service), the
hotkeys, the sound volume (with a Test button), the like
counter storage, and autostart. The optional Extra actions (archive
clean-up, best, follow artist, like cooldown) sit in a collapsed
section, each with a one-line explanation. On a fresh install they all
start switched off. On Windows, open it from the tray menu (Settings…).
Saved changes apply right away, hotkeys included. If the new settings can't
run yet (for example, you switched service but haven't signed in), the tray
keeps the old ones and tells you why. If a change can't be applied live, it
offers to restart. From a terminal, run like-current-song --settings (or
like-current-song-gui --settings). It needs Tk: on Linux, install your
distro's python3-tk package. The window only edits the keys it knows, so
anything else you added to config.json by hand is kept as is.
like-current-song is a console-subsystem executable, so any of the above
briefly shows a terminal window. On Windows, a windowed twin is also
installed — like-current-song-gui — that runs the exact same commands with no
console at all. Autostart uses it automatically; if you trigger like-once
/ discard-once from an external hotkey tool (AutoHotkey, a macro app, a
Stream Deck, etc.), point it at like-current-song-gui like-once instead of
like-current-song like-once to avoid the flash. (--setup / --config still
need like-current-song, since they read from the terminal.)
On Windows the tray host also binds a second global hotkey (default
Ctrl+Shift+Alt+Q) — the discard key, for tracks you want gone rather
than liked. One press does up to three independent things, without
liking:
- Dislike — tells the music service itself “not this one”. What that means differs per service, and the app does not pretend otherwise. On YouTube Music it is a real thumbs-down, which also clears any like you had on the track. On Spotify there is no dislike to send — the Web API has no such endpoint, and the “Hide this song” control in the official clients is not available to third-party apps — so the press removes the track from your Liked Songs instead.
- Un-archive — removes the track from your Discover-Weekly archive playlist, if you set an archive playlist name.
- Take it back out of your like destination — if a like goes to a
playlist of yours (
like.destination=playlistorboth), the press removes the track from that playlist too. Without this the discard key could not undo the like it exists to undo: on Spotify the like never touched your Liked Songs, so there was nothing for the dislike to remove. If the archive playlist and the like destination are the same playlist, the track is removed once and reported once.
Any one of the three is enough to earn the hotkey: with no playlists set you still get the dislike. None of them can cost you another — if one fails, the rest still go through, and the notification says what actually happened (“Disliked, removed from Archive and removed from My Songs”, “Disliked and removed from Archive — not removed from My Songs”, “Nothing changed”). If the combo collides with the like hotkey it's skipped. Audio feedback is audible through the default sound device and distinct per action (like / discard / error).
Choose ytmusic at the "Music service" prompt in --setup — it is the first
thing the wizard asks, before any sign-in. The hotkey then likes the song
playing in the YT Music browser tab or desktop app, and it lands in YT Music's
Liked music.
You need your own free Google OAuth client with the YouTube Data API enabled;
the steps are in
extensions/ytmusic/README.md.
Unlike the Android side, the desktop one has no session to drive, so the
sign-in is required rather than optional.
The playlist actions (archive-remove, best, follow-artist) work here too. Follow-artist subscribes to the artist's channel, and each playlist write costs YouTube API quota (see that README).
To switch back, re-run --setup and pick spotify, or change the music
service in the settings window. Both services keep their own tokens, so
switching does not sign you out of the other one.
Building the exe yourself: pip install -e .[dev,ytmusic], then
python tools/build_exe.py → dist\LikeCurrentSong.exe (it prints the
SHA-256 at the end). See
CONTRIBUTING.
Everything above works without this. Turning it on buys you two things: the same like counts on your phone and your computer, and the two rules that read them — promote-to-best (add a track to a "best" playlist once you have liked it N times) and follow-artist (follow an artist once you have liked N of their tracks). Every other feature is unaffected.
Both rules count in the shared sheet on both halves, so a track liked on the phone and one liked at the computer add up toward the same threshold. Follow-artist counts distinct tracks per artist, wherever each was liked. The two do differ without a counter: promote-to-best then stays inactive, while follow-artist falls back to a count kept on each device — distinct tracks as well, but not shared.
The counter is a Google Sheet you own. There is no service to sign up for, no database to run, and no backend operated by this project — the numbers are rows in a spreadsheet you can open, edit, chart or delete yourself.
You do not have to build that sheet yourself — either half will make one for you, tabs and header rows and all, in the Drive of the Google account you sign in with. Pasting the ID of a sheet you already have keeps working, and is how a second device joins an existing count.
-
Enable the Google Sheets API on its page in the API library, then create an OAuth client at console.cloud.google.com/apis/credentials. They are two different pages: the credentials one makes clients and cannot switch an API on, and a project with the API off refuses every call with a 403. Which kind of client depends on the half, because the two sign in differently:
Half Client type Why Desktop Desktop app it opens a browser and catches the reply on 127.0.0.1Android TVs and Limited Input devices it shows a code you type at google.com/deviceOne Cloud project covers both. If you already made a TVs and Limited Input devices client for YouTube Music, the phone can reuse that same client for the counter once the Sheets API is on — the two grants are still separate sign-ins with separate scopes.
-
Run
like-current-song --setup, picksheetsat the storage step, and enter the client ID and secret. A browser opens for the Google consent screen; the token is refreshed automatically afterwards and lives in~/.like_spotify/google_token.json. The wizard then asks about the spreadsheet itself and takes one of three answers:Answer What happens create(default)makes the sheet in your Drive and prints its ID pastecounts into a sheet you name by ID — how a second device joins skipleaves the counter off; likes still work, nothing is counted The Windows settings window has a Create spreadsheet button next to the Spreadsheet ID box.
-
Point the Android app at the same sheet to share counts between devices: Connected services → Shared like counter (optional). Enter the client ID and secret, sign in, then either press Create a sheet for me or paste the ID of the sheet the other device already uses. This is the counter's own Google sign-in, separate from the music service and asking for one scope,
spreadsheets— so a Spotify user gets a shared counter without granting any YouTube permission, and disconnecting the counter leaves the music service signed in. Creating a sheet needs no extra permission:spreadsheetsalready covers it.Whichever half creates the sheet, the other one joins it by ID. Neither will make a second sheet once one is configured — it says so instead, so a stray tap cannot split your counts across two files.
-
Only if you would rather build the sheet by hand: create a Google Sheet with a tab named
Likescarrying this header row:user_id | track_id | count | backfilled | updated_atFor follow-artist, add a second tab named
ArtistTrackswith the header rowuser_id | artist_id | track_id | created_at. Then paste its ID at step 2 or 3 instead of creating one.
Both halves address a row by your own account id — your Spotify user id, or the Google account id when the like came from YouTube Music — so two people using one sheet do not collide.
Storage is a seam, not a hard-coded choice. Storage is one of the five
extension points, and tests/test_storage_contract.py holds the seven
invariants any implementation has to satisfy. If a spreadsheet is the wrong
shape for you, a different backend is a plugin — see
CONTRIBUTING.md. Google Sheets is simply the one that
ships.
Upgrading from the Supabase backend? Both halves had one; both dropped it in v1.2.0. Nothing breaks: likes keep working, they just stop being counted until you point the half at a sheet.
On the desktop, a config still saying
backend: "supabase"says so once at startup — re-run--setup(or open the settings window) and picksheets. On the phone, the old Supabase fields are gone from Connected services; fill in Shared like counter instead. Counts do not carry over, on either half. Your Supabase project is untouched and yours to keep or delete.
Neither half is written against one music service or one place to keep counts. Both go through the same two seams: a provider that knows how to like a track somewhere, and a counter that knows how to add one to a number.
Android (Flutter + Kotlin) Desktop (Python framework)
┌────────────────────────────┐ ┌────────────────────────────┐
│ Trigger │ │ Trigger │
│ · headset / media buttons │ │ · tray + global hotkey │
│ (pause-play patterns) │ │ · one-shot CLI │
│ ↓ │ │ ↓ │
│ Music service │ │ MusicProvider │
│ · Spotify · YT Music │ │ · Spotify · YT Music │
│ · Automatic (whichever │ │ ↓ │
│ one is playing) │ │ PreLikeAction │
│ ↓ │ │ · like cooldown │
│ Extra actions │ │ ↓ │
│ · archive remove │ │ PostLikeAction │
│ · promote to best │ │ · archive remove │
│ · follow artist │ │ · promote to best │
└──────┬─────────────────────┘ │ · follow artist │
│ └──────┬─────────────────────┘
│ │
└──────────────┬─────────────────────┘
↓
Your music service's own API (what "like" means)
Storage · Google Sheets (shared counts, optional)
The desktop side names those seams as five ABCs in like_spotify/core/:
Trigger, MusicProvider, Storage, PreLikeAction and PostLikeAction.
Nine extensions ship against them, plus one skeleton:
| Seam | Ships today |
|---|---|
Trigger |
tray_hotkey_trigger, one_shot_cli_trigger |
MusicProvider |
spotify, ytmusic (beta) |
Storage |
google_sheets_storage |
PreLikeAction |
like_cooldown |
PostLikeAction |
archive_remove, promote_to_best, follow_artist |
volume_button_trigger is in the tree as well, but it is a skeleton: the
pattern matching is written, the HID read loop is a TODO, and its manifest
stage says experimental. Finishing it is
#74, and it is a
good way to see the Trigger seam end to end.
Each lives in its own folder under like_spotify/extensions/ with a
manifest.json describing it. The hosts read those manifests at startup, so
a new folder with a valid manifest is picked up without editing the host code
(to ship it in the wheel, also list the manifest in pyproject.toml's
package-data; see CONTRIBUTING).
like-current-song --config lists what was found, with each extension's
stage. Nothing in core/ or the pipeline knows the names above.
lib/— Flutter app (Dart): UI, state management (Riverpod), OAuthandroid/.../kotlin/— native Android: foreground service, notification listener, background workerlike_spotify/— Python desktop package:core/(the five ABCs and the pipeline),hosts/(tray runtime, setup wizard, settings window),extensions/(the ten above),samples/(alt-flavor examples)
Every desktop setting below except the token files can be changed in the
settings window (like-current-song --settings, or Settings… in the tray menu).
| Setting | Android | Desktop |
|---|---|---|
| Trigger pattern / hotkey | In-app UI | ~/.like_spotify/config.json → trigger.hotkey (default Ctrl+Shift+Alt+W) |
| Discard hotkey (dislike + un-archive + un-like) | n/a (one trigger on headphones) | ~/.like_spotify/config.json → trigger.remove_hotkey (default Ctrl+Shift+Alt+Q) |
| Where a like goes | In-app UI (Trigger configuration → Where likes go) | ~/.like_spotify/config.json → like.destination (native / playlist / both, default native) |
| Like destination playlist | In-app UI, matched by name on whichever service played the song | ~/.like_spotify/config.json → like.playlist_name (required by playlist / both; created on first use, and the discard hotkey removes from it) |
| Archive playlist name | In-app UI | ~/.like_spotify/config.json → actions.archive_remove.playlist_name (blank = the discard hotkey only dislikes) |
| Music service | In-app UI (Spotify / YouTube Music / Automatic) | ~/.like_spotify/config.json → music.provider (spotify / ytmusic, default spotify) |
| YouTube Music client ID / secret | In-app UI (Connected services), stored in FlutterSecureStorage; .env (YTMUSIC_CLIENT_ID, YTMUSIC_CLIENT_SECRET) seeds a build |
like-current-song --setup → ~/.like_spotify/config.json |
| YouTube Music tokens | FlutterSecureStorage (refreshed automatically) |
~/.like_spotify/youtube_token.json (refreshed automatically) |
| Spotify client_id | In-app UI (Connected services), stored in FlutterSecureStorage; .env (SPOTIFY_CLIENT_ID) seeds a build |
like-current-song --setup → ~/.like_spotify/config.json |
| Spotify tokens | FlutterSecureStorage |
~/.like_spotify/spotify_token.json |
| Counter spreadsheet ID | In-app UI (Connected services → Shared like counter), stored in FlutterSecureStorage; .env (COUNTER_SPREADSHEET_ID) seeds a build |
like-current-song --setup → ~/.like_spotify/config.json → sheets.spreadsheet_id |
| Counter Google client ID / secret | In-app UI (Shared like counter); .env (COUNTER_GOOGLE_CLIENT_ID, COUNTER_GOOGLE_CLIENT_SECRET) seeds a build |
like-current-song --setup, kept beside the tokens in ~/.like_spotify/google_token.json |
| Storage backend | Blank spreadsheet ID = counts stay on the device | ~/.like_spotify/config.json → storage.backend (sheets / none) |
| Counter Google tokens | FlutterSecureStorage (refreshed automatically) |
~/.like_spotify/google_token.json (refreshed automatically) |
| Best / follow | In-app UI | ~/.like_spotify/config.json → actions.{promote_to_best,follow_artist} |
It does what it set out to do, so development has slowed down since v1.2.0. It is not abandoned: bug reports and feature requests filed as issues are read and answered, and fixes still ship as releases.
Contributions are welcome — the desktop side is a plugin framework precisely so that other people's triggers, providers, storages, and actions can live in it.
- CONTRIBUTING.md — repo layout, the five extension points with code, the add-an-extension checklist, and how to run the tests. It also lists what's known to be easy to land.
- Good first issues · Help wanted
- CODE_OF_CONDUCT.md
- SECURITY.md — please report vulnerabilities privately, not as a public issue.
- CHANGELOG.md