sidechat.js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +3 -0
  2. package/classes/SidechatAPIError.js +8 -0
  3. package/dist/sidechat.cjs +2 -0
  4. package/dist/sidechat.cjs.map +1 -0
  5. package/dist/sidechat.modern.js +2 -0
  6. package/dist/sidechat.modern.js.map +1 -0
  7. package/dist/sidechat.module.js +2 -0
  8. package/dist/sidechat.module.js.map +1 -0
  9. package/dist/sidechat.umd.js +2 -0
  10. package/dist/sidechat.umd.js.map +1 -0
  11. package/docs/sidechat.js/1.0.0/data/search.json +1 -0
  12. package/docs/sidechat.js/1.0.0/fonts/Inconsolata-Regular.ttf +0 -0
  13. package/docs/sidechat.js/1.0.0/fonts/OpenSans-Regular.ttf +0 -0
  14. package/docs/sidechat.js/1.0.0/fonts/WorkSans-Bold.ttf +0 -0
  15. package/docs/sidechat.js/1.0.0/global.html +3 -0
  16. package/docs/sidechat.js/1.0.0/index.html +3 -0
  17. package/docs/sidechat.js/1.0.0/index.js.html +365 -0
  18. package/docs/sidechat.js/1.0.0/scripts/core.js +702 -0
  19. package/docs/sidechat.js/1.0.0/scripts/core.min.js +23 -0
  20. package/docs/sidechat.js/1.0.0/scripts/resize.js +90 -0
  21. package/docs/sidechat.js/1.0.0/scripts/search.js +265 -0
  22. package/docs/sidechat.js/1.0.0/scripts/search.min.js +6 -0
  23. package/docs/sidechat.js/1.0.0/scripts/third-party/Apache-License-2.0.txt +202 -0
  24. package/docs/sidechat.js/1.0.0/scripts/third-party/fuse.js +9 -0
  25. package/docs/sidechat.js/1.0.0/scripts/third-party/hljs-line-num-original.js +369 -0
  26. package/docs/sidechat.js/1.0.0/scripts/third-party/hljs-line-num.js +1 -0
  27. package/docs/sidechat.js/1.0.0/scripts/third-party/hljs-original.js +5171 -0
  28. package/docs/sidechat.js/1.0.0/scripts/third-party/hljs.js +1 -0
  29. package/docs/sidechat.js/1.0.0/scripts/third-party/popper.js +5 -0
  30. package/docs/sidechat.js/1.0.0/scripts/third-party/tippy.js +1 -0
  31. package/docs/sidechat.js/1.0.0/scripts/third-party/tocbot.js +672 -0
  32. package/docs/sidechat.js/1.0.0/scripts/third-party/tocbot.min.js +1 -0
  33. package/docs/sidechat.js/1.0.0/styles/clean-jsdoc-theme-base.css +975 -0
  34. package/docs/sidechat.js/1.0.0/styles/clean-jsdoc-theme-dark.css +407 -0
  35. package/docs/sidechat.js/1.0.0/styles/clean-jsdoc-theme-light.css +388 -0
  36. package/docs/sidechat.js/1.0.0/styles/clean-jsdoc-theme.min.css +1 -0
  37. package/docs/sidechat.js/1.0.0/types_SidechatTypes.js.html +82 -0
  38. package/index.js +362 -0
  39. package/jsdoc.json +38 -0
  40. package/package.json +36 -0
  41. package/tests/basicTest.js +8 -0
  42. package/types/SidechatTypes.js +79 -0
