1+ package org .telegram .messenger ;
2+
3+ import android .content .Context ;
4+ import android .content .SharedPreferences ;
5+
6+ import java .security .MessageDigest ;
7+ import java .util .HashMap ;
8+ import java .util .Map ;
9+ import java .util .TreeMap ;
10+
11+ public class LastFmHelper {
12+
13+ private static LastFmHelper instance ;
14+ private SharedPreferences prefs ;
15+ private long lastApiCallTime = 0 ;
16+ private static final long API_CALL_INTERVAL = 30000 ; // 30 seconds
17+
18+ public static LastFmHelper getInstance () {
19+ if (instance == null ) {
20+ instance = new LastFmHelper ();
21+ }
22+ return instance ;
23+ }
24+
25+ private LastFmHelper () {
26+ if (ApplicationLoader .applicationContext != null ) {
27+ prefs = ApplicationLoader .applicationContext .getSharedPreferences ("lastfm" , Context .MODE_PRIVATE );
28+ }
29+ }
30+
31+ public boolean isLoggedIn () {
32+ return prefs != null && prefs .getBoolean ("logged_in" , false );
33+ }
34+
35+ public String getUsername () {
36+ return prefs != null ? prefs .getString ("username" , "" ) : "" ;
37+ }
38+
39+ public void logout () {
40+ if (prefs != null ) {
41+ prefs .edit ().clear ().apply ();
42+ }
43+ }
44+
45+ public void login (String username , String password , LoginCallback callback ) {
46+ Utilities .globalQueue .postRunnable (() -> {
47+ try {
48+ // Step 1: Get token
49+ String token = getToken ();
50+ if (token == null ) {
51+ AndroidUtilities .runOnUIThread (() -> callback .onError ("Failed to get token" ));
52+ return ;
53+ }
54+
55+ // Step 2: Get session
56+ String sessionKey = getSession (username , password , token );
57+ if (sessionKey != null ) {
58+ prefs .edit ()
59+ .putBoolean ("logged_in" , true )
60+ .putString ("username" , username )
61+ .putString ("session_key" , sessionKey )
62+ .apply ();
63+ AndroidUtilities .runOnUIThread (() -> callback .onSuccess ());
64+ } else {
65+ AndroidUtilities .runOnUIThread (() -> callback .onError ("Invalid credentials" ));
66+ }
67+ } catch (Exception e ) {
68+ FileLog .e (e );
69+ AndroidUtilities .runOnUIThread (() -> callback .onError (e .getMessage ()));
70+ }
71+ });
72+ }
73+
74+ public interface LoginCallback {
75+ void onSuccess ();
76+ void onError (String error );
77+ }
78+
79+ public void scrobbleTrack (String artist , String track , String album , long timestamp ) {
80+ if (!isLoggedIn () || artist == null || track == null ) {
81+ return ;
82+ }
83+
84+ if (!canMakeApiCall ()) {
85+ return ;
86+ }
87+
88+ Map <String , String > params = new HashMap <>();
89+ params .put ("method" , "track.scrobble" );
90+ params .put ("api_key" , BuildVars .LASTFM_API_KEY );
91+ params .put ("artist" , artist );
92+ params .put ("track" , track );
93+ if (album != null ) {
94+ params .put ("album" , album );
95+ }
96+ params .put ("timestamp" , String .valueOf (timestamp ));
97+ params .put ("sk" , getSessionKey ());
98+ params .put ("api_sig" , generateApiSignature (params ));
99+
100+ Utilities .globalQueue .postRunnable (() -> {
101+ try {
102+ sendPostRequestVoid (params );
103+ if (BuildVars .LOGS_ENABLED ) {
104+ FileLog .d ("Last.fm scrobbled: " + artist + " - " + track );
105+ }
106+ } catch (Exception e ) {
107+ FileLog .e (e );
108+ }
109+ });
110+ }
111+
112+ public void updateNowPlaying (String artist , String track , String album ) {
113+ if (!isLoggedIn () || artist == null || track == null ) {
114+ return ;
115+ }
116+
117+ if (!canMakeApiCall ()) {
118+ return ;
119+ }
120+
121+ Map <String , String > params = new HashMap <>();
122+ params .put ("method" , "track.updateNowPlaying" );
123+ params .put ("api_key" , BuildVars .LASTFM_API_KEY );
124+ params .put ("artist" , artist );
125+ params .put ("track" , track );
126+ if (album != null ) {
127+ params .put ("album" , album );
128+ }
129+ params .put ("sk" , getSessionKey ());
130+ params .put ("api_sig" , generateApiSignature (params ));
131+
132+ Utilities .globalQueue .postRunnable (() -> {
133+ try {
134+ sendPostRequestVoid (params );
135+ if (BuildVars .LOGS_ENABLED ) {
136+ FileLog .d ("Last.fm now playing: " + artist + " - " + track );
137+ }
138+ } catch (Exception e ) {
139+ FileLog .e (e );
140+ }
141+ });
142+ }
143+
144+ private String getSessionKey () {
145+ return prefs != null ? prefs .getString ("session_key" , "" ) : "" ;
146+ }
147+
148+ private boolean canMakeApiCall () {
149+ long currentTime = System .currentTimeMillis ();
150+ if (currentTime - lastApiCallTime >= API_CALL_INTERVAL ) {
151+ lastApiCallTime = currentTime ;
152+ return true ;
153+ }
154+ return false ;
155+ }
156+
157+ public String generateApiSignature (Map <String , String > params ) {
158+ TreeMap <String , String > sortedParams = new TreeMap <>(params );
159+ StringBuilder signature = new StringBuilder ();
160+
161+ for (Map .Entry <String , String > entry : sortedParams .entrySet ()) {
162+ signature .append (entry .getKey ()).append (entry .getValue ());
163+ }
164+ signature .append (BuildVars .LASTFM_API_SECRET );
165+
166+ return md5 (signature .toString ());
167+ }
168+
169+ private String md5 (String input ) {
170+ try {
171+ MessageDigest md = MessageDigest .getInstance ("MD5" );
172+ byte [] messageDigest = md .digest (input .getBytes ());
173+ StringBuilder hexString = new StringBuilder ();
174+ for (byte b : messageDigest ) {
175+ String hex = Integer .toHexString (0xff & b );
176+ if (hex .length () == 1 ) {
177+ hexString .append ('0' );
178+ }
179+ hexString .append (hex );
180+ }
181+ return hexString .toString ();
182+ } catch (Exception e ) {
183+ return "" ;
184+ }
185+ }
186+
187+ private String getToken () {
188+ try {
189+ Map <String , String > params = new HashMap <>();
190+ params .put ("method" , "auth.getToken" );
191+ params .put ("api_key" , BuildVars .LASTFM_API_KEY );
192+ params .put ("api_sig" , generateApiSignature (params ));
193+
194+ String response = sendGetRequest (params );
195+ // Parse XML response to get token
196+ // Simplified implementation - real project needs XML parser
197+ if (response != null && response .contains ("<token>" )) {
198+ int start = response .indexOf ("<token>" ) + 7 ;
199+ int end = response .indexOf ("</token>" );
200+ if (start > 6 && end > start ) {
201+ return response .substring (start , end );
202+ }
203+ }
204+ } catch (Exception e ) {
205+ FileLog .e (e );
206+ }
207+ return null ;
208+ }
209+
210+ private String getSession (String username , String password , String token ) {
211+ try {
212+ Map <String , String > params = new HashMap <>();
213+ params .put ("method" , "auth.getMobileSession" );
214+ params .put ("api_key" , BuildVars .LASTFM_API_KEY );
215+ params .put ("username" , username );
216+ params .put ("password" , password );
217+ params .put ("api_sig" , generateApiSignature (params ));
218+
219+ String response = sendPostRequest (params );
220+ // Parse XML response to get session key
221+ if (response != null && response .contains ("<key>" )) {
222+ int start = response .indexOf ("<key>" ) + 5 ;
223+ int end = response .indexOf ("</key>" );
224+ if (start > 4 && end > start ) {
225+ return response .substring (start , end );
226+ }
227+ }
228+ } catch (Exception e ) {
229+ FileLog .e (e );
230+ }
231+ return null ;
232+ }
233+
234+ private String sendGetRequest (Map <String , String > params ) {
235+ try {
236+ StringBuilder urlBuilder = new StringBuilder (BuildVars .LASTFM_API_URL + "?" );
237+ for (Map .Entry <String , String > entry : params .entrySet ()) {
238+ if (urlBuilder .length () > BuildVars .LASTFM_API_URL .length () + 1 ) {
239+ urlBuilder .append ('&' );
240+ }
241+ urlBuilder .append (java .net .URLEncoder .encode (entry .getKey (), "UTF-8" ));
242+ urlBuilder .append ('=' );
243+ urlBuilder .append (java .net .URLEncoder .encode (entry .getValue (), "UTF-8" ));
244+ }
245+
246+ java .net .URL url = new java .net .URL (urlBuilder .toString ());
247+ java .net .HttpURLConnection connection = (java .net .HttpURLConnection ) url .openConnection ();
248+ connection .setRequestMethod ("GET" );
249+
250+ int responseCode = connection .getResponseCode ();
251+ if (responseCode == 200 ) {
252+ java .io .BufferedReader reader = new java .io .BufferedReader (
253+ new java .io .InputStreamReader (connection .getInputStream ()));
254+ StringBuilder response = new StringBuilder ();
255+ String line ;
256+ while ((line = reader .readLine ()) != null ) {
257+ response .append (line );
258+ }
259+ reader .close ();
260+ connection .disconnect ();
261+ return response .toString ();
262+ }
263+ connection .disconnect ();
264+ } catch (Exception e ) {
265+ FileLog .e (e );
266+ }
267+ return null ;
268+ }
269+
270+ private String sendPostRequest (Map <String , String > params ) {
271+ try {
272+ java .net .URL url = new java .net .URL (BuildVars .LASTFM_API_URL );
273+ java .net .HttpURLConnection connection = (java .net .HttpURLConnection ) url .openConnection ();
274+ connection .setRequestMethod ("POST" );
275+ connection .setDoOutput (true );
276+ connection .setRequestProperty ("Content-Type" , "application/x-www-form-urlencoded" );
277+
278+ StringBuilder postData = new StringBuilder ();
279+ for (Map .Entry <String , String > entry : params .entrySet ()) {
280+ if (postData .length () != 0 ) {
281+ postData .append ('&' );
282+ }
283+ postData .append (java .net .URLEncoder .encode (entry .getKey (), "UTF-8" ));
284+ postData .append ('=' );
285+ postData .append (java .net .URLEncoder .encode (entry .getValue (), "UTF-8" ));
286+ }
287+
288+ byte [] postDataBytes = postData .toString ().getBytes ("UTF-8" );
289+ connection .setRequestProperty ("Content-Length" , String .valueOf (postDataBytes .length ));
290+ connection .getOutputStream ().write (postDataBytes );
291+
292+ int responseCode = connection .getResponseCode ();
293+ if (responseCode == 200 ) {
294+ java .io .BufferedReader reader = new java .io .BufferedReader (
295+ new java .io .InputStreamReader (connection .getInputStream ()));
296+ StringBuilder response = new StringBuilder ();
297+ String line ;
298+ while ((line = reader .readLine ()) != null ) {
299+ response .append (line );
300+ }
301+ reader .close ();
302+ connection .disconnect ();
303+ return response .toString ();
304+ }
305+
306+ if (BuildVars .LOGS_ENABLED ) {
307+ FileLog .d ("Last.fm API response code: " + responseCode );
308+ }
309+
310+ connection .disconnect ();
311+ } catch (Exception e ) {
312+ FileLog .e (e );
313+ }
314+ return null ;
315+ }
316+
317+ private void sendPostRequestVoid (Map <String , String > params ) {
318+ try {
319+ java .net .URL url = new java .net .URL (BuildVars .LASTFM_API_URL );
320+ java .net .HttpURLConnection connection = (java .net .HttpURLConnection ) url .openConnection ();
321+ connection .setRequestMethod ("POST" );
322+ connection .setDoOutput (true );
323+ connection .setRequestProperty ("Content-Type" , "application/x-www-form-urlencoded" );
324+
325+ StringBuilder postData = new StringBuilder ();
326+ for (Map .Entry <String , String > entry : params .entrySet ()) {
327+ if (postData .length () != 0 ) {
328+ postData .append ('&' );
329+ }
330+ postData .append (java .net .URLEncoder .encode (entry .getKey (), "UTF-8" ));
331+ postData .append ('=' );
332+ postData .append (java .net .URLEncoder .encode (entry .getValue (), "UTF-8" ));
333+ }
334+
335+ byte [] postDataBytes = postData .toString ().getBytes ("UTF-8" );
336+ connection .setRequestProperty ("Content-Length" , String .valueOf (postDataBytes .length ));
337+ connection .getOutputStream ().write (postDataBytes );
338+
339+ int responseCode = connection .getResponseCode ();
340+ if (BuildVars .LOGS_ENABLED ) {
341+ FileLog .d ("Last.fm API response code: " + responseCode );
342+ }
343+
344+ connection .disconnect ();
345+ } catch (Exception e ) {
346+ FileLog .e (e );
347+ }
348+ }
349+ }
0 commit comments