1
+ const { ipcRenderer, webFrame, shell } = require ( 'electron' ) ;
2
+
3
+ const webview = document ;
4
+ const BASE_URL = 'https://www.mixcloud.com' ;
5
+ const DEBUG = process . env . ELECTRON_DEBUG || false ;
6
+
7
+ if ( DEBUG )
8
+ console . info ( JSON . stringify ( process . env , null , 4 ) ) ;
9
+
10
+ function concatEndpoints ( endpoints ) {
11
+ for ( const i in endpoints ) {
12
+ endpoints [ i ] = BASE_URL + endpoints [ i ] ;
13
+ }
14
+
15
+ return endpoints ;
16
+ }
17
+
18
+ const Endpoints = concatEndpoints ( {
19
+ DASHBOARD : '/dashboard/' ,
20
+ NEWSHOWS : '/dashboard/new-uploads/'
21
+ } ) ;
22
+
23
+ // webview.getSettings().setMediaPlaybackRequiresUserGesture(false); // to allow autoplay set by website
24
+
25
+ webview . addEventListener ( 'permissionrequest' , ( { e } ) => {
26
+ console . log ( 'permission requested' ) ;
27
+
28
+ if ( e . permission === 'media' ) {
29
+ e . request . allow ( ) ;
30
+ }
31
+ } ) ;
32
+
33
+ function didFinishLoad ( ) {
34
+ console . log ( 'didFinishLoad' ) ;
35
+ webview . removeEventListener ( 'did-finish-load' , didFinishLoad ) ;
36
+ webview . send ( 'init' ) ;
37
+ }
38
+ webview . addEventListener ( 'did-finish-load' , didFinishLoad ) ;
39
+
40
+ // Set Window Title
41
+ webview . addEventListener ( 'page-title-updated' , ( { title } ) => {
42
+ webview . title = `${ title } | Mixcloud Play` ;
43
+ } ) ;
44
+
45
+ ipcRenderer . on ( 'goToDashboard' , ( ) => {
46
+ console . log ( 'ipcRenderer: goToDashboard' ) ;
47
+ webview . location = Endpoints . DASHBOARD ;
48
+ } ) ;
49
+
50
+ ipcRenderer . on ( 'goToNewShows' , ( ) => {
51
+ console . log ( 'ipcRenderer: goToNewShows' ) ;
52
+ webview . location = Endpoints . NEWSHOWS ;
53
+ } ) ;
54
+
55
+ ipcRenderer . on ( 'notificationClicked' , ( _ , notificationIndex ) => {
56
+ webview . send ( 'notificationClicked' , notificationIndex ) ;
57
+ } ) ;
58
+
59
+ // MIXCLOUD
60
+ ipcRenderer . on ( 'playPause' , ( ) => {
61
+ console . log ( 'ipcRenderer: playPause' ) ;
62
+ webview . send ( 'playPause' ) ;
63
+ } ) ;
64
+ ipcRenderer . on ( 'next' , ( ) => {
65
+ console . log ( 'ipcRenderer: next' ) ;
66
+ webview . send ( 'next' ) ;
67
+ } ) ;
68
+
69
+ // Open all links in external browser
70
+ webview . addEventListener ( 'click' , function ( event ) {
71
+ if ( event . target . href ) {
72
+ console . log ( event . target . href ) ;
73
+ }
74
+ if ( event . target . tagName === 'A' && event . target . href . startsWith ( 'http' ) && ! event . target . href . includes ( 'https://www.mixcloud.com/' ) ) {
75
+ event . preventDefault ( ) ;
76
+ shell . openExternal ( event . target . href ) ;
77
+ }
78
+ } ) ;
79
+
80
+ if ( DEBUG ) {
81
+ webview . addEventListener ( 'dom-ready' , ( ) => {
82
+ webview . openDevTools ( ) ;
83
+ } ) ;
84
+ // webview.addEventListener('console-message', (e) => {
85
+ // console.log('Guest page logged a message:', e.message)
86
+ // });
87
+ }
88
+
89
+ // #region Notification
90
+ const notifications = [ ] ;
91
+ const NotificationOriginal = Notification ;
92
+ function NotificationDecorated ( title ) {
93
+ const notification = {
94
+ _handleClick : [ ] ,
95
+ close ( ) { } ,
96
+ addEventListener ( type , callback ) {
97
+ if ( type !== 'click' ) return ;
98
+
99
+ this . _handleClick . push ( callback ) ;
100
+ } ,
101
+ click ( ) {
102
+ for ( const callback of this . _handleClick ) {
103
+ callback . call ( this ) ;
104
+ }
105
+ }
106
+ }
107
+
108
+ ipcRenderer . send ( 'notification' , notifications . push ( notification ) - 1 , title ) ;
109
+ return notification ;
110
+ }
111
+
112
+ Object . defineProperties ( NotificationDecorated , {
113
+ permission : {
114
+ get ( ) { return NotificationOriginal . permission }
115
+ } ,
116
+ maxActions : {
117
+ get ( ) { return NotificationOriginal . maxActions }
118
+ } ,
119
+ requestPermission : {
120
+ get ( ) { return NotificationOriginal . requestPermission }
121
+ }
122
+ } ) ;
123
+
124
+ window . Notification = NotificationDecorated ;
125
+ // #endregion
126
+
127
+ // #region Custom notification sound
128
+ webFrame . registerURLSchemeAsBypassingCSP ( 'file' ) ;
129
+
130
+ const AudioOriginal = Audio ;
131
+ const beaconNotificationRegex = / b e a c o n - n o t i f i c a t i o n \. (?: .* ) $ / ;
132
+
133
+ function createObserverCallback ( tagName , callback ) {
134
+ return function ( records ) {
135
+ for ( const record of records ) {
136
+ for ( const node of record . addedNodes ) {
137
+ if ( node . tagName === tagName ) {
138
+ callback ( ) ;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ ipcRenderer . on ( 'notificationClicked' , ( _ , notificationIndex ) => {
146
+ const originalOpen = window . open ;
147
+ window . open = ( url ) => {
148
+ window . location = url ;
149
+ }
150
+ notifications [ notificationIndex ] . click ( ) ;
151
+ window . open = originalOpen ;
152
+ } ) ;
153
+
154
+ ipcRenderer . on ( 'playPause' , ( ) => {
155
+ console . log ( 'playPause' ) ;
156
+ const playPause = document . querySelector ( '[class*=PlayButton__PlayerControl]' ) ;
157
+ if ( playPause )
158
+ playPause . click ( ) ;
159
+ } ) ;
160
+
161
+ ipcRenderer . on ( 'next' , ( ) => {
162
+ // TODO
163
+ console . log ( 'next' ) ;
164
+ const row = Array . from ( document . getElementsByClassName ( 'cloudcast-upnext-row' ) ) ;
165
+ const nowPlaying = row . findIndex ( song => song . classList . contains ( 'now-playing' ) ) ;
166
+ const children = Array . from ( row [ nowPlaying + 1 ] . childNodes ) ;
167
+ const image = children . find ( child => child . classList . contains ( 'cloudcast-row-image' ) ) ;
168
+ if ( image )
169
+ image . click ( ) ;
170
+ } ) ;
171
+
172
+ // ipcRenderer.on('init', () => {
173
+ webview . addEventListener ( 'DOMContentLoaded' , ( ) => {
174
+ let currentArtist = '' ;
175
+ let currentTrack = '' ;
176
+
177
+ setInterval ( ( ) => {
178
+ console . log ( 'currentTrack' , currentTrack , 'currentArtist' , currentArtist ) ;
179
+
180
+ const titleElement = webview . querySelector ( '[class*=RebrandPlayerSliderComponent__Artist]' ) ;
181
+ if ( ! titleElement ) return ;
182
+
183
+ const title = titleElement . innerText ;
184
+ if ( title !== currentArtist ) {
185
+ currentArtist = String ( title ) ;
186
+ console . log ( 'New Artist' , currentArtist ) ;
187
+ ipcRenderer . send ( 'handlePlay' , currentArtist ) ;
188
+ }
189
+
190
+ let trackElement = webview . querySelector ( '[class*=RebrandPlayerSliderComponent__Track-]' ) ;
191
+ if ( ! trackElement ) return ;
192
+
193
+ let track = webview . querySelector ( '[class*=RebrandPlayerSliderComponent__Track-]' ) . innerText ;
194
+ if ( track !== currentTrack ) {
195
+ let trackTruncated = track . replace ( / [ \u2014 \u002d ] \s b u y $ / gi, '' ) ;
196
+ trackTruncated = trackTruncated . replace ( 'by ' , '' ) ;
197
+
198
+ currentTrack = trackTruncated ;
199
+ console . log ( 'New Track' , currentTrack ) ;
200
+
201
+ let notificationSubtitle = 'Mixcloud Play' ;
202
+
203
+ ipcRenderer . send ( 'nowPlaying' , currentTrack , title , notificationSubtitle ) ;
204
+ }
205
+ } , 2000 ) ;
206
+ } ) ;
207
+
208
+ //Elements
209
+ webview . addEventListener ( 'click' , function ( event ) {
210
+ const playPause = webview . querySelector ( '[class*=PlayButton__PlayerControl]' ) ;
211
+ const eventPath = event . path || ( event . composedPath && event . composedPath ( ) ) || [ ] ;
212
+ const playPauseClicked = eventPath . find ( path => path === playPause ) ;
213
+ if ( playPauseClicked ) {
214
+ console . log ( playPauseClicked ) ;
215
+ const paused = playPauseClicked . classList . contains ( 'dvjoTG' ) ;
216
+ console . log ( paused ) ;
217
+ let trackElement = webview . querySelector ( '[class*=RebrandPlayerControls__ShowTitle]' ) ;
218
+ if ( ! trackElement ) return ;
219
+
220
+ let track = trackElement . innerText ;
221
+ console . log ( track ) ;
222
+ ipcRenderer . send ( paused ? 'handlePause' : 'handlePlay' , track ) ;
223
+ }
224
+ console . log ( playPauseClicked ) ;
225
+ } ) ;
0 commit comments