package/index.js ADDED
@@ -0,0 +1,362 @@
1
+ import "./types/SidechatTypes.js";
2
+ import SidechatAPIError from "./classes/SidechatAPIError.js";
3
+ const defaultHeaders = {
4
+ Accept: "application/json",
5
+ "Content-Type": "application/json",
6
+ };
7
+
8
+ /**
9
+ * Initiate the login process with a phone number. Should be followed up with verifySMSCode().
10
+ * @param {Number} phoneNumber - US phone number (WITHOUT +1) to send verification code to
11
+ */
12
+ const loginViaSMS = async (phoneNumber) => {
13
+ try {
14
+ const res = await fetch(`https://api.sidechat.lol/v1/login_register`, {
15
+ method: "POST",
16
+ headers: defaultHeaders,
17
+ body: JSON.stringify({
18
+ phone_number: `+1${phoneNumber}`,
19
+ version: 3,
20
+ }),
21
+ });
22
+ const json = await res.json();
23
+ return json;
24
+ } catch (err) {
25
+ console.error(error);
26
+ throw new SidechatAPIError("Failed to request SMS verification.");
27
+ }
28
+ };
29
+
30
+ /**
31
+ * Verify the code sent via SMS with loginViaSMS().
32
+ * @param {Number} phoneNumber - US phone number (WITHOUT +1) that verification code was sent to
33
+ * @param {String} code - the verification code
34
+ */
35
+ const verifySMSCode = async (phoneNumber, code) => {
36
+ try {
37
+ const res = await fetch(`https://api.sidechat.lol/v1/verify_phone_number`, {
38
+ method: "POST",
39
+ headers: defaultHeaders,
40
+ body: JSON.stringify({
41
+ phone_number: `+1${phoneNumber}`,
42
+ code: code.toUpperCase(),
43
+ }),
44
+ });
45
+ const json = await res.json();
46
+ return json;
47
+ } catch (err) {
48
+ console.error(err);
49
+ throw new SidechatAPIError("Failed verify this code.");
50
+ }
51
+ };
52
+
53
+ /**
54
+ * Set the user's age
55
+ * @param {Number} age - user's age in years
56
+ * @param {String} registrationID - the registration ID generated by verifySMSCode()
57
+ */
58
+ const setAge = async (age, registrationID) => {
59
+ if (age < 13) {
60
+ throw new SidechatAPIError("You're too young to use Offsides.");
61
+ }
62
+ try {
63
+ const res = await fetch(
64
+ `https://api.sidechat.lol/v1/complete_registration`,
65
+ {
66
+ method: "POST",
67
+ headers: defaultHeaders,
68
+ body: JSON.stringify({
69
+ age: Number(age),
70
+ registration_id: registrationID,
71
+ }),
72
+ }
73
+ );
74
+ const json = await res.json();
75
+ return json;
76
+ } catch (err) {
77
+ console.error(err);
78
+ throw new SidechatAPIError("Failed verify this code.");
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Initiate the email setup process. Should be followed up with checkEmailVerification().
84
+ * @param {String} email - school email address to send verification code to
85
+ * @param {String} token - user bearer token
86
+ */
87
+ const registerEmail = async (email, token) => {
88
+ try {
89
+ const res = await fetch(
90
+ `https://api.sidechat.lol/v2/users/register_email`,
91
+ {
92
+ method: "POST",
93
+ headers: {
94
+ ...defaultHeaders,
95
+ Authorization: `Bearer ${token}`,
96
+ },
97
+ body: JSON.stringify({
98
+ email: email,
99
+ }),
100
+ }
101
+ );
102
+ const json = await res.json();
103
+ if (json.message) {
104
+ throw new SidechatAPIError(json.message);
105
+ }
106
+ return json;
107
+ } catch (err) {
108
+ console.error(error);
109
+ throw new SidechatAPIError("Failed to request email verification.");
110
+ }
111
+ };
112
+
113
+ /**
114
+ * Check is the user's email is verified.
115
+ * @param {String} token - user bearer token
116
+ */
117
+ const checkEmailVerification = async (token) => {
118
+ try {
119
+ const res = await fetch(
120
+ `https://api.sidechat.lol/v1/users/check_email_verified`,
121
+ {
122
+ method: "GET",
123
+ headers: {
124
+ ...defaultHeaders,
125
+ Authorization: `Bearer ${token}`,
126
+ },
127
+ }
128
+ );
129
+ const json = await res.json();
130
+ if (json.verified_email_updates_response) {
131
+ return json.verified_email_updates_response;
132
+ } else {
133
+ throw new SidechatAPIError(json?.message || "Email is not verified.");
134
+ }
135
+ } catch (err) {
136
+ console.error(error);
137
+ throw new SidechatAPIError("Email is not verified.");
138
+ }
139
+ };
140
+
141
+ /**
142
+ * Set the device ID of the current user
143
+ * @param {String} deviceId - the device ID to set
144
+ * @param {String} token - the user bearer token
145
+ */
146
+ const setDeviceID = async (deviceID, token) => {
147
+ try {
148
+ const res = await fetch(
149
+ `https://api.sidechat.lol/v1/register_device_token`,
150
+ {
151
+ method: "POST",
152
+ headers: {
153
+ ...defaultHeaders,
154
+ Authorization: `Bearer ${token}`,
155
+ },
156
+ body: JSON.stringify({
157
+ build_type: "release",
158
+ bundle_id: "com.flowerave.sidechat",
159
+ device_token: deviceID,
160
+ }),
161
+ }
162
+ );
163
+ const json = await res.json();
164
+ await AsyncStorage.setItem("deviceID", deviceID);
165
+ return json;
166
+ } catch (err) {
167
+ console.error(err);
168
+ throw new SidechatAPIError("Failed verify this code.");
169
+ }
170
+ };
171
+
172
+ /**
173
+ * Get updated status for user and group
174
+ * @param {String} token - user bearer token
175
+ * @param {String} [groupID] - ID of a specific group to retrieve info from
176
+ * @returns
177
+ */
178
+ const getUserAndGroup = async (token, groupID = "") => {
179
+ try {
180
+ const res = await fetch(
181
+ `https://api.sidechat.lol/v1/updates?group_id=${groupID}`,
182
+ {
183
+ method: "GET",
184
+ headers: {
185
+ ...defaultHeaders,
186
+ Authorization: `Bearer ${token}`,
187
+ },
188
+ }
189
+ );
190
+ const json = await res.json();
191
+ return json;
192
+ } catch (err) {
193
+ console.error(err);
194
+ throw new SidechatAPIError(`Failed to get posts from group.`);
195
+ }
196
+ };
197
+
198
+ /**
199
+ * Fetches posts from the specified category in a group
200
+ * @param {String} groupID - group ID
201
+ * @param {String} token - user bearer token
202
+ * @param {"hot"|"recent"|"top"} category - category to filter posts
203
+ * @param {SidechatCursorString} [cursor] - cursor string
204
+ * @returns {{posts: SidechatPostOrComment[], cursor: SidechatCursorString}} List of posts and cursor
205
+ */
206
+ const getGroupPosts = async (groupID, token, category = "hot", cursor) => {
207
+ try {
208
+ const res = await fetch(
209
+ `https://api.sidechat.lol/v1/posts?group_id=${groupID}&type=${category}${
210
+ cursor ? "&cursor=" + cursor : ""
211
+ }`,
212
+ {
213
+ method: "GET",
214
+ headers: {
215
+ ...defaultHeaders,
216
+ Authorization: `Bearer ${token}`,
217
+ },
218
+ }
219
+ );
220
+ const json = await res.json();
221
+ return json;
222
+ } catch (err) {
223
+ console.error(err);
224
+ throw new SidechatAPIError(`Failed to get posts from group.`);
225
+ }
226
+ };
227
+
228
+ /**
229
+ * Upvote or downvote, or unvote a post
230
+ * @param {String} postID - post ID to vote on
231
+ * @param {String} token - user bearer token
232
+ * @param {SidechatVoteString} action - whether to upvote, downvote, or reset vote
233
+ * @returns
234
+ */
235
+ const setVote = async (postID, token, action) => {
236
+ try {
237
+ const res = await fetch(`https://api.sidechat.lol/v1/posts/set_vote`, {
238
+ method: "POST",
239
+ headers: {
240
+ ...defaultHeaders,
241
+ Authorization: `Bearer ${token}`,
242
+ },
243
+ body: JSON.stringify({
244
+ post_id: postID,
245
+ vote_status: action,
246
+ }),
247
+ });
248
+ const json = await res.json();
249
+ return json;
250
+ } catch (err) {
251
+ console.error(err);
252
+ throw new SidechatAPIError(`Failed to change the vote on post.`);
253
+ }
254
+ };
255
+
256
+ /**
257
+ * Get all the commments on a post
258
+ * @param {String} postID - post ID to get comments for
259
+ * @param {String} token - user bearer token
260
+ * @returns {SidechatPostOrComment[]} list of comments
261
+ */
262
+ const getPostComments = async (postID, token) => {
263
+ try {
264
+ const res = await fetch(
265
+ `https://api.sidechat.lol/v1/posts/comments/?post_id=${postID}`,
266
+ {
267
+ method: "GET",
268
+ headers: {
269
+ ...defaultHeaders,
270
+ Authorization: `Bearer ${token}`,
271
+ },
272
+ }
273
+ );
274
+ const json = await res.json();
275
+ // Function to preprocess the comments and organize them into a nested structure
276
+ function preprocessComments(apiComments) {
277
+ // Map to store comments by their IDs for efficient lookup
278
+ const commentMap = new Map();
279
+ // List to store top-level comments
280
+ const topLevelComments = [];
281
+
282
+ // Iterate through the API comments
283
+ apiComments.forEach((comment) => {
284
+ // Store the comment in the map with its ID as the key
285
+ commentMap.set(comment.id, comment);
286
+ // Get the parent comment using the reply_post_id
287
+ const parentComment = commentMap.get(comment.reply_post_id);
288
+ // Check if the comment is a top-level comment
289
+ if (
290
+ !parentComment ||
291
+ comment.reply_post_id === comment.parent_post_id
292
+ ) {
293
+ // If it's a top-level comment, push it to the topLevelComments array
294
+ topLevelComments.push(comment);
295
+ } else {
296
+ // If it's a reply, add it to the parent comment's replies array
297
+ if (!parentComment.replies) parentComment.replies = [];
298
+ parentComment.replies.push(comment);
299
+ }
300
+ });
301
+
302
+ // Flatten the nested structure and return a single list of comments
303
+ return flattenComments(topLevelComments);
304
+ }
305
+
306
+ // Function to flatten nested comments into a single list
307
+ function flattenComments(comments) {
308
+ // Use reduce to flatten the nested comments array into a single list
309
+ return comments.reduce((flatComments, comment) => {
310
+ // Push the current comment to the flatComments array
311
+ flatComments.push(comment);
312
+ // If the current comment has replies, recursively flatten them and push to the flatComments array
313
+ if (comment.replies)
314
+ flatComments.push(...flattenComments(comment.replies));
315
+ // Return the flatComments array
316
+ return flatComments;
317
+ }, []);
318
+ }
319
+
320
+ const sortedComments = preprocessComments(json.posts);
321
+ return sortedComments;
322
+ } catch (err) {
323
+ console.error(err);
324
+ throw new SidechatAPIError(`Failed to get comments on post.`);
325
+ }
326
+ };
327
+
328
+ /**
329
+ * Gets groups to be displayed on the "Explore Groups" page
330
+ * @param {String} token - user bearer token
331
+ * @returns {SidechatGroup}
332
+ */
333
+ const getAvailableGroups = async (token) => {
334
+ try {
335
+ const res = await fetch(`https://api.sidechat.lol/v1/groups/explore`, {
336
+ method: "GET",
337
+ headers: {
338
+ ...defaultHeaders,
339
+ Authorization: `Bearer ${token}`,
340
+ },
341
+ });
342
+ const json = await res.json();
343
+ return json.groups;
344
+ } catch (err) {
345
+ console.error(err);
346
+ throw new SidechatAPIError(`Failed to get groups from explore.`);
347
+ }
348
+ };
349
+
350
+ export {
351
+ loginViaSMS,
352
+ verifySMSCode,
353
+ setAge,
354
+ setDeviceID,
355
+ registerEmail,
356
+ checkEmailVerification,
357
+ getUserAndGroup,
358
+ getGroupPosts,
359
+ setVote,
360
+ getPostComments,
361
+ getAvailableGroups,
362
+ };
package/jsdoc.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "source": {
3
+ "include": [
4
+ "index.js",
5
+ "package.json",
6
+ "README.md",
7
+ "types/SidechatTypes.js",
8
+ "classes/SidechatAPIError.js"
9
+ ],
10
+ "includePattern": ".js$",
11
+ "excludePattern": "(node_modules/|docs)"
12
+ },
13
+ "plugins": ["plugins/markdown"],
14
+ "opts": {
15
+ "encoding": "utf8",
16
+ "readme": "./README.md",
17
+ "destination": "docs/",
18
+ "package": "./package.json",
19
+ "recurse": true,
20
+ "verbose": true,
21
+ "template": "./node_modules/clean-jsdoc-theme",
22
+ "theme_opts": {
23
+ "default_theme": "dark",
24
+ "homepageTitle": "Sidechat.js Documentation",
25
+ "menu": [
26
+ {
27
+ "title": "Offsides (Sidechat for Android)",
28
+ "link": "https://github.com/micahlt/offsides#README.md",
29
+ "target": "_blank"
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ "markdown": {
35
+ "hardwrap": false,
36
+ "idInHeadings": true
37
+ }
38
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "sidechat.js",
3
+ "version": "1.0.0",
4
+ "description": "A reverse-engineered API wrapper for Sidechat",
5
+ "type": "module",
6
+ "source": "index.js",
7
+ "exports": {
8
+ "require": "./dist/sidechat.cjs",
9
+ "default": "./dist/sidechat.modern.js"
10
+ },
11
+ "main": "./dist/sidechat.cjs",
12
+ "module": "./dist/sidechat.module.js",
13
+ "unpkg": "./dist/sidechat.umd.js",
14
+ "scripts": {
15
+ "build": "microbundle",
16
+ "dev": "microbundle watch",
17
+ "docs": "npx jsdoc --configure jsdoc.json"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/micahlt/sidechat.js.git"
22
+ },
23
+ "author": "Micah Lindley",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/micahlt/sidechat.js/issues"
27
+ },
28
+ "homepage": "https://github.com/micahlt/sidechat.js#readme",
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "clean-jsdoc-theme": "^4.2.17",
34
+ "microbundle": "^0.15.1"
35
+ }
36
+ }
@@ -0,0 +1,8 @@
1
+ import * as API from "../index.js";
2
+
3
+ async function main() {
4
+ const res = await API.setVote();
5
+ console.log(await res);
6
+ }
7
+
8
+ main();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * A user-created post or comment, depending on the "type" prop
3
+ * @typedef {Object} SidechatPostOrComment
4
+ * @prop {"post"|"comment"} type - whether this object represents a post or a comment on a post
5
+ * @prop {String} id - alphanumeric ID of post or comment
6
+ * @prop {Boolean} authored_by_user - undocumented
7
+ * @prop {String} alias - post creator's name in-app
8
+ * @prop {String} group_id - alphanumeric ID of group
9
+ * @prop {SidechatGroup} group - group in which post or comment was created
10
+ * @prop {String} text - text content of post or comment
11
+ * @prop {String} created_at - date string of post or comment creation time
12
+ * @prop {Number} vote_total - sum of upvotes and downvotes
13
+ * @prop {SidechatVoteString} vote_status - current user's vote on post or comment
14
+ * @prop {SidechatAsset[]} assets - array of assets attached to post or comment
15
+ * @prop {Array} attachments - undocumented
16
+ * @prop {Boolean} dms_disabled - whether or not you can send a DM to creator of post
17
+ * @prop {Array} tags - undocumented
18
+ * @prop {SidechatIdentity} identity - creator's identity information
19
+ * @prop {Boolean} pinned - undocumented
20
+ * @prop {Boolean} is_saved - undocumented
21
+ * @prop {"following"|"not_following"} follow_status - undocumented
22
+ * @prop {"group"} [destination] - undocumented
23
+ * @prop {Number} [comment_count] - number of comments on post (only if type=post)
24
+ * @prop {Boolean} [comments_disabled] - whether or not you can comment on post (only if type=post)
25
+ * @prop {String} [parent_post_id] - alphanumeric ID of parent post (only if type=comment)
26
+ * @prop {String} [reply_post_id] - alphanumeric ID of comment being replied to, falls back to parent_post_id (only if type=comment)
27
+ * @prop {String} [context] - text content of comment being replied to, falls back to text (only if type=comment)
28
+ */
29
+
30
+ /**
31
+ * A group object, containing metadata about a group as well as its join conditions and states
32
+ * @typedef {Object} SidechatGroup
33
+ * @prop {String} id - alphanumeric ID of group
34
+ * @prop {String} name - human-readable name of group
35
+ * @prop {String} analytics_name - name to file group under
36
+ * @prop {"non_member"|"member"} membership_type - whether or not current user is a member of group
37
+ * @prop {String} color - group's theme color as hex code (e.g., #FFFFFF)
38
+ * @prop {"open"|"closed"|"email_domain"} group_join_type - basis on which group accepts new members
39
+ * @prop {"private"|"public_to_all"|"public_to_schools"} group_visibility - group's visibility setting
40
+ * @prop {"show"|"hide"} asset_library_visibility - whether or not group's asset library is visible
41
+ * @prop {Array} [roles] - undocumented
42
+ * @prop {String} [description] - description of group
43
+ * @prop {SidechatPrivateAssetURL} [icon_url] - image URL for group icon
44
+ * @prop {Number} [member_count] - number of users in group
45
+ * @prop {Boolean} [should_show_leaderboard] - undocumented
46
+ */
47
+
48
+ /**
49
+ * An asset object, containing information about an image within a post or comment
50
+ * @typedef {Object} SidechatAsset
51
+ * @prop {String} id - alphanumeric ID of asset
52
+ * @prop {"image"} type - undocumented
53
+ * @prop {"jpeg"|"png"|"gif"} content_type - file type of asset
54
+ * @prop {Number} width - the width of asset
55
+ * @prop {Number} height - the height of asset
56
+ * @prop {SidechatPrivateAssetURL} url - URL string of asset's location
57
+ */
58
+
59
+ /**
60
+ * A user's identity information
61
+ * @typedef {Object} SidechatIdentity
62
+ * @prop {String} name - representation of user's name (varies across app)
63
+ * @prop {Boolean} posted_with_username - undocumented
64
+ */
65
+
66
+ /**
67
+ * An asset URL that can only be accessed by making a request with a user's bearer token attached in the Authorization header
68
+ * @typedef {String} SidechatPrivateAssetURL
69
+ */
70
+
71
+ /**
72
+ * A cursor for paginating through post lists
73
+ * @typedef {String} SidechatCursorString
74
+ */
75
+
76
+ /**
77
+ * Represents a vote on a comment or post
78
+ * @typedef {"upvote"|"downvote"|"none"} SidechatVoteString
79
+ */