Skip to content

Instantly share code, notes, and snippets.

@Exerosis
Created March 14, 2018 21:12
Show Gist options
  • Select an option

  • Save Exerosis/67389b682aa0f98e1dc2f417a1bfec4c to your computer and use it in GitHub Desktop.

Select an option

Save Exerosis/67389b682aa0f98e1dc2f417a1bfec4c to your computer and use it in GitHub Desktop.
package com.mynt.backend.discord;
import com.mynt.backend.discord.responses.Token;
import com.mynt.backend.discord.responses.User;
import io.reactivex.Maybe;
import retrofit2.http.*;
import static com.mynt.backend.discord.Strings.*;
public interface DiscordAPI {
@GET(USERS)
Maybe<User> users(@Header(HEADER_AUTHORIZATION) String token, @Path(PATH_USER) String user);
@FormUrlEncoded
@POST(TOKEN)
Maybe<Token> token(@Field(FIELD_CLIENT_ID) String clientId,
@Field(FIELD_CLIENT_SECRET) String clientSecret,
@Field(FIELD_GRANT_TYPE) String grantType,
@Field(FIELD_CODE) String code);
default Maybe<Token> token(String code) {
return token(CLIENT_ID, CLIENT_SECRET, GRANT_TYPE, code);
}
}
package com.mynt.backend.discord;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mynt.backend.discord.responses.User;
import io.reactivex.Observable;
import io.reactivex.ObservableTransformer;
import io.reactivex.disposables.Disposable;
import io.reactivex.observables.ConnectableObservable;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.apache.commons.io.IOUtils;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import spark.Spark;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
import static com.mynt.backend.discord.Strings.*;
import static io.reactivex.schedulers.Schedulers.io;
import static java.lang.String.*;
import static java.nio.charset.Charset.defaultCharset;
import static retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory.createWithScheduler;
import static spark.Spark.port;
public class DiscordConnection {
private final ConnectableObservable<UUID> login;
private final ConnectableObservable<UUID> home;
private final ConnectableObservable<User> authenticated;
private final List<Disposable> disposables = new ArrayList<>();
public DiscordConnection() {
ServerAPI server = new ServerAPI();
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES)
.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
DiscordAPI discord = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(createWithScheduler(io()))
.baseUrl(format(URL_DISCORD, ""))
.client(client)
.build()
.create(DiscordAPI.class);
login = server.login().compose(schedulers()).publish();
home = server.home().compose(schedulers()).publish();
authenticated = server.codes()
.flatMapMaybe(code -> discord.token(code.getCode())
.flatMap(token -> discord.users(token.getTokenType() + " " + token.getAccessToken(), USER)
.doOnSuccess(user -> user.setUuid(code.getUuid()))
)
).compose(schedulers()).publish();
}
public Observable<User> onAuthenticated() {
return authenticated;
}
public Observable<UUID> onHome() {
return home;
}
public Observable<UUID> onLogin() {
return login;
}
public String start(int port) {
port(port);
disposables.add(home.connect());
disposables.add(login.connect());
disposables.add(authenticated.connect());
try {
return urlLocal(IOUtils.toString(new URL(URL_IP_CHECK), defaultCharset()).trim(), port, LOGIN);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void stop() {
Spark.stop();
disposables.forEach(Disposable::dispose);
}
private static <Type> ObservableTransformer<Type, Type> schedulers() {
return upstream -> upstream;
}
}
package com.mynt.backend.discord;
import java.util.UUID;
public class DiscordTest {
public static void main(String[] args) throws InterruptedException {
int port = 25565;
DiscordConnection discord = new DiscordConnection();
String loginUrl = discord.start(port);
System.out.println(loginUrl + "?uuid=" + UUID.randomUUID());
discord.onLogin().subscribe(System.out::println);
discord.onHome().subscribe(System.out::println);
discord.onAuthenticated().subscribe(user -> {
System.out.println();
System.out.println("Authenticated user!");
System.out.println(user.getUsername() + "#" + user.getDiscriminator());
System.out.println(user.getEmail());
System.out.println(user.getUuid());
System.out.println();
});
Thread.sleep(100_000);
}
}
package com.mynt.backend.discord.responses;
import lombok.Data;
import java.util.UUID;
@Data
public class Code {
private final String code;
private final UUID uuid;
}
package com.mynt.backend.discord.responses;
import lombok.Data;
@Data
public class Token {
private final String accessToken;
private final String tokenType;
private final int expiresIn;
private final String refreshToken;
private final String scope;
}
package com.mynt.backend.discord.responses;
import lombok.Data;
import java.util.UUID;
@Data
public class User {
private final String username;
private final String discriminator;
private final String email;
private final String avatar;
private final boolean verified;
private transient UUID uuid;
}
package com.mynt.backend.discord;
import com.mynt.backend.discord.responses.Code;
import io.reactivex.Observable;
import java.util.UUID;
import static com.mynt.backend.discord.Strings.*;
import static io.reactivex.Observable.*;
import static java.lang.String.format;
import static java.util.UUID.*;
import static spark.Spark.*;
public class ServerAPI {
public Observable<Code> codes() {
return create(observer -> get(CODES, (request, response) -> {
String code = request.queryParams(PARAM_CODE);
String state = request.queryParams(PARAM_STATE);
if (code == null)
return ERROR_CODE;
if (state == null)
return ERROR_STATE;
observer.onNext(new Code(code, fromString(state)));
return format(SCRIPT_REDIRECT, urlLocal(request.ip(), request.port(), format(STATE_HOME, state)));
}));
}
public Observable<UUID> home() {
return create(observer -> get(HOME, (request, response) -> {
String uuid = request.queryParams(PARAM_UUID);
if (uuid != null)
observer.onNext(fromString(uuid));
return HOME_MESSAGE;
}));
}
public Observable<UUID> login() {
return create(observer -> get(LOGIN, (request, response) -> {
String uuid = request.queryParams(PARAM_UUID);
if (uuid == null)
return ERROR_UUID;
observer.onNext(fromString(uuid));
response.redirect(format(URL_AUTHORIZE, uuid, encode(urlLocal(request.ip(), request.port(), CODES))));
return response;
}));
}
}
package com.mynt.backend.discord;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import static java.lang.String.format;
public class Strings {
//--Misc--
static final String SCRIPT_REDIRECT = "<script>window.location.href = \"%s\";</script>";
static final String ENCODING = "UTF-8";
static final String GRANT_TYPE = "authorization_code";
static final String USER = "@me";
static final String HOME_MESSAGE = "Home :)";
static final String HEADER_AUTHORIZATION = "Authorization";
static final String PATH_USER = "user";
//--Client--
static final String CLIENT_ID = "374400233448275981";
static final String CLIENT_SECRET = "is9ZQgY_kgvJ_d6fbGhc2QsarSOeOXND";
//--URLs--
static final String URL_IP_CHECK = "http://checkip.amazonaws.com/";
static final String URL_LOCAL = "http://%s:%s/%s";
static final String URL_DISCORD = "https://discordapp.com/api/v6/%s";
static final String URL_AUTHORIZE;
//--Paths--
static final String AUTHORIZE = "oauth2/authorize";
static final String USERS = "users/{" + PATH_USER + "}";
static final String TOKEN = "oauth2/token";
static final String CODES = "codes";
static final String HOME = "home";
static final String STATE_HOME = "home?uuid=%s";
static final String LOGIN = "login";
//--Params--
static final String PARAM_CODE = "code";
static final String PARAM_STATE = "state";
static final String PARAM_UUID = "uuid";
//--ERROR--
static final String ERROR_UUID = "Missing 'uuid' query param.";
static final String ERROR_CODE = "Missing 'code' query param.";
static final String ERROR_STATE = "Missing 'state' query param.";
//--Fields--
static final String FIELD_CLIENT_ID = "client_id";
static final String FIELD_CLIENT_SECRET = "client_secret";
static final String FIELD_GRANT_TYPE = "grant_type";
static final String FIELD_CODE = "code";
static {
URL_AUTHORIZE = format(URL_DISCORD, AUTHORIZE) +
"?client_id=" + CLIENT_ID +
"&response_type=code" +
"&scope=email" +
"&state=" + "%s" +
"&redirect_uri=" + "%s";
}
public static String urlLocal(String ip, int port, String path) {
return format(URL_LOCAL, ip, port, path);
}
public static String encode(String url) {
try {
return URLEncoder.encode(url, ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
package com.mynt.backend.discord;
import com.mynt.backend.discord.responses.Token;
import com.mynt.backend.discord.responses.User;
import io.reactivex.Maybe;
import retrofit2.http.*;
import static com.mynt.backend.discord.Strings.*;
public interface DiscordAPI {
@GET(USERS)
Maybe<User> users(@Header(HEADER_AUTHORIZATION) String token, @Path(PATH_USER) String user);
@FormUrlEncoded
@POST(TOKEN)
Maybe<Token> token(@Field(FIELD_CLIENT_ID) String clientId,
@Field(FIELD_CLIENT_SECRET) String clientSecret,
@Field(FIELD_GRANT_TYPE) String grantType,
@Field(FIELD_CODE) String code);
default Maybe<Token> token(String code) {
return token(CLIENT_ID, CLIENT_SECRET, GRANT_TYPE, code);
}
}
package com.mynt.backend.discord;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mynt.backend.discord.responses.User;
import io.reactivex.Observable;
import io.reactivex.ObservableTransformer;
import io.reactivex.disposables.Disposable;
import io.reactivex.observables.ConnectableObservable;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.apache.commons.io.IOUtils;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import spark.Spark;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
import static com.mynt.backend.discord.Strings.*;
import static io.reactivex.schedulers.Schedulers.io;
import static java.lang.String.*;
import static java.nio.charset.Charset.defaultCharset;
import static retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory.createWithScheduler;
import static spark.Spark.port;
public class DiscordConnection {
private final ConnectableObservable<UUID> login;
private final ConnectableObservable<UUID> home;
private final ConnectableObservable<User> authenticated;
private final List<Disposable> disposables = new ArrayList<>();
public DiscordConnection() {
ServerAPI server = new ServerAPI();
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES)
.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
DiscordAPI discord = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(createWithScheduler(io()))
.baseUrl(format(URL_DISCORD, ""))
.client(client)
.build()
.create(DiscordAPI.class);
login = server.login().compose(schedulers()).publish();
home = server.home().compose(schedulers()).publish();
authenticated = server.codes()
.flatMapMaybe(code -> discord.token(code.getCode())
.flatMap(token -> discord.users(token.getTokenType() + " " + token.getAccessToken(), USER)
.doOnSuccess(user -> user.setUuid(code.getUuid()))
)
).compose(schedulers()).publish();
}
public Observable<User> onAuthenticated() {
return authenticated;
}
public Observable<UUID> onHome() {
return home;
}
public Observable<UUID> onLogin() {
return login;
}
public String start(int port) {
port(port);
disposables.add(home.connect());
disposables.add(login.connect());
disposables.add(authenticated.connect());
try {
return urlLocal(IOUtils.toString(new URL(URL_IP_CHECK), defaultCharset()).trim(), port, LOGIN);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void stop() {
Spark.stop();
disposables.forEach(Disposable::dispose);
}
private static <Type> ObservableTransformer<Type, Type> schedulers() {
return upstream -> upstream;
}
}
package com.mynt.backend.discord;
import java.util.UUID;
public class DiscordTest {
public static void main(String[] args) throws InterruptedException {
int port = 25565;
DiscordConnection discord = new DiscordConnection();
String loginUrl = discord.start(port);
System.out.println(loginUrl + "?uuid=" + UUID.randomUUID());
discord.onLogin().subscribe(System.out::println);
discord.onHome().subscribe(System.out::println);
discord.onAuthenticated().subscribe(user -> {
System.out.println();
System.out.println("Authenticated user!");
System.out.println(user.getUsername() + "#" + user.getDiscriminator());
System.out.println(user.getEmail());
System.out.println(user.getUuid());
System.out.println();
});
Thread.sleep(100_000);
}
}
package com.mynt.backend.discord.responses;
import lombok.Data;
import java.util.UUID;
@Data
public class Code {
private final String code;
private final UUID uuid;
}
package com.mynt.backend.discord.responses;
import lombok.Data;
@Data
public class Token {
private final String accessToken;
private final String tokenType;
private final int expiresIn;
private final String refreshToken;
private final String scope;
}
package com.mynt.backend.discord.responses;
import lombok.Data;
import java.util.UUID;
@Data
public class User {
private final String username;
private final String discriminator;
private final String email;
private final String avatar;
private final boolean verified;
private transient UUID uuid;
}
package com.mynt.backend.discord;
import com.mynt.backend.discord.responses.Code;
import io.reactivex.Observable;
import java.util.UUID;
import static com.mynt.backend.discord.Strings.*;
import static io.reactivex.Observable.*;
import static java.lang.String.format;
import static java.util.UUID.*;
import static spark.Spark.*;
public class ServerAPI {
public Observable<Code> codes() {
return create(observer -> get(CODES, (request, response) -> {
String code = request.queryParams(PARAM_CODE);
String state = request.queryParams(PARAM_STATE);
if (code == null)
return ERROR_CODE;
if (state == null)
return ERROR_STATE;
observer.onNext(new Code(code, fromString(state)));
return format(SCRIPT_REDIRECT, urlLocal(request.ip(), request.port(), format(STATE_HOME, state)));
}));
}
public Observable<UUID> home() {
return create(observer -> get(HOME, (request, response) -> {
String uuid = request.queryParams(PARAM_UUID);
if (uuid != null)
observer.onNext(fromString(uuid));
return HOME_MESSAGE;
}));
}
public Observable<UUID> login() {
return create(observer -> get(LOGIN, (request, response) -> {
String uuid = request.queryParams(PARAM_UUID);
if (uuid == null)
return ERROR_UUID;
observer.onNext(fromString(uuid));
response.redirect(format(URL_AUTHORIZE, uuid, encode(urlLocal(request.ip(), request.port(), CODES))));
return response;
}));
}
}
package com.mynt.backend.discord;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import static java.lang.String.format;
public class Strings {
//--Misc--
static final String SCRIPT_REDIRECT = "<script>window.location.href = \"%s\";</script>";
static final String ENCODING = "UTF-8";
static final String GRANT_TYPE = "authorization_code";
static final String USER = "@me";
static final String HOME_MESSAGE = "Home :)";
static final String HEADER_AUTHORIZATION = "Authorization";
static final String PATH_USER = "user";
//--Client--
static final String CLIENT_ID = "374400233448275981";
static final String CLIENT_SECRET = "is9ZQgY_kgvJ_d6fbGhc2QsarSOeOXND";
//--URLs--
static final String URL_IP_CHECK = "http://checkip.amazonaws.com/";
static final String URL_LOCAL = "http://%s:%s/%s";
static final String URL_DISCORD = "https://discordapp.com/api/v6/%s";
static final String URL_AUTHORIZE;
//--Paths--
static final String AUTHORIZE = "oauth2/authorize";
static final String USERS = "users/{" + PATH_USER + "}";
static final String TOKEN = "oauth2/token";
static final String CODES = "codes";
static final String HOME = "home";
static final String STATE_HOME = "home?uuid=%s";
static final String LOGIN = "login";
//--Params--
static final String PARAM_CODE = "code";
static final String PARAM_STATE = "state";
static final String PARAM_UUID = "uuid";
//--ERROR--
static final String ERROR_UUID = "Missing 'uuid' query param.";
static final String ERROR_CODE = "Missing 'code' query param.";
static final String ERROR_STATE = "Missing 'state' query param.";
//--Fields--
static final String FIELD_CLIENT_ID = "client_id";
static final String FIELD_CLIENT_SECRET = "client_secret";
static final String FIELD_GRANT_TYPE = "grant_type";
static final String FIELD_CODE = "code";
static {
URL_AUTHORIZE = format(URL_DISCORD, AUTHORIZE) +
"?client_id=" + CLIENT_ID +
"&response_type=code" +
"&scope=email" +
"&state=" + "%s" +
"&redirect_uri=" + "%s";
}
public static String urlLocal(String ip, int port, String path) {
return format(URL_LOCAL, ip, port, path);
}
public static String encode(String url) {
try {
return URLEncoder.encode(url, ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment