mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2024-11-20 05:34:51 +01:00
557 lines
17 KiB
Java
557 lines
17 KiB
Java
|
|
import cafe.biskuteri.hinoki.Tree;
|
|
import cafe.biskuteri.hinoki.JsonConverter;
|
|
import cafe.biskuteri.hinoki.DSVTokeniser;
|
|
import java.net.URL;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.net.URLEncoder;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.FileReader;
|
|
import java.io.FileWriter;
|
|
import java.io.BufferedReader;
|
|
import java.io.IOException;
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
class
|
|
MastodonApi {
|
|
|
|
private String
|
|
instanceUrl;
|
|
|
|
private Tree<String>
|
|
appCredentials,
|
|
accessToken,
|
|
accountDetails;
|
|
|
|
// - -%- -
|
|
|
|
private static final String
|
|
SCOPES = "read+write";
|
|
|
|
// ---%-@-%---
|
|
|
|
public String
|
|
getInstanceUrl() { return instanceUrl; }
|
|
|
|
public Tree<String>
|
|
getAppCredentials() { return appCredentials; }
|
|
|
|
public Tree<String>
|
|
getAccessToken() { return accessToken; }
|
|
|
|
public Tree<String>
|
|
getAccountDetails() { return accountDetails; }
|
|
|
|
|
|
public void
|
|
setInstanceUrl(String a) { instanceUrl = a; }
|
|
|
|
public void
|
|
setAppCredentials(Tree<String> a) { appCredentials = a; }
|
|
|
|
public void
|
|
setAccessToken(Tree<String> a) { accessToken = a; }
|
|
|
|
public void
|
|
setAccountDetails(Tree<String> a) { accountDetails = a; }
|
|
|
|
|
|
public void
|
|
testUrlConnection(String url, RequestListener handler)
|
|
{
|
|
try
|
|
{
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
conn.connect();
|
|
|
|
returnResponseInTree(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
|
|
public void
|
|
getAppCredentials(RequestListener handler)
|
|
{
|
|
assert instanceUrl != null;
|
|
try
|
|
{
|
|
URL endpoint = new URL(instanceUrl + "/api/v1/apps");
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
conn.setRequestMethod("POST");
|
|
conn.setDoOutput(true);
|
|
conn.connect();
|
|
|
|
OutputStreamWriter output;
|
|
output = new OutputStreamWriter(conn.getOutputStream());
|
|
output.write("client_name=JKomasto alpha");
|
|
output.write("&redirect_uris=urn:ietf:wg:oauth:2.0:oob");
|
|
output.write("&scopes=" + SCOPES);
|
|
output.close();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public URI
|
|
getAuthorisationURL()
|
|
{
|
|
assert instanceUrl != null;
|
|
assert appCredentials != null;
|
|
String clientId = appCredentials.get("client_id").value;
|
|
|
|
try
|
|
{
|
|
StringBuilder b = new StringBuilder();
|
|
b.append(instanceUrl);
|
|
b.append("/oauth/authorize");
|
|
// Be careful of the spelling!!
|
|
b.append("?response_type=code");
|
|
b.append("&redirect_uri=urn:ietf:wg:oauth:2.0:oob");
|
|
b.append("&scope=" + SCOPES);
|
|
b.append("&client_id=" + clientId);
|
|
|
|
return new URI(b.toString());
|
|
}
|
|
catch (URISyntaxException eUs) { assert false; return null; }
|
|
}
|
|
|
|
public void
|
|
getAccessToken(String authorisationCode, RequestListener handler)
|
|
{
|
|
assert instanceUrl != null;
|
|
assert appCredentials != null;
|
|
String id = appCredentials.get("client_id").value;
|
|
String secret = appCredentials.get("client_secret").value;
|
|
|
|
try
|
|
{
|
|
URL endpoint = new URL(instanceUrl + "/oauth/token");
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
conn.setRequestMethod("POST");
|
|
conn.setDoOutput(true);
|
|
conn.connect();
|
|
|
|
OutputStreamWriter output;
|
|
output = new OutputStreamWriter(conn.getOutputStream());
|
|
output.write("client_id=" + id);
|
|
output.write("&client_secret=" + secret);
|
|
output.write("&redirect_uri=urn:ietf:wg:oauth:2.0:oob");
|
|
output.write("&grant_type=authorization_code");
|
|
output.write("&scope=" + SCOPES);
|
|
output.write("&code=" + authorisationCode);
|
|
output.close();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
getAccountDetails(RequestListener handler)
|
|
{
|
|
assert accessToken != null;
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
try
|
|
{
|
|
String s = "/api/v1/accounts/verify_credentials";
|
|
URL endpoint = new URL(instanceUrl + s);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s2 = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s2);
|
|
conn.connect();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
getTimelinePage(
|
|
TimelineType type,
|
|
int count, String maxId, String minId,
|
|
String accountId, String listId,
|
|
RequestListener handler)
|
|
{
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
assert !(accountId != null && listId != null);
|
|
|
|
String url = instanceUrl + "/api/v1";
|
|
if (accountId != null)
|
|
{
|
|
url += "/accounts/" + accountId + "/statuses";
|
|
}
|
|
else if (listId != null)
|
|
{
|
|
url += "/lists/" + listId;
|
|
}
|
|
else switch (type)
|
|
{
|
|
case FEDERATED:
|
|
case LOCAL: url += "/timelines/public"; break;
|
|
case HOME: url += "/timelines/home"; break;
|
|
default: assert false;
|
|
}
|
|
url += "?limit=" + count;
|
|
if (maxId != null) url += "&max_id=" + maxId;
|
|
if (minId != null) url += "&min_id=" + minId;
|
|
// This is a GET endpoint, it rejects receiving
|
|
// query params through the body.
|
|
|
|
try
|
|
{
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s2 = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s2);
|
|
conn.connect();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
setPostFavourited(
|
|
String postId, boolean favourited,
|
|
RequestListener handler)
|
|
{
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
String s1 = "/api/v1/statuses/" + postId;
|
|
String s2 = favourited ? "/favourite" : "/unfavourite";
|
|
String url = instanceUrl + s1 + s2;
|
|
try
|
|
{
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s3 = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s3);
|
|
conn.setRequestMethod("POST");
|
|
conn.connect();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
setPostBoosted(
|
|
String postId, boolean boosted,
|
|
RequestListener handler)
|
|
{
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
String s1 = "/api/v1/statuses/" + postId;
|
|
String s2 = boosted ? "/reblog" : "/unreblog";
|
|
String url = instanceUrl + s1 + s2;
|
|
try
|
|
{
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s3 = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s3);
|
|
conn.setRequestMethod("POST");
|
|
conn.connect();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
submit(
|
|
String text, PostVisibility visibility,
|
|
String replyTo, String contentWarning,
|
|
RequestListener handler)
|
|
{
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
String visibilityParam = "direct";
|
|
switch (visibility)
|
|
{
|
|
case PUBLIC: visibilityParam = "public"; break;
|
|
case UNLISTED: visibilityParam = "unlisted"; break;
|
|
case FOLLOWERS: visibilityParam = "private"; break;
|
|
case MENTIONED: visibilityParam = "direct"; break;
|
|
default: assert false;
|
|
}
|
|
|
|
String url = instanceUrl + "/api/v1/statuses";
|
|
try
|
|
{
|
|
text = encode(text);
|
|
contentWarning = encode(contentWarning);
|
|
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s1 = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s1);
|
|
String s2 = Integer.toString(text.hashCode());
|
|
conn.setRequestProperty("Idempotency-Key", s2);
|
|
conn.setDoOutput(true);
|
|
conn.setRequestMethod("POST");
|
|
conn.connect();
|
|
|
|
OutputStreamWriter output;
|
|
output = new OutputStreamWriter(conn.getOutputStream());
|
|
output.write("status=" + text);
|
|
output.write("&visibility=" + visibilityParam);
|
|
if (replyTo != null) {
|
|
output.write("&in_reply_to_id=" + replyTo);
|
|
}
|
|
if (contentWarning != null) {
|
|
output.write("&spoiler_text=" + contentWarning);
|
|
}
|
|
output.close();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
getNotifications(
|
|
int count, String maxId, String minId,
|
|
RequestListener handler)
|
|
{
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
String url = instanceUrl + "/api/v1/notifications";
|
|
url += "?limit=" + count;
|
|
if (maxId != null) url += "&max_id=" + maxId;
|
|
if (minId != null) url += "&min_id=" + minId;
|
|
|
|
try
|
|
{
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s1 = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s1);
|
|
conn.connect();
|
|
|
|
doStandardJsonReturn(conn, handler);
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
public void
|
|
monitorTimeline(
|
|
TimelineType type, ServerSideEventsListener handler)
|
|
{
|
|
String token = accessToken.get("access_token").value;
|
|
|
|
String url = instanceUrl + "/api/v1/streaming";
|
|
switch (type)
|
|
{
|
|
case FEDERATED: url += "/public"; break;
|
|
case LOCAL: url += "/public/local"; break;
|
|
case HOME: url += "/user"; break;
|
|
default: assert false;
|
|
}
|
|
|
|
try
|
|
{
|
|
URL endpoint = new URL(url);
|
|
HttpURLConnection conn;
|
|
conn = (HttpURLConnection)endpoint.openConnection();
|
|
String s = "Bearer " + token;
|
|
conn.setRequestProperty("Authorization", s);
|
|
conn.connect();
|
|
|
|
InputStreamReader input;
|
|
int code = conn.getResponseCode();
|
|
if (code >= 300)
|
|
{
|
|
input = new InputStreamReader(conn.getErrorStream());
|
|
Tree<String> response = JsonConverter.convert(input);
|
|
input.close();
|
|
handler.requestFailed(code, response);
|
|
return;
|
|
}
|
|
|
|
input = new InputStreamReader(conn.getInputStream());
|
|
BufferedReader br = new BufferedReader(input);
|
|
while (true) {
|
|
String line = br.readLine();
|
|
if (line != null) handler.lineReceived(line);
|
|
}
|
|
}
|
|
catch (IOException eIo) { handler.connectionFailed(eIo); }
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private void
|
|
doStandardJsonReturn(
|
|
HttpURLConnection conn, RequestListener handler)
|
|
throws IOException
|
|
{
|
|
InputStreamReader input;
|
|
int code = conn.getResponseCode();
|
|
if (code >= 300)
|
|
{
|
|
input = new InputStreamReader(conn.getErrorStream());
|
|
Tree<String> response = JsonConverter.convert(input);
|
|
input.close();
|
|
handler.requestFailed(code, response);
|
|
return;
|
|
}
|
|
|
|
input = new InputStreamReader(conn.getInputStream());
|
|
Tree<String> response = JsonConverter.convert(input);
|
|
input.close();
|
|
handler.requestSucceeded(response);
|
|
}
|
|
|
|
private void
|
|
returnResponseInTree(
|
|
HttpURLConnection conn, RequestListener handler)
|
|
throws IOException
|
|
{
|
|
InputStreamReader input;
|
|
int code = conn.getResponseCode();
|
|
if (code >= 300)
|
|
{
|
|
input = new InputStreamReader(conn.getErrorStream());
|
|
Tree<String> response = fromPlain(input);
|
|
input.close();
|
|
handler.requestFailed(code, response);
|
|
return;
|
|
}
|
|
|
|
input = new InputStreamReader(conn.getInputStream());
|
|
Tree<String> response = fromPlain(input);
|
|
input.close();
|
|
handler.requestSucceeded(response);
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private static Tree<String>
|
|
fromPlain(InputStreamReader r)
|
|
throws IOException
|
|
{
|
|
StringBuilder b = new StringBuilder();
|
|
int c; while ((c = r.read()) != -1) b.append((char)c);
|
|
|
|
Tree<String> leaf = new Tree<String>();
|
|
leaf.key = "body";
|
|
leaf.value = b.toString();
|
|
Tree<String> doc = new Tree<String>();
|
|
doc.add(leaf);
|
|
return doc;
|
|
}
|
|
|
|
private static String
|
|
encode(String s)
|
|
{
|
|
try {
|
|
if (s == null) return null;
|
|
return URLEncoder.encode(s, "UTF-8");
|
|
}
|
|
catch (UnsupportedEncodingException eUe) {
|
|
assert false;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ---%-@-%---
|
|
|
|
public void
|
|
loadCache()
|
|
throws IOException
|
|
{
|
|
FileReader r = new FileReader(getCachePath());
|
|
DSVTokeniser.Options o = new DSVTokeniser.Options();
|
|
Tree<String> row1 = DSVTokeniser.tokenise(r, o);
|
|
Tree<String> row2 = DSVTokeniser.tokenise(r, o);
|
|
Tree<String> row3 = DSVTokeniser.tokenise(r, o);
|
|
assert !row1.get(0).value.equals(o.endOfStreamValue);
|
|
assert !row2.get(0).value.equals(o.endOfStreamValue);
|
|
assert !row3.get(0).value.equals(o.endOfStreamValue);
|
|
r.close();
|
|
|
|
// Prepare to bark like mad.
|
|
boolean yes10 = !row1.get(0).value.equals(o.endOfStreamValue);
|
|
boolean yes20 = !row2.get(0).value.equals(o.endOfStreamValue);
|
|
boolean yes30 = !row3.get(0).value.equals(o.endOfStreamValue);
|
|
boolean yes11 = row1.size() == 1;
|
|
boolean yes21 = row2.size() == 2;
|
|
boolean yes31 = row3.size() == 1;
|
|
boolean all = yes10 & yes20 & yes30 & yes11 & yes21 & yes31;
|
|
if (!all) {
|
|
throw new IOException("Cache has invalid format!");
|
|
}
|
|
|
|
setInstanceUrl(row1.get(0).value);
|
|
appCredentials = new Tree<String>();
|
|
appCredentials.add(new Tree<String>());
|
|
appCredentials.add(new Tree<String>());
|
|
appCredentials.get(0).key = "client_id";
|
|
appCredentials.get(0).value = row2.get(0).value;
|
|
appCredentials.get(1).key = "client_secret";
|
|
appCredentials.get(1).value = row2.get(1).value;
|
|
accessToken = new Tree<String>();
|
|
accessToken.add(new Tree<String>());
|
|
accessToken.get(0).key = "access_token";
|
|
accessToken.get(0).value = row3.get(0).value;
|
|
}
|
|
|
|
public void
|
|
saveToCache()
|
|
throws IOException
|
|
{
|
|
String f10 = instanceUrl;
|
|
String f20 = appCredentials.get("client_id").value;
|
|
String f21 = appCredentials.get("client_secret").value;
|
|
String f30 = accessToken.get("access_token").value;
|
|
|
|
f10 = f10.replaceAll(":", "\\\\:") + "\n";
|
|
f20 = f20.replaceAll(":", "\\\\:") + ":";
|
|
f21 = f21.replaceAll(":", "\\\\:") + "\n";
|
|
f30 = f30.replaceAll(":", "\\\\:") + "\n";
|
|
|
|
FileWriter w = new FileWriter(getCachePath());
|
|
w.write(f10);
|
|
w.write(f20);
|
|
w.write(f21);
|
|
w.write(f30);
|
|
w.close();
|
|
}
|
|
|
|
// - -%- -
|
|
|
|
private static String
|
|
getCachePath()
|
|
{
|
|
String userHome = System.getProperty("user.home");
|
|
String osName = System.getProperty("os.name");
|
|
boolean isWindows = osName.contains("Windows");
|
|
boolean isUnix = !isWindows;
|
|
// We assume. If you're running JKomasto in classic Mac OS
|
|
// for some reason, you should probably edit the code..
|
|
String configDir = isWindows ? "AppData/Local" : ".config";
|
|
String filename = "jkomasto.cache.dsv";
|
|
String path = userHome + "/" + configDir + "/" + filename;
|
|
return path;
|
|
}
|
|
|
|
}
|