@chainfuse/helpers 0.1.8 → 0.3.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.
@@ -0,0 +1,21 @@
1
+ import type { CustomLoging } from '@chainfuse/types';
2
+ import type { ExecutionContext } from '@cloudflare/workers-types/experimental';
3
+ import { REST } from '@discordjs/rest';
4
+ export declare class DiscordHelpers {
5
+ /**
6
+ * Discord Epoch, the first second of 2015 or 1420070400000
7
+ * @link https://discord.com/developers/docs/reference#snowflakes
8
+ */
9
+ static readonly discordEpoch: bigint;
10
+ /**
11
+ * Generate a Discord Snowflake ID from a given date. If no date is provided, the current date is used.
12
+ * @link https://discord.com/developers/docs/reference#snowflake-ids-in-pagination-generating-a-snowflake-id-from-a-timestamp-example
13
+ */
14
+ static dateToDiscordSnowflake(date?: Date): bigint;
15
+ /**
16
+ * Generate a date from a Discord Snowflake ID.
17
+ * @link https://discord.com/developers/docs/reference#snowflakes-snowflake-id-format-structure-left-to-right
18
+ */
19
+ static discordSnowflakeToDate(snowflakeRaw?: bigint | string): Date;
20
+ static discordRest(apiKey: string, cacheTtl?: number, forceCache?: boolean, executionContext?: ExecutionContext, logger?: CustomLoging): REST;
21
+ }
@@ -0,0 +1,184 @@
1
+ import { REST } from '@discordjs/rest';
2
+ import { CryptoHelpers } from './crypto.mjs';
3
+ import { NetHelpers } from './net.mjs';
4
+ export class DiscordHelpers {
5
+ /**
6
+ * Discord Epoch, the first second of 2015 or 1420070400000
7
+ * @link https://discord.com/developers/docs/reference#snowflakes
8
+ */
9
+ static discordEpoch = BigInt(1420070400000);
10
+ /**
11
+ * Generate a Discord Snowflake ID from a given date. If no date is provided, the current date is used.
12
+ * @link https://discord.com/developers/docs/reference#snowflake-ids-in-pagination-generating-a-snowflake-id-from-a-timestamp-example
13
+ */
14
+ static dateToDiscordSnowflake(date = new Date()) {
15
+ const minDate = new Date(Number(this.discordEpoch));
16
+ const maxDate = new Date(2 ** 42 - 1);
17
+ if (date < minDate) {
18
+ throw new RangeError("The date is before discord's epoch");
19
+ }
20
+ else if (date > maxDate) {
21
+ throw new RangeError('The date is after the highest supported date');
22
+ }
23
+ return (BigInt(date.valueOf()) - this.discordEpoch) << BigInt(22);
24
+ }
25
+ /**
26
+ * Generate a date from a Discord Snowflake ID.
27
+ * @link https://discord.com/developers/docs/reference#snowflakes-snowflake-id-format-structure-left-to-right
28
+ */
29
+ static discordSnowflakeToDate(snowflakeRaw = this.discordEpoch) {
30
+ const snowflake = BigInt(snowflakeRaw);
31
+ return new Date(Number((snowflake >> BigInt(22)) + this.discordEpoch));
32
+ }
33
+ static discordRest(apiKey, cacheTtl = 24 * 60 * 60, forceCache = false, executionContext, logger = false) {
34
+ return new REST({
35
+ agent: null,
36
+ authPrefix: 'Bot',
37
+ makeRequest: (url, rawInit) => {
38
+ // Extra safety to make sure the string really is a URL
39
+ const info = new URL(url);
40
+ // CF's implementation of `RequestInit` is functionally the same as w3c `RequestInit` but TS doesn't know that
41
+ const init = rawInit;
42
+ const cacheKey = new Request(info, { ...init, headers: NetHelpers.stripSensitiveHeaders(new Headers(init.headers)) });
43
+ if (logger) {
44
+ console.debug('Discord Fetch request', cacheKey.url, JSON.stringify(NetHelpers.initBodyTrimmer({ ...init, headers: Object.fromEntries(NetHelpers.stripSensitiveHeaders(new Headers(init.headers)).entries()) }), null, '\t'));
45
+ }
46
+ /**
47
+ * Only cache GET requests
48
+ * Empty method means GET
49
+ */
50
+ if (cacheTtl && ((rawInit.method ?? 'GET').toLowerCase() === 'get' || forceCache)) {
51
+ // const cfCacheRef: Promise<Cache> | undefined = caches?.open('cfApi');
52
+ const discordCacheRef = caches?.open(`discordApi`);
53
+ return discordCacheRef
54
+ .then((discordCache) => discordCache
55
+ .match(cacheKey)
56
+ .then(async (cachedResponse) => {
57
+ if (cachedResponse) {
58
+ if (logger) {
59
+ console.debug('Discord Cache hit', cacheKey.url);
60
+ }
61
+ // Clear out bad cache
62
+ if (cachedResponse.status >= 500) {
63
+ if (executionContext) {
64
+ executionContext.waitUntil(discordCache.delete(cacheKey));
65
+ }
66
+ else {
67
+ await discordCache.delete(cacheKey);
68
+ }
69
+ }
70
+ return cachedResponse;
71
+ }
72
+ else {
73
+ if (logger) {
74
+ console.warn('Discord Cache missed', cacheKey.url);
75
+ }
76
+ return NetHelpers.loggingFetch(info, init, undefined, logger).then(async (response) => {
77
+ if (response.ok) {
78
+ response = new Response(response.body, {
79
+ ...response,
80
+ headers: NetHelpers.stripSensitiveHeaders(new Headers(response.headers)),
81
+ });
82
+ response.headers.set('Cache-Control', `s-maxage=${cacheTtl}`);
83
+ if (response.headers.has('ETag')) {
84
+ if (executionContext) {
85
+ executionContext.waitUntil(discordCache
86
+ .put(cacheKey, response.clone())
87
+ .then(() => {
88
+ if (logger) {
89
+ console.debug('Discord Cache saved', 'with original etag', cacheKey.url);
90
+ }
91
+ })
92
+ .catch((error) => {
93
+ if (logger) {
94
+ console.error('Discord Cache put error', error);
95
+ }
96
+ }));
97
+ }
98
+ else {
99
+ await discordCache
100
+ .put(cacheKey, response.clone())
101
+ .then(() => {
102
+ if (logger) {
103
+ console.debug('Discord Cache saved', 'with original etag', cacheKey.url);
104
+ }
105
+ })
106
+ .catch((error) => {
107
+ if (logger) {
108
+ console.error('Discord Cache put error', error);
109
+ }
110
+ });
111
+ }
112
+ }
113
+ else {
114
+ if (executionContext) {
115
+ executionContext.waitUntil(CryptoHelpers.generateETag(response)
116
+ .then((etag) => response.headers.set('ETag', etag))
117
+ .then(() => discordCache
118
+ .put(cacheKey, response.clone())
119
+ .then(() => {
120
+ if (logger) {
121
+ console.debug('Discord Cache saved', 'with generated etag', cacheKey.url);
122
+ }
123
+ })
124
+ .catch((error) => {
125
+ if (logger) {
126
+ console.error('Discord Cache put error', error);
127
+ }
128
+ }))
129
+ .catch((error) => {
130
+ if (logger) {
131
+ console.error('CryptoHelpers generateETag error', error);
132
+ }
133
+ }));
134
+ }
135
+ else {
136
+ await CryptoHelpers.generateETag(response)
137
+ .then((etag) => response.headers.set('ETag', etag))
138
+ .then(() => discordCache
139
+ .put(cacheKey, response.clone())
140
+ .then(() => {
141
+ if (logger) {
142
+ console.debug('Discord Cache saved', 'with generated etag', cacheKey.url);
143
+ }
144
+ })
145
+ .catch((error) => {
146
+ if (logger) {
147
+ console.error('Discord Cache put error', error);
148
+ }
149
+ }))
150
+ .catch((error) => {
151
+ if (logger) {
152
+ console.error('CryptoHelpers generateETag error', error);
153
+ }
154
+ });
155
+ }
156
+ }
157
+ }
158
+ return response.clone();
159
+ });
160
+ }
161
+ })
162
+ .catch((error) => {
163
+ if (logger) {
164
+ console.error('Discord Cache match error', error);
165
+ }
166
+ return NetHelpers.loggingFetch(info, init, undefined, logger);
167
+ }))
168
+ .catch((error) => {
169
+ if (logger) {
170
+ console.error('Discord Cache open error', error);
171
+ }
172
+ return NetHelpers.loggingFetch(info, init, undefined, logger);
173
+ });
174
+ }
175
+ else {
176
+ if (logger) {
177
+ console.warn('Discord Cache ignored', cacheKey.url);
178
+ }
179
+ return NetHelpers.loggingFetch(info, init, undefined, logger);
180
+ }
181
+ },
182
+ }).setToken(apiKey);
183
+ }
184
+ }
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './buffers.mjs';
2
2
  export * from './crypto.mjs';
