This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

5.0 kB 141 lines
1package us.lassul.assist; 2 3import android.content.Context; 4import android.content.SharedPreferences; 5import java.io.ByteArrayOutputStream; 6import java.io.IOException; 7import java.io.InputStream; 8import java.io.OutputStream; 9import java.net.HttpURLConnection; 10import java.net.URL; 11import java.nio.charset.StandardCharsets; 12import org.json.JSONException; 13import org.json.JSONObject; 14 15/** 16 * hermes-voice's API (tools/hermes-voice/hermes_voice.py) behind assist.lassul.us/app/, where the 17 * proxy passes the app's own bearer token through instead of asking for the web login. 18 */ 19final class Api { 20 private static final String BASE = "https://assist.lassul.us/app"; 21 private static final String PREFS = "api"; 22 23 /** An answer other than 2xx, with the server's error text. */ 24 static final class Failure extends IOException { 25 Failure(String message) { 26 super(message); 27 } 28 } 29 30 private final String token; 31 32 private Api(String token) { 33 this.token = token; 34 } 35 36 /** Null until the token is set (SetupActivity). */ 37 static Api load(Context context) { 38 String token = prefs(context).getString("token", null); 39 return token == null || token.isEmpty() ? null : new Api(token); 40 } 41 42 static void saveToken(Context context, String token) { 43 prefs(context).edit().putString("token", token.trim()).apply(); 44 } 45 46 static boolean hasToken(Context context) { 47 return load(context) != null; 48 } 49 50 private static SharedPreferences prefs(Context context) { 51 return context.getSharedPreferences(PREFS, Context.MODE_PRIVATE); 52 } 53 54 JSONObject get(String path) throws IOException, JSONException { 55 HttpURLConnection connection = open(path, "GET"); 56 try { 57 return answer(connection); 58 } finally { 59 connection.disconnect(); 60 } 61 } 62 63 JSONObject post(String path, JSONObject body) throws IOException, JSONException { 64 HttpURLConnection connection = open(path, "POST"); 65 try { 66 send(connection, body); 67 return answer(connection); 68 } finally { 69 connection.disconnect(); 70 } 71 } 72 73 /** 74 * A POST whose answer is read as a stream (/delegate's NDJSON); the caller reads and 75 * disconnects. Throws Failure on an error answer. 76 */ 77 HttpURLConnection stream(String path, JSONObject body) throws IOException, JSONException { 78 HttpURLConnection connection = open(path, "POST"); 79 // The server writes a keepalive line every 10 s, so a long silence means it is gone. 80 connection.setReadTimeout(60_000); 81 send(connection, body); 82 int status = connection.getResponseCode(); 83 if (status / 100 != 2) { 84 String error = errorOf(connection, status); 85 connection.disconnect(); 86 throw new Failure(error); 87 } 88 return connection; 89 } 90 91 private HttpURLConnection open(String path, String method) throws IOException { 92 HttpURLConnection connection = (HttpURLConnection) new URL(BASE + path).openConnection(); 93 connection.setRequestMethod(method); 94 connection.setRequestProperty("Authorization", "Bearer " + token); 95 connection.setConnectTimeout(10_000); 96 connection.setReadTimeout(30_000); 97 return connection; 98 } 99 100 private static void send(HttpURLConnection connection, JSONObject body) throws IOException { 101 byte[] bytes = body.toString().getBytes(StandardCharsets.UTF_8); 102 connection.setDoOutput(true); 103 connection.setRequestProperty("Content-Type", "application/json"); 104 connection.setFixedLengthStreamingMode(bytes.length); 105 try (OutputStream out = connection.getOutputStream()) { 106 out.write(bytes); 107 } 108 } 109 110 private static JSONObject answer(HttpURLConnection connection) 111 throws IOException, JSONException { 112 int status = connection.getResponseCode(); 113 if (status / 100 != 2) { 114 throw new Failure(errorOf(connection, status)); 115 } 116 return new JSONObject(read(connection.getInputStream())); 117 } 118 119 private static String errorOf(HttpURLConnection connection, int status) { 120 try { 121 InputStream stream = connection.getErrorStream(); 122 if (stream != null) { 123 return new JSONObject(read(stream)).optString("error", "HTTP " + status); 124 } 125 } catch (IOException | JSONException ignored) { 126 // The status says enough. 127 } 128 return "HTTP " + status; 129 } 130 131 private static String read(InputStream stream) throws IOException { 132 ByteArrayOutputStream out = new ByteArrayOutputStream(); 133 byte[] buffer = new byte[8192]; 134 try (InputStream in = stream) { 135 for (int n; (n = in.read(buffer)) > 0; ) { 136 out.write(buffer, 0, n); 137 } 138 } 139 return new String(out.toByteArray(), StandardCharsets.UTF_8); 140 } 141}