sidechat.js 2.6.6 → 2.7.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 (51) hide show
  1. package/dist/classes/SidechatAPIClient.d.ts +166 -11
  2. package/dist/classes/SidechatAPIClient.d.ts.map +1 -1
  3. package/dist/index.cjs +853 -311
  4. package/dist/index.mjs +853 -311
  5. package/dist/types/SidechatTypes.d.ts +260 -9
  6. package/dist/types/SidechatTypes.d.ts.map +1 -1
  7. package/docs/sidechat.js/2.7.0/SidechatAPIClient.html +3 -0
  8. package/docs/sidechat.js/2.7.0/SidechatAPIError.html +3 -0
  9. package/docs/sidechat.js/2.7.0/SidechatColorList.html +3 -0
  10. package/docs/sidechat.js/2.7.0/classes_SidechatAPIClient.js.html +1897 -0
  11. package/docs/sidechat.js/2.7.0/classes_SidechatAPIError.js.html +16 -0
  12. package/docs/sidechat.js/2.7.0/classes_SidechatColorList.js.html +79 -0
  13. package/docs/sidechat.js/2.7.0/data/search.json +1 -0
  14. package/docs/sidechat.js/2.7.0/fonts/Inconsolata-Regular.ttf +0 -0
  15. package/docs/sidechat.js/2.7.0/fonts/OpenSans-Regular.ttf +0 -0
  16. package/docs/sidechat.js/2.7.0/fonts/WorkSans-Bold.ttf +0 -0
  17. package/docs/sidechat.js/2.7.0/global.html +3 -0
  18. package/docs/sidechat.js/2.7.0/index.html +4 -0
  19. package/docs/sidechat.js/2.7.0/scripts/core.js +726 -0
  20. package/docs/sidechat.js/2.7.0/scripts/core.min.js +23 -0
  21. package/docs/sidechat.js/2.7.0/scripts/resize.js +90 -0
  22. package/docs/sidechat.js/2.7.0/scripts/search.js +265 -0
  23. package/docs/sidechat.js/2.7.0/scripts/search.min.js +6 -0
  24. package/docs/sidechat.js/2.7.0/scripts/third-party/Apache-License-2.0.txt +202 -0
  25. package/docs/sidechat.js/2.7.0/scripts/third-party/fuse.js +9 -0
  26. package/docs/sidechat.js/2.7.0/scripts/third-party/hljs-line-num-original.js +369 -0
  27. package/docs/sidechat.js/2.7.0/scripts/third-party/hljs-line-num.js +1 -0
  28. package/docs/sidechat.js/2.7.0/scripts/third-party/hljs-original.js +5171 -0
  29. package/docs/sidechat.js/2.7.0/scripts/third-party/hljs.js +1 -0
  30. package/docs/sidechat.js/2.7.0/scripts/third-party/popper.js +5 -0
  31. package/docs/sidechat.js/2.7.0/scripts/third-party/tippy.js +1 -0
  32. package/docs/sidechat.js/2.7.0/scripts/third-party/tocbot.js +672 -0
  33. package/docs/sidechat.js/2.7.0/scripts/third-party/tocbot.min.js +1 -0
  34. package/docs/sidechat.js/2.7.0/styles/clean-jsdoc-theme-base.css +1159 -0
  35. package/docs/sidechat.js/2.7.0/styles/clean-jsdoc-theme-dark.css +412 -0
  36. package/docs/sidechat.js/2.7.0/styles/clean-jsdoc-theme-light.css +482 -0
  37. package/docs/sidechat.js/2.7.0/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  38. package/docs/sidechat.js/2.7.0/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  39. package/docs/sidechat.js/2.7.0/styles/clean-jsdoc-theme.min.css +1 -0
  40. package/docs/sidechat.js/2.7.0/tutorial-Authentication.html +16 -0
  41. package/docs/sidechat.js/2.7.0/tutorial-Email Registration.html +6 -0
  42. package/docs/sidechat.js/2.7.0/tutorial-Feeds and Pagination.html +10 -0
  43. package/docs/sidechat.js/2.7.0/tutorial-Posting and Media.html +19 -0
  44. package/docs/sidechat.js/2.7.0/types_SidechatTypes.js.html +297 -0
  45. package/docs/tutorials/Authentication.md +63 -52
  46. package/docs/tutorials/Email Registration.md +19 -16
  47. package/docs/tutorials/Feeds and Pagination.md +45 -0
  48. package/docs/tutorials/Posting and Media.md +63 -0
  49. package/package.json +55 -55
  50. package/src/classes/SidechatAPIClient.js +477 -27
  51. package/src/types/SidechatTypes.js +294 -202
