29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # CI
2 |
3 | CI shows lyrics that you are currently playing on Spotify.
4 |
5 | Ci(詞) is a Chinese Character, which means Classical Chinese lyric poetry.
6 |
7 |

8 |
9 | ## Screenshot
10 |
11 |

12 |
13 | ## Usage
14 |
15 | CI stays at your menu bar, click the icon then CI will show the lyrics intanstly!
16 |
17 | There are four buttons on it, which are Leave, Play/Pause, Reload Spotify, Pinned.
18 |
19 | If the lyrics didn't show up, or it didn't refresh to the new song's lyrics, press Reload to reconnect Spotify.
20 |
21 | Jumping circles means that the lyrics is not found.
22 |
23 |
24 |
25 | ## Platform
26 |
27 | It supports Mac, Windows, and Linux. (Thanks to Electron!)
28 |
29 | ## Download
30 |
31 | Checkout our [Releases Page](https://github.com/kevchentw/CI/releases)
32 |
33 | ## Bug Report
34 | Open a issue on this repo.
35 | Or send me [Facebook message](https://www.facebook.com/kevchentw)
36 |
37 | ## Dev Instructions
38 |
39 | - run `npm install`
40 | - run `npm run build` to makeapp
41 | - run `npm start` to run app from CLI without building app
42 | - run `electron-packager . --all --overwrite --icon=Icon` to build Linux, Mac, Windows App
43 |
44 | ## License
45 | MIT © [Kevin Chen](https://github.com/kevchentw)
46 |
--------------------------------------------------------------------------------
/inject.js:
--------------------------------------------------------------------------------
1 | const ipc = require('electron').ipcRenderer
2 |
3 | function get_page_type() {
4 | var url = window.location.href;
5 | var type_lyrics = url.match("https:\/\/www.musixmatch.com\/lyrics\/(.*)\/embed")
6 | var type_black = url.match("about:blank")
7 | if (type_lyrics) {
8 | return {
9 | 'type': 'lyrics',
10 | 'url': url
11 | }
12 | } else if (type_black) {
13 | return {
14 | 'type': 'blank',
15 | 'url': url
16 | }
17 | } else {
18 | return {
19 | 'type': 'unknown',
20 | 'url': url
21 | }
22 | }
23 | }
24 |
25 | var page_type = get_page_type();
26 |
27 | function ready(fn) {
28 | if (document.readyState != 'loading') {
29 | fn();
30 | } else {
31 | document.addEventListener('DOMContentLoaded', fn);
32 | }
33 | }
34 |
35 | ready(init)
36 |
37 | function init() {
38 | var lyrics_exist = check_lyrics_exist()
39 | if (page_type['type'] == 'lyrics') {
40 | if(lyrics_exist){
41 | ipc.sendToHost('lyrics_exist', {'exist': true})
42 | }
43 | else{
44 | ipc.sendToHost('lyrics_exist', {'exist': false})
45 | }
46 | }
47 | console.log(lyrics_exist);
48 | }
49 |
50 | function check_lyrics_exist() {
51 | if (page_type['type'] == 'lyrics') {
52 | var exist = document.getElementsByClassName('track-widget-header')
53 | if (exist.length) {
54 | return true;
55 | }
56 | }
57 | return false
58 | }
59 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | var Client = require('electron-rpc/client')
2 | var client = new Client()
3 |
4 | const webview = document.getElementById('view');
5 |
6 | webview.addEventListener('ipc-message', (event, args) => {
7 | console.log(event.channel)
8 | console.log(event)
9 | if(event.channel=='lyrics_exist'){
10 | console.log('exist')
11 | if(!event.args[0].exist){
12 | client.request('get_lyrics_from_best_result');
13 | }
14 | }
15 | })
16 |
17 | client.on('new_track', function(err, data) {
18 | if (webview){
19 | view.src=encodeURI(data.url);
20 | }
21 | console.log(encodeURI(data.url));
22 | })
23 |
24 | client.on('player_status_change', function (err, data) {
25 | var icon = document.getElementById('iconPlay');
26 | if(data.status){
27 | icon.innerHTML = 'pause';
28 | }
29 | else{
30 | icon.innerHTML = 'play_arrow';
31 | }
32 | console.log("player_status_change")
33 | })
34 |
35 | function quit() {
36 | client.request('terminate');
37 | }
38 |
39 | function dev() {
40 | client.request('dev');
41 | }
42 |
43 | function refresh_spotify() {
44 | client.request('refresh_spotify');
45 | }
46 |
47 | function pinned() {
48 | client.request('pinned', {}, function (err, data){
49 | if (err) return;
50 | var icon = document.getElementById('iconPinned');
51 | if(data.new_pinned_status){
52 | icon.innerHTML = 'lock';
53 | }
54 | else{
55 | icon.innerHTML = 'lock_open';
56 | }
57 | });
58 | }
59 |
60 | function playPause() {
61 | client.request('play_pause');
62 | }
63 |
64 | webview.addEventListener('dom-ready', () => {
65 | console.log('dom ready');
66 | // webview.openDevTools()
67 | })
68 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | var menubar = require('menubar')
2 | var accents = require('remove-accents')
3 | var dialog = require('dialog');
4 | const shell = require('electron').shell;
5 |
6 | var mb = menubar({
7 | width: 415,
8 | height: 550,
9 | resizable: false,
10 | icon: __dirname + '/icon.png'
11 | })
12 | var SpotifyWebHelper = require('@jonny/spotify-web-helper')
13 | var helper = SpotifyWebHelper()
14 | var compare = require('node-version-compare');
15 | var VERSION_URL = "https://raw.githubusercontent.com/kevchentw/CI/master/version"
16 | var RELEASE_URL = "https://github.com/kevchentw/CI/releases"
17 | var request = require('superagent');
18 | var Server = require('electron-rpc/server')
19 | var app = new Server()
20 | var mb_ready = false;
21 | var spotify_ready = false;
22 |
23 | function check_new_version() {
24 | request
25 | .get(VERSION_URL)
26 | .end(function(err, res) {
27 | var now_version = mb.app.getVersion()
28 | var lastest_version = res.text
29 | var result = compare(lastest_version, now_version)
30 | if (result > 0) {
31 | var index = dialog.showMessageBox(mb.window, {
32 | type: 'info',
33 | buttons: ['Download', 'Cancel'],
34 | title: "New Version Available",
35 | message: 'New Version Available',
36 | detail: `You are currently on v${now_version}, update to v${lastest_version} to try out new features!`
37 | });
38 | if (index == 1) {
39 | return
40 | } else if (index == 0) {
41 | shell.openExternal(RELEASE_URL);
42 | }
43 | }
44 | });
45 | }
46 |
47 | function get_lyrics_from_best_result() {
48 | var track = helper.status.track
49 | request
50 | .get(`https://www.musixmatch.com/search/${encodeURI(track.artist_resource.name)}%20${encodeURI(track.track_resource.name)}`)
51 | .end(function(err, res) {
52 | var url = res.text.match('
0) {
54 | success = true;
55 | var data = {
56 | 'url': `https://www.musixmatch.com${url[1]}/embed`,
57 | 'track': track
58 | };
59 | if (mb_ready) {
60 | app.send('new_track', data);
61 | }
62 | } else {
63 | request
64 | .get(`https://www.musixmatch.com/search/${encodeURI(parseString(track.artist_resource.name))}%20${encodeURI(parseString(track.track_resource.name))}`)
65 | .end(function(err, res) {
66 | var url = res.text.match(' 0) {
68 | success = true;
69 | var data = {
70 | 'url': `https://www.musixmatch.com${url[1]}/embed`,
71 | 'track': track
72 | };
73 | if (mb_ready) {
74 | app.send('new_track', data);
75 | }
76 | }
77 | });
78 | }
79 | });
80 | }
81 |
82 | function replaceDot(s) {
83 | return s.replace(".", "-2").trim();
84 | }
85 |
86 | function removeBrackets(s) {
87 | return s.replace(/ *\([^)]*\) */g, "").trim();
88 | }
89 |
90 | function slugify(s) {
91 | return s.split(/'| |,|\.|-|&|!/).filter(Boolean).join("-").trim();
92 | }
93 |
94 | function removeDash(s) {
95 | return s.split('-')[0].trim();
96 | }
97 |
98 | function parseString(s) {
99 | s = removeDash(s);
100 | s = removeBrackets(s);
101 | s = replaceDot(s);
102 | s = accents(s);
103 | s = slugify(s)
104 | s = s.trim();
105 | return s;
106 | }
107 |
108 | function generate_musixmatch_url(track) {
109 | return `https://www.musixmatch.com/lyrics/${parseString(track.artist_resource.name)}/${parseString(track.track_resource.name)}/embed`
110 | }
111 |
112 | function initSpotify() {
113 | helper.player.on('ready', function() {
114 | spotify_ready = true;
115 | send_new_track(helper.status.track);
116 | send_player_status_change(helper.status.playing);
117 | helper.player.on('play', function() {
118 | send_player_status_change(true);
119 | })
120 |
121 | helper.player.on('pause', function() {
122 | send_player_status_change(false);
123 | })
124 |
125 | helper.player.on('end', function() {
126 | send_player_status_change(helper.status.playing);
127 | })
128 |
129 | helper.player.on('track-change', function(track) {
130 | send_player_status_change(helper.status.playing);
131 | send_new_track(track);
132 | })
133 |
134 | helper.player.on('error', function(err) {})
135 | });
136 | }
137 |
138 | function send_pinned_status() {
139 | var pinned_status = {
140 | 'status': mb.getOption('alwaysOnTop')
141 | }
142 | if (mb_ready) {
143 | app.send('pinned_status', pinned_status);
144 | }
145 | }
146 |
147 | function send_player_status_change(s) {
148 | var player_status = {
149 | 'status': s
150 | }
151 | if (mb_ready) {
152 | app.send('player_status_change', player_status);
153 | }
154 | }
155 |
156 | function send_new_track(track) {
157 | var data = {
158 | 'url': generate_musixmatch_url(track),
159 | 'track': track
160 | };
161 | if (mb_ready) {
162 | app.send('new_track', data);
163 | }
164 | }
165 |
166 |
167 | // menubar event listener
168 | mb.on('ready', function ready() {})
169 |
170 | mb.on('show', function ready() {
171 | send_pinned_status();
172 | })
173 |
174 | mb.on('after-create-window', function show() {
175 | app.configure(mb.window.webContents);
176 | check_new_version()
177 | mb_ready = true;
178 | initSpotify();
179 | // mb.window.openDevTools();
180 | })
181 |
182 |
183 | // rpc
184 | app.on('terminate', function(ev) {
185 | mb.app.quit();
186 | })
187 |
188 | app.on('dev', function(ev) {
189 | mb.window.openDevTools();
190 | })
191 |
192 | app.on('refresh_lyrics', function(ev) {
193 | if (spotify_ready) {
194 | refresh_lyrics(helper.status.track);
195 | }
196 | })
197 |
198 | app.on('refresh_spotify', function(ev) {
199 | helper = SpotifyWebHelper();
200 | spotify_ready = false;
201 | initSpotify();
202 | })
203 |
204 | app.on('pinned', function(req, next) {
205 | var next_pinned_status = !mb.getOption('alwaysOnTop');
206 | mb.setOption('alwaysOnTop', next_pinned_status);
207 | next(null, {
208 | 'new_pinned_status': next_pinned_status
209 | });
210 | mb.window.setAlwaysOnTop(next_pinned_status)
211 | })
212 |
213 | app.on('play_pause', function(ev) {
214 | if (spotify_ready) {
215 | if (helper.status.playing) {
216 | helper.player.pause();
217 | } else {
218 | helper.player.pause(true);
219 | }
220 | }
221 | })
222 |
223 | app.on('get_lyrics_from_best_result', function(ev) {
224 | get_lyrics_from_best_result();
225 | })
226 |
--------------------------------------------------------------------------------