3
+ export * from './discord.mjs';
3
4
  export * from './net.mjs';
4
5
  export declare class Helpers {
5
6
  static precisionFloat(input: string): number;
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './buffers.mjs';
2
2
  export * from './crypto.mjs';
3
+ export * from './discord.mjs';
3
4
  export * from './net.mjs';
4
5
  export class Helpers {
5
6
  static precisionFloat(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainfuse/helpers",
3
- "version": "0.1.8",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "author": "ChainFuse",
6
6
  "homepage": "https://github.com/ChainFuse/packages/tree/main/packages/helpers#readme",
@@ -49,13 +49,14 @@
49
49
  },
50
50
  "prettier": "@demosjarco/prettier-config",
51
51
  "dependencies": {
52
+ "@discordjs/rest": "^2.4.0",
52
53
  "chalk": "^5.3.0",
53
54
  "cloudflare": "^3.5.0",
54
55
  "uuid": "^11.0.3"
55
56
  },
56
57
  "devDependencies": {
57
- "@chainfuse/types": "^1.1.5",
58
+ "@chainfuse/types": "^1.1.7",
58
59
  "@types/node": "^22.9.0"
59
60
  },
60
- "gitHead": "bd7a7c6300bbe005b6e8b6a973b1bf4eb7e015e9"
61
+ "gitHead": "8818545b1a8c724cf0a9d1698d00edc45eb489b3"
61
62
  }