@@ -67,6 +67,16 @@ class SidechatAPIClient {
67
67
  this.apiRoot = url;
68
68
  };
69
69
 
70
+ /**
71
+ * Set an identifier for this device, which will be sent with every request in the `X-Device-ID` header. Should be stable across sessions on the same device.
72
+ * @method
73
+ * @param {String} deviceIdentifier - stable identifier for this device
74
+ * @since 2.7.0
75
+ */
76
+ setDeviceIdentifier = (deviceIdentifier) => {
77
+ this.defaultHeaders["X-Device-ID"] = deviceIdentifier;
78
+ };
79
+
70
80
  /**
71
81
  * Run an arbitrary API request using the current client's authentication
72
82
  * @method
@@ -97,19 +107,32 @@ class SidechatAPIClient {
97
107
  };
98
108
 
99
109
  /**
100
- * Initiate the login process with a phone number. Should be followed up with verifySMSCode().
110
+ * Initiate the login process with a phone number. Should be followed up with verifySMSCode(). If the response has an `error_code` of `"captcha_required"`, the server wants a captcha solved before it will send a code, and the request should be repeated with a captcha response. I haven't been able to recreate this yet, so I honestly don't know what captcha provider they want.
101
111
  * @method
102
112
  * @since 1.0.0
103
113
  * @param {Number} phoneNumber - US phone number (WITHOUT +1) to send verification code to
114
+ * @param {Boolean} [useFallback] - send the code through the backup SMS route, which the official app offers when a code doesn't arrive
115
+ * @param {String} [captchaMethod] - type of captcha being answered
116
+ * @param {String} [captchaResponse] - solved captcha token
104
117
  */
