Skip to content

Commit bfb386b

Browse files
committed
Added integration of last.fm.
1 parent 896a7d7 commit bfb386b

8 files changed

Lines changed: 671 additions & 2 deletions

File tree

.github/workflows/tandroid.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ jobs:
6767
echo "USER_REPO=$USER_REPO" >> $vars
6868
echo "CHECK_UPDATES=${{ matrix.updates }}" >> $vars
6969
echo "USER_ID_OWNER=${{ secrets.USER_ID_OWNER }}" >> $vars
70+
echo "LASTFM_API_KEY=${{ secrets.LASTFM_API_KEY }}" >> $vars
71+
echo "LASTFM_API_SECRET=${{ secrets.LASTFM_API_SECRET }}" >> $vars
7072
7173
###
7274
echo $ADDITIONAL_BUILD_NUMBER

TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public class BuildVars {
3333
public static String HUAWEI_STORE_URL = "https://appgallery.huawei.com/app/C101184875";
3434
public static String GOOGLE_AUTH_CLIENT_ID = "760348033671-81kmi3pi84p11ub8hp9a1funsv0rn2p9.apps.googleusercontent.com";
3535

36+
// Last.fm API constants
37+
public static String LASTFM_API_KEY = BuildConfig.LASTFM_API_KEY;
38+
public static String LASTFM_API_SECRET = BuildConfig.LASTFM_API_SECRET;
39+
public static String LASTFM_API_URL = "https://ws.audioscrobbler.com/2.0/";
3640

3741
// You can use this flag to disable Google Play Billing (If you're making fork and want it to be in Google Play)
3842
public static boolean IS_BILLING_UNAVAILABLE = false;
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
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

Comments
 (0)