diff --git a/API/src/main/java/me/braydon/tether/model/DiscordUser.java b/API/src/main/java/me/braydon/tether/model/DiscordUser.java index 87ec5a6..af848dc 100644 --- a/API/src/main/java/me/braydon/tether/model/DiscordUser.java +++ b/API/src/main/java/me/braydon/tether/model/DiscordUser.java @@ -4,7 +4,6 @@ import lombok.*; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.*; -import java.text.SimpleDateFormat; import java.util.EnumSet; import java.util.List; import java.util.Objects; @@ -153,12 +152,12 @@ public final class DiscordUser { @AllArgsConstructor @Getter @EqualsAndHashCode public static class Banner { /** - * The id of the user's avatar. + * The id of the user's banner. */ @NonNull private final String id; /** - * The URL of the user's avatar. + * The URL of the user's banner. */ @NonNull private final String url; } @@ -184,14 +183,14 @@ public final class DiscordUser { @NonNull private final String album; /** - * The current progress of the track. + * The current progress of the track (in millis). */ - @NonNull private final String trackProgress; + private final long trackProgress; /** - * The total length of the track. + * The total length of the track (in millis). */ - @NonNull private final String trackLength; + private final long trackLength; /** * The unix time of when this track started playing. @@ -211,7 +210,6 @@ public final class DiscordUser { */ @NonNull @SuppressWarnings("DataFlowIssue") public static SpotifyActivity fromActivity(@NonNull RichPresence richPresence) { - SimpleDateFormat dateFormat = new SimpleDateFormat("m:ss"); long started = Objects.requireNonNull(richPresence.getTimestamps()).getStart(); long ends = richPresence.getTimestamps().getEnd(); @@ -220,8 +218,7 @@ public final class DiscordUser { return new SpotifyActivity( richPresence.getDetails(), richPresence.getState().replace(";", ","), - richPresence.getLargeImage().getText(), dateFormat.format(trackProgress), - dateFormat.format(trackLength), started, ends + richPresence.getLargeImage().getText(), trackProgress, trackLength, started, ends ); } } diff --git a/JS-SDK/.gitignore b/JS-SDK/.gitignore new file mode 100644 index 0000000..cf87e5b --- /dev/null +++ b/JS-SDK/.gitignore @@ -0,0 +1,4 @@ +node_modules +.idea/ +.vscode/ +dist \ No newline at end of file diff --git a/JS-SDK/.npmrc b/JS-SDK/.npmrc new file mode 100644 index 0000000..bd3327a --- /dev/null +++ b/JS-SDK/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${NPM_TOKEN} \ No newline at end of file diff --git a/JS-SDK/README.md b/JS-SDK/README.md new file mode 100644 index 0000000..42dfc15 --- /dev/null +++ b/JS-SDK/README.md @@ -0,0 +1 @@ +# JS-SDK \ No newline at end of file diff --git a/JS-SDK/build.mjs b/JS-SDK/build.mjs new file mode 100644 index 0000000..9f65438 --- /dev/null +++ b/JS-SDK/build.mjs @@ -0,0 +1,8 @@ +import dts from "bun-plugin-dts"; + +await Bun.build({ + entrypoints: ["./src/index.ts"], + outdir: "./dist", + minify: true, + plugins: [dts()], +}); diff --git a/JS-SDK/bun.lockb b/JS-SDK/bun.lockb new file mode 100644 index 0000000..9631a51 Binary files /dev/null and b/JS-SDK/bun.lockb differ diff --git a/JS-SDK/package.json b/JS-SDK/package.json new file mode 100644 index 0000000..29a8826 --- /dev/null +++ b/JS-SDK/package.json @@ -0,0 +1,39 @@ +{ + "name": "usetether", + "version": "1.0.0", + "author": "Braydon (Rainnny) ", + "description": "An API designed to provide real-time access to a user's Discord data.", + "keywords": [ + "java", + "discord", + "api", + "restful", + "realtime" + ], + "homepage": "https://github.com/Rainnny7/Tether", + "repository": { + "type": "git", + "url": "git+https://github.com/Rainnny7/Tether.git" + }, + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun run build.mjs", + "prepublishOnly": "bun run build" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@types/bun": "latest", + "bun-plugin-dts": "^0.2.2" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@types/react": "^18.3.5", + "react": "^18.3.1" + } +} \ No newline at end of file diff --git a/JS-SDK/src/index.ts b/JS-SDK/src/index.ts new file mode 100644 index 0000000..5b6562a --- /dev/null +++ b/JS-SDK/src/index.ts @@ -0,0 +1,5 @@ +export * from "@/lib/tether"; + +// Types +export * from "@/types/config"; +export * from "@/types/user"; \ No newline at end of file diff --git a/JS-SDK/src/lib/tether.ts b/JS-SDK/src/lib/tether.ts new file mode 100644 index 0000000..b56d0a6 --- /dev/null +++ b/JS-SDK/src/lib/tether.ts @@ -0,0 +1,37 @@ +import TetherConfig from "@/types/config"; +import DiscordUser from "@/types/user"; +import {useEffect, useState} from "react"; + +export const useTether = (snowflake: number, {endpoint = "usetether.rest", secure = true, realtime = false}: TetherConfig): DiscordUser | undefined => { + const [user] = useState(); + + useEffect(() => { + let socket: WebSocket; // The current WebSocket connection + + /** + * Establish a connection with the API. + */ + const connect = () => { + socket = new WebSocket(`ws${secure && "s"}://${endpoint}/gateway`); // Connect to the gateway + + socket.addEventListener("open", () => { + console.log("[Tether] WebSocket connection established!"); + }); + + socket.addEventListener("close", connect); + + socket.addEventListener("message", event => { + console.log("data:", event.data); + }); + }; + connect(); + + // Cleanup + return () => { + socket.removeEventListener("close", connect); + socket.close(); + }; + }, [endpoint, secure]); + + return user; +} \ No newline at end of file diff --git a/JS-SDK/src/types/config.ts b/JS-SDK/src/types/config.ts new file mode 100644 index 0000000..1ce1493 --- /dev/null +++ b/JS-SDK/src/types/config.ts @@ -0,0 +1,17 @@ +type TetherConfig = { + /** + * The API endpoint to connect to. + */ + endpoint?: string, + + /** + * Whether the connection should be secure. + */ + secure?: boolean, + + /** + * Whether the data should be fetched in real-time. + */ + realtime?: boolean, +}; +export default TetherConfig; \ No newline at end of file diff --git a/JS-SDK/src/types/socket.ts b/JS-SDK/src/types/socket.ts new file mode 100644 index 0000000..29f4633 --- /dev/null +++ b/JS-SDK/src/types/socket.ts @@ -0,0 +1,7 @@ +type SocketPacket = { + /** + * The OP code for this packet. + */ + op: number; +}; +export default SocketPacket; \ No newline at end of file diff --git a/JS-SDK/src/types/user.ts b/JS-SDK/src/types/user.ts new file mode 100644 index 0000000..9dab9b2 --- /dev/null +++ b/JS-SDK/src/types/user.ts @@ -0,0 +1,148 @@ +type DiscordUser = { + /** + * The unique snowflake of this user. + */ + snowflake: number; + + /** + * The username of this user. + */ + username: string; + + /** + * The display name of this user, if any. + */ + displayName: string | undefined; + + /** + * The flags of this user. + */ + flags: UserFlags; + + /** + * The avatar of this user. + */ + avatar: Avatar; + + /** + * The banner of this user, if any. + */ + banner: Banner | undefined; + + /** + * The accent color of this user. + */ + accentColor: string; + + /** + * The online status of this user, if known. + */ + onlineStatus: "ONLINE" | "IDLE" | "DO_NOT_DISTURB" | "OFFLINE" | "UNKNOWN" | undefined; + + /** + * The clients this user is active on, if known. + */ + activeClients: "DESKTOP" | "MOBILE" | "WEB" | undefined; + + /** + * The Spotify activity of this user, if known. + */ + spotify: SpotifyActivity | undefined; + + /** + * Is this user a bot? + */ + bot: boolean; + + /** + * The unix time of when this user joined Discord. + */ + createdAt: number; +}; + +/** + * A user's flags. + */ +type UserFlags = { + /** + * The list of flags the user has. + */ + list: ("STAFF" | "PARTNER" | "HYPESQUAD" | "BUG_HUNTER_LEVEL_1" | "HYPESQUAD_BRAVERY" | "HYPESQUAD_BRILLIANCE" | "HYPESQUAD_BALANCE" | "EARLY_SUPPORTER" | "TEAM_USER" | "BUGHUNTER_LEVEL_2" | "VERIFIED_BOT" | "VERIFIED_DEVELOPER" | "CERTIFIED_MODERATOR" | "BOT_HTTP_INTERACTIONS" | "ACTIVE_DEVELOPER" | "UNKNOWN")[]; + + /** + * The raw flags the user has. + */ + raw: number; +} + +/** + * A user's avatar. + */ +type Avatar = { + /** + * The id of the user's avatar. + */ + id: string; + + /** + * The URL of the user's avatar. + */ + url: string; +}; + +/** + * A user's banner. + */ +type Banner = { + /** + * The id of the user's avatar. + */ + id: string; + + /** + * The URL of the user's avatar. + */ + url: string; +}; + +/** + * A user's Spotify activity data. + */ +type SpotifyActivity = { + /** + * The currently playing song. + */ + song: string; + + /** + * The currently playing artist. + */ + artist: string; + + /** + * The album the song is from. + */ + album: string; + + /** + * The current progress of the track (in millis). + */ + trackProgress: number; + + /** + * The total length of the track (in millis). + */ + trackLength: number; + + /** + * The unix time of when this track started playing. + */ + started: number; + + /** + * The unix time of when this track stops playing. + */ + ends: number; +}; + +export default DiscordUser; \ No newline at end of file diff --git a/JS-SDK/tsconfig.json b/JS-SDK/tsconfig.json new file mode 100644 index 0000000..5abdb79 --- /dev/null +++ b/JS-SDK/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "esnext", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "allowJs": true, + "noEmit": true, + "outDir": "dist", + "resolveJsonModule": true, + "paths": { + "@/*": ["./src/*"], + "@/lib/*": ["./src/lib/*"], + "@/types/*": ["./src/types/*"] + } + } +}