@stoatx/client 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -109,7 +109,6 @@ var Message = class extends Base {
109
109
  this.channelId = data.channel;
110
110
  const timestamp = decodeTime(this.id);
111
111
  if (timestamp) {
112
- console.log(timestamp);
113
112
  this.createdAt = new Date(timestamp);
114
113
  }
115
114
  if (data.attachments) {
@@ -385,7 +384,7 @@ var GatewayManager = class {
385
384
  if (payload.users) {
386
385
  for (const rawUser of payload.users) {
387
386
  this.client.users._add(rawUser);
388
- if (rawUser.relation === "User" && !this.client.user) {
387
+ if (rawUser.relationship === "User" && !this.client.user) {
389
388
  this.client.user = new ClientUser(this.client, rawUser);
390
389
  }
391
390
  }
@@ -552,7 +551,8 @@ var GatewayManager = class {
552
551
  }
553
552
  reconnect() {
554
553
  if (!this.token) {
555
- return this.client.emit("error", new Error("RECONNECT_FAILED: No token available."));
554
+ this.client.emit("error", new Error("RECONNECT_FAILED: No token available."));
555
+ return;
556
556
  }
557
557
  let waitTime = Math.pow(2, this.reconnectAttempts) * 1e3;
558
558
  const jitter = waitTime * 0.2 * Math.random();
@@ -586,6 +586,46 @@ var AsyncBucket = class {
586
586
  resetAt = 0;
587
587
  queue = Promise.resolve();
588
588
  };
589
+ var StoatAPIError = class _StoatAPIError extends Error {
590
+ statusCode;
591
+ apiType;
592
+ location;
593
+ rawData;
594
+ method;
595
+ path;
596
+ constructor(statusCode, data, method, path) {
597
+ let errorMessage = "Unknown API Error";
598
+ let type = "Unknown";
599
+ let location = "Unknown";
600
+ if (typeof data === "object" && data !== null) {
601
+ if ("type" in data || "location" in data) {
602
+ type = String(data.type || type);
603
+ location = String(data.location || location);
604
+ errorMessage = `Type: ${type} (Location: ${location})`;
605
+ } else if ("message" in data) {
606
+ errorMessage = String(data.message);
607
+ } else {
608
+ try {
609
+ errorMessage = JSON.stringify(data);
610
+ } catch {
611
+ errorMessage = "Unparseable Error Object";
612
+ }
613
+ }
614
+ } else if (typeof data === "string" && data.trim() !== "") {
615
+ errorMessage = data;
616
+ }
617
+ const routeInfo = method && path ? ` on ${method.toUpperCase()} ${path}` : "";
618
+ super(`${errorMessage}${routeInfo}`);
619
+ this.name = `StoatAPIError[${statusCode}]`;
620
+ this.statusCode = statusCode;
621
+ this.apiType = type;
622
+ this.location = location;
623
+ this.rawData = data;
624
+ this.method = method;
625
+ this.path = path;
626
+ Object.setPrototypeOf(this, _StoatAPIError.prototype);
627
+ }
628
+ };
589
629
  var RESTManager = class {
590
630
  constructor(client) {
591
631
  this.client = client;
@@ -645,22 +685,23 @@ var RESTManager = class {
645
685
  bucket.remaining = Number(remainingHeader);
646
686
  bucket.resetAt = Date.now() + Number(resetAfterHeader);
647
687
  }
688
+ const textBody = await response.body.text();
689
+ let data;
690
+ try {
691
+ data = JSON.parse(textBody);
692
+ } catch {
693
+ data = textBody;
694
+ }
648
695
  if (response.statusCode === 429) {
649
- const data2 = await response.body.json();
650
- const retryMs = data2.retry_after || Number(resetAfterHeader) || 5e3;
696
+ const retryMs = typeof data === "object" && data?.retry_after ? data.retry_after : Number(resetAfterHeader) || 5e3;
651
697
  this.client.emit("debug", `Hit 429 on [${method}:${endpoint}]. Retrying in ${retryMs}ms.`);
652
698
  bucket.remaining = 0;
653
699
  bucket.resetAt = Date.now() + retryMs;
654
700
  await sleep(retryMs);
655
701
  return this.execute(method, endpoint, body, bucket);
656
702
  }
657
- const data = await response.body.json();
658
703
  if (response.statusCode >= 400) {
659
- let errorMessage = "Unknown Error";
660
- if (typeof data === "object" && data !== null && "message" in data) {
661
- errorMessage = String(data.message);
662
- }
663
- throw new Error(`[Stoat API Error ${response.statusCode}]: ${errorMessage}`);
704
+ throw new StoatAPIError(response.statusCode, data, method, endpoint);
664
705
  }
665
706
  return data;
666
707
  }
@@ -680,7 +721,14 @@ var RESTManager = class {
680
721
  body: formData
681
722
  });
682
723
  if (!response.ok) {
683
- throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
724
+ let errData;
725
+ const errText = await response.text();
726
+ try {
727
+ errData = JSON.parse(errText);
728
+ } catch {
729
+ errData = errText;
730
+ }
731
+ throw new StoatAPIError(response.status, errData, "POST", "/attachments");
684
732
  }
685
733
  const data = await response.json();
686
734
  return data.id;
@@ -780,11 +828,17 @@ import * as util2 from "util";
780
828
 
781
829
  // src/managers/BaseManager.ts
782
830
  var BaseManager = class {
831
+ cache;
832
+ client;
783
833
  constructor(client, limit = Infinity) {
784
834
  this.client = client;
785
835
  this.cache = new Collection(limit);
836
+ Object.defineProperty(this, "client", {
837
+ value: client,
838
+ enumerable: false,
839
+ writable: false
840
+ });
786
841
  }
787
- cache;
788
842
  /**
789
843
  * Transforms raw data into a Structure, patches if existing, and saves to cache.
790
844
  * @internal
@@ -1493,16 +1547,28 @@ var MemberRoleManager = class {
1493
1547
  }
1494
1548
  };
1495
1549
 
1550
+ // src/utils/Constants.ts
1551
+ var StoatCDN = "https://cdn.stoatusercontent.com";
1552
+
1496
1553
  // src/structures/Member.ts
1497
1554
  var Member = class extends Base {
1555
+ // The server ID the member is in
1498
1556
  serverId;
1557
+ // The nickname the member has in the server, if any.
1499
1558
  nickname = null;
1559
+ // The avatar the member has in the server, if any.
1500
1560
  avatar = null;
1501
- roleIds = [];
1561
+ /** @internal */
1562
+ _roles = [];
1563
+ // The date the member joined the server
1502
1564
  joinedAt;
1565
+ // The date the member's timeout expires, or null if not timed out
1503
1566
  timeout = null;
1567
+ // Whatever the user can talk in Voice Chat
1504
1568
  canPublish = false;
1569
+ // Whatever the user can hear in Voice Chat
1505
1570
  canRecieve = false;
1571
+ // Member roles manager
1506
1572
  roles;
1507
1573
  constructor(client, data) {
1508
1574
  super(client, { _id: data.user._id });
@@ -1514,11 +1580,17 @@ var Member = class extends Base {
1514
1580
  _patch(data) {
1515
1581
  if (data.nickname !== void 0) this.nickname = data.nickname;
1516
1582
  if (data.avatar !== void 0) this.avatar = data.avatar;
1517
- if (data.roles !== void 0) this.roleIds = data.roles;
1583
+ if (data.roles !== void 0) this._roles = data.roles;
1518
1584
  if (data.timeout !== void 0) this.timeout = data.timeout ? new Date(data.timeout) : null;
1519
1585
  if (data.can_publish !== void 0) this.canPublish = data.canPublish;
1520
1586
  if (data.can_recieve !== void 0) this.canRecieve = data.canRecieve;
1521
1587
  }
1588
+ /**
1589
+ * Get member role IDs
1590
+ */
1591
+ get roleIds() {
1592
+ return this._roles;
1593
+ }
1522
1594
  /** Gets the global User object for this member */
1523
1595
  get user() {
1524
1596
  return this.client.users.cache.get(this.id);
@@ -1527,18 +1599,12 @@ var Member = class extends Base {
1527
1599
  get server() {
1528
1600
  return this.client.servers.cache.get(this.serverId);
1529
1601
  }
1530
- /** Resolves the array of role strings into actual Role objects */
1531
- get roleObjects() {
1532
- const server = this.server;
1533
- if (!server) return [];
1534
- return this.roleIds.map((id) => server.roles.cache.get(id)).filter((role) => role !== void 0);
1535
- }
1536
1602
  /** Calculates the member's total permissions using BigInt */
1537
1603
  get permissions() {
1538
1604
  const server = this.server;
1539
1605
  if (!server) return 0n;
1540
1606
  let totalPerms = server.defaultPermissions ?? 0n;
1541
- for (const role of this.roleObjects) {
1607
+ for (const role of this.roles.cache.values()) {
1542
1608
  totalPerms |= BigInt(role.permissions);
1543
1609
  }
1544
1610
  if (server.ownerId === this.id) {
@@ -1546,40 +1612,76 @@ var Member = class extends Base {
1546
1612
  }
1547
1613
  return totalPerms;
1548
1614
  }
1549
- /** Checks if the member has a specific permission */
1550
- hasPermission(permission) {
1551
- return Permissions.has(this.permissions, permission);
1615
+ /** Get avatar URL for this member, or null if they don't have one.
1616
+ * @example
1617
+ * // Get a member's avatar URL
1618
+ * const avatarURL = member.avatarURL;
1619
+ * console.log(avatarURL); // https://cdn.stoat.chat/attachments/avatars/1234567890/avatar.png
1620
+ */
1621
+ get avatarURL() {
1622
+ if (!this.avatar) return null;
1623
+ return `${StoatCDN}/attachments/avatars/${this.avatar.id}/${this.avatar.filename}`;
1552
1624
  }
1553
1625
  /**
1554
- * Edits this member's nickname, avatar, roles, or timeout.
1626
+ * Ban this member from the server.
1627
+ * @param options The options for this ban
1628
+ * @example
1629
+ * // Ban a member with a reason and delete their messages from the last hour
1630
+ * await member.ban({ reason: "Spamming", deleteMessageSeconds: 3600 });
1555
1631
  */
1556
- async edit(options) {
1632
+ async ban(options) {
1557
1633
  let server = this.server;
1558
1634
  if (!server) server = await this.client.servers.fetch(this.serverId);
1559
- return await server.members.edit(this.id, options);
1635
+ await server.members.ban(this.id, options);
1560
1636
  }
1561
1637
  /**
1562
- * Kicks this member from the server.
1638
+ * Creates a DM channel between the client's user and this member.
1639
+ * @param force If true, forces the creation of a new DM channel even if one already exists.
1640
+ * @returns A promise that resolves to the created DMChannel object.
1641
+ * @throws {Error} If the API request fails.
1642
+ * @example
1643
+ * // Create a DM with this member
1644
+ * const dm = await member.createDM();
1645
+ * console.log(`DM channel ID: ${dm.id}`);
1563
1646
  */
1564
- async kick() {
1647
+ async createDM(force = false) {
1648
+ await this.client.users.createDM(this.id, { force });
1649
+ }
1650
+ /**
1651
+ * Timeout this member for a specified duration.
1652
+ * @param duration The duration of the timeout in milliseconds.
1653
+ * @example
1654
+ * // Timeout a member for 10 minutes (600,000 milliseconds)
1655
+ * await member.setTimeout(600000);
1656
+ */
1657
+ async setTimeout(duration) {
1565
1658
  let server = this.server;
1566
1659
  if (!server) server = await this.client.servers.fetch(this.serverId);
1567
- await server.members.kick(this.id);
1660
+ await server.members.setTimeout(this.id, duration);
1661
+ }
1662
+ /** Checks if the member has a specific permission */
1663
+ hasPermission(permission) {
1664
+ return Permissions.has(this.permissions, permission);
1568
1665
  }
1569
1666
  /**
1570
- * Bans this member from the server.
1571
- * @param options The options for this ban
1667
+ * Edit this member.
1668
+ * @param options The options to edit the member with (nickname, roles, timeout, etc.)
1669
+ * @returns A promise that resolves to the updated Member.
1572
1670
  */
1573
- async ban(options) {
1671
+ async edit(options) {
1574
1672
  let server = this.server;
1575
1673
  if (!server) server = await this.client.servers.fetch(this.serverId);
1576
- await server.members.ban(this.id, options);
1674
+ return await server.members.edit(this.id, options);
1577
1675
  }
1578
- async unban() {
1579
- let server = this.client.servers.cache.get(this.serverId);
1676
+ /**
1677
+ * Kick this member from the server.
1678
+ */
1679
+ async kick() {
1680
+ let server = this.server;
1580
1681
  if (!server) server = await this.client.servers.fetch(this.serverId);
1581
- await server.members.unban(this.id);
1682
+ await server.members.kick(this.id);
1582
1683
  }
1684
+ /** @internal */
1583
1685
  [util4.inspect.custom](depth, options, inspect10) {
1584
1686
  const { client, serverId, ...props } = this;
1585
1687
  return `${this.constructor.name} ${inspect10(
@@ -1617,24 +1719,48 @@ var MemberManager = class extends BaseManager {
1617
1719
  }
1618
1720
  return new Member(this.client, data);
1619
1721
  }
1722
+ /**
1723
+ * Resolve a string or mention to Member
1724
+ * @param member The MemberResolvable to resolve
1725
+ * @returns The resolved Member or undefined if not found
1726
+ */
1620
1727
  resolve(member) {
1621
1728
  if (member instanceof Member) return member;
1622
1729
  if (member instanceof User) return this.cache.get(member.id);
1623
1730
  if (typeof member === "string") return this.cache.get(member.replace(/[<@>]/g, ""));
1624
1731
  return void 0;
1625
1732
  }
1733
+ /**
1734
+ * Resolve a Member to their ID string.
1735
+ * @param member The MemberResolvable to resolve
1736
+ * @returns The resolved ID string
1737
+ * @throws {TypeError} If the provided resolvable is invalid
1738
+ */
1626
1739
  resolveId(member) {
1627
1740
  if (typeof member === "string") return member.replace(/[<@>]/g, "");
1628
1741
  if ("id" in member) return member.id;
1629
1742
  throw new TypeError("Invalid MemberResolvable provided.");
1630
1743
  }
1631
- async fetch(member, force = true) {
1744
+ /**
1745
+ * Fetches a member from the server, or returns the cached version if available and not forced.
1746
+ * @param member The MemberResolvable to fetch
1747
+ * @param force Whether to bypass the cache and fetch fresh data from the API
1748
+ * @returns A promise that resolves to the fetched Member
1749
+ * @throws {Error} If the API request fails or the member is not found
1750
+ * @example
1751
+ * // Fetch a member by ID, using cache if available
1752
+ * const member = await server.members.fetch("1234567890");
1753
+ *
1754
+ * // Fetch a member by mention, bypassing cache
1755
+ * const member = await server.members.fetch("<@1234567890>", true);
1756
+ */
1757
+ async fetch(member, force = false) {
1632
1758
  if (!force) {
1633
1759
  const cached = this.resolve(member);
1634
1760
  if (cached) return cached;
1635
1761
  }
1636
1762
  const id = this.resolveId(member);
1637
- const data = await this.client.rest.get(`/channels/${id}`);
1763
+ const data = await this.client.rest.get(`/servers/${this.server.id}/members/${id}`);
1638
1764
  return this._add(data);
1639
1765
  }
1640
1766
  /**
@@ -1674,6 +1800,14 @@ var MemberManager = class extends BaseManager {
1674
1800
  * Edits a member in the server.
1675
1801
  * @param member The MemberResolvable to edit.
1676
1802
  * @param options The fields to update (nickname, roles, timeout, etc.).
1803
+ * @returns A promise that resolves to the updated Member.
1804
+ * @throws {Error} If the API request fails or the member is not found.
1805
+ * @example
1806
+ * // Change a member's nickname and add a role
1807
+ * const updatedMember = await server.members.edit("1234567890", {
1808
+ * nickname: "New Nickname",
1809
+ * roles: ["roleId1", "roleId2"],
1810
+ * });
1677
1811
  */
1678
1812
  async edit(member, options) {
1679
1813
  const id = this.resolveId(member);
@@ -1700,6 +1834,9 @@ var MemberManager = class extends BaseManager {
1700
1834
  /**
1701
1835
  * Kicks a member from the server.
1702
1836
  * @param member The MemberResolvable to kick.
1837
+ * @example
1838
+ * // Kick a member by ID
1839
+ * await server.members.kick("1234567890");
1703
1840
  */
1704
1841
  async kick(member) {
1705
1842
  const id = this.resolveId(member);
@@ -1710,6 +1847,9 @@ var MemberManager = class extends BaseManager {
1710
1847
  * Bans a member from the server.
1711
1848
  * @param member The MemberResolvable to ban.
1712
1849
  * @param options The ban options
1850
+ * @example
1851
+ * // Ban a member by ID
1852
+ * await server.members.ban("1234567890", { reason: "Spamming", deleteMessageSeconds: 3600 });
1713
1853
  */
1714
1854
  async ban(member, options) {
1715
1855
  const id = this.resolveId(member);
@@ -1722,11 +1862,26 @@ var MemberManager = class extends BaseManager {
1722
1862
  /**
1723
1863
  * Unbans a user from the server
1724
1864
  * @param member The MemberResolvable to unban
1865
+ * @example
1866
+ * // Unban a member by ID
1867
+ * await server.members.unban("1234567890");
1725
1868
  */
1726
1869
  async unban(member) {
1727
1870
  const id = this.resolveId(member);
1728
1871
  await this.client.rest.delete(`/servers/${this.server.id}/bans/${id}`);
1729
1872
  }
1873
+ /**
1874
+ * Timeouts a member in the server for a specified duration.
1875
+ * @param member The MemberResolvable to timeout
1876
+ * @param duration The duration of the timeout in milliseconds
1877
+ * @example
1878
+ * // Timeout a member for 10 minutes
1879
+ * await server.members.setTimeout("1234567890", 10 * 60 * 1000);
1880
+ */
1881
+ async setTimeout(member, duration) {
1882
+ const id = this.resolveId(member);
1883
+ await this.edit(id, { timeout: new Date(Date.now() + duration).toISOString() });
1884
+ }
1730
1885
  [util5.inspect.custom]() {
1731
1886
  return this.cache;
1732
1887
  }
@@ -2091,7 +2246,7 @@ var RoleManager = class extends BaseManager {
2091
2246
  * console.log("Role deleted successfully.");
2092
2247
  *
2093
2248
  * // Delete a role using a Role object
2094
- * const role = await server.roles.fetch("01JE2MM759J5D7CHJF084R7MJ2");
2249
+ * const role = await server.roles.fetch("01JE2MM759J5D7CHJF084R7");
2095
2250
  * await server.roles.delete(role);
2096
2251
  * console.log("Role deleted successfully.");
2097
2252
  *
@@ -2579,6 +2734,38 @@ var UserManager = class extends BaseManager {
2579
2734
  const data = await this.client.rest.patch(`/users/@me`, payload);
2580
2735
  return this._add(data);
2581
2736
  }
2737
+ /**
2738
+ * The DM between the client's user and a user
2739
+ *
2740
+ * @param {string} userId The user id
2741
+ * @returns {?DMChannel}
2742
+ * @private
2743
+ */
2744
+ dmChannel(userId) {
2745
+ return this.client.channels.cache.find((channel) => channel.isDM() && channel.recipients.includes(userId)) ?? null;
2746
+ }
2747
+ /**
2748
+ * Creates a DM channel between the client's user and another user.
2749
+ * @param user The UserResolvable to create a DM with.
2750
+ * @param options Additional options for DM creation.
2751
+ * @param options.force If true, forces the creation of a new DM channel even if one already exists.
2752
+ * @returns A promise that resolves to the created DMChannel object.
2753
+ * @throws {TypeError} If an invalid UserResolvable is provided.
2754
+ * @throws {Error} If the API request fails.
2755
+ * @example
2756
+ * // Create a DM with a user by ID
2757
+ * const dm = await client.users.createDM("1234567890");
2758
+ * console.log(`DM channel ID: ${dm.id}`);
2759
+ */
2760
+ async createDM(user, { force = false } = {}) {
2761
+ const id = this.resolveId(user);
2762
+ if (!force) {
2763
+ const dmChannel = this.dmChannel(id);
2764
+ if (dmChannel) return dmChannel;
2765
+ }
2766
+ const data = await this.client.rest.get(`/users/${id}/dm`);
2767
+ return this.client.channels._add(data);
2768
+ }
2582
2769
  };
2583
2770
 
2584
2771
  // src/managers/SweepManager.ts
@@ -2724,6 +2911,7 @@ export {
2724
2911
  Server,
2725
2912
  ServerChannelManager,
2726
2913
  ServerManager,
2914
+ StoatAPIError,
2727
2915
  SweeperManager,
2728
2916
  TextChannel,
2729
2917
  UnknownChannel,
@@ -2732,3 +2920,4 @@ export {
2732
2920
  UserPresence,
2733
2921
  UserRelationship
2734
2922
  };
2923
+ //# sourceMappingURL=index.js.map