105
- loginViaSMS = async (phoneNumber) => {
118
+ loginViaSMS = async (
119
+ phoneNumber,
120
+ useFallback = false,
121
+ captchaMethod = undefined,
122
+ captchaResponse = undefined,
123
+ ) => {
106
124
  try {
107
- const res = await fetch(`${this.apiRoot}/v1/login_register`, {
125
+ const endpoint = useFallback
126
+ ? "/v1/login_register_sms_fallback"
127
+ : "/v1/login_register";
128
+ const res = await fetch(`${this.apiRoot}${endpoint}`, {
108
129
  method: "POST",
109
130
  headers: this.defaultHeaders,
110
131
  body: JSON.stringify({
111
132
  phone_number: `+1${phoneNumber}`,
112
133
  version: 3,
134
+ captcha_method: captchaMethod,
135
+ captcha_response: captchaResponse,
113
136
  }),
114
137
  });
115
138
  const json = await res.json();
@@ -154,8 +177,15 @@ class SidechatAPIClient {
154
177
  * @since 1.0.0
155
178
  * @param {Number} age - user's age in years
156
179
  * @param {String} registrationID - the registration ID generated by verifySMSCode()
180
+ * @param {String} [timeZone] - time zone of the user (e.g. "America/Chicago"). Falls back to the system time zone.
181
+ * @param {String} [inviteLinkURL] - group invite link that the user signed up through
157
182
  */
158
- setAge = async (age, registrationID) => {
183
+ setAge = async (
184
+ age,
185
+ registrationID,
186
+ timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone,
187
+ inviteLinkURL = undefined,
188
+ ) => {
159
189
  if (age < 13) {
160
190
  throw new SidechatAPIError("You're too young to use Offsides.");
161
191
  }
@@ -166,6 +196,9 @@ class SidechatAPIClient {
166
196
  body: JSON.stringify({
167
197
  age: Number(age),
168
198
  registration_id: registrationID,
199
+ time_zone: timeZone,
200
+ invite_link_url: inviteLinkURL,
201
+ device_id: this.defaultHeaders["X-Device-ID"],
169
202
  }),
170
203
  });
171
204
  const json = await res.json();
@@ -248,8 +281,9 @@ class SidechatAPIClient {
248
281
  * @method
249
282
  * @since 1.0.0
250
283
  * @param {String} deviceId - the device ID to set
284
+ * @param {"com.flowerave.sidechat"|"com.flowerave.yikyak"} [bundleID] - bundle ID of the app the push token belongs to
251
285
  */
252
- setDeviceID = async (deviceID) => {
286
+ setDeviceID = async (deviceID, bundleID = "com.flowerave.sidechat") => {
253
287
  if (!this.userToken) {
254
288
  throw new SidechatAPIError("User is not authenticated.");
255
289
  }
@@ -262,7 +296,7 @@ class SidechatAPIClient {
262
296
  },
263
297
  body: JSON.stringify({
264
298
  build_type: "release",
265
- bundle_id: "com.flowerave.sidechat",
299
+ bundle_id: bundleID,
266
300
  device_token: deviceID,
267
301
  }),
268
302
  });
@@ -322,9 +356,10 @@ class SidechatAPIClient {
322
356
  * @param {String} groupID - group ID
323
357
  * @param {"hot"|"recent"|"top"} category - category to filter posts
324
358
  * @param {types.SidechatCursorString} [cursor] - cursor string
359
+ * @param {"day"|"week"|"month"|"year"|"all_time"} [period] - time period to pull top posts from (only if category is "top")
325
360
  * @returns {Promise<types.SidechatPostsAndCursor>} List of posts and cursor
326
361
  */
327
- getGroupPosts = async (groupID, category = "hot", cursor) => {
362
+ getGroupPosts = async (groupID, category = "hot", cursor, period) => {
328
363
  if (!this.userToken) {
329
364
  throw new SidechatAPIError("User is not authenticated.");
330
365
  }
@@ -332,6 +367,8 @@ class SidechatAPIClient {
332
367
  const res = await fetch(
333
368
  `${this.apiRoot}/v1/posts?${
334
369
  cursor ? "cursor=" + encodeURIComponent(cursor) + "&" : ""
370
+ }${
371
+ period ? "period=" + period + "&" : ""
335
372
  }group_id=${groupID}&type=${category}&cacheBust=${Date.now()}`,
336
373
  {
337
374
  method: "GET",
@@ -428,7 +465,7 @@ class SidechatAPIClient {
428
465
  contentType = "my_comments";
429
466
  }
430
467
  try {
431
- const res = await fetch(`${this.apiRoot}/v1/posts&type=${contentType}`, {
468
+ const res = await fetch(`${this.apiRoot}/v1/posts?type=${contentType}`, {
432
469
  method: "GET",
433
470
  headers: {
434
471
  ...this.defaultHeaders,
@@ -668,8 +705,9 @@ class SidechatAPIClient {
668
705
  throw new SidechatAPIError("User is not authenticated.");
669
706
  }
670
707
  try {
708
+ // Joining moved to the v2 endpoint, which takes a list of IDs. Leave still sends group_id by itself, no list.
671
709
  const res = await fetch(
672
- `${this.apiRoot}/v1/groups/${isMember ? "join" : "leave"}`,
710
+ `${this.apiRoot}${isMember ? "/v2/groups/join" : "/v1/groups/leave"}`,
673
711
  {
674
712
  method: "POST",
675
713
  headers: {
@@ -677,7 +715,8 @@ class SidechatAPIClient {
677
715
  Authorization: `Bearer ${this.userToken}`,
678
716
  },
679
717
  body: JSON.stringify({
680
- group_id: groupID,
718
+ group_id: isMember ? undefined : groupID,
719
+ group_ids: [groupID],
681
720
  }),
682
721
  },
683
722
  );
@@ -736,15 +775,17 @@ class SidechatAPIClient {
736
775
  }),
737
776
  });
738
777
  const json = await res.json();
778
+ if (!res.ok) throw new SidechatAPIError(json.message);
739
779
  return await json.comment;
740
780
  } catch (err) {
741
781
  console.error(err);
782
+ if (err instanceof SidechatAPIError && err.message) throw err;
742
783
  throw new SidechatAPIError(`Failed to post comment.`);
743
784
  }
744
785
  };
745
786
 
746
787
  /**
747
- * Creates a new post in the specified group
788
+ * Creates a new post in the specified group. Users can only make two posts a minute; if a post is rejected, the thrown error contains the reason from the server.
748
789
  * @method
749
790
  * @since 2.2.0
750
791
  * @param {String} text - text content of comment
@@ -754,6 +795,7 @@ class SidechatAPIClient {
754
795
  * @param {Boolean} [disableComments] - whether or not comments should be disabled on post
755
796
  * @param {Boolean} [anonymous] - whether or not to hide user's name and icon on post
756
797
  * @param {Array<String>} [pollOptions] - List of poll options. If provided, a poll will be created with these options.
798
+ * @param {Array<String>} [attachmentIDs] - IDs of link attachments created with createLinkAttachment()
757
799
  * @returns {Promise<types.SidechatPostOrComment>} the created post
758
800
  */
759
801
  createPost = async (
@@ -765,6 +807,7 @@ class SidechatAPIClient {
765
807
  anonymous = false,
766
808
  repostId = undefined,
767
809
  pollOptions = undefined,
810
+ attachmentIDs = [],
768
811
  ) => {
769
812
  if (!this.userToken) {
770
813
  throw new SidechatAPIError("User is not authenticated.");
@@ -776,7 +819,7 @@ class SidechatAPIClient {
776
819
  assets: assetList,
777
820
  group_ids: [groupID],
778
821
  text: text,
779
- attachments: [],
822
+ attachments: attachmentIDs,
780
823
  dms_disabled: disableDMs,
781
824
  comments_disabled: disableComments,
782
825
  using_identity: !anonymous,
@@ -798,9 +841,11 @@ class SidechatAPIClient {
798
841
  body: JSON.stringify(body),
799
842
  });
800
843
  const json = await res.json();
844
+ if (!res.ok) throw new SidechatAPIError(json.message);
801
845
  return await json.posts[0];
802
846
  } catch (err) {
803
847
  console.error(err);
848
+ if (err instanceof SidechatAPIError && err.message) throw err;
804
849
  throw new SidechatAPIError(`Failed to make post.`);
805
850
  }
806
851
  };
@@ -877,7 +922,7 @@ class SidechatAPIClient {
877
922
  }
878
923
  try {
879
924
  const res = await fetch(
880
- `${this.apiRoot}/v1/polls/view_results&cacheBust=${Date.now()}`,
925
+ `${this.apiRoot}/v1/polls/view_results?cacheBust=${Date.now()}`,
881
926
  {
882
927
  method: "POST",
883
928
  headers: {
@@ -898,22 +943,27 @@ class SidechatAPIClient {
898
943
  };
899
944
 
900
945
  /**
901
- * Uploads an asset to AWS S3 for use in posts and comments. Currently photos only
946
+ * Uploads an asset to AWS S3 for use in posts and comments. Supports images and videos (videos must be under 50MB and are processed after upload)
902
947
  * @method
903
948
  * @param {String} uri - URI of the asset to upload
904
- * @param {String} mimeType - mimetype of the asset (e.g. "image/png")
949
+ * @param {String} mimeType - mimetype of the asset (e.g. "image/png" or "video/mp4")
905
950
  * @param {String} [name] - filename of the asset
951
+ * @param {"post"|"post_vod_source"|"chat"|"chat_icon"|"group_icon"|"profile_picture_icon"} [context] - where the asset will be used. Videos default to "post_vod_source", which is required for them to be processed into a playable stream.
906
952
  * @returns {Promise<String>} URL of the uploaded asset
907
953
  * @since 2.5.1
908
954
  */
909
- uploadAsset = async (uri, mimeType, name = "") => {
955
+ uploadAsset = async (uri, mimeType, name = "", context = undefined) => {
910
956
  if (!this.userToken) {
911
957
  throw new SidechatAPIError("User is not authenticated.");
912
958
  }
913
959
 
914
960
  let imageType = mimeType.split("/")[1];
915
- if (!["png", "jpeg", "gif"].includes(imageType)) {
916
- throw new SidechatAPIError("Unsupported image format.");
961
+ if (imageType == "quicktime") imageType = "mov";
962
+ if (!["png", "jpeg", "gif", "webp", "mp4", "mov"].includes(imageType)) {
963
+ throw new SidechatAPIError("Unsupported file format.");
964
+ }
965
+ if (!context && ["mp4", "mov"].includes(imageType)) {
966
+ context = "post_vod_source";
917
967
  }
918
968
 
919
969
  const data = new FormData();
@@ -924,7 +974,9 @@ class SidechatAPIClient {
924
974
  });
925
975
 
926
976
  const urlReq = await this.sendRequest(
927
- `/v1/assets/upload_url?content_type=${imageType}`,
977
+ `/v1/assets/upload_url?content_type=${imageType}${
978
+ context ? "&context=" + context : ""
979
+ }`,
928
980
  );
929
981
  const urlJson = await urlReq.json();
930
982
 
@@ -1180,7 +1232,7 @@ class SidechatAPIClient {
1180
1232
  }
1181
1233
  try {
1182
1234
  const res = await fetch(
1183
- `${this.apiRoot}/v1/chats/explore&cacheBust=${Date.now()}`,
1235
+ `${this.apiRoot}/v1/chats/explore?cacheBust=${Date.now()}`,
1184
1236
  {
1185
1237
  method: "GET",
1186
1238
  headers: {
@@ -1245,21 +1297,27 @@ class SidechatAPIClient {
1245
1297
  /**
1246
1298
  * Gets a list of the user's direct messages
1247
1299
  * @method
1300
+ * @param {types.SidechatCursorString} [cursor] - cursor string
1248
1301
  * @returns {Promise<types.SidechatDirectThread[]>}
1249
1302
  * @since 2.4.4
1250
1303
  */
1251
- getDMs = async () => {
1304
+ getDMs = async (cursor) => {
1252
1305
  if (!this.userToken) {
1253
1306
  throw new SidechatAPIError("User is not authenticated.");
1254
1307
  }
1255
1308
  try {
1256
- const res = await fetch(`${this.apiRoot}/v1/chats`, {
1257
- method: "GET",
1258
- headers: {
1259
- ...this.defaultHeaders,
1260
- Authorization: `Bearer ${this.userToken}`,
1309
+ const res = await fetch(
1310
+ `${this.apiRoot}/v1/chats${
1311
+ cursor ? "?cursor=" + encodeURIComponent(cursor) : ""
1312
+ }`,
1313
+ {
1314
+ method: "GET",
1315
+ headers: {
1316
+ ...this.defaultHeaders,
1317
+ Authorization: `Bearer ${this.userToken}`,
1318
+ },
1261
1319
  },
1262
- });
1320
+ );
1263
1321
  const json = await res.json();
1264
1322
  const list = await json.chats;
1265
1323
  let r = [];
@@ -1388,6 +1446,7 @@ class SidechatAPIClient {
1388
1446
  * @method
1389
1447
  * @param {String} postID - alphanumeric ID of post to hide
1390
1448
  * @since 2.6.2
1449
+ * @deprecated since 2.7.0. Please use `blockUser` instead!
1391
1450
  */
1392
1451
  hidePostsFromUser = async (postID) => {
1393
1452
  if (!this.userToken) {
@@ -1418,6 +1477,7 @@ class SidechatAPIClient {
1418
1477
  * Unhides all posts from all users
1419
1478
  * @method
1420
1479
  * @since 2.6.2
1480
+ * @deprecated since 2.7.0. No longer used by the official app and may stop working.
1421
1481
  */
1422
1482
  unhidePostsFromAllUsers = async () => {
1423
1483
  if (!this.userToken) {
@@ -1439,6 +1499,396 @@ class SidechatAPIClient {
1439
1499
  throw new SidechatAPIError(`Failed to unhide post.`);
1440
1500
  }
1441
1501
  };
1502
+
1503
+ /**
1504
+ * Fetches the home feed, which mixes posts from all of the user's groups
1505
+ * @method
1506
+ * @param {"for_you"|"hot"|"recent"} [type] - how the feed should be sorted
1507
+ * @param {String} [schoolGroupID] - alphanumeric ID of the user's school group
1508
+ * @param {types.SidechatCursorString} [cursor] - cursor string
1509
+ * @returns {Promise<types.SidechatFeed>} feed items and cursor
1510
+ * @since 2.7.0
1511
+ */
1512
+ getHomeFeed = async (type = "for_you", schoolGroupID, cursor) => {
1513
+ if (!this.userToken) {
1514
+ throw new SidechatAPIError("User is not authenticated.");
1515
+ }
1516
+ try {
1517
+ const params = new URLSearchParams({ type: type });
1518
+ if (schoolGroupID) params.set("school_group_id", schoolGroupID);
1519
+ if (cursor) params.set("cursor", cursor);
1520
+ const res = await this.sendRequest(`/v1/posts/home?${params}`);
1521
+ return await res.json();
1522
+ } catch (err) {
1523
+ console.error(err);
1524
+ throw new SidechatAPIError(`Failed to get home feed.`);
1525
+ }
1526
+ };
1527
+
1528
+ /**
1529
+ * Follows or unfollows a post. Followed posts send activity notifications when they get new comments.
1530
+ * @method
1531
+ * @param {String} postID - alphanumeric ID of post
1532
+ * @param {Boolean} following - whether or not the user should follow the post
1533
+ * @returns {Promise<{post: types.SidechatPostOrComment}>} the updated post
1534
+ * @since 2.7.0
1535
+ */
1536
+ setPostFollowing = async (postID, following) => {
1537
+ if (!this.userToken) {
1538
+ throw new SidechatAPIError("User is not authenticated.");
1539
+ }
1540
+ try {
1541
+ const res = await this.sendRequest(
1542
+ `/v1/posts/${following ? "follow" : "unfollow"}`,
1543
+ "POST",
1544
+ JSON.stringify({ post_id: postID }),
1545
+ );
1546
+ return await res.json();
1547
+ } catch (err) {
1548
+ console.error(err);
1549
+ throw new SidechatAPIError(`Failed to change post follow status.`);
1550
+ }
1551
+ };
1552
+
1553
+ /**
1554
+ * Reports a post or comment to the group's moderators
1555
+ * @method
1556
+ * @param {String} postID - alphanumeric ID of post or comment to report
1557
+ * @param {types.SidechatReportReason} reason - why the content is being reported
1558
+ * @param {String} [note] - explanation to send along with the report
1559
+ * @since 2.7.0
1560
+ */
1561
+ flagPost = async (postID, reason, note = undefined) => {
1562
+ if (!this.userToken) {
1563
+ throw new SidechatAPIError("User is not authenticated.");
1564
+ }
1565
+ try {
1566
+ const res = await this.sendRequest(
1567
+ "/v1/posts/flag",
1568
+ "POST",
1569
+ JSON.stringify({ post_id: postID, reason: reason, note: note }),
1570
+ );
1571
+ return await res.json();
1572
+ } catch (err) {
1573
+ console.error(err);
1574
+ throw new SidechatAPIError(`Failed to report post.`);
1575
+ }
1576
+ };
1577
+
1578
+ /**
1579
+ * Blocks the author of a post or comment, hiding all of their content from the user. Replaces `hidePostsFromUser`.
1580
+ * @method
1581
+ * @param {String} postID - alphanumeric ID of a post or comment by the user to block
1582
+ * @param {Boolean} [report] - whether or not to also report the content
1583
+ * @param {"feed"|"chat"} [postContext] - where the content was seen
1584
+ * @since 2.7.0
1585
+ */
1586
+ blockUser = async (postID, report = false, postContext = "feed") => {
1587
+ if (!this.userToken) {
1588
+ throw new SidechatAPIError("User is not authenticated.");
1589
+ }
1590
+ try {
1591
+ const res = await this.sendRequest(
1592
+ "/v1/posts/block_user",
1593
+ "POST",
1594
+ JSON.stringify({
1595
+ post_id: postID,
1596
+ post_context: postContext,
1597
+ report: report,
1598
+ }),
1599
+ );
1600
+ return await res.json();
1601
+ } catch (err) {
1602
+ console.error(err);
1603
+ throw new SidechatAPIError(`Failed to block user.`);
1604
+ }
1605
+ };
1606
+
1607
+ /**
1608
+ * Creates a link attachment that can be added to a post with createPost(). YouTube links are recognized and shown as embedded videos.
1609
+ * @method
1610
+ * @param {String} url - URL to attach
1611
+ * @returns {Promise<types.SidechatAttachment>} the created attachment
1612
+ * @since 2.7.0
1613
+ */
1614
+ createLinkAttachment = async (url) => {
1615
+ if (!this.userToken) {
1616
+ throw new SidechatAPIError("User is not authenticated.");
1617
+ }
1618
+ try {
1619
+ const res = await this.sendRequest(
1620
+ "/v1/attachments/upload_link",
1621
+ "POST",
1622
+ JSON.stringify({ link_url: url }),
1623
+ );
1624
+ return await res.json();
1625
+ } catch (err) {
1626
+ console.error(err);
1627
+ throw new SidechatAPIError(`Failed to create link attachment.`);
1628
+ }
1629
+ };
1630
+
1631
+ /**
1632
+ * Gets the user's activity feed (notifications for comments, votes, followers, etc.)
1633
+ * @method
1634
+ * @param {types.SidechatCursorString} [cursor] - cursor string
1635
+ * @returns {Promise<types.SidechatActivityList>} activity items and cursor
1636
+ * @since 2.7.0
1637
+ */
1638
+ getActivity = async (cursor) => {
1639
+ if (!this.userToken) {
1640
+ throw new SidechatAPIError("User is not authenticated.");
1641
+ }
1642
+ try {
1643
+ const res = await this.sendRequest(
1644
+ `/v1/activity${cursor ? "?cursor=" + encodeURIComponent(cursor) : ""}`,
1645
+ );
1646
+ return await res.json();
1647
+ } catch (err) {
1648
+ console.error(err);
1649
+ throw new SidechatAPIError(`Failed to get activity.`);
1650
+ }
1651
+ };
1652
+
1653
+ /**
1654
+ * Gets the groups the user is a member of
1655
+ * @method
1656
+ * @returns {Promise<types.SidechatGroup[]>}
1657
+ * @since 2.7.0
1658
+ */
1659
+ getGroups = async () => {
1660
+ if (!this.userToken) {
1661
+ throw new SidechatAPIError("User is not authenticated.");
1662
+ }
1663
+ try {
1664
+ const res = await this.sendRequest("/v1/groups");
1665
+ const json = await res.json();
1666
+ return json.groups;
1667
+ } catch (err) {
1668
+ console.error(err);
1669
+ throw new SidechatAPIError(`Failed to get groups.`);
1670
+ }
1671
+ };
1672
+
1673
+ /**
1674
+ * Gets groups suggested for the user to join
1675
+ * @method
1676
+ * @param {Number} [limit] - maximum number of groups to return
1677
+ * @returns {Promise<types.SidechatGroup[]>}
1678
+ * @since 2.7.0
1679
+ */
1680
+ getSuggestedGroups = async (limit = 10) => {
1681
+ if (!this.userToken) {
1682
+ throw new SidechatAPIError("User is not authenticated.");
1683
+ }
1684
+ try {
1685
+ const res = await this.sendRequest(
1686
+ `/v1/groups/suggestions?limit=${limit}`,
1687
+ );
1688
+ const json = await res.json();
1689
+ return json.groups;
1690
+ } catch (err) {
1691
+ console.error(err);
1692
+ throw new SidechatAPIError(`Failed to get suggested groups.`);
1693
+ }
1694
+ };
1695
+
1696
+ /**
1697
+ * Gets a shareable invite link for a group. School groups don't have invite links and will throw an error.
1698
+ * @method
1699
+ * @param {String} groupID - alphanumeric ID of group
1700
+ * @returns {Promise<String>} invite link URL
1701
+ * @since 2.7.0
1702
+ */
1703
+ getGroupInviteLink = async (groupID) => {
1704
+ if (!this.userToken) {
1705
+ throw new SidechatAPIError("User is not authenticated.");
1706
+ }
1707
+ try {
1708
+ const res = await this.sendRequest(
1709
+ `/v1/groups/invite_link?group_id=${groupID}`,
1710
+ );
1711
+ const json = await res.json();
1712
+ return json.link;
1713
+ } catch (err) {
1714
+ console.error(err);
1715
+ throw new SidechatAPIError(`Failed to get invite link.`);
1716
+ }
1717
+ };
1718
+
1719
+ /**
1720
+ * Gets the yakarma leaderboard for a group, along with the user's own karma
1721
+ * @method
1722
+ * @param {String} groupID - alphanumeric ID of group
1723
+ * @returns {Promise<types.SidechatLeaderboard>}
1724
+ * @since 2.7.0
1725
+ */
1726
+ getLeaderboard = async (groupID) => {
1727
+ if (!this.userToken) {
1728
+ throw new SidechatAPIError("User is not authenticated.");
1729
+ }
1730
+ try {
1731
+ const res = await this.sendRequest(`/v1/leaderboard?group_id=${groupID}`);
1732
+ return await res.json();
1733
+ } catch (err) {
1734
+ console.error(err);
1735
+ throw new SidechatAPIError(`Failed to get leaderboard.`);
1736
+ }
1737
+ };
1738
+
1739
+ /**
1740
+ * Gets the existing direct message thread for a post or comment. Returns undefined if the user hasn't started one.
1741
+ * @method
1742
+ * @param {String} postID - alphanumeric ID of post or comment
1743
+ * @returns {Promise<types.SidechatDirectThread>}
1744
+ * @since 2.7.0
1745
+ */
1746
+ getDMForPost = async (postID) => {
1747
+ if (!this.userToken) {
1748
+ throw new SidechatAPIError("User is not authenticated.");
1749
+ }
1750
+ try {
1751
+ const res = await this.sendRequest(`/v1/chats/by_post?post_id=${postID}`);
1752
+ const json = await res.json();
1753
+ return json.chat;
1754
+ } catch (err) {
1755
+ console.error(err);
1756
+ throw new SidechatAPIError(`Failed to get DM thread for post.`);
1757
+ }
1758
+ };
1759
+
1760
+ /**
1761
+ * Reports a direct message thread
1762
+ * @method
1763
+ * @param {String} chatID - alphanumeric ID of the thread to report
1764
+ * @param {types.SidechatReportReason} reason - why the thread is being reported
1765
+ * @since 2.7.0
1766
+ */
1767
+ reportDM = async (chatID, reason) => {
1768
+ if (!this.userToken) {
1769
+ throw new SidechatAPIError("User is not authenticated.");
1770
+ }
1771
+ try {
1772
+ const res = await this.sendRequest(
1773
+ "/v1/chats/report",
1774
+ "POST",
1775
+ JSON.stringify({ chat_id: chatID, reason: reason }),
1776
+ );
1777
+ return await res.json();
1778
+ } catch (err) {
1779
+ console.error(err);
1780
+ throw new SidechatAPIError(`Failed to report DM.`);
1781
+ }
1782
+ };
1783
+
1784
+ /**
1785
+ * Sets which push notifications the user receives
1786
+ * @method
1787
+ * @param {types.SidechatNotificationPreferences} preferences - notification types to enable or disable
1788
+ * @since 2.7.0
1789
+ */
1790
+ setNotificationPreferences = async (preferences) => {
1791
+ if (!this.userToken) {
1792
+ throw new SidechatAPIError("User is not authenticated.");
1793
+ }
1794
+ try {
1795
+ const res = await this.sendRequest(
1796
+ "/v1/users/set_notification_preferences",
1797
+ "POST",
1798
+ JSON.stringify({ notification_preferences: preferences }),
1799
+ );
1800
+ return await res.json();
1801
+ } catch (err) {
1802
+ console.error(err);
1803
+ throw new SidechatAPIError(`Failed to set notification preferences.`);
1804
+ }
1805
+ };
1806
+
1807
+ /**
1808
+ * Verifies the user's school email by signing in with Google or Microsoft instead of clicking an emailed link. Returns the same object as checkEmailVerification(). This was discovered from me decompiling the new Android app, I haven't tested this but based on the implementation it should work fine and might resolve the problem that a lot of people have with their accounts not being associated with their school automatically.
1809
+ * @method
1810
+ * @param {String} authToken - token from the Google or Microsoft sign-in
1811
+ * @param {"google"|"microsoft"} authType - provider the token came from
1812
+ * @param {Boolean} [isIncomingFreshman] - whether or not the user is an incoming freshman
1813
+ * @since 2.7.0
1814
+ * @tutorial Email Registration
1815
+ */
1816
+ verifyEmailWithThirdParty = async (
1817
+ authToken,
1818
+ authType,
1819
+ isIncomingFreshman = false,
1820
+ ) => {
1821
+ if (!this.userToken) {
1822
+ throw new SidechatAPIError("User is not authenticated.");
1823
+ }
1824
+ try {
1825
+ const res = await this.sendRequest(
1826
+ "/v1/users/third_party_email_verified",
1827
+ "POST",
1828
+ JSON.stringify({
1829
+ auth: authToken,
1830
+ auth_type: authType,
1831
+ is_incoming_freshman: isIncomingFreshman,
1832
+ }),
1833
+ );
1834
+ const json = await res.json();
1835
+ if (json.verified_email_updates_response) {
1836
+ return json.verified_email_updates_response;
1837
+ } else {
1838
+ throw new SidechatAPIError(json?.message || "Email is not verified.");
1839
+ }
1840
+ } catch (err) {
1841
+ console.error(err);
1842
+ throw new SidechatAPIError("Failed to verify email.");
1843
+ }
1844
+ };
1845
+
1846
+ /**
1847
+ * Authorizes a realtime (Pusher) connection to subscribe to a private channel, such as `private-chat-<chatID>` for live direct messages. Pass the result to your Pusher client's channel authorizer. This hasn't been tested, and is mostly theoretical.
1848
+ * @method
1849
+ * @param {String} socketID - socket ID assigned by Pusher
1850
+ * @param {String} channelName - name of the private channel to subscribe to
1851
+ * @returns {Promise<{auth: String}>} signed channel authorization
1852
+ * @since 2.7.0
1853
+ */
1854
+ authorizeRealtimeChannel = async (socketID, channelName) => {
1855
+ if (!this.userToken) {
1856
+ throw new SidechatAPIError("User is not authenticated.");
1857
+ }
1858
+ try {
1859
+ const res = await this.sendRequest(
1860
+ "/pusher/auth",
1861
+ "POST",
1862
+ JSON.stringify({ socket_id: socketID, channel_name: channelName }),
1863
+ );
1864
+ return await res.json();
1865
+ } catch (err) {
1866
+ console.error(err);
1867
+ throw new SidechatAPIError(`Failed to authorize realtime channel.`);
1868
+ }
1869
+ };
1870
+
1871
+ /**
1872
+ * Permanently deletes the current user's account. This cannot be undone!
1873
+ * @method
1874
+ * @since 2.7.0
1875
+ */
1876
+ deleteAccount = async () => {
1877
+ if (!this.userToken) {
1878
+ throw new SidechatAPIError("User is not authenticated.");
1879
+ }
1880
+ try {
1881
+ const res = await this.sendRequest(
1882
+ "/v1/users/delete",
1883
+ "POST",
1884
+ JSON.stringify({}),
1885
+ );
1886
+ return await res.json();
1887
+ } catch (err) {
1888
+ console.error(err);
1889
+ throw new SidechatAPIError(`Failed to delete account.`);
1890
+ }
1891
+ };
1442
1892
  }
1443
1893
 
1444
1894
  export default SidechatAPIClient;