@devvit/public-api 0.12.0-next-2025-04-10-a27f59c35.0 → 0.12.0-next-2025-04-10-14eb9113e.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/apis/reddit/RedditAPIClient.d.ts +40 -40
- package/apis/reddit/RedditAPIClient.js +35 -35
- package/apis/reddit/models/Flair.js +1 -1
- package/apis/reddit/models/ModMail.d.ts +23 -23
- package/apis/reddit/models/ModMail.js +23 -23
- package/apis/reddit/models/Post.d.ts +4 -4
- package/apis/reddit/models/Post.js +4 -4
- package/apis/reddit/models/Subreddit.d.ts +7 -7
- package/apis/reddit/models/Subreddit.js +2 -2
- package/apis/reddit/models/User.d.ts +1 -1
- package/apis/reddit/models/User.js +1 -1
- package/devvit/Devvit.d.ts +1 -1
- package/devvit/Devvit.js +1 -1
- package/meta.json +14 -14
- package/meta.min.json +8 -8
- package/package.json +7 -7
- package/public-api.d.ts +37 -37
- package/public-api.iife.js +33 -33
- package/public-api.min.js.map +1 -1
- package/types/media.d.ts +1 -1
package/public-api.min.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../node_modules/kind-of/index.js", "../../../node_modules/shallow-clone/index.js", "../../../node_modules/isobject/index.js", "../../../node_modules/clone-deep/node_modules/is-plain-object/index.js", "../../../node_modules/clone-deep/index.js", "../../../node_modules/base64-js/index.js", "../src/apis/reddit/common.ts", "../src/apis/reddit/index.ts", "../../shared-types/dist/Header.js", "../../shared-types/dist/assert.js", "../../shared-types/dist/tid.js", "../src/devvit/Devvit.ts", "../../shared-types/dist/Actor.js", "../src/types/form.ts", "../src/apis/ui/helpers/assertValidFormFields.ts", "../src/types/hooks.ts", "../src/types/icons.ts", "../src/types/triggers.ts", "../src/devvit/internals/app-settings.ts", "../src/apis/ui/helpers/transformForm.ts", "../src/devvit/internals/async-metadata.ts", "../src/devvit/internals/helpers/extendDevvitPrototype.ts", "../src/devvit/internals/helpers/settingsUtils.ts", "../src/devvit/internals/blocks/useChannel.ts", "../../shared-types/dist/NonNull.js", "../src/apis/ui/helpers/getFormValues.ts", "../src/devvit/internals/blocks/useForm.ts", "../src/devvit/internals/blocks/useInterval.ts", "../src/devvit/internals/blocks/useState.ts", "../src/devvit/internals/promise_cache.ts", "../src/devvit/internals/cache.ts", "../src/apis/AssetsClient/AssetsClient.ts", "../src/apis/key-value-storage/KeyValueStorage.ts", "../src/apis/media/MediaClient.ts", "../src/apis/modLog/ModLogClient.ts", "../src/apis/realtime/RealtimeClient.ts", "../src/apis/redis/RedisClient.ts", "../src/apis/scheduler/SchedulerClient.ts", "../src/apis/settings/SettingsClient.ts", "../src/apis/ui/UIClient.ts", "../src/apis/makeAPIClients.ts", "../package.json", "../src/devvit/internals/context.ts", "../src/devvit/internals/custom-post.ts", "../src/devvit/internals/blocks/BlocksReconciler.ts", "../src/apis/ui/helpers/getEffectsFromUIClient.ts", "../src/devvit/internals/helpers/makeUniqueIdGenerator.ts", "../src/devvit/internals/blocks/BlocksTransformer.ts", "../src/devvit/internals/semanticColors.ts", "../src/devvit/internals/helpers/color.ts", "../src/devvit/internals/blocks/transformContext.ts", "../src/devvit/internals/blocks/transformerUtils.ts", "../src/devvit/internals/installation-settings.ts", "../src/devvit/internals/menu-items.ts", "../src/devvit/internals/csrf.ts", "../src/devvit/internals/plugins.ts", "../src/devvit/internals/scheduler.ts", "../src/devvit/internals/triggers.ts", "../../shared-types/dist/StringUtil.js", "../src/devvit/internals/ui-event-handler.ts", "../../../node_modules/moderndash/src/array/chunk.ts", "../../../node_modules/moderndash/src/array/count.ts", "../../../node_modules/moderndash/src/helpers/fastArrayFlat.ts", "../../../node_modules/moderndash/src/array/difference.ts", "../../../node_modules/moderndash/src/array/dropRightWhile.ts", "../../../node_modules/moderndash/src/array/dropWhile.ts", "../../../node_modules/moderndash/src/array/group.ts", "../../../node_modules/moderndash/src/array/unique.ts", "../../../node_modules/moderndash/src/array/intersection.ts", "../../../node_modules/moderndash/src/array/move.ts", "../../../node_modules/moderndash/src/array/range.ts", "../../../node_modules/moderndash/src/array/shuffle.ts", "../../../node_modules/moderndash/src/array/sort.ts", "../../../node_modules/moderndash/src/array/takeRightWhile.ts", "../../../node_modules/moderndash/src/array/takeWhile.ts", "../../../node_modules/moderndash/src/crypto/hash.ts", "../../../node_modules/moderndash/src/crypto/randomInt.ts", "../../../node_modules/moderndash/src/crypto/randomElem.ts", "../../../node_modules/moderndash/src/crypto/randomFloat.ts", "../../../node_modules/moderndash/src/crypto/randomString.ts", "../../../node_modules/moderndash/src/decorator/toDecorator.ts", "../../../node_modules/moderndash/src/function/debounce.ts", "../../../node_modules/moderndash/src/decorator/decDebounce.ts", "../../../node_modules/moderndash/src/function/maxCalls.ts", "../../../node_modules/moderndash/src/decorator/decMaxCalls.ts", "../../../node_modules/moderndash/src/function/memoize.ts", "../../../node_modules/moderndash/src/decorator/decMemoize.ts", "../../../node_modules/moderndash/src/function/minCalls.ts", "../../../node_modules/moderndash/src/decorator/decMinCalls.ts", "../../../node_modules/moderndash/src/function/throttle.ts", "../../../node_modules/moderndash/src/decorator/decThrottle.ts", "../../../node_modules/moderndash/src/function/times.ts", "../../../node_modules/moderndash/src/number/sum.ts", "../../../node_modules/moderndash/src/number/average.ts", "../../../node_modules/moderndash/src/number/median.ts", "../../../node_modules/moderndash/src/number/round.ts", "../../../node_modules/moderndash/src/validate/isPlainObject.ts", "../../../node_modules/moderndash/src/object/flatKeys.ts", "../../../node_modules/moderndash/src/object/merge.ts", "../../../node_modules/moderndash/src/object/pick.ts", "../../../node_modules/moderndash/src/object/omit.ts", "../../../node_modules/moderndash/src/object/set.ts", "../../../node_modules/moderndash/src/promise/queue.ts", "../../../node_modules/moderndash/src/promise/races.ts", "../../../node_modules/moderndash/src/promise/sleep.ts", "../../../node_modules/moderndash/src/promise/retry.ts", "../../../node_modules/moderndash/src/promise/timeout.ts", "../../../node_modules/moderndash/src/promise/tryCatch.ts", "../../../node_modules/moderndash/src/string/splitWords.ts", "../../../node_modules/moderndash/src/string/capitalize.ts", "../../../node_modules/moderndash/src/string/deburr.ts", "../../../node_modules/moderndash/src/string/camelCase.ts", "../../../node_modules/moderndash/src/string/escapeHtml.ts", "../../../node_modules/moderndash/src/string/escapeRegExp.ts", "../../../node_modules/moderndash/src/string/kebabCase.ts", "../../../node_modules/moderndash/src/string/pascalCase.ts", "../../../node_modules/moderndash/src/string/replaceLast.ts", "../../../node_modules/moderndash/src/string/snakeCase.ts", "../../../node_modules/moderndash/src/string/titleCase.ts", "../../../node_modules/moderndash/src/string/trim.ts", "../../../node_modules/moderndash/src/string/trimEnd.ts", "../../../node_modules/moderndash/src/string/trimStart.ts", "../../../node_modules/moderndash/src/string/truncate.ts", "../../../node_modules/moderndash/src/string/unescapeHtml.ts", "../../../node_modules/moderndash/src/validate/isEmpty.ts", "../../../node_modules/moderndash/src/validate/isEqual.ts", "../../../node_modules/moderndash/src/validate/isUrl.ts", "../src/devvit/internals/ui-request-handler.tsx", "../src/devvit/internals/blocks/handler/BlocksHandler.ts", "../../shared-types/dist/CircuitBreaker.js", "../src/devvit/internals/blocks/handler/promise_cache.ts", "../src/devvit/internals/blocks/handler/cache.ts", "../src/devvit/internals/blocks/handler/UIClient.ts", "../../shared-types/dist/useForm.js", "../src/devvit/internals/blocks/handler/useForm.ts", "../src/devvit/internals/blocks/handler/useChannel.ts", "../src/devvit/internals/blocks/handler/useInterval.ts", "../src/devvit/internals/blocks/handler/RenderContext.ts", "../src/devvit/internals/blocks/handler/useState.ts", "../src/devvit/internals/blocks/handler/types.ts", "../src/devvit/internals/blocks/handler/useAsync.ts", "../src/devvit/internals/blocks/handler/ContextBuilder.ts", "../src/devvit/internals/upgrade-app-shim.tsx", "../src/apis/reddit/helpers/makeGettersEnumerable.ts", "../../shared-types/dist/richtext/types.js", "../../shared-types/dist/richtext/elements.js", "../../shared-types/dist/richtext/mixins.js", "../../shared-types/dist/richtext/RichTextBuilder.js", "../src/apis/reddit/helpers/richtextToString.ts", "../src/apis/reddit/models/Listing.ts", "../src/apis/reddit/models/ModNote.ts", "../src/apis/reddit/graphql/GraphQL.ts", "../src/apis/reddit/helpers/permissions.ts", "../src/apis/reddit/models/Flair.ts", "../src/apis/reddit/models/Post.ts", "../src/apis/reddit/helpers/textFallbackToRichtext.ts", "../src/apis/reddit/models/User.ts", "../src/apis/reddit/models/Comment.ts", "../src/apis/reddit/models/ModAction.ts", "../src/apis/reddit/models/ModMail.ts", "../src/apis/reddit/models/PrivateMessage.ts", "../src/apis/reddit/models/Subreddit.ts", "../src/apis/reddit/models/Widget.ts", "../src/apis/reddit/models/WikiPage.ts", "../src/apis/reddit/models/Vault.ts", "../../shared-types/dist/sanitizeSvg.js", "../src/apis/ui/helpers/svg.ts", "../src/devvit/internals/blocks/handler/useWebView.ts", "../src/devvit/internals/helpers/devvitInternalMessage.ts"],
|
|
4
|
-
"sourcesContent": ["var toString = Object.prototype.toString;\n\nmodule.exports = function kindOf(val) {\n if (val === void 0) return 'undefined';\n if (val === null) return 'null';\n\n var type = typeof val;\n if (type === 'boolean') return 'boolean';\n if (type === 'string') return 'string';\n if (type === 'number') return 'number';\n if (type === 'symbol') return 'symbol';\n if (type === 'function') {\n return isGeneratorFn(val) ? 'generatorfunction' : 'function';\n }\n\n if (isArray(val)) return 'array';\n if (isBuffer(val)) return 'buffer';\n if (isArguments(val)) return 'arguments';\n if (isDate(val)) return 'date';\n if (isError(val)) return 'error';\n if (isRegexp(val)) return 'regexp';\n\n switch (ctorName(val)) {\n case 'Symbol': return 'symbol';\n case 'Promise': return 'promise';\n\n // Set, Map, WeakSet, WeakMap\n case 'WeakMap': return 'weakmap';\n case 'WeakSet': return 'weakset';\n case 'Map': return 'map';\n case 'Set': return 'set';\n\n // 8-bit typed arrays\n case 'Int8Array': return 'int8array';\n case 'Uint8Array': return 'uint8array';\n case 'Uint8ClampedArray': return 'uint8clampedarray';\n\n // 16-bit typed arrays\n case 'Int16Array': return 'int16array';\n case 'Uint16Array': return 'uint16array';\n\n // 32-bit typed arrays\n case 'Int32Array': return 'int32array';\n case 'Uint32Array': return 'uint32array';\n case 'Float32Array': return 'float32array';\n case 'Float64Array': return 'float64array';\n }\n\n if (isGeneratorObj(val)) {\n return 'generator';\n }\n\n // Non-plain objects\n type = toString.call(val);\n switch (type) {\n case '[object Object]': return 'object';\n // iterators\n case '[object Map Iterator]': return 'mapiterator';\n case '[object Set Iterator]': return 'setiterator';\n case '[object String Iterator]': return 'stringiterator';\n case '[object Array Iterator]': return 'arrayiterator';\n }\n\n // other\n return type.slice(8, -1).toLowerCase().replace(/\\s/g, '');\n};\n\nfunction ctorName(val) {\n return typeof val.constructor === 'function' ? val.constructor.name : null;\n}\n\nfunction isArray(val) {\n if (Array.isArray) return Array.isArray(val);\n return val instanceof Array;\n}\n\nfunction isError(val) {\n return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number');\n}\n\nfunction isDate(val) {\n if (val instanceof Date) return true;\n return typeof val.toDateString === 'function'\n && typeof val.getDate === 'function'\n && typeof val.setDate === 'function';\n}\n\nfunction isRegexp(val) {\n if (val instanceof RegExp) return true;\n return typeof val.flags === 'string'\n && typeof val.ignoreCase === 'boolean'\n && typeof val.multiline === 'boolean'\n && typeof val.global === 'boolean';\n}\n\nfunction isGeneratorFn(name, val) {\n return ctorName(name) === 'GeneratorFunction';\n}\n\nfunction isGeneratorObj(val) {\n return typeof val.throw === 'function'\n && typeof val.return === 'function'\n && typeof val.next === 'function';\n}\n\nfunction isArguments(val) {\n try {\n if (typeof val.length === 'number' && typeof val.callee === 'function') {\n return true;\n }\n } catch (err) {\n if (err.message.indexOf('callee') !== -1) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * If you need to support Safari 5-7 (8-10 yr-old browser),\n * take a look at https://github.com/feross/is-buffer\n */\n\nfunction isBuffer(val) {\n if (val.constructor && typeof val.constructor.isBuffer === 'function') {\n return val.constructor.isBuffer(val);\n }\n return false;\n}\n", "/*!\n * shallow-clone <https://github.com/jonschlinkert/shallow-clone>\n *\n * Copyright (c) 2015-present, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n'use strict';\n\nconst valueOf = Symbol.prototype.valueOf;\nconst typeOf = require('kind-of');\n\nfunction clone(val, deep) {\n switch (typeOf(val)) {\n case 'array':\n return val.slice();\n case 'object':\n return Object.assign({}, val);\n case 'date':\n return new val.constructor(Number(val));\n case 'map':\n return new Map(val);\n case 'set':\n return new Set(val);\n case 'buffer':\n return cloneBuffer(val);\n case 'symbol':\n return cloneSymbol(val);\n case 'arraybuffer':\n return cloneArrayBuffer(val);\n case 'float32array':\n case 'float64array':\n case 'int16array':\n case 'int32array':\n case 'int8array':\n case 'uint16array':\n case 'uint32array':\n case 'uint8clampedarray':\n case 'uint8array':\n return cloneTypedArray(val);\n case 'regexp':\n return cloneRegExp(val);\n case 'error':\n return Object.create(val);\n default: {\n return val;\n }\n }\n}\n\nfunction cloneRegExp(val) {\n const flags = val.flags !== void 0 ? val.flags : (/\\w+$/.exec(val) || void 0);\n const re = new val.constructor(val.source, flags);\n re.lastIndex = val.lastIndex;\n return re;\n}\n\nfunction cloneArrayBuffer(val) {\n const res = new val.constructor(val.byteLength);\n new Uint8Array(res).set(new Uint8Array(val));\n return res;\n}\n\nfunction cloneTypedArray(val, deep) {\n return new val.constructor(val.buffer, val.byteOffset, val.length);\n}\n\nfunction cloneBuffer(val) {\n const len = val.length;\n const buf = Buffer.allocUnsafe ? Buffer.allocUnsafe(len) : Buffer.from(len);\n val.copy(buf);\n return buf;\n}\n\nfunction cloneSymbol(val) {\n return valueOf ? Object(valueOf.call(val)) : {};\n}\n\n/**\n * Expose `clone`\n */\n\nmodule.exports = clone;\n", "/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n'use strict';\n\nmodule.exports = function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n", "/*!\n * is-plain-object <https://github.com/jonschlinkert/is-plain-object>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n'use strict';\n\nvar isObject = require('isobject');\n\nfunction isObjectObject(o) {\n return isObject(o) === true\n && Object.prototype.toString.call(o) === '[object Object]';\n}\n\nmodule.exports = function isPlainObject(o) {\n var ctor,prot;\n\n if (isObjectObject(o) === false) return false;\n\n // If has modified constructor\n ctor = o.constructor;\n if (typeof ctor !== 'function') return false;\n\n // If has modified prototype\n prot = ctor.prototype;\n if (isObjectObject(prot) === false) return false;\n\n // If constructor does not have an Object-specific method\n if (prot.hasOwnProperty('isPrototypeOf') === false) {\n return false;\n }\n\n // Most likely a plain Object\n return true;\n};\n", "'use strict';\n\n/**\n * Module dependenices\n */\n\nconst clone = require('shallow-clone');\nconst typeOf = require('kind-of');\nconst isPlainObject = require('is-plain-object');\n\nfunction cloneDeep(val, instanceClone) {\n switch (typeOf(val)) {\n case 'object':\n return cloneObjectDeep(val, instanceClone);\n case 'array':\n return cloneArrayDeep(val, instanceClone);\n default: {\n return clone(val);\n }\n }\n}\n\nfunction cloneObjectDeep(val, instanceClone) {\n if (typeof instanceClone === 'function') {\n return instanceClone(val);\n }\n if (instanceClone || isPlainObject(val)) {\n const res = new val.constructor();\n for (let key in val) {\n res[key] = cloneDeep(val[key], instanceClone);\n }\n return res;\n }\n return val;\n}\n\nfunction cloneArrayDeep(val, instanceClone) {\n const res = new val.constructor(val.length);\n for (let i = 0; i < val.length; i++) {\n res[i] = cloneDeep(val[i], instanceClone);\n }\n return res;\n}\n\n/**\n * Expose `cloneDeep`\n */\n\nmodule.exports = cloneDeep;\n", "'use strict'\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n var i\n for (i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n", "export const RunAs = {\n APP: 0,\n USER: 1,\n} as const;\n\nexport type UserGeneratedContent = {\n text: string;\n imageUrls: string[];\n};\n", "import { type FlairCsvResult, type JsonStatus } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { T1ID, T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT3ID, asT5ID, asTID, isT1ID, isT3ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport { getMetadata } from '../../devvit/internals/async-metadata.js';\nimport type { BaseContext } from '../../types/index.js';\nimport {\n type AboutSubredditTypes,\n type AddRemovalNoteOptions,\n type AddWidgetData,\n type BanUserOptions,\n type BanWikiContributorOptions,\n type CommentSubmissionOptions,\n type CreateFlairTemplateOptions,\n type CreateModNoteOptions,\n type CreateWikiPageOptions,\n type CrosspostOptions,\n type DeleteNotesOptions,\n type EditFlairTemplateOptions,\n type GetCommentsByUserOptions,\n type GetCommentsOptions,\n getCurrentUserFromMetadata,\n type GetHotPostsOptions,\n type GetModerationLogOptions,\n type GetModNotesOptions,\n type GetPageRevisionsOptions,\n type GetPostsByUserOptions,\n type GetPostsOptions,\n type GetPostsOptionsWithTimeframe,\n type GetPrivateMessagesOptions,\n type GetSubredditUsersByTypeOptions,\n type GetUserOverviewOptions,\n type Listing,\n type ModAction,\n type ModeratorPermission,\n type ModLogOptions,\n type RemovalReason,\n type SendPrivateMessageAsSubredditOptions,\n type SendPrivateMessageOptions,\n type SetPostFlairOptions,\n type SetUserFlairBatchConfig,\n type SetUserFlairOptions,\n type SubmitPostOptions,\n type SubredditInfo,\n type SubredditLeaderboard,\n type SubredditStyles,\n type UpdatePageSettingsOptions,\n type UpdateWikiPageOptions,\n type Vault,\n type WikiPageRevision,\n type WikiPageSettings,\n} from './models/index.js';\nimport {\n _getModerationLog,\n _getSubredditInfoById,\n _getSubredditInfoByName,\n _getSubredditLeaderboard,\n _getSubredditNameById,\n _getSubredditStyles,\n AboutLocations,\n Comment,\n Flair,\n FlairTemplate,\n getCurrentUsernameFromMetadata,\n ModMailService,\n ModNote,\n Post,\n PrivateMessage,\n Subreddit,\n User,\n Widget,\n WikiPage,\n} from './models/index.js';\nimport {\n getVaultByAddress as _getVaultByAddress,\n getVaultByUserId as _getVaultByUserId,\n} from './models/Vault.js';\n\nexport type RedditContext = Pick<BaseContext, 'metadata'>;\n\ntype GetSubredditUsersOptions = Omit<GetSubredditUsersByTypeOptions, 'type'>;\n\nexport type InviteModeratorOptions = {\n /** The name of the subreddit to invite the user to moderate */\n subredditName: string;\n /** The name of the user to invite as a moderator */\n username: string;\n /** The permissions to grant the user */\n permissions?: ModeratorPermission[];\n};\n\nexport type MuteUserOptions = {\n /** The name of the subreddit to mute the user in */\n subredditName: string;\n /** The name of the user to mute */\n username: string;\n /** A mod note on why the user was muted. (optional) */\n note?: string;\n};\n\n/**\n * Get ModMail API object\n *\n * @example\n * ```ts\n * await modMail.reply({\n * body: \"Here is my message\",\n * conversationId: \"abcd42\";\n * })\n * ```\n */\nexport function modMail(): ModMailService {\n const metadata = getMetadata();\n return new ModMailService(metadata);\n}\n\n/**\n * Gets a {@link Subreddit} object by ID\n *\n * @deprecated Use {@link getSubredditInfoById} instead.\n * @param {string} id - The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n * @returns {Promise<Subreddit>} A Promise that resolves a Subreddit object.\n * @example\n * ```ts\n * const memes = await getSubredditById('t5_2qjpg');\n * ```\n */\nexport function getSubredditById(id: string): Promise<Subreddit | undefined> {\n const metadata = getMetadata();\n return Subreddit.getById(asTID<T5ID>(id), metadata);\n}\n\n/**\n * Gets a {@link SubredditInfo} object by ID\n *\n * @param {string} id - The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n * @example\n * ```ts\n * const memes = await getSubredditInfoById('t5_2qjpg');\n * ```\n */\nexport function getSubredditInfoById(id: string): Promise<SubredditInfo> {\n const metadata = getMetadata();\n return _getSubredditInfoById(id, metadata);\n}\n\n/**\n * Gets a {@link Subreddit} object by name\n *\n * @deprecated Use {@link getSubredditInfoByName} instead.\n * @param {string} name The name of a subreddit omitting the r/. This is case-insensitive.\n * @returns {Promise<Subreddit>} A Promise that resolves a Subreddit object.\n * @example\n * ```ts\n * const askReddit = await getSubredditByName('askReddit');\n * ```\n */\nexport function getSubredditByName(name: string): Promise<Subreddit> {\n const metadata = getMetadata();\n return Subreddit.getByName(name, metadata);\n}\n\n/**\n * Gets a {@link SubredditInfo} object by name\n *\n * @param {string} name The name of a subreddit omitting the r/. This is case-insensitive.\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n * @example\n * ```ts\n * const askReddit = await getSubredditInfoByName('askReddit');\n * ```\n */\nexport function getSubredditInfoByName(name: string): Promise<SubredditInfo> {\n const metadata = getMetadata();\n return _getSubredditInfoByName(name, metadata);\n}\n\n/**\n * Add a removal reason to a subreddit\n *\n * @param subredditName Name of the subreddit being removed.\n * @param options Options.\n * @param options.title The title of the removal reason.\n * @param options.message The message associated with the removal reason.\n * @example\n * ```ts\n * const newReason = await addSubredditRemovalReasons(\n * 'askReddit',\n * {\n * title: 'Spam',\n * message: 'This is spam!'\n * }\n * );\n * console.log(newReason.id)\n * ```\n *\n * @returns {string} Removal Reason ID\n */\nexport function addSubredditRemovalReason(\n subredditName: string,\n options: { title: string; message: string }\n): Promise<string> {\n const metadata = getMetadata();\n return Subreddit.addRemovalReason(subredditName, options.title, options.message, metadata);\n}\n\n/**\n * Get the list of subreddit's removal reasons (ordered)\n *\n * @param subredditName\n * @example\n * ```ts\n * const reasons = await getSubredditRemovalReasons('askReddit');\n *\n * for (let reason of reasons) {\n * console.log(reason.id, reason.message, reason.title)\n * }\n * ```\n *\n * @returns Ordered array of Removal Reasons\n */\nexport function getSubredditRemovalReasons(subredditName: string): Promise<RemovalReason[]> {\n const metadata = getMetadata();\n return Subreddit.getRemovalReasons(subredditName, metadata);\n}\n\n/**\n * Retrieves the name of the current subreddit.\n *\n * @returns {Promise<string>} A Promise that resolves a string representing the current subreddit's name.\n * @example\n * ```ts\n * const currentSubredditName = await getCurrentSubredditName();\n * ```\n */\nexport async function getCurrentSubredditName(): Promise<string> {\n const metadata = getMetadata();\n const nameFromMetadata = metadata?.[Header.SubredditName]?.values[0];\n if (nameFromMetadata) {\n return nameFromMetadata;\n }\n\n const subredditId = metadata?.[Header.Subreddit]?.values[0];\n const nameFromId = await _getSubredditNameById(asT5ID(subredditId), metadata);\n if (!nameFromId) {\n throw new Error(\"Couldn't get current subreddit's name\");\n }\n return nameFromId;\n}\n\n/**\n * Retrieves the current subreddit.\n *\n * @returns {Promise<Subreddit>} A Promise that resolves a Subreddit object.\n * @example\n * ```ts\n * const currentSubreddit = await getCurrentSubreddit();\n * ```\n */\nexport async function getCurrentSubreddit(): Promise<Subreddit> {\n const metadata = getMetadata();\n const currentSubreddit = await Subreddit.getFromMetadata(metadata);\n if (!currentSubreddit) {\n throw new Error(\"Couldn't get current subreddit\");\n }\n return currentSubreddit;\n}\n\n/**\n * Gets a {@link Post} object by ID\n *\n * @param id\n * @returns A Promise that resolves to a Post object.\n */\nexport function getPostById(id: string): Promise<Post> {\n const metadata = getMetadata();\n return Post.getById(asTID<T3ID>(id), metadata);\n}\n\n/**\n * Submits a new post to a subreddit.\n *\n * @param options - Either a self post or a link post.\n * @returns A Promise that resolves to a Post object.\n * @example\n * ```ts\n * const post = await submitPost({\n * subredditName: 'devvit',\n * title: 'Hello World',\n * richtext: new RichTextBuilder()\n * .heading({ level: 1 }, (h) => {\n * h.rawText('Hello world');\n * })\n * .codeBlock({}, (cb) => cb.rawText('This post was created via the Devvit API'))\n * .build()\n * });\n * ```\n */\nexport function submitPost(options: SubmitPostOptions): Promise<Post> {\n const metadata = getMetadata();\n return Post.submit(options, metadata);\n}\n\n/**\n * Crossposts a post to a subreddit.\n *\n * @param options - Options for crossposting a post\n * @param options.subredditName - The name of the subreddit to crosspost to\n * @param options.postId - The ID of the post to crosspost\n * @param options.title - The title of the crosspost\n * @returns - A Promise that resolves to a Post object.\n */\nexport function crosspost(options: CrosspostOptions): Promise<Post> {\n const metadata = getMetadata();\n return Post.crosspost(options, metadata);\n}\n\n/**\n * Gets a {@link User} object by ID\n *\n * @param id - The ID (starting with t2_) of the user to retrieve. e.g. t2_1qjpg\n * @returns A Promise that resolves to a User object.\n * @example\n * ```ts\n * const user = await getUserById('t2_1qjpg');\n * ```\n */\nexport function getUserById(id: string): Promise<User | undefined> {\n const metadata = getMetadata();\n return User.getById(asTID<T2ID>(id), metadata);\n}\n\n/**\n * Gets a {@link User} object by username\n *\n * @param username - The username of the user omitting the u/. e.g. 'devvit'\n * @returns A Promise that resolves to a User object or undefined if user is\n * not found (user doesn't exist, account suspended, etc).\n * @example\n * ```ts\n * const user = await getUserByUsername('devvit');\n * if (user) {\n * console.log(user)\n * }\n * ```\n */\nexport function getUserByUsername(username: string): Promise<User | undefined> {\n const metadata = getMetadata();\n return User.getByUsername(username, metadata);\n}\n\n/**\n * Get the current calling user's username.\n * Resolves to undefined for logged-out custom post renders.\n *\n * @returns A Promise that resolves to a string representing the username or undefined\n * @example\n * ```ts\n * const username = await getCurrentUsername();\n * ```\n */\nexport async function getCurrentUsername(): Promise<string | undefined> {\n const metadata = getMetadata();\n return getCurrentUsernameFromMetadata(metadata);\n}\n\n/**\n * Get the current calling user.\n * Resolves to undefined for logged-out custom post renders.\n *\n * @returns A Promise that resolves to a User object or undefined\n * @example\n * ```ts\n * const user = await getCurrentUser();\n * ```\n */\nexport async function getCurrentUser(): Promise<User | undefined> {\n const metadata = getMetadata();\n return getCurrentUserFromMetadata(metadata);\n}\n\n/**\n * Get the user that the app runs as on the provided metadata.\n *\n * @returns A Promise that resolves to a User object.\n * @example\n * ```ts\n * const user = await getAppUser(metadata);\n * ```\n */\nexport function getAppUser(): Promise<User> {\n const metadata = getMetadata();\n return User.getFromMetadata(Header.AppUser, metadata) as Promise<User>;\n}\n\n/**\n * Get the snoovatar URL for a given username.\n *\n * @param username - The username of the snoovatar to retrieve\n * @returns A Promise that resolves to a URL of the snoovatar image if it exists.\n */\nexport function getSnoovatarUrl(username: string): Promise<string | undefined> {\n const metadata = getMetadata();\n return User.getSnoovatarUrl(username, metadata);\n}\n\n/**\n * Get a {@link Comment} object by ID\n *\n * @param id - The ID (starting with t1_) of the comment to retrieve. e.g. t1_1qjpg\n * @returns A Promise that resolves to a Comment object.\n * @example\n * ```ts\n * const comment = await getCommentById('t1_1qjpg');\n * ```\n */\nexport function getCommentById(id: string): Promise<Comment> {\n const metadata = getMetadata();\n return Comment.getById(asTID<T1ID>(id), metadata);\n}\n\n/**\n * Get a list of comments from a specific post or comment.\n *\n * @param options - Options for the request\n * @param options.postId - The ID of the post e.g. 't3_1qjpg'\n * @param options.commentId - The ID of the comment e.g. 't1_1qjpg'\n * @param options.limit - The maximum number of comments to return. e.g. 1000\n * @param options.pageSize - The number of comments to return per request. e.g. 100\n * @param options.sort - The sort order of the comments. e.g. 'new'\n * @returns A Listing of Comment objects.\n * @example\n * ```ts\n * const comments = await getComments({\n * postId: 't3_1qjpg',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getComments(options: GetCommentsOptions): Listing<Comment> {\n const metadata = getMetadata();\n return Comment.getComments(options, metadata);\n}\n\n/**\n * Get a list of comments by a specific user.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user omitting the u/. e.g. 'spez'\n * @param options.sort - The sort order of the comments. e.g. 'new'\n * @param options.timeframe - The timeframe of the comments. e.g. 'all'\n * @param options.limit - The maximum number of comments to return. e.g. 1000\n * @param options.pageSize - The number of comments to return per request. e.g. 100\n * @returns A Listing of Comment objects.\n */\nexport function getCommentsByUser(options: GetCommentsByUserOptions): Listing<Comment> {\n const metadata = getMetadata();\n return Comment.getCommentsByUser(options, metadata);\n}\n\n/**\n * Submit a new comment to a post or comment.\n *\n * @param options - You must provide either `options.text` or `options.richtext` but not both.\n * @param options.id - The ID of the post or comment to comment on. e.g. 't3_1qjpg' for post and 't1_1qgif' for comment\n * @param options.text - The text of the comment\n * @param options.richtext - The rich text of the comment\n * @returns A Promise that resolves to a Comment object.\n */\nexport function submitComment(\n options: CommentSubmissionOptions & { id: string }\n): Promise<Comment> {\n const metadata = getMetadata();\n return Comment.submit(\n {\n ...options,\n id: asTID<T3ID | T1ID>(options.id),\n },\n metadata\n );\n}\n\n/**\n * Get a list of controversial posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getControversialPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getControversialPosts(options: GetPostsOptionsWithTimeframe): Listing<Post> {\n const metadata = getMetadata();\n return Post.getControversialPosts(options, metadata);\n}\n\n/**\n * Get a list of controversial posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getControversialPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getTopPosts(options: GetPostsOptionsWithTimeframe): Listing<Post> {\n const metadata = getMetadata();\n return Post.getTopPosts(options, metadata);\n}\n\n/**\n * Get a list of hot posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getHotPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getHotPosts(options: GetHotPostsOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getHotPosts(options, metadata);\n}\n\n/**\n * Get a list of new posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getNewPosts({\n * subredditName: 'memes',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getNewPosts(options: GetPostsOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getNewPosts(options, metadata);\n}\n\n/**\n * Get a list of hot posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getRisingPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getRisingPosts(options: GetPostsOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getRisingPosts(options, metadata);\n}\n\n/**\n * Get a list of posts from a specific user.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user omitting the u/. e.g. 'spez'\n * @param options.sort - The sort method to use. e.g. 'new'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n */\nexport function getPostsByUser(options: GetPostsByUserOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getPostsByUser(options, metadata);\n}\n\n/**\n * Get a list of posts and comments from a specific user.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user omitting the u/. e.g. 'spez'\n * @param options.sort - The sort method to use. e.g. 'new'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of `Post` and `Comment` objects.\n */\nexport function getCommentsAndPostsByUser(\n options: GetUserOverviewOptions\n): Listing<Post | Comment> {\n const metadata = getMetadata();\n return User.getOverview(options, metadata);\n}\n\n/**\n * Get the moderation log for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the moderation log from. e.g. 'memes'\n * @param options.moderatorUsernames (optional) A moderator filter. Accepts an array of usernames\n * @param options.type (optional) Filter the entries by the type of the Moderator action\n * @param options.limit - (optional) The maximum number of ModActions to return. e.g. 1000\n * @param options.pageSize - (optional) The number of ModActions to return per request. e.g. 100\n * @returns A Listing of ModAction objects.\n * @example\n * ```ts\n * const modActions = await getModerationLog({\n * subredditName: 'memes',\n * moderatorUsernames: ['spez'],\n * type: 'banuser',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getModerationLog(options: GetModerationLogOptions): Listing<ModAction> {\n const metadata = getMetadata();\n return _getModerationLog(options, metadata);\n}\n\n/**\n * Get a list of users who have been approved to post in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the approved users from. e.g. 'memes'\n * @param options.username - Use this to see if a user is approved to post in the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getApprovedUsers(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'contributors',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Approve a user to post in a subreddit.\n *\n * @param username - The username of the user to approve. e.g. 'spez'\n * @param subredditName - The name of the subreddit to approve the user in. e.g. 'memes'\n */\nexport function approveUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n username,\n subredditName,\n type: 'contributor',\n },\n metadata\n );\n}\n\n/**\n * Remove a user's approval to post in a subreddit.\n *\n * @param username - The username of the user to remove approval from. e.g. 'spez'\n * @param subredditName - The name of the subreddit to remove the user's approval from. e.g. 'memes'\n */\nexport function removeUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'contributor',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are wiki contributors of a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the wiki contributors from. e.g. 'memes'\n * @param options.username - Use this to see if a user is a wiki contributor for the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getWikiContributors(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'wikicontributors',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Add a user as a wiki contributor for a subreddit.\n *\n * @param username - The username of the user to add as a wiki contributor. e.g. 'spez'\n * @param subredditName - The name of the subreddit to add the user as a wiki contributor. e.g. 'memes'\n */\nexport function addWikiContributor(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n username,\n subredditName,\n type: 'wikicontributor',\n },\n metadata\n );\n}\n\n/**\n * Remove a user's wiki contributor status for a subreddit.\n *\n * @param username - The username of the user to remove wiki contributor status from. e.g. 'spez'\n * @param subredditName - The name of the subreddit to remove the user's wiki contributor status from. e.g. 'memes'\n */\nexport function removeWikiContributor(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'wikicontributor',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are banned from a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the banned users from. e.g. 'memes'\n * @param options.username - Use this to see if a user is banned from the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getBannedUsers(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'banned',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Ban a user from a subreddit.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to ban. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to ban the user from. e.g. 'memes'\n * @param options.note - A mod note for the ban. (optional)\n * @param options.duration - The number of days the user should be banned for. (optional)\n * @param options.message - A message to send to the user when they are banned. (optional)\n * @param options.context - The ID of the post or comment that caused the ban. (optional)\n * @param options.reason - The reason for the ban. (optional)\n */\nexport function banUser(options: BanUserOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n username: options.username,\n subredditName: options.subredditName,\n type: 'banned',\n banReason: options.reason,\n banMessage: options.message,\n note: options.note,\n duration: options.duration,\n banContext: options.context,\n },\n metadata\n );\n}\n\n/**\n * Unban a user from a subreddit.\n *\n * @param username - The username of the user to unban. e.g. 'spez'\n * @param subredditName - The name of the subreddit to unban the user from. e.g. 'memes'\n */\nexport function unbanUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'banned',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are banned from contributing to the wiki on a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the banned wiki contributors from. e.g. 'memes'\n * @param options.username - Use this to see if a user is banned from contributing to the wiki on a subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getBannedWikiContributors(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'wikibanned',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Ban a user from contributing to the wiki on a subreddit.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to ban. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to ban the user from contributing to the wiki on. e.g. 'memes'\n * @param options.reason - The reason for the ban. (optional)\n * @param options.duration - The number of days the user should be banned for. (optional)\n * @param options.note - A mod note for the ban. (optional)\n */\nexport function banWikiContributor(options: BanWikiContributorOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n ...options,\n type: 'wikibanned',\n },\n metadata\n );\n}\n\n/**\n *\n * @param username - The username of the user to unban. e.g. 'spez'\n * @param subredditName - The name of the subreddit to unban the user from contributing to the wiki on. e.g. 'memes'\n */\nexport function unbanWikiContributor(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'wikibanned',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are moderators for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the moderators from. e.g. 'memes'\n * @param options.username - Use this to see if a user is a moderator of the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getModerators(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'moderators',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Invite a user to become a moderator of a subreddit.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to invite. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to invite the user to moderate. e.g. 'memes'\n * @param options.permissions - The permissions to give the user. (optional) Defaults to 'all'.\n */\nexport function inviteModerator(options: InviteModeratorOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n type: 'moderator_invite',\n subredditName: options.subredditName,\n username: options.username,\n permissions: options.permissions ?? [],\n },\n metadata\n );\n}\n\n/**\n * Revoke a moderator invite for a user to a subreddit.\n *\n * @param username - The username of the user to revoke the invite for. e.g. 'spez'\n * @param subredditName - The name of the subreddit to revoke the invite for. e.g. 'memes'\n */\nexport function revokeModeratorInvite(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'moderator_invite',\n },\n metadata\n );\n}\n\n/**\n * Remove a user as a moderator of a subreddit.\n *\n * @param username - The username of the user to remove as a moderator. e.g. 'spez'\n * @param subredditName - The name of the subreddit to remove the user as a moderator from. e.g. 'memes'\n */\nexport function removeModerator(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n type: 'moderator',\n subredditName,\n username,\n },\n metadata\n );\n}\n\n/**\n * Update the permissions of a moderator of a subreddit.\n *\n * @param username - The username of the user to update the permissions for. e.g. 'spez'\n * @param subredditName - The name of the subreddit. e.g. 'memes'\n * @param permissions - The permissions to give the user. e.g ['posts', 'wiki']\n */\nexport function setModeratorPermissions(\n username: string,\n subredditName: string,\n permissions: ModeratorPermission[]\n): Promise<void> {\n const metadata = getMetadata();\n return User.setModeratorPermissions(username, subredditName, permissions, metadata);\n}\n\n/**\n * Get a list of users who are muted in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the muted users from. e.g. 'memes'\n * @param options.username - Use this to see if a user is muted in the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A listing of User objects.\n */\nexport function getMutedUsers(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'muted',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Mute a user in a subreddit. Muting a user prevents them from sending modmail.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to mute. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to mute the user in. e.g. 'memes'\n * @param options.note - A mod note on why the user was muted. (optional)\n */\nexport function muteUser(options: MuteUserOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n ...options,\n type: 'muted',\n },\n metadata\n );\n}\n\n/**\n * Unmute a user in a subreddit. Unmuting a user allows them to send modmail.\n *\n * @param username - The username of the user to unmute. e.g. 'spez'\n * @param subredditName - The name of the subreddit to unmute the user in. e.g. 'memes'\n */\nexport function unmuteUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'muted',\n },\n metadata\n );\n}\n\n/**\n * Get a list of mod notes related to a user in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the mod notes from. e.g. 'memes'\n * @param options.username - The username of the user to get the mod notes for. e.g. 'spez'\n * @param options.filter - Filter the mod notes by type. e.g. 'NOTE', 'BAN', 'APPROVAL'\n * @param options.limit - The maximum number of mod notes to return. e.g. 1000\n * @param options.pageSize - The number of mod notes to return per request. e.g. 100\n * @returns A listing of ModNote objects.\n */\nexport function getModNotes(options: GetModNotesOptions): Listing<ModNote> {\n const metadata = getMetadata();\n return ModNote.get(options, metadata);\n}\n\n/**\n * Delete a mod note.\n *\n * @param options - Options for the request\n * @param options.subreddit - The name of the subreddit to delete the mod note from. e.g. 'memes'\n * @param options.noteId - The ID of the mod note to delete (should have a ModNote_ prefix).\n * @returns True if it was deleted successfully; false otherwise.\n */\nexport function deleteModNote(options: DeleteNotesOptions): Promise<boolean> {\n const metadata = getMetadata();\n return ModNote.delete(options, metadata);\n}\n\n/**\n * Add a mod note.\n *\n * @param options - Options for the request\n * @param options.subreddit - The name of the subreddit to add the mod note to. e.g. 'memes'\n * @param options.user - The username of the user to add the mod note to. e.g. 'spez'\n * @param options.redditId - (optional) The ID of the comment or post to add the mod note to. e.g. 't3_1234'\n * @param options.label - (optional) The label of the mod note. e.g. 'SPAM_WARNING'\n * @param options.note - The text of the mod note.\n * @returns A Promise that resolves if the mod note was successfully added.\n */\nexport function addModNote(options: CreateModNoteOptions): Promise<ModNote> {\n const metadata = getMetadata();\n const req = {\n ...options,\n redditId: options.redditId ? asTID<T1ID | T3ID>(options.redditId) : undefined,\n };\n return ModNote.add(req, metadata);\n}\n\n/**\n * Add a mod note for why a post or comment was removed\n *\n * @param options - The options for adding a removal note.\n * @param options.itemIds list of thing ids\n * @param options.reasonId id of a Removal Reason - you can leave this as an empty string if you don't have one\n * @param options.modNote the reason for removal (maximum 100 characters) (optional)\n */\nexport function addRemovalNote(options: AddRemovalNoteOptions): Promise<void> {\n const metadata = getMetadata();\n return ModNote.addRemovalNote(options, metadata);\n}\n\n/**\n * Sends a private message to a user.\n *\n * @param options - The options for sending the message.\n * @returns A Promise that resolves if the private message was successfully sent.\n */\nexport async function sendPrivateMessage(options: SendPrivateMessageOptions): Promise<void> {\n const metadata = getMetadata();\n return PrivateMessage.send(options, metadata);\n}\n\n/**\n * Sends a private message to a user on behalf of a subreddit.\n *\n * @param options - The options for sending the message as a subreddit.\n * @returns A Promise that resolves if the private message was successfully sent.\n */\nexport async function sendPrivateMessageAsSubreddit(\n options: SendPrivateMessageAsSubredditOptions\n): Promise<void> {\n const metadata = getMetadata();\n return PrivateMessage.sendAsSubreddit(options, metadata);\n}\n\n/**\n * Approve a post or comment.\n *\n * @param id - The id of the post (t3_) or comment (t1_) to approve.\n * @example\n * ```ts\n * await approve('t3_123456');\n * await approve('t1_123456');\n * ```\n */\nexport async function approve(id: string): Promise<void> {\n const metadata = getMetadata();\n if (isT1ID(id)) {\n return Comment.approve(id, metadata);\n } else if (isT3ID(id)) {\n return Post.approve(id, metadata);\n }\n\n throw new Error('id must start with either t1_ or t3_');\n}\n\n/**\n * Remove a post or comment.\n *\n * @param id - The id of the post (t3_) or comment (t1_) to remove.\n * @param isSpam - Is the post or comment being removed because it's spam?\n * @example\n * ```ts\n * await remove('t3_123456', false);\n * await remove('t1_123456', true);\n * ```\n */\nexport async function remove(id: string, isSpam: boolean): Promise<void> {\n const metadata = getMetadata();\n if (isT1ID(id)) {\n return Comment.remove(id, isSpam, metadata);\n } else if (isT3ID(id)) {\n return Post.remove(id, isSpam, metadata);\n }\n\n throw new Error('id must start with either t1_ or t3_');\n}\n\n/**\n * Get the list of post flair templates for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the post flair templates for.\n * @returns A Promise that resolves with an array of FlairTemplate objects.\n */\nexport async function getPostFlairTemplates(subredditName: string): Promise<FlairTemplate[]> {\n const metadata = getMetadata();\n return FlairTemplate.getPostFlairTemplates(subredditName, metadata);\n}\n\n/**\n * Get the list of user flair templates for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the user flair templates for.\n * @returns A Promise that resolves with an array of FlairTemplate objects.\n */\nexport async function getUserFlairTemplates(subredditName: string): Promise<FlairTemplate[]> {\n const metadata = getMetadata();\n return FlairTemplate.getUserFlairTemplates(subredditName, metadata);\n}\n\n/**\n * Create a post flair template for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to create the flair template for.\n * @param options.allowableContent - The content that is allowed to be used with this flair template. e.g. 'all' or 'text' or 'emoji'\n * @param options.backgroundColor - The background color of the flair template. e.g. '#ff0000' or 'transparent'\n * @param options.maxEmojis - The maximum number of emojis that can be used with this flair template.\n * @param options.modOnly - Whether or not this flair template is only available to mods.\n * @param options.text - The text of the flair template.\n * @param options.textColor - The text color of the flair template. Either 'dark' or 'light'.\n * @param options.allowUserEdits - Whether or not users can edit the flair template when selecting a flair.\n * @returns The created FlairTemplate object.\n */\nexport async function createPostFlairTemplate(\n options: CreateFlairTemplateOptions\n): Promise<FlairTemplate> {\n const metadata = getMetadata();\n return FlairTemplate.createPostFlairTemplate(options, metadata);\n}\n\n/**\n * Create a user flair template for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to create the flair template for.\n * @param options.allowableContent - The content that is allowed to be used with this flair template. e.g. 'all' or 'text' or 'emoji'\n * @param options.backgroundColor - The background color of the flair template. e.g. '#ff0000' or 'transparent'\n * @param options.maxEmojis - The maximum number of emojis that can be used with this flair template.\n * @param options.modOnly - Whether or not this flair template is only available to mods.\n * @param options.text - The text of the flair template.\n * @param options.textColor - The text color of the flair template. Either 'dark' or 'light'.\n * @param options.allowUserEdits - Whether or not users can edit the flair template when selecting a flair.\n * @returns The created FlairTemplate object.\n */\nexport async function createUserFlairTemplate(\n options: CreateFlairTemplateOptions\n): Promise<FlairTemplate> {\n const metadata = getMetadata();\n return FlairTemplate.createUserFlairTemplate(options, metadata);\n}\n\n/**\n * Edit a flair template for a subreddit. This can be either a post or user flair template.\n * Note: If you leave any of the options fields as undefined, they will reset to their default values.\n *\n * @param options - Options for the request\n * @param options.id - The ID of the flair template to edit.\n * @param options.subredditName - The name of the subreddit to create the flair template for.\n * @param options.allowableContent - The content that is allowed to be used with this flair template. e.g. 'all' or 'text' or 'emoji'\n * @param options.backgroundColor - The background color of the flair template. e.g. '#ff0000' or 'transparent'\n * @param options.maxEmojis - The maximum number of emojis that can be used with this flair template.\n * @param options.modOnly - Is this flair template only available to mods?\n * @param options.text - The text of the flair template.\n * @param options.textColor - The text color of the flair template. Either 'dark' or 'light'.\n * @param options.allowUserEdits - Can users can edit the flair template when selecting a flair?\n * @returns The edited FlairTemplate object.\n */\nexport async function editFlairTemplate(options: EditFlairTemplateOptions): Promise<FlairTemplate> {\n const metadata = getMetadata();\n return FlairTemplate.editFlairTemplate(options, metadata);\n}\n\n/**\n * Delete a flair template from a subreddit.\n *\n * @param subredditName - The name of the subreddit to delete the flair template from.\n * @param flairTemplateId - The ID of the flair template to delete.\n */\nexport async function deleteFlairTemplate(\n subredditName: string,\n flairTemplateId: string\n): Promise<void> {\n const metadata = getMetadata();\n return FlairTemplate.deleteFlairTemplate(subredditName, flairTemplateId, metadata);\n}\n\n/**\n * Set the flair for a user in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to set the flair for.\n * @param options.username - The username of the user to set the flair for.\n * @param options.flairTemplateId - The ID of the flair template to use.\n * @param options.text - The text of the flair.\n * @param options.cssClass - The CSS class of the flair.\n * @param options.backgroundColor - The background color of the flair.\n * @param options.textColor - The text color of the flair.\n */\nexport async function setUserFlair(options: SetUserFlairOptions): Promise<void> {\n const metadata = getMetadata();\n return Flair.setUserFlair(options, metadata);\n}\n\n/**\n * Set the flair of multiple users in the same subreddit with a single API call.\n * Can process up to 100 entries at once.\n *\n * @param subredditName - The name of the subreddit to edit flairs in.\n * @param {SetUserFlairBatchConfig[]} flairs - Array of user flair configuration objects. If both text and cssClass are empty for a given user the flair will be cleared.\n * @param flairs[].username - The username of the user to edit the flair for.\n * @param flairs[].text - The text of the flair.\n * @param flairs[].cssClass - The CSS class of the flair.\n * @returns {FlairCsvResult[]} - Array of statuses for each entry provided.\n */\nexport async function setUserFlairBatch(\n subredditName: string,\n flairs: SetUserFlairBatchConfig[]\n): Promise<FlairCsvResult[]> {\n const metadata = getMetadata();\n return Flair.setUserFlairBatch(subredditName, flairs, metadata);\n}\n\n/**\n * Remove the flair for a user in a subreddit.\n *\n * @param subredditName - The name of the subreddit to remove the flair from.\n * @param username - The username of the user to remove the flair from.\n */\nexport async function removeUserFlair(subredditName: string, username: string): Promise<void> {\n const metadata = getMetadata();\n return Flair.removeUserFlair(subredditName, username, metadata);\n}\n\n/**\n * Set the flair for a post in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to set the flair for.\n * @param options.postId - The ID of the post to set the flair for.\n * @param options.flairTemplateId - The ID of the flair template to use.\n * @param options.text - The text of the flair.\n * @param options.cssClass - The CSS class of the flair.\n * @param options.backgroundColor - The background color of the flair.\n * @param options.textColor - The text color of the flair.\n */\nexport async function setPostFlair(options: SetPostFlairOptions): Promise<void> {\n const metadata = getMetadata();\n return Flair.setPostFlair(options, metadata);\n}\n\n/**\n * Remove the flair for a post in a subreddit.\n *\n * @param subredditName - The name of the subreddit to remove the flair from.\n * @param postId - The ID of the post to remove the flair from.\n */\nexport async function removePostFlair(subredditName: string, postId: string): Promise<void> {\n const metadata = getMetadata();\n return Flair.removePostFlair(subredditName, asT3ID(postId), metadata);\n}\n\n/**\n * Get the widgets for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the widgets for.\n * @returns - An array of Widget objects.\n */\nexport async function getWidgets(subredditName: string): Promise<Widget[]> {\n const metadata = getMetadata();\n return Widget.getWidgets(subredditName, metadata);\n}\n\n/**\n * Delete a widget from a subreddit.\n *\n * @param subredditName - The name of the subreddit to delete the widget from.\n * @param widgetId - The ID of the widget to delete.\n */\nexport async function deleteWidget(subredditName: string, widgetId: string): Promise<void> {\n const metadata = getMetadata();\n return Widget.delete(subredditName, widgetId, metadata);\n}\n\n/**\n * Add a widget to a subreddit.\n *\n * @param widgetData - The data for the widget to add.\n * @returns - The added Widget object.\n */\nexport async function addWidget(widgetData: AddWidgetData): Promise<Widget> {\n const metadata = getMetadata();\n return Widget.add(widgetData, metadata);\n}\n\n/**\n * Reorder the widgets for a subreddit.\n *\n * @param subredditName - The name of the subreddit to reorder the widgets for.\n * @param orderByIds - An array of widget IDs in the order that they should be displayed.\n */\nexport async function reorderWidgets(subredditName: string, orderByIds: string[]): Promise<void> {\n const metadata = getMetadata();\n return Widget.reorder(subredditName, orderByIds, metadata);\n}\n\n/**\n * Get a wiki page from a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the wiki page from.\n * @param page - The name of the wiki page to get.\n * @returns The requested WikiPage object.\n */\nexport async function getWikiPage(subredditName: string, page: string): Promise<WikiPage> {\n const metadata = getMetadata();\n return WikiPage.getPage(subredditName, page, metadata);\n}\n\n/**\n * Get the wiki pages for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the wiki pages from.\n * @returns A list of the wiki page names for the subreddit.\n */\nexport async function getWikiPages(subredditName: string): Promise<string[]> {\n const metadata = getMetadata();\n return WikiPage.getPages(subredditName, metadata);\n}\n\n/**\n * Create a new wiki page for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to create.\n * @param options.content - The Markdown content of the wiki page.\n * @param options.reason - The reason for creating the wiki page.\n * @returns - The created WikiPage object.\n */\nexport async function createWikiPage(options: CreateWikiPageOptions): Promise<WikiPage> {\n const metadata = getMetadata();\n return WikiPage.createPage(options, metadata);\n}\n\n/**\n * Update a wiki page.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to update.\n * @param options.content - The Markdown content of the wiki page.\n * @param options.reason - The reason for updating the wiki page.\n * @returns The updated WikiPage object.\n */\nexport async function updateWikiPage(options: UpdateWikiPageOptions): Promise<WikiPage> {\n const metadata = getMetadata();\n return WikiPage.updatePage(options, metadata);\n}\n\n/**\n * Get the revisions for a wiki page.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to get the revisions for.\n * @param options.limit - The maximum number of revisions to return.\n * @param options.after - The ID of the revision to start after.\n * @returns A Listing of WikiPageRevision objects.\n */\nexport function getWikiPageRevisions(options: GetPageRevisionsOptions): Listing<WikiPageRevision> {\n const metadata = getMetadata();\n return WikiPage.getPageRevisions(options, metadata);\n}\n\n/**\n * Revert a wiki page to a previous revision.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to revert.\n * @param revisionId - The ID of the revision to revert to.\n */\nexport async function revertWikiPage(\n subredditName: string,\n page: string,\n revisionId: string\n): Promise<void> {\n const metadata = getMetadata();\n return WikiPage.revertPage(subredditName, page, revisionId, metadata);\n}\n\n/**\n * Get the settings for a wiki page.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to get the settings for.\n * @returns A WikiPageSettings object.\n */\nexport async function getWikiPageSettings(\n subredditName: string,\n page: string\n): Promise<WikiPageSettings> {\n const metadata = getMetadata();\n return WikiPage.getPageSettings(subredditName, page, metadata);\n}\n\n/**\n * Update the settings for a wiki page.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to update the settings for.\n * @param options.listed - Whether the wiki page should be listed in the wiki index.\n * @param options.permLevel - The permission level required to edit the wiki page.\n * @returns A WikiPageSettings object.\n */\nexport async function updateWikiPageSettings(\n options: UpdatePageSettingsOptions\n): Promise<WikiPageSettings> {\n const metadata = getMetadata();\n return WikiPage.updatePageSettings(options, metadata);\n}\n\n/**\n * Add an editor to a wiki page.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to add the editor to.\n * @param username - The username of the user to add as an editor.\n */\nexport async function addEditorToWikiPage(\n subredditName: string,\n page: string,\n username: string\n): Promise<void> {\n const metadata = getMetadata();\n return WikiPage.addEditor(subredditName, page, username, metadata);\n}\n\n/**\n * Remove an editor from a wiki page.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to remove the editor from.\n * @param username - The username of the user to remove as an editor.\n */\nexport async function removeEditorFromWikiPage(\n subredditName: string,\n page: string,\n username: string\n): Promise<void> {\n const metadata = getMetadata();\n return WikiPage.removeEditor(subredditName, page, username, metadata);\n}\n\n/**\n * Get private messages sent to the currently authenticated user.\n *\n * @param options - Options for the request\n * @param options.type - The type of messages to get.\n */\nexport function getMessages(options: GetPrivateMessagesOptions): Promise<Listing<PrivateMessage>> {\n const metadata = getMetadata();\n return PrivateMessage.getMessages(options, metadata);\n}\n\n/**\n * Mark all private messages as read.\n *\n */\nexport function markAllMessagesAsRead(): Promise<void> {\n const metadata = getMetadata();\n return PrivateMessage.markAllAsRead(metadata);\n}\n\n/**\n * Report a Post or Comment\n *\n * The report is sent to the moderators of the subreddit for review.\n *\n * @param thing Post or Comment\n * @param options Options\n * @param options.reason Why the thing is reported\n *\n * @example\n * ```ts\n * await report(post, {\n * reason: 'This is spam!',\n * })\n * ```\n */\nexport function report(thing: Post | Comment, options: { reason: string }): Promise<JsonStatus> {\n const metadata = getMetadata();\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n return client.Report(\n {\n reason: options.reason,\n thingId: thing.id,\n srName: thing.subredditName,\n usernames: thing.authorName,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things requiring moderator review, such as reported things and items.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getModQueue();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getModQueue({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getModQueue(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getModQueue(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getModQueue(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getModQueue(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Modqueue,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have been reported.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getReports();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getReports({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getReports(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getReports(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getReports(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getReports(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Reports,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have been marked as spam or otherwise removed.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getSpam();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getSpam({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getSpam(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getSpam(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getSpam(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getSpam(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Spam,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have yet to be approved/removed by a mod.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getUnmoderated();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getUnmoderated({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getUnmoderated(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getUnmoderated(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getUnmoderated(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getUnmoderated(\n options: ModLogOptions<AboutSubredditTypes>\n): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Unmoderated,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have been edited recently.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getEdited();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getEdited({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getEdited(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getEdited(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getEdited(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getEdited(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Edited,\n },\n metadata\n );\n}\n\n/**\n * Gets a {@link Vault} for the specified address.\n *\n * @param {string} address - The address (starting with 0x) of the Vault.\n * @example\n * ```ts\n * const vault = await getVaultByAddress('0x205ee28744456bDBf180A0Fa7De51e0F116d54Ed');\n * ```\n */\nexport function getVaultByAddress(address: string): Promise<Vault> {\n const metadata = getMetadata();\n return _getVaultByAddress(address, metadata);\n}\n\n/**\n * Gets a {@link Vault} for the specified user.\n *\n * @param {string} userId - The ID (starting with t2_) of the Vault owner.\n * @example\n * ```ts\n * const vault = await getVaultByUserId('t2_1w72');\n * ```\n */\nexport function getVaultByUserId(userId: string): Promise<Vault> {\n const metadata = getMetadata();\n return _getVaultByUserId(asTID<T2ID>(userId), metadata);\n}\n\n/**\n * Returns a leaderboard for a given subreddit ID.\n *\n * @param subredditId ID of the subreddit for which the leaderboard is being queried.\n *\n * @returns {SubredditLeaderboard} Leaderboard for the given subreddit.\n */\nexport function getSubredditLeaderboard(subredditId: string): Promise<SubredditLeaderboard> {\n const metadata = getMetadata();\n return _getSubredditLeaderboard(subredditId, metadata);\n}\n\n/**\n * Returns the styles for a given subreddit ID.\n *\n * @param subredditId ID of the subreddit from which to retrieve the styles.\n *\n * @returns {SubredditStyles} Styles for the given subreddit.\n */\nexport function getSubredditStyles(subredditId: string): Promise<SubredditStyles> {\n const metadata = getMetadata();\n return _getSubredditStyles(subredditId, metadata);\n}\n\n/**\n * Subscribes to the subreddit in which the app is installed. No-op if the user is already subscribed.\n * This method will execute as the app account by default.\n * To subscribe on behalf of a user, please contact Reddit.\n */\nexport async function subscribeToCurrentSubreddit(): Promise<void> {\n const metadata = getMetadata();\n const currentSubreddit = await getCurrentSubreddit();\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n await client.Subscribe(\n {\n action: 'sub',\n actionSource: '',\n srName: currentSubreddit.name,\n sr: '',\n skipInitialDefaults: true,\n },\n metadata\n );\n}\n\n/**\n * Unsubscribes from the subreddit in which the app is installed. No-op if the user isn't subscribed.\n * This method will execute as the app account by default.\n * To unsubscribe on behalf of a user, please contact Reddit.\n */\nexport async function unsubscribeFromCurrentSubreddit(): Promise<void> {\n const metadata = getMetadata();\n const currentSubreddit = await getCurrentSubreddit();\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n await client.Subscribe(\n {\n action: 'unsub',\n actionSource: '',\n srName: currentSubreddit.name,\n sr: '',\n skipInitialDefaults: false,\n },\n metadata\n );\n}\n", "/**\n * Metadata header key. Every system header should start with \"devvit-\".\n *\n * Synchronize to headers.md.\n */\nexport const Header = Object.freeze({\n Actor: 'devvit-actor',\n App: 'devvit-app',\n AppUser: 'devvit-app-user',\n AppViewerAuthToken: 'devvit-app-viewer-authorization',\n Caller: 'devvit-caller',\n CallerPortID: 'devvit-caller-port-id',\n Canary: 'devvit-canary',\n Debug: 'devvit-debug',\n GQLHost: 'devvit-gql-host',\n Installation: 'devvit-installation',\n ModPermissions: 'devvit-mod-permissions',\n Post: 'devvit-post',\n PostAuthor: 'devvit-post-author',\n R2Auth: 'devvit-sec-authorization',\n R2Host: 'devvit-r2-host',\n RemoteHostname: 'devvit-remote-hostname',\n SettingsUri: 'devvit-sec-settings-uri',\n StreamID: 'devvit-stream-id',\n Subreddit: 'devvit-subreddit',\n SubredditName: 'devvit-subreddit-name',\n TraceID: 'devvit-trace-id',\n User: 'devvit-user',\n Username: 'devvit-user-name',\n UserAgent: 'devvit-user-agent',\n Version: 'devvit-version',\n Language: 'devvit-accept-language',\n Timezone: 'devvit-accept-timezone',\n Traceparent: 'traceparent',\n AppDependencies: 'devvit-app-dependencies',\n});\n/** See DevvitGlobal and ContextDebugInfo. */\nexport var AppDebug;\n(function (AppDebug) {\n /** Enable debug logging for blocks. */\n AppDebug[\"Blocks\"] = \"blocks\";\n /**\n * Log the entire reified blocks JSX/XML tree on each render. Eg:\n *\n * <hstack><text>hi world</text></hstack>\n */\n AppDebug[\"EmitSnapshots\"] = \"emitSnapshots\";\n /** Log app state changes. */\n AppDebug[\"EmitState\"] = \"emitState\";\n /** Enable debug logging for realtime and useChannel() hook. */\n AppDebug[\"Realtime\"] = \"realtime\";\n /** Enable runtime and dispatcher logging. */\n AppDebug[\"Runtime\"] = \"runtime\";\n /** Enable debug logging for devvit-surface and dispatcher. */\n AppDebug[\"Surface\"] = \"surface\";\n /** Enable debug logging for the useAsync() hook family. */\n AppDebug[\"UseAsync\"] = \"useAsync\";\n /** Enable debug logging for payments APIs */\n AppDebug[\"Payments\"] = \"payments\";\n /** Enable bootstrap logging */\n AppDebug[\"Bootstrap\"] = \"bootstrap\";\n /** WebView debug logs */\n AppDebug[\"WebView\"] = \"webView\";\n})(AppDebug || (AppDebug = {}));\n", "export function assert(condition, msg) {\n if (!condition)\n throw Error(msg);\n}\n", "import { assert } from './assert.js';\n// CALMS!\nexport var T_PREFIX;\n(function (T_PREFIX) {\n T_PREFIX[\"COMMENT\"] = \"t1_\";\n T_PREFIX[\"ACCOUNT\"] = \"t2_\";\n T_PREFIX[\"LINK\"] = \"t3_\";\n T_PREFIX[\"MESSAGE\"] = \"t4_\";\n T_PREFIX[\"SUBREDDIT\"] = \"t5_\";\n T_PREFIX[\"AWARD\"] = \"t6_\";\n})(T_PREFIX || (T_PREFIX = {}));\n// type guards\nexport function isT1ID(id) {\n return id.startsWith(T_PREFIX.COMMENT);\n}\nexport function isT2ID(id) {\n return id.startsWith(T_PREFIX.ACCOUNT);\n}\nexport function isT3ID(id) {\n return id.startsWith(T_PREFIX.LINK);\n}\nexport function isT4ID(id) {\n return id.startsWith(T_PREFIX.MESSAGE);\n}\nexport function isT5ID(id) {\n return id.startsWith(T_PREFIX.SUBREDDIT);\n}\nexport function isT6ID(id) {\n return id.startsWith(T_PREFIX.AWARD);\n}\n// assertion functions\nexport function assertT1ID(id) {\n assert(isT1ID(id), `Expected comment id to start with ${T_PREFIX.COMMENT}, got ${id}}`);\n}\nexport function assertT2ID(id) {\n assert(isT2ID(id), `Expected account id to start with ${T_PREFIX.ACCOUNT}, got ${id}}`);\n}\nexport function assertT3ID(id) {\n assert(isT3ID(id), `Expected link id to start with ${T_PREFIX.LINK}, got ${id}}`);\n}\nexport function assertT4ID(id) {\n assert(isT4ID(id), `Expected message id to start with ${T_PREFIX.MESSAGE}, got ${id}}`);\n}\nexport function assertT5ID(id) {\n assert(isT5ID(id), `Expected subreddit id to start with ${T_PREFIX.SUBREDDIT}, got ${id}}`);\n}\nexport function assertT6ID(id) {\n assert(isT6ID(id), `Expected award id to start with ${T_PREFIX.AWARD}, got ${id}}`);\n}\n// factory functions\nexport function asT1ID(id) {\n assertT1ID(id);\n return id;\n}\nexport function asT2ID(id) {\n assertT2ID(id);\n return id;\n}\nexport function asT3ID(id) {\n assertT3ID(id);\n return id;\n}\nexport function asT4ID(id) {\n assertT4ID(id);\n return id;\n}\nexport function asT5ID(id) {\n assertT5ID(id);\n return id;\n}\nexport function asT6ID(id) {\n assertT6ID(id);\n return id;\n}\nexport function asTID(id) {\n if (isT1ID(id)) {\n return asT1ID(id);\n }\n if (isT2ID(id)) {\n return asT2ID(id);\n }\n if (isT3ID(id)) {\n return asT3ID(id);\n }\n if (isT4ID(id)) {\n return asT4ID(id);\n }\n if (isT5ID(id)) {\n return asT5ID(id);\n }\n if (isT6ID(id)) {\n return asT6ID(id);\n }\n throw new Error(`Expected thing id to start with ${Object.values(T_PREFIX).join(', ')} got ${id}}`);\n}\n// convenience functions\nexport function isCommentId(id) {\n return isT1ID(id);\n}\nexport function isAccountId(id) {\n return isT2ID(id);\n}\nexport function isLinkId(id) {\n return isT3ID(id);\n}\nexport function isMessageId(id) {\n return isT4ID(id);\n}\nexport function isSubredditId(id) {\n return isT5ID(id);\n}\nexport function isAwardId(id) {\n return isT6ID(id);\n}\n", "import type { UnknownMessage } from '@devvit/protos';\nimport * as protos from '@devvit/protos';\nimport type { PaymentsService } from '@devvit/protos/payments.js';\nimport { Actor } from '@devvit/shared-types/Actor.js';\nimport type { AssetMap } from '@devvit/shared-types/Assets.js';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport type { JSONObject, JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport { assertValidFormFields } from '../apis/ui/helpers/assertValidFormFields.js';\nimport type {\n BaseContext,\n Configuration,\n ContextAPIClients,\n CustomPostType,\n Form,\n FormDefinition,\n FormFunction,\n FormOnSubmitEventHandler,\n FormToFormValues,\n IconName,\n MenuItem,\n MultiTriggerDefinition,\n OnTriggerRequest,\n ScheduledJobHandler,\n ScheduledJobType,\n SettingsFormField,\n TriggerContext,\n TriggerDefinition,\n TriggerEvent,\n TriggerEventType,\n TriggerOnEventHandler,\n} from '../types/index.js';\nimport { SettingScope } from '../types/index.js';\nimport { registerAppSettings } from './internals/app-settings.js';\nimport { registerCustomPost } from './internals/custom-post.js';\nimport { registerInstallationSettings } from './internals/installation-settings.js';\nimport { registerMenuItems } from './internals/menu-items.js';\nimport { pluginIsEnabled } from './internals/plugins.js';\nimport { registerScheduler } from './internals/scheduler.js';\nimport { registerTriggers } from './internals/triggers.js';\nimport { registerUIEventHandler } from './internals/ui-event-handler.js';\nimport { registerUIRequestHandlers } from './internals/ui-request-handler.js';\n\ntype UseHandler = {\n [name: string]: (args: UnknownMessage | undefined, metadata?: protos.Metadata) => void;\n};\n\ntype PluginType =\n | protos.HTTP\n | protos.Logger\n | protos.Scheduler\n | protos.ContextAction\n | protos.KVStore\n | protos.SchedulerHandler\n | protos.Flair\n | protos.GraphQL\n | protos.LinksAndComments\n | protos.Listings\n | protos.Moderation\n | protos.ModNote\n | protos.Modlog\n | protos.NewModmail\n | protos.PrivateMessages\n | protos.RedisAPI\n | protos.Settings\n | protos.Subreddits\n | protos.Users\n | protos.Widgets\n | protos.Wiki\n | protos.MediaService\n | protos.Realtime\n | protos.UserActions\n | PaymentsService;\n\n/**\n * Home for debug flags, settings, and other information. Any type removals\n * may cause type errors but not runtime errors.\n *\n * **Favor ContextDebugInfo since request-based state is preferred.**\n */\nexport type DevvitDebug = {\n /**\n * Should debug block rendering in console.log according to the reified JSX/XML output. Example:\n *\n * <hstack><text>hi world</text></hstack>\n *\n */\n emitSnapshots?: boolean | undefined;\n\n /**\n * Should console.log the state of the app after every event.\n *\n */\n emitState?: boolean | undefined;\n};\n\nexport class Devvit extends Actor {\n static debug: DevvitDebug = {};\n\n static #appSettings: SettingsFormField[] | undefined;\n static #assets: AssetMap = {};\n static #config: Configuration = {};\n static #customPostType: CustomPostType | undefined;\n static readonly #formDefinitions: Map<FormKey, FormDefinition> = new Map();\n static #installationSettings: SettingsFormField[] | undefined;\n static readonly #menuItems: MenuItem[] = [];\n static readonly #scheduledJobHandlers: Map<string, ScheduledJobHandler> = new Map();\n static readonly #triggerOnEventHandlers: Map<\n TriggerEvent,\n TriggerOnEventHandler<OnTriggerRequest>[]\n > = new Map();\n static #webViewAssets: AssetMap = {};\n\n static #additionallyProvides: protos.Definition[] = [];\n\n /**\n * To use certain APIs and features of Devvit, you must enable them using this function.\n *\n * @param config - The configuration object.\n * @param config.http - Enables the HTTP API.\n * @param config.redditAPI - Enables the Reddit API.\n * @param config.kvStore - Enables the Key Value Storage API.\n * @example\n * ```ts\n * Devvit.configure({\n * http: true,\n * redditAPI: true,\n * redis: true,\n * media: true\n * });\n * ```\n */\n static configure(config: Configuration): void {\n this.#config = { ...this.#config, ...config };\n\n if (pluginIsEnabled(config.http)) {\n this.use(protos.HTTPDefinition);\n }\n\n // We're now defaulting this to on.\n const redisNotSpecified = config.redis === undefined;\n if (redisNotSpecified || pluginIsEnabled(config.kvStore) || pluginIsEnabled(config.redis)) {\n this.use(protos.KVStoreDefinition);\n this.use(protos.RedisAPIDefinition);\n }\n\n if (pluginIsEnabled(config.media)) {\n this.use(protos.MediaServiceDefinition);\n }\n\n if (pluginIsEnabled(config.modLog)) {\n this.use(protos.ModlogDefinition);\n }\n\n if (pluginIsEnabled(config.redditAPI)) {\n // Loading all Reddit API plugins for now.\n // In the future we can split this by oauth scope or section.\n this.use(protos.FlairDefinition);\n this.use(protos.GraphQLDefinition);\n this.use(protos.LinksAndCommentsDefinition);\n this.use(protos.ListingsDefinition);\n this.use(protos.ModerationDefinition);\n this.use(protos.ModNoteDefinition);\n this.use(protos.NewModmailDefinition);\n this.use(protos.PrivateMessagesDefinition);\n this.use(protos.SubredditsDefinition);\n this.use(protos.UsersDefinition);\n this.use(protos.WidgetsDefinition);\n this.use(protos.WikiDefinition);\n }\n\n if (pluginIsEnabled(config.realtime)) {\n this.use(protos.RealtimeDefinition);\n }\n\n if (pluginIsEnabled(config.userActions)) {\n this.use(protos.UserActionsDefinition);\n }\n }\n\n /**\n * Add a menu item to the Reddit UI.\n * @param menuItem - The menu item to add.\n * @param menuItem.label - The label of the menu item.\n * @example\n * ```ts\n * Devvit.addMenuItem({\n * label: 'My Menu Item',\n * location: 'subreddit',\n * onPress: (event, context) => {\n * const location = event.location;\n * const targetId = event.targetId;\n * context.ui.showToast(`You clicked on ${location} ${targetId}`);\n * }\n * });\n * ```\n */\n static addMenuItem(menuItem: MenuItem): void {\n this.#menuItems.push(menuItem);\n }\n\n /**\n * Add a custom post type for your app.\n * @param customPostType - The custom post type to add.\n * @param customPostType.name - The name of the custom post type.\n * @param customPostType.description - An optional description.\n * @param customPostType.height - An optional parameter to set post height, defaults to 'regular'.\n * @param customPostType.render - A function or `Devvit.CustomPostComponent` that returns the UI for the custom post.\n * @example\n * ```ts\n * import { Devvit, useState } from '@devvit/public-api';\n *\n * Devvit.addCustomPostType({\n * name: 'Counter',\n * description: 'A simple click counter post.',\n * render: (context) => {\n * const [counter, setCounter] = useState();\n *\n * return (\n * <vstack>\n * <text>{counter}</text>\n * <button onPress={() => setCounter((counter) => counter + 1)}>Click me!</button>\n * </vstack>\n * );\n * },\n * });\n * ```\n */\n static addCustomPostType(customPostType: CustomPostType): void {\n this.#customPostType = customPostType;\n }\n\n /**\n * Create a form that can be opened from menu items and custom posts.\n * @param form - The form or a function that returns the form.\n * @param onSubmit - The function to call when the form is submitted.\n * @returns A unique key for the form that can used with `ui.showForm`.\n */\n static createForm<const T extends Form | FormFunction>(\n form: T,\n onSubmit: FormOnSubmitEventHandler<FormToFormValues<T>>\n ): FormKey {\n const formKey: FormKey = `form.${this.#formDefinitions.size}`;\n this.#formDefinitions.set(formKey, {\n form,\n onSubmit,\n } as FormDefinition);\n return formKey;\n }\n\n /**\n * Add a scheduled job type for your app. This will allow you to schedule jobs using the `scheduler` API.\n * @param job - The scheduled job type to add.\n * @param job.name - The name of the scheduled job type.\n * @param job.onRun - The function to call when the scheduled job is run.\n * @example\n * ```ts\n * Devvit.addSchedulerJob({\n * name: 'checkNewPosts',\n * onRun: async (event, context) => {\n * const newPosts = await context.reddit.getNewPosts({ limit: 5 }).all();\n * for (const post of newPosts) {\n * if (post.title.includes('bad word')) {\n * await post.remove();\n * }\n * }\n * }\n * });\n *\n * Devvit.addMenuItem({\n * label: 'Check for new posts',\n * location: 'location',\n * onPress: (event, context) => {\n * const = await context.scheduler.runJob({\n * name: 'checkNewPosts',\n * when: new Date(Date.now() + 5000) // in 5 seconds\n * });\n * }\n * });\n * ```\n */\n static addSchedulerJob<T extends JSONObject | undefined>(job: ScheduledJobType<T>): void {\n if (!this.#pluginClients[protos.SchedulerDefinition.fullName]) {\n this.use(protos.SchedulerDefinition);\n }\n\n if (this.#scheduledJobHandlers.has(job.name)) {\n throw new Error(`Job ${job.name} is already defined`);\n }\n\n this.#scheduledJobHandlers.set(\n job.name,\n job.onRun as ScheduledJobHandler<JSONObject | undefined>\n );\n }\n\n /**\n * Add settings that can be configured to customize the behavior of your app. There are two levels of settings: App settings (scope: 'app') and\n * install settings (scope: 'installation' or unspecified scope). Install settings are meant to be configured by the user that installs your app.\n * This is a good place to add anything that a user might want to change to personalize the app (e.g. the default city to show the weather for or a\n * specific sport team that a subreddit follows). Note that these are good for subreddit level customization but not necessarily good for things\n * that might be different for two users in a subreddit (e.g. setting the default city to show the weather for is only useful at a sub level if\n * the sub is for a specific city or region). Install settings can be viewed and configured here: https://developers.reddit.com/r/subreddit-name/apps/app-name.\n * App settings can be accessed and consumed by all installations of the app. This is mainly useful for developer secrets/API keys that your\n * app needs to function. They can only be changed/viewed by you via the CLI (devvit settings set and devvit settings list). This ensures secrets\n * are persisted in an encrypted store and don't get committed in the source code. You should never paste your actual key into any fields passed into\n * Devvit.addSettings - this is merely where you state what your API key's name and description are. You will be able to set the actual value of the key via CLI.\n * Note: setting names must be unique across all settings.\n * @param fields - Fields for the app and installation settings.\n * @example\n * ```ts\n * Devvit.addSettings([\n * {\n * type: 'string',\n * name: 'weather-api-key',\n * label: 'My weather.com API key',\n * scope: SettingScope.App,\n * isSecret: true\n * },\n * {\n * type: 'string',\n * name: 'Default City',\n * label: 'Default city to show the weather for by default',\n * scope: SettingScope.Installation,\n * onValidate: ({ value }) => {\n * if (!isValidCity(value)) {\n * return 'You must ender a valid city: ${validCities.join(\", \")}';\n * }\n * }\n * },\n * {\n * type: 'number',\n * name: 'Default Forecast Window (in days)',\n * label: 'The number of days to show for forecast for by default',\n * scope: SettingScope.Installation,\n * onValidate: ({ value }) => {\n * if (value > 10 || value < 1) {\n * return 'Forecast window must be from 1 to 10 days';\n * }\n * }\n * },\n * ]);\n * ```\n */\n static addSettings(fields: SettingsFormField[]): void {\n assertValidFormFields(fields);\n const installSettings = fields.filter(\n (field) => field.type === 'group' || !field.scope || field.scope === SettingScope.Installation\n );\n const appSettings = fields.filter(\n (field) => field.type !== 'group' && field.scope === SettingScope.App\n );\n\n if (installSettings.length > 0) {\n this.#installationSettings = installSettings;\n }\n\n if (appSettings.length > 0) {\n this.#appSettings = appSettings;\n }\n\n if (!this.#pluginClients[protos.SettingsDefinition.fullName]) {\n this.use(protos.SettingsDefinition);\n }\n }\n\n /**\n * Add a trigger handler that will be invoked when the given event\n * occurs in a subreddit where the app is installed.\n *\n * @param triggerDefinition - The trigger definition.\n * @param triggerDefinition.event - The event to listen for.\n * @param triggerDefinition.events - The events to listen for.\n * @param triggerDefinition.onEvent - The function to call when the event happens.\n * @example\n * ```ts\n * Devvit.addTrigger({\n * event: 'PostSubmit',\n * async onEvent(event, context) {\n * console.log(\"a new post was created!\")\n * }\n * });\n *\n * Devvit.addTrigger({\n * events: ['PostSubmit', 'PostReport'],\n * async onEvent(event, context){\n * if (event.type === 'PostSubmit') {\n * console.log(\"a new post was created!\")\n * } else if (event.type === 'PostReport') {\n * console.log(\"a post was reported!\")\n * }\n * }\n * });\n * ```\n */\n static addTrigger<T extends keyof TriggerEventType>(definition: {\n event: T;\n onEvent: TriggerOnEventHandler<TriggerEventType[T]>;\n }): typeof Devvit;\n static addTrigger<Event extends TriggerEvent>(\n triggerDefinition: MultiTriggerDefinition<Event>\n ): typeof Devvit;\n static addTrigger(\n triggerDefinition: TriggerDefinition | MultiTriggerDefinition<TriggerEvent>\n ): typeof Devvit {\n if ('events' in triggerDefinition) {\n for (const eventType of triggerDefinition.events) {\n this.addTrigger({\n event: eventType,\n onEvent: (event: OnTriggerRequest, context: TriggerContext) =>\n (triggerDefinition.onEvent as TriggerOnEventHandler<OnTriggerRequest>)(event, context),\n } as any); // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n return this;\n }\n\n if (this.#triggerOnEventHandlers.has(triggerDefinition.event)) {\n this.#triggerOnEventHandlers\n .get(triggerDefinition.event)\n ?.push(triggerDefinition.onEvent as TriggerOnEventHandler<OnTriggerRequest>);\n } else {\n this.#triggerOnEventHandlers.set(triggerDefinition.event, [\n triggerDefinition.onEvent as TriggerOnEventHandler<OnTriggerRequest>,\n ]);\n }\n\n return Devvit;\n }\n\n /**\n * @internal\n * utility static method to register additional actor types without exposing an explicit\n * registration hook such as `addTrigger` or `addMenuItem`\n */\n static provide(def: protos.Definition): void {\n this.#additionallyProvides.push(def);\n }\n\n /** @internal */\n static #uses: {\n [fullName: protos.Definition['fullName']]: {\n def: protos.Definition;\n options: DeepPartial<protos.PackageQuery>;\n handler: Readonly<UseHandler> | undefined;\n };\n } = {};\n\n /** @internal */\n static #pluginClients: {\n [fullName: protos.Definition['fullName']]: PluginType;\n } = {};\n\n /** @internal */\n static use<T>(d: protos.Definition, opts?: DeepPartial<protos.PackageQuery>): T {\n this.#uses[d.fullName] = {\n def: d,\n options: opts ?? {},\n handler: undefined,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const wrapped: any = {};\n for (const method of Object.values(d.methods)) {\n wrapped[method.name] = (args: UnknownMessage | undefined, metadata?: protos.Metadata) =>\n this.#uses[d.fullName].handler?.[method.name]?.(\n // eslint-disable-next-line no-restricted-properties\n method.requestType?.fromPartial(args ?? {}),\n metadata\n );\n }\n\n this.#pluginClients[d.fullName] = wrapped;\n\n return wrapped as T;\n }\n\n /** @internal */\n static get redditAPIPlugins(): {\n NewModmail: protos.NewModmail;\n Widgets: protos.Widgets;\n ModNote: protos.ModNote;\n LinksAndComments: protos.LinksAndComments;\n Moderation: protos.Moderation;\n GraphQL: protos.GraphQL;\n Listings: protos.Listings;\n Flair: protos.Flair;\n Wiki: protos.Wiki;\n Users: protos.Users;\n PrivateMessages: protos.PrivateMessages;\n Subreddits: protos.Subreddits;\n } {\n if (!pluginIsEnabled(this.#config.redditAPI)) {\n throw new Error(\n 'Reddit API is not enabled. You can enable it by passing `redditAPI: true` to `Devvit.configure`.'\n );\n }\n\n return {\n Flair: this.#pluginClients[protos.FlairDefinition.fullName] as protos.Flair,\n GraphQL: this.#pluginClients[protos.GraphQLDefinition.fullName] as protos.GraphQL,\n LinksAndComments: this.#pluginClients[\n protos.LinksAndCommentsDefinition.fullName\n ] as protos.LinksAndComments,\n Listings: this.#pluginClients[protos.ListingsDefinition.fullName] as protos.Listings,\n Moderation: this.#pluginClients[protos.ModerationDefinition.fullName] as protos.Moderation,\n ModNote: this.#pluginClients[protos.ModNoteDefinition.fullName] as protos.ModNote,\n NewModmail: this.#pluginClients[protos.NewModmailDefinition.fullName] as protos.NewModmail,\n PrivateMessages: this.#pluginClients[\n protos.PrivateMessagesDefinition.fullName\n ] as protos.PrivateMessages,\n Subreddits: this.#pluginClients[protos.SubredditsDefinition.fullName] as protos.Subreddits,\n Users: this.#pluginClients[protos.UsersDefinition.fullName] as protos.Users,\n Widgets: this.#pluginClients[protos.WidgetsDefinition.fullName] as protos.Widgets,\n Wiki: this.#pluginClients[protos.WikiDefinition.fullName] as protos.Wiki,\n };\n }\n\n /** @internal */\n static get modLogPlugin(): protos.Modlog {\n const modLog = this.#pluginClients[protos.ModlogDefinition.fullName];\n\n if (!modLog) {\n throw new Error(\n 'ModLog is not enabled. You can enable it by passing `modLog: true` to `Devvit.configure`'\n );\n }\n\n return modLog as protos.Modlog;\n }\n\n /** @internal */\n static get schedulerPlugin(): protos.Scheduler {\n const scheduler = this.#pluginClients[protos.SchedulerDefinition.fullName];\n\n if (!scheduler) {\n // todo: better error with more details\n throw new Error(\n 'Scheduler is not enabled. You can enable it by calling `Devvit.addSchedulerJob` at the top level of your app.'\n );\n }\n\n return scheduler as protos.Scheduler;\n }\n\n /** @internal */\n static get kvStorePlugin(): protos.KVStore {\n const kvStore = this.#pluginClients[protos.KVStoreDefinition.fullName];\n\n if (!kvStore) {\n throw new Error(\n 'Key Value Store is not enabled. You can enable it by passing `kvStore: true` to `Devvit.configure`'\n );\n }\n\n return kvStore as protos.KVStore;\n }\n\n /** @internal */\n static get redisPlugin(): protos.RedisAPI {\n const redis = this.#pluginClients[protos.RedisAPIDefinition.fullName];\n\n if (!redis) {\n throw new Error(\n 'Redis is not enabled. You can enable it by passing `redis: true` to `Devvit.configure`'\n );\n }\n\n return redis as protos.RedisAPI;\n }\n\n /** @internal */\n static get mediaPlugin(): protos.MediaService {\n const media = this.#pluginClients[protos.MediaServiceDefinition.fullName];\n if (!media) {\n throw new Error(\n 'MediaService is not enabled. You can enable it by passing `media: true` to `Devvit.configure`'\n );\n }\n return media as protos.MediaService;\n }\n\n /** @internal */\n static get settingsPlugin(): protos.Settings {\n const settings = this.#pluginClients[protos.SettingsDefinition.fullName];\n\n if (!settings) {\n throw new Error(\n 'Settings must first be configured with `Devvit.addSettings()` before they can be accessed'\n );\n }\n\n return settings as protos.Settings;\n }\n\n /** @internal */\n static get realtimePlugin(): protos.Realtime {\n const realtime = this.#pluginClients[protos.RealtimeDefinition.fullName];\n\n if (!realtime) {\n throw new Error(\n 'Realtime is not enabled. You can enable it by passing `realtime: true` to `Devvit.configure`'\n );\n }\n\n return realtime as protos.Realtime;\n }\n\n /** @internal */\n static get userActionsPlugin(): protos.UserActions {\n const userActionsAndRedditApiEnabled =\n pluginIsEnabled(this.#config.userActions) && pluginIsEnabled(this.#config.redditAPI);\n\n if (!userActionsAndRedditApiEnabled) {\n throw new Error(\n 'UserActions is not enabled. You can enable it by passing both `userActions: true` and `redditAPI: true` to `Devvit.configure`'\n );\n }\n\n return this.#pluginClients[protos.UserActionsDefinition.fullName] as protos.UserActions;\n }\n\n /** @internal */\n static get menuItems(): MenuItem[] {\n return this.#menuItems;\n }\n\n /** @internal */\n static get customPostType(): CustomPostType | undefined {\n return this.#customPostType;\n }\n\n /** @internal */\n static get formDefinitions(): Map<FormKey, FormDefinition> {\n return this.#formDefinitions;\n }\n\n /** @internal */\n static get scheduledJobHandlers(): Map<string, ScheduledJobHandler> {\n return this.#scheduledJobHandlers;\n }\n\n /** @internal */\n static get installationSettings(): SettingsFormField[] | undefined {\n return this.#installationSettings;\n }\n\n /** @internal */\n static get appSettings(): SettingsFormField[] | undefined {\n return this.#appSettings;\n }\n\n /** @internal */\n static get triggerOnEventHandlers(): Map<\n TriggerEvent,\n TriggerOnEventHandler<OnTriggerRequest>[]\n > {\n return this.#triggerOnEventHandlers;\n }\n\n /** @internal */\n static get assets(): AssetMap {\n return this.#assets;\n }\n\n /** @internal */\n static get webViewAssets(): AssetMap {\n return this.#webViewAssets;\n }\n\n /** @internal */\n constructor(config: Config) {\n super(config);\n\n Devvit.#assets = config.assets ?? {};\n Devvit.#webViewAssets = config.webviewAssets ?? {};\n\n for (const fullName in Devvit.#uses) {\n const use = Devvit.#uses[fullName];\n use.handler = config.use<UseHandler>(use.def, use.options);\n }\n\n if (Devvit.#menuItems.length > 0) {\n registerMenuItems(config);\n }\n\n if (Devvit.#scheduledJobHandlers.size > 0) {\n registerScheduler(config);\n }\n\n if (Devvit.#customPostType) {\n registerCustomPost(config);\n /**\n * We're trying to migrate custom posts to generic ui handlers, but they'll\n * both work for now.\n */\n registerUIRequestHandlers(config);\n }\n\n if (Devvit.#customPostType || Devvit.#formDefinitions.size > 0) {\n registerUIEventHandler(config);\n }\n\n if (Devvit.#installationSettings) {\n registerInstallationSettings(config);\n }\n\n if (Devvit.#appSettings) {\n registerAppSettings(config);\n }\n\n if (Devvit.#triggerOnEventHandlers.size > 0) {\n registerTriggers(config);\n }\n\n for (const provides of Devvit.#additionallyProvides) {\n config.provides(provides);\n }\n }\n}\n\nexport namespace Devvit {\n export type Fragment = JSX.Fragment;\n export type ElementChildren = JSX.Element | JSX.Children | undefined;\n export type StringChild = Fragment | string | number;\n export type StringChildren = StringChild | (StringChild | StringChild[])[] | undefined;\n type ComponentFunctionValue = BlockElement | Fragment | undefined;\n\n // Generic createElement to handle Blocks, custom elements, etc...\n export function createElement(\n type: Blocks.IntrinsicElementsType,\n props: { [key: string]: unknown } | undefined,\n ...children: JSX.Children[]\n ): BlockElement;\n export function createElement(\n type: JSX.ComponentFunction | string | undefined,\n props: { [key: string]: unknown } | undefined,\n ...children: JSX.Children[]\n ): ComponentFunctionValue | Promise<ComponentFunctionValue> {\n const blockElement: BlockElement = {\n type,\n props,\n children,\n };\n\n return blockElement;\n }\n\n // Workaround for typing: aliasing global Context as Devvit.Context bundles\n // incorrectly as `type Context = Context`.\n /** The current app context of the event or render. */\n export type Context = ContextAPIClients & BaseContext;\n\n export type BlockComponentProps<P = { [key: string]: unknown }> = P & { children?: JSX.Children };\n export type BlockComponent<P = { [key: string]: unknown }> = (\n props: BlockComponentProps<P>,\n context: Context\n ) => JSX.Element;\n export type CustomPostComponent = (context: Context) => JSX.Element;\n\n export namespace Blocks {\n export interface IntrinsicElements {\n blocks: Devvit.Blocks.RootProps;\n hstack: Devvit.Blocks.StackProps;\n vstack: Devvit.Blocks.StackProps;\n zstack: Devvit.Blocks.StackProps;\n text: Devvit.Blocks.TextProps;\n button: Devvit.Blocks.ButtonProps;\n image: Devvit.Blocks.ImageProps;\n spacer: Devvit.Blocks.SpacerProps;\n icon: Devvit.Blocks.IconProps;\n avatar: Devvit.Blocks.AvatarProps;\n webview: Devvit.Blocks.WebViewProps;\n }\n\n export type IntrinsicElementsType = keyof IntrinsicElements;\n\n //region Attribute Values\n export type SizePixels = `${number}px`;\n export type SizePercent = `${number}%`;\n export type SizeString = SizePixels | SizePercent | number;\n export type Alignment =\n | `${VerticalAlignment}`\n | `${HorizontalAlignment}`\n | `${VerticalAlignment} ${HorizontalAlignment}`\n | `${HorizontalAlignment} ${VerticalAlignment}`;\n export type AvatarBackground = 'light' | 'dark';\n export type AvatarFacing = 'left' | 'right';\n export type AvatarSize =\n | 'xxsmall'\n | 'xsmall'\n | 'small'\n | 'medium'\n | 'large'\n | 'xlarge'\n | 'xxlarge'\n | 'xxxlarge';\n export type ButtonAppearance =\n | 'secondary'\n | 'primary'\n | 'plain'\n | 'bordered'\n | 'media'\n | 'destructive'\n | 'caution'\n | 'success';\n /**\n * Affects the button height.\n * small = 32px;\n * medium = 40px;\n * large = 48px;\n */\n export type ButtonSize = 'small' | 'medium' | 'large';\n export type ColorString = string;\n /**\n * thin = 1px;\n * thick = 2px;\n */\n export type ContainerBorderWidth = Thickness;\n /**\n * small = 8px;\n * medium = 16px;\n * large = 24px;\n */\n export type ContainerCornerRadius = 'none' | 'small' | 'medium' | 'large' | 'full';\n /**\n * small = 8px;\n * medium = 16px;\n * large = 32px;\n */\n export type ContainerGap = 'none' | 'small' | 'medium' | 'large';\n /**\n * xsmall = 4px;\n * small = 8px;\n * medium = 16px;\n * large = 32px;\n */\n export type ContainerPadding = 'none' | 'xsmall' | 'small' | 'medium' | 'large';\n export type HorizontalAlignment = 'start' | 'center' | 'end';\n /**\n * xsmall = 12px;\n * small = 16px;\n * medium = 20px;\n * large = 24px;\n */\n export type IconSize = 'xsmall' | 'small' | 'medium' | 'large';\n export type ImageResizeMode = 'none' | 'fit' | 'fill' | 'cover' | 'scale-down';\n /**\n * xsmall = 4px;\n * small = 8px;\n * medium = 16px;\n * large = 32px;\n */\n export type SpacerSize = 'xsmall' | 'small' | 'medium' | 'large';\n export type SpacerShape = 'invisible' | 'thin' | 'square';\n /**\n * thin = 1px;\n * thick = 2px;\n */\n export type TextOutline = Thickness;\n /**\n * xsmall = 10px;\n * small = 12px;\n * medium = 14px;\n * large = 16px;\n * xlarge = 18px;\n * xxlarge = 24px;\n */\n export type TextSize = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';\n export type TextStyle = 'body' | 'metadata' | 'heading';\n export type TextWeight = 'regular' | 'bold';\n export type TextOverflow = 'clip' | 'ellipsis';\n export type Thickness = 'none' | 'thin' | 'thick';\n export type VerticalAlignment = 'top' | 'middle' | 'bottom';\n export type RootHeight = 'regular' | 'tall';\n //endregion\n\n //region Element Attributes\n export type BaseProps = {\n width?: SizeString;\n height?: SizeString;\n minWidth?: SizeString;\n minHeight?: SizeString;\n maxWidth?: SizeString;\n maxHeight?: SizeString;\n grow?: boolean;\n\n /**\n * This optional field provides some efficiencies around re-ordering elements in a list. Rather\n * Than re-rendering the entire list, the client can use the key to determine if the element has\n * changed. In the example below, if a and b were swapped, the client would know to reuse the\n * existing elements from b, rather than re-creating an expensive tree of elements.\n *\n * Unlike id, key is local to the parent element. This means that the same key can be used in different\n * parts of the tree without conflict.\n *\n * <hstack>\n * <text key=\"a\">hi world</text>\n * <hstack key=\"b\">...deeply nested content...</hstack>\n * </hstack>\n */\n key?: string;\n\n /**\n * This optional field provides a unique identifier for the element. This is useful for ensuring\n * re-use of elements across renders. See the `key` field for more information. Unlike key, id\n * is global. You cannot have two elements with the same id in the same tree.\n */\n id?: string;\n };\n\n export type OnPressEventHandler = (data: JSONObject) => void | Promise<void>;\n\n export type OnWebViewEventHandler = <T extends JSONValue>(message: T) => void | Promise<void>;\n\n export type Actionable = {\n onPress?: OnPressEventHandler | undefined;\n };\n\n export type WebViewActionable = {\n onMessage?: OnWebViewEventHandler | undefined;\n };\n\n export type ActionHandlers = keyof (Actionable & WebViewActionable);\n\n export type HasElementChildren = {\n children?: Devvit.ElementChildren;\n };\n\n export type HasStringChildren = {\n children?: Devvit.StringChildren;\n };\n\n export type RootProps = HasElementChildren & {\n height?: Devvit.Blocks.RootHeight | undefined;\n };\n\n export type StackProps = BaseProps &\n HasElementChildren &\n Actionable & {\n reverse?: boolean | undefined;\n alignment?: Alignment;\n padding?: ContainerPadding | undefined;\n gap?: ContainerGap | undefined;\n border?: ContainerBorderWidth | undefined;\n borderColor?: ColorString | undefined;\n lightBorderColor?: ColorString | undefined;\n darkBorderColor?: ColorString | undefined;\n cornerRadius?: ContainerCornerRadius | undefined;\n backgroundColor?: ColorString | undefined;\n lightBackgroundColor?: ColorString | undefined;\n darkBackgroundColor?: ColorString | undefined;\n };\n\n export type TextProps = BaseProps &\n HasStringChildren &\n Actionable & {\n size?: TextSize | undefined;\n weight?: TextWeight | undefined;\n color?: ColorString | undefined;\n lightColor?: ColorString | undefined;\n darkColor?: ColorString | undefined;\n alignment?: Alignment | undefined;\n outline?: TextOutline | undefined;\n style?: TextStyle | undefined;\n selectable?: boolean | undefined;\n wrap?: boolean | undefined;\n overflow?: TextOverflow | undefined;\n };\n\n export type ButtonProps = BaseProps &\n HasStringChildren &\n Actionable & {\n icon?: IconName | undefined;\n size?: ButtonSize | undefined;\n appearance?: ButtonAppearance | undefined;\n textColor?: ColorString | undefined;\n lightTextColor?: ColorString | undefined;\n darkTextColor?: ColorString | undefined;\n // not available in all platforms yet\n // backgroundColor?: ColorString | undefined;\n disabled?: boolean | undefined;\n };\n\n export type ImageProps = BaseProps &\n Actionable & {\n url: string;\n imageWidth: SizePixels | number;\n imageHeight: SizePixels | number;\n description?: string | undefined;\n resizeMode?: ImageResizeMode | undefined;\n };\n\n export type SpacerProps = BaseProps & {\n size?: SpacerSize | undefined;\n shape?: SpacerShape | undefined;\n };\n\n export type IconProps = BaseProps &\n HasStringChildren &\n Actionable & {\n name: IconName;\n color?: ColorString | undefined;\n lightColor?: ColorString | undefined;\n darkColor?: ColorString | undefined;\n size?: IconSize | undefined;\n };\n\n export type AvatarProps = BaseProps &\n Actionable & {\n thingId: string;\n facing?: AvatarFacing | undefined;\n size?: AvatarSize | undefined;\n background?: AvatarBackground | undefined;\n };\n\n export type WebViewProps = BaseProps &\n WebViewActionable & {\n url: string;\n };\n }\n}\n\nexport type BlockElement = {\n type: JSX.ComponentFunction | string | undefined;\n\n props: { [key: string]: unknown } | undefined;\n children: JSX.Element[];\n};\n\n// Workaround api-extractor https://github.com/microsoft/rushstack/issues/1709\n// type augmentations bug. Everything starting at declare is concatenated onto\n// public-api.d.ts.\ndeclare global {\n namespace JSX {\n interface IntrinsicElements extends Devvit.Blocks.IntrinsicElements {}\n\n type Fragment = Iterable<JSX.Element>;\n type SyncElement = BlockElement | JSX.Fragment | string | number | boolean | null;\n type Element = SyncElement | Promise<SyncElement>;\n type ElementChildrenAttribute = { children: {} };\n type Children = JSX.Element | JSX.Element[];\n type Props<T extends {} = {}> = T & { children?: Devvit.ElementChildren };\n\n type ComponentFunction = (props: JSX.Props, context: Devvit.Context) => JSX.Element;\n }\n}\n", "/**\n * All Bundle programs are Actor subclasses. Actor instances are expected to\n * populate the passed in Config which is used by the builder to compile and\n * link the programs. Bundle programs are expected to export their subclass\n * constructor, either their own Actor subclass or Devvit.\n *\n * Devvit is an Actor subclass singleton with a non-class-based API.\n *\n * `instanceof` and `isPrototypeOf()` tests fail because Devvit and Actor\n * are multiply defined.\n *\n * Developers will, for now, implement this class and\n * utilize the the `cfg.use/provide` api for both:\n *\n * 1. specifying their `DependencySpec` (proto: devvit/runtime/bundle)\n * Build packs will, for now, create an instance of the Actor and use the\n * result of `config.export` for linking\n *\n * 2. When running within the overall system, `Bootstrap` actor will\n * inject a config that is used to setup links within the runtime.\n */\n/**\n * Subclasses are expected to call config.init(), provides(), and uses(). It\n * is erroneous to not override the constructor. Override, invoke\n * `super(config)`, and call the Config APIs.\n */\nexport class Actor {\n constructor(\n // @ts-expect-error ignore unused variable without a _ prefix.\n config) { }\n}\n", "import type {\n FieldConfig_Boolean,\n FieldConfig_Number,\n FieldConfig_Paragraph,\n FieldConfig_Selection,\n FieldConfig_Selection_Item,\n FieldConfig_String,\n} from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { Devvit } from '../devvit/Devvit.js';\nimport type { FormToFormValues } from './index.js';\n\nexport type { FormKey };\n\nexport type FormValues = JSONObject;\n\nexport type FormOnSubmitEvent<T extends Partial<JSONObject>> = {\n /** The form values that were submitted */\n values: T;\n};\n\nexport type FormOnSubmitEventHandler<Data extends Partial<JSONObject>> = (\n /** The event object containing the results of the form submission */\n event: FormOnSubmitEvent<Data>,\n /** The current app context of the form submission event */\n context: Devvit.Context\n) => void | Promise<void>;\n\nexport type Form = {\n /** The fields that will be displayed in the form */\n fields: readonly FormField[];\n /** An optional title for the form */\n title?: string;\n /** An optional description for the form */\n description?: string;\n /** An optional label for the submit button */\n acceptLabel?: string;\n /** An optional label for the cancel button */\n cancelLabel?: string;\n};\n\n/**\n * A function that returns a form. You can use this to dynamically generate a form.\n * @example\n * ```ts\n * const formKey = Devvit.createForm((data) => ({\n * fields: data.fields,\n * title: data.title,\n * }), callback);\n *\n * ...\n *\n * ui.showForm(formKey, {\n * fields: [{ type: 'string', name: 'title', label: 'Title' }]\n * title: 'My dynamic form'\n * });\n * ```\n * */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FormFunction<T extends { [key: string]: any } = { [key: string]: any }> = (\n data: T\n) => Form;\n\nexport type FormDefinition<T extends Form | FormFunction = Form | FormFunction> = {\n /** A form or a function that returns a form */\n form: T;\n /** A callback that will be invoked when the form is submitted */\n onSubmit: FormOnSubmitEventHandler<FormToFormValues<T>>;\n};\n\nexport type BaseField<ValueType> = {\n /**\n * The name of the field. This will be used as the key in the `values` object\n * when the form is submitted.\n */\n name: string;\n /** The label of the field. This will be displayed to the user */\n label: string;\n /** An optional help text that will be displayed below the field */\n helpText?: string | undefined;\n /**\n * If true the field will be required and the user will not be able to submit\n * the form without filling it in.\n */\n required?: boolean | undefined;\n /** If true the field will be disabled */\n disabled?: boolean | undefined;\n /** The default value of the field */\n defaultValue?: ValueType | undefined;\n /**\n * This indicates whether the field (setting) is an app level or install level\n * setting. App setting values can be used by any installation.\n */\n scope?: SettingScopeType | undefined;\n};\n\nexport type SettingScopeType = 'installation' | 'app';\n\nexport enum SettingScope {\n Installation = 'installation',\n App = 'app',\n}\n\n/** A text field */\nexport type StringField = Prettify<\n BaseField<string> &\n Omit<FieldConfig_String, 'minLength' | 'maxLength'> & {\n type: 'string';\n isSecret?: boolean;\n }\n>;\n\n// TODO remove @experimental once we've implemented the image field in the UI on all platforms\n/**\n * Allows a user to upload an image as part of submitting the form. The string value that's\n * given back is the URL of the image.\n * @experimental\n */\nexport type ImageField = Omit<BaseField<string>, 'defaultValue'> & {\n type: 'image';\n};\n\n/** A paragraph or textarea field */\nexport type ParagraphField = Prettify<\n BaseField<string> &\n Omit<FieldConfig_Paragraph, 'maxCharacters'> & {\n type: 'paragraph';\n }\n>;\n\n/** A number field */\nexport type NumberField = Prettify<\n BaseField<number> &\n // TODO: allow \"step\" when <faceplate-text-input> start supporting it\n Omit<FieldConfig_Number, 'min' | 'max' | 'step'> & {\n type: 'number';\n }\n>;\n\n/** A boolean field displayed as a toggle */\nexport type BooleanField = Prettify<\n // Note: 'required' doesn't make sense for a boolean field\n Omit<BaseField<boolean>, 'required'> &\n FieldConfig_Boolean & {\n type: 'boolean';\n }\n>;\n\n/** A dropdown field that allows users to pick from a list of options */\nexport type SelectField = Prettify<\n BaseField<string[]> &\n Omit<FieldConfig_Selection, 'choices' | 'renderAsList' | 'minSelections' | 'maxSelections'> & {\n type: 'select';\n options: FieldConfig_Selection_Item[];\n }\n>;\n\n/** A grouping of fields */\nexport type FormFieldGroup = {\n type: 'group';\n /** The label of the group that will be displayed to the user */\n label: string;\n /** The fields that will be displayed in the group */\n fields: readonly FormField[];\n /** An optional help text that will be displayed below the group */\n helpText?: string | undefined;\n required?: never;\n};\n\nexport type FormField =\n | StringField\n | ImageField\n | ParagraphField\n | NumberField\n | BooleanField\n | SelectField\n | FormFieldGroup;\n", "import type { FormField } from '../../../types/form.js';\nimport { SettingScope } from '../../../types/form.js';\n\n/**\n * Make sure that the form fields have unique names.\n */\nexport function assertValidFormFields(\n fields: readonly FormField[],\n seenNames: Set<string> = new Set()\n): void {\n for (const field of fields) {\n if (field.type === 'group') {\n assertValidFormFields(field.fields, seenNames);\n continue;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldName = (field as any).name as string;\n\n if (seenNames.has(fieldName)) {\n throw new Error(`Duplicate field name: ${fieldName}`);\n }\n\n seenNames.add(fieldName);\n }\n assertAppSecretsOnly(fields);\n}\n\nexport function assertAppSecretsOnly(fields: readonly FormField[]): void {\n for (const field of fields) {\n if (field.type === 'string' && field.isSecret && field.scope !== SettingScope.App) {\n throw `Invalid setting: only app settings can be secrets. Add \"scope: SettingScope.App\" to field \"${field.name}\"`;\n }\n }\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { Context } from './context.js';\nimport type {\n BooleanField,\n Form,\n FormField,\n FormFieldGroup,\n FormFunction,\n ImageField,\n NumberField,\n ParagraphField,\n SelectField,\n StringField,\n} from './form.js';\nimport type { ChannelOptions, ChannelStatus } from './realtime.js';\n\nexport type Dispatch<A> = (value: A) => void;\nexport type SetStateAction<S> = S | ((prevState: S) => S);\nexport type StateSetter<S> = Dispatch<SetStateAction<S>>;\n\n/** A tuple containing the current state and a function to update it */\nexport type UseStateResult<S> = [S, StateSetter<S>];\nexport type UseStateHook = Context['useState'];\nexport type AsyncUseStateInitializer<S> = () => Promise<S>;\nexport type UseStateInitializer<S> = S | (() => S) | AsyncUseStateInitializer<S>;\nexport type AsyncError = { message: string; details: string | null };\nexport type UseAsyncResult<S> = { data: S | null; loading: boolean; error: AsyncError | null };\n\n/** @internal */\nexport const Hook = Object.freeze({\n INTERVAL: 0, // useInterval hook\n FORM: 1, // useForm hook\n STATE: 2, // useState hook\n CHANNEL: 3, // useChannel hook\n WEB_VIEW: 4, // useWebView hook\n});\n\n/** @internal */\nexport type Hook = (typeof Hook)[keyof typeof Hook];\n\n/** @internal */\nexport type UseFormHookState = {\n formKey: FormKey;\n preventSubmit: boolean;\n type: Hook;\n};\n\n/** A hook that returns a form key that can be used in the `ui.showForm` */\nexport type UseFormHook<T extends Form | FormFunction = Form | FormFunction> = (\n form: T,\n onSubmit: (values: FormToFormValues<T>) => void | Promise<void>\n) => FormKey;\n\nexport type FormToFormValues<T extends Form | FormFunction = Form | FormFunction> =\n FormFieldsToFormValues<(T extends FormFunction ? ReturnType<T> : T)['fields']>;\n\n/**\n * Input is a FormField[], output is a\n * {fieldNameA: fieldTypeA, fieldNameB: fieldTypeB}.\n */\ntype FormFieldsToFormValues<T extends readonly FormField[]> = T extends readonly [\n infer Field extends FormField,\n ...infer Rest extends FormField[],\n]\n ? FormFieldToFormValue<Field> & FormFieldsToFormValues<Rest>\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n { [key: string]: any }; // possibly empty but more likely couldn't infer.\n\n/** Input is a FormField, output is a {fieldName: fieldType}. */\ntype FormFieldToFormValue<T extends FormField> = T extends BooleanField\n ? { [_ in T['name']]: boolean }\n : T extends ImageField | ParagraphField | StringField\n ? FormFieldToRequiredFormValue<T, string>\n : T extends NumberField\n ? FormFieldToRequiredFormValue<T, number>\n : T extends SelectField\n ? { [_ in T['name']]: string[] }\n : T extends FormFieldGroup\n ? FormFieldsToFormValues<T['fields']>\n : never;\n\n/**\n * Input is a FormField, output is a {fieldName: fieldType} or\n * {fieldName?: fieldType}.\n */\ntype FormFieldToRequiredFormValue<\n T extends ImageField | ParagraphField | StringField | NumberField,\n V,\n> = T extends { required: true } | { defaultValue: boolean | number | string }\n ? { [_ in T['name']]: V }\n : { [_ in T['name']]?: V };\n\n/** An object that contains functions to start and stop the interval created by the `useInterval` hook */\nexport type UseIntervalResult = {\n /** Start the interval */\n start: () => void;\n /** Stop the interval */\n stop: () => void;\n};\n\n/** A hook that can used to run a callback on an interval between Block renders. Only one useInterval hook may be running at a time. */\nexport type UseIntervalHook = (\n /** The callback to run on an interval */\n callback: () => void | Promise<void>,\n /** The delay between each callback run in milliseconds. Delay must be at least 100ms. */\n delay: number\n) => UseIntervalResult;\n\n/** @internal */\nexport type UseIntervalHookState = {\n lastRun: number | undefined;\n running: boolean;\n preventCallback: boolean;\n type: Hook;\n};\n\nexport type UseChannelHook<Message extends JSONValue = JSONValue> = (\n options: ChannelOptions<Message>\n) => UseChannelResult<Message>;\n\n/** @internal */\nexport type UseChannelHookState = {\n channel: string;\n active: boolean;\n connected: boolean;\n preventCallback: boolean;\n type: Hook;\n};\n\nexport type UseChannelResult<Message extends JSONValue = JSONValue> = {\n /** Subscribe to the channel */\n subscribe(): void;\n /** Unsubscribe from the channel */\n unsubscribe(): void;\n /** Publish a message to the channel */\n send(msg: Message): Promise<void>;\n /** Current subscription status */\n status: ChannelStatus;\n};\n\n/**\n * @template From Message from web view to Devvit Blocks app.\n * @template To Message from Devvit Blocks app to web view.\n */\nexport type UseWebViewOnMessage<\n From extends JSONValue = JSONValue,\n To extends JSONValue = JSONValue,\n> = (message: From, hook: UseWebViewResult<To>) => void | Promise<void>;\n\n/**\n * @template From Message from web view to Devvit Blocks app.\n * @template To Message from Devvit Blocks app to web view.\n */\nexport type UseWebViewOptions<\n From extends JSONValue = JSONValue,\n To extends JSONValue = JSONValue,\n> = {\n /** Relative HTML asset filename like `foo/bar.html`. Defaults to index.html if omitted. */\n url?: string;\n /** Handle UI events originating from the web view to be handled by a Devvit app */\n onMessage: UseWebViewOnMessage<From, To>;\n /**\n * The callback to run when the web view has been unmounted. Might be used to\n * set state, stop or resume timers, or perform other tasks now that the web view is no longer visible.\n * @deprecated use the page visibility API for now.\n */\n onUnmount?: (hook: UseWebViewResult<To>) => void | Promise<void>;\n};\n\n/** @template To Message from Devvit Blocks app to web view. */\nexport type UseWebViewResult<To extends JSONValue = JSONValue> = {\n /** Send a message to the web view */\n postMessage(message: To): void;\n /** Initiate a request for the web view to open */\n mount(): void;\n /** Initiate a request for the web view to be closed */\n unmount(): void;\n};\n", "export const ALL_ICON_NAMES = [\"3rd-party\",\"activity\",\"add-emoji\",\"add\",\"add-media\",\"add-to-feed\",\"admin\",\"ads\",\"ai\",\"align-center\",\"align-left\",\"align-right\",\"all\",\"ama\",\"appearance\",\"approve\",\"archived\",\"aspect-ratio\",\"aspect-rectangle\",\"attach\",\"audience\",\"audio\",\"author\",\"automod\",\"avatar-style\",\"award\",\"back\",\"backup\",\"ban\",\"best\",\"block\",\"blockchain\",\"bold\",\"boost\",\"bot\",\"bounce\",\"brand-awareness\",\"browse\",\"browser\",\"cake\",\"calendar\",\"camera\",\"campaign\",\"caret-down\",\"caret-left\",\"caret-right\",\"caret-up\",\"chat\",\"chat-group\",\"chat-new\",\"chat-private\",\"checkbox-dismiss\",\"checkbox\",\"checkmark\",\"chrome\",\"clear\",\"client-list\",\"close\",\"closed-captioning\",\"code-block\",\"code-inline\",\"coins-color-old\",\"coins\",\"collapse-left\",\"collapse-right\",\"collectible-expressions\",\"collection\",\"comment\",\"comments\",\"communities\",\"community\",\"confidence\",\"contest\",\"controversial\",\"conversion\",\"copy-clipboard\",\"crop\",\"crosspost\",\"crowd-control\",\"custom-feed\",\"customize\",\"dashboard\",\"day\",\"delete-column\",\"delete\",\"delete-row\",\"devvit\",\"discover\",\"dismiss-all\",\"distinguish\",\"down-arrow\",\"down\",\"download\",\"downvote\",\"downvotes\",\"drag\",\"drugs\",\"duplicate\",\"edit\",\"effect\",\"embed\",\"emoji\",\"end-live-chat\",\"error\",\"expand-left\",\"expand-right\",\"external\",\"feed-video\",\"filter\",\"format\",\"forward\",\"funnel\",\"gif-post\",\"gold\",\"hashtag\",\"heart\",\"help\",\"hide\",\"history\",\"home\",\"hot\",\"ignore-reports\",\"image-post\",\"inbox\",\"info\",\"insert-column-left\",\"insert-column-right\",\"insert-row-above\",\"insert-row-below\",\"internet\",\"invite\",\"italic\",\"join\",\"joined\",\"jump-down\",\"jump-up\",\"karma\",\"keyboard\",\"kick\",\"language\",\"leave\",\"left\",\"link\",\"link-post\",\"list-bulleted\",\"list-numbered\",\"live-chat\",\"live\",\"load\",\"location\",\"lock\",\"logout\",\"loop\",\"macro\",\"mark-read\",\"marketplace\",\"mask\",\"media-gallery\",\"meme\",\"menu\",\"message\",\"mic\",\"mic-mute\",\"mod\",\"mod-mail\",\"mod-mode\",\"mod-mute\",\"mod-overflow\",\"mod-queue\",\"mod-unmute\",\"music\",\"mute\",\"new\",\"night\",\"no-internet\",\"notification\",\"notification-frequent\",\"notification-off\",\"nsfw\",\"nsfw-language\",\"nsfw-violence\",\"official\",\"original\",\"overflow-caret\",\"overflow-horizontal\",\"overflow-vertical\",\"pause\",\"payment\",\"peace\",\"pending-posts\",\"phone\",\"pin\",\"play\",\"poll-post\",\"popular\",\"posts\",\"powerup\",\"predictions\",\"premium\",\"privacy\",\"profile\",\"qa\",\"qr-code\",\"quarantined\",\"quote\",\"r-slash\",\"radar\",\"radio-button\",\"raise-hand\",\"random\",\"ratings-everyone\",\"ratings-mature\",\"ratings-nsfw\",\"ratings-violence\",\"recovery-phrase\",\"refresh\",\"removal-reasons\",\"remove\",\"reply\",\"report\",\"reverse\",\"rich-text\",\"right\",\"rising\",\"rotate\",\"rotate-image\",\"rpan\",\"rules\",\"safari\",\"save\",\"save-view\",\"saved\",\"saved-response\",\"search\",\"self\",\"send\",\"settings\",\"severity\",\"share\",\"share-new\",\"show\",\"side-menu\",\"skipback10\",\"skipforward10\",\"sort-az\",\"sort\",\"sort-price\",\"sort-za\",\"spam\",\"spoiler\",\"sponsored\",\"spreadsheet\",\"star\",\"statistics\",\"status-live\",\"sticker\",\"strikethrough\",\"subtract\",\"superscript\",\"swap-camera\",\"swipe-back\",\"swipe-down\",\"swipe\",\"swipe-up\",\"table\",\"tag\",\"tap\",\"text\",\"text-post\",\"text-size\",\"toggle\",\"tools\",\"top\",\"topic-activism\",\"topic-addictionsupport\",\"topic-advice\",\"topic-animals\",\"topic-anime\",\"topic-art\",\"topic-beauty\",\"topic-business\",\"topic-careers\",\"topic-cars\",\"topic-celebrity\",\"topic-craftsdiy\",\"topic-crypto\",\"topic-culture\",\"topic-diy\",\"topic-entertainment\",\"topic-ethics\",\"topic-family\",\"topic-fashion\",\"topic\",\"topic-fitness\",\"topic-food\",\"topic-funny\",\"topic-gender\",\"topic-health\",\"topic-help\",\"topic-history\",\"topic-hobbies\",\"topic-homegarden\",\"topic-internet\",\"topic-law\",\"topic-learning\",\"topic-lifestyle\",\"topic-marketplace\",\"topic-mature\",\"topic-mensfashion\",\"topic-menshealth\",\"topic-meta\",\"topic-military\",\"topic-movies\",\"topic-music\",\"topic-news\",\"topic-other\",\"topic-outdoors\",\"topic-pets\",\"topic-photography\",\"topic-places\",\"topic-podcasts\",\"topic-politics\",\"topic-programming\",\"topic-reading\",\"topic-religion\",\"topic-science\",\"topic-sexorientation\",\"topic-sports\",\"topic-style\",\"topic-tabletop\",\"topic-technology\",\"topic-television\",\"topic-traumasupport\",\"topic-travel\",\"topic-videogaming\",\"topic-womensfashion\",\"topic-womenshealth\",\"translate\",\"translation-off\",\"trim\",\"u-slash\",\"unban\",\"undo\",\"unheart\",\"unlock\",\"unmod\",\"unpin\",\"unstar\",\"unverified\",\"up-arrow\",\"up\",\"upload\",\"upvote\",\"upvotes\",\"user\",\"user-note\",\"users\",\"vault\",\"verified\",\"video-camera\",\"video-feed\",\"video-post\",\"video-thread\",\"video-transcription\",\"view-card\",\"view-classic\",\"view-compact\",\"view-grid\",\"view-sort\",\"views\",\"volume\",\"wallet\",\"warning\",\"webhook\",\"whale\",\"wiki-ban\",\"wiki\",\"wiki-unban\",\"world\",\"coins-color\",\"powerup-color\",\"powerup-fill-color\",\"share-android\",\"share-ios\",\"video-live-1\",\"video-live-fill-1\",\"video-live\",\"volume-mute\",\"binoculars\",\"caret-updown\",\"planet\",\"telescope\"] as const;\nexport type AllIconName = typeof ALL_ICON_NAMES[number];\nexport type IconName = `${AllIconName}` | `${AllIconName}-outline` | `${AllIconName}-fill`;\n", "import type * as protos from '@devvit/protos';\nimport type * as EventTypes from '@devvit/protos/types/devvit/events/v1alpha/events.d.ts';\n\nimport type { Devvit } from '../devvit/Devvit.js';\n\nexport { DeletionReason, EventSource } from '@devvit/protos';\nexport type { EventTypes };\n\n/** The event name for when a post is submitted */\nexport type PostSubmit = 'PostSubmit';\n/** The event name for when a post is created, after safety delay */\nexport type PostCreate = 'PostCreate';\n/** The event name for when a post is updated */\nexport type PostUpdate = 'PostUpdate';\n/** The event name for when a post is reported */\nexport type PostReport = 'PostReport';\n/** The event name for when a post is deleted */\nexport type PostDelete = 'PostDelete';\n/** The event name for when the flair of a post is updated */\nexport type PostFlairUpdate = 'PostFlairUpdate';\n/** The event name for when a comment is submitted */\nexport type CommentSubmit = 'CommentSubmit';\n/** The event name for when a comment is created, after safety delay */\nexport type CommentCreate = 'CommentCreate';\n/** The event name for when a comment is updated */\nexport type CommentUpdate = 'CommentUpdate';\n/** The event name for when a comment is reported */\nexport type CommentReport = 'CommentReport';\n/** The event name for when a comment is deleted */\nexport type CommentDelete = 'CommentDelete';\n/** The event name for when your app is installed */\nexport type AppInstall = 'AppInstall';\n/** The event name for when your app is upgraded */\nexport type AppUpgrade = 'AppUpgrade';\n/** The event name for when a moderator action is recorded to a subreddit's modlog */\nexport type ModActionTrigger = 'ModAction';\n/** The event name for when a mod mail is sent/received */\nexport type ModMailTrigger = 'ModMail';\n/** The event name for when a post is marked/unmarked as nsfw*/\nexport type PostNsfwUpdate = 'PostNsfwUpdate';\n/** The event name for when a post is marked/unmarked as spoiler*/\nexport type PostSpoilerUpdate = 'PostSpoilerUpdate';\n/** The event name for when a post is filtered by automoderator */\nexport type AutomoderatorFilterPost = 'AutomoderatorFilterPost';\n/** The event name for when a comment is filtered by automoderator */\nexport type AutomoderatorFilterComment = 'AutomoderatorFilterComment';\n\n/** Maps a TriggerEvent to a Protobuf message and type. */\nexport type TriggerEventType = {\n PostSubmit: { type: 'PostSubmit' } & protos.PostSubmit;\n PostCreate: { type: 'PostCreate' } & protos.PostCreate;\n PostUpdate: { type: 'PostUpdate' } & protos.PostUpdate;\n PostReport: { type: 'PostReport' } & protos.PostReport;\n PostDelete: { type: 'PostDelete' } & protos.PostDelete;\n PostFlairUpdate: { type: 'PostFlairUpdate' } & protos.PostFlairUpdate;\n CommentSubmit: { type: 'CommentSubmit' } & protos.CommentSubmit;\n CommentCreate: { type: 'CommentCreate' } & protos.CommentCreate;\n CommentUpdate: { type: 'CommentUpdate' } & protos.CommentUpdate;\n CommentReport: { type: 'CommentReport' } & protos.CommentReport;\n CommentDelete: { type: 'CommentDelete' } & protos.CommentDelete;\n AppInstall: { type: 'AppInstall' } & protos.AppInstall;\n AppUpgrade: { type: 'AppUpgrade' } & protos.AppUpgrade;\n ModAction: { type: 'ModAction' } & protos.ModAction;\n ModMail: { type: 'ModMail' } & protos.ModMail;\n PostNsfwUpdate: { type: 'PostNsfwUpdate' } & protos.PostNsfwUpdate;\n PostSpoilerUpdate: { type: 'PostSpoilerUpdate' } & protos.PostSpoilerUpdate;\n AutomoderatorFilterPost: { type: 'AutomoderatorFilterPost' } & protos.AutomoderatorFilterPost;\n AutomoderatorFilterComment: {\n type: 'AutomoderatorFilterComment';\n } & protos.AutomoderatorFilterComment;\n};\n\nexport type TriggerEvent =\n | PostSubmit\n | PostCreate\n | PostUpdate\n | PostReport\n | PostDelete\n | PostFlairUpdate\n | CommentSubmit\n | CommentCreate\n | CommentUpdate\n | CommentReport\n | CommentDelete\n | AppInstall\n | AppUpgrade\n | ModActionTrigger\n | ModMailTrigger\n | PostNsfwUpdate\n | PostSpoilerUpdate\n | AutomoderatorFilterPost\n | AutomoderatorFilterComment;\n\ntype TriggerResult = Promise<void> | void;\n\nexport type TriggerContext = Omit<Devvit.Context, 'ui' | 'dimensions' | 'modLog' | 'uiEnvironment'>;\n\nexport type TriggerOnEventHandler<RequestType> = (\n event: RequestType,\n context: TriggerContext\n) => TriggerResult;\n\nexport type PostSubmitDefinition = {\n event: PostSubmit;\n onEvent: TriggerOnEventHandler<protos.PostSubmit>;\n};\n\nexport type PostCreateDefinition = {\n event: PostCreate;\n onEvent: TriggerOnEventHandler<protos.PostCreate>;\n};\n\nexport type PostUpdateDefinition = {\n event: PostUpdate;\n onEvent: TriggerOnEventHandler<protos.PostUpdate>;\n};\n\nexport type PostReportDefinition = {\n event: PostReport;\n onEvent: TriggerOnEventHandler<protos.PostReport>;\n};\n\nexport type PostDeleteDefinition = {\n event: PostDelete;\n onEvent: TriggerOnEventHandler<protos.PostDelete>;\n};\n\nexport type PostFlairUpdateDefinition = {\n event: PostFlairUpdate;\n onEvent: TriggerOnEventHandler<protos.PostFlairUpdate>;\n};\n\nexport type CommentSubmitDefinition = {\n event: CommentSubmit;\n onEvent: TriggerOnEventHandler<protos.CommentSubmit>;\n};\n\nexport type CommentCreateDefinition = {\n event: CommentCreate;\n onEvent: TriggerOnEventHandler<protos.CommentCreate>;\n};\n\nexport type CommentUpdateDefinition = {\n event: CommentUpdate;\n onEvent: TriggerOnEventHandler<protos.CommentUpdate>;\n};\n\nexport type CommentReportDefinition = {\n event: CommentReport;\n onEvent: TriggerOnEventHandler<protos.CommentReport>;\n};\n\nexport type CommentDeleteDefinition = {\n event: CommentDelete;\n onEvent: TriggerOnEventHandler<protos.CommentDelete>;\n};\n\nexport type AppInstallDefinition = {\n event: AppInstall;\n onEvent: TriggerOnEventHandler<protos.AppInstall>;\n};\n\nexport type AppUpgradeDefinition = {\n event: AppUpgrade;\n onEvent: TriggerOnEventHandler<protos.AppUpgrade>;\n};\n\nexport type ModActionDefinition = {\n event: ModActionTrigger;\n onEvent: TriggerOnEventHandler<protos.ModAction>;\n};\n\nexport type ModMailDefinition = {\n event: ModMailTrigger;\n onEvent: TriggerOnEventHandler<protos.ModMail>;\n};\n\nexport type PostNsfwUpdateDefinition = {\n event: PostNsfwUpdate;\n onEvent: TriggerOnEventHandler<protos.PostNsfwUpdate>;\n};\n\nexport type PostSpoilerUpdateDefinition = {\n event: PostSpoilerUpdate;\n onEvent: TriggerOnEventHandler<protos.PostSpoilerUpdate>;\n};\n\nexport type OnAutomoderatorFilterPostDefinition = {\n event: AutomoderatorFilterPost;\n onEvent: TriggerOnEventHandler<protos.AutomoderatorFilterPost>;\n};\n\nexport type OnAutomoderatorFilterCommentDefinition = {\n event: AutomoderatorFilterComment;\n onEvent: TriggerOnEventHandler<protos.AutomoderatorFilterComment>;\n};\n\nexport type MultiTriggerDefinition<Event extends TriggerEvent> = {\n events: readonly Event[];\n onEvent: TriggerOnEventHandler<TriggerEventType[Event]>;\n};\n\nexport type TriggerDefinition =\n | PostSubmitDefinition\n | PostCreateDefinition\n | PostUpdateDefinition\n | PostFlairUpdateDefinition\n | PostReportDefinition\n | PostDeleteDefinition\n | CommentSubmitDefinition\n | CommentCreateDefinition\n | CommentUpdateDefinition\n | CommentReportDefinition\n | CommentDeleteDefinition\n | AppInstallDefinition\n | AppUpgradeDefinition\n | ModActionDefinition\n | ModMailDefinition\n | PostSpoilerUpdateDefinition\n | PostNsfwUpdateDefinition\n | OnAutomoderatorFilterPostDefinition\n | OnAutomoderatorFilterCommentDefinition;\n\nexport type OnTriggerRequest =\n | protos.PostFlairUpdate\n | protos.PostSubmit\n | protos.PostCreate\n | protos.PostUpdate\n | protos.PostReport\n | protos.PostDelete\n | protos.CommentSubmit\n | protos.CommentCreate\n | protos.CommentUpdate\n | protos.CommentReport\n | protos.CommentDelete\n | protos.AppInstall\n | protos.AppUpgrade\n | protos.ModAction\n | protos.ModMail\n | protos.PostNsfwUpdate\n | protos.PostSpoilerUpdate\n | protos.AutomoderatorFilterPost\n | protos.AutomoderatorFilterComment;\n", "import type { Metadata, ValidateFormRequest, ValidateFormResponse } from '@devvit/protos';\nimport { AppSettingsDefinition, GetFieldsResponse } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { transformFormFields } from '../../apis/ui/helpers/transformForm.js';\nimport { Devvit } from '../Devvit.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport { onValidateFormHelper } from './helpers/settingsUtils.js';\n\nasync function onGetSettingsFields(): Promise<GetFieldsResponse> {\n if (!Devvit.appSettings) {\n throw new Error('App settings were not defined.');\n }\n\n return GetFieldsResponse.fromJSON({\n fields: {\n fields: transformFormFields(Devvit.appSettings),\n },\n });\n}\n\nasync function onValidateForm(\n req: ValidateFormRequest,\n metadata: Metadata\n): Promise<ValidateFormResponse> {\n return onValidateFormHelper(req, Devvit.appSettings, metadata);\n}\n\nexport function registerAppSettings(config: Config): void {\n config.provides(AppSettingsDefinition);\n extendDevvitPrototype('GetAppSettingsFields', onGetSettingsFields);\n extendDevvitPrototype('ValidateAppForm', onValidateForm);\n}\n", "import { FormField as FormFieldProto, FormFieldType } from '@devvit/protos';\n\nimport type {\n BooleanField,\n FormField,\n FormFieldGroup,\n ImageField,\n NumberField,\n ParagraphField,\n SelectField,\n StringField,\n} from '../../../types/form.js';\n\nexport function transformFormFields(fields: readonly FormField[]): FormFieldProto[] {\n return fields.map((field) => {\n switch (field.type) {\n case 'string':\n return transformStringField(field);\n case 'image':\n return transformImageField(field);\n case 'paragraph':\n return transformParagraphField(field);\n case 'number':\n return transformNumberField(field);\n case 'select':\n return transformSelectField(field);\n case 'boolean':\n return transformBooleanField(field);\n case 'group':\n return transformGroupField(field);\n default:\n throw new Error('Unknown field type.');\n }\n });\n}\n\nfunction transformStringField(field: StringField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.STRING,\n stringValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n stringConfig: {\n placeholder: field.placeholder,\n },\n },\n fieldId: field.name,\n fieldType: FormFieldType.STRING,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n isSecret: field.isSecret,\n };\n}\n\nfunction transformImageField(field: ImageField): FormFieldProto {\n return {\n disabled: field.disabled,\n fieldId: field.name,\n fieldType: FormFieldType.IMAGE,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformParagraphField(field: ParagraphField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.PARAGRAPH,\n stringValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n paragraphConfig: {\n lineHeight: field.lineHeight,\n placeholder: field.placeholder,\n },\n },\n fieldId: field.name,\n fieldType: FormFieldType.PARAGRAPH,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformNumberField(field: NumberField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.NUMBER,\n numberValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n numberConfig: {},\n },\n fieldId: field.name,\n fieldType: FormFieldType.NUMBER,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformSelectField(field: SelectField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.SELECTION,\n selectionValue: {\n values: field.defaultValue ?? [],\n },\n },\n disabled: field.disabled,\n fieldConfig: {\n selectionConfig: {\n choices: field.options,\n multiSelect: field.multiSelect,\n },\n },\n fieldId: field.name,\n fieldType: FormFieldType.SELECTION,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformBooleanField(field: BooleanField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.BOOLEAN,\n boolValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldId: field.name,\n fieldType: FormFieldType.BOOLEAN,\n helpText: field.helpText,\n label: field.label,\n };\n}\n\nfunction transformGroupField(field: FormFieldGroup): FormFieldProto {\n return {\n fieldId: '',\n fieldType: FormFieldType.GROUP,\n fieldConfig: {\n groupConfig: {\n fields: transformFormFields(field.fields),\n },\n },\n label: field.label,\n helpText: field.helpText,\n };\n}\n", "import type { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { Metadata } from '@devvit/protos';\n\n// Do this as an IIFE so that we can have the storage be a const\nconst asyncLocalStorage: AsyncLocalStorage<Metadata> | undefined = (function () {\n try {\n // This require() will fail during bundling, and on browsers. That's OK. Leave\n // the async local storage undefined in those cases, and we'll let follow on\n // code decide if they're OK running with that, or if they want to CircuitBreak.\n\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { AsyncLocalStorage } = require('node:async_hooks');\n return new AsyncLocalStorage();\n } catch {\n // nop\n }\n return undefined;\n})();\n\n// Only set localMetadata if we're not in a remote runtime. If we are in a remote\n// runtime, then we should be using asyncLocalStorage.\nlet localMetadata: Metadata | undefined;\n\n/**\n * Get the current metadata. If called outside a request, this will throw an\n * error.\n * @internal\n * @returns {Metadata} The current metadata.\n */\nexport function getMetadata(): Metadata {\n // Q: How does this work, without jumbling up the metadata between requests?\n // A: If we're running in a remote runtime, we use the async local storage to\n // get the metadata, which guarantees that we get the correct metadata for the\n // current thread. If we're not in a remote runtime, then we'll use the\n // metadata saved in localMetadata; this is set by `withMetadata()` when\n // invoked in a local runtime, and since local runtimes can't handle\n // multiple requests at once, we can safely use this saved metadata without\n // fear of jumbling them up.\n if (asyncLocalStorage) {\n const metadata = asyncLocalStorage.getStore();\n if (!metadata) {\n throw Error('API error; metadata should never be accessed outside a request');\n }\n return metadata;\n }\n\n if (!localMetadata) {\n throw Error('Devvit metadata can only be accessed within a request');\n }\n\n return localMetadata;\n}\n\n/** @internal */\nexport async function withMetadata<T>(\n metadata: Metadata,\n callback: () => T | Promise<T>\n): Promise<T> {\n if (asyncLocalStorage) {\n // If asyncLocalStorage is available, we use it to run the function with\n // access to the metadata.\n return asyncLocalStorage.run(metadata, callback);\n } else {\n // If asyncLocalStorage is not available, set the value on localMetadata,\n // then run the function directly.\n try {\n localMetadata = metadata;\n return await callback();\n } finally {\n // Unset the localMetadata after we're done, to prevent any leakage.\n localMetadata = undefined;\n }\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport { Devvit } from '../../Devvit.js';\nimport { withMetadata } from '../async-metadata.js';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ntype DevvitPrototypeFunctionWithMetadata = (\n arg1: any,\n arg2: Metadata,\n ...rest: any[]\n) => Promise<any> | void;\ntype DevvitPrototypeFunctionWithOptionalMetadata = (\n arg1: any,\n arg2: Metadata | undefined,\n ...rest: any[]\n) => Promise<any> | void;\ntype DevvitPrototypeFunction =\n | DevvitPrototypeFunctionWithMetadata\n | DevvitPrototypeFunctionWithOptionalMetadata;\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nexport function extendDevvitPrototype(key: string, value: DevvitPrototypeFunction): void {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (Devvit as Function).prototype[key] = ((req, meta, ...rest) => {\n // If the metadata is nullish, we set it to an empty object instead. This\n // makes sure that when we call `getMetadata` later, we only get an error\n // in \"non-sane\" cases, like if it's called outside a request.\n const guaranteedMeta = meta ?? {};\n return withMetadata(guaranteedMeta, () => value(req, guaranteedMeta, ...rest));\n }) satisfies DevvitPrototypeFunctionWithOptionalMetadata;\n}\n", "import type { Metadata, ValidateFormRequest } from '@devvit/protos';\nimport { ValidateFormResponse } from '@devvit/protos';\n\nimport { makeAPIClients } from '../../../apis/makeAPIClients.js';\nimport { getFormValues } from '../../../apis/ui/helpers/getFormValues.js';\nimport type {\n OnValidateHandler,\n SettingsFormField,\n SettingsFormFieldGroup,\n} from '../../../types/settings.js';\nimport { getContextFromMetadata } from '../context.js';\n\ntype SettingsPlainField = Exclude<SettingsFormField, SettingsFormFieldGroup>;\n\nexport function extractSettingsFields(settings: SettingsFormField[]): SettingsPlainField[] {\n return settings.flatMap((field) => {\n if (field.type === 'group') {\n return extractSettingsFields(field.fields);\n }\n return field;\n }) as SettingsPlainField[];\n}\n\nexport async function onValidateFormHelper(\n req: ValidateFormRequest,\n settings: SettingsFormField[] | undefined,\n metadata: Metadata\n): Promise<ValidateFormResponse> {\n if (!settings) {\n throw new Error('Settings were not defined.');\n }\n\n const response: ValidateFormResponse = {\n success: true,\n errors: {},\n };\n\n const formValues = getFormValues(req.fieldValues);\n const flattendFields = extractSettingsFields(settings);\n\n await Promise.all(\n flattendFields.map(async (field) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldName = (field as any).name as string | undefined;\n\n if (fieldName && field.onValidate) {\n const value = formValues[fieldName];\n const validator = field.onValidate as OnValidateHandler<unknown>;\n\n const context = Object.assign(\n makeAPIClients({\n metadata,\n }),\n getContextFromMetadata(metadata)\n );\n\n const error = await validator(\n {\n value,\n isEditing: req.editing,\n },\n context\n );\n\n if (error) {\n response.success = false;\n response.errors[fieldName] = error;\n }\n }\n })\n );\n\n return ValidateFormResponse.fromJSON(response);\n}\n", "import type { RealtimeSubscriptionEvent } from '@devvit/protos';\nimport { RealtimeSubscriptionStatus } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport type {\n UseChannelHook,\n UseChannelHookState,\n UseChannelResult,\n} from '../../../types/hooks.js';\nimport { Hook } from '../../../types/hooks.js';\nimport type { ChannelOptions } from '../../../types/realtime.js';\nimport { ChannelStatus } from '../../../types/realtime.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseChannelHook(reconciler: BlocksReconciler): UseChannelHook {\n function useChannel<Message extends JSONValue>(\n options: ChannelOptions<Message>\n ): UseChannelResult<Message> {\n const debug = false;\n const hookIndex = reconciler.currentHookIndex;\n const currentState = reconciler.getCurrentComponentState<UseChannelHookState>();\n const previousState = reconciler.getPreviousComponentState<UseChannelHookState>();\n\n const appId = reconciler.metadata[Header.App]?.values[0];\n assertNonNull<string | undefined>(appId, 'useChannel - app is missing from Context');\n\n const installationId = reconciler.metadata[Header.Installation]?.values[0];\n assertNonNull<string | undefined>(\n installationId,\n 'useChannel - installation is missing from Context'\n );\n\n async function send(msg: Message): Promise<void> {\n if (debug) console.debug('[realtime] sends', msg);\n const name = currentState[hookIndex].channel;\n if (currentState[hookIndex].active) {\n if (currentState[hookIndex].connected) {\n await reconciler.realtime.send(name, msg);\n } else {\n throw Error(`Failed to send to channel '${name}'; it is active but not yet connected`);\n }\n } else {\n throw Error(`Cannot send a message over inactive channel: ${name}`);\n }\n }\n\n function subscribe(): void {\n if (!currentState[hookIndex].active) {\n if (debug) console.debug('[realtime] subscribe');\n const name = currentState[hookIndex].channel;\n currentState[hookIndex].active = true;\n\n reconciler.addRealtimeChannel(name);\n }\n }\n\n function unsubscribe(): void {\n if (currentState[hookIndex].active) {\n if (debug) console.debug('[realtime] unsubscribe');\n const name = currentState[hookIndex].channel;\n currentState[hookIndex].active = false;\n\n reconciler.removeRealtimeChannel(name);\n }\n }\n\n function hook(event: RealtimeSubscriptionEvent): () => Promise<void> {\n return async () => {\n let result;\n switch (event.status) {\n case RealtimeSubscriptionStatus.REALTIME_SUBSCRIBED:\n if (debug) console.debug('[realtime] onSubscribed()');\n currentState[hookIndex].connected = true;\n result = options.onSubscribed?.();\n break;\n case RealtimeSubscriptionStatus.REALTIME_UNSUBSCRIBED:\n if (debug) console.debug('[realtime] onUnsubscribed()');\n currentState[hookIndex].connected = false;\n result = options.onUnsubscribed?.();\n break;\n default:\n if (debug) console.debug('[realtime] receives', event.event?.data);\n // expect msg. this must align to RealtimeClient.send().\n result = options.onMessage(event.event?.data?.msg);\n break;\n }\n\n await result;\n };\n }\n\n let hookState: UseChannelHookState = {\n channel: `${appId}:${installationId}:${options.name}`,\n active: false,\n connected: false,\n preventCallback: false,\n type: Hook.CHANNEL,\n };\n\n if (hookIndex in currentState) {\n hookState = currentState[hookIndex];\n } else if (hookIndex in previousState) {\n hookState = previousState[hookIndex];\n }\n\n const event = reconciler.realtimeEvent;\n\n if (reconciler.isInitialRender) {\n hookState.active = false;\n } else if (event && hookState.active && event.event!.channel === hookState.channel) {\n if (!hookState.preventCallback) {\n reconciler.runHook(hook(event));\n }\n\n reconciler.rerenderIn(0);\n }\n\n currentState[hookIndex] = hookState;\n reconciler.currentHookIndex++;\n\n let status = ChannelStatus.Unknown;\n if (hookState.active && hookState.connected) {\n status = ChannelStatus.Connected;\n } else if (hookState.active && !hookState.connected) {\n status = ChannelStatus.Connecting;\n } else if (!hookState.active && hookState.connected) {\n status = ChannelStatus.Disconnecting;\n } else if (!hookState.active && !hookState.connected) {\n status = ChannelStatus.Disconnected;\n }\n\n return {\n subscribe,\n unsubscribe,\n send,\n status,\n };\n }\n\n return useChannel;\n}\n", "export function assertNonNull(val, msg) {\n if (val == null)\n throw Error(msg ?? 'Expected nonnullish value.');\n}\nexport function NonNull(val, msg) {\n assertNonNull(val, msg);\n return val;\n}\n", "import type { FormFieldValue } from '@devvit/protos';\nimport { FormFieldType } from '@devvit/protos';\n\nimport type { FormValues } from '../../../types/form.js';\n\nexport function flattenFormFieldValue(\n value: FormFieldValue\n): undefined | string | string[] | number | boolean {\n switch (value.fieldType) {\n case FormFieldType.STRING:\n return value.stringValue;\n case FormFieldType.IMAGE:\n // the string value is the URL\n return value.stringValue;\n case FormFieldType.PARAGRAPH:\n return value.stringValue;\n case FormFieldType.NUMBER:\n return value.numberValue;\n case FormFieldType.BOOLEAN:\n return value.boolValue;\n case FormFieldType.SELECTION:\n return value.selectionValue?.values ?? [];\n default:\n return undefined;\n }\n}\n\nexport function getFormValues(results: { [key: string]: FormFieldValue }): FormValues {\n return Object.keys(results).reduce((acc, key) => {\n const val = flattenFormFieldValue(results[key]);\n if (val !== undefined) acc[key] = val;\n return acc;\n }, {} as FormValues);\n}\n", "import type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport { getFormValues } from '../../../apis/ui/helpers/getFormValues.js';\nimport type { Form, FormFunction, FormValues } from '../../../types/form.js';\nimport type { UseFormHook, UseFormHookState } from '../../../types/hooks.js';\nimport { Hook } from '../../../types/hooks.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseFormHook(reconciler: BlocksReconciler): UseFormHook {\n function useForm(\n form: Form | FormFunction,\n onSubmit: (values: FormValues) => void | Promise<void>\n ): FormKey {\n const hookIndex = reconciler.currentHookIndex;\n const componentKey = reconciler.getCurrentComponentKey();\n const currentState = reconciler.getCurrentComponentState<UseFormHookState>();\n const previousState = reconciler.getPreviousComponentState<UseFormHookState>();\n\n const formKey: FormKey = `form.hook.${componentKey}.${hookIndex}`;\n\n let hookState: UseFormHookState = {\n formKey,\n preventSubmit: false,\n type: Hook.FORM,\n };\n\n if (hookIndex in currentState) {\n hookState = currentState[hookIndex];\n } else if (hookIndex in previousState) {\n hookState = previousState[hookIndex];\n }\n\n currentState[hookIndex] = hookState;\n reconciler.forms.set(formKey, form);\n\n const formSubmittedEvent = reconciler.formSubmittedEvent;\n\n if (formSubmittedEvent && !hookState.preventSubmit) {\n if (formSubmittedEvent.formId === formKey) {\n reconciler.runHook(async () => {\n const response = onSubmit(getFormValues(formSubmittedEvent.results));\n\n if (response && response instanceof Promise) {\n await response;\n }\n\n reconciler.rerenderIn(0);\n });\n }\n }\n\n currentState[hookIndex].preventSubmit = false;\n reconciler.currentHookIndex++;\n\n return formKey;\n }\n\n return useForm;\n}\n", "import type {\n UseIntervalHook,\n UseIntervalHookState,\n UseIntervalResult,\n} from '../../../types/hooks.js';\nimport { Hook } from '../../../types/hooks.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseIntervalHook(reconciler: BlocksReconciler): UseIntervalHook {\n function useInterval(\n callback: () => void | Promise<void>,\n requestedDelayMs: number\n ): UseIntervalResult {\n const hookIndex = reconciler.currentHookIndex;\n const currentState = reconciler.getCurrentComponentState<UseIntervalHookState>();\n const previousState = reconciler.getPreviousComponentState<UseIntervalHookState>();\n\n // delayMs may only be a minimum of 100ms\n const minDelay = 100;\n const delayMs = Math.max(minDelay, requestedDelayMs);\n\n let hookState: UseIntervalHookState = {\n lastRun: undefined,\n running: false,\n preventCallback: false,\n type: Hook.INTERVAL,\n };\n\n if (hookIndex in currentState) {\n hookState = currentState[hookIndex];\n } else if (hookIndex in previousState) {\n hookState = previousState[hookIndex];\n }\n\n function start(): void {\n if (requestedDelayMs < minDelay) {\n console.error(\n `useInterval delay must be at least ${minDelay}ms. Your interval of ${requestedDelayMs}ms was automatically extended.`\n );\n }\n\n for (const [i, stateItem] of Object.entries(currentState)) {\n if (i !== hookIndex.toString() && stateItem?.type === Hook.INTERVAL && stateItem?.running) {\n throw new Error('Only one useInterval hook may be running at a time');\n }\n }\n\n currentState[hookIndex] = {\n running: true,\n lastRun: currentState[hookIndex]?.lastRun ?? Date.now(),\n preventCallback: false,\n type: Hook.INTERVAL,\n };\n\n reconciler.rerenderIn(delayMs);\n }\n\n function stop(): void {\n currentState[hookIndex].running = false;\n currentState[hookIndex].lastRun = undefined;\n currentState[hookIndex].preventCallback = false;\n }\n\n if (reconciler.isEffectRender && hookState.running) {\n if (!hookState.preventCallback) {\n if (hookState.lastRun === undefined || hookState.lastRun + delayMs < Date.now()) {\n reconciler.runHook(async () => {\n const response = callback();\n\n if (response && response instanceof Promise) {\n await response;\n }\n\n hookState.lastRun = Date.now();\n });\n }\n }\n\n reconciler.rerenderIn(delayMs);\n }\n\n hookState.preventCallback = false;\n currentState[hookIndex] = hookState;\n reconciler.currentHookIndex++;\n\n return {\n start,\n stop,\n };\n }\n\n return useInterval;\n}\n", "import type { SetStateAction, UseStateHook, UseStateResult } from '../../../types/hooks.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseStateHook(reconciler: BlocksReconciler): UseStateHook {\n function useState<S>(initialState: S): UseStateResult<S> {\n const hookIndex = reconciler.currentHookIndex;\n const currentState = reconciler.getCurrentComponentState<S>();\n const previousState = reconciler.getPreviousComponentState<S>();\n\n if (hookIndex in currentState) {\n reconciler.currentHookIndex++;\n return [currentState[hookIndex], stateSetter];\n }\n\n if (reconciler.isInitialRender || !(hookIndex in previousState)) {\n const value = initialState instanceof Function ? initialState() : initialState;\n\n if (value instanceof Promise) {\n const asyncResolver = async (): Promise<void> => {\n currentState[hookIndex] = await value;\n reconciler.currentHookIndex = 0;\n };\n throw asyncResolver();\n }\n\n currentState[hookIndex] = value;\n } else {\n currentState[hookIndex] = previousState[hookIndex];\n }\n\n function stateSetter(valueOrFunction: SetStateAction<S>): void {\n if (reconciler.isRendering) {\n throw new Error('Cannot call setState while rendering.');\n }\n\n currentState[hookIndex] =\n valueOrFunction instanceof Function\n ? valueOrFunction(currentState[hookIndex])\n : valueOrFunction;\n }\n\n reconciler.currentHookIndex++;\n\n return [currentState[hookIndex], stateSetter];\n }\n return useState;\n}\n", "import type { JSONValue, RedisClient } from '../../index.js';\n\nexport type CacheEntry = {\n value: JSONValue | null;\n expires: number; // Timestamp in milliseconds\n error: string | null;\n errorTime: number | null;\n checkedAt: number;\n errorCount: number;\n};\n\nexport type Clock = {\n now(): Date;\n};\n\nexport const SystemClock: Clock = {\n now() {\n return new Date();\n },\n};\n\nexport type CacheOptions = {\n /**\n * Time to live in milliseconds.\n */\n ttl: number;\n\n /**\n * Key to use for caching.\n */\n key: string;\n};\n\nexport type LocalCache = { [key: string]: CacheEntry };\n\nexport function _namespaced(key: string): string {\n return `__autocache__${key}`;\n}\nexport function _lock(key: string): string {\n return `__lock__${key}`;\n}\n\nconst pollEvery = 300; // milli\nconst maxPollingTimeout = 1000; // milli\nconst minTtlValue = 5000;\nexport const retryLimit = 3;\nconst errorRetryProbability = 0.1;\nexport const clientRetryDelay = 1000;\nexport const allowStaleFor = 30_000;\n\ntype WithLocalCache = {\n __cache?: LocalCache;\n};\n\nfunction _unwrap<T>(entry: CacheEntry): T {\n if (entry.error) {\n throw new Error(entry.error);\n }\n return entry.value as T;\n}\n\n/**\n * Refactored out into a class to allow for easier testing and clarity of purpose.\n *\n * This class is responsible for managing the caching of promises. It is a layered cache, meaning it will first check\n * the local cache, then the redis cache, and finally the source of truth. It will also handle refreshing the cache according\n * to the TTL and error handling.\n *\n * Please note that in order to prevent a stampede of requests to the source of truth, we use a lock in redis to ensure only one\n * request is made to the source of truth at a time. If the lock is obtained, the cache will be updated and the lock will be released.\n *\n * Additionally, we use a polling mechanism to fetch the cache if the lock is not obtained. This is to prevent unnecessary errors.\n *\n * Finally, we also want to prevent stampedes against redis for the lock election and the retries. We use a ramping probability to ease in the\n * attempts to get the lock, and not every error will trigger a retry.\n *\n * This means that the cache will be eventually consistent, but will not be immediately consistent. This is a tradeoff we are willing to make.\n * Additionally, this means that the TTL is not precice. The cache may be updated a bit more often than the TTL, but it will not be updated less often.\n *\n */\nexport class PromiseCache {\n #redis: RedisClient;\n #localCache: LocalCache = {};\n #clock: Clock;\n #state: WithLocalCache;\n\n constructor(redis: RedisClient, state: WithLocalCache, clock: Clock = SystemClock) {\n this.#redis = redis;\n this.#state = state;\n this.#clock = clock;\n }\n\n /**\n * This is the public API for the cache. Call this method to cache a promise.\n *\n * @param closure\n * @param options\n * @returns\n */\n async cache<T extends JSONValue>(closure: () => Promise<T>, options: CacheOptions): Promise<T> {\n this.#localCache = this.#state.__cache = this.#state.__cache ?? {};\n this.#enforceTTL(options);\n\n const localCachedAnswer = this.#localCachedAnswer<T>(options.key);\n if (localCachedAnswer !== undefined) {\n return localCachedAnswer;\n }\n\n const existing = await this.#redisEntry(options.key);\n const entry = await this.#maybeRefreshCache(options, existing, closure);\n\n return _unwrap(entry);\n }\n\n /**\n * Get the value from the local cache if it exists and is not expired. We're willing to retry errors, and we're willing\n * to throw errors if we have them in cache.\n *\n * We don't want to retry excessively, so we have a limit on the number of retries. If someone else has retried in the last\n * clientRetryDelay, let's not retry again. We also have a probability of retrying, so we don't retry every time.\n */\n #localCachedAnswer<T extends JSONValue>(key: string): T | undefined {\n const val = this.#localCache[key];\n if (val) {\n const now = this.#clock.now().getTime();\n const hasRetryableError =\n val?.error &&\n val?.errorTime &&\n val.errorCount < retryLimit &&\n Math.random() < errorRetryProbability &&\n val.errorTime! + clientRetryDelay < now;\n const expired = val?.expires && val.expires < now && val.checkedAt + clientRetryDelay < now;\n if (expired || hasRetryableError) {\n delete this.#localCache[key];\n return undefined;\n } else {\n return _unwrap(val);\n }\n }\n return undefined;\n }\n\n /**\n * If we've bothered to check redis, we're already on the backend. Let's see if the cache either (1) contains an error, (2)\n * is expired, (3) is missing, or (4) is about to expire. If any of these are true, we'll refresh the cache based on heuristics.\n *\n * We'll always refresh if missing or expired, but its probabilistic if we'll refresh if about to expire or if we have an error.\n */\n async #maybeRefreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const expires = entry?.expires;\n const rampProbability = expires ? this.#calculateRamp(expires) : 1;\n if (\n !entry ||\n (entry?.error && entry.errorCount < retryLimit && errorRetryProbability > Math.random()) ||\n rampProbability > Math.random()\n ) {\n return this.#refreshCache(options, entry, closure);\n } else {\n return entry!;\n }\n }\n\n /**\n * The conditions for refreshing the cache are handled in the calling method, which should be\n * #maybeRefreshCache.\n *\n * If you don't win the lock, you'll poll for the cache. If you don't get the cache within maxPollingTimeout, you'll throw an error.\n */\n async #refreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const lockKey = _lock(options.key);\n const now = this.#clock.now().getTime();\n\n /**\n * The write lock should last for a while, but not the full TTL. Hopefully write attempts settle down after a while.\n */\n const lockExpiration = new Date(now + options.ttl / 2);\n\n const lockObtained = await this.#redis.set(lockKey, '1', {\n expiration: lockExpiration,\n nx: true,\n });\n if (lockObtained) {\n return this.#updateCache(options.key, entry, closure, options.ttl);\n } else if (entry) {\n // This entry is still valid, return it\n return entry;\n } else {\n const start = this.#clock.now();\n return this.#pollForCache(start, options.key, options.ttl);\n }\n }\n\n async #pollForCache(start: Date, key: string, ttl: number): Promise<CacheEntry> {\n const pollingTimeout = Math.min(ttl, maxPollingTimeout);\n const existing = await this.#redisEntry(key);\n if (existing) {\n return existing;\n }\n\n if (this.#clock.now().getTime() - start.getTime() >= pollingTimeout) {\n throw new Error(`Cache request timed out trying to get data at key: ${key}`);\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollEvery));\n return this.#pollForCache(start, key, ttl);\n }\n\n /**\n * Actually update the cache. This is the method that will be called if we have the lock.\n */\n async #updateCache<T extends JSONValue>(\n key: string,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>,\n ttl: number\n ): Promise<CacheEntry> {\n const expires = this.#clock.now().getTime() + ttl;\n entry = entry ?? {\n value: null,\n expires,\n errorCount: 0,\n error: null,\n errorTime: null,\n checkedAt: 0,\n };\n try {\n entry.value = await closure();\n entry.error = null;\n entry.errorCount = 0;\n entry.errorTime = null;\n } catch (e) {\n entry.value = null;\n entry.error = (e as Error).message ?? 'Unknown error';\n entry.errorTime = this.#clock.now().getTime();\n entry.errorCount++;\n }\n\n this.#localCache[key] = entry;\n\n await this.#redis.set(_namespaced(key), JSON.stringify(entry), {\n expiration: new Date(expires + allowStaleFor),\n });\n\n /**\n * Unlocking will allow retries to happen if there was an error. Otherwise we don't unlock, because the lock\n * will expire on its own.\n */\n if (entry.error && entry.errorCount < retryLimit) {\n await this.#redis.del(_lock(key));\n }\n\n return entry;\n }\n\n /**\n * This is the schedule for optimistic pre-fetch of an about-to-expire cache. It exponentially ramps in, which hopefully provides\n * a degree of flexibility in the face of varying traffic levels.\n */\n #calculateRamp(expiry: number): number {\n const now = this.#clock.now().getTime();\n const remaining = expiry - now;\n\n if (remaining < 0) {\n return 1;\n } else if (remaining < 1000) {\n return 0.1;\n } else if (remaining < 2000) {\n return 0.01;\n } else if (remaining < 3000) {\n return 0.001;\n } else {\n return 0;\n }\n }\n\n async #redisEntry(key: string): Promise<CacheEntry | undefined> {\n const val = await this.#redis.get(_namespaced(key));\n if (val) {\n const entry = JSON.parse(val) as CacheEntry;\n entry.checkedAt = this.#clock.now().getTime();\n this.#localCache[key] = entry;\n return entry;\n }\n return undefined;\n }\n\n #enforceTTL(options: CacheOptions): void {\n if (options.ttl < minTtlValue) {\n console.warn(\n `Cache TTL cannot be less than ${minTtlValue} milliseconds! Updating ttl value of ${options.ttl} to ${minTtlValue}.`\n );\n options.ttl = minTtlValue;\n }\n }\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { RedisClient } from '../../types/redis.js';\nimport type { BlocksReconciler } from './blocks/BlocksReconciler.js';\nimport type { CacheOptions, Clock, LocalCache } from './promise_cache.js';\nimport { PromiseCache, SystemClock } from './promise_cache.js';\n\nexport type CacheHelper = <T extends JSONValue>(\n fn: () => Promise<T>,\n options: CacheOptions\n) => Promise<T>;\n\nexport function makeCache(\n redis: RedisClient,\n state: Partial<BlocksReconciler['state']> & { __cache?: LocalCache },\n clock: Clock = SystemClock\n): CacheHelper {\n const pc = new PromiseCache(redis, state, clock);\n return pc.cache.bind(pc);\n}\n", "import type { AssetMap } from '@devvit/shared-types/Assets.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\n\nexport type GetURLOptions = {\n webView?: boolean | undefined;\n};\n\nfunction assertValidUrl(path: string): void | never {\n // This will throw an exception if it's an invalid URL such as a relative path\n // NOTE: substring is here to only check up until the data segment if this is a data URL so we don't waste time needlessly parsing data.\n // Technically this will lose the last character if this isn't a data URL but we're just validating structure.\n new URL(path.slice(0, path.indexOf(',')));\n}\n\nexport class AssetsClient {\n readonly #assetMap: AssetMap = {};\n readonly #webViewAssetMap: AssetMap = {};\n\n constructor() {\n this.#assetMap = Devvit.assets;\n this.#webViewAssetMap = Devvit.webViewAssets;\n }\n\n /**\n * Gets the public URLs for an asset.\n * @param assetPath A path, relative to the 'assets/' folder.\n * @param options\n * @returns The public URL for that asset (https://i.redd.it/<id>.<ext>)\n */\n getURL(assetPath: string): string;\n\n getURL(assetPath: string, options: GetURLOptions | undefined): string;\n\n /**\n * Gets the public URLs for multiple assets.\n * @param assetPaths An array of paths, relative to the 'assets/' folder.\n * @returns A map of each asset path to its public URL (https://i.redd.it/<id>.<ext>)\n */\n getURL(assetPaths: string[]): AssetMap;\n\n getURL(assetPaths: string[], options: GetURLOptions | undefined): AssetMap;\n /**\n * Takes one or more asset names, relative to the 'assets/' folder, and returns either the\n * public URL for that one asset, or a map of each asset name to its URL.\n * @param assetPathOrPaths - Either the path you need the public URL for, or an array of paths.\n * @param options\n * @returns Either the public URL for the one asset you asked for, or a map of assets to their URLs.\n */\n getURL(\n assetPathOrPaths: string | string[],\n options?: GetURLOptions | undefined\n ): string | AssetMap {\n if (typeof assetPathOrPaths === 'string') {\n return this.#getURL(assetPathOrPaths, options ?? { webView: false });\n }\n return this.#getURLs(assetPathOrPaths, options ?? { webView: false });\n }\n\n #getURL(assetPath: string, options: GetURLOptions): string {\n // Has the assetPath already been resolved?\n const localUrl = options.webView ? this.#webViewAssetMap[assetPath] : this.#assetMap[assetPath];\n if (localUrl) {\n return localUrl;\n }\n\n try {\n assertValidUrl(assetPath);\n // URL is valid\n return assetPath;\n } catch {\n // Not a fully qualified URL, not an asset, return an empty string\n return '';\n }\n }\n\n #getURLs(assetPaths: string[], options: GetURLOptions): AssetMap {\n const retval: Record<string, string> = {};\n let missingPaths: string[] = [];\n\n // Try and short circuit using the locally available assets list if possible, keeping a list\n // of all the paths that we couldn't find locally to ask the backend about\n const cache = options.webView ? this.#webViewAssetMap : this.#assetMap;\n if (cache) {\n for (const path of assetPaths) {\n if (cache[path]) {\n retval[path] = cache[path];\n } else {\n try {\n assertValidUrl(path);\n retval[path] = path;\n } catch {\n // invalid URL, missing from cache\n missingPaths.push(path);\n }\n }\n }\n } else {\n // No local assets - everything is missing\n missingPaths = assetPaths;\n }\n\n if (missingPaths.length > 0) {\n throw new Error(\n `The following assets were missing from the assets list: ${missingPaths.join(', ')}`\n );\n }\n\n return retval;\n }\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { KVStore } from '../../types/kvStore.js';\n\nexport class KeyValueStorage implements KVStore {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async get<T extends JSONValue = JSONValue>(key: string): Promise<T | undefined> {\n const { messages } = await Devvit.kvStorePlugin.Get({ keys: [key] }, this.#metadata);\n try {\n if (messages[key]) {\n return JSON.parse(messages[key]);\n }\n } catch {\n return undefined;\n }\n\n return undefined;\n }\n\n async put(key: string, value: JSONValue): Promise<void> {\n const messages: { [key: string]: string } = {};\n messages[key] = JSON.stringify(value);\n await Devvit.kvStorePlugin.Put({ messages }, this.#metadata);\n }\n\n async delete(key: string): Promise<void> {\n await Devvit.kvStorePlugin.Del({ keys: [key] }, this.#metadata);\n }\n\n async list(): Promise<string[]> {\n const { keys } = await Devvit.kvStorePlugin.List({ filter: '*' }, this.#metadata);\n return keys;\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { MediaAsset, MediaPlugin, UploadMediaOptions } from '../../types/media.js';\n\nexport class MediaClient implements MediaPlugin {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async upload(opts: UploadMediaOptions): Promise<MediaAsset> {\n const response = await Devvit.mediaPlugin.Upload(opts, this.#metadata);\n if (!response.mediaId) {\n throw new Error('unable to get mediaId via uploads');\n }\n return Promise.resolve({ mediaId: response.mediaId, mediaUrl: response.mediaUrl });\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { ModLog, ModLogAddOptions } from '../../types/modlog.js';\n\nexport class ModLogClient implements ModLog {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async add(options: Readonly<ModLogAddOptions>): Promise<void> {\n await Devvit.modLogPlugin.Add(options, this.#metadata);\n }\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\n\nexport class RealtimeClient {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async send(channel: string, msg: JSONValue): Promise<void> {\n // guarantee an object by wrapping msg. the key must align to useChannel().\n await Devvit.realtimePlugin.Send({ channel, data: { msg } }, this.#metadata);\n }\n}\n", "import type {\n BitfieldCommand as BitfieldCommandProto,\n HScanRequest,\n HScanResponse,\n Metadata,\n RedisAPI,\n TransactionId,\n ZMember,\n ZScanRequest,\n ZScanResponse,\n} from '@devvit/protos';\nimport { BitfieldOverflowBehavior, RedisKeyScope } from '@devvit/protos';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type {\n BitfieldCommand,\n RedisClient as RedisClientLike,\n SetOptions,\n TxClientLike,\n ZRangeOptions,\n} from '../../types/redis.js';\n\nfunction isRedisNilError(e: unknown): boolean {\n // TODO: Replace with impl in a Gatsby-only world\n //return e && e.details === 'redis: nil';\n\n if (\n e &&\n typeof e === 'object' &&\n 'message' in e &&\n e.message !== undefined &&\n typeof e.message === 'string'\n ) {\n return e.message.includes('redis: nil');\n } else {\n return false;\n }\n}\n\nexport class TxClient implements TxClientLike {\n #storage: RedisAPI;\n #transactionId: TransactionId;\n #metadata: Metadata | undefined;\n\n constructor(storage: RedisAPI, transactionId: TransactionId, metadata: Metadata) {\n this.#storage = storage;\n this.#transactionId = transactionId;\n this.#metadata = metadata;\n }\n\n async get(key: string): Promise<TxClientLike> {\n await this.#storage.Get({ key: key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async multi(): Promise<void> {\n await this.#storage.Multi(this.#transactionId, this.#metadata);\n }\n\n async set(key: string, value: string, options?: SetOptions): Promise<TxClientLike> {\n let expiration;\n if (options?.expiration) {\n expiration = Math.floor((options.expiration.getTime() - Date.now()) / 1000); // convert to seconds\n if (expiration < 1) {\n expiration = 1; // minimum expiration is 1 second, clock skew can cause issues, so let's set 1 second.\n }\n }\n await this.#storage.Set(\n {\n key,\n value,\n nx: options?.nx === true,\n xx: options?.xx === true,\n expiration: expiration || 0,\n transactionId: this.#transactionId,\n },\n this.#metadata\n );\n return this;\n }\n\n async del(...keys: string[]): Promise<TxClientLike> {\n await this.#storage.Del({ keys: keys, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async type(key: string): Promise<TxClientLike> {\n await this.#storage.Type({ key: key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async exec(): Promise<any[]> {\n const response = await this.#storage.Exec(this.#transactionId, this.#metadata);\n // eslint-disable-next-line\n let output: any[] = [];\n for (const result of response.response) {\n if (result.members) {\n output.push(result.members);\n } else if (result.nil !== undefined) {\n output.push(null);\n } else if (result.num !== undefined) {\n output.push(result.num);\n } else if (result.values !== undefined) {\n output.push(result.values.values);\n } else if (result.str !== undefined) {\n output.push(result.str);\n } else if (result.dbl !== undefined) {\n output.push(result.dbl);\n }\n }\n return output;\n }\n\n async discard(): Promise<void> {\n await this.#storage.Discard(this.#transactionId, this.#metadata);\n }\n\n async watch(...keys: string[]): Promise<TxClientLike> {\n await this.#storage.Watch({ keys: keys, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async unwatch(): Promise<TxClientLike> {\n await this.#storage.Unwatch(this.#transactionId, this.#metadata);\n return this;\n }\n\n async getRange(key: string, start: number, end: number): Promise<TxClientLike> {\n await this.#storage.GetRange(\n { key, start, end, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n async setRange(key: string, offset: number, value: string): Promise<TxClientLike> {\n await this.#storage.SetRange(\n { key, offset, value, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async strlen(key: string): Promise<TxClientLike> {\n return this.strLen(key);\n }\n\n async strLen(key: string): Promise<TxClientLike> {\n await this.#storage.Strlen({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async mget(keys: string[]): Promise<TxClientLike> {\n return this.mGet(keys);\n }\n\n async mGet(keys: string[]): Promise<TxClientLike> {\n await this.#storage.MGet({ keys, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async mset(keyValues: { [key: string]: string }): Promise<TxClientLike> {\n return this.mSet(keyValues);\n }\n\n async mSet(keyValues: { [key: string]: string }): Promise<TxClientLike> {\n const kv = Object.entries(keyValues).map(([key, value]) => ({ key, value }));\n await this.#storage.MSet({ kv, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async incrBy(key: string, value: number): Promise<TxClientLike> {\n await this.#storage.IncrBy({ key, value, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async expire(key: string, seconds: number): Promise<TxClientLike> {\n await this.#storage.Expire(\n { key, seconds, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async expireTime(key: string): Promise<TxClientLike> {\n await this.#storage.ExpireTime({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async zAdd(key: string, ...members: ZMember[]): Promise<TxClientLike> {\n await this.#storage.ZAdd({ key, members, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async zScore(key: string, member: string): Promise<TxClientLike> {\n await this.#storage.ZScore(\n { key: { key, transactionId: this.#transactionId }, member },\n this.#metadata\n );\n return this;\n }\n\n async zRank(key: string, member: string): Promise<TxClientLike> {\n await this.#storage.ZRank(\n { key: { key, transactionId: this.#transactionId }, member },\n this.#metadata\n );\n return this;\n }\n\n async zIncrBy(key: string, member: string, value: number): Promise<TxClientLike> {\n await this.#storage.ZIncrBy(\n { key, member, value, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async zScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<TxClientLike> {\n const request: ZScanRequest = {\n key,\n cursor,\n pattern,\n count,\n transactionId: this.#transactionId,\n };\n await this.#storage.ZScan(request, this.#metadata);\n return this;\n }\n\n async zCard(key: string): Promise<TxClientLike> {\n await this.#storage.ZCard({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async zRange(\n key: string,\n start: number | string,\n stop: number | string,\n options?: ZRangeOptions\n ): Promise<TxClientLike> {\n // eslint-disable-next-line\n let opts = { rev: false, byLex: false, byScore: false, offset: 0, count: 1000 };\n if (options?.reverse) {\n opts.rev = options.reverse;\n }\n if (options?.by === 'lex') {\n opts.byLex = true;\n } else if (options?.by === 'score') {\n opts.byScore = true;\n }\n if (options?.limit) {\n if (opts.byLex || opts.byScore) {\n opts.offset = options.limit.offset;\n opts.count = options.limit.count;\n } else {\n throw new Error(\n `zRange parsing error: 'limit' only allowed when 'options.by' is 'lex' or 'score'`\n );\n }\n }\n\n await this.#storage.ZRange(\n {\n key: { key: key, transactionId: this.#transactionId },\n start: start + '',\n stop: stop + '',\n ...opts,\n },\n this.#metadata\n );\n return this;\n }\n\n async zRem(key: string, members: string[]): Promise<TxClientLike> {\n await this.#storage.ZRem(\n { key: { key, transactionId: this.#transactionId }, members: members },\n this.#metadata\n );\n return this;\n }\n\n async zRemRangeByLex(key: string, min: string, max: string): Promise<TxClientLike> {\n await this.#storage.ZRemRangeByLex(\n { key: { key, transactionId: this.#transactionId }, min: min, max: max },\n this.#metadata\n );\n return this;\n }\n\n async zRemRangeByRank(key: string, start: number, stop: number): Promise<TxClientLike> {\n await this.#storage.ZRemRangeByRank(\n { key: { key, transactionId: this.#transactionId }, start: start, stop: stop },\n this.#metadata\n );\n return this;\n }\n\n async zRemRangeByScore(key: string, min: number, max: number): Promise<TxClientLike> {\n await this.#storage.ZRemRangeByScore(\n { key: { key, transactionId: this.#transactionId }, min: min, max: max },\n this.#metadata\n );\n return this;\n }\n\n async hgetall(key: string): Promise<TxClientLike> {\n return this.hGetAll(key);\n }\n\n async hGetAll(key: string): Promise<TxClientLike> {\n await this.#storage.HGetAll({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hget(key: string, field: string): Promise<TxClientLike> {\n return this.hGet(key, field);\n }\n\n async hGet(key: string, field: string): Promise<TxClientLike> {\n await this.#storage.HGet(\n { key: key, field: field, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async hMGet(key: string, fields: string[]): Promise<TxClientLike> {\n await this.#storage.HMGet(\n { key: key, fields: fields, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async hset(key: string, fieldValues: { [field: string]: string }): Promise<TxClientLike> {\n return this.hSet(key, fieldValues);\n }\n\n async hSet(key: string, fieldValues: { [field: string]: string }): Promise<TxClientLike> {\n const fv = Object.entries(fieldValues).map(([field, value]) => ({ field, value }));\n await this.#storage.HSet({ key, fv, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hincrby(key: string, field: string, value: number): Promise<TxClientLike> {\n return this.hIncrBy(key, field, value);\n }\n\n async hIncrBy(key: string, field: string, value: number): Promise<TxClientLike> {\n await this.#storage.HIncrBy(\n { key, field, value, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async hdel(key: string, fields: string[]): Promise<TxClientLike> {\n return this.hDel(key, fields);\n }\n\n async hDel(key: string, fields: string[]): Promise<TxClientLike> {\n await this.#storage.HDel({ key, fields, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hscan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<TxClientLike> {\n return this.hScan(key, cursor, pattern, count);\n }\n\n async hScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<TxClientLike> {\n const request: HScanRequest = {\n key,\n cursor,\n pattern,\n count,\n transactionId: this.#transactionId,\n };\n await this.#storage.HScan(request, this.#metadata);\n return this;\n }\n\n async hkeys(key: string): Promise<TxClientLike> {\n return this.hKeys(key);\n }\n\n async hKeys(key: string): Promise<TxClientLike> {\n await this.#storage.HKeys({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hlen(key: string): Promise<TxClientLike> {\n return this.hLen(key);\n }\n\n async hLen(key: string): Promise<TxClientLike> {\n await this.#storage.HLen({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n}\n\n/**\n * This is a subset of the overall Redis API. You should be able to look up https://redis.io/commands\n * for more details on each command.\n *\n * For the moment, we've implemented a lot of the basic string/number commands, sorted sets, and transactions.\n * This is the most powerful subset and the safest.\n */\nexport class RedisClient implements RedisClientLike {\n readonly #metadata: Metadata;\n readonly #storage: RedisAPI | undefined;\n readonly scope: RedisKeyScope;\n readonly global: Omit<RedisClientLike, 'global'>;\n\n constructor(\n metadata: Metadata,\n storage: RedisAPI | undefined = undefined,\n scope: RedisKeyScope = RedisKeyScope.INSTALLATION\n ) {\n this.#metadata = metadata;\n this.#storage = storage;\n this.scope = scope;\n this.global =\n scope === RedisKeyScope.INSTALLATION\n ? new RedisClient(this.#metadata, this.#storage, RedisKeyScope.GLOBAL)\n : this;\n }\n\n get storage(): RedisAPI {\n return this.#storage || Devvit.redisPlugin;\n }\n\n async watch(...keys: string[]): Promise<TxClientLike> {\n const txId = await this.storage.Watch({ keys }, this.#metadata);\n return new TxClient(this.storage, txId, this.#metadata);\n }\n\n async get(key: string): Promise<string | undefined> {\n try {\n const response = await this.storage.Get(\n { key, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? (response.value ?? undefined) : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async getBuffer(key: string): Promise<Buffer | undefined> {\n try {\n const response = await this.storage.GetBytes(\n { key, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? Buffer.from(response.value) : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async set(key: string, value: string, options?: SetOptions): Promise<string> {\n let expiration;\n if (options?.expiration) {\n expiration = Math.floor((options.expiration.getTime() - Date.now()) / 1000); // convert to seconds\n if (expiration < 1) {\n expiration = 1; // minimum expiration is 1 second, clock skew can cause issues, so let's set 1 second.\n }\n }\n\n const response = await this.storage.Set(\n {\n key,\n value,\n nx: options?.nx === true && !options.xx,\n xx: options?.xx === true && !options.nx,\n expiration: expiration || 0,\n scope: this.scope,\n },\n this.#metadata\n );\n return response.value;\n }\n\n async exists(...keys: string[]): Promise<number> {\n const response = await this.storage.Exists({ keys, scope: this.scope }, this.#metadata);\n return response.existingKeys;\n }\n\n async del(...keys: string[]): Promise<void> {\n await this.storage.Del({ keys, scope: this.scope }, this.#metadata);\n }\n\n async incrBy(key: string, value: number): Promise<number> {\n const response = await this.storage.IncrBy({ key, value, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async getRange(key: string, start: number, end: number): Promise<string> {\n const response = await this.storage.GetRange(\n { key, start, end, scope: this.scope },\n this.#metadata\n );\n return response !== null ? response.value : response;\n }\n\n async setRange(key: string, offset: number, value: string): Promise<number> {\n const response = await this.storage.SetRange(\n { key, offset, value, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async strlen(key: string): Promise<number> {\n return this.strLen(key);\n }\n\n async strLen(key: string): Promise<number> {\n const response = await this.storage.Strlen({ key, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async expire(key: string, seconds: number): Promise<void> {\n await this.storage.Expire({ key, seconds, scope: this.scope }, this.#metadata);\n }\n\n async expireTime(key: string): Promise<number> {\n const response = await this.storage.ExpireTime({ key, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async zAdd(key: string, ...members: ZMember[]): Promise<number> {\n return (await this.storage.ZAdd({ key, members, scope: this.scope }, this.#metadata)).value;\n }\n\n async zRange(\n key: string,\n start: number | string,\n stop: number | string,\n options?: ZRangeOptions\n ): Promise<{ member: string; score: number }[]> {\n // eslint-disable-next-line\n let opts = { rev: false, byLex: false, byScore: false, offset: 0, count: 1000 };\n if (options?.reverse) {\n opts.rev = options.reverse;\n }\n if (options?.by === 'lex') {\n opts.byLex = true;\n } else if (options?.by === 'score') {\n opts.byScore = true;\n } else {\n // LIMIT requires BYLEX/BYSCORE\n opts.offset = 0;\n opts.count = 0;\n }\n\n if (options?.limit) {\n if (opts.byLex || opts.byScore) {\n opts.offset = options.limit.offset;\n opts.count = options.limit.count;\n } else {\n throw new Error(\n `zRange parsing error: 'limit' only allowed when 'options.by' is 'lex' or 'score'`\n );\n }\n }\n\n return (\n await this.storage.ZRange(\n { key: { key: key }, start: start + '', stop: stop + '', ...opts, scope: this.scope },\n this.#metadata\n )\n ).members;\n }\n\n async zRem(key: string, members: string[]): Promise<number> {\n const response = await this.storage.ZRem(\n { key: { key }, members, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zRemRangeByLex(key: string, min: string, max: string): Promise<number> {\n const response = await this.storage.ZRemRangeByLex(\n { key: { key }, min, max, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zRemRangeByRank(key: string, start: number, stop: number): Promise<number> {\n const response = await this.storage.ZRemRangeByRank(\n { key: { key }, start, stop, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zRemRangeByScore(key: string, min: number, max: number): Promise<number> {\n const response = await this.storage.ZRemRangeByScore(\n { key: { key }, min, max, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zScore(key: string, member: string): Promise<number | undefined> {\n try {\n const response = await this.storage.ZScore(\n { key: { key }, member, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n\n return response !== null ? response.value : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async zRank(key: string, member: string): Promise<number | undefined> {\n try {\n const response = await this.storage.ZRank(\n { key: { key }, member, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? response.value : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async zIncrBy(key: string, member: string, value: number): Promise<number> {\n const response = await this.storage.ZIncrBy(\n { key, member, value, scope: this.scope },\n this.#metadata\n );\n return response !== null ? response.value : response;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n return this.mGet(keys);\n }\n\n async mGet(keys: string[]): Promise<(string | null)[]> {\n const response = await this.storage.MGet({ keys, scope: this.scope }, this.#metadata);\n return response !== null ? response.values.map((value) => value || null) : response;\n }\n\n async mset(keyValues: { [key: string]: string }): Promise<void> {\n return this.mSet(keyValues);\n }\n\n async mSet(keyValues: { [key: string]: string }): Promise<void> {\n const kv = Object.entries(keyValues).map(([key, value]) => ({ key, value }));\n await this.storage.MSet({ kv, scope: this.scope }, this.#metadata);\n }\n\n async zCard(key: string): Promise<number> {\n const response = await this.storage.ZCard({ key, scope: this.scope }, this.#metadata);\n return response !== null ? response.value : response;\n }\n\n async zScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<ZScanResponse> {\n const request: ZScanRequest = { key, cursor, pattern, count, scope: this.scope };\n return await this.storage.ZScan(request, this.#metadata);\n }\n\n async type(key: string): Promise<string> {\n const response = await this.storage.Type({ key: key, scope: this.scope }, this.#metadata);\n return response !== null ? response.value : response;\n }\n\n async rename(key: string, newKey: string): Promise<string> {\n const response = await this.storage.Rename({ key, newKey, scope: this.scope }, this.#metadata);\n return response.result;\n }\n\n async hget(key: string, field: string): Promise<string | undefined> {\n return this.hGet(key, field);\n }\n\n async hGet(key: string, field: string): Promise<string | undefined> {\n try {\n const response = await this.storage.HGet(\n { key, field, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? (response.value ?? undefined) : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async hMGet(key: string, fields: string[]): Promise<(string | null)[]> {\n const response = await this.storage.HMGet({ key, fields, scope: this.scope }, this.#metadata);\n return response !== null ? response.values.map((value) => value || null) : response;\n }\n\n async hset(key: string, fieldValues: { [field: string]: string }): Promise<number> {\n return this.hSet(key, fieldValues);\n }\n\n async hSet(key: string, fieldValues: { [field: string]: string }): Promise<number> {\n const fv = Object.entries(fieldValues).map(([field, value]) => ({ field, value }));\n const response = await this.storage.HSet({ key, fv, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async hSetNX(key: string, field: string, value: string): Promise<number> {\n const response = await this.storage.HSetNX(\n { key, field, value, scope: this.scope },\n this.#metadata\n );\n return response.success;\n }\n\n async hgetall(key: string): Promise<Record<string, string>> {\n return this.hGetAll(key);\n }\n\n async hGetAll(key: string): Promise<Record<string, string>> {\n const response = await this.storage.HGetAll({ key, scope: this.scope }, this.#metadata);\n return response !== null ? response.fieldValues : response;\n }\n\n async hdel(key: string, fields: string[]): Promise<number> {\n return this.hDel(key, fields);\n }\n\n async hDel(key: string, fields: string[]): Promise<number> {\n const response = await this.storage.HDel({ key, fields, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async hscan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<HScanResponse> {\n return this.hScan(key, cursor, pattern, count);\n }\n\n async hScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<HScanResponse> {\n const request: HScanRequest = { key, cursor, pattern, count, scope: this.scope };\n return await this.storage.HScan(request, this.#metadata);\n }\n\n async hkeys(key: string): Promise<string[]> {\n return this.hKeys(key);\n }\n\n async hKeys(key: string): Promise<string[]> {\n const response = await this.storage.HKeys({ key, scope: this.scope }, this.#metadata);\n return response !== null ? response.keys : response;\n }\n\n async hincrby(key: string, field: string, value: number): Promise<number> {\n return this.hIncrBy(key, field, value);\n }\n\n async hIncrBy(key: string, field: string, value: number): Promise<number> {\n const response = await this.storage.HIncrBy(\n { key, field, value, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async hlen(key: string): Promise<number> {\n return this.hLen(key);\n }\n\n async hLen(key: string): Promise<number> {\n const response = await this.storage.HLen({\n key,\n scope: this.scope,\n });\n return response.value;\n }\n\n async bitfield(\n key: string,\n ...cmds:\n | []\n | BitfieldCommand\n | [...BitfieldCommand, ...BitfieldCommand]\n | [...BitfieldCommand, ...BitfieldCommand, ...BitfieldCommand, ...(number | string)[]]\n ): Promise<number[]> {\n const commands: BitfieldCommandProto[] = [];\n for (let argIndex = 0; argIndex < cmds.length; ) {\n const currentArg = cmds[argIndex];\n const command: BitfieldCommandProto = {};\n\n switch (currentArg) {\n case 'get': {\n if (argIndex + 2 >= cmds.length) {\n throw Error(`bitfield command parse failed; not enough arguments for 'get' command`);\n }\n command.get = {\n encoding: cmds[argIndex + 1] as string,\n offset: cmds[argIndex + 2].toString(),\n };\n\n argIndex += 3;\n break;\n }\n case 'set': {\n if (argIndex + 3 >= cmds.length) {\n throw Error(`bitfield command parse failed; not enough arguments for 'set' command`);\n }\n command.set = {\n encoding: cmds[argIndex + 1] as string,\n offset: cmds[argIndex + 2].toString(),\n value: cmds[argIndex + 3].toString(),\n };\n\n argIndex += 4;\n break;\n }\n case 'incrBy': {\n if (argIndex + 3 >= cmds.length) {\n throw Error(`bitfield command parse failed; not enough arguments for 'incrBy' command`);\n }\n command.incrBy = {\n encoding: cmds[argIndex + 1] as string,\n offset: cmds[argIndex + 2].toString(),\n increment: cmds[argIndex + 3].toString(),\n };\n\n argIndex += 4;\n break;\n }\n case 'overflow': {\n if (argIndex + 1 >= cmds.length) {\n throw Error(\n `bitfield command parse failed; not enough arguments for 'overflow' command`\n );\n }\n const behavior = cmds[argIndex + 1].toString();\n command.overflow = {\n behavior: toBehaviorProto(behavior),\n };\n\n argIndex += 2;\n break;\n }\n default: {\n throw Error(\n `bitfield command parse failed; ${currentArg} unrecognized (must be 'get', 'set', 'incrBy', or 'overflow')`\n );\n }\n }\n commands.push(command);\n }\n\n const response = await this.storage.Bitfield({\n key,\n commands,\n });\n\n return response.results;\n }\n}\n\nfunction toBehaviorProto(behavior: string): BitfieldOverflowBehavior {\n const lowercase = behavior.toLowerCase();\n switch (lowercase) {\n case 'wrap':\n return BitfieldOverflowBehavior.BITFIELD_OVERFLOW_BEHAVIOR_WRAP;\n case 'sat':\n return BitfieldOverflowBehavior.BITFIELD_OVERFLOW_BEHAVIOR_SAT;\n case 'fail':\n return BitfieldOverflowBehavior.BITFIELD_OVERFLOW_BEHAVIOR_FAIL;\n default:\n throw Error(`unknown bitfield overflow behavior: ${lowercase}`);\n }\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type {\n ScheduledCronJob,\n ScheduledCronJobOptions,\n ScheduledJob,\n ScheduledJobOptions,\n Scheduler,\n} from '../../types/scheduler.js';\n\nexport class SchedulerClient implements Scheduler {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async runJob(job: ScheduledJobOptions | ScheduledCronJobOptions): Promise<string> {\n const response = await Devvit.schedulerPlugin.Schedule(\n {\n action: {\n type: job.name,\n data: job.data,\n },\n cron: 'cron' in job ? job.cron : undefined,\n when: 'runAt' in job ? job.runAt : undefined,\n },\n this.#metadata\n );\n\n return response.id;\n }\n\n async cancelJob(jobId: string): Promise<void> {\n await Devvit.schedulerPlugin.Cancel(\n {\n id: jobId,\n },\n this.#metadata\n );\n }\n\n async listJobs(): Promise<(ScheduledJob | ScheduledCronJob)[]> {\n const response = await Devvit.schedulerPlugin.List(\n /**\n * after and before are required for this API to work\n * so we hardcode after to Unix epoch and before to 10 years from now\n * https://reddit.atlassian.net/browse/DX-3060\n */\n {\n after: new Date(0),\n before: new Date(Date.now() + 10 * 365 * 86400 * 1000),\n },\n this.#metadata\n );\n\n return response.actions.map((action) => {\n assertNonNull(action.request?.action, 'Scheduled job is malformed');\n\n if ('when' in action.request && action.request.when != null) {\n return {\n id: action.id,\n name: action.request.action.type,\n runAt: action.request.when,\n data: action.request.action.data as JSONObject | undefined,\n };\n }\n\n return {\n id: action.id,\n name: action.request.action.type,\n cron: action.request.cron ?? '',\n data: action.request.action as unknown as JSONObject | undefined,\n };\n });\n }\n}\n", "import { FormFieldType, type FormFieldValue, type Metadata } from '@devvit/protos';\nimport type { SettingsResponse } from '@devvit/protos/types/devvit/plugin/settings/v1alpha/settings.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type {\n SettingsClient as _SettingsClient,\n SettingsFormField,\n SettingsValues,\n} from '../../types/settings.js';\nimport { flattenFormFieldValue } from '../ui/helpers/getFormValues.js';\n\nexport class SettingsClient implements _SettingsClient {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async get<T = string | number | boolean | string[] | undefined>(\n name: string\n ): Promise<T | undefined> {\n const settings = await this.getAll();\n return settings[name] as T;\n }\n\n async getAll<T extends object = SettingsValues>(): Promise<T> {\n const settingsClient = Devvit.settingsPlugin;\n\n const response = await settingsClient.GetSettings({}, this.#metadata);\n\n if (!response.installationSettings) {\n throw new Error('Could not get installation settings');\n }\n\n if (!response.appSettings) {\n throw new Error('Could not get app settings');\n }\n\n cleanAppSettings(response);\n\n return {\n ...getSettingsValues(response.installationSettings.settings, Devvit.installationSettings),\n ...getSettingsValues(response.appSettings.settings, Devvit.appSettings),\n } as T;\n }\n}\n\nexport function cleanAppSettings(response: SettingsResponse): void {\n if (!response.appSettings) {\n throw new Error('Could not get app settings');\n }\n\n // FIX: appSettings don't come back typed correctly for bools or numbers.\n // Fix that. This is a workaround. We intend to make appSettings set this\n // way entirely obsolete in the future; this is just a stopgap.\n for (const [key, value] of Object.entries(response.appSettings.settings)) {\n // Find the matching setting definition, because we can't trust the types in the response.\n const settingDefinition = Devvit.appSettings?.find((s) => s.type !== 'group' && s.name === key);\n if (!settingDefinition) {\n continue;\n }\n\n // If the setting is a boolean or number, we need to convert it from string to the correct type.\n if (\n settingDefinition.type === 'boolean' &&\n value.fieldType === FormFieldType.STRING &&\n value.boolValue == null &&\n value.stringValue != null\n ) {\n value.fieldType = FormFieldType.BOOLEAN;\n value.boolValue = Boolean(value.stringValue);\n delete value.stringValue;\n } else if (\n settingDefinition.type === 'number' &&\n value.fieldType === FormFieldType.STRING &&\n value.numberValue == null &&\n value.stringValue != null\n ) {\n value.fieldType = FormFieldType.NUMBER;\n value.numberValue = Number(value.stringValue);\n delete value.stringValue;\n }\n }\n}\n\nexport function getSettingsValues(\n results: { [key: string]: FormFieldValue },\n settingsDefinitions: SettingsFormField[] | undefined\n): SettingsValues {\n const settingsValues = Object.keys(results).reduce((acc, key) => {\n acc[key] = flattenFormFieldValue(results[key]);\n return acc;\n }, {} as SettingsValues);\n\n if (settingsDefinitions) {\n setDefaultsIfNecessary(settingsValues, settingsDefinitions);\n }\n\n return settingsValues;\n}\n\nexport function setDefaultsIfNecessary(\n settingsValues: SettingsValues,\n settingsDefinitions: SettingsFormField[]\n): void {\n for (const definition of settingsDefinitions) {\n if (definition.type === 'group') {\n // Groups get their defaults set recursively\n setDefaultsIfNecessary(settingsValues, definition.fields);\n } else {\n // Only set the default if the value is not already set - note that this checks if the key is\n // missing from the object, not if the value is falsy. So if the user somehow manually sets a\n // setting to `undefined`, we won't use the default value here. Same goes for 'defaultValue' -\n // if the user didn't set a default value, we won't set it to anything.\n if (!(definition.name in settingsValues) && 'defaultValue' in definition) {\n settingsValues[definition.name] = definition.defaultValue;\n }\n }\n }\n}\n", "import {\n type Effect,\n EffectType,\n Form,\n type Toast as ToastProto,\n ToastAppearance,\n} from '@devvit/protos';\nimport type { JSONObject, JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { BlocksReconciler } from '../../devvit/internals/blocks/BlocksReconciler.js';\nimport type { Toast } from '../../types/toast.js';\nimport type { UIClient as _UIClient } from '../../types/ui-client.js';\nimport type { WebViewUIClient } from '../../types/web-view-ui-client.js';\nimport type { Comment, Post, Subreddit, User } from '../reddit/models/index.js';\nimport { assertValidFormFields } from './helpers/assertValidFormFields.js';\nimport { transformFormFields } from './helpers/transformForm.js';\n\nexport class UIClient implements _UIClient {\n readonly #effects: Effect[] = [];\n readonly #reconciler: BlocksReconciler | undefined;\n readonly #webViewClient: WebViewUIClient;\n\n constructor(reconciler?: BlocksReconciler) {\n this.#reconciler = reconciler;\n this.#webViewClient = {\n postMessage: this.#postMessage,\n };\n }\n\n get webView(): WebViewUIClient {\n return this.#webViewClient;\n }\n\n showForm(formKey: FormKey, data?: JSONObject | undefined): void {\n let formDefinition = Devvit.formDefinitions.get(formKey);\n\n if (!formDefinition && this.#reconciler) {\n const hookForm = this.#reconciler.forms.get(formKey);\n\n if (hookForm) {\n formDefinition = {\n form: hookForm,\n onSubmit: () => {}, // no-op\n };\n }\n }\n\n if (!formDefinition) {\n throw new Error(\n 'Form does not exist. Make sure you have added it using Devvit.createForm at the root of your app.'\n );\n }\n\n const formData =\n formDefinition.form instanceof Function\n ? formDefinition.form(data ?? {})\n : formDefinition.form;\n\n const form: Form = {\n fields: [],\n id: formKey,\n title: formData.title,\n acceptLabel: formData.acceptLabel,\n cancelLabel: formData.cancelLabel,\n shortDescription: formData.description,\n };\n\n assertValidFormFields(formData.fields);\n form.fields = transformFormFields(formData.fields);\n\n this.#effects.push({\n type: EffectType.EFFECT_SHOW_FORM,\n showForm: {\n form,\n },\n });\n }\n\n showToast(text: string): void;\n showToast(toast: Toast): void;\n showToast(textOrToast: string | Toast): void {\n let toast: ToastProto;\n\n if (textOrToast instanceof Object) {\n toast = {\n text: textOrToast.text,\n appearance:\n textOrToast.appearance === 'success' ? ToastAppearance.SUCCESS : ToastAppearance.NEUTRAL,\n };\n } else {\n toast = {\n text: textOrToast,\n };\n }\n\n this.#effects.push({\n type: EffectType.EFFECT_SHOW_TOAST,\n showToast: {\n toast,\n },\n });\n }\n\n navigateTo(url: string): void;\n navigateTo(subreddit: Subreddit): void;\n navigateTo(post: Post): void;\n navigateTo(comment: Comment): void;\n navigateTo(user: User): void;\n navigateTo(thingOrUrl: string | Subreddit | Post | Comment | User): void {\n let url: string;\n\n if (typeof thingOrUrl === 'string') {\n // Validate URL\n url = new URL(thingOrUrl).toString();\n } else {\n url = new URL(thingOrUrl.permalink, 'https://www.reddit.com').toString();\n }\n this.#effects.push({\n type: EffectType.EFFECT_NAVIGATE_TO_URL,\n navigateToUrl: {\n url,\n },\n });\n }\n\n #postMessage: WebViewUIClient['postMessage'] = <T extends JSONValue>(\n webViewIdOrMessage: string | T,\n message?: T | undefined\n ): void => {\n const webViewId = message !== undefined ? (webViewIdOrMessage as string) : '';\n const msg = message !== undefined ? message : webViewIdOrMessage;\n this.#effects.push({\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n postMessage: {\n webViewId,\n app: { message: msg },\n },\n },\n });\n };\n\n /** @internal */\n get __effects(): Effect[] {\n return this.#effects;\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport type { BlocksReconciler } from '../devvit/internals/blocks/BlocksReconciler.js';\nimport { makeUseChannelHook } from '../devvit/internals/blocks/useChannel.js';\nimport { makeUseFormHook } from '../devvit/internals/blocks/useForm.js';\nimport { makeUseIntervalHook } from '../devvit/internals/blocks/useInterval.js';\nimport { makeUseStateHook } from '../devvit/internals/blocks/useState.js';\nimport { makeCache } from '../devvit/internals/cache.js';\nimport type { ContextAPIClients } from '../index.js';\nimport { AssetsClient } from './AssetsClient/AssetsClient.js';\nimport { KeyValueStorage } from './key-value-storage/KeyValueStorage.js';\nimport { MediaClient } from './media/MediaClient.js';\nimport { ModLogClient } from './modLog/ModLogClient.js';\nimport { RealtimeClient } from './realtime/RealtimeClient.js';\nimport { RedisClient } from './redis/RedisClient.js';\nimport { SchedulerClient } from './scheduler/SchedulerClient.js';\nimport { SettingsClient } from './settings/SettingsClient.js';\nimport { UIClient } from './ui/UIClient.js';\n\nexport type MakeAPIClientsOptions = {\n metadata: Metadata;\n ui?: boolean;\n hooks?: boolean;\n reconciler?: BlocksReconciler;\n};\n\nexport function makeAPIClients({\n metadata,\n ui,\n hooks,\n reconciler,\n}: MakeAPIClientsOptions): ContextAPIClients {\n const modLog = new ModLogClient(metadata);\n const kvStore = new KeyValueStorage(metadata);\n const redis = new RedisClient(metadata);\n const cache = makeCache(redis, reconciler ? reconciler.state : {});\n const scheduler = new SchedulerClient(metadata);\n const settings = new SettingsClient(metadata);\n const uiClient = ui ? new UIClient(reconciler) : undefined;\n const media = new MediaClient(metadata);\n const assets = new AssetsClient();\n const realtime = new RealtimeClient(metadata);\n const useState = hooks && reconciler ? makeUseStateHook(reconciler) : undefined;\n const useInterval = hooks && reconciler ? makeUseIntervalHook(reconciler) : undefined;\n const useForm = hooks && reconciler ? makeUseFormHook(reconciler) : undefined;\n const useChannel = hooks && reconciler ? makeUseChannelHook(reconciler) : undefined;\n\n return {\n modLog,\n kvStore,\n redis,\n scheduler,\n settings,\n media,\n assets,\n realtime,\n get useForm() {\n return (useForm ??\n (() => {\n throw new Error('useForm() is unavailable');\n })) as ContextAPIClients['useForm'];\n },\n get cache() {\n return (\n cache ??\n (() => {\n throw new Error('cache() is unavailable');\n })\n );\n },\n get useState() {\n return (\n useState ??\n (() => {\n throw new Error('useState() is unavailable');\n })\n );\n },\n get useInterval() {\n return (\n useInterval ??\n (() => {\n throw new Error('useInterval() is unavailable');\n })\n );\n },\n get useChannel() {\n return (\n useChannel ??\n (() => {\n throw new Error('useChannel() is unavailable');\n })\n );\n },\n get ui() {\n if (!uiClient) {\n throw new Error('ui client is not available');\n }\n return uiClient;\n },\n };\n}\n", "{\n \"name\": \"@devvit/public-api\",\n \"version\": \"0.12.0-dev\",\n \"license\": \"BSD-3-Clause\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://developers.reddit.com/\"\n },\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\",\n \"./package.json\": \"./package.json\",\n \"./*\": \"./dist/*\"\n },\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"dist/**\"\n ],\n \"scripts\": {\n \"build\": \"yarn build:icon-types && yarn build:semantic-colors && tsc && cp -af devvit.tsconfig.json dist/ && yarn build:types && yarn build:min && yarn build:unmin\",\n \"build:icon-types\": \"make-icons src/types/icons.ts\",\n \"build:min\": \"esbuild --bundle --sourcemap=linked --target=es2020 --format=esm --metafile=dist/meta.min.json --outfile=dist/public-api.min.js --external:@devvit/protos --minify src/index.ts\",\n \"build:semantic-colors\": \"node scripts/make-semantic-colors.js\",\n \"build:types\": \"api-extractor run && sed -ne '/declare global {/,$ p' src/devvit/Devvit.ts >> dist/public-api.d.ts\",\n \"build:unmin\": \"esbuild --bundle --sourcemap=inline --target=es2020 --format=iife --metafile=dist/meta.json --outfile=dist/public-api.iife.js --global-name=devvitPublicAPI src/index.ts\",\n \"clean\": \"rm -rf .turbo api-extractor coverage dist src/devvit/internals/semanticColors.ts src/types/icons.ts || :\",\n \"clobber\": \"yarn clean && rm -rf node_modules\",\n \"dev\": \"tsc -w\",\n \"dev:build\": \"chokidar ./src ../shared-types/dist --command 'yarn build' --ignore './src/types/icons.ts' --ignore './src/devvit/internals/semanticColors.ts'\",\n \"lint\": \"redlint .\",\n \"lint:fix\": \"yarn lint --fix\",\n \"prepublishOnly\": \"publish-package-json\",\n \"test\": \"yarn test:unit && yarn test:types && yarn test:size\",\n \"test:size\": \"filesize\",\n \"test:types\": \"tsc --noEmit\",\n \"test:unit\": \"vitest run\",\n \"test:unit-with-coverage\": \"vitest run --coverage\"\n },\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@devvit/metrics\": \"0.12.0-dev\",\n \"@devvit/protos\": \"0.12.0-dev\",\n \"@devvit/shared-types\": \"0.12.0-dev\",\n \"base64-js\": \"1.5.1\",\n \"clone-deep\": \"4.0.1\",\n \"moderndash\": \"4.0.0\"\n },\n \"devDependencies\": {\n \"@ampproject/filesize\": \"4.3.0\",\n \"@devvit/repo-tools\": \"0.12.0-dev\",\n \"@devvit/tsconfig\": \"0.12.0-dev\",\n \"@microsoft/api-extractor\": \"7.41.0\",\n \"@reddit/faceplate-ui\": \"18.0.1\",\n \"@types/clone-deep\": \"4.0.1\",\n \"@vitest/coverage-c8\": \"0.32.0\",\n \"chokidar-cli\": \"3.0.0\",\n \"esbuild\": \"0.23.0\",\n \"eslint\": \"9.11.1\",\n \"typescript\": \"5.3.2\",\n \"vitest\": \"1.6.0\"\n },\n \"publishConfig\": {\n \"directory\": \"dist\"\n },\n \"filesize\": {\n \"dist/public-api.min.js\": {\n \"gzip\": \"62 KB\",\n \"none\": \"218 KB\"\n }\n },\n \"source\": \"./src/index.ts\"\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { AppDebug } from '@devvit/shared-types/Header.js';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport type { BaseContext, ContextDebugInfo } from '../../types/context.js';\nimport pkg from '../../version.json' with { type: 'json' };\n\nexport function getContextFromMetadata(\n metadata: Metadata,\n postId?: string,\n commentId?: string\n): BaseContext {\n const subredditId = metadata[Header.Subreddit]?.values[0];\n assertNonNull<string | undefined>(subredditId, 'subreddit is missing from Context');\n\n const subredditName = metadata[Header.SubredditName]?.values[0];\n\n // devvit-app-user is only available in the remote runtime.\n const appAccountId = metadata[Header.AppUser]?.values[0];\n const appName = metadata[Header.App]?.values[0];\n const appVersion = metadata[Header.Version]?.values[0];\n\n const userId = metadata[Header.User]?.values[0];\n const debug = parseDebug(metadata);\n\n return {\n get appAccountId() {\n assertNonNull<string | undefined>(appAccountId, 'appAccountId is missing from Context');\n return appAccountId;\n },\n subredditId,\n subredditName,\n userId,\n postId,\n commentId,\n appName,\n appVersion,\n debug,\n metadata,\n toJSON() {\n return {\n appAccountId,\n appName,\n appVersion,\n subredditId,\n subredditName,\n userId,\n postId,\n commentId,\n debug,\n metadata,\n };\n },\n };\n}\n\n/** @internal */\nexport function parseDebug(meta: Readonly<Metadata>): ContextDebugInfo {\n // Roughly aligns to initDevvitGlobal() except an empty devvit-debug doesn't\n // enable all.\n\n // All known AppDebug keys.\n const keyset: { readonly [key in AppDebug]: undefined } = {\n blocks: undefined,\n emitSnapshots: undefined,\n emitState: undefined,\n realtime: undefined,\n runtime: undefined,\n surface: undefined,\n useAsync: undefined,\n payments: undefined,\n bootstrap: undefined,\n webView: undefined,\n };\n // {[key: Lowercase<AppDebug>]: AppDebug}\n const lowerKeyToKey: { [lower: string]: string } = {};\n for (const key in keyset) lowerKeyToKey[key.toLowerCase()] = key;\n\n const debug: { [key in AppDebug]?: string } = {};\n // hack: gRPC-web header values don't split in compute. always join then split\n // for parity in both local and remote runtimes.\n for (const kv of (meta[Header.Debug]?.values ?? []).join().split(',')) {\n let [k, v] = kv.split('=');\n if (!k) continue;\n\n k = k.trim();\n v = v?.trim();\n\n if (k.toLowerCase() in lowerKeyToKey) k = lowerKeyToKey[k.toLowerCase()];\n\n debug[k as AppDebug] ??= v || `${true}`;\n }\n\n // @devvit/public-api v1.2.3 emitSnapshots=true foo=bar\n if (meta[Header.Debug])\n console.info(\n `[api] @devvit/public-api v${pkg.version} ${Object.entries(debug)\n .map(([k, v]) => `${k}=${v}`)\n .join(' ')}`\n );\n\n return { ...debug };\n}\n", "import type { Metadata, RenderPostRequest } from '@devvit/protos';\nimport { CustomPostDefinition, RenderPostResponse } from '@devvit/protos';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { Devvit } from '../Devvit.js';\nimport { BlocksReconciler } from './blocks/BlocksReconciler.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nasync function renderPost(\n req: RenderPostRequest,\n metadata: Metadata\n): Promise<DeepPartial<RenderPostResponse>> {\n const customPostType = Devvit.customPostType;\n\n if (!customPostType) {\n throw new Error('Custom post type not registered');\n }\n\n const reconciler = new BlocksReconciler(\n (_props, context) => customPostType.render(context),\n req.blocks,\n req.state,\n metadata,\n req.dimensions\n );\n\n const blocksUI = await reconciler.render();\n\n return RenderPostResponse.fromJSON({\n state: reconciler.state,\n blocks: {\n ui: blocksUI,\n },\n effects: reconciler.getEffects(),\n });\n}\n\nexport function registerCustomPost(config: Config): void {\n config.provides(CustomPostDefinition);\n extendDevvitPrototype('RenderPost', renderPost);\n}\n", "import type {\n Block,\n BlockRenderRequest,\n Dimensions,\n Effect,\n FormSubmittedEvent,\n Metadata,\n RealtimeSubscriptionEvent,\n UIEvent,\n} from '@devvit/protos';\nimport { BlockRenderEventType, EffectType } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { JSONObject, PartialJSONObject } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { AssetsClient } from '../../../apis/AssetsClient/AssetsClient.js';\nimport { makeAPIClients } from '../../../apis/makeAPIClients.js';\nimport type { ModLogClient } from '../../../apis/modLog/ModLogClient.js';\nimport type { RealtimeClient } from '../../../apis/realtime/RealtimeClient.js';\nimport { getEffectsFromUIClient } from '../../../apis/ui/helpers/getEffectsFromUIClient.js';\nimport type {\n Form,\n FormFunction,\n MediaPlugin,\n Scheduler,\n SettingsClient,\n UIClient,\n UseChannelHook,\n UseFormHook,\n UseFormHookState,\n UseIntervalHook,\n UseIntervalHookState,\n UseStateHook,\n} from '../../../types/index.js';\nimport type { KVStore } from '../../../types/kvStore.js';\nimport type { RedisClient } from '../../../types/redis.js';\nimport type { BlockElement } from '../../Devvit.js';\nimport { Devvit } from '../../Devvit.js';\nimport type { CacheHelper } from '../cache.js';\nimport { getContextFromMetadata } from '../context.js';\nimport { makeUniqueIdGenerator } from '../helpers/makeUniqueIdGenerator.js';\nimport type { LocalCache } from '../promise_cache.js';\nimport { BlocksTransformer } from './BlocksTransformer.js';\nimport type { EffectEmitter } from './EffectEmitter.js';\n\nexport type ReifiedBlockElement = {\n type: string;\n\n props: { [key: string]: unknown } | undefined;\n children: ReifiedBlockElementOrLiteral[];\n};\n\nexport type ReifiedBlockElementOrLiteral = ReifiedBlockElement | string;\n\n/** Serializable. */\ntype ComponentState = {\n [hookIndex: number]: UseIntervalHookState | UseFormHookState | JSONObject;\n};\n\n/** Serializable. */\ntype RenderState = {\n [componentKey: string]: ComponentState;\n};\n\nexport function assertNotString(\n reified: ReifiedBlockElementOrLiteral\n): asserts reified is ReifiedBlockElement {\n if (typeof reified === 'string') {\n throw new Error('Root element cannot be a string');\n }\n}\n\n// FIXME: don't build XML with a string concatenation :facepalm:\nconst toXML = (node: ReifiedBlockElement): string => {\n let xml = '';\n\n const attributes = node.props\n ? Object.keys(node.props)\n .map((key) => (key === 'onPress' ? '' : `${key}=\"${node.props![key]}\"`))\n .join(' ')\n : '';\n\n xml += `<${node.type}${attributes ? ' ' + attributes : ''}>`;\n\n for (const child of node.children) {\n if (typeof child === 'string') {\n xml += child;\n } else {\n xml += toXML(child);\n }\n }\n\n xml += `</${node.type}>`;\n\n return xml;\n};\n\nfunction getIndentation(level: number): string {\n return ' '.repeat(level);\n}\n\n// FIXME: don't build XML with a string concatenation :facepalm:\nfunction indentXML(xml: string): string {\n let formatted = '';\n let indentLevel = 0;\n const xmlArr = xml.split(/>\\s*</);\n for (let i = 0; i < xmlArr.length; i++) {\n let node = xmlArr[i];\n if (i === 0) {\n node = node.trim();\n }\n if (i === xmlArr.length - 1) {\n node = node.trim();\n }\n const isClosingTag = node.charAt(0) === '/';\n if (isClosingTag) {\n indentLevel--;\n }\n formatted += getIndentation(indentLevel);\n if (i !== 0) {\n formatted += '<';\n }\n formatted += node;\n if (i !== xmlArr.length - 1) {\n formatted += '>\\n';\n }\n if (!isClosingTag && node.indexOf('</') === -1 && node.charAt(node.length - 1) !== '/') {\n indentLevel++;\n }\n }\n return formatted;\n}\n\n/**\n * @internal\n * An instance of this class should be instantiated for each OnRender call.\n * This class is responsible for:\n * - rendering JSX elements into Blocks.\n * - managing state and hooks for each component.\n * - drilling the shared clients into function components.\n */\nexport class BlocksReconciler implements EffectEmitter {\n component: JSX.ComponentFunction;\n event: BlockRenderRequest | UIEvent | undefined;\n state: {\n __renderState: RenderState;\n __postData?: { thingId?: string };\n __realtimeChannels?: string[];\n __cache?: LocalCache;\n };\n metadata: Metadata;\n\n // Common clients for props\n modLog: ModLogClient;\n kvStore: KVStore;\n cache: CacheHelper;\n redis: RedisClient;\n scheduler: Scheduler;\n dimensions?: Dimensions;\n ui: UIClient;\n settings: SettingsClient;\n media: MediaPlugin;\n assets: AssetsClient;\n realtime: RealtimeClient;\n hooks: {\n useState: UseStateHook;\n useInterval: UseIntervalHook;\n useForm: UseFormHook;\n useChannel: UseChannelHook;\n };\n\n emitEffect(_dedupeKey: string, effect: Effect): void {\n this.effects.push(effect);\n }\n\n // hook management\n renderState: RenderState = {};\n currentComponentKey: string[] = [];\n currentHookIndex: number = 0;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n actions: Map<string, Function> = new Map();\n forms: Map<FormKey, Form | FormFunction> = new Map();\n realtimeChannels: string[] = [];\n realtimeUpdated: boolean = false;\n pendingHooks: (() => Promise<void>)[] = [];\n\n isRendering: boolean = false;\n transformer: BlocksTransformer = new BlocksTransformer(() => this.assets);\n\n effects: Effect[] = [];\n\n constructor(\n component: JSX.ComponentFunction,\n event: BlockRenderRequest | UIEvent | undefined,\n state: PartialJSONObject | undefined,\n metadata: Metadata,\n dimensions: Dimensions | undefined\n ) {\n this.component = component;\n this.event = event;\n this.state = {\n __renderState: {},\n ...state,\n };\n this.metadata = metadata;\n if (this.state.__realtimeChannels) {\n this.realtimeChannels = this.state.__realtimeChannels;\n }\n\n const apiClients = makeAPIClients({\n reconciler: this,\n hooks: true,\n ui: true,\n metadata,\n });\n this.cache = apiClients.cache;\n this.modLog = apiClients.modLog;\n this.kvStore = apiClients.kvStore;\n this.redis = apiClients.redis;\n this.settings = apiClients.settings;\n this.scheduler = apiClients.scheduler;\n this.media = apiClients.media;\n this.assets = apiClients.assets;\n this.realtime = apiClients.realtime;\n this.dimensions = dimensions;\n this.ui = apiClients.ui;\n this.hooks = {\n useState: apiClients.useState,\n useInterval: apiClients.useInterval,\n useForm: apiClients.useForm,\n useChannel: apiClients.useChannel,\n };\n }\n\n #reset(): void {\n this.actions.clear();\n this.currentComponentKey = [];\n this.currentHookIndex = 0;\n this.pendingHooks = [];\n this.realtimeChannels = [];\n this.realtimeUpdated = false;\n }\n\n // This return type is an absolute mess here. Let this slide.\n\n #makeContextProps(): Devvit.Context {\n // skip typechecks for useForm which is templatized.\n const props: Omit<Devvit.Context, 'useForm'> = {\n ...getContextFromMetadata(this.metadata, this.state.__postData?.thingId),\n modLog: this.modLog,\n cache: this.cache,\n kvStore: this.kvStore,\n redis: this.redis,\n settings: this.settings,\n scheduler: this.scheduler,\n media: this.media,\n assets: this.assets,\n realtime: this.realtime,\n ui: this.ui,\n dimensions: this.dimensions,\n uiEnvironment: {\n timezone: this.metadata[Header.Timezone]?.values[0],\n locale: this.metadata[Header.Language]?.values[0],\n dimensions: this.dimensions,\n },\n ...this.hooks,\n };\n props.debug.effects = this;\n return props as Devvit.Context;\n }\n\n async render(): Promise<Block> {\n await this.reconcile();\n\n this.isRendering = true;\n const results = await this.buildBlocksUI();\n this.isRendering = false;\n\n return results;\n }\n\n makeUniqueActionID(id: string): string {\n let uniqueId = id;\n let counter = 1;\n while (this.actions.has(uniqueId)) {\n uniqueId = `${id}.${counter++}`;\n }\n return uniqueId;\n }\n\n async reconcile(): Promise<void> {\n const ctx = this.#makeContextProps();\n const blockElement: BlockElement = {\n type: this.component,\n props: ctx,\n children: [],\n };\n\n const reified = await this.processBlock(blockElement);\n assertNotString(reified);\n\n this.transformer.createBlocksElementOrThrow(reified);\n\n if (this.isUserActionRender && this.blockRenderEventId) {\n const handler = this.actions.get(this.blockRenderEventId);\n if (handler) {\n await handler((this.event as BlockRenderRequest).data);\n }\n }\n\n for (const hook of this.pendingHooks) {\n await hook();\n }\n\n this.buildNextState();\n this.#reset();\n }\n\n async buildBlocksUI(): Promise<Block> {\n const ctx = this.#makeContextProps();\n const rootBlockElement: BlockElement = {\n type: this.component,\n props: ctx,\n children: [],\n };\n\n const block = await this.renderElement(ctx, rootBlockElement);\n return this.transformer.ensureRootBlock(block);\n }\n\n async renderElement(ctx: Devvit.Context, element: JSX.Element): Promise<Block> {\n const reified = await this.processBlock(element);\n assertNotString(reified);\n\n if (Devvit.debug.emitSnapshots || ctx.debug.emitSnapshots)\n console.debug(indentXML(toXML(reified)));\n if (Devvit.debug.emitState || ctx.debug.emitState)\n console.debug(JSON.stringify(this.state, undefined, 2));\n\n return this.transformer.createBlocksElementOrThrow(reified);\n }\n\n #flatten(arr: ReifiedBlockElementOrLiteral[]): ReifiedBlockElementOrLiteral[] {\n const out: ReifiedBlockElementOrLiteral[] = [];\n for (const child of arr) {\n if (typeof child === 'string') {\n out.push(child);\n } else if (child.type === '__fragment') {\n out.push(...this.#flatten(child.children));\n } else {\n out.push(child);\n }\n }\n return out;\n }\n\n async processProps(block: BlockElement): Promise<void> {\n const props = block.props ?? {};\n const actionHandlers = ['onPress', 'onMessage'];\n for (const action of actionHandlers) {\n if (action in props) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n const handler = props[action] as Function;\n const name = handler?.name;\n const id = this.makeUniqueActionID(`${block.type}.${name ?? action}`);\n this.actions.set(id, handler);\n props[action] = id;\n }\n }\n }\n\n async processBlock(\n element: JSX.Element | JSX.Element[],\n idGenerator: (id: string) => string = makeUniqueIdGenerator(),\n path: string[] = []\n ): Promise<ReifiedBlockElementOrLiteral> {\n // Strings - return as is\n if (typeof element === 'string') {\n return element;\n }\n\n // Numbers - transform to string\n if (typeof element === 'number') {\n return `${element}`;\n }\n\n const blockElement = element as BlockElement;\n\n // Intrinsic elements\n if (typeof blockElement.type === 'string') {\n const childrens: ReifiedBlockElementOrLiteral[] = [];\n\n /**\n * Resist the urge to clean this up with flatMap. We need to await each processBlock _in order_.\n *\n * Number of times this has caused a major headache: 3\n *\n * Please increment the counter above if you had to revert this.\n */\n for (let i = 0; i < blockElement.children.length; i++) {\n const child = blockElement.children[i];\n if (child === undefined || child === null) continue;\n childrens.push(\n await this.processBlock(child, idGenerator, [\n ...path,\n `${blockElement.type as string}.${i}`,\n ])\n );\n }\n\n const children = childrens.flat();\n const collapsedChildren = this.#flatten(children);\n\n await this.processProps(blockElement);\n\n return {\n type: blockElement.type,\n props: blockElement.props,\n children: collapsedChildren,\n };\n }\n\n // Function components\n if (typeof blockElement.type === 'function') {\n // Create a unique component key to keep track of its state/hooks.\n const componentKey = idGenerator(\n `${path.length ? path.join('.') : 'root'}.${\n blockElement.type.name.length ? blockElement.type.name : 'anonymous'\n }`\n );\n this.currentComponentKey.push(componentKey);\n if (!this.renderState[componentKey]) {\n this.renderState[componentKey] = {};\n }\n\n const children = blockElement.children.flatMap((c) => c);\n\n const ctx = this.#makeContextProps();\n const props = {\n ...ctx,\n ...blockElement.props,\n children,\n };\n\n let result: JSX.Element | undefined;\n while (result === undefined) {\n try {\n result = await blockElement.type(props, ctx);\n } catch (promiseOrError) {\n // If the component throws a promise, wait for it to resolve and try again.\n // This is for components that have async logic in their hook callbacks.\n if (promiseOrError instanceof Promise) {\n result = await promiseOrError;\n } else {\n throw promiseOrError;\n }\n }\n }\n\n // Ensure that the number of hooks are same from the previous render.\n if (!this.isInitialRender) {\n const previousState = this.getPreviousComponentState();\n /**\n * Note: This relies on the magic of {0: \"a\", 1: \"b\"} => [\"a\", \"b\"] and back under many circumstances.\n * It is too magic, but as this component is complicated and deprecated, it is not worth fixing.\n *\n * In fact previousState is an array. This is confusing.\n */\n const prevHookCount = Object.keys(previousState).length;\n if (prevHookCount !== this.currentHookIndex) {\n throw new Error(\n 'Invalid hook call. Hooks can only be called at the top-level of a function component. Make sure that you are not calling hooks inside loops, conditions, or nested functions.'\n );\n }\n }\n\n // Remember to always reset the current component key and hook index.\n this.currentComponentKey.pop();\n this.currentHookIndex = 0;\n\n if (typeof result === 'object') {\n return this.processBlock(result, idGenerator, [...path, blockElement.type.name]);\n }\n }\n\n let children: JSX.Children[] = [];\n let pathPrefix = '';\n\n if (Array.isArray(blockElement)) {\n // Array of elements\n children = blockElement;\n } else if (typeof blockElement.type === 'undefined' && blockElement.children) {\n // Fragment\n children = blockElement.children;\n pathPrefix = 'fragmentChild.';\n }\n return {\n type: '__fragment',\n props: undefined,\n children: await Promise.all(\n children.flatMap(\n async (child, i) =>\n await this.processBlock(child, idGenerator, [...path, `${pathPrefix}${i}`])\n )\n ),\n };\n }\n\n getCurrentComponentKey(): string[] {\n if (!this.currentComponentKey.at(-1)) {\n throw new Error('Current component key is missing');\n }\n\n return this.currentComponentKey;\n }\n\n getCurrentComponentState<S>(): { [hookIndex: number]: S } {\n const componentKey = this.currentComponentKey.at(-1);\n if (!componentKey) {\n throw new Error('Current component key is missing');\n }\n return this.renderState[componentKey] as { [hookIndex: number]: S };\n }\n\n getPreviousComponentState<S>(): { [hookIndex: number]: S } {\n const componentKey = this.currentComponentKey.at(-1);\n if (!componentKey) {\n throw new Error('Current component key is missing');\n }\n\n if (!this.state.__renderState[componentKey]) {\n this.state.__renderState[componentKey] = {};\n }\n\n return this.state.__renderState[componentKey] as { [hookIndex: number]: S };\n }\n\n get isInitialRender(): boolean {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).type === BlockRenderEventType.RENDER_INITIAL;\n }\n\n get isUserActionRender(): boolean {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).type === BlockRenderEventType.RENDER_USER_ACTION;\n }\n\n get isEffectRender(): boolean {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).type === BlockRenderEventType.RENDER_EFFECT_EVENT;\n }\n\n get formSubmittedEvent(): FormSubmittedEvent | undefined | false {\n if (!this.event) {\n return false;\n }\n\n return (this.event as UIEvent).formSubmitted;\n }\n\n get blockRenderEventId(): string | undefined | false {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).id;\n }\n\n get realtimeEvent(): RealtimeSubscriptionEvent | undefined | false {\n if (!this.event) {\n return false;\n }\n\n const realtimeEvent = (this.event as UIEvent).realtimeEvent;\n if (!realtimeEvent?.event?.channel) {\n return false;\n }\n\n return realtimeEvent;\n }\n\n runHook(hook: () => Promise<void>): void {\n this.pendingHooks.push(hook);\n }\n\n #rerenderAlreadyScheduled = false;\n\n rerenderIn(delayMs: number): void {\n if (this.#rerenderAlreadyScheduled) {\n return;\n }\n this.#rerenderAlreadyScheduled = true;\n this.effects.push({\n type: EffectType.EFFECT_RERENDER_UI,\n rerenderUi: {\n delaySeconds: delayMs / 1000,\n },\n });\n }\n\n addRealtimeChannel(channel: string): void {\n this.realtimeChannels.push(channel);\n this.realtimeUpdated = true;\n }\n\n removeRealtimeChannel(channel: string): void {\n this.realtimeChannels = this.realtimeChannels.filter((c) => c !== channel);\n this.realtimeUpdated = true;\n }\n\n get realtimeEffect(): Effect[] {\n if (this.realtimeUpdated && this.realtimeChannels.length > 0) {\n return [\n {\n type: EffectType.EFFECT_REALTIME_SUB,\n realtimeSubscriptions: {\n subscriptionIds: this.realtimeChannels,\n },\n },\n ];\n } else {\n return [];\n }\n }\n\n getEffects(): Effect[] {\n return [...getEffectsFromUIClient(this.ui), ...this.effects, ...this.realtimeEffect];\n }\n\n buildNextState(): void {\n // Flatten the renderStates down to arrays to ensure `undefined` values are maintained through serialization.\n // Input:\n // {\"0\": \"one\", \"1\": undefined, \"2\": \"three\"} (length: 3)\n // Before:\n // {\"0\": \"one\", \"2\": \"three\"} (length: 2; fails consistency check, see line 251)\n // After:\n // [\"one\", undefined, \"three\"] (length: 3)\n /**\n * Note: This relies on the magic of {0: \"a\", 1: \"b\"} => [\"a\", \"b\"] and back under many circumstances.\n * It is too magic, but as this component is complicated and deprecated, it is not worth fixing.\n */\n for (const key of Object.keys(this.renderState)) {\n this.renderState[key] = Object.values(this.renderState[key]);\n }\n this.state = {\n __postData: this.state.__postData,\n __renderState: this.renderState,\n __cache: this.state.__cache,\n ...(this.realtimeChannels.length > 0 ? { __realtimeChannels: this.realtimeChannels } : {}),\n };\n }\n}\n", "import type { Effect } from '@devvit/protos';\n\nimport type { UIClient as UIClientType } from '../../../types/ui-client.js';\nimport type { UIClient } from '../UIClient.js';\n\n/** A simple helper to cast ui into the class instance to get the effects */\nexport function getEffectsFromUIClient(ui: UIClient | UIClientType): Effect[] {\n return (ui as UIClient).__effects;\n}\n", "export function makeUniqueIdGenerator(): (id: string) => string {\n const seenActionIds = new Set<string>();\n return (id: string) => {\n let uniqueId = id;\n let counter = 1;\n while (seenActionIds.has(uniqueId)) {\n uniqueId = `${id}.${counter++}`;\n }\n seenActionIds.add(uniqueId);\n return uniqueId;\n };\n}\n", "import type {\n Block,\n BlockAction,\n BlockAlignment,\n BlockBorder,\n BlockColor,\n BlockConfig,\n} from '@devvit/protos';\nimport {\n BlockActionType,\n BlockAvatarBackground,\n BlockAvatarFacing,\n BlockAvatarSize,\n BlockBorderWidth,\n BlockButtonAppearance,\n BlockButtonSize,\n BlockGap,\n BlockHorizontalAlignment,\n BlockIconSize,\n BlockImageResizeMode,\n BlockPadding,\n BlockRadius,\n BlockSpacerShape,\n BlockSpacerSize,\n BlockStackDirection,\n BlockTextOutline,\n BlockTextOverflow,\n BlockTextSize,\n BlockTextStyle,\n BlockTextWeight,\n BlockType,\n BlockVerticalAlignment,\n} from '@devvit/protos';\n\nimport type { AssetsClient, GetURLOptions } from '../../../apis/AssetsClient/AssetsClient.js';\nimport { Devvit } from '../../Devvit.js';\nimport {\n getHexFromNamedHTMLColor,\n getHexFromRgbaColor,\n getHexFromRPLColor,\n isHexColor,\n isHslColor,\n isNamedHTMLColor,\n isRgbaColor,\n isRPLColor,\n} from '../helpers/color.js';\nimport type { ReifiedBlockElement, ReifiedBlockElementOrLiteral } from './BlocksReconciler.js';\nimport type { TransformContext } from './transformContext.js';\nimport { makeStackDimensionsDetails, ROOT_STACK_TRANSFORM_CONTEXT } from './transformContext.js';\nimport { makeBlockSizes } from './transformerUtils.js';\n\ntype DataSet = Record<string, unknown>;\nconst DATA_PREFIX = 'data-';\n\nconst ACTION_HANDLERS: Set<Devvit.Blocks.ActionHandlers> = new Set(['onPress', 'onMessage']);\nconst ACTION_TYPES: Map<Devvit.Blocks.ActionHandlers, BlockActionType> = new Map([\n ['onPress', BlockActionType.ACTION_CLICK],\n ['onMessage', BlockActionType.ACTION_WEBVIEW],\n]);\n\nexport class BlocksTransformer {\n readonly #assetsClient: () => AssetsClient | undefined;\n\n constructor(getAssetsClient: () => AssetsClient | undefined = () => undefined) {\n this.#assetsClient = getAssetsClient;\n }\n\n createBlocksElementOrThrow({ type, props, children }: ReifiedBlockElement): Block {\n const block = this.createBlocksElement({ type, props, children }, ROOT_STACK_TRANSFORM_CONTEXT);\n if (!block) {\n throw new Error(`Could not create block of type ${type}`);\n }\n return block;\n }\n\n createBlocksElement(\n { type, props, children }: ReifiedBlockElement,\n transformContext: TransformContext\n ): Block | undefined {\n switch (type) {\n case 'blocks':\n return this.makeRoot(props, ...children);\n case 'hstack':\n return this.makeHStack(props, transformContext, ...children);\n case 'vstack':\n return this.makeVStack(props, transformContext, ...children);\n case 'zstack':\n return this.makeZStack(props, transformContext, ...children);\n case 'text':\n return this.makeText(props, transformContext, ...children);\n case 'button':\n return this.makeButton(props, transformContext, ...children);\n case 'image':\n return this.makeImage(props as Devvit.Blocks.ImageProps, transformContext);\n case 'spacer':\n return this.makeSpacer(props, transformContext);\n case 'icon':\n return this.makeIcon(props as Devvit.Blocks.IconProps, transformContext);\n case 'avatar':\n return this.makeAvatar(props as Devvit.Blocks.AvatarProps, transformContext);\n case 'webview':\n return this.makeWebView(props as Devvit.Blocks.WebViewProps, transformContext);\n case '__fragment':\n throw new Error(\"root fragment is not supported - use 'blocks' instead\");\n }\n return undefined;\n }\n\n makeRootHeight(height: Devvit.Blocks.RootHeight): number {\n switch (height) {\n case 'regular':\n return 320;\n case 'tall':\n return 512;\n }\n }\n\n makeBlockPadding(padding: Devvit.Blocks.ContainerPadding | undefined): BlockPadding | undefined {\n switch (padding) {\n case 'none':\n return BlockPadding.PADDING_NONE;\n case 'xsmall':\n return BlockPadding.PADDING_XSMALL;\n case 'small':\n return BlockPadding.PADDING_SMALL;\n case 'medium':\n return BlockPadding.PADDING_MEDIUM;\n case 'large':\n return BlockPadding.PADDING_LARGE;\n }\n return undefined;\n }\n\n makeBlockRadius(\n radius: Devvit.Blocks.ContainerCornerRadius | undefined\n ): BlockRadius | undefined {\n switch (radius) {\n case 'none':\n return BlockRadius.RADIUS_NONE;\n case 'small':\n return BlockRadius.RADIUS_SMALL;\n case 'medium':\n return BlockRadius.RADIUS_MEDIUM;\n case 'large':\n return BlockRadius.RADIUS_LARGE;\n case 'full':\n return BlockRadius.RADIUS_FULL;\n }\n return undefined;\n }\n\n makeBlockGap(gap: Devvit.Blocks.ContainerGap | undefined): BlockGap | undefined {\n switch (gap) {\n case 'none':\n return BlockGap.GAP_NONE;\n case 'small':\n return BlockGap.GAP_SMALL;\n case 'medium':\n return BlockGap.GAP_MEDIUM;\n case 'large':\n return BlockGap.GAP_LARGE;\n }\n return undefined;\n }\n\n makeBlockAlignment(alignment: Devvit.Blocks.Alignment | undefined): BlockAlignment | undefined {\n if (alignment === undefined) return undefined;\n let vertical: BlockVerticalAlignment | undefined = undefined;\n let horizontal: BlockHorizontalAlignment | undefined = undefined;\n if (alignment.includes('top')) {\n vertical = BlockVerticalAlignment.ALIGN_TOP;\n } else if (alignment.includes('middle')) {\n vertical = BlockVerticalAlignment.ALIGN_MIDDLE;\n } else if (alignment.includes('bottom')) {\n vertical = BlockVerticalAlignment.ALIGN_BOTTOM;\n }\n if (alignment.includes('start')) {\n horizontal = BlockHorizontalAlignment.ALIGN_START;\n } else if (alignment.includes('center')) {\n horizontal = BlockHorizontalAlignment.ALIGN_CENTER;\n } else if (alignment.includes('end')) {\n horizontal = BlockHorizontalAlignment.ALIGN_END;\n }\n if (vertical !== undefined || horizontal !== undefined) {\n return {\n vertical,\n horizontal,\n };\n }\n return undefined;\n }\n\n makeBlockBorder(\n borderWidth: Devvit.Blocks.ContainerBorderWidth | undefined,\n color: string | undefined,\n lightColor: string | undefined,\n darkColor: string | undefined\n ): BlockBorder | undefined {\n if (!borderWidth && !color) return undefined;\n\n let width: BlockBorderWidth | undefined = undefined;\n switch (borderWidth) {\n case 'none':\n width = BlockBorderWidth.BORDER_WIDTH_NONE;\n break;\n case 'thin':\n width = BlockBorderWidth.BORDER_WIDTH_THIN;\n break;\n case 'thick':\n width = BlockBorderWidth.BORDER_WIDTH_THICK;\n break;\n default:\n // Default to a thin border when a color was set, but no borderWidth.\n width = BlockBorderWidth.BORDER_WIDTH_THIN;\n break;\n }\n\n // Default to #00000019 when a border was set, but no color.\n const borderColor = color ?? 'neutral-border-weak';\n const colors = this.getThemedColors(borderColor, lightColor, darkColor);\n\n return {\n width,\n color: colors?.light,\n colors,\n };\n }\n\n makeBlockTextSize(textSize: Devvit.Blocks.TextSize | undefined): BlockTextSize | undefined {\n switch (textSize) {\n case 'xsmall':\n return BlockTextSize.TEXT_SIZE_XSMALL;\n case 'small':\n return BlockTextSize.TEXT_SIZE_SMALL;\n case 'medium':\n return BlockTextSize.TEXT_SIZE_MEDIUM;\n case 'large':\n return BlockTextSize.TEXT_SIZE_LARGE;\n case 'xlarge':\n return BlockTextSize.TEXT_SIZE_XLARGE;\n case 'xxlarge':\n return BlockTextSize.TEXT_SIZE_XXLARGE;\n }\n return undefined;\n }\n\n makeBlockTextStyle(style: Devvit.Blocks.TextStyle | undefined): BlockTextStyle | undefined {\n switch (style) {\n case 'body':\n return BlockTextStyle.TEXT_STYLE_BODY;\n case 'metadata':\n return BlockTextStyle.TEXT_STYLE_METADATA;\n case 'heading':\n return BlockTextStyle.TEXT_STYLE_HEADING;\n }\n return undefined;\n }\n\n makeBlockTextOutline(\n outline: Devvit.Blocks.TextOutline | undefined\n ): BlockTextOutline | undefined {\n switch (outline) {\n case 'none':\n return BlockTextOutline.TEXT_OUTLINE_NONE;\n case 'thin':\n return BlockTextOutline.TEXT_OUTLINE_THIN;\n case 'thick':\n return BlockTextOutline.TEXT_OUTLINE_THICK;\n }\n return undefined;\n }\n\n makeBlockTextWeight(weight: Devvit.Blocks.TextWeight | undefined): BlockTextWeight | undefined {\n switch (weight) {\n case 'regular':\n return BlockTextWeight.TEXT_WEIGHT_REGULAR;\n case 'bold':\n return BlockTextWeight.TEXT_WEIGHT_BOLD;\n }\n return undefined;\n }\n\n makeBlockTextOverflow(\n overflow: Devvit.Blocks.TextOverflow | undefined\n ): BlockTextOverflow | undefined {\n switch (overflow) {\n case 'clip':\n return BlockTextOverflow.TEXT_OVERFLOW_CLIP;\n case 'ellipsis':\n return BlockTextOverflow.TEXT_OVERFLOW_ELLIPSE;\n }\n return BlockTextOverflow.TEXT_OVERFLOW_ELLIPSE;\n }\n\n makeBlockButtonAppearance(\n appearance: Devvit.Blocks.ButtonAppearance | undefined\n ): BlockButtonAppearance | undefined {\n switch (appearance) {\n case 'secondary':\n return BlockButtonAppearance.BUTTON_APPEARANCE_SECONDARY;\n case 'primary':\n return BlockButtonAppearance.BUTTON_APPEARANCE_PRIMARY;\n case 'plain':\n return BlockButtonAppearance.BUTTON_APPEARANCE_PLAIN;\n case 'bordered':\n return BlockButtonAppearance.BUTTON_APPEARANCE_BORDERED;\n case 'media':\n return BlockButtonAppearance.BUTTON_APPEARANCE_MEDIA;\n case 'destructive':\n return BlockButtonAppearance.BUTTON_APPEARANCE_DESTRUCTIVE;\n case 'caution':\n return BlockButtonAppearance.BUTTON_APPEARANCE_CAUTION;\n case 'success':\n return BlockButtonAppearance.BUTTON_APPEARANCE_SUCCESS;\n }\n return undefined;\n }\n\n makeBlockButtonSize(size: Devvit.Blocks.ButtonSize | undefined): BlockButtonSize | undefined {\n switch (size) {\n case 'small':\n return BlockButtonSize.BUTTON_SIZE_SMALL;\n case 'medium':\n return BlockButtonSize.BUTTON_SIZE_MEDIUM;\n case 'large':\n return BlockButtonSize.BUTTON_SIZE_LARGE;\n }\n return undefined;\n }\n\n makeBlockImageResizeMode(\n resize: Devvit.Blocks.ImageResizeMode | undefined\n ): BlockImageResizeMode | undefined {\n switch (resize) {\n case 'none':\n return BlockImageResizeMode.IMAGE_RESIZE_NONE;\n case 'fit':\n return BlockImageResizeMode.IMAGE_RESIZE_FIT;\n case 'fill':\n return BlockImageResizeMode.IMAGE_RESIZE_FILL;\n case 'cover':\n return BlockImageResizeMode.IMAGE_RESIZE_COVER;\n case 'scale-down':\n return BlockImageResizeMode.IMAGE_RESIZE_SCALE_DOWN;\n }\n return undefined;\n }\n\n makeBlockSpacerSize(size: Devvit.Blocks.SpacerSize | undefined): BlockSpacerSize | undefined {\n switch (size) {\n case 'xsmall':\n return BlockSpacerSize.SPACER_XSMALL;\n case 'small':\n return BlockSpacerSize.SPACER_SMALL;\n case 'medium':\n return BlockSpacerSize.SPACER_MEDIUM;\n case 'large':\n return BlockSpacerSize.SPACER_LARGE;\n }\n return undefined;\n }\n\n makeBlockSpacerShape(size: Devvit.Blocks.SpacerShape | undefined): BlockSpacerShape | undefined {\n switch (size) {\n case 'invisible':\n return BlockSpacerShape.SPACER_INVISIBLE;\n case 'thin':\n return BlockSpacerShape.SPACER_THIN;\n case 'square':\n return BlockSpacerShape.SPACER_SQUARE;\n }\n return undefined;\n }\n\n makeBlockIconSize(size: Devvit.Blocks.IconSize | undefined): BlockIconSize | undefined {\n switch (size) {\n case 'xsmall':\n return BlockIconSize.ICON_SIZE_XSMALL;\n case 'small':\n return BlockIconSize.ICON_SIZE_SMALL;\n case 'medium':\n return BlockIconSize.ICON_SIZE_MEDIUM;\n case 'large':\n return BlockIconSize.ICON_SIZE_LARGE;\n }\n return undefined;\n }\n\n makeBlockAvatarSize(size: Devvit.Blocks.AvatarSize | undefined): BlockAvatarSize | undefined {\n switch (size) {\n case 'xxsmall':\n return BlockAvatarSize.AVATAR_SIZE_XXSMALL;\n case 'xsmall':\n return BlockAvatarSize.AVATAR_SIZE_XSMALL;\n case 'small':\n return BlockAvatarSize.AVATAR_SIZE_SMALL;\n case 'medium':\n return BlockAvatarSize.AVATAR_SIZE_MEDIUM;\n case 'large':\n return BlockAvatarSize.AVATAR_SIZE_LARGE;\n case 'xlarge':\n return BlockAvatarSize.AVATAR_SIZE_XXLARGE;\n case 'xxlarge':\n return BlockAvatarSize.AVATAR_SIZE_XXLARGE;\n case 'xxxlarge':\n return BlockAvatarSize.AVATAR_SIZE_XXXLARGE;\n }\n return undefined;\n }\n\n makeBlockAvatarFacing(\n facing: Devvit.Blocks.AvatarFacing | undefined\n ): BlockAvatarFacing | undefined {\n switch (facing) {\n case 'left':\n return BlockAvatarFacing.AVATAR_FACING_LEFT;\n case 'right':\n return BlockAvatarFacing.AVATAR_FACING_RIGHT;\n }\n return undefined;\n }\n\n makeBlockAvatarBackground(\n background: Devvit.Blocks.AvatarBackground | undefined\n ): BlockAvatarBackground | undefined {\n switch (background) {\n case 'dark':\n return BlockAvatarBackground.AVATAR_BG_DARK;\n case 'light':\n return BlockAvatarBackground.AVATAR_BG_LIGHT;\n }\n return undefined;\n }\n\n getDataSet(props: DataSet): DataSet {\n return Object.keys(props)\n .filter((key) => key.startsWith(DATA_PREFIX))\n .reduce((p, c) => {\n p[c.substring(DATA_PREFIX.length)] = props[c];\n return p;\n }, {} as DataSet);\n }\n\n makeActions(_type: BlockType, props: { [key: string]: unknown }): BlockAction[] {\n const actions: BlockAction[] = [];\n const dataSet = this.getDataSet(props as DataSet);\n ACTION_HANDLERS.forEach((action) => {\n if (action in props) {\n const id = props[action]!;\n actions.push({\n type: ACTION_TYPES.get(action) ?? BlockActionType.UNRECOGNIZED,\n id: id.toString(),\n data: dataSet,\n });\n }\n });\n return actions;\n }\n\n blockColorToHex(\n color: Devvit.Blocks.ColorString | undefined,\n theme: 'light' | 'dark' = 'light'\n ): Devvit.Blocks.ColorString | undefined {\n if (!color) return undefined;\n\n color = color.toLowerCase();\n if (isHexColor(color)) {\n return color;\n } else if (isRPLColor(color)) {\n return getHexFromRPLColor(color, theme);\n } else if (isNamedHTMLColor(color)) {\n return getHexFromNamedHTMLColor(color);\n } else if (isRgbaColor(color)) {\n return getHexFromRgbaColor(color);\n } else if (isHslColor(color)) {\n return color;\n }\n\n // Color could not be parsed, return red as fallback.\n console.warn(`Could not parse color: ${color}.`);\n return getHexFromNamedHTMLColor('red');\n }\n\n childrenToBlocks(\n children: ReifiedBlockElementOrLiteral[],\n transformContext: TransformContext\n ): Block[] {\n return children.flatMap(\n (child) =>\n (typeof child !== 'string'\n ? this.createBlocksElement(child, transformContext)\n : undefined) ?? []\n );\n }\n\n getThemedColors(\n color: Devvit.Blocks.ColorString | undefined,\n light?: Devvit.Blocks.ColorString | undefined,\n dark?: Devvit.Blocks.ColorString | undefined\n ): BlockColor | undefined {\n let lightColor = this.blockColorToHex(light, 'light');\n let darkColor = this.blockColorToHex(dark, 'dark');\n\n const tokens: string[] = [];\n\n // don't spend time parsing color if light/dark are already provided\n if (color && (!lightColor || !darkColor)) {\n // split color string, preserving color functions with spaces, such as rgb(r, g, b)\n // eslint-disable-next-line security/detect-unsafe-regex\n const matches = Array.from(color?.matchAll(/[\\w#-]+(?:\\([\\w\\t ,.#-]+\\))?/g) ?? []);\n tokens.push(...matches.map((group) => group[0]));\n }\n\n if (!lightColor) {\n lightColor = this.blockColorToHex(tokens?.at(0), 'light');\n }\n if (!darkColor) {\n // if only one color was provided, use it for both light and dark colors\n darkColor = this.blockColorToHex(tokens?.at(1) ?? tokens?.at(0), 'dark');\n }\n\n return lightColor || darkColor\n ? {\n light: lightColor,\n dark: darkColor,\n }\n : undefined;\n }\n\n parsePixels(input: Devvit.Blocks.SizePixels | number): number {\n if (typeof input === 'string') {\n return Number(input.slice(0, -2));\n }\n return input;\n }\n\n resolveAssetUrl(url: string, options?: GetURLOptions): string {\n // try and resolve the URL but allow the client to decide if unknown URLs are allowed\n return this.#assetsClient()?.getURL(url, options) ?? url;\n }\n\n childrenToString(children: ReifiedBlockElementOrLiteral[]): string {\n return children.map((c) => c.toString()).join('');\n }\n\n makeRoot(\n props: Devvit.Blocks.BaseProps | undefined,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.wrapRoot(props, this.childrenToBlocks(children, ROOT_STACK_TRANSFORM_CONTEXT));\n }\n\n wrapRoot(props: Devvit.Blocks.BaseProps | undefined, children: Block[]): Block {\n return this.makeBlock(\n BlockType.BLOCK_ROOT,\n {},\n {},\n {\n rootConfig: {\n children: children,\n height: this.makeRootHeight(\n Devvit.customPostType?.height ??\n (props as unknown as Devvit.Blocks.RootProps)?.height ??\n 'regular'\n ),\n },\n }\n );\n }\n\n makeStackBlock(\n direction: BlockStackDirection,\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n children: ReifiedBlockElementOrLiteral[]\n ): Block {\n const backgroundColors = this.getThemedColors(\n props?.backgroundColor,\n props?.lightBackgroundColor,\n props?.darkBackgroundColor\n );\n const alignment = this.makeBlockAlignment(props?.alignment);\n const blockSizes = makeBlockSizes(props, transformContext);\n\n const blockDimensionsDetails = makeStackDimensionsDetails(\n props,\n transformContext.stackParentLayout,\n blockSizes\n );\n\n return this.makeBlock(BlockType.BLOCK_STACK, props, transformContext, {\n stackConfig: {\n alignment,\n backgroundColor: backgroundColors?.light,\n backgroundColors,\n border: this.makeBlockBorder(\n props?.border,\n props?.borderColor,\n props?.lightBorderColor,\n props?.darkBorderColor\n ),\n children: this.childrenToBlocks(children, {\n stackParentLayout: { ...blockDimensionsDetails, direction, alignment },\n }),\n cornerRadius: this.makeBlockRadius(props?.cornerRadius),\n direction: direction,\n gap: this.makeBlockGap(props?.gap),\n padding: this.makeBlockPadding(props?.padding),\n reverse: props?.reverse,\n },\n });\n }\n\n makeHStack(\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.makeStackBlock(\n BlockStackDirection.STACK_HORIZONTAL,\n props,\n transformContext,\n children\n );\n }\n\n makeVStack(\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.makeStackBlock(\n BlockStackDirection.STACK_VERTICAL,\n props,\n transformContext,\n children\n );\n }\n\n makeZStack(\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.makeStackBlock(BlockStackDirection.STACK_DEPTH, props, transformContext, children);\n }\n\n makeText(\n props: Devvit.Blocks.TextProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n const colors = this.getThemedColors(props?.color, props?.lightColor, props?.darkColor);\n return this.makeBlock(BlockType.BLOCK_TEXT, props, transformContext, {\n textConfig: {\n alignment: this.makeBlockAlignment(props?.alignment),\n color: colors?.light,\n colors,\n outline: this.makeBlockTextOutline(props?.outline),\n size: this.makeBlockTextSize(props?.size),\n style: this.makeBlockTextStyle(props?.style),\n text: this.childrenToString(children),\n weight: this.makeBlockTextWeight(props?.weight),\n selectable: props?.selectable,\n wrap: props?.wrap,\n overflow: this.makeBlockTextOverflow(props?.overflow),\n },\n });\n }\n\n makeButton(\n props: Devvit.Blocks.ButtonProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n const textColors = this.getThemedColors(\n props?.textColor,\n props?.lightTextColor,\n props?.darkTextColor\n );\n return this.makeBlock(BlockType.BLOCK_BUTTON, props, transformContext, {\n buttonConfig: {\n buttonAppearance: this.makeBlockButtonAppearance(props?.appearance),\n // not available in all platforms yet\n // backgroundColor: props?.backgroundColor,\n icon: props?.icon,\n buttonSize: this.makeBlockButtonSize(props?.size),\n text: this.childrenToString(children),\n textColor: textColors?.light,\n textColors,\n disabled: props?.disabled,\n },\n });\n }\n\n makeImage(\n props: Devvit.Blocks.ImageProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_IMAGE, props, transformContext, {\n imageConfig: {\n description: props?.description,\n resizeMode: this.makeBlockImageResizeMode(props.resizeMode),\n url: this.resolveAssetUrl(props.url),\n width: this.parsePixels(props.imageWidth),\n height: this.parsePixels(props.imageHeight),\n },\n })\n );\n }\n\n makeSpacer(\n props: Devvit.Blocks.SpacerProps | undefined,\n transformContext: TransformContext\n ): Block {\n return this.makeBlock(BlockType.BLOCK_SPACER, props, transformContext, {\n spacerConfig: {\n size: this.makeBlockSpacerSize(props?.size),\n shape: this.makeBlockSpacerShape(props?.shape),\n },\n });\n }\n\n makeIcon(\n props: Devvit.Blocks.IconProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n const colors = this.getThemedColors(props?.color, props?.lightColor, props?.darkColor);\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_ICON, props, transformContext, {\n iconConfig: {\n icon: props.name,\n color: colors?.light,\n colors,\n size: this.makeBlockIconSize(props.size),\n },\n })\n );\n }\n\n makeAvatar(\n props: Devvit.Blocks.AvatarProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_AVATAR, props, transformContext, {\n avatarConfig: {\n thingId: props.thingId,\n size: this.makeBlockAvatarSize(props.size),\n facing: this.makeBlockAvatarFacing(props.facing),\n background: this.makeBlockAvatarBackground(props.background),\n },\n })\n );\n }\n\n makeWebView(\n props: Devvit.Blocks.WebViewProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_WEBVIEW, props, transformContext, {\n webviewConfig: {\n url: this.resolveAssetUrl(props.url, { webView: true }),\n },\n })\n );\n }\n\n makeBlock(\n type: BlockType,\n props: Devvit.Blocks.BaseProps | undefined,\n transformContext: TransformContext,\n config?: BlockConfig | undefined\n ): Block {\n return {\n type,\n sizes: makeBlockSizes(props, transformContext),\n config: config,\n actions: (props && this.makeActions(type, props)) ?? [],\n id: props?.id,\n key: props?.key,\n };\n }\n\n ensureRootBlock(block: Block): Block {\n let root: Block;\n\n if (block.type === BlockType.BLOCK_ROOT) {\n if (block.config?.rootConfig && Devvit.customPostType?.height) {\n block.config.rootConfig.height = this.makeRootHeight(Devvit.customPostType?.height);\n }\n root = block;\n } else {\n root = this.wrapRoot(undefined, [block]);\n }\n\n if (!root) {\n throw new Error('Could not create root block');\n }\n\n return root;\n }\n}\n", "export const semanticColors = {\n \"alienblue-100\": \"#DCE2FB\",\n \"alienblue-200\": \"#B8C5FC\",\n \"alienblue-25\": \"#F7F8FD\",\n \"alienblue-300\": \"#90A9FD\",\n \"alienblue-400\": \"#648EFC\",\n \"alienblue-50\": \"#EEF0FB\",\n \"alienblue-500\": \"#1870F4\",\n \"alienblue-600\": \"#115BCA\",\n \"alienblue-700\": \"#0A449B\",\n \"alienblue-75\": \"#E6E9FB\",\n \"alienblue-800\": \"#0A2F6C\",\n \"alienblue-900\": \"#0A1A3F\",\n \"alienblue-950\": \"#07102B\",\n \"berrypurple-100\": \"#F3DAFB\",\n \"berrypurple-200\": \"#EAB3FD\",\n \"berrypurple-25\": \"#FBF6FD\",\n \"berrypurple-300\": \"#DE8BFF\",\n \"berrypurple-400\": \"#CF5FFF\",\n \"berrypurple-50\": \"#F8EDFC\",\n \"berrypurple-500\": \"#BC0EFF\",\n \"berrypurple-600\": \"#9B00D4\",\n \"berrypurple-700\": \"#7600A3\",\n \"berrypurple-75\": \"#F6E4FB\",\n \"berrypurple-800\": \"#520472\",\n \"berrypurple-900\": \"#300643\",\n \"berrypurple-950\": \"#20042A\",\n \"black\": \"#000000\",\n \"black-opacity-0\": \"#00000000\",\n \"black-opacity-10\": \"#00000019\",\n \"black-opacity-15\": \"#00000026\",\n \"black-opacity-20\": \"#00000033\",\n \"black-opacity-25\": \"#0000003F\",\n \"black-opacity-33\": \"#00000054\",\n \"black-opacity-5\": \"#0000000C\",\n \"black-opacity-50\": \"#0000007F\",\n \"black-opacity-60\": \"#00000099\",\n \"black-opacity-67\": \"#000000AA\",\n \"black-opacity-80\": \"#000000CC\",\n \"black-opacity-90\": \"#000000E5\",\n \"brown-100\": \"#F1DFCF\",\n \"brown-200\": \"#E9BE95\",\n \"brown-25\": \"#FBF7F4\",\n \"brown-300\": \"#DC9D5D\",\n \"brown-400\": \"#BA854E\",\n \"brown-50\": \"#F7EFE9\",\n \"brown-500\": \"#9A6D3F\",\n \"brown-600\": \"#7B5631\",\n \"brown-700\": \"#5D4023\",\n \"brown-75\": \"#F4E7DC\",\n \"brown-800\": \"#3F2C19\",\n \"brown-900\": \"#221A11\",\n \"brown-950\": \"#15100A\",\n \"coolgray-100\": \"#DBE4E9\",\n \"coolgray-150\": \"#C9D7DE\",\n \"coolgray-200\": \"#B7CAD4\",\n \"coolgray-25\": \"#F6F8F9\",\n \"coolgray-250\": \"#A4BDCA\",\n \"coolgray-300\": \"#97AFBC\",\n \"coolgray-350\": \"#8BA2AD\",\n \"coolgray-400\": \"#7F949F\",\n \"coolgray-450\": \"#748791\",\n \"coolgray-50\": \"#EEF1F3\",\n \"coolgray-500\": \"#667780\",\n \"coolgray-525\": \"#576F76\",\n \"coolgray-550\": \"#5C6C74\",\n \"coolgray-600\": \"#536168\",\n \"coolgray-650\": \"#48545B\",\n \"coolgray-700\": \"#3D494E\",\n \"coolgray-75\": \"#E5EBEE\",\n \"coolgray-750\": \"#333D42\",\n \"coolgray-800\": \"#2A3236\",\n \"coolgray-850\": \"#21272A\",\n \"coolgray-900\": \"#181C1F\",\n \"coolgray-950\": \"#0E1113\",\n \"global-black\": {\n \"light\": \"#000000\",\n \"dark\": \"#000000\"\n },\n \"global-brand-orangered\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"global-white\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"kiwigreen-100\": \"#A8F5A0\",\n \"kiwigreen-200\": \"#58E15B\",\n \"kiwigreen-25\": \"#EBFDE7\",\n \"kiwigreen-300\": \"#00C61C\",\n \"kiwigreen-400\": \"#01A816\",\n \"kiwigreen-50\": \"#DDF8D7\",\n \"kiwigreen-500\": \"#008A10\",\n \"kiwigreen-600\": \"#016E0B\",\n \"kiwigreen-700\": \"#005306\",\n \"kiwigreen-75\": \"#CAF5C2\",\n \"kiwigreen-800\": \"#033902\",\n \"kiwigreen-900\": \"#0D2005\",\n \"kiwigreen-950\": \"#081404\",\n \"lightblue-100\": \"#CAE7FB\",\n \"lightblue-200\": \"#87D0FD\",\n \"lightblue-25\": \"#F3F9FD\",\n \"lightblue-300\": \"#02B9FB\",\n \"lightblue-400\": \"#029DD5\",\n \"lightblue-50\": \"#E6F3FC\",\n \"lightblue-500\": \"#007FAE\",\n \"lightblue-600\": \"#01668D\",\n \"lightblue-700\": \"#014D6B\",\n \"lightblue-75\": \"#D9EDFB\",\n \"lightblue-800\": \"#03354B\",\n \"lightblue-900\": \"#051E2B\",\n \"lightblue-950\": \"#04131A\",\n \"lime-100\": \"#B7F28E\",\n \"lime-200\": \"#90DA58\",\n \"lime-25\": \"#EEFDDC\",\n \"lime-300\": \"#6DBF01\",\n \"lime-400\": \"#5BA200\",\n \"lime-50\": \"#E0F8C8\",\n \"lime-500\": \"#4A8500\",\n \"lime-600\": \"#3A6A00\",\n \"lime-700\": \"#2A5000\",\n \"lime-75\": \"#CEF5AD\",\n \"lime-800\": \"#1E3702\",\n \"lime-900\": \"#151E05\",\n \"lime-950\": \"#0D1304\",\n \"mintgreen-100\": \"#9BF5D9\",\n \"mintgreen-200\": \"#00E2B7\",\n \"mintgreen-25\": \"#E7FDF5\",\n \"mintgreen-300\": \"#00C29D\",\n \"mintgreen-400\": \"#01A484\",\n \"mintgreen-50\": \"#D7F8EC\",\n \"mintgreen-500\": \"#01876D\",\n \"mintgreen-600\": \"#006C56\",\n \"mintgreen-700\": \"#015140\",\n \"mintgreen-75\": \"#C0F5E3\",\n \"mintgreen-800\": \"#00382B\",\n \"mintgreen-900\": \"#032019\",\n \"mintgreen-950\": \"#03140F\",\n \"orangered-100\": \"#FCDBCF\",\n \"orangered-200\": \"#FDB498\",\n \"orangered-25\": \"#FDF6F4\",\n \"orangered-300\": \"#FF895D\",\n \"orangered-400\": \"#FF4500\",\n \"orangered-50\": \"#FCEEE8\",\n \"orangered-500\": \"#D93900\",\n \"orangered-600\": \"#AE2C00\",\n \"orangered-700\": \"#842100\",\n \"orangered-75\": \"#FBE5DC\",\n \"orangered-800\": \"#591B02\",\n \"orangered-900\": \"#2F1405\",\n \"orangered-950\": \"#1C0D04\",\n \"periwinkle-100\": \"#E6DFFB\",\n \"periwinkle-200\": \"#CDBEFD\",\n \"periwinkle-25\": \"#F9F7FD\",\n \"periwinkle-300\": \"#B29FFF\",\n \"periwinkle-400\": \"#9580FF\",\n \"periwinkle-50\": \"#F2EFFC\",\n \"periwinkle-500\": \"#6A5CFF\",\n \"periwinkle-600\": \"#523DFF\",\n \"periwinkle-700\": \"#4001EA\",\n \"periwinkle-75\": \"#ECE7FB\",\n \"periwinkle-800\": \"#250AA6\",\n \"periwinkle-900\": \"#160E5B\",\n \"periwinkle-950\": \"#0C083F\",\n \"poopbrown-100\": \"#FEEEDD\",\n \"poopbrown-200\": \"#F6DDC3\",\n \"poopbrown-300\": \"#EECCAA\",\n \"poopbrown-400\": \"#CAA075\",\n \"poopbrown-50\": \"#FEF7EE\",\n \"poopbrown-500\": \"#9A6D3F\",\n \"poopbrown-600\": \"#6E4924\",\n \"poopbrown-700\": \"#54371A\",\n \"poopbrown-800\": \"#3B2510\",\n \"poopbrown-900\": \"#29190A\",\n \"poopbrown-950\": \"#110B04\",\n \"puregray-100\": \"#F2F2F2\",\n \"puregray-150\": \"#EBEBEB\",\n \"puregray-200\": \"#E4E4E4\",\n \"puregray-250\": \"#DDDDDD\",\n \"puregray-300\": \"#D6D6D6\",\n \"puregray-350\": \"#C3C3C3\",\n \"puregray-400\": \"#ACACAC\",\n \"puregray-450\": \"#919191\",\n \"puregray-50\": \"#F8F8F8\",\n \"puregray-500\": \"#767676\",\n \"puregray-550\": \"#5C5C5C\",\n \"puregray-600\": \"#434343\",\n \"puregray-650\": \"#393939\",\n \"puregray-700\": \"#303030\",\n \"puregray-750\": \"#272727\",\n \"puregray-800\": \"#1E1E1E\",\n \"puregray-850\": \"#181818\",\n \"puregray-900\": \"#131313\",\n \"puregray-950\": \"#080808\",\n \"red-100\": \"#FBDBD4\",\n \"red-200\": \"#FDB3A4\",\n \"red-25\": \"#FDF6F5\",\n \"red-300\": \"#FF8773\",\n \"red-400\": \"#FF4F40\",\n \"red-50\": \"#FCEEEA\",\n \"red-500\": \"#EB001F\",\n \"red-600\": \"#BC0117\",\n \"red-700\": \"#90000F\",\n \"red-75\": \"#FBE4DF\",\n \"red-800\": \"#650405\",\n \"red-900\": \"#340F05\",\n \"red-950\": \"#1F0B04\",\n \"sakurapink-100\": \"#FBD9EB\",\n \"sakurapink-200\": \"#FDAEDA\",\n \"sakurapink-25\": \"#FDF6F9\",\n \"sakurapink-300\": \"#FF7ECA\",\n \"sakurapink-400\": \"#FF38BB\",\n \"sakurapink-50\": \"#FCEDF4\",\n \"sakurapink-500\": \"#DE019F\",\n \"sakurapink-600\": \"#B2007F\",\n \"sakurapink-700\": \"#880060\",\n \"sakurapink-75\": \"#FBE3EF\",\n \"sakurapink-800\": \"#5F0443\",\n \"sakurapink-900\": \"#380626\",\n \"sakurapink-950\": \"#250419\",\n \"transparent\": \"transparent\",\n \"white\": \"#ffffff\",\n \"white-opacity-0\": \"#FFFFFF00\",\n \"white-opacity-10\": \"#FFFFFF19\",\n \"white-opacity-15\": \"#FFFFFF26\",\n \"white-opacity-20\": \"#FFFFFF33\",\n \"white-opacity-25\": \"#FFFFFF3F\",\n \"white-opacity-5\": \"#FFFFFF0C\",\n \"white-opacity-50\": \"#FFFFFF7F\",\n \"yellow-100\": \"#FFE284\",\n \"yellow-200\": \"#FFBF0B\",\n \"yellow-25\": \"#FFF9E0\",\n \"yellow-300\": \"#D8A100\",\n \"yellow-400\": \"#B78800\",\n \"yellow-50\": \"#FFF3C0\",\n \"yellow-500\": \"#977000\",\n \"yellow-600\": \"#785800\",\n \"yellow-700\": \"#5B4200\",\n \"yellow-75\": \"#FFEAA2\",\n \"yellow-800\": \"#3F2D00\",\n \"yellow-900\": \"#231A03\",\n \"yellow-950\": \"#161003\",\n \"yelloworange-100\": \"#FCDCC8\",\n \"yelloworange-200\": \"#FDB586\",\n \"yelloworange-25\": \"#FDF7F3\",\n \"yelloworange-300\": \"#FF8A35\",\n \"yelloworange-400\": \"#E46C00\",\n \"yelloworange-50\": \"#FCEEE6\",\n \"yelloworange-500\": \"#BD5800\",\n \"yelloworange-600\": \"#974500\",\n \"yelloworange-700\": \"#733300\",\n \"yelloworange-75\": \"#FBE5D7\",\n \"yelloworange-800\": \"#4E2402\",\n \"yelloworange-900\": \"#2A1705\",\n \"yelloworange-950\": \"#1A0E04\",\n \"action-downvote\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"action-upvote\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"ai-background-weaker\": {\n \"light\": \"#E7FDF5\",\n \"dark\": \"#00382B\"\n },\n \"ai-plain\": {\n \"light\": \"#006C56\",\n \"dark\": \"#01A484\"\n },\n \"ai-plain-hover\": {\n \"light\": \"#015140\",\n \"dark\": \"#00C29D\"\n },\n \"alert-caution\": {\n \"light\": \"#977000\",\n \"dark\": \"#977000\"\n },\n \"avatar-gradient\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-background\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"brand-background-hover\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#AE2C00\"\n },\n \"brand-gradient-active\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-active-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-default\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-default-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-hover\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-hover-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"category-live\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"category-nsfw\": {\n \"light\": \"#DE019F\",\n \"dark\": \"#DE019F\"\n },\n \"category-spoiler\": {\n \"light\": \"#131313\",\n \"dark\": \"#F2F2F2\"\n },\n \"caution-background\": {\n \"light\": \"#FFBF0B\",\n \"dark\": \"#D8A100\"\n },\n \"caution-background-hover\": {\n \"light\": \"#D8A100\",\n \"dark\": \"#FFBF0B\"\n },\n \"caution-onBackground\": {\n \"light\": \"#000000\",\n \"dark\": \"#000000\"\n },\n \"caution-plain\": {\n \"light\": \"#785800\",\n \"dark\": \"#FFBF0B\"\n },\n \"caution-plain-hover\": {\n \"light\": \"#5B4200\",\n \"dark\": \"#FFE284\"\n },\n \"danger-background\": {\n \"light\": \"#EB001F\",\n \"dark\": \"#BC0117\"\n },\n \"danger-background-disabled\": {\n \"light\": \"#FDB3A4\",\n \"dark\": \"#340F05\"\n },\n \"danger-background-hover\": {\n \"light\": \"#BC0117\",\n \"dark\": \"#EB001F\"\n },\n \"danger-background-weaker\": {\n \"light\": \"#FBDBD4\",\n \"dark\": \"#650405\"\n },\n \"danger-content\": {\n \"light\": \"#BC0117\",\n \"dark\": \"#FF4F40\"\n },\n \"danger-content-default\": {\n \"light\": \"#ffffff\",\n \"dark\": \"#ffffff\"\n },\n \"danger-content-hover\": {\n \"light\": \"#90000F\",\n \"dark\": \"#FF8773\"\n },\n \"danger-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"danger-plain\": {\n \"light\": \"#BC0117\",\n \"dark\": \"#FF8773\"\n },\n \"danger-plain-hover\": {\n \"light\": \"#90000F\",\n \"dark\": \"#FDB3A4\"\n },\n \"downvote-background\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"downvote-background-disabled\": {\n \"light\": \"#6a5cff4d\",\n \"dark\": \"#6a5cff4d\"\n },\n \"downvote-background-hover\": {\n \"light\": \"#523DFF\",\n \"dark\": \"#523DFF\"\n },\n \"downvote-content\": {\n \"light\": \"#523DFF\",\n \"dark\": \"#9580FF\"\n },\n \"downvote-content-weak\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"downvote-disabled\": {\n \"light\": \"#523DFF4c\",\n \"dark\": \"#9580FF4c\"\n },\n \"downvote-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"downvote-onStrongScrim\": {\n \"light\": \"#B29FFF\",\n \"dark\": \"#B29FFF\"\n },\n \"downvote-onStrongScrim-disabled\": {\n \"light\": \"#b29fff4d\",\n \"dark\": \"#b29fff4d\"\n },\n \"downvote-onStrongScrim-weaker\": {\n \"light\": \"#9580FF\",\n \"dark\": \"#9580FF\"\n },\n \"downvote-plain\": {\n \"light\": \"#523DFF\",\n \"dark\": \"#9580FF\"\n },\n \"downvote-plain-disabled\": {\n \"light\": \"#523dff4d\",\n \"dark\": \"#9580ff4d\"\n },\n \"downvote-plain-weaker\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"elevation-large1\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#0000007F\"\n },\n \"elevation-large2\": {\n \"light\": \"#00000026\",\n \"dark\": \"#00000033\"\n },\n \"elevation-medium1\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#0000007F\"\n },\n \"elevation-medium2\": {\n \"light\": \"#00000019\",\n \"dark\": \"#00000033\"\n },\n \"elevation-small\": {\n \"light\": \"#00000026\",\n \"dark\": \"#00000054\"\n },\n \"elevation-xsmall\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#000000AA\"\n },\n \"favorite\": {\n \"light\": \"#977000\",\n \"dark\": \"#B78800\"\n },\n \"global-admin\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"global-alienblue\": {\n \"light\": \"#1870F4\",\n \"dark\": \"#1870F4\"\n },\n \"global-darkblue\": {\n \"light\": \"#2A3236\",\n \"dark\": \"#2A3236\"\n },\n \"global-focus\": {\n \"light\": \"#029DD5\",\n \"dark\": \"#007FAE\"\n },\n \"global-gold\": {\n \"light\": \"#B78800\",\n \"dark\": \"#D8A100\"\n },\n \"global-moderator\": {\n \"light\": \"#008A10\",\n \"dark\": \"#008A10\"\n },\n \"global-nsfw\": {\n \"light\": \"#DE019F\",\n \"dark\": \"#DE019F\"\n },\n \"global-offline\": {\n \"light\": \"#667780\",\n \"dark\": \"#667780\"\n },\n \"global-online\": {\n \"light\": \"#00C61C\",\n \"dark\": \"#00C61C\"\n },\n \"global-orangered\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"global-stars\": {\n \"light\": \"#977000\",\n \"dark\": \"#D8A100\"\n },\n \"gradient-media\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"gradient-media-strong\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"identity-admin\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"identity-coins\": {\n \"light\": \"#B78800\",\n \"dark\": \"#B78800\"\n },\n \"identity-moderator\": {\n \"light\": \"#008A10\",\n \"dark\": \"#008A10\"\n },\n \"identity-self\": {\n \"light\": \"#01876D\",\n \"dark\": \"#01876D\"\n },\n \"interactive-background-disabled\": {\n \"light\": \"#0000000C\",\n \"dark\": \"#FFFFFF0C\"\n },\n \"interactive-content-disabled\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#FFFFFF3F\"\n },\n \"interactive-focused\": {\n \"light\": \"#007FAE\",\n \"dark\": \"#007FAE\"\n },\n \"interactive-pressed\": {\n \"light\": \"#00000026\",\n \"dark\": \"#FFFFFF26\"\n },\n \"inverted-interactive-background-disabled\": {\n \"light\": \"#FFFFFF0C\",\n \"dark\": \"#0000000C\"\n },\n \"inverted-interactive-content-disabled\": {\n \"light\": \"#FFFFFF3F\",\n \"dark\": \"#0000003F\"\n },\n \"inverted-interactive-pressed\": {\n \"light\": \"#FFFFFF26\",\n \"dark\": \"#00000026\"\n },\n \"inverted-neutral-background\": {\n \"light\": \"#0E1113\",\n \"dark\": \"#FFFFFF\"\n },\n \"inverted-neutral-background-hover\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#F6F8F9\"\n },\n \"inverted-neutral-border\": {\n \"light\": \"#FFFFFF33\",\n \"dark\": \"#00000033\"\n },\n \"inverted-neutral-content\": {\n \"light\": \"#B7CAD4\",\n \"dark\": \"#333D42\"\n },\n \"inverted-neutral-content-strong\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#181C1F\"\n },\n \"inverted-secondary-background\": {\n \"light\": \"#2A3236\",\n \"dark\": \"#E5EBEE\"\n },\n \"inverted-secondary-background-hover\": {\n \"light\": \"#333D42\",\n \"dark\": \"#DBE4E9\"\n },\n \"inverted-secondary-background-selected\": {\n \"light\": \"#3D494E\",\n \"dark\": \"#C9D7DE\"\n },\n \"inverted-secondary-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#000000\"\n },\n \"inverted-secondary-plain\": {\n \"light\": \"#DBE4E9\",\n \"dark\": \"#181C1F\"\n },\n \"inverted-secondary-plain-hover\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#000000\"\n },\n \"media-background\": {\n \"light\": \"#00000099\",\n \"dark\": \"#00000099\"\n },\n \"media-background-hover\": {\n \"light\": \"#000000cc\",\n \"dark\": \"#000000cc\"\n },\n \"media-background-selected\": {\n \"light\": \"#000000cc\",\n \"dark\": \"#000000cc\"\n },\n \"media-border-selected\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"media-border-weak\": {\n \"light\": \"#FFFFFF19\",\n \"dark\": \"#FFFFFF19\"\n },\n \"media-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"media-onBackground-disabled\": {\n \"light\": \"#FFFFFF3F\",\n \"dark\": \"#FFFFFF3F\"\n },\n \"media-onBackground-weak\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#E5EBEE\"\n },\n \"media-onbackground\": {\n \"light\": \"#ffffff\",\n \"dark\": \"#ffffff\"\n },\n \"media-onbackground-disabled\": {\n \"light\": \"#ffffff3f\",\n \"dark\": \"#ffffff3f\"\n },\n \"media-onbackground-weak\": {\n \"light\": \"#B7CAD4\",\n \"dark\": \"#B7CAD4\"\n },\n \"neutral-background\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#0E1113\"\n },\n \"neutral-background-container\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-container-hover\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-container-strong\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-container-strong-hover\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#2A3236\"\n },\n \"neutral-background-gilded\": {\n \"light\": \"#FFF9E0\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-gilded-hover\": {\n \"light\": \"#FFF3C0\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-hover\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-medium\": {\n \"light\": \"#F8F8F8\",\n \"dark\": \"#131313\"\n },\n \"neutral-background-pinned\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#0E1113\"\n },\n \"neutral-background-selected\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#2A3236\"\n },\n \"neutral-background-strong\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-strong-hover\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-weak\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#000000\"\n },\n \"neutral-background-weak-hover\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#0E1113\"\n },\n \"neutral-border\": {\n \"light\": \"#00000033\",\n \"dark\": \"#FFFFFF33\"\n },\n \"neutral-border-medium\": {\n \"light\": \"#0000007F\",\n \"dark\": \"#FFFFFF7F\"\n },\n \"neutral-border-strong\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#F6F8F9\"\n },\n \"neutral-border-weak\": {\n \"light\": \"#00000019\",\n \"dark\": \"#FFFFFF19\"\n },\n \"neutral-content\": {\n \"light\": \"#333D42\",\n \"dark\": \"#B7CAD4\"\n },\n \"neutral-content-disabled\": {\n \"light\": \"#D6D6D6\",\n \"dark\": \"#303030\"\n },\n \"neutral-content-strong\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#EEF1F3\"\n },\n \"neutral-content-weak\": {\n \"light\": \"#5C6C74\",\n \"dark\": \"#8BA2AD\"\n },\n \"offline\": {\n \"light\": \"#767676\",\n \"dark\": \"#767676\"\n },\n \"online\": {\n \"light\": \"#01A816\",\n \"dark\": \"#01A816\"\n },\n \"opacity-08\": {\n \"light\": \"#13131314\",\n \"dark\": \"#13131314\"\n },\n \"opacity-50\": {\n \"light\": \"#0000007f\",\n \"dark\": \"#F2F2F27f\"\n },\n \"opacity-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"pizzaRed\": {\n \"light\": \"#ef5350\",\n \"dark\": \"#c62828\"\n },\n \"primary\": {\n \"light\": \"#115BCA\",\n \"dark\": \"#648EFC\"\n },\n \"primary-background\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#115BCA\"\n },\n \"primary-background-hover\": {\n \"light\": \"#0A2F6C\",\n \"dark\": \"#1870F4\"\n },\n \"primary-background-selected\": {\n \"light\": \"#0A1A3F\",\n \"dark\": \"#648EFC\"\n },\n \"primary-border\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#648EFC\"\n },\n \"primary-border-hover\": {\n \"light\": \"#0A2F6C\",\n \"dark\": \"#90A9FD\"\n },\n \"primary-hover\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#90A9FD\"\n },\n \"primary-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"primary-onBackground-selected\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#000000\"\n },\n \"primary-plain\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#648EFC\"\n },\n \"primary-plain-hover\": {\n \"light\": \"#0A2F6C\",\n \"dark\": \"#90A9FD\"\n },\n \"primary-plain-visited\": {\n \"light\": \"#7600A3\",\n \"dark\": \"#CF5FFF\"\n },\n \"primary-visited\": {\n \"light\": \"#9B00D4\",\n \"dark\": \"#CF5FFF\"\n },\n \"scrim\": {\n \"light\": \"#00000099\",\n \"dark\": \"#00000099\"\n },\n \"scrim-background\": {\n \"light\": \"#00000099\",\n \"dark\": \"#00000099\"\n },\n \"scrim-background-strong\": {\n \"light\": \"#000000CC\",\n \"dark\": \"#000000CC\"\n },\n \"scrim-gradient\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"scrim-strong\": {\n \"light\": \"#000000cc\",\n \"dark\": \"#000000cc\"\n },\n \"secondary\": {\n \"light\": \"#21272A\",\n \"dark\": \"#DBE4E9\"\n },\n \"secondary-background\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#2A3236\"\n },\n \"secondary-background-hover\": {\n \"light\": \"#DBE4E9\",\n \"dark\": \"#333D42\"\n },\n \"secondary-background-selected\": {\n \"light\": \"#C9D7DE\",\n \"dark\": \"#3D494E\"\n },\n \"secondary-hover\": {\n \"light\": \"#000000\",\n \"dark\": \"#ffffff\"\n },\n \"secondary-onBackground\": {\n \"light\": \"#000000\",\n \"dark\": \"#FFFFFF\"\n },\n \"secondary-plain\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#DBE4E9\"\n },\n \"secondary-plain-hover\": {\n \"light\": \"#000000\",\n \"dark\": \"#FFFFFF\"\n },\n \"secondary-plain-weak\": {\n \"light\": \"#5C6C74\",\n \"dark\": \"#8BA2AD\"\n },\n \"secondary-weak\": {\n \"light\": \"#576F76\",\n \"dark\": \"#748791\"\n },\n \"success-background\": {\n \"light\": \"#008A10\",\n \"dark\": \"#016E0B\"\n },\n \"success-background-hover\": {\n \"light\": \"#016E0B\",\n \"dark\": \"#008A10\"\n },\n \"success-content\": {\n \"light\": \"#016E0B\",\n \"dark\": \"#01A816\"\n },\n \"success-hover\": {\n \"light\": \"#005306\",\n \"dark\": \"#00C61C\"\n },\n \"success-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"success-plain\": {\n \"light\": \"#016E0B\",\n \"dark\": \"#00C61C\"\n },\n \"success-plain-hover\": {\n \"light\": \"#005306\",\n \"dark\": \"#58E15B\"\n },\n \"tone-1\": {\n \"light\": \"#131313\",\n \"dark\": \"#F2F2F2\"\n },\n \"tone-2\": {\n \"light\": \"#434343\",\n \"dark\": \"#ACACAC\"\n },\n \"tone-3\": {\n \"light\": \"#ACACAC\",\n \"dark\": \"#434343\"\n },\n \"tone-4\": {\n \"light\": \"#E4E4E4\",\n \"dark\": \"#303030\"\n },\n \"tone-5\": {\n \"light\": \"#F2F2F2\",\n \"dark\": \"#1E1E1E\"\n },\n \"tone-6\": {\n \"light\": \"#F8F8F8\",\n \"dark\": \"#131313\"\n },\n \"tone-7\": {\n \"light\": \"#ffffff\",\n \"dark\": \"#080808\"\n },\n \"transparent-background-hover\": {\n \"light\": \"#74879119\",\n \"dark\": \"#66778019\"\n },\n \"ui-canvas\": {\n \"light\": \"#F2F2F2\",\n \"dark\": \"#080808\"\n },\n \"ui-modalbackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#181C1F\"\n },\n \"upvote-background\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"upvote-background-disabled\": {\n \"light\": \"#d939004d\",\n \"dark\": \"#d939004d\"\n },\n \"upvote-background-hover\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#AE2C00\"\n },\n \"upvote-content\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#FF4500\"\n },\n \"upvote-content-weak\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"upvote-disabled\": {\n \"light\": \"#AE2C004c\",\n \"dark\": \"#FF45004c\"\n },\n \"upvote-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"upvote-onStrongScrim\": {\n \"light\": \"#FF895D\",\n \"dark\": \"#FF895D\"\n },\n \"upvote-onStrongScrim-disabled\": {\n \"light\": \"#ff895d4d\",\n \"dark\": \"#ff895d4d\"\n },\n \"upvote-onStrongScrim-weaker\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"upvote-plain\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#FF895D\"\n },\n \"upvote-plain-disabled\": {\n \"light\": \"#ae2c004d\",\n \"dark\": \"#ff895d4d\"\n },\n \"upvote-plain-weaker\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"warning-background\": {\n \"light\": \"#B78800\",\n \"dark\": \"#B78800\"\n },\n \"warning-background-hover\": {\n \"light\": \"#977000\",\n \"dark\": \"#D8A100\"\n },\n \"warning-content\": {\n \"light\": \"#785800\",\n \"dark\": \"#B78800\"\n },\n \"warning-content-hover\": {\n \"light\": \"#5B4200\",\n \"dark\": \"#D8A100\"\n },\n \"warning-onBackground\": {\n \"light\": \"#000000\",\n \"dark\": \"#000000\"\n }\n};\n", "import { semanticColors } from '../semanticColors.js';\n\nexport const namedHTMLColorToHex: Record<string, string> = {\n aliceblue: '#f0f8ff',\n antiquewhite: '#faebd7',\n aqua: '#00ffff',\n aquamarine: '#7fffd4',\n azure: '#f0ffff',\n beige: '#f5f5dc',\n bisque: '#ffe4c4',\n black: '#000000',\n blanchedalmond: '#ffebcd',\n blue: '#0000ff',\n blueviolet: '#8a2be2',\n brown: '#a52a2a',\n burlywood: '#deb887',\n cadetblue: '#5f9ea0',\n chartreuse: '#7fff00',\n chocolate: '#d2691e',\n coral: '#ff7f50',\n cornflowerblue: '#6495ed',\n cornsilk: '#fff8dc',\n crimson: '#dc143c',\n cyan: '#00ffff',\n darkblue: '#00008b',\n darkcyan: '#008b8b',\n darkgoldenrod: '#b8860b',\n darkgray: '#a9a9a9',\n darkgreen: '#006400',\n darkgrey: '#a9a9a9',\n darkkhaki: '#bdb76b',\n darkmagenta: '#8b008b',\n darkolivegreen: '#556b2f',\n darkorange: '#ff8c00',\n darkorchid: '#9932cc',\n darkred: '#8b0000',\n darksalmon: '#e9967a',\n darkseagreen: '#8fbc8f',\n darkslateblue: '#483d8b',\n darkslategray: '#2f4f4f',\n darkslategrey: '#2f4f4f',\n darkturquoise: '#00ced1',\n darkviolet: '#9400d3',\n deeppink: '#ff1493',\n deepskyblue: '#00bfff',\n dimgray: '#696969',\n dimgrey: '#696969',\n dodgerblue: '#1e90ff',\n firebrick: '#b22222',\n floralwhite: '#fffaf0',\n forestgreen: '#228b22',\n fuchsia: '#ff00ff',\n gainsboro: '#dcdcdc',\n ghostwhite: '#f8f8ff',\n gold: '#ffd700',\n goldenrod: '#daa520',\n gray: '#808080',\n green: '#008000',\n greenyellow: '#adff2f',\n grey: '#808080',\n honeydew: '#f0fff0',\n hotpink: '#ff69b4',\n indianred: '#cd5c5c',\n indigo: '#4b0082',\n ivory: '#fffff0',\n khaki: '#f0e68c',\n lavender: '#e6e6fa',\n lavenderblush: '#fff0f5',\n lawngreen: '#7cfc00',\n lemonchiffon: '#fffacd',\n lightblue: '#add8e6',\n lightcoral: '#f08080',\n lightcyan: '#e0ffff',\n lightgoldenrodyellow: '#fafad2',\n lightgray: '#d3d3d3',\n lightgreen: '#90ee90',\n lightgrey: '#d3d3d3',\n lightpink: '#ffb6c1',\n lightsalmon: '#ffa07a',\n lightseagreen: '#20b2aa',\n lightskyblue: '#87cefa',\n lightslategray: '#778899',\n lightslategrey: '#778899',\n lightsteelblue: '#b0c4de',\n lightyellow: '#ffffe0',\n lime: '#00ff00',\n limegreen: '#32cd32',\n linen: '#faf0e6',\n magenta: '#ff00ff',\n maroon: '#800000',\n mediumaquamarine: '#66cdaa',\n mediumblue: '#0000cd',\n mediumorchid: '#ba55d3',\n mediumpurple: '#9370db',\n mediumseagreen: '#3cb371',\n mediumslateblue: '#7b68ee',\n mediumspringgreen: '#00fa9a',\n mediumturquoise: '#48d1cc',\n mediumvioletred: '#c71585',\n midnightblue: '#191970',\n mintcream: '#f5fffa',\n mistyrose: '#ffe4e1',\n moccasin: '#ffe4b5',\n navajowhite: '#ffdead',\n navy: '#000080',\n oldlace: '#fdf5e6',\n olive: '#808000',\n olivedrab: '#6b8e23',\n orange: '#ffa500',\n orangered: '#ff4500',\n orchid: '#da70d6',\n palegoldenrod: '#eee8aa',\n palegreen: '#98fb98',\n paleturquoise: '#afeeee',\n palevioletred: '#db7093',\n papayawhip: '#ffefd5',\n peachpuff: '#ffdab9',\n peru: '#cd853f',\n pink: '#ffc0cb',\n plum: '#dda0dd',\n powderblue: '#b0e0e6',\n purple: '#800080',\n rebeccapurple: '#663399',\n red: '#ff0000',\n rosybrown: '#bc8f8f',\n royalblue: '#4169e1',\n saddlebrown: '#8b4513',\n salmon: '#fa8072',\n sandybrown: '#f4a460',\n seagreen: '#2e8b57',\n seashell: '#fff5ee',\n sienna: '#a0522d',\n silver: '#c0c0c0',\n skyblue: '#87ceeb',\n slateblue: '#6a5acd',\n slategray: '#708090',\n slategrey: '#708090',\n snow: '#fffafa',\n springgreen: '#00ff7f',\n steelblue: '#4682b4',\n tan: '#d2b48c',\n teal: '#008080',\n thistle: '#d8bfd8',\n tomato: '#ff6347',\n turquoise: '#40e0d0',\n transparent: '#FFFFFF00',\n violet: '#ee82ee',\n wheat: '#f5deb3',\n white: '#ffffff',\n whitesmoke: '#f5f5f5',\n yellow: '#ffff00',\n yellowgreen: '#9acd32',\n};\n\nexport const isHexColor = (color: string): boolean =>\n Boolean(color.match(/^(#[0-9a-fA-F]{3,8}).*/));\n\nexport const isRPLColor = (color: string): boolean => color in semanticColors;\n\nexport const isNamedHTMLColor = (color: string): boolean => color in namedHTMLColorToHex;\n\n// Matches on rgb or rgba colors\nexport const isRgbaColor = (color: string): boolean => Boolean(color.match(/^rgba?\\(.*/));\n\nexport const isHslColor = (color: string): boolean => Boolean(color.match(/^hsla?\\(.*/));\n\nexport const getHexFromRPLColor = (color: string, theme: 'light' | 'dark' = 'light'): string => {\n // Prefer named HTML colors\n if (isNamedHTMLColor(color)) {\n return getHexFromNamedHTMLColor(color);\n }\n\n const colors = semanticColors as Record<string, { light: string; dark: string } | string>;\n const finalColor = colors[color];\n return typeof finalColor === 'string' ? finalColor : finalColor[theme];\n};\n\nexport const getHexFromNamedHTMLColor = (color: string): string =>\n namedHTMLColorToHex[color] ?? color;\n\nexport const getHexFromRgbaColor = (color: string): string => {\n // Works for rgb or rgba colors\n const [r, g, b, a] = color.replace(/^rgba?\\(|\\s+|\\)$/g, '').split(',');\n\n // Not valid rgb\n if (!r || !g || !b) return color;\n\n const rgbfloatValues = [r, g, b].map((val) => parseFloat(val));\n\n const convertedValues = [...rgbfloatValues];\n if (a) {\n const aFloat = parseFloat(a);\n const alphaNumberValue = Math.round(aFloat * 255);\n convertedValues.push(alphaNumberValue);\n }\n\n const finalHexValues = convertedValues\n .map((number) => number.toString(16).padStart(2, '0'))\n .join('');\n\n return `#${finalHexValues}`;\n};\n", "import type { BlockAlignment, BlockSizes } from '@devvit/protos';\nimport { BlockStackDirection } from '@devvit/protos';\n\nimport type { Devvit } from '../../Devvit.js';\n\nexport const ROOT_STACK_TRANSFORM_CONTEXT: TransformContext = {\n stackParentLayout: {\n hasHeight: true,\n hasWidth: true,\n direction: BlockStackDirection.UNRECOGNIZED,\n alignment: undefined,\n },\n};\n\nexport interface TransformContext {\n stackParentLayout?: StackParentLayout;\n}\n\nexport interface StackParentLayout {\n hasHeight: boolean;\n hasWidth: boolean;\n direction: BlockStackDirection;\n alignment: BlockAlignment | undefined;\n}\n\nenum ExpandDirection {\n NONE,\n HORIZONTAL,\n VERTICAL,\n}\n\nexport interface BlockGrowStretchDirection {\n growDirection: ExpandDirection;\n stretchDirection: ExpandDirection;\n}\n\nexport interface BlockDimensionsDetails {\n hasHeight: boolean;\n hasWidth: boolean;\n}\n\n/*\n Determine if a block has height and/or width based on its sizing, and its parent grow/stretch direction.\n*/\nexport function makeStackDimensionsDetails(\n props: Devvit.Blocks.StackProps | undefined,\n stackParentLayout: StackParentLayout | undefined,\n blockSizes: BlockSizes | undefined\n): BlockDimensionsDetails {\n if (!stackParentLayout) return { hasHeight: false, hasWidth: false };\n\n const { growDirection, stretchDirection } = makeBlockGrowStretchDetails(\n props?.grow,\n stackParentLayout.direction,\n stackParentLayout.alignment\n );\n\n const hasHeight =\n blockSizes?.height?.value?.value ||\n blockSizes?.height?.min?.value ||\n isExpandingOnConstrainedRespectiveAxis(\n ExpandDirection.VERTICAL,\n growDirection,\n stretchDirection,\n stackParentLayout.hasHeight\n );\n\n const hasWidth =\n blockSizes?.width?.value?.value ||\n blockSizes?.width?.min?.value ||\n isExpandingOnConstrainedRespectiveAxis(\n ExpandDirection.HORIZONTAL,\n growDirection,\n stretchDirection,\n stackParentLayout.hasWidth\n );\n\n return {\n hasHeight: Boolean(hasHeight),\n hasWidth: Boolean(hasWidth),\n };\n}\n\n/*\n Determine if the parent is growing or stretching on the defined axis.\n If true, tells us that the child may have height/width, even if its parent is not explicitly set.\n*/\nfunction isExpandingOnConstrainedRespectiveAxis(\n axis: ExpandDirection,\n growDirection: ExpandDirection,\n stretchDirection: ExpandDirection,\n parentHasDimensionSet: boolean\n): boolean {\n return (\n (growDirection === axis && parentHasDimensionSet) ||\n (stretchDirection === axis && parentHasDimensionSet)\n );\n}\n\n/*\n Determine the grow/stretch direction of a block based on its parent stack direction and alignment.\n*/\nfunction makeBlockGrowStretchDetails(\n isGrowing: boolean | undefined,\n parentStackDirection: BlockStackDirection,\n parentAlignment: BlockAlignment | undefined\n): BlockGrowStretchDirection {\n const parentIsVerticalOrRoot =\n parentStackDirection === BlockStackDirection.STACK_VERTICAL ||\n parentStackDirection === BlockStackDirection.UNRECOGNIZED;\n const parentIsHoritzontal = parentStackDirection === BlockStackDirection.STACK_HORIZONTAL;\n\n let growDirection = ExpandDirection.NONE;\n if (parentIsHoritzontal && isGrowing) {\n growDirection = ExpandDirection.HORIZONTAL;\n } else if (parentIsVerticalOrRoot && isGrowing) {\n growDirection = ExpandDirection.VERTICAL;\n }\n\n const hnone = parentAlignment === undefined || parentAlignment.horizontal === undefined;\n const vnone = parentAlignment === undefined || parentAlignment.vertical === undefined;\n\n const isStretching = parentIsHoritzontal\n ? Boolean(vnone)\n : parentIsVerticalOrRoot\n ? Boolean(hnone)\n : false;\n\n let stretchDirection = ExpandDirection.NONE;\n if (parentIsHoritzontal && isStretching) {\n stretchDirection = ExpandDirection.VERTICAL;\n } else if (parentIsVerticalOrRoot && isStretching) {\n stretchDirection = ExpandDirection.HORIZONTAL;\n }\n\n return {\n growDirection,\n stretchDirection,\n };\n}\n", "import type { BlockSizes, BlockSizes_Dimension_Value } from '@devvit/protos';\nimport { BlockSizeUnit, BlockStackDirection } from '@devvit/protos';\n\nimport type { Devvit } from '../../Devvit.js';\nimport type { StackParentLayout, TransformContext } from './transformContext.js';\n\nexport function parseSize(\n size: Devvit.Blocks.SizeString | undefined\n): BlockSizes_Dimension_Value | undefined {\n if (size == null) return undefined;\n if (typeof size === 'number') {\n return { value: size as number, unit: BlockSizeUnit.SIZE_UNIT_PERCENT };\n }\n\n // Regex:\n // Group 1: Digits with optional decimal trailer\n // Group 2: Optional suffix: 'px' or '%' (defaults to %)\n // eslint-disable-next-line security/detect-unsafe-regex\n const parts = size.match(/^(\\d+(?:\\.\\d+)?)(px|%)?$/);\n if (parts == null) {\n return undefined;\n }\n let unit = BlockSizeUnit.SIZE_UNIT_PERCENT;\n if (parts.at(2) === 'px') {\n unit = BlockSizeUnit.SIZE_UNIT_PIXELS;\n }\n const value = Number.parseFloat(parts[1]);\n return { value, unit };\n}\n\n/**\n * If a child has a relative size along its parent's main axis, but that parent's axis is not set, omit the dimension.\n * This is done to enforce consistency between web and Yoga (the layout engine used by mobile).\n * */\nexport function omitRelativeSizes(\n blockSizes: BlockSizes,\n stackParentLayout: StackParentLayout\n): BlockSizes {\n if (\n blockSizes.width?.value?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT ||\n blockSizes.width?.min?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT\n ) {\n if (\n stackParentLayout.direction === BlockStackDirection.STACK_HORIZONTAL ||\n stackParentLayout.direction === BlockStackDirection.STACK_DEPTH\n ) {\n if (!stackParentLayout.hasWidth) {\n blockSizes.width = undefined;\n }\n }\n }\n\n if (\n blockSizes.height?.value?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT ||\n blockSizes.height?.min?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT\n ) {\n if (\n stackParentLayout.direction === BlockStackDirection.STACK_VERTICAL ||\n stackParentLayout.direction === BlockStackDirection.STACK_DEPTH\n ) {\n if (!stackParentLayout.hasHeight) {\n blockSizes.height = undefined;\n }\n }\n }\n\n return blockSizes;\n}\n\nexport function makeBlockSizes(\n props: Devvit.Blocks.BaseProps | undefined,\n transformContext: TransformContext\n): BlockSizes | undefined {\n if (props) {\n const hasWidth = props.width != null || props.minWidth != null || props.maxWidth != null;\n const hasHeight = props.height != null || props.minHeight != null || props.maxHeight != null;\n if (hasWidth || hasHeight || props.grow != null) {\n let blockSizes: BlockSizes = {\n width: hasWidth\n ? {\n value: parseSize(props.width),\n min: parseSize(props.minWidth),\n max: parseSize(props.maxWidth),\n }\n : undefined,\n height: hasHeight\n ? {\n value: parseSize(props.height),\n min: parseSize(props.minHeight),\n max: parseSize(props.maxHeight),\n }\n : undefined,\n grow: props.grow,\n };\n\n if (transformContext.stackParentLayout) {\n blockSizes = omitRelativeSizes(blockSizes, transformContext.stackParentLayout);\n }\n\n return blockSizes;\n }\n }\n return undefined;\n}\n", "import type { Metadata, ValidateFormRequest, ValidateFormResponse } from '@devvit/protos';\nimport { GetFieldsResponse, InstallationSettingsDefinition } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { transformFormFields } from '../../apis/ui/helpers/transformForm.js';\nimport { Devvit } from '../Devvit.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport { onValidateFormHelper } from './helpers/settingsUtils.js';\n\nasync function onGetSettingsFields(): Promise<GetFieldsResponse> {\n if (!Devvit.installationSettings) {\n throw new Error('Installation settings were not defined.');\n }\n\n return GetFieldsResponse.fromJSON({\n fields: {\n fields: transformFormFields(Devvit.installationSettings),\n },\n });\n}\n\nasync function onValidateForm(\n req: ValidateFormRequest,\n metadata: Metadata\n): Promise<ValidateFormResponse> {\n return onValidateFormHelper(req, Devvit.installationSettings, metadata);\n}\n\nexport function registerInstallationSettings(config: Config): void {\n config.provides(InstallationSettingsDefinition);\n extendDevvitPrototype('GetSettingsFields', onGetSettingsFields);\n extendDevvitPrototype('ValidateForm', onValidateForm);\n}\n", "import type { ContextActionDescription, ContextActionRequest, Metadata } from '@devvit/protos';\nimport { ContextActionDefinition, ContextActionList, ContextActionResponse } from '@devvit/protos';\nimport type { Empty } from '@devvit/protos/types/google/protobuf/empty.js';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport { getEffectsFromUIClient } from '../../apis/ui/helpers/getEffectsFromUIClient.js';\nimport type { MenuItem, MenuItemOnPressEvent } from '../../types/index.js';\nimport { Devvit } from '../Devvit.js';\nimport { getContextFromMetadata } from './context.js';\nimport { addCSRFTokenToContext } from './csrf.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nconst getActionId = (index: number): string => {\n return `menuItem.${index}`;\n};\n\nexport function getMenuItemById(id: string): MenuItem | undefined {\n return Devvit.menuItems.find((_, index) => getActionId(index) === id);\n}\n\nasync function getActions(\n _: Empty,\n _metadata: Metadata | undefined\n): Promise<DeepPartial<ContextActionList>> {\n const menuItems = Devvit.menuItems;\n\n if (!menuItems.length) {\n throw new Error('No menu items registered.');\n }\n\n const actions: DeepPartial<ContextActionDescription>[] = menuItems.map((item, index) => {\n return {\n actionId: getActionId(index),\n name: item.label,\n description: item.description,\n contexts: {\n subreddit: item.location.includes('subreddit'),\n post: item.location.includes('post'),\n comment: item.location.includes('comment'),\n },\n users: {\n loggedOut: item.forUserType?.includes('loggedOut'),\n moderator: item.forUserType?.includes('moderator'),\n },\n postFilters: item.postFilter === 'currentApp' ? { currentApp: true } : undefined,\n };\n });\n\n return ContextActionList.fromJSON({ actions });\n}\n\nasync function onAction(\n req: ContextActionRequest,\n metadata: Metadata\n): Promise<ContextActionResponse> {\n const menuItem = getMenuItemById(req.actionId);\n\n if (!menuItem) {\n throw new Error(`MenuItem ${req.actionId} not found`);\n }\n\n const commentId = req.comment?.id && `t1_${req.comment.id}`;\n const postId = req.post?.id && `t3_${req.post.id}`;\n const subredditId = req.subreddit?.id && `t5_${req.subreddit.id}`;\n const targetId = commentId || postId || subredditId;\n assertNonNull(targetId, 'targetId is missing from ContextActionRequest');\n\n const event: MenuItemOnPressEvent = {\n targetId,\n location: req.comment ? 'comment' : req.post ? 'post' : 'subreddit',\n };\n\n const context = Object.assign(\n makeAPIClients({\n ui: true,\n metadata,\n }),\n getContextFromMetadata(metadata, postId, commentId),\n {\n uiEnvironment: {\n timezone: metadata[Header.Timezone]?.values[0],\n locale: metadata[Header.Language]?.values[0],\n },\n }\n );\n\n await addCSRFTokenToContext(context, req);\n await menuItem.onPress(event, context);\n\n return ContextActionResponse.fromJSON({\n effects: getEffectsFromUIClient(context.ui),\n });\n}\n\nexport function registerMenuItems(config: Config): void {\n config.provides(ContextActionDefinition);\n extendDevvitPrototype('GetActions', getActions);\n extendDevvitPrototype('OnAction', onAction);\n}\n", "import type { ContextActionRequest, HandleUIEventRequest } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\n\nimport {\n getCommentById,\n getModerators,\n getPostById,\n getSubredditInfoById,\n} from '../../apis/reddit/index.js';\nimport type { BaseContext, ContextAPIClients } from '../../types/context.js';\nimport { getMenuItemById } from './menu-items.js';\n\ntype CSRF = {\n needsModCheck: boolean;\n};\n\nfunction getUserHeader(context: BaseContext & ContextAPIClients): string {\n const userHeader = context.metadata[Header.User].values[0];\n if (!userHeader) {\n throw new Error('User missing from context');\n }\n return userHeader;\n}\n\nfunction getSubredditHeader(context: BaseContext & ContextAPIClients): string {\n const subredditHeader = context.metadata[Header.Subreddit].values[0];\n if (!subredditHeader) {\n throw new Error('Subreddit missing from context');\n }\n return subredditHeader;\n}\n\nexport async function addCSRFTokenToContext(\n context: BaseContext & ContextAPIClients,\n req: ContextActionRequest\n) {\n /**\n * These headers represent the canonical user and subreddit for the current context. The user is validated by gateway against\n * the edge context, so we trust it.\n */\n const userHeader = getUserHeader(context);\n const subredditHeader = getSubredditHeader(context);\n\n if (!userHeader || !subredditHeader) {\n throw new Error('User or subreddit missing from context');\n }\n\n /**\n * These are the target ids for the action. They are provided by the user, but we need to validate them.\n */\n const commentId = req.comment?.id && `t1_${req.comment.id}`;\n const postId = req.post?.id && `t3_${req.post.id}`;\n const subredditId = req.subreddit?.id && `t5_${req.subreddit.id}`;\n const targetId = commentId || postId || subredditId;\n\n if (!targetId) {\n throw new Error('targetId is missing from ContextActionRequest');\n }\n\n /**\n * Validate user-provided commentId\n */\n if (commentId) {\n const comment = await getCommentById(commentId);\n if (!comment) {\n throw new Error(`Comment ${commentId} not found`);\n }\n if (comment.subredditId !== subredditHeader) {\n throw new Error(`Comment does not belong to the subreddit`);\n }\n }\n\n /**\n * Validate user-provided postId\n */\n if (postId) {\n const post = await getPostById(postId);\n if (!post) {\n throw new Error(`Post ${postId} not found`);\n }\n if (post.subredditId !== subredditHeader) {\n throw new Error(`Post does not belong to the subreddit`);\n }\n }\n\n /**\n * Validate user-provided subredditId\n */\n if (subredditId && subredditId !== subredditHeader) {\n throw new Error(`Subreddit ${subredditId} ${subredditHeader} not found`);\n }\n\n /**\n * Validate moderator status if needed\n */\n const thisItem = getMenuItemById(req.actionId);\n if (!thisItem) {\n throw new Error('Action not found');\n }\n const needsModCheck = !!thisItem.forUserType?.includes('moderator');\n if (needsModCheck && !isModerator(context)) {\n throw new Error('User is not a moderator');\n }\n\n const val: CSRF = {\n needsModCheck,\n };\n\n /**\n * Store a CSRF token in redis for this action. There's no way to pass this along, so we need to store it in redis.\n */\n await context.redis.set(\n `${userHeader}${subredditHeader}${targetId}${req.actionId}`,\n JSON.stringify(val)\n );\n await context.redis.expire(`${userHeader}${subredditHeader}${targetId}${req.actionId}`, 600);\n}\n\nasync function isModerator(context: BaseContext & ContextAPIClients) {\n const userHeader = getUserHeader(context);\n const subredditHeader = getSubredditHeader(context);\n\n const subreddit = await getSubredditInfoById(subredditHeader);\n const mods = await getModerators({ subredditName: subreddit.name! }).all();\n return !!mods.find((mod) => mod.id === userHeader);\n}\n\nexport async function validateCSRFToken(\n context: BaseContext & ContextAPIClients,\n req: HandleUIEventRequest\n) {\n const userHeader = getUserHeader(context);\n const subredditHeader = getSubredditHeader(context);\n const { actionId, thingId } = req.state?.__contextAction ?? {};\n const csrfData = await context.redis.get(`${userHeader}${subredditHeader}${thingId}${actionId}`);\n if (!csrfData) {\n throw new Error('CSRF token not found');\n }\n const csrf = JSON.parse(csrfData) as CSRF;\n if (csrf.needsModCheck && !(await isModerator(context))) {\n throw new Error('User is not a moderator: ' + userHeader + '; ' + subredditHeader);\n }\n}\n", "import type { PluginSettings } from '../../types/index.js';\n\nexport function pluginIsEnabled(settings: PluginSettings | boolean | undefined): boolean {\n if (!settings) {\n return false;\n }\n\n if (settings === true) {\n return true;\n }\n\n return settings.enabled;\n}\n", "import type { Metadata, ScheduledAction } from '@devvit/protos';\nimport { SchedulerHandlerDefinition } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport { Devvit } from '../Devvit.js';\nimport { getContextFromMetadata } from './context.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nasync function handleScheduledAction(args: ScheduledAction, metadata: Metadata): Promise<void> {\n const jobName = args.type;\n const scheduledJobHandler = Devvit.scheduledJobHandlers.get(jobName);\n\n if (!scheduledJobHandler) {\n throw new Error(`Job ${jobName} not found`);\n }\n\n const event = {\n name: jobName,\n data: args.data,\n };\n\n const context = Object.assign(\n makeAPIClients({\n metadata,\n }),\n getContextFromMetadata(metadata)\n );\n\n await scheduledJobHandler(event, context);\n}\n\nexport function registerScheduler(config: Config): void {\n config.provides(SchedulerHandlerDefinition);\n extendDevvitPrototype('HandleScheduledAction', handleScheduledAction);\n}\n", "import type { Metadata } from '@devvit/protos';\nimport {\n HandlerResult,\n OnAppInstallDefinition,\n OnAppUpgradeDefinition,\n OnAutomoderatorFilterCommentDefinition,\n OnAutomoderatorFilterPostDefinition,\n OnCommentCreateDefinition,\n OnCommentDeleteDefinition,\n OnCommentReportDefinition,\n OnCommentSubmitDefinition,\n OnCommentUpdateDefinition,\n OnModActionDefinition,\n OnModMailDefinition,\n OnPostCreateDefinition,\n OnPostDeleteDefinition,\n OnPostFlairUpdateDefinition,\n OnPostNsfwUpdateDefinition,\n OnPostReportDefinition,\n OnPostSpoilerUpdateDefinition,\n OnPostSubmitDefinition,\n OnPostUpdateDefinition,\n} from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport { StringUtil } from '@devvit/shared-types/StringUtil.js';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport type { TriggerEvent, TriggerOnEventHandler } from '../../types/triggers.js';\nimport { Devvit } from '../Devvit.js';\nimport { getContextFromMetadata } from './context.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nfunction createCombinedHandler<Arg>(\n eventType: TriggerEvent,\n handlers: TriggerOnEventHandler<Arg>[] | undefined\n): (arg: Arg, metadata: Metadata) => Promise<HandlerResult> {\n assertNonNull(handlers);\n return async (arg, metadata) => {\n const event = {\n ...arg,\n type: eventType,\n };\n\n const context = Object.assign(\n makeAPIClients({\n metadata,\n }),\n getContextFromMetadata(metadata)\n );\n\n // Users can set multiple triggers for a single event. An error in one\n // shouldn't technically impact another but there's no actual guarantees\n // made around this.\n const results = await Promise.allSettled(handlers.map((fn) => fn(event, context)));\n // Throw any failed promises so that the EnvelopeClient will surface them to\n // the user.\n const errResult = joinSettledErrors(results);\n if (errResult) throw errResult.err;\n\n return {};\n };\n}\n\nexport function registerTriggers(config: Config): void {\n for (const event of Devvit.triggerOnEventHandlers.keys()) {\n switch (event) {\n case 'PostSubmit':\n config.provides(OnPostSubmitDefinition);\n extendDevvitPrototype(\n 'OnPostSubmit',\n createCombinedHandler('PostSubmit', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostCreate':\n config.provides(OnPostCreateDefinition);\n extendDevvitPrototype(\n 'OnPostCreate',\n createCombinedHandler('PostCreate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostUpdate':\n config.provides(OnPostUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostUpdate',\n createCombinedHandler('PostUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostReport':\n config.provides(OnPostReportDefinition);\n extendDevvitPrototype(\n 'OnPostReport',\n createCombinedHandler('PostReport', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostDelete':\n config.provides(OnPostDeleteDefinition);\n extendDevvitPrototype(\n 'OnPostDelete',\n createCombinedHandler('PostDelete', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostFlairUpdate':\n config.provides(OnPostFlairUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostFlairUpdate',\n createCombinedHandler('PostFlairUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentSubmit':\n config.provides(OnCommentSubmitDefinition);\n extendDevvitPrototype(\n 'OnCommentSubmit',\n createCombinedHandler('CommentSubmit', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentCreate':\n config.provides(OnCommentCreateDefinition);\n extendDevvitPrototype(\n 'OnCommentCreate',\n createCombinedHandler('CommentCreate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentUpdate':\n config.provides(OnCommentUpdateDefinition);\n extendDevvitPrototype(\n 'OnCommentUpdate',\n createCombinedHandler('CommentUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentReport':\n config.provides(OnCommentReportDefinition);\n extendDevvitPrototype(\n 'OnCommentReport',\n createCombinedHandler('CommentReport', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentDelete':\n config.provides(OnCommentDeleteDefinition);\n extendDevvitPrototype(\n 'OnCommentDelete',\n createCombinedHandler('CommentDelete', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AppInstall':\n config.provides(OnAppInstallDefinition);\n extendDevvitPrototype(\n 'OnAppInstall',\n createCombinedHandler('AppInstall', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AppUpgrade':\n config.provides(OnAppUpgradeDefinition);\n extendDevvitPrototype(\n 'OnAppUpgrade',\n createCombinedHandler('AppUpgrade', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'ModAction':\n config.provides(OnModActionDefinition);\n extendDevvitPrototype(\n 'OnModAction',\n createCombinedHandler('ModAction', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'ModMail':\n config.provides(OnModMailDefinition);\n extendDevvitPrototype(\n 'OnModMail',\n createCombinedHandler('ModMail', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostNsfwUpdate':\n config.provides(OnPostNsfwUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostNsfwUpdate',\n createCombinedHandler('PostNsfwUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostSpoilerUpdate':\n config.provides(OnPostSpoilerUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostSpoilerUpdate',\n createCombinedHandler('PostSpoilerUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AutomoderatorFilterPost':\n config.provides(OnAutomoderatorFilterPostDefinition);\n extendDevvitPrototype(\n 'OnAutomoderatorFilterPost',\n createCombinedHandler('AutomoderatorFilterPost', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AutomoderatorFilterComment':\n config.provides(OnAutomoderatorFilterCommentDefinition);\n extendDevvitPrototype(\n 'OnAutomoderatorFilterComment',\n createCombinedHandler(\n 'AutomoderatorFilterComment',\n Devvit.triggerOnEventHandlers.get(event)\n )\n );\n break;\n\n default:\n throw new Error(`Unknown trigger event: ${event}`);\n }\n }\n}\n\nfunction joinSettledErrors(results: PromiseSettledResult<unknown>[]): { err: unknown } | undefined {\n const errs = results.reduce(\n (sum, result) => (result.status === 'rejected' ? [...sum, result.reason] : sum),\n [] as unknown[]\n );\n if (errs.length === 0) return;\n if (errs.length === 1) return { err: errs[0] }; // Return original error.\n // The return type is a wrapper just to prevent ambiguity over nullish\n // rejections which some parts of our code have mistakenly done historically.\n return { err: new Error(errs.map((err) => StringUtil.caughtToString(err)).join('\\n')) };\n}\n", "export var StringUtil;\n(function (StringUtil) {\n /**\n * Returns a string, truncated as needed, of length limit or less. When\n * truncated, appends \"\u2026\".\n */\n function ellipsize(str, limit) {\n return str.length <= limit ? str : `${str.slice(0, limit - 1)}\u2026`;\n }\n StringUtil.ellipsize = ellipsize;\n /** Converts the first character of str to uppercase. */\n function capitalize(str) {\n if (str[0] == null)\n return str;\n return `${str[0].toLocaleUpperCase()}${str.slice(1)}`;\n }\n StringUtil.capitalize = capitalize;\n /** Returns true if string is nullish, empty, or whitespace-only. */\n function isBlank(str) {\n return str == null || /^\\s*$/.test(str);\n }\n StringUtil.isBlank = isBlank;\n /**\n * Converts an unknown type to a verbose stacktrace if an error or string form\n * otherwise.\n */\n function caughtToString(val, preferredErrorProperty = 'stack') {\n return val instanceof Error\n ? `${val[preferredErrorProperty] || val.stack || val.message || val.name}`\n : String(val);\n }\n StringUtil.caughtToString = caughtToString;\n /**\n * Converts an unknown type to a verbose stacktrace if an error or string form\n * otherwise. Prefer caughtToString where possible for typing reasons.\n */\n function caughtToStringUntyped(val, preferredErrorProperty = 'stack') {\n return val instanceof Error\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n `${val[preferredErrorProperty] || val.stack || val.message || val.name}`\n : String(val);\n }\n StringUtil.caughtToStringUntyped = caughtToStringUntyped;\n})(StringUtil || (StringUtil = {}));\n", "import type { HandleUIEventRequest, Metadata } from '@devvit/protos';\nimport { EffectType, HandleUIEventResponse, UIEventHandlerDefinition } from '@devvit/protos';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\nimport cloneDeep from 'clone-deep';\nimport { isEqual } from 'moderndash';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport { getEffectsFromUIClient } from '../../apis/ui/helpers/getEffectsFromUIClient.js';\nimport { getFormValues } from '../../apis/ui/helpers/getFormValues.js';\nimport { Devvit } from '../Devvit.js';\nimport { BlocksReconciler } from './blocks/BlocksReconciler.js';\nimport { getContextFromMetadata } from './context.js';\nimport { validateCSRFToken } from './csrf.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport { getMenuItemById } from './menu-items.js';\n\nasync function handleUIEvent(\n req: HandleUIEventRequest,\n metadata: Metadata\n): Promise<DeepPartial<HandleUIEventResponse>> {\n // Keep track of the original state so we can check if it was updated.\n const originalState = req.state ?? {};\n const state = cloneDeep(originalState);\n\n const apiClients = makeAPIClients({\n ui: true,\n metadata,\n });\n\n if (req.event?.formSubmitted && req.event.formSubmitted.formId) {\n const formKey = req.event.formSubmitted.formId as FormKey;\n\n if (formKey.includes('form.hook.')) {\n if (Devvit.customPostType) {\n const blocksReconciler = new BlocksReconciler(\n (_props: {}, context: Devvit.Context) => Devvit.customPostType?.render(context) ?? null,\n req.event,\n req.state,\n metadata,\n undefined\n );\n\n await blocksReconciler.reconcile();\n\n return HandleUIEventResponse.fromJSON({\n state: blocksReconciler.state,\n effects: blocksReconciler.getEffects(),\n });\n }\n }\n\n const formDefinition = Devvit.formDefinitions.get(formKey);\n\n if (!formDefinition) {\n throw new Error(`Form with key ${formKey} not found`);\n }\n\n let postId: string | undefined;\n let commentId: string | undefined;\n\n if (state.__contextAction) {\n const { actionId, thingId } = state.__contextAction;\n const menuItem = getMenuItemById(actionId);\n\n if (menuItem?.location === 'post') {\n postId = thingId;\n } else if (menuItem?.location === 'comment') {\n commentId = thingId;\n }\n }\n\n const context: Devvit.Context = Object.assign(\n apiClients,\n getContextFromMetadata(metadata, postId, commentId),\n {\n uiEnvironment: {\n timezone: metadata[Header.Timezone]?.values[0],\n locale: metadata[Header.Language]?.values[0],\n },\n }\n );\n\n await validateCSRFToken(context, req);\n\n await formDefinition.onSubmit(\n {\n values: getFormValues(req.event.formSubmitted.results),\n },\n context\n );\n } else if (req.event?.realtimeEvent) {\n if (Devvit.customPostType) {\n const blocksReconciler = new BlocksReconciler(\n (_props: {}, context: Devvit.Context) => Devvit.customPostType?.render(context) ?? null,\n req.event,\n req.state,\n metadata,\n undefined\n );\n\n await blocksReconciler.reconcile();\n\n return HandleUIEventResponse.fromJSON({\n state: blocksReconciler.state,\n effects: blocksReconciler.getEffects(),\n });\n }\n } else if (req.event?.toastAction) {\n throw new Error('Toast actions not yet implemented');\n }\n\n // Check if the state was updated to determine if we need to rerender.\n const stateWasUpdated = !isEqual(originalState, state);\n\n const uiEffects = getEffectsFromUIClient(apiClients.ui);\n const effects = stateWasUpdated\n ? [\n ...uiEffects,\n {\n type: EffectType.EFFECT_RERENDER_UI,\n rerenderUi: {\n delaySeconds: 0,\n },\n },\n ]\n : uiEffects;\n\n return HandleUIEventResponse.fromJSON({\n state,\n effects,\n });\n}\n\nexport function registerUIEventHandler(config: Config): void {\n config.provides(UIEventHandlerDefinition);\n extendDevvitPrototype('HandleUIEvent', handleUIEvent);\n}\n", "/**\n * Creates an array of elements split into groups the length of size. If array can't be split evenly, the final chunk will be the remaining elements.\n *\n * @example\n * chunk(['a', 'b', 'c', 'd'], 2)\n * // => [['a', 'b'], ['c', 'd']]\n *\n * chunk(['a', 'b', 'c', 'd'], 3)\n * // => [['a', 'b', 'c'], ['d']]\n * @param chunkSize The length of each chunk\n * @param array The array to chunk\n * @template TElem The type of the array elements\n * @returns Returns the new array of chunks\n */\n\nexport function chunk<TElem>(array: readonly TElem[], chunkSize: number): TElem[][] {\n const intSize = Math.trunc(chunkSize);\n\n if (array.length === 0 || intSize < 1)\n return [];\n \n let index = 0;\n let resultIndex = 0;\n const result = new Array(Math.ceil(array.length / intSize)) as TElem[][];\n\n while (index < array.length) {\n result[resultIndex++] = array.slice(index, (index += intSize));\n }\n\n return result;\n}", "/**\n * Creates an object with counts of occurrences of items in the array.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': true, age: 36 },\n * { 'user': 'betty', 'active': false, age: 36 },\n * { 'user': 'fred', 'active': true, age: 40 }\n * ]\n *\n * count(users, value => value.active ? 'active' : 'inactive');\n * // => { 'active': 2, 'inactive': 1 }\n *\n * count(users, value => value.age);\n * // => { 36: 2, 40: 1 }\n *\n * @param criteria The criteria to count by\n * @param array The array or record to iterate over\n * @template TElem The type of the array elements\n * @returns Returns the composed aggregate object\n */\n\nexport function count<TElem, TKey extends PropertyKey>(array: readonly TElem[], criteria: (value: TElem) => TKey): Record<TKey, number> {\n const result = {} as Record<TKey, number>;\n for (const value of array) {\n const key = criteria(value);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n result[key] = (result[key] ?? 0) + 1;\n }\n return result;\n}", "// native Array.flat is much slower than this - node 19\nexport function fastArrayFlat<TElem>(arrays: (readonly TElem[])[]): readonly TElem[] {\n let result = arrays.shift() ?? [];\n\n for (const array of arrays) {\n result = [...result, ...array];\n }\n\n return result;\n}", "import type { CompareFunction } from \"@helpers/ArrayTypeUtils.js\";\nimport type { ArrayMinLength } from \"@type/ArrayMinLength.js\";\n\nimport { fastArrayFlat } from \"@helpers/fastArrayFlat.js\";\n\n/**\n * Create a new array with values from the first array that are not present in the other arrays.\n * Optionally, use a compare function to determine the comparison of elements (default is `===`).\n * \n * **Consider using the native [Set.prototype.difference()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/difference) function instead.**\n * \n * @example\n * difference([2, 1], [2, 3], [6])\n * // => [1]\n *\n * // ---- Custom compare function ----\n * const compareByFloor = (a, b) => Math.floor(a) === Math.floor(b);\n * difference([1.2, 3.1], [1.3, 2.4], compareByFloor)\n * // => [3.1]\n *\n * // ---- Only compare by id ----\n * const arr1 = [{ id: 1, name: 'Yeet' }, { id: 3, name: 'John' }];\n * const arr2 = [{ id: 3, name: 'Carl' }, { id: 4, name: 'Max' }];\n *\n * difference(arr1, arr2, (a, b) => a.id === b.id)\n * // => [{ id: 1, name: 'Yeet' }]\n * \n * @param arraysOrCompareFn Two or more arrays with an optional compare function at the end\n * @template TElem The type of the array elements\n * @template TArrays The type of the arrays provided\n * @returns Returns a new array of filtered values\n */\nexport function difference<TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2>): TElem[];\nexport function difference<TArrays extends ArrayMinLength<unknown[], 2>>(...arraysOrCompareFn: [...TArrays, CompareFunction<TArrays>]): TArrays[0];\nexport function difference<TArrays extends ArrayMinLength<unknown[], 2>, TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2> | [...TArrays, CompareFunction<TArrays>]): TArrays[0] {\n const compareFnProvided = typeof arraysOrCompareFn.at(-1) === \"function\";\n const compareFunction = compareFnProvided && arraysOrCompareFn.pop() as CompareFunction<TArrays>;\n\n const arrays = arraysOrCompareFn as TArrays;\n const firstArray = arrays.shift()!;\n const combinedRestArray = fastArrayFlat(arrays);\n\n if (!compareFunction) {\n const restSet = new Set(combinedRestArray);\n return firstArray.filter(element => !restSet.has(element));\n }\n\n const difference: TArrays[0] = [];\n for (const element of firstArray) {\n if (combinedRestArray.every(item => !compareFunction(element, item))) {\n difference.push(element);\n }\n }\n\n return difference;\n}\n", "/**\n * Creates a slice of `array` excluding elements dropped from the end. \n * Elements are dropped until `predicate` returns falsy.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': false },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': true }\n * ]\n *\n * dropRightWhile(users, user => user.active)\n * // => objects for ['barney']\n * @param predicate The function invoked per iteration\n * @param array The array to query\n * @template TElem The type of the array elements\n * @returns Returns the slice of `array`\n */\n\nexport function dropRightWhile<TElem>(array: readonly TElem[], predicate: (value: TElem) => boolean) {\n let i = array.length;\n while (i > 0 && predicate(array[i - 1])) {\n i--;\n }\n return array.slice(0, i);\n}\n", "/**\n * Creates a slice of `array` excluding elements dropped from the beginning. \n * Elements are dropped until `predicate` returns falsy.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': true },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': false }\n * ]\n *\n * dropWhile(users, user => user.active)\n * // => objects for ['pebbles']\n * @param predicate The function invoked per iteration\n * @param array The array to query\n * @template TElem The type of the array elements\n * @returns Returns the slice of `array`\n */\n\nexport function dropWhile<TElem>(array: readonly TElem[], predicate: (value: TElem) => boolean): TElem[] {\n const index = array.findIndex(x => !predicate(x));\n return array.slice(index === -1 ? array.length : index);\n}\n", "/**\n * Creates an object with grouped items in the array.\n * \n * @deprecated\n * **Deprecated: Use the native \"Object.groupBy()\" function instead.**\n * \n * @example\n * group([6.1, 4.2, 6.3], Math.floor)\n * // => { 4: [4.2], 6: [6.1, 6.3] }\n *\n * group([6.1, 4.2, 6.3], value => value > 5 ? '>5' : '<=5')\n * // => { '<=5': [4.2], '>5': [6.1, 6.3] }\n * \n * @param collection The array or object to iterate over\n * @param getGroupKey A function that returns the group id for each item\n * @template TElem The type of the array elements\n * @returns An object with grouped items\n */\n\nexport function group<TElem, TKey extends PropertyKey>(array: readonly TElem[], getGroupKey: (elem: TElem) => TKey): Record<TKey, TElem[]> {\n const result = {} as Record<TKey, TElem[]>;\n for (const elem of array) {\n const key = getGroupKey(elem);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (result[key] ??= []).push(elem);\n }\n return result;\n}", "/**\n * Creates unique array retaining first occurrence of elements.\n *\n * A compare function is optional (default is `===`).\n * \n * @example\n * unique([2, 1, 2])\n * // => [2, 1]\n * \n * // compare by object values\n * const users = [\n * { id: 1, name: 'john' },\n * { id: 2, name: 'john' },\n * { id: 2, name: 'john' },\n * ]\n * \n * unique(users, isEqual)\n * // => [{ id: 1, name: 'john' }, { id: 2, name: 'john' }]\n * \n * // compare by id\n * unique(users, (a, b) => a.name === b.name)\n * // => [{ id: 1, name: 'john' }]\n *\n * @param array Array to inspect\n * @param iteratee Iteratee invoked per element\n * @template TElem Type of the array elements\n * @returns A new unique array\n */\n\nexport function unique<TElem>(array: readonly TElem[], compareFn?: (a: TElem, b: TElem) => boolean): TElem[] {\n if (!compareFn)\n return [...new Set(array)];\n\n // Custom compare function can't be optimized with Set\n const uniqueArray: TElem[] = [];\n\n for (const value of array) {\n if (!uniqueArray.some(uniqueValue => compareFn(value, uniqueValue)))\n uniqueArray.push(value);\n }\n \n return uniqueArray;\n}\n", "import type { CompareFunction } from \"@helpers/ArrayTypeUtils.js\";\nimport type { ArrayMinLength } from \"@type/ArrayMinLength.js\";\n\nimport { fastArrayFlat } from \"@helpers/fastArrayFlat.js\";\n\nimport { unique } from \"./unique.js\";\n\n/**\n * Create an array with unique values that are present in all arrays. \n * The order of the values is based on the first array. \n * \n * Optionally, use a compare function for element comparison (default is `===`).\n * \n * **Consider using the native [Set.prototype.intersection()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/intersection) function instead.**\n * \n * @example\n * intersection([2, 1], [2, 3], [6, 2])\n * // => [2]\n *\n * // ---- Custom compare function ----\n * const compareFn = (a, b) => Math.floor(a) === Math.floor(b);\n * \n * intersection([1.2, 1.1], [1.3, 2.4], compareFn)\n * // => [1.2]\n *\n * // ---- Only compare by id ----\n * const arr1 = [{ id: 1, name: 'Yeet' }, { id: 3, name: 'John' }];\n * const arr2 = [{ id: 3, name: 'Carl' }, { id: 4, name: 'Max' }];\n *\n * intersection(arr1, arr2, (a, b) => a.id === b.id)\n * // => [{ id: 3, name: 'John' }]\n * \n * @param arraysOrCompareFn Two or more arrays with an optional compare function at the end\n * @template TElem Type of the array elements\n * @template TArrays Type of the arrays provided\n * @returns New array of intersecting values\n */\n\nexport function intersection<TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2>): TElem[];\nexport function intersection<TArrays extends ArrayMinLength<unknown[], 2>>(...arraysOrCompareFn: [...TArrays, CompareFunction<TArrays>]): TArrays[0];\nexport function intersection<TArrays extends ArrayMinLength<unknown[], 2>, TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2> | [...TArrays, CompareFunction<TArrays>]): TArrays[0] {\n const compareFnProvided = typeof arraysOrCompareFn.at(-1) === \"function\";\n const compareFunction = compareFnProvided && arraysOrCompareFn.pop() as CompareFunction<TArrays>;\n\n const arrays = arraysOrCompareFn as TArrays;\n const firstArray = unique(arrays.shift()!);\n const combinedRestArray = fastArrayFlat(arrays);\n\n if (!compareFunction) {\n const restSet = new Set(combinedRestArray);\n return firstArray.filter(element => restSet.has(element));\n }\n \n const intersection: TArrays[0] = [];\n\n for (const element of firstArray) {\n if (combinedRestArray.some(item => compareFunction(element, item))) {\n intersection.push(element);\n }\n }\n\n return intersection;\n}\n", "/**\n * Moves an element within an array.\n * \n * @example\n * ```typescript\n * move([1, 2, 3, 4, 5], 0, 2);\n * // => [2, 3, 1, 4, 5]\n * ```\n * \n * @param array The input array\n * @param fromIndex Index of the element to move\n * @param toIndex Target index for the element\n * @throws If index is out of bounds\n * @template TArr Type of the array elements\n * @returns The modified array with the moved element\n */\nexport function move<TArr>(array: TArr[], fromIndex: number, toIndex: number): TArr[] {\n if (fromIndex < 0 || fromIndex >= array.length)\n throw new Error(`Invalid 'fromIndex': ${fromIndex}. Must be between 0 and ${array.length - 1}.`);\n\n if (toIndex < 0 || toIndex >= array.length)\n throw new Error(`Invalid 'toIndex': ${toIndex}. Must be between 0 and ${array.length - 1}.`);\n\n if (fromIndex === toIndex)\n return array;\n\n const item = array[fromIndex];\n\n if (fromIndex < toIndex)\n for (let index = fromIndex; index < toIndex; index++)\n array[index] = array[index + 1];\n else\n for (let index = fromIndex; index > toIndex; index--)\n array[index] = array[index - 1];\n\n array[toIndex] = item;\n\n return array;\n}", "/**\n * Creates an array from start to end (inclusive), stepping by step. \n * If start is larger than end, the array is generated in reverse\n *\n * @example\n * for (const num of range(1, 5)) {\n * console.log(num);\n * }\n * // => 1 2 3 4 5\n * \n * // Array of even numbers between 0 and 10:\n * range(0, 10, 2);\n * // => [0, 2, 4, 6, 8, 10]\n * \n * // Descending range:\n * range(5, 0, 2);\n * // => [5, 3, 1]\n * \n * @param start Start number of sequence\n * @param end End number of sequence\n * @param step Step between numbers, default: 1\n * @throws If range is negative or step is 0\n * @returns An array of numbers\n */\nexport function range(start: number, end: number, step = 1): number[] {\n if (step <= 0)\n throw new Error(\"The step must be greater than 0.\");\n\n step = start > end ? -step : step;\n const length = Math.floor(Math.abs((end - start) / step)) + 1;\n\n const result = new Array(length) as number[];\n \n for (let i = 0; i < length; i++) {\n result[i] = start + (i * step);\n }\n\n return result;\n}", "/**\n * Creates a new array of shuffled values, using the Fisher-Yates-Durstenfeld Shuffle algorithm.\n *\n * @example\n * shuffle([1, 2, 3, 4])\n * // => [4, 1, 3, 2]\n * @param array Array to shuffle\n * @template TElem The type of the array elements\n * @returns A new shuffled array\n */\n\nexport function shuffle<TElem>(array: readonly TElem[]): TElem[] {\n const shuffledArray = [...array];\n \n for (let index = shuffledArray.length - 1; index > 0; index--) {\n const randomIndex = Math.floor(Math.random() * (index + 1));\n [shuffledArray[index], shuffledArray[randomIndex]] = [shuffledArray[randomIndex], shuffledArray[index]];\n }\n \n return shuffledArray;\n}", "\n/**\n * Creates new array sorted in ascending/descending order with single or multiple criteria.\n * \n * @example\n * sort([1, 2, 3, 4], { order: 'desc' })\n * // => [4, 3, 2, 1]\n * \n * // --- Sorting by multiple properties ---\n * const array = [{ a: 2, b: 1 }, { a: 1, b: 2 }, { a: 1, b: 1 }];\n * \n * sort(array,\n * { order: 'asc', by: item => item.a },\n * { order: 'desc', by: item => item.b }\n * )\n * // => [{ a: 1, b: 2 }, { a: 1, b: 1 }, { a: 2, b: 1 }]\n * \n * @param array Array to sort\n * @param criteria Criteria to sort by\n * @param criteria.order Order to sort in, either 'asc' or 'desc'\n * @param criteria.by Iteratee function to sort based on a specific property\n * @template TElem Type of the array elements\n * @returns New sorted array\n*/\nexport function sort<TElem>(array: readonly TElem[], ...criteria: { order?: \"asc\" | \"desc\", by?: (item: TElem) => number | bigint | Date | string }[]): TElem[] {\n return [...array].sort((a, b) => {\n for (const { order = \"asc\", by = (item: TElem) => item } of criteria) {\n const aValue = by(a);\n const bValue = by(b);\n if (aValue !== bValue) {\n const compare = aValue < bValue ? -1 : 1;\n return order === \"asc\" ? compare : -compare;\n }\n }\n return 0;\n });\n}\n", "/**\n * Creates a slice of `array` with elements taken from the end. \n * Elements are taken until `predicate` returns falsy.\n * \n * @example\n * const users = [\n * { 'user': 'barney', 'active': false },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': true }\n * ]\n *\n * takeRightWhile(users, user => user.active)\n * // => objects for ['fred', 'pebbles']\n * @param predicate The function invoked per iteration.\n * @param array The array to query.\n * @template TElem The type of the array elements.\n * @returns Returns the slice of `array`.\n */\n\nexport function takeRightWhile<TElem>(array: readonly TElem[], predicate: (elem: TElem) => boolean): TElem[] {\n const result: TElem[] = [];\n\n for (let i = array.length - 1; i >= 0; i--) {\n if (predicate(array[i])) {\n result.unshift(array[i]);\n } else {\n break;\n }\n }\n\n return result;\n}\n", "/**\n * Creates a slice of `array` with elements taken from the beginning. \n * Elements are taken until `predicate` returns falsy.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': true },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': false }\n * ]\n *\n * takeWhile(users, user => user.active)\n * // => objects for ['barney', 'fred']\n * @param predicate The function invoked per iteration.\n * @param array The array to query.\n * @template TElem The type of the array elements.\n * @returns A new array of taken elements.\n */\n\nexport function takeWhile<TElem>(array: readonly TElem[], predicate: (elem: TElem) => boolean): TElem[] {\n const result: TElem[] = [];\n\n for (const element of array) {\n if (predicate(element)) {\n result.push(element);\n } else {\n break;\n }\n }\n\n return result;\n}\n", "import type { Jsonifiable } from \"@type/Jsonifiable.js\";\n\ntype SupportedAlgorithms = \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\nlet textEncoder: TextEncoder | undefined;\n\n/**\n * Generates a hash of the given data using the specified algorithm.\n *\n * It uses the Web Crypto API to generate the hash.\n * \n * *Note: If you need a secure hash use a specialized library like [crypto-js](https://www.npmjs.com/package/crypto-js) instead.*\n * \n * @example\n * // Hash a string using the default algorithm (SHA-256)\n * await hash('hello world'); \n * // => \"b94d27b9934d3e08a52e52d7da7dabfac484efe37a53...\"\n *\n * // Hash an object using the SHA-512 algorithm\n * await hash({ foo: 'bar', baz: 123 }, 'SHA-512');\n * // => \"d8f3c752c6820e580977099368083f4266b569660558...\"\n * \n * @param data The data to hash, either as a string or a JSON-serializable object.\n * @param algorithm The hashing algorithm to use. Defaults to 'SHA-256'.\n * @returns A Promise that resolves to the hexadecimal representation of the hash.\n *\n * @throws {DOMException} If the specified algorithm is not supported by the Web Crypto API.\n */\n\nexport async function hash(data: Jsonifiable, algorithm: SupportedAlgorithms = \"SHA-256\"): Promise<string> {\n textEncoder ??= new TextEncoder();\n\n const dataBuffer = typeof data === \"string\"\n ? textEncoder.encode(data) \n : textEncoder.encode(JSON.stringify(data));\n \n const hashBuffer = await crypto.subtle.digest(algorithm, dataBuffer);\n const hashArray = [...new Uint8Array(hashBuffer)];\n const hexValues = hashArray.map(b => b.toString(16).padStart(2, \"0\"));\n return hexValues.join(\"\");\n}\n", "\n/**\n * Generates a random integer between two given numbers, including those numbers.\n * \n * It uses `crypto.getRandomValues` to generate the random number.\n * @example\n * randomInt(1, 10) \n * // => 5\n * \n * @param min The smallest integer that can be generated.\n * @param max The largest integer that can be generated.\n * \n * @returns A random integer between `min` and `max`, including `min` and `max`.\n */\n\nexport function randomInt(min: number, max: number): number {\n // Taken from https://stackoverflow.com/a/41452318\n if (!Number.isInteger(min) || !Number.isInteger(max))\n throw new TypeError(\"min and max must be integers\");\n\n if (min >= max) \n throw new Error(\"max must be greater than min\");\n\n const range = max - min + 1;\n const randomBytes = Math.ceil(Math.log2(range) / 8);\n const maxRandNumber = Math.pow(256, randomBytes);\n const randomBuffer = new Uint8Array(randomBytes);\n\n let randomValue: number;\n do {\n crypto.getRandomValues(randomBuffer);\n randomValue = 0;\n for (let index = 0; index < randomBytes; index++) {\n // eslint-disable-next-line no-bitwise\n randomValue = (randomValue << 8) + randomBuffer[index];\n }\n // rerun if randomValue is bigger than range\n } while (randomValue >= maxRandNumber - (maxRandNumber % range));\n\n return min + (randomValue % range);\n}\n", "import { randomInt } from \"./randomInt.js\";\n\n/**\n * Gets a random element an array. A single element is returned by default. \n * Specify the `multi` parameter to get an array of multiple random elements.\n *\n * If the array is empty, `undefined` is returned. \n * If `multi` is defined it returns an empty array.\n * \n * It uses `crypto.getRandomValues` to get the random element.\n * @example\n * randomElem([1, 2, 3, 4])\n * // => 2\n *\n * randomElem([1, 2, 3, 4], 2)\n * // => [3, 1]\n * @param array The array to sample.\n * @returns Returns the random element.\n */\n\nexport function randomElem<TArr>(array: TArr[]): TArr | undefined;\nexport function randomElem<TArr>(array: TArr[], multi: number): TArr[];\nexport function randomElem<TArr>(array: TArr[], multi?: number): TArr | undefined | TArr[] {\n if (multi === undefined) {\n if (array.length === 0) return undefined;\n return getSingleElement(array);\n }\n\n if (multi && array.length === 0) return [];\n\n // Multiple samples\n const result = new Array<TArr>(multi);\n for (let i = 0; i < multi; i++) {\n result[i] = getSingleElement(array);\n }\n return result;\n}\n\nfunction getSingleElement<TArr>(array: TArr[]): TArr {\n const randomIndex = randomInt(0, array.length - 1);\n return array[randomIndex];\n}", "/* eslint-disable no-bitwise */\n\n/**\n * Generates a random float between two given numbers, including those numbers.\n * \n * It uses `crypto.getRandomValues` to generate the random number.\n * @example\n * randomFloat(1, 10) \n * // => 1.123456789\n * \n * @param min The smallest float that can be generated.\n * @param max The largest float that can be generated.\n * \n * @returns A random float between `min` and `max`, including `min` and `max`.\n */\nexport function randomFloat(min: number, max: number): number {\n if (min >= max) \n throw new Error(\"max must be greater than min\");\n\n // TODO: Switch to UInt64Array when safari support is better (https://caniuse.com/mdn-javascript_builtins_bigint64array)\n const randomBuffer = new Uint32Array(2);\n crypto.getRandomValues(randomBuffer);\n\n // keep all 32 bits of the the first, top 21 of the second for 53 random bits\n const randomBigInt = (BigInt(randomBuffer[0]) << 21n) | (BigInt(randomBuffer[1]) >> 11n);\n \n // fraction between 0 and 1 with full 53bit precision\n const fraction = Number(randomBigInt) / Number.MAX_SAFE_INTEGER; // (2 ** 53)\n return min + (fraction * (max - min));\n}\n", "import { randomInt } from \"./randomInt.js\";\n\nconst DEFAULT_CHARSET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\n/**\n * Generates a random string of the specified length.\n * The default charset is alphanumeric characters.\n * \n * It uses `crypto.getRandomValues` to generate the random string.\n * \n * @example\n * randomString(8);\n * // => \"JWw1p6rD\"\n *\n * randomString(16, 'abc');\n * // => \"cbaacbabcabccabc\"\n * @param length The length of the string to generate.\n * @param charSet The set of characters to use when generating the string. Defaults to alphanumeric characters.\n * @returns A random string of the specified length.\n */\n\nexport function randomString(length: number, charSet = DEFAULT_CHARSET): string {\n if (charSet.length <= 0) return \"\";\n\n let result = \"\";\n for (let index = 0; index < length; index++) {\n const randomIndex = randomInt(0, charSet.length - 1);\n result += charSet[randomIndex];\n }\n\n return result;\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\ntype Tail<T extends unknown[]> = T extends [infer _Head, ...infer Tail] ? Tail : never;\n\n/**\n * Transforms a function into a decorator function.\n * \n * @example\n * ```typescript\n * function log(func: Function, message: string) {\n * return function (...args: unknown[]) {\n * console.log(message);\n * return func(...args);\n * };\n * }\n * \n * const logger = toDecorator(log);\n * \n * class TestClass {\n * @logger(\"Hello world!\")\n * testMethod() {\n * return 1; \n * }\n * }\n * \n * const instance = new TestClass();\n * instance.testMethod(); \n * // => Log \"Hello World\" and return 1\n * ```\n * @param func The function to transform.\n * @returns A decorator function that can be used to decorate a method.\n */\n// waiting for https://github.com/evanw/esbuild/issues/104\nexport function toDecorator<TFunc extends GenericFunction<TFunc>>(func: TFunc) {\n return function (...args: Tail<Parameters<TFunc>>) {\n return function (originalMethod: unknown, _context: ClassMethodDecoratorContext) {\n const funcArgs = [originalMethod, ...args] as Parameters<TFunc>;\n return func(...funcArgs);\n };\n };\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Creates a debounced version of a function. Only calling it after a specified amount of time has passed without any new calls.\n * \n * **Methods:** \n * - `cancel()` will cancel the next invocation of the debounced function. \n * - `flush()` will immediately invoke the debounced function and cancel any pending invocations.\n * - `pending()` returns true if the debounced function is set to invoke.\n * \n * This function can be used as a decorator with {@link decDebounce}.\n * \n * @example\n * const sayHello = (name: string) => console.log(`Hello, ${name}!`);\n * const debouncedSayHello = debounce(sayHello, 200);\n * \n * debouncedSayHello(\"John\");\n * debouncedSayHello(\"Jane\");\n * // => Only the second invocation of `debouncedSayHello` is executed, after a delay of 200ms.\n * @param func The function to debounce.\n * @param wait The number of milliseconds to wait before invoking `func`.\n * @returns A debounced version of `func` with `cancel` and `flush` methods.\n */\n\nexport function debounce<TFunc extends GenericFunction<TFunc>>(func: TFunc, wait: number): TFunc & {\n cancel: () => void;\n flush: () => void;\n pending: () => boolean;\n} {\n let timeoutId: NodeJS.Timeout | undefined;\n const debounced = function (this: unknown, ...args: Parameters<TFunc>) {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n func.apply(this, args);\n timeoutId = undefined;\n }, wait);\n };\n\n debounced.cancel = function () {\n clearTimeout(timeoutId);\n timeoutId = undefined;\n };\n\n debounced.flush = function (this: unknown, ...args: Parameters<TFunc>) {\n debounced.cancel();\n func.apply(this, args);\n };\n\n debounced.pending = function () {\n return timeoutId !== undefined;\n };\n\n return debounced as TFunc & { cancel: () => void; flush: () => void; pending: () => boolean };\n}\n", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { debounce } from \"@function/debounce.js\";\n\n/**\n * Debounces the decorated function. Only calling it after a specified amount of time has passed without any new calls.\n * \n * Look at {@link debounce} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decDebounce(100)\n * testMethod(str: string) {\n * console.log(\"Debounced:\", str);\n * }\n * }\n * \n * const instance = new TestClass();\n * instance.testMethod(\"Hello\");\n * instance.testMethod(\"World\");\n * // => Only the second invocation of `debouncedSayHello` is executed, after a delay of 1000ms.\n * ```\n * @param wait Milliseconds to wait before invoking the decorated function after the last invocation.\n */\n\nexport function decDebounce(wait: number) {\n return toDecorator(debounce)(wait);\n}\n", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Creates a function that invokes the given function as long as it's called `<= n` times.\n * \n * Subsequent calls to the created function return the result of the last `func` invocation.\n *\n * This function can be used as a decorator with {@link decMaxCalls}.\n * @example\n * let count = 0;\n * const addCount = () => ++count;\n *\n * // Allow addCount to be invoked twice.\n * const limitAddCount = maxCalls(addCount, 2)\n *\n * limitAddCount() // => 1\n * limitAddCount() // => 2\n * limitAddCount() // => 2\n * // => `limitAddCount` is invoked twice and the result is cached.\n * @param n The number of calls before the cached result is returned.\n * @param func The function to restrict.\n * @returns Returns the new restricted function.\n */\n\nexport function maxCalls<TFunc extends GenericFunction<TFunc>>(func: TFunc, n: number): TFunc {\n let count = 0;\n let result: ReturnType<TFunc>;\n return function (this: unknown, ...args: Parameters<TFunc>): ReturnType<TFunc> {\n if (count < n) {\n count += 1;\n result = func.apply(this, args);\n }\n return result;\n } as TFunc;\n}\n", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { maxCalls } from \"@function/maxCalls.js\";\n\n/**\n * Only invokes the decorated function as long as it's called `<= n` times. \n * Subsequent calls to the decorated function return the result of the last invocation.\n * \n * Look at {@link maxCalls} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * private count = 0;\n * @decMaxCalls(2)\n * testMethod() {\n * return ++this.count;\n * }\n * }\n * const instance = new TestClass();\n * instance.testMethod(); // => 1 \n * instance.testMethod(); // => 2\n * instance.testMethod(); // => 2\n * ```\n * @param n The number of calls before the cached result is returned.\n */\n\nexport function decMaxCalls(n: number) {\n return toDecorator(maxCalls)(n);\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\nconst defaultResolver = (...args: unknown[]) => JSON.stringify(args);\n\n/**\n * Creates a function that memoizes the result of a given function.\n * \n * The cache key is determined by the `resolver` or by the arguments from the function call.\n *\n * **Options:**\n * - `resolver` A function that determines the cache key based on the arguments provided.\n * - `ttl` the time to live for the cache entries in milliseconds.\n * \n * **Properties:**\n * - `cache` The cache is an instance of `Map` and can be used to clear or inspect the cache. \n * It can be replaced by a custom cache that matches the `Map` interface.\n * \n * \n * This function can be used as a decorator with {@link decMemoize}.\n * \n * @example\n * ```typescript\n * function fibonacci(n: number) {\n * if (n <= 1) return n;\n * return fibonacci(n - 1) + fibonacci(n - 2);\n * }\n *\n * const memoizedFib = memoize(fibonacci, { ttl: 1000 })\n * \n * memoizedFib(40) // => 102334155\n * memoizedFib(40) // => 102334155 (cache hit)\n * setTimeout(() => memoizedFib(40), 1000) // => 102334155 (cache miss)\n * \n * // Cached values are exposed as the `cache` property.\n * memoizedFib.cache.get(\"40\") // => [value, timestamp]\n * memoizedFib.cache.set(\"40\", [1234, Date.now()])\n * memoizedFib.cache.clear()\n * \n * // This is the default way to create cache keys.\n * const defaultResolver = (...args: unknown[]) => JSON.stringify(args)\n * ```\n * @param func The function to have its output memoized.\n * @param options The options object with optional `resolver` and `ttl` parameters.\n * @param options.resolver - A function that determines the cache key for storing the result based on the arguments provided.\n * @param options.ttl - The time to live for the cache in milliseconds.\n * @template TFunc The type of the function to memoize.\n * @template Cache The type of the cache storage.\n * @returns Returns the new memoized function.\n */\n\nexport function memoize<TFunc extends GenericFunction<TFunc>, Cache extends Map<string, [ReturnType<TFunc>, number]>>(\n func: TFunc, options: { resolver?: (...args: Parameters<TFunc>) => string, ttl?: number; } = {}\n): TFunc & { cache: Cache } {\n const resolver = options.resolver ?? defaultResolver;\n const ttl = options.ttl;\n const cache = new Map() as Cache;\n\n const memoizedFunc = function (this: unknown, ...args: Parameters<TFunc>): ReturnType<TFunc> {\n const key = resolver(...args);\n if (cache.has(key)) {\n const [cacheResult, cacheTime] = cache.get(key)!;\n if (ttl === undefined || (Date.now() - cacheTime < ttl)) {\n return cacheResult;\n }\n }\n const result = func.apply(this, args);\n cache.set(key, [result, Date.now()]);\n return result;\n };\n\n memoizedFunc.cache = cache;\n return memoizedFunc as TFunc & { cache: Cache };\n} ", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { memoize } from \"@function/memoize.js\";\n\n/**\n * Memoizes the decorated function. \n * The cache key is either determined by the provided resolver or by the arguments used in the memoized function.\n * \n * **Options:**\n * - `resolver` A function that determines the cache key for storing the result based on the arguments provided.\n * - `ttl` sets the time to live for the cache in milliseconds. After `ttl` milliseconds, the next call to the memoized function will result in a cache miss.\n * \n * Look at {@link memoize} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decMemoize({ ttl: 1000 })\n * testMethod(a: number, b: number) {\n * return a + b;\n * }\n * }\n * const instance = new TestClass();\n * instance.testMethod(1, 2); // => 3\n * instance.testMethod(1, 2); // => 3 (cached)\n * \n * // After 1 second:\n * instance.testMethod(1, 2); // => 3 (cache miss)\n * ```\n * @param options The options object.\n * @param options.resolver - A function that determines the cache key for storing the result based on the arguments provided.\n * @param options.ttl - The time to live for the cache in milliseconds.\n */\n\nexport function decMemoize(options: Parameters<typeof memoize>[1] = {}) {\n return toDecorator(memoize)(options);\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Creates a function that invokes the given function once it's called more than `n` times. \n * Returns undefined until the minimum call count is reached.\n * \n * This function can be used as a decorator with {@link decMinCalls}.\n * @example\n * const caution = () => console.log(\"Caution!\");\n * const limitedCaution = minCalls(caution, 2);\n *\n * limitedCaution()\n * limitedCaution()\n * limitedCaution()\n * // => `caution` is invoked on the third call.\n * @param n The number of calls before the given function is invoked.\n * @param func The function to restrict.\n * @returns Returns the new restricted function.\n */\n\nexport function minCalls<TFunc extends GenericFunction<TFunc>>(func: TFunc, n: number) {\n let count = 1;\n return function (this: unknown, ...args: Parameters<TFunc>): ReturnType<TFunc> | undefined {\n if (count > n) {\n return func.apply(this, args);\n }\n count += 1;\n };\n}", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { minCalls } from \"@function/minCalls.js\";\n\n/** \n * Only invokes the decorated function after it's called more than `n` times.\n * \n * Look at {@link minCalls} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decMinCalls(2)\n * testMethod() {\n * return 1;\n * }\n * }\n * const instance = new TestClass();\n * instance.testMethod(); // => undefined\n * instance.testMethod(); // => undefined\n * instance.testMethod(); // => 1\n * ```\n * @param n The number of calls before the decorated function is invoked.\n */\n\nexport function decMinCalls(n: number) {\n return toDecorator(minCalls)(n);\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Generates a function that invokes the given function `func` at most once per every `wait` milliseconds. \n * The throttled function always returns the result of the last `func` invocation.\n * \n * This function can be used as a decorator with {@link decThrottle}.\n * @example\n * const throttled = throttle(() => console.log(\"Throttled!\"), 1000);\n * \n * throttled();\n * throttled();\n * // => \"Throttled!\" is logged once per second.\n * @param func The function to throttle.\n * @param wait The number of milliseconds to throttle invocations to.\n * @returns Returns the new throttled function.\n */\n\nexport function throttle<TFunc extends GenericFunction<TFunc>>(func: TFunc, wait: number): TFunc {\n let inThrottle = false;\n let lastResult: ReturnType<TFunc>;\n return function (this: unknown, ...args: Parameters<TFunc>) {\n if (!inThrottle) {\n lastResult = func.apply(this, args);\n inThrottle = true;\n setTimeout(() => (inThrottle = false), wait);\n }\n\n return lastResult;\n } as TFunc;\n}\n \n", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { throttle } from \"@function/throttle.js\";\n\n/**\n * The decorated function is invoked at most once per every `wait` milliseconds.\n * \n * Look at {@link throttle} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decThrottle(1000)\n * testMethod() {\n * console.log(\"Throttled!\");\n * }\n * }\n * \n * const instance = new TestClass();\n * instance.testMethod(); // => \"Throttled!\" is logged once per second.\n * instance.testMethod(); // nothing happens\n * ```\n * @param wait The number of milliseconds to wait between invocations.\n */\n\nexport function decThrottle(wait: number) {\n return toDecorator(throttle)(wait);\n}", "/**\n * Invokes a function `n` times, returning an array of the results of\n * each invocation.\n * \n * @example\n * times(index => console.log(\"Run\", index), 3)\n * // => \"Run 0\" | \"Run 1\" | \"Run 2\"\n * times(Math.random, 3)\n * // => [0.123, 0.456, 0.789]\n * times(() => 0, 4)\n * // => [0, 0, 0, 0]\n * @param n The number of times to invoke `func`.\n * @param func The function invoked per iteration.\n * @returns Returns an array of results.\n */\n\nexport function times<TInput>(func: (index: number) => TInput, n: number): TInput[] {\n const result: TInput[] = [];\n for (let i = 0; i < n; i++) {\n result.push(func(i));\n }\n return result;\n}", "\n/**\n * Calculates the sum of an array of numbers.\n * \n * Returns `NaN` if the input array is empty.\n * @example\n * sum([1, 2, 3, 4, 5]) // => 15\n * \n * @param numbers The input array of numbers\n * @returns The sum of the input array \n */\n\nexport function sum(numbers: readonly number[]): number {\n if (numbers.length === 0)\n return NaN;\n return numbers.reduce((total, current) => total + current, 0);\n}", "import { sum } from \"@number/sum.js\";\n\n/**\n * Calculates the average of an array of numbers\n * \n * Returns `NaN` if the input array is empty.\n * @example\n * average([1, 2, 3, 4, 5]) // => 3\n * \n * @param numbers The input array of numbers\n * @returns The average of the input array, or NaN if the input array is empty\n */\n\nexport function average(numbers: readonly number[]): number {\n if (numbers.length === 0)\n return NaN;\n return sum(numbers) / numbers.length;\n}", "/**\n * Calculates the median of an array of numbers\n * \n * Returns `NaN` if the input array is empty.\n * @example\n * median([1, 2, 3, 4, 5]) // => 3\n * median([1, 2, 3, 4, 5, 6]) // => 3.5\n * \n * @param numbers The input array of numbers\n * @returns The median of the input array\n */\n\nexport function median(numbers: readonly number[]): number {\n if (numbers.length === 0)\n return NaN;\n const sortedArray = [...numbers].sort((a, b) => a - b);\n const mid = Math.floor(sortedArray.length / 2);\n return sortedArray.length % 2 === 0 ? ((sortedArray[mid - 1] + sortedArray[mid]) / 2) : sortedArray[mid];\n}", "/**\n * Rounds a number to the given precision.\n *\n * @example\n * round(1.23456, 2); // => 1.23\n * round(1.235, 1); // => 1.2\n * round(1234.56); // => 1234.56\n * \n * @param number The number to be rounded.\n * @param precision The number of decimal places to round to. Defaults to 2.\n * @returns The rounded number.\n */\n\nexport function round(number: number, precision = 2): number {\n const factor = Math.pow(10, precision);\n return Math.round((number + Number.EPSILON) * factor) / factor;\n}", "import type { PlainObject } from \"@type/PlainObject.js\";\n\n/**\n * Checks if the value is a plain object.\n * \n * Refers to the {@link PlainObject} type.\n * @example\n * isPlainObject({}) // => true\n * isPlainObject({ a: 1 }) // => true\n * isPlainObject(null) // => false\n * isPlainObject('1') // => false\n * isPlainObject([]) // => false\n * isPlainObject(new Function()) // => false\n * isPlainObject(new Date()) // => false\n * @param value The value to check\n * @returns Boolean indicating if the value is a plain object\n */\n\nexport function isPlainObject(value: unknown): value is PlainObject {\n return value?.constructor === Object;\n}", "import type { GenericObject } from \"@type/GenericObject\";\nimport type { Paths } from \"type-fest\";\n\nimport { isPlainObject } from \"@validate/isPlainObject.js\";\n\ntype StringIfNever<Type> = [Type] extends [never] ? string : Type;\ntype PathOrString<TObj> = StringIfNever<Paths<TObj, { bracketNotation: true, maxRecursionDepth: 20 }>>;\n\n/**\n * Flattens an object into a single level object.\n * \n * @example\n * const obj = { a: { b: 2, c: [{ d: 3 }, { d: 4 }] } };\n * flatKeys(obj);\n * // => { 'a.b': 2, 'a.c[0].d': 3, 'a.c[1].d': 4 }\n * \n * @param obj The object to flatten.\n * @template TObj The type of the object to flatten.\n * @returns A new object with flattened keys.\n */\n\nexport function flatKeys<TObj extends GenericObject>(obj: TObj): Record<PathOrString<TObj>, unknown> {\n const flatObject: Record<string, unknown> = {};\n \n for (const [key, value] of Object.entries(obj)) {\n addToResult(key, value, flatObject);\n }\n \n return flatObject;\n}\n\nfunction addToResult(prefix: string, value: unknown, flatObject: Record<string, unknown>) {\n if (isPlainObject(value)) {\n const flatObj = flatKeys(value);\n for (const [flatKey, flatValue] of Object.entries(flatObj)) {\n flatObject[`${prefix}.${flatKey}`] = flatValue;\n }\n } else if (Array.isArray(value)) {\n for (const [index, element] of value.entries()) {\n addToResult(`${prefix}[${index}]`, element, flatObject);\n }\n\n } else {\n flatObject[prefix] = value;\n }\n}", "import type { ArrayMinLength } from \"@type/ArrayMinLength.js\";\nimport type { GenericObject } from \"@type/GenericObject\";\nimport type { PlainObject } from \"@type/PlainObject.js\";\n\nimport { isPlainObject } from \"@validate/isPlainObject.js\";\n\n/**\n * This function combines two or more objects into a single new object. Arrays and other types are overwritten.\n * \n * @example\n * // ---- Nested objects are merged ----\n * merge({ a: 1 }, { b: 2 }, { c: 3, d: { e: 4 } }) \n * // => { a: 1, b: 2, c: 3, d: { e: 4 } }\n *\n * // ---- Other types are overwritten ----\n * merge({ a: [1, 2] }, { a: [3, 4] })\n * // => { a: [3, 4] }\n * \n * merge({ a: 1 }, { a: \"Yes\" })\n * // => { a: \"Yes\" }\n * @param target The target object\n * @param sources The source objects\n * @template TTarget The type of the target object\n * @template TSources The type of the source objects\n * @returns A new merged object\n */\n\nexport function merge<TTarget extends GenericObject, TSources extends ArrayMinLength<GenericObject, 1>>(target: TTarget, ...sources: TSources): MergeDeepObjects<[TTarget, ...TSources]> {\n const targetCopy = { ...target };\n for (const source of sources) {\n for (const [key, value] of Object.entries(source)) {\n (targetCopy as PlainObject)[key] = isPlainObject(value) && isPlainObject(targetCopy[key]) \n ? merge(targetCopy[key], value) \n : value;\n }\n }\n return targetCopy as MergeDeepObjects<[TTarget, ...TSources]>; \n}\n\ntype OptionalPropertyNames<T> =\n { [K in keyof T]-?: (PlainObject extends { [P in K]: T[K] } ? K : never) }[keyof T];\n\ntype SpreadProperties<L, R, K extends keyof L & keyof R> =\n { [P in K]: L[P] | Exclude<R[P], undefined> };\n\ntype Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;\n\ntype SpreadTwo<L, R> = Id<\n& Pick<L, Exclude<keyof L, keyof R>>\n& Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>\n& Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>\n& SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>\n>;\n\ntype MergeDeepObjects<A extends readonly [...unknown[]]> = A extends [infer L, ...infer R] ?\n SpreadTwo<L, MergeDeepObjects<R>> : unknown;\n", "import type { GenericObject } from \"@type/GenericObject\";\n\n/**\n * Creates an object composed of the picked `object` properties.\n *\n * @example\n * const object = { 'a': 1, 'b': '2', 'c': 3 }\n *\n * pick(object, ['a', 'c'])\n * // => { 'a': 1, 'c': 3 }\n * @param object The source object.\n * @param keysToPick The property paths to pick.\n * @template TObj The type of the object.\n * @returns Returns the new object.\n */\n\nexport function pick<TObj extends GenericObject, Key extends keyof TObj>(object: TObj, keysToPick: Key[]): Pick<TObj, Key> {\n const result = {} as Pick<TObj, Key>;\n for (const key of keysToPick) {\n result[key] = object[key];\n }\n return result;\n}\n", "import type { GenericObject } from \"@type/GenericObject\";\n\nimport { difference } from \"@array/difference.js\";\nimport { pick } from \"@object/pick.js\";\n\n/**\n * Omit specified keys from an object\n *\n * @example\n * const obj = {a: 1, b: 2, c: 3};\n * omit(obj, ['a', 'b']);\n * // => {c: 3}\n *\n * @param object The object to filter\n * @param keysToOmit The keys to exclude from the returned object\n * @template TObj The type of the object\n * @returns - An object without the specified keys\n */\n\nexport function omit<TObj extends GenericObject, Key extends keyof TObj>(object: TObj, keysToOmit: Key[]): Omit<TObj, Key> {\n const allKeys = Object.keys(object);\n const filteredKeys = difference(allKeys, keysToOmit as string[]) as Exclude<keyof TObj, Key>[];\n\n return pick(object, filteredKeys);\n}", "import type { GenericObject } from \"@type/GenericObject\";\nimport type { PlainObject } from \"@type/PlainObject.js\";\nimport type { Call, Objects } from \"hotscript\";\n\nimport { isPlainObject } from \"@validate/isPlainObject.js\";\n\nconst validPathRegex = /^[^.[\\]]+(?:\\.[^.[\\]]+)*(?:\\[\\d+])*(?:\\.[^.[\\]]+(?:\\[\\d+])*)*$/;\nconst pathSplitRegex = /\\.|(?=\\[)/g;\nconst matchBracketsRegex = /[[\\]]/g;\n\n// eslint-disable-next-line @typescript-eslint/ban-types\ntype Paths<TObj> = Call<Objects.AllPaths, TObj> | string & {};\ntype UpdateObj<TObj extends PlainObject, TPath extends string, TVal> = Call<Objects.Update<TPath, TVal>, TObj>;\n\n/**\n * Sets the value at path of object. If a portion of path doesn’t exist, it’s created.\n * \n * @example\n * const obj = { a: { b: 2 } };\n * set(obj, 'a.c', 1);\n * // => { a: { b: 2, c: 1 } }\n * \n * // `[number]` can be used to access array elements\n * set(obj, 'a.c[0]', 'hello');\n * // => { a: { b: 2, c: ['hello'] } }\n * \n * // numbers with dots are treated as keys\n * set(obj, 'a.c.0.d', 'world');\n * // => { a: { b: 2, c: { 0: { d: 'world' } } }\n * \n * // supports numbers in keys\n * set(obj, 'a.e0.a', 1);\n * // => { a: { e0: { a: 1 } } }\n * \n * @param obj The object to modify.\n * @param path The path of the property to set.\n * @param value The value to set.\n * @template TObj The type of the object.\n * @template TPath The type of the object path.\n * @template TVal The type of the value to set.\n * @returns The modified object.\n */\n\nexport function set<TObj extends GenericObject, TPath extends Paths<TObj>, TVal>(obj: TObj, path: TPath, value: TVal): UpdateObj<TObj, TPath, TVal> {\n if (!validPathRegex.test(path))\n throw new Error(\"Invalid path, look at the examples for the correct format.\");\n\n const pathParts = (path as string).split(pathSplitRegex);\n let currentObj: PlainObject = obj;\n\n for (let index = 0; index < pathParts.length; index++) {\n const key = pathParts[index].replace(matchBracketsRegex, \"\");\n\n if (index === pathParts.length - 1) {\n currentObj[key] = value;\n break;\n }\n\n const nextElementInArray = pathParts[index + 1].startsWith(\"[\");\n const nextElementInObject = !nextElementInArray;\n\n if (nextElementInArray && !Array.isArray(currentObj[key])) {\n currentObj[key] = [];\n }\n\n if (nextElementInObject && !isPlainObject(currentObj[key])) {\n currentObj[key] = {};\n }\n \n currentObj = currentObj[key] as PlainObject;\n }\n\n return obj as UpdateObj<TObj, TPath, TVal>;\n}", "/**\n * A class managing an async function queue with limited concurrency (e.g., 10 functions with 3 running at a time).\n * \n * **Methods:**\n * - `add` - Adds an async function or array of functions to the queue. Returns a promise that resolves or rejects when the added function(s) finish.\n * - `clear` - Clears the queue.\n * - `pause` - Pauses the queue.\n * - `resume` - Resumes the queue. \n * - `getQueue` - Returns the current queue.\n * - `isPaused` - Returns whether the queue is paused.\n * - `done` - Returns a promise resolving when all added tasks are finished. Individual rejections don't affect the done() promise.\n * \n * @example\n * // Create a queue that can run 3 tasks concurrently\n * const queue = new Queue(3);\n * \n * queue.add(() => fetch('https://example.com'));\n * \n * queue.add(async () => {\n * const response = await fetch('https://example.com');\n * return response.json();\n * });\n * \n * await queue.done();\n * console.log(\"All tasks finished\");\n * \n * // Add an array of tasks to the queue and wait for them to resolve\n * await queue.add([\n * () => fetch('https://apple.com'),\n * () => fetch('https://microsoft.com')\n * ]);\n * // => [Response, Response]\n */\n\nexport class Queue {\n private running = 0;\n private maxConcurrent: number;\n private paused = false;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private queue: { asyncFn: () => Promise<any>, resolve: (value: any) => void, reject: (reason?: any) => void }[] = [];\n private finishedPromise: Promise<boolean> | undefined;\n private finishedResolver: (() => void) | undefined;\n\n /**\n * @constructor\n * @param maxConcurrent The maximum number of async functions to run concurrently.\n */\n constructor(maxConcurrent: number) {\n this.maxConcurrent = maxConcurrent;\n }\n\n /**\n * Add async functions or an array of async functions to the queue.\n * \n * @param asyncFn The async function(s) to add to the queue.\n * @returns A promise that resolves when the added function(s) finishes.\n */\n add<TProm, TAsyncFn extends () => Promise<TProm>>(asyncFn: TAsyncFn): Promise<TProm>;\n add<TProm, TAsyncFn extends () => Promise<TProm>>(asyncFn: TAsyncFn[]): Promise<TProm[]>;\n add<TProm, TAsyncFn extends () => Promise<TProm>>(asyncFn: TAsyncFn | TAsyncFn[]): Promise<TProm> | Promise<TProm[]> {\n if (Array.isArray(asyncFn)) {\n const promises = asyncFn.map((fn) => this.buildWaitingPromise(fn));\n return Promise.all(promises);\n } else {\n return this.buildWaitingPromise(asyncFn);\n } \n }\n\n private buildWaitingPromise<TProm>(asyncFn: () => Promise<TProm>): Promise<TProm> {\n return new Promise((resolve, reject) => {\n this.queue.push({ asyncFn, resolve, reject });\n this.run();\n });\n } \n\n private run() {\n while (this.queue.length > 0 && this.running < this.maxConcurrent && !this.paused) {\n this.running++;\n const queueElement = this.queue.shift()!;\n void queueElement.asyncFn()\n .then((result) => {\n queueElement.resolve(result);\n }).catch((error) => {\n queueElement.reject(error);\n }).finally(() => {\n this.running--;\n this.run();\n });\n }\n\n this.checkIfDone();\n }\n\n /** Removes all the tasks from the queue */\n clear() {\n for (const queueElement of this.queue) {\n queueElement.reject(new Error(\"Queue cleared\"));\n }\n this.queue = [];\n }\n\n /** Pauses the execution of the queue */\n pause() {\n this.paused = true;\n }\n\n /** Resumes the execution of the tasks in the queue */\n resume() {\n this.paused = false;\n this.run();\n }\n\n /** Return the tasks added to the queue */\n getQueue() {\n return this.queue.map((queueElement) => queueElement.asyncFn);\n }\n\n /** Returns whether the queue is paused */\n isPaused() {\n return this.paused;\n }\n\n /** Returns a shared promise that resolves when the queue is empty and all tasks have finished executing. */\n done() {\n if (this.queue.length === 0 && this.running === 0)\n return Promise.resolve(true);\n \n this.finishedPromise ??= new Promise(resolve => this.finishedResolver = () => resolve(true));\n return this.finishedPromise;\n }\n\n private checkIfDone() {\n if (this.queue.length === 0 && this.running === 0 && this.finishedResolver) {\n this.finishedResolver();\n this.finishedPromise = undefined;\n this.finishedResolver = undefined;\n }\n }\n}\n", "/**\n * Similar to [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race?retiredLocale=de) \n * but allows to specify how many promises to wait for.\n *\n * @example\n * const prom1 = Promise.resolve(1);\n * const prom2 = new Promise(resolve => setTimeout(resolve, 100, 2));\n * const prom3 = Promise.resolve(3);\n * \n * const firstTwo = await races(2, prom1, prom2, prom3);\n * // => [1, 3]\n * \n * @template TRes The type of the result of the promises.\n * @param waitFor The number of promises to wait for.\n * @param promises The promises to wait for.\n * @returns A promise that resolves an array of the results of the first n promises.\n */\n\nexport function races<TRes>(waitFor: number, ...promises: Promise<TRes>[]): Promise<TRes[]> {\n return new Promise((resolve, reject) => {\n if (promises.length < waitFor)\n waitFor = promises.length;\n\n const results: TRes[] = [];\n let resolved = 0;\n for (const promise of promises) {\n promise.then((value) => {\n results.push(value);\n resolved++;\n if (resolved >= waitFor) {\n resolve(results);\n }\n }).catch((error) => {\n reject(error);\n });\n }\n });\n}", "/**\n * Sleeps for the given amount of time.\n *\n * @example\n * await sleep(1000);\n * // => Waits for 1 second.\n * @param ms Amount of time to sleep in milliseconds.\n * @returns A promise that resolves after the given amount of time.\n */\nexport function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}", "/* eslint-disable no-await-in-loop */\nimport { sleep } from \"@promise/sleep.js\";\n\n/**\n * Retry a function until it succeeds or the maximum number of retries is reached.\n * \n * Default maxRetries: `5`. \n * Default backoff: `2^retries * 100ms` (100, 200, 400, 800, 1600, 3200, ...)\n *\n * @example\n * await retry(() => fetch('https://example.com'));\n * \n * // ---- Advanced example ----\n * const fetchSite = async () => {\n * const response = await fetch('https://example.com');\n * if(!response.ok)\n * throw new Error('Failed to fetch');\n * }\n * \n * const logger = (error: unknown, retry?: number) => console.log(\"Retrying\", retry, error);\n * \n * await retry(fetchSite, { maxRetries: 3, backoff: retries => retries * 1000, onRetry: logger });\n * // => Will retry 3 times with a 1 second delay between each retry.\n * // => Will log the error and retry number.\n * \n * @param func The function to retry.\n * @param options The options for the retry.\n * @param options.maxRetries The maximum number of retries. Defaults to `5`.\n * @param options.backoff The backoff function to use. Defaults to `2^retries * 100`.\n * @param options.onRetry The function to call when a retry is attempted. It will be called with the error and the attempt number.\n * @template TRes The type of the result of the function.\n * @returns A promise that resolves when the function succeeds.\n */\n\nexport async function retry<TRes>(\n func: () => Promise<TRes>, \n options?: { \n maxRetries?: number,\n backoff?: ((retries: number) => number),\n onRetry?: (error?: unknown, attempted?: number) => void\n }\n): Promise<TRes> {\n const backOffFn = options?.backoff ?? (retries => (2 ** retries) * 100);\n const maxRetries = options?.maxRetries ?? 5;\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const onRetry = options?.onRetry ?? (() => {});\n let retries = 0;\n let lastError: unknown;\n\n while (retries <= maxRetries) {\n try {\n if (retries > 0)\n onRetry(lastError, retries);\n return await func();\n } catch (error) {\n lastError = error;\n retries++;\n if (retries > maxRetries) {\n throw error;\n }\n await sleep(backOffFn(retries));\n }\n /* c8 ignore next 3 */\n }\n throw new Error(\"Retry terminated without success, this should never happen\");\n}", "/**\n * Returns a new promise that will reject with an error after a specified timeout. \n *\n * @example\n * try {\n * await timeout(fetch('https://example.com'), 1000);\n * } catch (error) {\n * console.log(error.message);\n * // => 'Promise timed out after 1000ms'\n * }\n * @template TRes - The type of the resolved value.\n * @param promise The promise to wrap.\n * @param timeout The timeout in milliseconds.\n * \n * @returns A new promise that will reject with an error after the specified timeout.\n */\nexport function timeout<TRes>(promise: Promise<TRes>, timeout: number): Promise<TRes> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Promise timed out after ${timeout}ms`));\n }, timeout);\n \n promise.then(\n (result) => {\n clearTimeout(timeoutId);\n resolve(result);\n },\n (error) => {\n clearTimeout(timeoutId);\n reject(error);\n }\n );\n });\n}", "/**\n * Attempts to execute a promise and returns an array with the result or error.\n * \n * This is useful for handling errors in async functions without try/catch blocks.\n * \n * @example\n * ```typescript\n * const [data, error] = await tryCatch(fetch('https://example.com/api'));\n * if (error)\n * console.error(`Error: ${error.message}`);\n * ```\n * @param promise A Promise to be executed.\n * @returns A Promise that resolves to an array containing the result or error. \n * If the Promise executes successfully, the array contains the result and a null error. \n * If the Promise throws an error, the array contains undefined for the result and the error object.\n *\n * @template TRes The type of the result.\n */\n\nexport async function tryCatch<TRes>(promise: Promise<TRes>): Promise<[TRes, undefined] | [undefined, Error]> {\n try {\n const data = await promise;\n return [data, undefined];\n } catch (error) {\n if (error instanceof Error)\n return [undefined, error];\n\n throw error;\n }\n}", "const wordsRegex = /(?:\\d*[a-z]+)|(?:[A-Z][a-z]+)|(?:\\d*[A-Z]+(?=[^a-z]|$))|\\d+/g;\n\n/**\n * Split a string into words. Can deal with camelCase, PascalCase & snake_case.\n * \n * @example\n * splitWords('camelCase')\n * // => ['camel', 'Case']\n * \n * splitWords('PascalCase')\n * // => ['Pascal', 'Case']\n * \n * splitWords('hello_world-123')\n * // => ['hello', 'world', '123']\n * \n * @param str The string to split into words.\n * @returns An array of words.\n */\n\nexport function splitWords(str: string): string[] {\n return str.match(wordsRegex) ?? [];\n}", "/**\n * Converts the first character of a string to upper case and the remaining to lower case.\n *\n * @example\n * capitalize('FRED')\n * // => 'Fred'\n * @param str The string to capitalize.\n * @returns Returns the capitalized string.\n */\n\nexport function capitalize(str: string): string {\n if (str === \"\") return \"\";\n return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();\n}\n", "const accentControlRegex = /[\\u0300-\\u036F]/g;\n\n/**\n * Deburrs a string by converting\n * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)\n * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)\n * letters to basic Latin letters and removing\n * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).\n *\n * @example\n * deburr('déjà vu')\n * // => 'deja vu'\n * @param str The string to deburr.\n * @returns Returns the deburred string.\n */\n\nexport function deburr(str: string): string {\n return str.normalize(\"NFD\").replace(accentControlRegex, \"\");\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { capitalize } from \"./capitalize.js\";\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts `string` to camelCase.\n *\n * @example\n * camelCase('Foo Bar')\n * // => 'fooBar'\n * camelCase('--foo-bar--')\n * // => 'fooBar'\n * camelCase('__FOO_BAR__')\n * // => 'fooBar'\n * @param str The string to convert.\n * @returns Returns the camel cased string.\n */\n\nexport function camelCase(str: string): string {\n if (str === \"\") return \"\";\n\n str = deburr(str);\n const words = splitWords(str);\n\n if (words.length === 0) return \"\";\n let camelCase = words[0].toLowerCase();\n\n // Start the loop from the second word\n for (let index = 1; index < words.length; index++) {\n const word = words[index];\n camelCase += capitalize(word);\n }\n\n return camelCase;\n}", "const charRegex = /[\"&'<>]/g;\nconst escapeChars = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n [\"'\", \"'\"],\n ['\"', \""\"]\n]);\n\n/**\n * Converts the characters `&`, `<`, `>`, `\"` and `'` in a string to their corresponding HTML entities.\n *\n * @example\n * escapeHtml('fred, barney, & pebbles')\n * // => 'fred, barney, & pebbles'\n * @param str The string to escape.\n * @returns Returns the escaped string.\n */\n\nexport function escapeHtml(str: string): string {\n return str.replace(charRegex, char => escapeChars.get(char)!);\n}\n", "const escapeCharsRegex = /[$()*+.?[\\\\\\]^{|}]/g;\n\n/**\n * Escapes the `RegExp` special characters `^`, `$`, `\\`, `.`, `*`, `+`,\n * `?`, `(`, `)`, `[`, `]`, `{`, `}`, and `|` in a string.\n *\n * @example\n * escapeRegExp('[moderndash](https://moderndash.io/)')\n * // => '\\[moderndash\\]\\(https://moderndash\\.io/\\)'\n * @param str The string to escape.\n * @returns Returns the escaped string.\n */\n\nexport function escapeRegExp(str: string): string {\n return str.replace(escapeCharsRegex, \"\\\\$&\");\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts a string to kebab-case.\n *\n * @example\n * kebabCase('Foo Bar')\n * // => 'foo-bar'\n * kebabCase('fooBar')\n * // => 'foo-bar'\n * kebabCase('__FOO_BAR__')\n * // => 'foo-bar'\n * \n * @param str The string to convert.\n * @returns Returns the kebab cased string.\n */\n\nexport function kebabCase(str: string): string {\n if (str === \"\") return \"\";\n \n str = deburr(str);\n const words = splitWords(str);\n let kebabCase = \"\";\n for (const word of words) {\n kebabCase += word.toLowerCase() + \"-\";\n }\n return kebabCase.slice(0, -1);\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { capitalize } from \"./capitalize.js\";\nimport { deburr } from \"./deburr.js\";\n\n\n/**\n * Converts a string to PascalCase.\n *\n * @example\n * pascalCase('Foo Bar')\n * // => 'FooBar'\n * pascalCase('fooBar')\n * // => 'FooBar'\n * pascalCase('__FOO_BAR__')\n * // => 'FooBar'\n * \n * @param str The string to convert.\n * @returns Returns the pascal cased string.\n */\n\nexport function pascalCase(str: string): string {\n if (str === \"\") return \"\";\n \n str = deburr(str);\n const words = splitWords(str);\n let pascalCase = \"\";\n for (const word of words) {\n pascalCase += capitalize(word);\n }\n return pascalCase;\n}\n", "/**\n * Replaces the last occurrence of a string.\n * \n * @example\n * ```typescript\n * replaceLast(\"Foo Bar Bar\", \"Bar\", \"Boo\"); \n * // => \"Foo Bar Boo\"\n * ```\n * \n * @param str The string to replace in.\n * @param searchFor The string to search for.\n * @param replaceWith The string to replace with.\n * @returns The replaced string.\n */\n\nexport function replaceLast(str: string, searchFor: string, replaceWith: string): string {\n const index = str.lastIndexOf(searchFor);\n\n if (index === -1)\n return str;\n\n return str.slice(0, index) + replaceWith + str.slice(index + searchFor.length);\n}", "import { splitWords } from \"@string/splitWords\";\n\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts a string to snake_case.\n *\n * @example\n * snakeCase('Foo Bar')\n * // => 'foo_bar'\n * snakeCase('fooBar')\n * // => 'foo_bar'\n * snakeCase('--FOO-BAR--')\n * // => 'foo_bar'\n * snakeCase('foo2bar')\n * // => 'foo_2_bar'\n * \n * @param str The string to convert.\n * @returns Returns the snake cased string.\n */\n\nexport function snakeCase(str: string): string {\n if (str === \"\") return \"\";\n \n str = deburr(str);\n const words = splitWords(str);\n let snakeCase = \"\";\n for (const word of words) {\n if (snakeCase.length > 0) {\n snakeCase += \"_\";\n }\n snakeCase += word.toLowerCase();\n }\n return snakeCase;\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { capitalize } from \"./capitalize.js\";\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts a string to Title Case.\n *\n * @example\n * titleCase('--foo-bar--')\n * // => 'Foo Bar'\n * titleCase('fooBar')\n * // => 'Foo Bar'\n * titleCase('__FOO_BAR__')\n * // => 'Foo Bar'\n * titleCase('HélloWorld')\n * // => 'Hello World'\n * @param str The string to convert.\n * @returns Returns the title cased string.\n */\n\nexport function titleCase(str: string): string {\n if (str === \"\") return \"\";\n\n str = deburr(str);\n const words = splitWords(str);\n let titleCase = \"\";\n for (const word of words) {\n titleCase += capitalize(word) + \" \";\n }\n return titleCase.trimEnd();\n}\n", "/**\n * Trim the string from the left and right by the given characters\n * \n * *Use the native [trim](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim) method if you want to trim whitespace.*\n * @example\n * ```ts\n * trim('$$abc$', '$') // => 'abc'\n * trim('!!abc_!', '_!') // => 'abc'\n * ```\n * @param str The string to trim\n * @param chars The characters to trim\n * @returns The trimmed string\n */\n\nexport function trim(str: string, chars: string): string {\n let startIndex = 0;\n while (startIndex < str.length && chars.includes(str[startIndex])) {\n startIndex++;\n }\n \n let endIndex = str.length - 1;\n while (endIndex >= startIndex && chars.includes(str[endIndex])) {\n endIndex--;\n }\n \n return str.slice(startIndex, endIndex + 1);\n}", "/**\n * Trims the specified characters from the end of the string.\n * \n * *Use the native [trimEnd](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd) method if you want to trim whitespace.*\n * @example\n * ```ts\n * trimEnd('abc$$$', '$') // => 'abc'\n * trimEnd('abc_!!_', '_!') // => 'abc'\n * ```\n * @param str The string to trim\n * @param chars The characters to trim\n * @returns The trimmed string\n */\n\nexport function trimEnd(str: string, chars: string): string {\n let lastIndex = str.length - 1;\n while (lastIndex >= 0 && chars.includes(str[lastIndex])) {\n lastIndex--;\n }\n return str.slice(0, lastIndex + 1);\n\n}", "/**\n * Trims specified characters from the start of the string.\n * \n * *Use the native [trimStart](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart) method if you want to trim whitespace.*\n * @example\n * ```ts\n * trimStart('$$$abc', '$') // => 'abc'\n * trimStart('_!!_abc', '_!') // => 'abc'\n * ```\n * @param str The string to trim\n * @param chars The characters to trim\n * @returns The trimmed string\n */\n\nexport function trimStart(str: string, chars: string): string {\n let startIndex = 0;\n while (startIndex < str.length && chars.includes(str[startIndex])) {\n startIndex++;\n }\n return str.slice(startIndex);\n}", "/**\n * Truncates a string if it's longer than the given maximum length.\n * The last characters of the truncated string are replaced with the ellipsis\n * string which defaults to \"...\".\n *\n * @example\n * truncate(\"Hello, world!\", { length: 5 })\n * // => \"Hello...\"\n * \n * truncate(\"Hello, world!\", { length: 5, ellipsis: \" [...]\" })\n * // => \"Hello [...]\"\n * \n * truncate(\"Hello, world!\", { length: 5, separator: \" \" })\n * // => \"Hello, ...\"\n * \n * @param str The string to truncate\n * @param options The options object\n * @param options.length The maximum string length (default: 30)\n * @param options.ellipsis The string to indicate text is omitted (default: \"...\")\n * @param options.separator The separator pattern to truncate to (default: none)\n * @returns The truncated string\n */\n\nexport function truncate(str: string, options?: { length?: number; ellipsis?: string; separator?: string }): string {\n const { length = 30, ellipsis = \"...\", separator } = options ?? {};\n if (str.length <= length) return str;\n\n const end = length - ellipsis.length;\n\n if (end < 1) \n return ellipsis;\n\n // Actually long enough to truncate the string\n let truncated = str.slice(0, end);\n\n if (separator) {\n const sepIndex = truncated.lastIndexOf(separator);\n if (sepIndex > -1) {\n truncated = truncated.slice(0, sepIndex);\n }\n }\n\n return truncated + ellipsis;\n}\n", "const htmlEntitiesRegex = /&(?:amp|lt|gt|quot|#39);/g;\nconst entityMap = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n [\""\", '\"'],\n [\"'\", \"'\"]\n]);\n\n/**\n * Converts the HTML entities `&`, `<`, `>`, `"` and `'`\n * in a string to their corresponding characters.\n *\n * @example\n * unescapeHtml('fred, barney, & pebbles')\n * // => 'fred, barney, & pebbles'\n * @param str The string to unescape.\n * @returns Returns the unescaped string.\n */\n\nexport function unescapeHtml(str: string): string {\n return str.replace(htmlEntitiesRegex, (entity: string) => entityMap.get(entity)!);\n}\n", "\n/**\n * Checks if a value is empty.\n * \n * Supports: strings, arrays, objects, maps, sets, typed arrays.\n * @example\n * isEmpty(null)\n * // => true\n *\n * isEmpty({})\n * // => true\n *\n * isEmpty(\"\")\n * // => true\n *\n * isEmpty([1, 2, 3])\n * // => false\n *\n * isEmpty('abc')\n * // => false\n *\n * isEmpty({ 'a': 1 })\n * // => false\n * @param value The value to check.\n * @returns Returns `true` if `value` is empty, else `false`.\n */\n\nexport function isEmpty(value: string | object | null | undefined): boolean {\n if (value === null || value === undefined)\n return true;\n\n if (typeof value === \"string\" || Array.isArray(value))\n return value.length === 0;\n\n if (value instanceof Map || value instanceof Set)\n return value.size === 0;\n\n if (ArrayBuffer.isView(value))\n return value.byteLength === 0;\n\n if (typeof value === \"object\")\n return Object.keys(value).length === 0;\n\n return false;\n}\n", "/* eslint-disable complexity */\nimport type { PlainObject } from \"@type/PlainObject.js\";\n\nimport { isPlainObject } from \"./isPlainObject.js\";\n\ntype TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;\n\n/**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n * \n * Supports: primitives, arrays, objects, dates, regexes, maps, sets, buffers, typed arrays\n * \n * @example\n * const object = { a: { b: 2 } };\n * const other = { a: { b: 2 } };\n *\n * isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n * @param a The value to compare.\n * @param b The other value to compare.\n * @returns Returns `true` if the values are equivalent, else `false`.\n */\n\nexport function isEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n \n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b))\n return isSameArray(a, b);\n\n if (a instanceof Date && b instanceof Date)\n return a.getTime() === b.getTime();\n\n if (a instanceof RegExp && b instanceof RegExp)\n return a.toString() === b.toString();\n\n if (isPlainObject(a) && isPlainObject(b))\n return isSameObject(a, b);\n\n if (a instanceof ArrayBuffer && b instanceof ArrayBuffer)\n return dataViewsAreEqual(new DataView(a), new DataView(b));\n\n if (a instanceof DataView && b instanceof DataView)\n return dataViewsAreEqual(a, b);\n\n if (isTypedArray(a) && isTypedArray(b)) {\n if (a.byteLength !== b.byteLength) return false;\n return isSameArray(a, b);\n }\n\n return false;\n}\n\nfunction isSameObject(a: PlainObject, b: PlainObject) {\n // check if the objects have the same keys\n const keys1 = Object.keys(a);\n const keys2 = Object.keys(b);\n if (!isEqual(keys1, keys2)) return false;\n\n // check if the values of each key in the objects are equal\n for (const key of keys1) {\n if (!isEqual(a[key], b[key])) return false;\n }\n\n // the objects are deeply equal\n return true;\n}\n\nfunction isSameArray(a: unknown[] | TypedArray, b: unknown[] | TypedArray) {\n if (a.length !== b.length) return false;\n return a.every((element, index) => isEqual(element, b[index]));\n}\n\nfunction dataViewsAreEqual(a: DataView, b: DataView) {\n if (a.byteLength !== b.byteLength) return false;\n for (let offset = 0; offset < a.byteLength; offset++) {\n if (a.getUint8(offset) !== b.getUint8(offset)) return false;\n }\n return true;\n}\n\nfunction isTypedArray(value: unknown): value is TypedArray {\n return ArrayBuffer.isView(value) && !(value instanceof DataView);\n}", "/**\n * Checks if given string is a valid URL\n * \n * @deprecated \n * **Deprecated: Use the native \"URL.canParse\" method instead.**\n * \n * @example\n * isUrl('https://google.com')\n * // => true\n * isUrl('google.com')\n * // => false\n * @param str The string to check.\n * @returns Returns `true` if given string is a valid URL, else `false`.\n */\n\nexport function isUrl(str: string): boolean {\n try {\n new URL(str);\n return true;\n } catch {\n return false;\n }\n}", "import type { Definition, Metadata, MethodDefinition, UIRequest } from '@devvit/protos';\nimport { CustomPostDefinition, UIResponse } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { Devvit } from '../Devvit.js';\nimport { BlocksHandler } from './blocks/handler/BlocksHandler.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport {\n makeUpgradeAppComponent,\n parseDevvitUserAgent,\n shouldShowUpgradeAppScreen,\n} from './upgrade-app-shim.js';\n\nconst FeatureUnavailable: Devvit.BlockComponent = () => (\n <vstack alignment=\"center middle\" width=\"100%\" height=\"100%\">\n <text>This feature is not available yet</text>\n </vstack>\n);\n\n/**\n * Extend me to add new surfaces to Devvit.\n */\nconst UIComponentBindings: [Definition, MethodDefinition, JSX.ComponentFunction][] = [\n [\n CustomPostDefinition,\n CustomPostDefinition.methods['renderPostContent'],\n (_props: {}, context: Devvit.Context) => Devvit.customPostType?.render(context) ?? null,\n ],\n [\n CustomPostDefinition,\n CustomPostDefinition.methods['renderPostComposer'],\n (_props: {}, _context: Devvit.Context) => <FeatureUnavailable />,\n ],\n];\n\nexport function makeHandler(\n component: JSX.ComponentFunction\n): (req: UIRequest, metadata: Metadata) => Promise<UIResponse> {\n return async (req: UIRequest, metadata: Metadata) => {\n const parsedUserAgent = parseDevvitUserAgent(metadata['devvit-user-agent']?.values?.[0] ?? '');\n if (parsedUserAgent && shouldShowUpgradeAppScreen(parsedUserAgent)) {\n const handler = new BlocksHandler(makeUpgradeAppComponent(parsedUserAgent.platform));\n return UIResponse.fromJSON(await handler.handle(req, metadata));\n }\n\n const handler = new BlocksHandler(component);\n return UIResponse.fromJSON(await handler.handle(req, metadata));\n };\n}\n\nexport function registerUIRequestHandlers(config: Config): void {\n for (const [definition, method, component] of UIComponentBindings) {\n config.provides(definition);\n extendDevvitPrototype(method.name, makeHandler(component));\n }\n}\n", "import {\n type Effect,\n type Metadata,\n type UIEvent,\n UIEventScope,\n type UIRequest,\n type UIResponse,\n} from '@devvit/protos';\nimport { isCircuitBreaker } from '@devvit/shared-types/CircuitBreaker.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { BlockElement } from '../../../Devvit.js';\nimport type { ReifiedBlockElement, ReifiedBlockElementOrLiteral } from '../BlocksReconciler.js';\nimport { BlocksTransformer } from '../BlocksTransformer.js';\nimport type { EffectEmitter } from '../EffectEmitter.js';\nimport { ContextBuilder } from './ContextBuilder.js';\nimport { _isTombstone, RenderContext } from './RenderContext.js';\nimport type { BlocksState, Hook, HookParams, HookSegment, Props } from './types.js';\nimport { RenderInterruptError } from './types.js';\n\n/**\n * This can be a global/singleton because render is synchronous.\n *\n * If you want to use this from somewhere else, please consider using one of the\n * functions like isRendering or registerHook, and then try to add additional\n * functions here if needed. Don't use this directly.\n */\nexport let _activeRenderContext: RenderContext | null = null;\n\nexport function useEffectEmitter(): EffectEmitter {\n if (!_activeRenderContext) {\n throw new Error('Hooks can only be declared at the top of a component.');\n }\n return _activeRenderContext;\n}\n\nexport function isRendering(): boolean {\n return _activeRenderContext !== null;\n}\n\nfunction _structuredClone<T extends JSONValue>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * See [HookSegment](./types.ts) for more information.\n */\nexport function assertValidNamespace(input: string): void {\n const regex = /[-.]/;\n const valid = input !== '' && !regex.test(input);\n\n if (!valid) {\n throw new Error(\n `Hook with namespace '${input}' is invalid. Hook namespaces cannot be empty string or contain dashes/dots because they are used as delimiters internally. Please update the hook namespace and try again.`\n );\n }\n}\n\ntype RegisterHookOptions<H extends Hook> = HookSegment & {\n /**\n * Factory function to build the hook. Only called once for the entire lifecycle of a\n * BlocksHandler.handle() invocation.\n */\n initializer: (p: HookParams) => H;\n};\n\n/**\n * This can get called multiple times in a given render. Initialize is only called once!\n *\n * This is the recommended low-level interface for creating hooks like useState or useAsync.\n *\n * Practically, this initializes your hook if it doesn't already exist, and makes sure\n * that its state gets all sync'd up.\n *\n * @param HookSegment -- A name for this hook. This is used to dedupe hooks.\n * @param initializer\n * factory for building this hook\n * @returns\n */\nexport function registerHook<H extends Hook>({\n initializer,\n ...hookSegment\n}: RegisterHookOptions<H>): H {\n if (!_activeRenderContext) {\n throw new Error(\n \"Hooks can only be declared at the top of a component. You cannot declare hooks outside of components or inside of event handlers. It's almost always a mistake to declare hooks inside of loops or conditionals.\"\n );\n }\n\n assertValidNamespace(hookSegment.namespace);\n\n const hookId = _activeRenderContext.nextHookId(hookSegment);\n const context = _activeRenderContext;\n const params: HookParams = {\n hookId,\n invalidate: () => {\n context._changed[hookId] = true;\n context._state[hookId] = context?._hooks[hookId]?.state;\n },\n context: _activeRenderContext,\n };\n const fromNull =\n _activeRenderContext._state[hookId] === undefined ||\n _isTombstone(_activeRenderContext._state[hookId]);\n _activeRenderContext._hooks[hookId] = _activeRenderContext._hooks[hookId] ?? initializer(params);\n const hook: H = _activeRenderContext._hooks[hookId] as H;\n\n if (!fromNull) {\n hook.state = _activeRenderContext._state[hookId];\n }\n hook.onStateLoaded?.();\n if (fromNull && hook.state !== undefined && hook.state !== null) {\n params.invalidate();\n }\n return hook;\n}\n\nexport let _latestBlocksHandler: BlocksHandler | null = null;\n\n/**\n * Limit the number of render cycles to prevent infinite loops.\n */\nconst MaxIterations = 128;\n\n/**\n * Replacing BlocksReconciler, the model is now less of a \"reconciliation\", and more\n * of a handling a request/response lifecycle.\n *\n */\nexport class BlocksHandler {\n #root: JSX.ComponentFunction;\n #contextBuilder: ContextBuilder = new ContextBuilder();\n #blocksTransformer: BlocksTransformer = new BlocksTransformer(\n () => this._latestRenderContext?.devvitContext?.assets\n );\n _latestRenderContext: RenderContext | null = null;\n\n constructor(root: JSX.ComponentFunction) {\n if (this.#debug) console.debug('[blocks] BlocksHandler v1');\n this.#root = root;\n _latestBlocksHandler = this;\n }\n\n async handle(request: UIRequest, metadata: Metadata): Promise<UIResponse> {\n const context = new RenderContext(request, metadata);\n context.devvitContext = this.#contextBuilder.buildContext(context, request, metadata);\n\n let blocks;\n\n /**\n * Events on the main queue must be handled in order, so that state is updated in the correct order. Events\n * on other queues can be handled in parallel, because they only emit effects.\n *\n * There is an optimization here to process SendEventEffects locally, instead of letting them bubble up to the\n * platform. This prevents a round trip to the platform for every event.\n *\n * This also means we need to respect execution queues here, and not just in the platform.\n */\n const drop = request.events.length - MaxIterations;\n let eventsToProcess = request.events;\n if (drop > 0) {\n eventsToProcess = [];\n\n // Filter out old realtime messages until drop is met.\n {\n let dropped = 0;\n for (const ev of request.events) {\n if (dropped < drop && ev.realtimeEvent) dropped++;\n else eventsToProcess.push(ev);\n }\n }\n\n // Filter out _any_ old remaining messages until drop is met.\n while (eventsToProcess.length > MaxIterations) eventsToProcess.shift();\n\n console.warn(`dropped ${drop} events`);\n }\n const noEvents = !eventsToProcess.length;\n const isMainQueue = noEvents || eventsToProcess.some((e) => !e.async);\n\n const isBlockingSSR = eventsToProcess.some((e) => e.blocking);\n\n let changed: { [hookID: string]: true };\n let progress:\n | {\n _state: BlocksState;\n _effects: { [key: string]: Effect };\n }\n | undefined;\n let remaining: UIEvent[] = [...eventsToProcess];\n\n /**\n * When purely rendering, we now add one synthetic event. This enables us to loop over the events that an initial render may generate\n * in an async world. This is a bit of a hack, but it's the simplest way to handle this.\n */\n if (eventsToProcess.length === 0) {\n eventsToProcess.push({\n scope: UIEventScope.ALL,\n });\n }\n\n if (this.#debug) console.debug('[blocks] starting processing events');\n\n let iterations = 0;\n while (eventsToProcess.length > 0) {\n if (iterations++ > MaxIterations) {\n throw new Error(`Exceeded maximum iterations of ${MaxIterations}`);\n }\n if (this.#debug)\n console.debug('[blocks] processing events loop iteration', eventsToProcess.length);\n /**\n * A concurrently executable batch is a set of events that can be executed in parallel. This either one main queue event,\n * or any number of other queue events.\n */\n const batch = [];\n if (!eventsToProcess[0].async) {\n batch.push(eventsToProcess.shift()!);\n } else {\n while (eventsToProcess[0]?.async) {\n batch.push(eventsToProcess.shift()!);\n }\n }\n if (!batch.length) throw Error('batch must have at least one event');\n try {\n if (batch[0].async) {\n const stateCopy = _structuredClone(context._state);\n await this.#handleAsyncQueues(context, ...batch);\n // enforce that state updates are only allowed on the main queue.\n context._state = stateCopy;\n } else {\n await this.#handleMainQueue(context, ...batch);\n }\n } catch (e) {\n /**\n * Guard clause, retries are only applicable for circuit breakers. If we're not a circuit breaker, we need to throw the error.\n */\n if (!isCircuitBreaker(e)) {\n throw e;\n }\n\n if (this.#debug) console.debug('[blocks] caught in handler', e);\n context._latestRenderContent = undefined;\n /**\n * If we have a progress, we can recover from an error by rolling back to the last progress, and then letting the\n * remaining events be reprocessed.\n */\n if (progress) {\n context._state = progress._state;\n context._changed = changed!;\n context._effects = progress._effects;\n\n const requeueable = remaining.map((e) => {\n const requeueEvent = { ...e };\n requeueEvent.retry = true;\n return requeueEvent;\n });\n context.addToRequeueEvents(...requeueable);\n break;\n } else {\n if (!isCircuitBreaker(e)) {\n console.error('[blocks] unhandled error in handler', e);\n }\n throw e;\n }\n }\n\n if (this.#debug) console.debug('[blocks] remaining events', context._requeueEvents);\n const remainingRequeueEvents: UIEvent[] = [];\n for (const event of context._requeueEvents) {\n if (!isMainQueue && !event.async) {\n if (this.#debug)\n console.debug(\n '[blocks] NOT reprocessing event in BlocksHandler, sync mismatch A',\n event\n );\n // We're async, this is a main queue event. We need to send it back to the platform to let\n // the platform synchronize it.\n remainingRequeueEvents.push(event);\n continue;\n }\n if (isMainQueue && event.async && !isBlockingSSR) {\n if (this.#debug)\n console.debug(\n '[blocks] NOT reprocessing event in BlocksHandler, sync mismatch B',\n event\n );\n // We're main queue, and this is an async event. We're not in SSR mode, so let's prioritize\n // returning control quickly to the platform so we don't block event loops.\n remainingRequeueEvents.push(event);\n continue;\n }\n if (this.#debug) console.debug('[blocks] reprocessing event in BlocksHandler', event);\n eventsToProcess.push(event);\n }\n context._requeueEvents = remainingRequeueEvents; //\n\n /**\n * If we're going back through this again, we need to capture the progress, and the remaining events.\n */\n if (eventsToProcess.length > 0) {\n changed = { ...context._changed };\n progress = {\n _state: _structuredClone(context._state),\n _effects: { ...context._effects },\n };\n remaining = [...eventsToProcess];\n }\n } // End of while loop\n\n // Rendering only happens on the main queue.\n if (isMainQueue) {\n if (!context._latestRenderContent) {\n context._latestRenderContent = this.#renderRoot(\n this.#root,\n context.request.props ?? {},\n context\n );\n }\n const tags = context._latestRenderContent;\n\n if (tags) {\n blocks = this.#blocksTransformer.createBlocksElementOrThrow(tags);\n blocks = this.#blocksTransformer.ensureRootBlock(blocks);\n }\n }\n\n return {\n state: context._changedState,\n effects: context.effects,\n blocks: blocks,\n events: context._requeueEvents,\n };\n }\n\n get #debug(): boolean {\n return !!this._latestRenderContext?.devvitContext.debug.blocks;\n }\n\n #loadHooks(context: RenderContext, ..._events: UIEvent[]): void {\n context._hooks = {};\n\n this.#renderRoot(this.#root, context.request.props ?? {}, context);\n }\n\n /**\n * These can all run in parallel, because they only emit effects\n */\n async #handleAsyncQueues(context: RenderContext, ...batch: UIEvent[]): Promise<void> {\n this.#loadHooks(context, ...batch);\n\n await Promise.all(\n batch.map(async (event) => {\n if (!event.async) {\n throw new Error(\n \"You can't mix main and other queues in one batch. This is likely a platform bug. Please file an issue in the Discord for someone to help! https://discord.com/channels/1050224141732687912/1115441897079574620\"\n );\n }\n await this.#attemptHook(context, event);\n })\n );\n }\n\n async #attemptHook(context: RenderContext, event: UIEvent): Promise<void> {\n const hook = context._hooks[event.hook!];\n if (hook?.onUIEvent) {\n try {\n await hook.onUIEvent(event, context);\n } catch (e) {\n if (isCircuitBreaker(e)) {\n if (this.#debug) {\n console.error('Server call required', e);\n }\n } else {\n console.error('Error in event handler', e);\n }\n\n throw e;\n }\n } else {\n await context.handleUndeliveredEvent(event);\n }\n }\n\n async #handleMainQueue(context: RenderContext, ...batch: UIEvent[]): Promise<void> {\n // We need to handle events in order, so that the state is updated in the correct order.\n for (const event of batch) {\n if (this.#debug) console.log('[blocks] handling main queue event', event);\n if (this.#debug) console.log('[blocks] before', context._state);\n this.#loadHooks(context, event);\n await this.#attemptHook(context, event);\n if (this.#debug) console.log('[blocks] after', context._state);\n }\n // We need this to process possible unmounts given the most recent event\n this.#loadHooks(context);\n }\n\n #renderRoot(\n component: JSX.ComponentFunction,\n props: Props,\n context: RenderContext\n ): ReifiedBlockElement | undefined {\n if (this.#debug) console.debug('[blocks] renderRoot');\n context._generated = {};\n _activeRenderContext = context;\n this._latestRenderContext = context;\n try {\n const roots = this.#render(component, props, context);\n\n if (roots.length !== 1) {\n throw new Error(\n `There can only be one root element, received: ${roots.length}. Please wrap these elements in a <vstack></vstack> or <hstack></hstack> to continue. Fragments cannot be used as a root element.`\n );\n }\n const root = roots[0];\n\n // By the time it gets to this point the promise has been serialized.\n // That's why the check is not root instanceof Promise\n if (root === '[object Promise]') {\n throw new Error(\n `Root elements cannot be async. To use data from an async endpoint, please use \"const [data] = useState(async () => {/** your async code */})\".`\n );\n }\n\n if (typeof root === 'string') {\n throw new Error(\n `The root element must return a valid block, not a string. Try wrapping the element in a <text>your content</text> tag to continue.`\n );\n }\n\n context._latestRenderContent = root;\n\n return root;\n } catch (e) {\n if (e instanceof RenderInterruptError) {\n return undefined;\n } else {\n throw e;\n }\n } finally {\n _activeRenderContext = null;\n }\n }\n\n #render(\n component: JSX.ComponentFunction,\n props: Props,\n context: RenderContext\n ): ReifiedBlockElementOrLiteral[] {\n // Anonymous functions don't have a name\n // use || instead of ?? due to empty string\n context.push({ namespace: component.name || 'anonymous', ...props });\n try {\n const element = component(props, context.devvitContext);\n\n if (element instanceof Promise) {\n throw new Error(\n `Components (found: ${component.name ?? 'unknown component'}) cannot be async. To use data from an async endpoint, please use \"const [data] = useState(async () => {/** your async code */})\".`\n );\n }\n\n return this.#renderElement(element, context);\n } finally {\n context.pop();\n }\n }\n\n #renderList(list: JSX.Element[], context: RenderContext): ReifiedBlockElementOrLiteral[] {\n list = list.flat(Infinity);\n return list.flatMap((e, i) => {\n if (e && typeof e === 'object' && 'props' in e) {\n if (!e.props?.key) {\n e.props = e.props ?? {};\n e.props.key = `${i}`;\n }\n }\n return this.#renderElement(e, context);\n });\n }\n\n #renderElement(element: JSX.Element, context: RenderContext): ReifiedBlockElementOrLiteral[] {\n if (Array.isArray(element)) {\n return this.#renderList(element, context);\n } else if (isBlockElement(element)) {\n // This means the element is a fragment\n if (element.type === undefined) {\n try {\n context.push({ namespace: 'fragment', ...element.props });\n // Since it's a fragment, don't reifyProps because you can't have props on a fragment\n return this.#renderList(element.children, context);\n } finally {\n context.pop();\n }\n } else if (typeof element.type === 'function') {\n const propsWithChildren = { ...element.props, children: element.children.flat(Infinity) };\n return this.#render(element.type, propsWithChildren, context);\n } else {\n try {\n context.push({ namespace: element.type, ...element.props });\n const reifiedChildren = this.#renderList(element.children, context);\n const reifiedProps = this.#reifyProps(element.props ?? {});\n\n return [{ type: element.type, children: reifiedChildren, props: reifiedProps }];\n } finally {\n context.pop();\n }\n }\n } else {\n return [(element ?? '').toString()];\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n #reifyProps(props: { [key: string]: any }): { [key: string]: JSONValue } {\n const reifiedProps: { [key: string]: JSONValue } = {};\n for (const key in props) {\n if (typeof props[key] === 'undefined') {\n // skip\n } else if (typeof props[key] === 'function') {\n const hook = registerHook({\n namespace: key,\n key: false,\n initializer: ({ hookId }) => ({\n hookId,\n state: null,\n onUIEvent: (event) => {\n if (event.userAction) {\n return props[key](event.userAction.data);\n } else if (event.webView?.postMessage) {\n // Fallback to deprecated message field for mobile client backwards compatibility\n const message = event.webView.postMessage.jsonString\n ? JSON.parse(event.webView.postMessage.jsonString)\n : event.webView.postMessage.message;\n return props[key](message);\n }\n },\n }),\n });\n reifiedProps[key] = hook.hookId;\n if ('captureHookRef' in props[key]) {\n props[key].captureHookRef();\n }\n } else {\n // push value through the JSON parser to filter incompatible types\n const value = JSON.parse(JSON.stringify(props[key]));\n if (value !== undefined && value !== null) {\n reifiedProps[key] = value;\n }\n }\n }\n return reifiedProps;\n }\n}\n\nfunction isBlockElement(e: JSX.Element): e is BlockElement {\n return typeof e === 'object' && e != null && 'type' in e;\n}\n", "// This message is referenced in LocalRuntimeJSEngine.kt.\nexport const CIRCUIT_BREAKER_MSG = 'ServerCallRequired';\n// to-do: use TypeScript to reference this function in SandboxedRuntimeLite.\n/** @arg method API method called for debugging. Eg, UserDataByAccountIds. */\nexport function CircuitBreak(method) {\n // Warning: this function is serialized by SandboxRuntimeLite. Don't use\n // import dependencies.\n // Error must be postable.\n const err = Error('ServerCallRequired');\n err.cause = { method, stack: err.stack };\n return err;\n}\nexport class CircuitBreakerResponse extends Error {\n constructor(response, cause) {\n super(CIRCUIT_BREAKER_MSG);\n this.response = response;\n this.cause = cause;\n this.name = 'CircuitBreakerResponse';\n }\n}\nexport function isCircuitBreaker(err) {\n return err?.message === CIRCUIT_BREAKER_MSG;\n}\n", "import type { JSONValue, RedisClient } from '../../../../index.js';\n\nexport type CacheEntry = {\n value: JSONValue | null;\n expires: number; // Timestamp in milliseconds\n error: string | null;\n errorTime: number | null;\n checkedAt: number;\n errorCount: number;\n};\n\nexport type Clock = {\n now(): Date;\n};\n\nexport const SystemClock: Clock = {\n now() {\n return new Date();\n },\n};\n\nexport type CacheOptions = {\n /**\n * Time to live in milliseconds.\n */\n ttl: number;\n\n /**\n * Key to use for caching.\n */\n key: string;\n};\n\nexport type LocalCache = { [key: string]: CacheEntry };\n\nexport function _namespaced(key: string): string {\n return `__autocache__${key}`;\n}\nexport function _lock(key: string): string {\n return `__lock__${key}`;\n}\n\nconst pollEvery = 300; // milli\nconst maxPollingTimeout = 1000; // milli\nconst minTtlValue = 5000;\nexport const retryLimit = 3;\nconst errorRetryProbability = 0.1;\nexport const clientRetryDelay = 1000;\nexport const allowStaleFor = 30_000;\n\ntype WithLocalCache = {\n __cache?: LocalCache;\n};\n\nfunction _unwrap<T>(entry: CacheEntry): T {\n if (entry.error) {\n throw new Error(entry.error);\n }\n return entry.value as T;\n}\n\n/**\n * Refactored out into a class to allow for easier testing and clarity of purpose.\n *\n * This class is responsible for managing the caching of promises. It is a layered cache, meaning it will first check\n * the local cache, then the redis cache, and finally the source of truth. It will also handle refreshing the cache according\n * to the TTL and error handling.\n *\n * Please note that in order to prevent a stampede of requests to the source of truth, we use a lock in redis to ensure only one\n * request is made to the source of truth at a time. If the lock is obtained, the cache will be updated and the lock will be released.\n *\n * Additionally, we use a polling mechanism to fetch the cache if the lock is not obtained. This is to prevent unnecessary errors.\n *\n * Finally, we also want to prevent stampedes against redis for the lock election and the retries. We use a ramping probability to ease in the\n * attempts to get the lock, and not every error will trigger a retry.\n *\n * This means that the cache will be eventually consistent, but will not be immediately consistent. This is a tradeoff we are willing to make.\n * Additionally, this means that the TTL is not precice. The cache may be updated a bit more often than the TTL, but it will not be updated less often.\n *\n */\nexport class PromiseCache {\n #redis: RedisClient;\n /**\n * LocalCache is just an aliased reference to this.#state. Mutations to\n * this object will also mutate this.#state\n */\n #localCache: LocalCache = {};\n #clock: Clock;\n #state: WithLocalCache;\n\n constructor(redis: RedisClient, state: WithLocalCache, clock: Clock = SystemClock) {\n this.#redis = redis;\n this.#state = state;\n this.#clock = clock;\n }\n\n /**\n * This is the public API for the cache. Call this method to cache a promise.\n *\n * @param closure\n * @param options\n * @returns\n */\n async cache<T extends JSONValue>(closure: () => Promise<T>, options: CacheOptions): Promise<T> {\n this.#state.__cache ??= {};\n this.#localCache = this.#state.__cache;\n\n this.#enforceTTL(options);\n\n const localCachedAnswer = this.#localCachedAnswer<T>(options.key);\n if (localCachedAnswer !== undefined) {\n return localCachedAnswer;\n }\n\n const existing = await this.#redisEntry(options.key);\n const entry = await this.#maybeRefreshCache(options, existing, closure);\n\n return _unwrap(entry);\n }\n\n /**\n * Get the value from the local cache if it exists and is not expired. We're willing to retry errors, and we're willing\n * to throw errors if we have them in cache.\n *\n * We don't want to retry excessively, so we have a limit on the number of retries. If someone else has retried in the last\n * clientRetryDelay, let's not retry again. We also have a probability of retrying, so we don't retry every time.\n */\n #localCachedAnswer<T extends JSONValue>(key: string): T | undefined {\n const val = this.#localCache[key];\n if (val) {\n const now = this.#clock.now().getTime();\n const hasRetryableError =\n val?.error &&\n val?.errorTime &&\n val.errorCount < retryLimit &&\n Math.random() < errorRetryProbability &&\n val.errorTime! + clientRetryDelay < now;\n const expired = val?.expires && val.expires < now && val.checkedAt + clientRetryDelay < now;\n if (expired || hasRetryableError) {\n delete this.#localCache[key];\n return undefined;\n } else {\n return _unwrap(val);\n }\n }\n return undefined;\n }\n\n /**\n * If we've bothered to check redis, we're already on the backend. Let's see if the cache either (1) contains an error, (2)\n * is expired, (3) is missing, or (4) is about to expire. If any of these are true, we'll refresh the cache based on heuristics.\n *\n * We'll always refresh if missing or expired, but its probabilistic if we'll refresh if about to expire or if we have an error.\n */\n async #maybeRefreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const expires = entry?.expires;\n const rampProbability = expires ? this.#calculateRamp(expires) : 1;\n if (\n !entry ||\n (entry?.error && entry.errorCount < retryLimit && errorRetryProbability > Math.random()) ||\n rampProbability > Math.random()\n ) {\n return this.#refreshCache(options, entry, closure);\n } else {\n return entry!;\n }\n }\n\n /**\n * The conditions for refreshing the cache are handled in the calling method, which should be\n * #maybeRefreshCache.\n *\n * If you don't win the lock, you'll poll for the cache. If you don't get the cache within maxPollingTimeout, you'll throw an error.\n */\n async #refreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const lockKey = _lock(options.key);\n const now = this.#clock.now().getTime();\n\n /**\n * The write lock should last for a while, but not the full TTL. Hopefully write attempts settle down after a while.\n */\n const lockExpiration = new Date(now + options.ttl / 2);\n\n const lockObtained = await this.#redis.set(lockKey, '1', {\n expiration: lockExpiration,\n nx: true,\n });\n if (lockObtained) {\n return this.#updateCache(options.key, entry, closure, options.ttl);\n } else if (entry) {\n // This entry is still valid, return it\n return entry;\n } else {\n const start = this.#clock.now();\n return this.#pollForCache(start, options.key, options.ttl);\n }\n }\n\n async #pollForCache(start: Date, key: string, ttl: number): Promise<CacheEntry> {\n const pollingTimeout = Math.min(ttl, maxPollingTimeout);\n const existing = await this.#redisEntry(key);\n if (existing) {\n return existing;\n }\n\n if (this.#clock.now().getTime() - start.getTime() >= pollingTimeout) {\n throw new Error(`Cache request timed out trying to get data at key: ${key}`);\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollEvery));\n return this.#pollForCache(start, key, ttl);\n }\n\n /**\n * Actually update the cache. This is the method that will be called if we have the lock.\n */\n async #updateCache<T extends JSONValue>(\n key: string,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>,\n ttl: number\n ): Promise<CacheEntry> {\n const expires = this.#clock.now().getTime() + ttl;\n entry = entry ?? {\n value: null,\n expires,\n errorCount: 0,\n error: null,\n errorTime: null,\n checkedAt: 0,\n };\n try {\n entry.value = await closure();\n entry.error = null;\n entry.errorCount = 0;\n entry.errorTime = null;\n } catch (e) {\n entry.value = null;\n entry.error = (e as Error).message ?? 'Unknown error';\n entry.errorTime = this.#clock.now().getTime();\n entry.errorCount++;\n }\n\n this.#localCache[key] = entry;\n\n await this.#redis.set(_namespaced(key), JSON.stringify(entry), {\n expiration: new Date(expires + allowStaleFor),\n });\n\n /**\n * Unlocking will allow retries to happen if there was an error. Otherwise we don't unlock, because the lock\n * will expire on its own.\n */\n if (entry.error && entry.errorCount < retryLimit) {\n await this.#redis.del(_lock(key));\n }\n\n return entry;\n }\n\n /**\n * This is the schedule for optimistic pre-fetch of an about-to-expire cache. It exponentially ramps in, which hopefully provides\n * a degree of flexibility in the face of varying traffic levels.\n */\n #calculateRamp(expiry: number): number {\n const now = this.#clock.now().getTime();\n const remaining = expiry - now;\n\n if (remaining < 0) {\n return 1;\n } else if (remaining < 1000) {\n return 0.1;\n } else if (remaining < 2000) {\n return 0.01;\n } else if (remaining < 3000) {\n return 0.001;\n } else {\n return 0;\n }\n }\n\n async #redisEntry(key: string): Promise<CacheEntry | undefined> {\n const val = await this.#redis.get(_namespaced(key));\n if (val) {\n const entry = JSON.parse(val) as CacheEntry;\n entry.checkedAt = this.#clock.now().getTime();\n this.#localCache[key] = entry;\n return entry;\n }\n return undefined;\n }\n\n #enforceTTL(options: CacheOptions): void {\n if (options.ttl < minTtlValue) {\n console.warn(\n `Cache TTL cannot be less than ${minTtlValue} milliseconds! Updating ttl value of ${options.ttl} to ${minTtlValue}.`\n );\n options.ttl = minTtlValue;\n }\n }\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { RedisClient } from '../../../../types/redis.js';\nimport type { CacheOptions, Clock, LocalCache } from './promise_cache.js';\nimport { PromiseCache, SystemClock } from './promise_cache.js';\nimport type { RenderContext } from './RenderContext.js';\n\nexport type CacheHelper = <T extends JSONValue>(\n fn: () => Promise<T>,\n options: CacheOptions\n) => Promise<T>;\n\nexport function makeCache(\n redis: RedisClient,\n state: RenderContext['_state'] & { __cache?: LocalCache },\n clock: Clock = SystemClock\n): CacheHelper {\n const pc = new PromiseCache(redis, state, clock);\n return pc.cache.bind(pc);\n}\n", "import { EffectType, Form, type Toast as ToastProto, ToastAppearance } from '@devvit/protos';\nimport type { JSONObject, JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { Comment, Post, Subreddit, User } from '../../../../apis/reddit/models/index.js';\nimport { assertValidFormFields } from '../../../../apis/ui/helpers/assertValidFormFields.js';\nimport { transformFormFields } from '../../../../apis/ui/helpers/transformForm.js';\nimport type { Toast } from '../../../../types/toast.js';\nimport type { UIClient as _UIClient } from '../../../../types/ui-client.js';\nimport type { WebViewUIClient } from '../../../../types/web-view-ui-client.js';\nimport { _activeRenderContext } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport { getFormDefinition } from './useForm.js';\n\nexport function useUI(): _UIClient {\n const renderContext = _activeRenderContext;\n if (!renderContext) {\n throw new Error('useUI can only be called from within the top level of a component.');\n }\n return new UIClient(renderContext);\n}\n\nexport class UIClient implements _UIClient {\n readonly #renderContext: RenderContext;\n // Auto-incrementing count of the number of WebviewMessage effects called this frame.\n // Used as part of the dedup key for emitEvent to prevent messages from being dedup'd.\n #webViewMessageCount: number = 0;\n readonly #webViewClient: WebViewUIClient;\n\n constructor(renderContext: RenderContext) {\n this.#renderContext = renderContext;\n this.#webViewClient = {\n postMessage: this.#webViewPostMessage,\n };\n }\n\n get webView(): WebViewUIClient {\n return this.#webViewClient;\n }\n\n showForm(formKey: FormKey, data?: JSONObject | undefined): void {\n const formDefinition = getFormDefinition(this.#renderContext, formKey);\n\n if (!formDefinition) {\n throw new Error('Form does not exist. Make sure you have added it using useForm.');\n }\n\n const formData =\n formDefinition.form instanceof Function\n ? formDefinition.form(data ?? {})\n : formDefinition.form;\n\n const form: Form = {\n fields: [],\n id: formKey,\n title: formData.title,\n acceptLabel: formData.acceptLabel,\n cancelLabel: formData.cancelLabel,\n shortDescription: formData.description,\n };\n\n assertValidFormFields(formData.fields);\n form.fields = transformFormFields(formData.fields);\n\n this.#renderContext.emitEffect(formKey, {\n type: EffectType.EFFECT_SHOW_FORM,\n showForm: {\n form,\n },\n });\n }\n\n showToast(text: string): void;\n showToast(toast: Toast): void;\n showToast(textOrToast: string | Toast): void {\n let toast: ToastProto;\n\n if (textOrToast instanceof Object) {\n toast = {\n text: textOrToast.text,\n appearance:\n textOrToast.appearance === 'success' ? ToastAppearance.SUCCESS : ToastAppearance.NEUTRAL,\n };\n } else {\n toast = {\n text: textOrToast,\n };\n }\n\n this.#renderContext.emitEffect(textOrToast.toString(), {\n type: EffectType.EFFECT_SHOW_TOAST,\n showToast: {\n toast,\n },\n });\n }\n\n navigateTo(url: string): void;\n navigateTo(subreddit: Subreddit): void;\n navigateTo(post: Post): void;\n navigateTo(comment: Comment): void;\n navigateTo(user: User): void;\n navigateTo(thingOrUrl: string | Subreddit | Post | Comment | User): void {\n let url: string;\n\n if (typeof thingOrUrl === 'string') {\n // Validate URL\n url = new URL(thingOrUrl).toString();\n } else {\n url = new URL(thingOrUrl.permalink, 'https://www.reddit.com').toString();\n }\n this.#renderContext.emitEffect(url, {\n type: EffectType.EFFECT_NAVIGATE_TO_URL,\n navigateToUrl: {\n url,\n },\n });\n }\n\n #webViewPostMessage: WebViewUIClient['postMessage'] = <T extends JSONValue>(\n webViewIdOrMessage: string | T,\n message?: T | undefined\n ): void => {\n const webViewId = message !== undefined ? (webViewIdOrMessage as string) : '';\n const msg = message !== undefined ? message : webViewIdOrMessage;\n this.#renderContext.emitEffect(`postMessage${this.#webViewMessageCount++}`, {\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n postMessage: {\n webViewId,\n app: {\n message: msg, // This is deprecated, but populated for backwards compatibility\n jsonString: JSON.stringify(msg), // Encode as JSON for consistency with the mobile clients\n },\n },\n },\n });\n };\n}\n", "export function formKeyToHookId(formKey) {\n // extract the hook id from the form key with a regex\n const match = formKey.match(/form\\.hook\\.(.+)\\.0/);\n /**\n * It's tempting to throw here, but it will be RenderPost. All events for RenderPost flow to RenderPostContent\n * lands in `Devvit.ts`. That means form ids flow through here and in the new world we've changed what a hook ID looks\n * like. For now, we warn until the migration is complete and can later on turn this into a throw.\n */\n if (!match?.[1]) {\n // This shows on every form submit in RenderPost so just going to leave it out\n // console.warn(\n // `Could not parse a hook ref from form key '${formKey}'. Please make sure you are passing in a string that starts with 'form.hook.' and ends with '0'. If you are seeing this warning using RenderPost, it is safe to ignore.`\n // );\n return;\n }\n return match[1];\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\nimport { formKeyToHookId } from '@devvit/shared-types/useForm.js';\n\nimport { getFormValues } from '../../../../apis/ui/helpers/getFormValues.js';\nimport type {\n Form,\n FormDefinition,\n FormFunction,\n FormToFormValues,\n FormValues,\n} from '../../../../index.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { EventHandler, Hook, HookRef } from './types.js';\n\nclass UseFormHook implements Hook, FormDefinition {\n hookId: string;\n state: JSONValue = null;\n onUIEvent?: EventHandler | undefined;\n onStateLoaded?: (() => void) | undefined;\n form: Form | FormFunction;\n onSubmit: (values: FormValues) => void | Promise<void>;\n\n constructor(\n params: { hookId: string },\n form: Form | FormFunction,\n onSubmit: (values: FormValues) => void | Promise<void>\n ) {\n this.hookId = params.hookId;\n this.form = form;\n this.onSubmit = onSubmit;\n this.onUIEvent = async (event) => {\n if (event.formSubmitted) {\n await onSubmit(getFormValues(event.formSubmitted.results));\n }\n };\n }\n}\n\nexport function useForm<const T extends Form | FormFunction>(\n form: T,\n onSubmit: (values: FormToFormValues<T>) => void | Promise<void>\n): FormKey {\n const hook = registerHook({\n namespace: 'useForm',\n initializer: (params) =>\n new UseFormHook(params, form, onSubmit as (values: FormValues) => void | Promise<void>),\n });\n return hookRefToFormKey({ id: hook.hookId });\n}\n\nexport function hookRefToFormKey(hookRef: HookRef): FormKey {\n return `form.hook.${hookRef.id}.0`;\n}\n\nexport function getFormDefinition(renderContext: RenderContext, formKey: FormKey): UseFormHook {\n const hookId = formKeyToHookId(formKey);\n return renderContext.getHook({ id: hookId }) as UseFormHook;\n}\n", "import { EffectType, RealtimeSubscriptionStatus, type UIEvent } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { UseChannelResult } from '../../../../types/hooks.js';\nimport type { ChannelOptions } from '../../../../types/realtime.js';\nimport { ChannelStatus } from '../../../../types/realtime.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\ntype ChannelHookState = {\n /** `<app ID>:<install ID>:<channel name>`. */\n readonly channel: string;\n connected: boolean;\n /** Hook subscribe state, not RPC connection state. */\n subscribed: boolean;\n} & Hook['state'];\n\nclass ChannelHook<Message extends JSONValue> implements UseChannelResult<Message>, Hook {\n state: ChannelHookState;\n\n readonly #context: RenderContext;\n readonly #debug: boolean;\n /** Record state in BlocksHandler. */\n readonly #invalidate: () => void;\n readonly #opts: Readonly<ChannelOptions<Message>>;\n\n constructor(opts: Readonly<ChannelOptions<Message>>, params: Readonly<HookParams>) {\n this.#context = params.context;\n this.#debug = !!params.context._devvitContext?.debug.realtime;\n this.#opts = opts;\n this.#invalidate = params.invalidate;\n\n const appID = params.context.meta[Header.App]?.values[0];\n if (!appID) throw Error('useChannel missing app ID metadata');\n\n const installID = params.context.meta[Header.Installation]?.values[0];\n if (!installID) throw Error('useChannel missing install ID from metadata');\n\n const channel = `${appID}:${installID}:${opts.name}`;\n const duplicate = Object.values(this.#context._hooks)\n .filter((hook) => hook instanceof ChannelHook)\n .some((hook) => (hook as ChannelHook<Message>).state.channel === channel);\n if (duplicate) throw Error(`useChannel channel names must be unique; \"${channel}\" duplicated`);\n\n this.state = {\n channel,\n connected: false,\n subscribed: false,\n };\n }\n\n async onUIEvent(ev: UIEvent): Promise<void> {\n const realtime = ev.realtimeEvent;\n if (!realtime || !this.state.subscribed) return;\n switch (realtime.status) {\n case RealtimeSubscriptionStatus.REALTIME_SUBSCRIBED:\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" connected`);\n this.state.connected = true;\n this.#invalidate();\n await this.#opts.onSubscribed?.();\n break;\n case RealtimeSubscriptionStatus.REALTIME_UNSUBSCRIBED:\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" disconnected`);\n this.state.connected = false;\n this.#invalidate();\n await this.#opts.onUnsubscribed?.();\n break;\n default:\n if (this.#debug)\n console.debug(\n `[realtime] \"${this.state.channel}\" received message: ${JSON.stringify(\n ev,\n undefined,\n 2\n )}`\n );\n // to-do: define a RealtimeSubscriptionStatus.MESSAGE. this could have\n // been a oneOf but the current approach allows for status + data\n // and this default case will break if another new type is added.\n // expect msg. this must align to RealtimeClient.send().\n this.#opts.onMessage(realtime.event?.data?.msg);\n break;\n }\n }\n\n async send(msg: Message): Promise<void> {\n if (this.#debug)\n console.debug(\n `[realtime] \"${this.state.channel}\" send message: ${JSON.stringify(msg, undefined, 2)}`\n );\n if (!this.state.subscribed || !this.state.connected) {\n console.debug(`[realtime] \"${this.state.channel}\" send failed; channel not connected`);\n throw Error(`useChannel send failed; \"${this.state.channel}\" channel not connected`);\n }\n await this.#context.devvitContext.realtime.send(this.state.channel, msg);\n }\n\n get status(): ChannelStatus {\n if (this.state.subscribed && this.state.connected) return ChannelStatus.Connected;\n else if (this.state.subscribed && !this.state.connected) return ChannelStatus.Connecting;\n else if (!this.state.subscribed && this.state.connected) return ChannelStatus.Disconnecting;\n return ChannelStatus.Disconnected;\n }\n\n subscribe(): void {\n if (this.state.subscribed) return;\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" subscribed`);\n this.state.subscribed = true;\n this.#invalidate();\n this.#emitSubscribed();\n }\n\n unsubscribe(): void {\n if (!this.state.subscribed) return;\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" unsubscribed`);\n this.state.subscribed = false;\n this.#invalidate();\n this.#emitSubscribed();\n }\n\n #emitSubscribed(): void {\n const channels = Object.values(this.#context._hooks)\n .filter((hook) => hook instanceof ChannelHook && hook.state.subscribed)\n .map((hook) => (hook as ChannelHook<Message>).state.channel);\n this.#context.emitEffect(this.state.channel, {\n type: EffectType.EFFECT_REALTIME_SUB,\n realtimeSubscriptions: { subscriptionIds: channels },\n });\n }\n}\n\nexport function useChannel<Message extends JSONValue>(\n opts: Readonly<ChannelOptions<Message>>\n): UseChannelResult<Message> {\n if (!opts.name || /[^a-zA-Z0-9_]/.test(opts.name))\n throw Error(\n `useChannel error: The name \"${opts.name}\" you provided for the hook is invalid. Valid names can only contain letters, numbers, and underscores (_).`\n );\n\n // allow RealtimeEffectHandler to compute hook ID. maintain compatibility with\n // realtimeChannelToHookID().\n const id = `useChannel:${opts.name}`;\n return registerHook({\n id,\n namespace: id,\n initializer: (params) => new ChannelHook(opts, params),\n });\n}\n", "import type { IntervalDetails, UIEvent } from '@devvit/protos';\nimport { EffectType } from '@devvit/protos';\n\nimport type { UseIntervalResult } from '../../../../types/hooks.js';\nimport { registerHook } from './BlocksHandler.js';\nimport { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\n/**\n * Keeps track of all the intervals that are currently running in a convenient global.\n */\nconst intervals: { [hookId: string]: IntervalDetails } = {};\n\nRenderContext.addGlobalUndeliveredEventHandler('intervals', async (event, context) => {\n if (event.timer && event.hook) {\n delete intervals[event.hook];\n context.emitEffect('timers', {\n type: EffectType.EFFECT_SET_INTERVALS,\n interval: { intervals },\n });\n }\n});\n\nclass IntervalHook implements UseIntervalResult, Hook {\n #hookId: string;\n #invalidate: () => void;\n #callback: () => void | Promise<void>;\n #context: RenderContext;\n state = { duration: { seconds: 0, nanos: 0 }, running: false };\n constructor(callback: () => void | Promise<void>, requestedDelayMs: number, params: HookParams) {\n this.#invalidate = params.invalidate;\n this.#hookId = params.hookId;\n this.#callback = callback;\n this.#context = params.context;\n const seconds = Math.floor(requestedDelayMs / 1000);\n const nanos = (requestedDelayMs % 1000) * 1_000_000;\n this.state.duration = { seconds, nanos };\n }\n\n onStateLoaded(): void {\n if (this.state.running) {\n intervals[this.#hookId] = { duration: this.state.duration };\n } else {\n delete intervals[this.#hookId];\n }\n }\n\n async onUIEvent(_event: UIEvent): Promise<void> {\n await this.#callback();\n }\n\n start(): void {\n intervals[this.#hookId] = { duration: this.state.duration };\n this.state.running = true;\n this.#invalidate();\n this.#context.emitEffect('timers', {\n type: EffectType.EFFECT_SET_INTERVALS,\n interval: { intervals },\n });\n }\n\n stop(): void {\n delete intervals[this.#hookId];\n this.state.running = false;\n this.#invalidate();\n this.#context.emitEffect('timers', {\n type: EffectType.EFFECT_SET_INTERVALS,\n interval: { intervals },\n });\n }\n}\n\nexport function useInterval(\n callback: () => void | Promise<void>,\n requestedDelayMs: number\n): UseIntervalResult {\n const hook = registerHook({\n namespace: 'useInterval',\n initializer: (params) => {\n return new IntervalHook(callback, requestedDelayMs, params);\n },\n });\n\n return {\n start: () => hook.start(),\n stop: () => hook.stop(),\n };\n}\n", "import type { Effect, Metadata, UIEvent, UIRequest } from '@devvit/protos';\n\nimport type { Devvit } from '../../../Devvit.js';\nimport type { ReifiedBlockElement } from '../BlocksReconciler.js';\nimport type { EffectEmitter } from '../EffectEmitter.js';\nimport type { BlocksState, EventHandler, Hook, HookRef, HookSegment } from './types.js';\n\n/**\n * @internal\n *\n * Tombstone is a special object that indicates that a hook has been unmounted, and\n * therefore can be removed from the state.\n * */\nexport type Tombstone = { __deleted: true };\n\n/** @internal */\nexport function _isTombstone(value: unknown): boolean {\n return typeof value === 'object' && value !== null && '__deleted' in value;\n}\n\n/**\n * The RenderContext is a class that holds the state of the rendering process.\n *\n * There are many properties that start with an underscore, which is a convention we use to\n * indicate that they are private and should not be accessed directly. They are used internally\n * in tests and in the implementation of the BlocksHandler.\n *\n * DO your best to avoid adding new properties to this class to support new features. It will be tempting\n * to add special cases for new features, but we should strive to work within the existing framework.\n */\nexport class RenderContext implements EffectEmitter {\n readonly request: Readonly<UIRequest>;\n readonly meta: Readonly<Metadata>;\n #state: BlocksState;\n _segments: (HookSegment & { next: number })[] = [];\n #hooks: { [hookID: string]: Hook } = {};\n _prevHooks: { [hookID: string]: Hook } = {};\n _prevHookId: string = '';\n _effects: { [key: string]: Effect } = {};\n\n /**\n * While processing events, we do some renders to load hooks. If those renders produce valid content, then\n * we won't have to render at the end of the event processing, rather we can hang onto the last render and\n * reuse it.\n */\n _latestRenderContent: ReifiedBlockElement | undefined;\n\n /**\n * Has this state been mutated since initially loaded?\n *\n * _changed is used to determine the state deltas to report in the response\n * of the handle function inside of BlocksHandler. It's very important\n * that this list contains a comprehensive list of all hooks that have\n * changed during the entire invocation of BlocksHandler.handle.\n *\n * It may be tempting to clear the state deltas on every iteration via the\n * loop, but in doing so you'll create many roundtrips from the client to\n * the runtime resulting in the UI flickering.\n */\n _changed: { [hookID: string]: true } = {};\n /** Does this hook still exist in the most recent render? */\n _touched: { [hookID: string]: true } = {};\n /** Events that will re-enter the dispatcher queue */\n _requeueEvents: UIEvent[] = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n _rootProps: { [key: string]: any } = {};\n _generated: { [key: string]: boolean } = {};\n static _staticUndeliveredHandlers: { [key: string]: EventHandler } = {};\n _undeliveredHandlers: { [key: string]: EventHandler } = {};\n _devvitContext?: Devvit.Context;\n\n get devvitContext(): Devvit.Context {\n if (!this._devvitContext) {\n throw new Error('Devvit context not available');\n }\n return this._devvitContext;\n }\n\n set devvitContext(context: Devvit.Context) {\n this._devvitContext = context;\n }\n\n constructor(request: UIRequest, meta: Metadata) {\n this.request = request;\n this.meta = meta;\n this.#state = request.state ?? {\n __cache: {},\n };\n this._rootProps = request.props ?? {};\n }\n\n /** The state delta new to this render. */\n get _changedState(): BlocksState {\n const changed: BlocksState = {\n __cache: this.#state.__cache ?? {},\n };\n for (const key in this._changed) changed[key] = this._state[key];\n const unmounted = new Set(Object.keys(this._state));\n Object.keys(this.#hooks).forEach((key) => {\n if (key === '__cache') {\n return;\n }\n unmounted.delete(key);\n });\n unmounted.forEach((key) => {\n if (key === '__cache') {\n return;\n }\n\n const t: Tombstone = { __deleted: true };\n this._state[key] = changed[key] = t;\n });\n\n return changed;\n }\n\n get _hooks(): { [hookID: string]: Hook } {\n return this.#hooks;\n }\n\n set _hooks(hooks: { [hookID: string]: Hook }) {\n this._prevHooks = this.#hooks;\n this.#hooks = hooks;\n }\n\n /** The complete render state. */\n get _state(): BlocksState {\n return this.#state;\n }\n\n /** Replacing state resets the delta for the next render. */\n set _state(state: BlocksState) {\n // You may be tempted to put `this._changed = {}` here, please DON'T!\n // There are many times we may choose to reset the state while processing\n // events. Remember that the BlocksHandler can do N number of passes before\n // it determines it is time to send a response back to the client. _changed\n // needs to encompass all of those changes.\n\n // You may also be tempted to put `this._hooks = {}` here, please DON'T!\n // Some hooks (useState) may rely on values held on the previous hook. If you\n // set this._hooks = {} and then call #loadHooks from BlocksHandler, you will\n // accidentally remove all state held in prevHooks since the first step of\n // #loadHooks is to set hooks values to {}. Setting hooks to {} is a concern that\n // should live outside of RenderContext and inside of BlocksHandler on a case\n // by case basis.\n this.#state = state;\n }\n\n push(options: HookSegment): void {\n this._segments.push({ ...options, next: 0 });\n }\n\n pop(): void {\n this._segments.pop();\n }\n\n addUndeliveredEventHandler(id: string, handler: EventHandler): void {\n this._undeliveredHandlers[id] = handler;\n }\n\n addGlobalUndeliveredEventHandler(id: string, handler: EventHandler): void {\n RenderContext.addGlobalUndeliveredEventHandler(id, handler);\n }\n\n getHook(ref: HookRef): Hook {\n return this.#hooks[ref.id!];\n }\n /** Catches events with no active handler and routes to the corresponding hook to detach/unsubscribe/etc **/\n static addGlobalUndeliveredEventHandler(id: string, handler: EventHandler): void {\n RenderContext._staticUndeliveredHandlers[id] = handler;\n }\n\n async handleUndeliveredEvent(ev: UIEvent): Promise<Effect[] | void> {\n const allHandlers = {\n ...RenderContext._staticUndeliveredHandlers,\n ...this._undeliveredHandlers,\n };\n for (const [_, handler] of Object.entries(allHandlers)) {\n await handler(ev, this);\n }\n }\n\n emitEffect(dedupeKey: string, effect: Effect): void {\n this._effects[dedupeKey] = effect;\n }\n\n /**\n * Adds event that will re-enter the dispatcher queue.\n */\n addToRequeueEvents(...events: UIEvent[]): void {\n if (this._devvitContext?.debug.blocks) console.debug('[blocks] requeueing events', events);\n\n const grouped = events.reduce(\n (acc, event) => {\n if (event.retry) {\n acc.retry.push(event);\n } else {\n acc.normal.push(event);\n }\n return acc;\n },\n { retry: [], normal: [] } as { retry: UIEvent[]; normal: UIEvent[] }\n );\n\n // We need to maintain the order of the events, so we need to add the retry events first\n this._requeueEvents = [...grouped.retry, ...this._requeueEvents, ...grouped.normal];\n }\n\n get effects(): Effect[] {\n return Object.values(this._effects);\n }\n\n nextHookId(options: HookSegment): string {\n if (options.key === undefined) {\n options.key = this._segments[this._segments.length - 1].next++ + '';\n }\n this.push(options);\n try {\n const builder = [];\n /**\n * We need to build the hook id from the segments in reverse order, because an explicit id\n * overrides parent path info.\n */\n for (let i = this._segments.length - 1; i >= 0; i--) {\n const segment = this._segments[i];\n if (segment.id) {\n builder.unshift(segment.id);\n break;\n }\n\n const tag = [];\n if (segment.namespace) {\n tag.push(segment.namespace);\n }\n\n if (segment.key !== undefined && segment.key !== false) {\n tag.push(segment.key);\n }\n\n builder.unshift(tag.join('-'));\n }\n const id = builder.join('.');\n if (this._generated[id] && !options.shared) {\n throw new Error(\n `Hook id ${id} already used, cannot register another hook with the same id`\n );\n }\n this._generated[id] = true;\n this._prevHookId = id;\n return id;\n } finally {\n this.pop();\n }\n }\n}\n", "import { type UIEvent, UIEventScope } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type {\n SetStateAction,\n UseStateInitializer,\n UseStateResult,\n} from '../../../../types/hooks.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\nimport { RenderInterruptError } from './types.js';\nimport { type LoadState, toSerializableErrorOrCircuitBreak } from './useAsync.js';\n\n/**\n * Implementation of the useState hook.\n */\nclass UseStateHook<S extends JSONValue> implements Hook {\n state: {\n value: S | null;\n load_state: LoadState;\n error: { message: string; details: string } | null;\n } = { value: null, load_state: 'initial', error: null };\n #changed: () => void;\n #ctx: RenderContext;\n #initializer: UseStateInitializer<S>;\n #hookId: string;\n _promise: Promise<S> | undefined;\n\n constructor(initializer: UseStateInitializer<S>, params: HookParams) {\n this.#initializer = initializer;\n this.#hookId = params.hookId;\n this.#changed = params.invalidate;\n this.#ctx = params.context;\n }\n\n /**\n * For state initialized with a promise, this function is called when the promise resolves. This should\n * happen inside the same event loop as the original call to useState, assuming circuit-breaking didn't happen.\n *\n * If circuit-breaking did happen, this function will be called on the server and the state will propagate to\n * the client.\n *\n * This is intended to be synchronous in the event loop, so will block and waterfall. useAsync is the non-waterfalling\n * version, but this version is provided for backwards compatibility.\n */\n async onUIEvent(): Promise<void> {\n if (this.state.load_state === 'loading' && this._promise) {\n try {\n this.state.value = await this._promise;\n this.state.load_state = 'loaded';\n } catch (e) {\n this.state.load_state = 'error';\n this.state.error = toSerializableErrorOrCircuitBreak(e);\n }\n this.#changed();\n } else {\n /**\n * This would probably be some sort of concurrent access bug. It's not clear what would put us\n * in this state, but it's worth logging so we can investigate.\n */\n console.warn(\n `Invalid state for hook (${this.#hookId}):`,\n this.state.load_state,\n this._promise\n );\n }\n }\n\n setter(action: SetStateAction<S>): void {\n this.state.value = action instanceof Function ? action(this.state.value as S) : action;\n this.state.load_state = 'loaded';\n this.#changed();\n }\n\n /**\n * After the existing state is loaded, we need to run the initializer if there was no state found.\n */\n onStateLoaded(): void {\n this._promise = (this.#ctx._prevHooks[this.#hookId] as UseStateHook<S> | undefined)?._promise;\n\n /**\n * If the state is still loading, we need to throw an error to prevent using the null state.\n */\n if (this.state.load_state === 'loading') {\n throw new RenderInterruptError();\n }\n if (this.state.load_state === 'error') {\n throw new Error(this.state.error?.message ?? 'Unknown error');\n }\n if (this.state.load_state === 'initial') {\n let initialValue: S | Promise<S>;\n try {\n initialValue =\n this.#initializer instanceof Function ? this.#initializer() : this.#initializer;\n } catch (e) {\n console.log('error in loading async', e);\n this.state.load_state = 'error';\n this.#changed();\n return;\n }\n if (initialValue instanceof Promise) {\n this._promise = initialValue;\n this.state.load_state = 'loading';\n const requeueEvent: UIEvent = {\n scope: UIEventScope.ALL,\n asyncRequest: { requestId: this.#hookId },\n hook: this.#hookId,\n };\n this.#ctx.addToRequeueEvents(requeueEvent);\n this.#changed();\n throw new RenderInterruptError();\n } else {\n this.state.value = initialValue;\n this.state.load_state = 'loaded';\n }\n this.#changed();\n }\n }\n}\n\nexport function useState(initialState: UseStateInitializer<boolean>): UseStateResult<boolean>;\nexport function useState(initialState: UseStateInitializer<number>): UseStateResult<number>;\nexport function useState(initialState: UseStateInitializer<string>): UseStateResult<string>;\nexport function useState<S extends JSONValue>(\n initialState: UseStateInitializer<S>\n): UseStateResult<S>;\nexport function useState<S extends JSONValue>(\n initialState: UseStateInitializer<S>\n): UseStateResult<S> {\n const hook = registerHook({\n namespace: 'useState',\n initializer: (params) => new UseStateHook(initialState, params),\n });\n\n return [hook.state.value as S, hook.setter.bind(hook)];\n}\n", "import type { UIEvent } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { RenderContext, Tombstone } from './RenderContext.js';\n\nexport type BlocksState = { [hookID: string]: JSONValue | Tombstone };\n\nexport type Props = { [key: string]: unknown };\n\nexport type HookSegment = {\n /**\n * This is usually the name of the hook: useAsync, useAsyncState,\n * useChannel:channelName, useForm, useInterval, useState, the block element,\n * or the component name (eg, AppToolbar or FooBar).\n *\n * Namespaces can be used to encode additional data such as logically shared\n * instances that would otherwise have to be gathered from Hook instances.\n *\n * Dashes (-) and dots (.) delimit hook IDs so don't use those in your\n * namespace.\n */\n namespace: string;\n\n /**\n * If the ordering is known, you can provide it here. Else, the key will be a\n * generated auto-incrementing number.\n *\n * The exception is that if key === false, then no key will be added to the hook.\n */\n\n key?: string | false;\n\n /**\n * If you want to use a specific id, you can provide it here. Else, the id will be\n * generated based on the namespace and key.\n */\n\n id?: string;\n\n /**\n * If the hook id & hook state is shared across multiple components, you can set this to true. The only\n * example of this that I can think of is something like createContext and useContext.\n */\n shared?: boolean;\n};\n\nexport type EventHandler = (event: UIEvent, context: RenderContext) => Promise<void>;\n\n/**\n * Syncs state between client and server, responds to events, provides user\n * API, and transitions state across renders.\n */\nexport type Hook = {\n /**\n * State to carry across renders. Hook constructor arguments are recreated on\n * render but state may be passed in the render request and used to prime that\n * render's hook before onLoad().\n */\n state: JSONValue | Tombstone;\n\n /**\n * What event callbacks want to hit.\n */\n onUIEvent?: EventHandler;\n\n /**\n * Invoked when hook has been updated with the last saved state. The lifecycle\n * is:\n *\n * 1. constructor.\n * 2. state gets copied into the hook.\n * 3. onStateLoaded().\n * 4. possible changes to the state.\n * 5. state gets serialized.\n */\n onStateLoaded?(): void;\n};\n\nexport type HookRef = { id?: string };\n\nexport type HookParams = {\n hookId: string;\n /**\n * Record state mutation in BlocksHandler. Caller is responsible for filtering\n * out a nop transition where previous state is equivalent to next as wanted.\n * This state will be provided right before Hook.onStateLoaded().\n */\n invalidate(): void;\n context: RenderContext;\n};\n\n/**\n * Circuit-breaking triggers a {scope=REMOTE, async=false, retry=true} event.\n * This triggers a {scope=ALL, async=true, retry=false} event. In\n * practice, that has some slight implications for how it flows through the\n * event loop. If we wanted to, we could change this out, if we fully understand\n * the semantics. This aligns more with useAsync than circuit-breaking today,\n * though either is likely just fine.\n */\nexport class RenderInterruptError extends Error {}\n", "import { type AsyncResponse, type UIEvent, UIEventScope } from '@devvit/protos';\nimport { CIRCUIT_BREAKER_MSG } from '@devvit/shared-types/CircuitBreaker.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\nimport { StringUtil } from '@devvit/shared-types/StringUtil.js';\nimport { isEqual } from 'moderndash';\n\nimport type { AsyncUseStateInitializer, UseAsyncResult } from '../../../../types/hooks.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\nexport type AsyncOptions<S extends JSONValue> = {\n /**\n * The data loader will re-run if the value of `depends` changes.\n */\n depends?: JSONValue;\n\n /**\n * A callback that will be called after the data is loaded, regardless of whether it succeeds or fails.\n * This is a good place to run setStates or other side effects, because calling a setState in the main\n * body is not allowed.\n *\n * useAsync(async () => {\n * const response = await fetch(`https://date.api/today?timezone=${timezone}`);\n * return response.json();\n * }, {\n * depends: [timezone],\n * finally: (data, error) => {\n * if (error) {\n * console.error(\"Failed to load date data:\", error);\n * } else {\n * setTodayDate(data['currentDate']);\n * }\n * },\n * });\n *\n */\n finally?: (data: S | null, error: Error | null) => void;\n};\n\nexport type LoadState = 'initial' | 'loading' | 'loaded' | 'error';\n\n/**\n * This tries to save an error into the state. If the error is a circuit breaker, it will throw the error instead,\n * as those errors are not meant to be saved in state.\n *\n * @param e -- an original error type\n * @returns A JSONValue that can be saved in states\n */\nexport function toSerializableErrorOrCircuitBreak(e: unknown): {\n message: string;\n details: string;\n} {\n // This is a little side-effecty, so open to suggestions on how to improve.\n if (e instanceof Error) {\n if (e.message === CIRCUIT_BREAKER_MSG) {\n throw e;\n }\n console.error(e);\n return { message: e.message, details: e.stack ?? '' };\n } else {\n console.error(e);\n return { message: 'Unknown error', details: StringUtil.caughtToString(e) };\n }\n}\n\ntype AsyncState<S extends JSONValue> = {\n depends: JSONValue;\n load_state: LoadState;\n data: S | null;\n error: { message: string; details: string } | null;\n};\n\nclass AsyncHook<S extends JSONValue> implements Hook {\n state: AsyncState<S>;\n readonly #debug: boolean;\n #hookId: string;\n initializer: AsyncUseStateInitializer<S>;\n #invalidate: () => void;\n #ctx: RenderContext;\n localDepends: JSONValue;\n #options: AsyncOptions<S>;\n\n constructor(\n initializer: AsyncUseStateInitializer<S>,\n options: AsyncOptions<S>,\n params: HookParams\n ) {\n this.#debug = !!params.context.devvitContext.debug.useAsync;\n if (this.#debug) console.debug('[useAsync] v1', options);\n this.state = { data: null, load_state: 'initial', error: null, depends: null };\n this.#hookId = params.hookId;\n this.initializer = initializer;\n this.#invalidate = params.invalidate;\n this.#ctx = params.context;\n this.localDepends = options.depends ?? null;\n this.#options = options;\n }\n\n /**\n * After we look at our state, we need to decide if we need to dispatch a request to load the data.\n */\n onStateLoaded(): void {\n if (this.#debug) console.debug('[useAsync] async onLoad ', this.#hookId, this.state);\n if (this.#debug)\n console.debug('[useAsync] async onLoad have ', this.localDepends, 'and', this.state.depends);\n if (!isEqual(this.localDepends, this.state.depends) || this.state.load_state === 'initial') {\n if (this.#debug) console.debug(`[useAsync] attempting to resolve for hookId`, this.#hookId);\n this.state.load_state = 'loading';\n this.state.depends = this.localDepends;\n this.#invalidate();\n\n const requeueEvent: UIEvent = {\n scope: UIEventScope.ALL,\n hook: this.#hookId,\n async: true,\n asyncRequest: {\n requestId: this.#hookId + '-' + JSON.stringify(this.state.depends),\n },\n };\n if (this.#debug) console.debug('[useAsync] onLoad requeue');\n this.#ctx.addToRequeueEvents(requeueEvent);\n }\n }\n\n async onUIEvent(event: UIEvent, context: RenderContext): Promise<void> {\n /**\n * Requests and responses are both handled here. If we have a request, we need to load the data\n * and then send a response. If we have a response, we need to update our state and re-render.\n *\n * This is a very event-driven way to handle state, but it's the only way to handle async state.\n */\n if (event.asyncRequest) {\n const asyncResponse: AsyncResponse = { requestId: event.asyncRequest.requestId };\n try {\n asyncResponse.data = {\n value: await this.initializer(),\n };\n } catch (e) {\n asyncResponse.error = toSerializableErrorOrCircuitBreak(e);\n }\n\n const requeueEvent: UIEvent = {\n scope: UIEventScope.ALL,\n asyncResponse: asyncResponse,\n hook: this.#hookId,\n };\n if (this.#debug) console.debug('[useAsync] onReq requeue');\n context.addToRequeueEvents(requeueEvent);\n } else if (event.asyncResponse) {\n const anticipatedRequestId = this.#hookId + '-' + JSON.stringify(this.state.depends);\n if (event.asyncResponse.requestId === anticipatedRequestId) {\n this.state = {\n ...this.state,\n data: (event.asyncResponse.data?.value as S | undefined) ?? null,\n error: event.asyncResponse.error ?? null,\n load_state: event.asyncResponse.error ? 'error' : 'loaded',\n };\n if (this.#options.finally) {\n await this.#options.finally(\n this.state.data,\n this.state.error ? new Error(this.state.error?.message ?? 'Unknown error') : null\n );\n }\n this.#invalidate();\n } else {\n if (this.#debug)\n console.debug(\n '[useAsync] onResp skip, stale event',\n event.asyncResponse.requestId,\n ' !== ',\n anticipatedRequestId\n );\n }\n } else {\n throw new Error('Unknown event type');\n }\n }\n}\n\n/**\n * This is the preferred way to handle async state in Devvit.\n *\n * @param initializer -- any async function that returns a JSONValue\n * @returns UseAsyncResult<S>\n */\nexport function useAsync<S extends JSONValue>(\n initializer: AsyncUseStateInitializer<S>,\n options: AsyncOptions<S> = {}\n): UseAsyncResult<S> {\n const hook = registerHook({\n namespace: 'useAsync',\n initializer: (params) => {\n return new AsyncHook(initializer, options, params);\n },\n });\n\n return {\n data: hook.state.data,\n error: hook.state.error,\n loading: hook.state.load_state === 'loading',\n };\n}\n", "import type { Metadata, UIRequest } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\n\nimport { AssetsClient } from '../../../../apis/AssetsClient/AssetsClient.js';\nimport { KeyValueStorage } from '../../../../apis/key-value-storage/KeyValueStorage.js';\nimport { MediaClient } from '../../../../apis/media/MediaClient.js';\nimport { ModLogClient } from '../../../../apis/modLog/ModLogClient.js';\nimport { RealtimeClient } from '../../../../apis/realtime/RealtimeClient.js';\nimport { RedisClient } from '../../../../apis/redis/RedisClient.js';\nimport { SchedulerClient } from '../../../../apis/scheduler/SchedulerClient.js';\nimport { SettingsClient } from '../../../../apis/settings/SettingsClient.js';\nimport type { ContextAPIClients } from '../../../../types/context.js';\nimport type { Devvit } from '../../../Devvit.js';\nimport { getContextFromMetadata } from '../../context.js';\nimport { makeCache } from './cache.js';\nimport type { RenderContext } from './RenderContext.js';\nimport { UIClient } from './UIClient.js';\nimport { useChannel } from './useChannel.js';\nimport { useForm } from './useForm.js';\nimport { useInterval } from './useInterval.js';\nimport { useState } from './useState.js';\n\nexport class ContextBuilder {\n public buildContext(\n renderContext: RenderContext,\n request: UIRequest,\n metadata: Metadata\n ): Devvit.Context {\n const modLog = new ModLogClient(metadata);\n const kvStore = new KeyValueStorage(metadata);\n const redis = new RedisClient(metadata);\n const scheduler = new SchedulerClient(metadata);\n const settings = new SettingsClient(metadata);\n const ui = new UIClient(renderContext);\n const media = new MediaClient(metadata);\n const assets = new AssetsClient();\n const realtime = new RealtimeClient(metadata);\n const cache = makeCache(redis, renderContext._state);\n const apiClients: ContextAPIClients = {\n modLog,\n kvStore,\n redis,\n scheduler,\n settings,\n media,\n assets,\n realtime,\n ui,\n useState,\n useChannel,\n useInterval,\n useForm: useForm as ContextAPIClients['useForm'],\n cache,\n dimensions: request.env?.dimensions,\n uiEnvironment: {\n timezone: metadata[Header.Timezone]?.values[0],\n locale: metadata[Header.Language]?.values[0],\n dimensions: request.env?.dimensions,\n colorScheme: request.env?.colorScheme,\n },\n };\n\n // TODO: Would commentId ever be needed for the blocks handler context?\n // You'd want something like....\n // const baseContext = getContextFromMetadata(metadata, request.props?.postId, request.props?.commentId);\n const baseContext = getContextFromMetadata(metadata, request.props?.postId);\n baseContext.debug.effects = renderContext;\n return { ...baseContext, ...apiClients };\n }\n}\n", "/** @jsx Devvit.createElement */\n/** @jsxFrag Devvit.Fragment */\nimport { Devvit } from '../Devvit.js';\n\nexport type ParsedDevvitUserAgent =\n | {\n company: 'Reddit';\n platform: 'iOS';\n rawVersion: string;\n versionNumber: number;\n }\n | {\n company: 'Reddit';\n platform: 'Android';\n rawVersion: string;\n versionNumber: number;\n }\n | {\n company: 'Reddit';\n platform: 'Shreddit';\n rawVersion: string;\n }\n | {\n company: 'Reddit';\n platform: 'Play';\n rawVersion: string;\n };\n\nconst getVersionNumberFromRawVersion = (rawVersion: string): number | undefined => {\n const versionNumber = Number(rawVersion.trim().split('.').pop());\n return isNaN(versionNumber) ? undefined : versionNumber;\n};\n\nexport const parseDevvitUserAgent = (input: string): ParsedDevvitUserAgent | undefined => {\n const [company, platform, rawVersion] = input.trim().split(';');\n\n if (!company || !platform || !rawVersion) {\n console.warn(`Received a malformed devvit-user-agent! Received: '${input}'`);\n return;\n }\n\n if (company !== 'Reddit') {\n console.warn(`Received unknown company name in user agent! Received: '${input}'`);\n return;\n }\n\n if (platform === 'iOS') {\n const versionNumber = getVersionNumberFromRawVersion(rawVersion);\n\n if (versionNumber === undefined) {\n console.warn(`Could not parse version number from user agent! Received: '${input}'`);\n return;\n }\n\n return {\n company: 'Reddit',\n platform: 'iOS',\n rawVersion,\n versionNumber,\n };\n }\n\n if (platform === 'Android') {\n const versionNumber = getVersionNumberFromRawVersion(rawVersion);\n\n if (versionNumber === undefined) {\n console.warn(`Could not parse version number from user agent! Received: '${input}'`);\n return;\n }\n\n return {\n company: 'Reddit',\n platform: 'Android',\n rawVersion,\n versionNumber,\n };\n }\n\n if (platform === 'Shreddit') {\n return {\n company: 'Reddit',\n platform: 'Shreddit',\n rawVersion,\n };\n }\n\n if (platform === 'Play') {\n return {\n company: 'Reddit',\n platform: 'Play',\n rawVersion,\n };\n }\n\n console.warn(`Received unknown platform:`, platform);\n};\n\nexport const shouldShowUpgradeAppScreen = (\n parsedDevvitUserAgent: ParsedDevvitUserAgent | undefined\n): boolean => {\n // If we couldn't parse, default to trying to render the app\n if (!parsedDevvitUserAgent) {\n console.warn(\n `Could not parse devvit-user-agent! Received: '${JSON.stringify(parsedDevvitUserAgent)}'`\n );\n return false;\n }\n\n if (parsedDevvitUserAgent.platform === 'Android') {\n return parsedDevvitUserAgent.versionNumber < 1875012;\n }\n\n if (parsedDevvitUserAgent.platform === 'iOS') {\n return parsedDevvitUserAgent.versionNumber < 614973;\n }\n\n // Default to trying to render since we couldn't explicitly get the version number\n return false;\n};\n\nconst getUpgradeLinkForPlatform = (\n platform: ParsedDevvitUserAgent['platform']\n): string | undefined => {\n switch (platform) {\n case 'Android':\n return 'https://play.google.com/store/apps/details?id=com.reddit.frontpage';\n case 'iOS':\n return 'https://apps.apple.com/us/app/reddit/id1064216828';\n case 'Play':\n case 'Shreddit':\n break;\n default:\n console.error(`No upgrade link for platform: ${platform satisfies never}`);\n }\n};\n\nexport const UpgradeAppComponent: Devvit.BlockComponent<{\n platform: ParsedDevvitUserAgent['platform'];\n}> = ({ platform }, context) => {\n return (\n <blocks>\n <vstack alignment=\"middle center\" height={100} width={100}>\n <vstack maxWidth={'300px'} gap=\"large\" alignment=\"middle center\">\n <vstack gap=\"medium\" alignment=\"middle center\">\n <image imageHeight={100} imageWidth={100} url=\"https://i.redd.it/p1vmc5ulmpib1.png\" />\n <text size=\"large\" weight=\"bold\" wrap alignment=\"center\">\n Uh oh, the app you're trying to use requires the latest version of Reddit. Please\n upgrade your app to continue.\n </text>\n </vstack>\n <hstack alignment=\"middle center\">\n <button\n onPress={() => {\n const upgradeLink = getUpgradeLinkForPlatform(platform);\n if (upgradeLink) {\n context.ui.navigateTo(upgradeLink);\n } else {\n console.warn(`No upgrade link found for platform:`, platform);\n }\n }}\n >\n Upgrade\n </button>\n </hstack>\n </vstack>\n </vstack>\n </blocks>\n );\n};\n\n// A builder to make a component so that we don't have to rename `ui-request.handler.ts` to `.tsx`\n// Not that there's really anything wrong with that, but I like the separation of concerns since\n// we also has to set the pragma at the top of files that use Devvit-y stuff\nexport const makeUpgradeAppComponent = (platform: ParsedDevvitUserAgent['platform']) => () => (\n <UpgradeAppComponent platform={platform} />\n);\n", "export function makeGettersEnumerable(obj: object): void {\n // Get a list of all the properties on this class's constructor.\n const descriptors = Object.getOwnPropertyDescriptors(obj.constructor.prototype);\n // For each property on the constructor...\n for (const [key, descriptor] of Object.entries(descriptors)) {\n // If it's a getter...\n if (descriptor.get) {\n // Create a property **on this object directly** that mirrors the one on the prototype, except it's\n // enumerable. It has to be on the object directly for things like Object.keys() to find it. For details,\n // see the MSDN documentation on how this works:\n // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties#traversing_object_properties\n // This feels silly to me, but it's the only way that actually works.\n Object.defineProperty(obj, key, {\n ...descriptor,\n enumerable: true,\n });\n }\n }\n}\n", "// Rich-Text-JSON\n//\n// Rich-Text-JSON or (RTF|J) for short, is a declarative JSON format that encodes\n// Reddit content. You can think of it as AST designed for rendering content.\n// Historically, Reddit has encoded all of its content as Markdown. With RTF,\n// clients will submit and receive content in a JSON tree instead. This\n// removes the burden of parsing from clients, while providing a data-structure\n// that's more suited to rendering interactive content. e.g. media galleries,\n// sortable tables, etc\n//\n// The high level idea of the format, is that every part of the \"document\"\n// can be represented by element nodes. Each node is a plain old JS Object.\n// To differentiate each object, nodes specify their element kind. Some example\n// element kinds are `par` for paragraphs, `h` for headers, `link` for links,\n// `table` for tables, etc. Note: not all elements are allowable at the top-level,\n// some are used to differentiate nested data inside more complicated\n// constructs, such as tables, or nested lists.\n//\n// Since payload size is a concern, all of the keys are shortened to be\n// one character in length. There's some common conventions that will make\n// it easier to grok an element's json.\n//\n// e - `Element`, the element type of a node. Should always be a string.\n// t - `text content`, the text content of a node.\n// f - `formatting`, the formatting, or text styles, applied to the node\n// c - `content`, the content of the element. In most cases this will be an array\n// other objects. e.g. a paragraph's content (`c`) is a list of `Text` nodes\n// and `Link` nodes (simplified).\nexport var FormattingFlag;\n(function (FormattingFlag) {\n FormattingFlag[FormattingFlag[\"bold\"] = 1] = \"bold\";\n FormattingFlag[FormattingFlag[\"italic\"] = 2] = \"italic\";\n FormattingFlag[FormattingFlag[\"underline\"] = 4] = \"underline\";\n FormattingFlag[FormattingFlag[\"strikethrough\"] = 8] = \"strikethrough\";\n FormattingFlag[FormattingFlag[\"subscript\"] = 16] = \"subscript\";\n FormattingFlag[FormattingFlag[\"superscript\"] = 32] = \"superscript\";\n FormattingFlag[FormattingFlag[\"monospace\"] = 64] = \"monospace\";\n})(FormattingFlag || (FormattingFlag = {}));\nexport const TEXT_ELEMENT = 'text';\nexport const RAW_TEXT_ELEMENT = 'raw';\nexport const LINEBREAK_ELEMENT = 'br';\n// Links -----------------------------------------------------------------------\nexport const LINK_ELEMENT = 'link';\n// Reddit Links ----------------------------------------------------------------\nexport const COMMENT_LINK_ELEMENT = 'c/';\nexport const POST_LINK_ELEMENT = 'p/';\nexport const SUBREDDIT_LINK_ELEMENT = 'r/';\nexport const USER_LINK_ELEMENT = 'u/';\nexport const USER_MENTION_ELEMENT = '@';\nexport const SPOILER_TEXT_ELEMENT = 'spoilertext';\nexport const PARAGRAPH_ELEMENT = 'par';\nexport const HEADING_ELEMENT = 'h';\nexport const HORIZONTAL_RULE_ELEMENT = 'hr';\nexport const BLOCK_QUOTE_ELEMENT = 'blockquote';\nexport const CODE_BLOCK_ELEMENT = 'code';\n// Lists -----------------------------------------------------------------------\n// lists are stored as flat list of list items.\nexport const LIST_ITEM_ELEMENT = 'li';\nexport const LIST_ELEMENT = 'list';\nexport const COLUMN_ALIGN_LEFT = 'L';\nexport const COLUMN_ALIGN_RIGHT = 'R';\nexport const COLUMN_ALIGN_CENTER = 'C';\nexport const TABLE_ELEMENT = 'table';\n// Embeds ----------------------------------------------------------------------\n// embeds are essentially a nested iframe embed that previews a link.\nexport const EMBED_ELEMENT = 'embed';\nexport const IMAGE_ELEMENT = 'img';\n// Animated images, gifs, or html5 videos are similar to images, they just have\n// slightly different descriptos given the different urls required to render them.\n// Having a separate node type lets clients know what component should be\n// used to render\nexport const ANIMATED_IMAGE_ELEMENT = 'gif';\n// Videos are rendered via dynamic streaming. so instead of having different urls\n// for each size we just store two urls for different streaming formats.\n// Additionally, we store an array of placeholder image previews .\nexport const VIDEO_ELEMENT = 'video';\n", "import { mixinBlockQuoteContext, mixinCodeBlockContext, mixinHeadingContext, mixinHorizontalRuleContext, mixinImageContext, mixinLineBreakContext, mixinLinkContext, mixinListContext, mixinListItemContext, mixinParagraphContext, mixinRawTextContext, mixinSpoilerContext, mixinTableContentContext, mixinTableContext, mixinTableRowContext, mixinTextContext, } from './mixins.js';\nimport { ANIMATED_IMAGE_ELEMENT, BLOCK_QUOTE_ELEMENT, CODE_BLOCK_ELEMENT, COLUMN_ALIGN_CENTER, COLUMN_ALIGN_LEFT, COLUMN_ALIGN_RIGHT, COMMENT_LINK_ELEMENT, EMBED_ELEMENT, FormattingFlag, HEADING_ELEMENT, HORIZONTAL_RULE_ELEMENT, IMAGE_ELEMENT, LINEBREAK_ELEMENT, LINK_ELEMENT, LIST_ELEMENT, LIST_ITEM_ELEMENT, PARAGRAPH_ELEMENT, POST_LINK_ELEMENT, RAW_TEXT_ELEMENT, SPOILER_TEXT_ELEMENT, SUBREDDIT_LINK_ELEMENT, TABLE_ELEMENT, TEXT_ELEMENT, USER_LINK_ELEMENT, USER_MENTION_ELEMENT, VIDEO_ELEMENT, } from './types.js';\n/**\n *\n * @param opts {@link ImageOptions}\n * @returns AnimatedImage\n */\nexport function makeAnimatedImage(opts) {\n return {\n e: ANIMATED_IMAGE_ELEMENT,\n id: opts.mediaId,\n ...(opts.caption && { c: opts.caption }),\n ...(opts.blur && { o: opts.blur }),\n };\n}\nexport function makeBlockQuote(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinBlockQuoteContext(context, content), mixinCodeBlockContext(context, content), mixinHeadingContext(context, content), mixinListContext(context, content), mixinParagraphContext(context, content), mixinTableContext(context, content));\n cb(context);\n return {\n e: BLOCK_QUOTE_ELEMENT,\n c: content,\n ...(opts.author && { a: opts.author }),\n };\n}\nexport function makeCodeBlock(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinRawTextContext(context, content));\n cb(context);\n return {\n e: CODE_BLOCK_ELEMENT,\n c: content,\n ...(opts.language && { l: opts.language }),\n };\n}\nexport function makeCommentLink(opts) {\n return {\n e: COMMENT_LINK_ELEMENT,\n t: opts.permalink,\n };\n}\nexport function makeHeadingContext(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinRawTextContext(context, content), mixinLinkContext(context, content));\n cb(context);\n return {\n e: HEADING_ELEMENT,\n l: opts.level,\n c: content,\n };\n}\nexport function makeEmbed(opts) {\n return {\n e: EMBED_ELEMENT,\n u: opts.sourceUrl,\n c: opts.contentUrl,\n x: opts.width,\n y: opts.height,\n };\n}\nexport function makeFormatting(opts) {\n let spec = 0;\n if (opts.bold) {\n spec |= FormattingFlag.bold;\n }\n if (opts.italic) {\n spec |= FormattingFlag.italic;\n }\n if (opts.underline) {\n spec |= FormattingFlag.underline;\n }\n if (opts.strikethrough) {\n spec |= FormattingFlag.strikethrough;\n }\n if (opts.subscript) {\n spec |= FormattingFlag.subscript;\n }\n if (opts.superscript) {\n spec |= FormattingFlag.superscript;\n }\n if (opts.monospace) {\n spec |= FormattingFlag.monospace;\n }\n return [spec, opts.startIndex, opts.length];\n}\nexport function makeHorizontalRule() {\n return { e: HORIZONTAL_RULE_ELEMENT };\n}\nexport function makeImage(opts) {\n return {\n e: IMAGE_ELEMENT,\n id: opts.mediaId,\n ...(opts.caption && { c: opts.caption }),\n ...(opts.blur && { o: opts.blur }),\n };\n}\nexport function makeLineBreak() {\n return {\n e: LINEBREAK_ELEMENT,\n };\n}\nexport function makeLink(opts) {\n return {\n e: LINK_ELEMENT,\n t: opts.text,\n u: opts.url,\n ...(opts.formatting && { f: opts.formatting }),\n ...(opts.tooltip && { a: opts.tooltip }),\n };\n}\nexport function makeList(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinListItemContext(context, content));\n cb(context);\n return {\n e: LIST_ELEMENT,\n o: opts.ordered,\n c: content,\n };\n}\nexport function makeListItem(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinBlockQuoteContext(context, content), mixinCodeBlockContext(context, content), mixinHeadingContext(context, content), mixinHorizontalRuleContext(context, content), mixinListContext(context, content), mixinParagraphContext(context, content), mixinTableContext(context, content));\n cb(context);\n return {\n e: LIST_ITEM_ELEMENT,\n c: content,\n };\n}\nexport function makeParagraph(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinTextContext(context, content), mixinLinkContext(context, content), mixinLineBreakContext(context, content), mixinSpoilerContext(context, content), mixinImageContext(context, content));\n cb(context);\n return {\n e: PARAGRAPH_ELEMENT,\n c: content,\n };\n}\nexport function makePostLink(opts) {\n return {\n e: POST_LINK_ELEMENT,\n t: opts.permalink,\n };\n}\nexport function makeRawText(text) {\n return {\n e: RAW_TEXT_ELEMENT,\n t: text,\n };\n}\nexport function makeSpoilerText(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinTextContext(context, content), mixinLinkContext(context, content), mixinLineBreakContext(context, content));\n cb(context);\n return {\n e: SPOILER_TEXT_ELEMENT,\n c: content,\n };\n}\nexport function makeSubredditLink(opts) {\n return {\n e: SUBREDDIT_LINK_ELEMENT,\n t: opts.subredditName,\n l: opts.showPrefix,\n };\n}\nexport function makeTable(cb) {\n const context = {};\n const headerContent = [];\n const rowContent = [];\n Object.assign(context, mixinTableContentContext(context, headerContent, rowContent));\n cb(context);\n return {\n e: TABLE_ELEMENT,\n h: headerContent,\n c: rowContent,\n };\n}\nexport function makeTableCell(cb) {\n const [context, content] = tableCellTextContext();\n cb(context);\n return {\n c: content,\n };\n}\nexport function makeTableHeaderCell(opts, cb) {\n const [context, content] = tableCellTextContext();\n cb(context);\n let alignment;\n switch (opts.columnAlignment) {\n case 'left':\n alignment = COLUMN_ALIGN_LEFT;\n break;\n case 'right':\n alignment = COLUMN_ALIGN_RIGHT;\n break;\n case 'center':\n alignment = COLUMN_ALIGN_CENTER;\n break;\n }\n return {\n ...(alignment && { a: alignment }),\n ...(content && { c: content }),\n };\n}\nexport function makeTableRow(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinTableRowContext(context, content));\n cb(context);\n return content;\n}\nexport function makeText(opts) {\n return {\n e: TEXT_ELEMENT,\n t: opts.text,\n ...(opts.formatting && { f: opts.formatting }),\n };\n}\nexport function makeUserLink(opts) {\n return {\n e: USER_LINK_ELEMENT,\n t: opts.username,\n l: opts.showPrefix,\n };\n}\nexport function makeUserMention(opts) {\n return {\n e: USER_MENTION_ELEMENT,\n t: opts.username,\n l: opts.showPrefix,\n };\n}\nexport function makeVideo(opts) {\n return {\n e: VIDEO_ELEMENT,\n id: opts.mediaId,\n ...(opts.caption && { c: opts.caption }),\n ...(opts.blur && { o: opts.blur }),\n ...(opts.thumbnail && { p: opts.thumbnail }),\n ...(opts.convertToGif && { gifify: opts.convertToGif }),\n };\n}\nfunction tableCellTextContext() {\n const context = {};\n const content = [];\n Object.assign(context, mixinTextContext(context, content), mixinLinkContext(context, content), mixinSpoilerContext(context, content), mixinImageContext(context, content));\n return [context, content];\n}\n", "import { makeAnimatedImage, makeBlockQuote, makeCodeBlock, makeCommentLink, makeEmbed, makeHeadingContext, makeHorizontalRule, makeImage, makeLineBreak, makeLink, makeList, makeListItem, makeParagraph, makePostLink, makeRawText, makeSpoilerText, makeSubredditLink, makeTable, makeTableCell, makeTableHeaderCell, makeTableRow, makeText, makeUserLink, makeUserMention, makeVideo, } from './elements.js';\nexport function mixinBlockQuoteContext(ctx, c) {\n return {\n blockQuote(opts, cb) {\n c.push(makeBlockQuote(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinCodeBlockContext(ctx, c) {\n return {\n codeBlock(opts, cb) {\n c.push(makeCodeBlock(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinEmbedContext(ctx, c) {\n return {\n embed(opts) {\n c.push(makeEmbed(opts));\n return ctx;\n },\n };\n}\nexport function mixinHeadingContext(ctx, c) {\n return {\n heading(opts, cb) {\n c.push(makeHeadingContext(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinHorizontalRuleContext(ctx, c) {\n return {\n horizontalRule() {\n c.push(makeHorizontalRule());\n return ctx;\n },\n };\n}\nexport function mixinImageContext(ctx, c) {\n return {\n image(opts) {\n c.push(makeImage(opts));\n return ctx;\n },\n animatedImage(opts) {\n c.push(makeAnimatedImage(opts));\n return ctx;\n },\n };\n}\nexport function mixinLineBreakContext(ctx, c) {\n return {\n linebreak() {\n c.push(makeLineBreak());\n return ctx;\n },\n };\n}\nexport function mixinLinkContext(ctx, c) {\n return {\n link(opts) {\n c.push(makeLink(opts));\n return ctx;\n },\n commentLink(opts) {\n c.push(makeCommentLink(opts));\n return ctx;\n },\n postLink(opts) {\n c.push(makePostLink(opts));\n return ctx;\n },\n subredditLink(opts) {\n c.push(makeSubredditLink(opts));\n return ctx;\n },\n userLink(opts) {\n c.push(makeUserLink(opts));\n return ctx;\n },\n userMention(opts) {\n c.push(makeUserMention(opts));\n return ctx;\n },\n };\n}\nexport function mixinListContext(ctx, c) {\n return {\n list(opts, cb) {\n c.push(makeList(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinListItemContext(ctx, c) {\n return {\n item(cb) {\n c.push(makeListItem(cb));\n return ctx;\n },\n };\n}\nexport function mixinParagraphContext(ctx, c) {\n return {\n paragraph(cb) {\n c.push(makeParagraph(cb));\n return ctx;\n },\n };\n}\nexport function mixinRawTextContext(ctx, c) {\n return {\n rawText(text) {\n c.push(makeRawText(text));\n return ctx;\n },\n };\n}\nexport function mixinSpoilerContext(ctx, c) {\n return {\n spoiler(cb) {\n c.push(makeSpoilerText(cb));\n return ctx;\n },\n };\n}\nexport function mixinTableContentContext(ctx, h, c) {\n return {\n headerCell(opts, cb) {\n h.push(makeTableHeaderCell(opts, cb));\n return ctx;\n },\n row(cb) {\n c.push(makeTableRow(cb));\n return ctx;\n },\n };\n}\nexport function mixinTableContext(ctx, c) {\n return {\n table(cb) {\n c.push(makeTable(cb));\n return ctx;\n },\n };\n}\nexport function mixinTableRowContext(ctx, c) {\n return {\n cell(cb) {\n c.push(makeTableCell(cb));\n return ctx;\n },\n };\n}\nexport function mixinTextContext(ctx, c) {\n return {\n text(opts) {\n c.push(makeText(opts));\n return ctx;\n },\n };\n}\nexport function mixinVideoContext(ctx, c) {\n return {\n video(opts) {\n c.push(makeVideo(opts));\n return ctx;\n },\n };\n}\n", "var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n};\nvar __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n};\nvar _RichTextBuilder_content;\nimport { mixinBlockQuoteContext, mixinCodeBlockContext, mixinEmbedContext, mixinHeadingContext, mixinHorizontalRuleContext, mixinImageContext, mixinListContext, mixinParagraphContext, mixinTableContext, mixinVideoContext, } from './mixins.js';\n/**\n * @mixes ParagraphContainer\n * @mixes HeadingContainer\n * @mixes HorizontalRuleContainer\n * @mixes BlockQuoteContainer\n * @mixes CodeBlockContainer\n * @mixes EmbedContainer\n * @mixes ListContainer\n * @mixes TableContainer\n * @mixes ImageContainer\n * @mixes VideoContainer\n */\nexport class RichTextBuilder {\n constructor() {\n _RichTextBuilder_content.set(this, void 0);\n const content = [];\n Object.assign(this, mixinParagraphContext(this, content), mixinHeadingContext(this, content), mixinHorizontalRuleContext(this, content), mixinBlockQuoteContext(this, content), mixinCodeBlockContext(this, content), mixinEmbedContext(this, content), mixinListContext(this, content), mixinTableContext(this, content), mixinImageContext(this, content), mixinVideoContext(this, content));\n __classPrivateFieldSet(this, _RichTextBuilder_content, {\n document: content,\n }, \"f\");\n }\n /**\n * Serializes the document to a JSON string\n */\n build() {\n return JSON.stringify(__classPrivateFieldGet(this, _RichTextBuilder_content, \"f\"));\n }\n // #region Empty interface methods\n /* These are here to satisfy the interfaces but are overwritten by the mixins in the constructor */\n paragraph(_cb) {\n return this;\n }\n heading(_opts, _cb) {\n return this;\n }\n horizontalRule() {\n return this;\n }\n blockQuote(_opts, _cb) {\n return this;\n }\n codeBlock(_opts, _cb) {\n return this;\n }\n embed(_opts) {\n return this;\n }\n list(_opts, _cb) {\n return this;\n }\n table(_cb) {\n return this;\n }\n image(_opts) {\n return this;\n }\n animatedImage(_opts) {\n return this;\n }\n video(_opts) {\n return this;\n }\n}\n_RichTextBuilder_content = new WeakMap();\n", "import { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\n\nexport function richtextToString(richtext: RichTextBuilder | object | string): string {\n let richtextString: string;\n\n if (richtext instanceof RichTextBuilder) {\n richtextString = richtext.build();\n } else if (typeof richtext === 'object') {\n richtextString = JSON.stringify(richtext);\n } else {\n richtextString = richtext;\n }\n\n return richtextString;\n}\n", "import type { T1ID, T3ID } from '@devvit/shared-types/tid.js';\n\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\n\nexport type MoreObject = {\n parentId: T1ID | T3ID;\n children: T1ID[];\n depth: number;\n};\n\nexport type ListingFetchOptions = {\n after?: string;\n before?: string;\n limit?: number;\n pageSize?: number;\n more?: MoreObject;\n};\n\nexport type ListingFetchResponse<T> = {\n children: T[];\n before?: string;\n after?: string;\n more?: MoreObject;\n};\n\nexport interface Listing<T> {\n /** @internal */\n children: T[];\n /** @internal */\n _fetch: (options: ListingFetchOptions) => Promise<ListingFetchResponse<T>>;\n}\n\ntype ListingOptions<T> = {\n hasMore?: boolean;\n after?: string;\n before?: string;\n pageSize?: number;\n limit?: number;\n children?: T[];\n more?: MoreObject;\n fetch: (options: ListingFetchOptions) => Promise<ListingFetchResponse<T>>;\n};\n\nconst DEFAULT_PAGE_SIZE = 100;\nconst DEFAULT_LIMIT = Infinity;\n\nexport class Listing<T> {\n #before?: string;\n #after?: string;\n #more?: MoreObject;\n #started: boolean = false;\n\n pageSize: number = DEFAULT_PAGE_SIZE;\n limit: number = DEFAULT_LIMIT;\n children: T[] = [];\n\n /**\n * @internal\n */\n constructor(options: ListingOptions<T>) {\n makeGettersEnumerable(this);\n\n this._fetch = options.fetch;\n this.pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;\n this.limit = options.limit ?? DEFAULT_LIMIT;\n this.#after = options.after;\n this.#before = options.before;\n this.#more = options.more;\n\n if (options.children) {\n this.children = options.children;\n }\n }\n\n get hasMore(): boolean {\n return !this.#started || Boolean(this.#after || this.#before || this.#more);\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n let currentIndex = 0;\n\n while (true) {\n if (currentIndex === this.children.length) {\n if (this.hasMore) {\n const nextPage = await this.#next();\n // r2 api sometimes returns an empty page\n if (nextPage.length === 0) {\n break;\n }\n } else {\n break;\n }\n }\n\n yield this.children[currentIndex];\n currentIndex++;\n\n if (currentIndex === this.limit) {\n break;\n }\n }\n }\n\n setMore(more: MoreObject | undefined): void {\n this.#more = more;\n }\n\n preventInitialFetch(): void {\n this.#started = true;\n }\n\n async #next(): Promise<T[]> {\n if (!this.hasMore) {\n throw new Error('This listing does not have any more items to load.');\n }\n\n const { children, before, after, more } = await this._fetch({\n before: this.#before,\n after: this.#after,\n limit: this.pageSize,\n more: this.#more,\n });\n\n this.children.push(...children);\n this.#before = before;\n this.#after = after;\n this.#more = more;\n\n this.#started = true;\n\n return children;\n }\n\n async all(): Promise<T[]> {\n while (this.hasMore && this.children.length < this.limit) {\n await this.#next();\n }\n\n return this.children.slice(0, this.limit);\n }\n\n async get(count: number): Promise<T[]> {\n const limit = count <= this.limit ? count : this.limit;\n\n while (this.hasMore && this.children.length < limit) {\n await this.#next();\n }\n\n return this.children.slice(0, limit);\n }\n}\n", "import type {\n DeleteNotesRequest,\n GetNotesRequest,\n Metadata,\n ModNoteObject,\n PostNotesRequest,\n PostRemovalNoteRequest,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { T1ID, T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, asT5ID, asTID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type { ModAction } from './ModAction.js';\n\nexport type ModNoteType =\n | 'NOTE'\n | 'APPROVAL'\n | 'REMOVAL'\n | 'BAN'\n | 'MUTE'\n | 'INVITE'\n | 'SPAM'\n | 'CONTENT_CHANGE'\n | 'MOD_ACTION'\n | 'ALL';\n\nexport type UserNoteLabel =\n | 'BOT_BAN'\n | 'PERMA_BAN'\n | 'BAN'\n | 'ABUSE_WARNING'\n | 'SPAM_WARNING'\n | 'SPAM_WATCH'\n | 'SOLID_CONTRIBUTOR'\n | 'HELPFUL_USER';\n\nexport type UserNote = {\n note?: string;\n redditId?: T1ID | T3ID | T5ID;\n label?: UserNoteLabel;\n};\n\nexport interface ModNote {\n id: string;\n operator: {\n id?: T2ID | undefined;\n name?: string | undefined;\n };\n user: {\n id?: T2ID | undefined;\n name?: string | undefined;\n };\n subreddit: {\n id?: T5ID | undefined;\n name?: string | undefined;\n };\n type: ModNoteType;\n createdAt: Date;\n userNote?: UserNote;\n modAction?: ModAction;\n}\n\nexport type GetModNotesOptions = Prettify<\n Pick<GetNotesRequest, 'subreddit' | 'user'> & {\n filter?: ModNoteType;\n } & Pick<ListingFetchOptions, 'limit' | 'before'>\n>;\n\nexport type CreateModNoteOptions = Prettify<\n PostNotesRequest & {\n redditId?: T1ID | T3ID;\n label?: UserNoteLabel;\n }\n>;\n\nexport type DeleteNotesOptions = Prettify<DeleteNotesRequest>;\n\nexport type AddRemovalNoteOptions = Prettify<PostRemovalNoteRequest>;\n\nexport class ModNote {\n /**\n * @internal\n */\n private constructor() {}\n\n static #fromProto(protoModNote: ModNoteObject): ModNote {\n // check that all required fields of protoModNote needed to create a ModNote are present\n assertNonNull(protoModNote.id, 'Mod note ID is null or undefined');\n assertNonNull(protoModNote.createdAt, 'Mod note createdAt is null or undefined');\n assertNonNull(protoModNote.type, 'Mod note type is null or undefined');\n assertNonNull(protoModNote.subreddit, 'Mod note subreddit is null or undefined');\n assertNonNull(protoModNote.subredditId, 'Mod note subredditId is null or undefined');\n assertNonNull(protoModNote.operator, 'Mod note operator is null or undefined');\n assertNonNull(protoModNote.operatorId, 'Mod note operatorId is null or undefined');\n assertNonNull(protoModNote.user, 'Mod note user is null or undefined');\n assertNonNull(protoModNote.userId, 'Mod note userId is null or undefined');\n assertNonNull(protoModNote.userNoteData, 'Mod note userNote is null or undefined');\n assertNonNull(protoModNote.modActionData, 'Mod note modAction is null or undefined');\n\n return {\n id: protoModNote.id,\n user: {\n id: asT2ID(protoModNote.userId ?? ''),\n name: protoModNote.user,\n },\n subreddit: {\n id: asT5ID(protoModNote.subredditId ?? ''),\n name: protoModNote.subreddit,\n },\n operator: {\n id: asT2ID(protoModNote.operatorId ?? ''),\n name: protoModNote.operator,\n },\n createdAt: new Date(protoModNote.createdAt! * 1000), // convert to ms\n userNote: {\n note: protoModNote.userNoteData?.note,\n redditId: protoModNote.userNoteData?.redditId\n ? asTID<T1ID | T3ID | T5ID>(protoModNote.userNoteData?.redditId)\n : undefined,\n label: protoModNote.userNoteData?.label as UserNoteLabel,\n },\n type: protoModNote.type as ModNoteType,\n };\n }\n\n /** @internal */\n static get(options: GetModNotesOptions, metadata: Metadata | undefined): Listing<ModNote> {\n const client = Devvit.redditAPIPlugins.ModNote;\n\n return new Listing<ModNote>({\n hasMore: true,\n before: options.before,\n limit: options.limit,\n pageSize: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const protoRes = await client.GetNotes(\n {\n subreddit: options.subreddit,\n user: options.user,\n filter: options.filter,\n before: fetchOptions.before,\n limit: fetchOptions.limit,\n },\n metadata\n );\n\n return {\n children: protoRes.modNotes?.map((protoModNote) => this.#fromProto(protoModNote)) || [],\n // if the response says that there are no more pages, then we should set before to undefined\n // to prevent more requests from being made\n before: protoRes.hasNextPage ? protoRes.endCursor : undefined,\n hasMore: protoRes.hasNextPage,\n } as ListingFetchResponse<ModNote>;\n },\n });\n }\n\n /** @internal */\n static async delete(\n options: DeleteNotesOptions,\n metadata: Metadata | undefined\n ): Promise<boolean> {\n const client = Devvit.redditAPIPlugins.ModNote;\n const { deleted } = await client.DeleteNotes(options, metadata);\n return !!deleted;\n }\n\n /** @internal */\n static async add(\n options: CreateModNoteOptions,\n metadata: Metadata | undefined\n ): Promise<ModNote> {\n const client = Devvit.redditAPIPlugins.ModNote;\n const res = await client.PostNotes(options, metadata);\n if (!res?.created) {\n throw new Error('Failed to create mod note');\n }\n return this.#fromProto(res.created);\n }\n\n /** @internal */\n static async addRemovalNote(\n options: AddRemovalNoteOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.ModNote;\n\n await client.PostRemovalNote(options, metadata);\n }\n}\n", "import type { Metadata, QueryResponse } from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\n\nexport class GraphQL {\n /** @internal */\n static queryWithQueryString(q: string, metadata: Metadata | undefined): Promise<QueryResponse> {\n return Devvit.redditAPIPlugins.GraphQL.Query(\n {\n query: q,\n },\n metadata\n );\n }\n\n /** @internal */\n static query(\n operationName: string,\n id: string,\n variables: JSONObject,\n metadata: Metadata | undefined\n ): Promise<QueryResponse> {\n return Devvit.redditAPIPlugins.GraphQL.PersistedQuery(\n {\n operationName,\n id,\n variables,\n },\n metadata\n );\n }\n}\n", "import type { ModeratorPermission } from '../models/User.js';\n\nexport const MODERATOR_PERMISSIONS: ModeratorPermission[] = [\n 'all',\n 'wiki',\n 'posts',\n 'access',\n 'mail',\n 'config',\n 'flair',\n 'channels',\n 'chat_config',\n 'chat_operator',\n 'community_chat',\n];\n\nexport function formatPermissions(permissions: string[], allPermissions: string[]): string {\n return allPermissions\n .map((permission) => (permissions.includes(permission) ? '+' : '-') + permission)\n .join(',');\n}\n\nexport function formatModeratorPermissions(permissions: string[]): string {\n return formatPermissions(permissions, MODERATOR_PERMISSIONS);\n}\n\nexport function validModPermissions(permissions: string[]): ModeratorPermission[] {\n return permissions.filter((permission) =>\n MODERATOR_PERMISSIONS.includes(permission as ModeratorPermission)\n ) as ModeratorPermission[];\n}\n", "import {\n type FlairCsvResult,\n type FlairObject,\n type Metadata,\n type UserFlair as UserFlairProto,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { T3ID } from '@devvit/shared-types/tid.js';\nimport { asT3ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\n\nexport enum FlairType {\n User = 'USER_FLAIR',\n Post = 'LINK_FLAIR',\n}\n\nexport type AllowableFlairContent = 'all' | 'emoji' | 'text';\nexport type FlairTextColor = 'light' | 'dark';\nexport type FlairBackgroundColor = `#${string}` | 'transparent';\n\nexport type CreateFlairTemplateOptions = {\n /** The name of the subreddit to create the flair template in. */\n subredditName: string;\n /** The flair template's allowable content. Either 'all', 'emoji', or 'text'. */\n allowableContent?: AllowableFlairContent;\n /** The background color of the flair. Either 'transparent' or a hex color code. e.g. #FFC0CB */\n backgroundColor?: string;\n maxEmojis?: number;\n /** Whether or not this flair template is only available to moderators. */\n modOnly?: boolean;\n /** The text to display in the flair. */\n text: string;\n /** Either 'dark' or 'light'. */\n textColor?: FlairTextColor;\n /** Whether or not users are allowed to edit this flair template before using it. */\n allowUserEdits?: boolean;\n};\n\nexport type EditFlairTemplateOptions = CreateFlairTemplateOptions & {\n id: string;\n};\n\nexport class FlairTemplate {\n #id: string;\n #subredditName: string;\n #text: string;\n #textColor: FlairTextColor;\n #backgroundColor: FlairBackgroundColor;\n #allowableContent: AllowableFlairContent;\n #modOnly: boolean;\n #maxEmojis: number;\n #allowUserEdits: boolean;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: FlairObject, subredditName: string, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id);\n assertNonNull(data.text);\n\n this.#id = data.id;\n this.#subredditName = subredditName;\n this.#text = data.text;\n this.#textColor = asFlairTextColor(data.textColor);\n this.#backgroundColor = asFlairBackgroundColor(data.backgroundColor);\n this.#allowableContent = asAllowableContent(data.allowableContent);\n this.#modOnly = data.modOnly;\n this.#maxEmojis = data.maxEmojis;\n this.#allowUserEdits = data.textEditable;\n\n this.#metadata = metadata;\n }\n\n /** The flair template's ID */\n get id(): string {\n return this.#id;\n }\n\n /** The flair template's text */\n get text(): string {\n return this.#text;\n }\n\n /** The flair template's text color. Either 'dark' or 'light'. */\n get textColor(): FlairTextColor {\n return this.#textColor;\n }\n\n /** The flair template's background color. Either 'transparent' or a hex color code. e.g. #FFC0CB */\n get backgroundColor(): FlairBackgroundColor {\n return this.#backgroundColor;\n }\n\n /** The flair template's allowable content. Either 'all', 'emoji', or 'text'. */\n get allowableContent(): AllowableFlairContent {\n return this.#allowableContent;\n }\n\n /** Is the flair template only available to moderators? */\n get modOnly(): boolean {\n return this.#modOnly;\n }\n\n /** The flair template's maximum number of emojis. */\n get maxEmojis(): number {\n return this.#maxEmojis;\n }\n\n /** Does the flair template allow users to edit their flair? */\n get allowUserEdits(): boolean {\n return this.#allowUserEdits;\n }\n\n /** Delete this flair template */\n async delete(): Promise<void> {\n return FlairTemplate.deleteFlairTemplate(this.#id, this.#subredditName, this.#metadata);\n }\n\n /** Edit this flair template */\n async edit(\n options: Partial<Omit<EditFlairTemplateOptions, 'id' | 'subredditName'>>\n ): Promise<FlairTemplate> {\n return FlairTemplate.editFlairTemplate(\n {\n id: this.#id,\n subredditName: this.#subredditName,\n text: options.text ?? this.#text,\n allowableContent: options.allowableContent ?? this.#allowableContent,\n backgroundColor: options.backgroundColor ?? this.#backgroundColor,\n maxEmojis: options.maxEmojis ?? this.#maxEmojis,\n modOnly: options.modOnly ?? this.#modOnly,\n textColor: options.textColor ?? this.#textColor,\n allowUserEdits: options.allowUserEdits ?? this.#allowUserEdits,\n },\n this.#metadata\n );\n }\n\n /** @internal */\n static async createPostFlairTemplate(\n options: CreateFlairTemplateOptions,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n return FlairTemplate.#createOrUpdateFlairTemplate(\n { ...options, flairType: FlairType.Post },\n metadata\n );\n }\n\n /** @internal */\n static async createUserFlairTemplate(\n options: CreateFlairTemplateOptions,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n return FlairTemplate.#createOrUpdateFlairTemplate(\n { ...options, flairType: FlairType.User },\n metadata\n );\n }\n\n /** @internal */\n static async editFlairTemplate(\n editOptions: EditFlairTemplateOptions,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n return FlairTemplate.#createOrUpdateFlairTemplate(editOptions, metadata);\n }\n\n /** @internal */\n static async #createOrUpdateFlairTemplate(\n options: CreateFlairTemplateOptions & { flairType?: FlairType; id?: string },\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n const {\n subredditName: subreddit,\n allowableContent = 'all',\n backgroundColor = 'transparent',\n flairType = '',\n maxEmojis = 10,\n modOnly = false,\n text,\n textColor = 'dark',\n allowUserEdits: textEditable = false,\n id: flairTemplateId = '',\n } = options;\n\n if (modOnly && textEditable) {\n throw new Error('Cannot have a mod only flair that is editable by users');\n }\n\n const client = Devvit.redditAPIPlugins.Flair;\n\n const response = await client.FlairTemplate(\n {\n subreddit,\n allowableContent,\n backgroundColor,\n flairType,\n maxEmojis,\n modOnly,\n text,\n textColor,\n textEditable,\n flairTemplateId,\n cssClass: '',\n overrideCss: false,\n },\n metadata\n );\n\n return new FlairTemplate(response, subreddit, metadata);\n }\n\n /** @internal */\n static async getPostFlairTemplates(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate[]> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n const response = await client.LinkFlair(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n return response.flair?.map((flair) => new FlairTemplate(flair, subredditName, metadata)) || [];\n }\n\n /** @internal */\n static async getUserFlairTemplates(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate[]> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n const response = await client.UserFlair(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n return response.flair?.map((flair) => new FlairTemplate(flair, subredditName, metadata)) || [];\n }\n\n /** @internal */\n static async deleteFlairTemplate(\n subredditName: string,\n flairTemplateId: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n await client.DeleteFlairTemplate(\n {\n subreddit: subredditName,\n flairTemplateId,\n },\n metadata\n );\n }\n}\n\nexport type SetFlairOptions = {\n /** The name of the subreddit of the item to set the flair on */\n subredditName: string;\n /** The flair template's ID */\n flairTemplateId?: string;\n /** The flair text */\n text?: string;\n /** The flair CSS class */\n cssClass?: string;\n /** The flair text color. Either 'dark' or 'light'. */\n textColor?: FlairTextColor;\n /** The flair background color. Either 'transparent' or a hex color code. e.g. #FFC0CB */\n backgroundColor?: string;\n};\n\nexport type SetUserFlairOptions = SetFlairOptions & {\n /** The username of the user to set the flair on */\n username: string;\n};\n\nexport type SetPostFlairOptions = SetFlairOptions & {\n /** The ID of the post to set the flair on */\n postId: string;\n};\n\nexport type InternalSetPostFlairOptions = SetFlairOptions & {\n postId: T3ID;\n};\n\nexport type SetUserFlairBatchConfig = {\n /** The username of the user to edit the flair on */\n username: string;\n /** The flair text. Can't contain the comma character (\",\") */\n text?: string | undefined;\n /** The flair CSS class */\n cssClass?: string | undefined;\n};\n\nexport type UserFlairPageOptions = {\n /** A user id optionally provided which will result in a slice of user flairs, starting after this user, to be returned. */\n after?: string;\n /** A user id optionally provided which will result in a slice of user flairs, starting before this user, to be returned. */\n before?: string;\n /** A limit to the number of flairs that will be returned. Default: 25, Max: 1000 */\n limit?: number;\n};\n\nexport type GetUserFlairBySubredditOptions = UserFlairPageOptions & {\n /** The subreddit associated with the flair being retrieved. */\n subreddit: string;\n /** The username associated with the flair being retrieved. */\n name?: string;\n};\n\nexport type UserFlair = {\n /** The CSS class applied to this flair in the UI. */\n flairCssClass?: string;\n /** The username of the user to which this flair is assigned.*/\n user?: string;\n /** The text displayed in the UI for this flair. */\n flairText?: string;\n};\n\nexport type GetUserFlairBySubredditResponse = {\n /** The list of user flair */\n users: UserFlair[];\n /** The user id of the last user flair in this slice. Its presence indicates\n * that there are more items that can be fetched. Pass this into the \"after\" parameter\n * in the next call to get the next slice of data */\n next?: string;\n /** The user id of the first user flair in this slice. Its presence indicates\n * that there are items before this item that can be fetched. Pass this into the \"before\" parameter\n * in the next call to get the previous slice of data */\n prev?: string;\n};\n\n/** @internal */\nexport function convertUserFlairProtoToAPI(userFlair: UserFlairProto): UserFlair {\n return {\n flairCssClass: userFlair.flairCssClass,\n user: userFlair.user,\n flairText: userFlair.flairText,\n };\n}\n\nexport class Flair {\n /**\n * Exposes the ListFlair API. This method will return the list of user flair for the subreddit. If name\n * is specified then it will return the user flair for the given user.\n *\n * @param { GetUserFlairBySubredditOptions } options See the interface\n * @param { Metadata | undefined } metadata See the interface\n *\n * @returns { Promise<GetUserFlairBySubredditResponse> }\n *\n * @example\n * ```ts\n * const response = await reddit.flair.getUserFlairBySubreddit({\n * subreddit: \"EyeBleach\",\n * name: \"badapple\"\n * },\n * metadata\n * );\n * ```\n * @internal\n */\n static async getUserFlairBySubreddit(\n options: GetUserFlairBySubredditOptions,\n metadata: Metadata | undefined\n ): Promise<GetUserFlairBySubredditResponse> {\n const client = Devvit.redditAPIPlugins.Flair;\n return client.FlairList(options, metadata);\n }\n\n /** @internal */\n static setUserFlair(options: SetUserFlairOptions, metadata: Metadata | undefined): Promise<void> {\n return Flair.#setFlair(options, metadata);\n }\n\n /** @internal */\n static setUserFlairBatch(\n subredditName: string,\n flairs: SetUserFlairBatchConfig[],\n metadata: Metadata | undefined\n ): Promise<FlairCsvResult[]> {\n return Flair.#setUserFlairBatch(subredditName, flairs, metadata);\n }\n\n /** @internal */\n static setPostFlair(options: SetPostFlairOptions, metadata: Metadata | undefined): Promise<void> {\n return Flair.#setFlair(\n {\n ...options,\n postId: asT3ID(options.postId),\n },\n metadata\n );\n }\n\n /** @internal */\n static async #setFlair(\n options: (Omit<SetPostFlairOptions, 'postId'> & { postId: T3ID }) | SetUserFlairOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n await client.SelectFlair(\n {\n subreddit: options.subredditName,\n flairTemplateId: options.flairTemplateId ?? '',\n text: options.text ?? '',\n name: (options as SetUserFlairOptions).username,\n link: (options as InternalSetPostFlairOptions).postId,\n backgroundColor: options.backgroundColor ?? '',\n textColor: options.textColor ?? 'dark',\n cssClass: options.cssClass ?? '',\n returnRtjson: 'none',\n },\n metadata\n );\n }\n\n /** @internal */\n static async #setUserFlairBatch(\n subredditName: string,\n flairs: SetUserFlairBatchConfig[],\n metadata: Metadata | undefined\n ): Promise<FlairCsvResult[]> {\n if (!flairs.length) {\n return [];\n }\n\n const maxFlairsPerRequest = 100;\n if (flairs.length > maxFlairsPerRequest) {\n throw new Error('Unexpected input: flairs array cannot be longer than 100 entries.');\n }\n\n const csvDelimiter = ',';\n\n const flairCsv = flairs\n .map((userConfig) => {\n for (const propertyName in userConfig) {\n if (userConfig[propertyName as keyof SetUserFlairBatchConfig]?.includes(csvDelimiter)) {\n throw new Error(`Unexpected input: ${propertyName} cannot contain the \",\" character`);\n }\n }\n return [userConfig.username, userConfig.text || '', userConfig.cssClass || ''].join(\n csvDelimiter\n );\n })\n .join('\\n');\n\n const client = Devvit.redditAPIPlugins.Flair;\n const response = await client.FlairCsv(\n {\n subreddit: subredditName,\n flairCsv,\n },\n metadata\n );\n\n return response.result;\n }\n\n /** @internal */\n static async removePostFlair(\n subredditName: string,\n postId: T3ID,\n metadata: Metadata | undefined\n ): Promise<void> {\n return Flair.#removeFlair(subredditName, postId, undefined, metadata);\n }\n\n /** @internal */\n static async removeUserFlair(\n subredditName: string,\n username: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n return Flair.#removeFlair(subredditName, undefined, username, metadata);\n }\n\n static async #removeFlair(\n subredditName: string,\n postId: string | undefined,\n username: string | undefined,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n await client.SelectFlair(\n {\n subreddit: subredditName,\n name: username ?? '',\n link: postId ?? '',\n flairTemplateId: '',\n backgroundColor: '',\n text: '',\n textColor: '',\n cssClass: '',\n returnRtjson: 'none',\n },\n metadata\n );\n }\n}\n\nfunction asFlairTextColor(color?: string): FlairTextColor {\n assertNonNull(color, 'Flair text color is required');\n\n if (color === 'light' || color === 'dark') {\n return color;\n }\n\n throw new Error(`Invalid flair text color: ${color}`);\n}\n\nfunction asFlairBackgroundColor(color?: string): FlairBackgroundColor {\n if (!color || color.length === 0 || color === 'transparent') {\n return 'transparent';\n }\n\n if (/^#([A-Fa-f0-9]{6})$/.test(color)) {\n return color as FlairBackgroundColor;\n }\n\n throw new Error(`Invalid flair background color: ${color}`);\n}\n\nfunction asAllowableContent(allowableContent?: string): AllowableFlairContent {\n if (allowableContent === 'all' || allowableContent === 'text' || allowableContent === 'emoji') {\n return allowableContent;\n }\n\n throw new Error(`Invalid allowable content: ${allowableContent}`);\n}\n", "import type {\n Listing as ListingProto,\n Metadata,\n RedditObject,\n SubmitRequest,\n SubmitResponse,\n} from '@devvit/protos';\nimport { type SetCustomPostPreviewRequest_BodyType } from '@devvit/protos';\nimport { Block, UIResponse } from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\nimport type { T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, asT3ID, asT5ID, isT3ID } from '@devvit/shared-types/tid.js';\nimport { fromByteArray } from 'base64-js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { BlocksReconciler } from '../../../devvit/internals/blocks/BlocksReconciler.js';\nimport { BlocksHandler } from '../../../devvit/internals/blocks/handler/BlocksHandler.js';\nimport { RunAs, type UserGeneratedContent } from '../common.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { richtextToString } from '../helpers/richtextToString.js';\nimport { getCustomPostRichTextFallback } from '../helpers/textFallbackToRichtext.js';\nimport type { CommentSubmissionOptions } from './Comment.js';\nimport { Comment } from './Comment.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport { ModNote } from './ModNote.js';\nimport { User } from './User.js';\n\nexport type GetPostsOptions = ListingFetchOptions & {\n subredditName?: string;\n};\n\nexport type GetPostsOptionsWithTimeframe = GetPostsOptions & {\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n};\n\nexport type GetSortedPostsOptions = GetPostsOptionsWithTimeframe & {\n sort: 'top' | 'controversial';\n};\n\nexport type GetHotPostsOptions = GetPostsOptions & {\n location?:\n | 'GLOBAL'\n | 'US'\n | 'AR'\n | 'AU'\n | 'BG'\n | 'CA'\n | 'CL'\n | 'CO'\n | 'HR'\n | 'CZ'\n | 'FI'\n | 'FR'\n | 'DE'\n | 'GR'\n | 'HU'\n | 'IS'\n | 'IN'\n | 'IE'\n | 'IT'\n | 'JP'\n | 'MY'\n | 'MX'\n | 'NZ'\n | 'PH'\n | 'PL'\n | 'PT'\n | 'PR'\n | 'RO'\n | 'RS'\n | 'SG'\n | 'ES'\n | 'SE'\n | 'TW'\n | 'TH'\n | 'TR'\n | 'GB'\n | 'US_WA'\n | 'US_DE'\n | 'US_DC'\n | 'US_WI'\n | 'US_WV'\n | 'US_HI'\n | 'US_FL'\n | 'US_WY'\n | 'US_NH'\n | 'US_NJ'\n | 'US_NM'\n | 'US_TX'\n | 'US_LA'\n | 'US_NC'\n | 'US_ND'\n | 'US_NE'\n | 'US_TN'\n | 'US_NY'\n | 'US_PA'\n | 'US_CA'\n | 'US_NV'\n | 'US_VA'\n | 'US_CO'\n | 'US_AK'\n | 'US_AL'\n | 'US_AR'\n | 'US_VT'\n | 'US_IL'\n | 'US_GA'\n | 'US_IN'\n | 'US_IA'\n | 'US_OK'\n | 'US_AZ'\n | 'US_ID'\n | 'US_CT'\n | 'US_ME'\n | 'US_MD'\n | 'US_MA'\n | 'US_OH'\n | 'US_UT'\n | 'US_MO'\n | 'US_MN'\n | 'US_MI'\n | 'US_RI'\n | 'US_KS'\n | 'US_MT'\n | 'US_MS'\n | 'US_SC'\n | 'US_KY'\n | 'US_OR'\n | 'US_SD';\n};\n\nexport type GetPostsByUserOptions = {\n username: string;\n sort?: 'hot' | 'new' | 'top' | 'controversial';\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n pageSize?: number;\n limit?: number;\n after?: string;\n before?: string;\n};\n\nexport type PostSuggestedCommentSort =\n | 'BLANK'\n /** \"Best\" sort. */\n | 'CONFIDENCE'\n | 'CONTROVERSIAL'\n | 'LIVE'\n /** Sort comments by creation time. */\n | 'NEW'\n | 'OLD'\n /** Similar to the \"best\" (confidence) sort, but specially designed for\n Q&A-type threads to highlight good question/answer pairs. */\n | 'QA'\n | 'RANDOM'\n /** Sort by top upvoted comments. */\n | 'TOP';\n\nexport type PostTextOptions =\n | {\n text: string;\n }\n | {\n richtext: object | RichTextBuilder;\n };\n\nexport type CustomPostRichTextFallback = RichTextBuilder | string;\n\nexport type CustomPostTextFallbackOptions =\n | {\n /**\n * May also include markdown. See https://www.reddit.com/r/reddit.com/wiki/markdown/\n */\n text: string;\n }\n | {\n richtext: CustomPostRichTextFallback;\n };\n\n// TODO - refactor submit post options\nexport type SubmitLinkOptions = CommonSubmitPostOptions & {\n url: string;\n /**\n * @deprecated Unsupported. This property is for backwards compatibility and\n * has no effect. It will removed in a future version. New code should not\n * use it.\n */\n resubmit?: boolean;\n};\n\nexport type SubmitMediaOptions = CommonSubmitPostOptions & {\n kind: 'image' | 'video' | 'videogif';\n // If `kind` is \"video\" or \"videogif\" this must be set to the thumbnail URL\n // https://www.reddit.com/dev/api/#POST_api_submit\n videoPosterUrl?: string;\n // If `kind` is \"image\" this must be set to the image URL\n // Currently Devvit only supports posts with a single image\n imageUrls?: [string];\n};\n\nexport type SubmitSelfPostOptions = PostTextOptions & CommonSubmitPostOptions;\n\nexport type SubmitCustomPostTextFallbackOptions = {\n textFallback?: CustomPostTextFallbackOptions;\n};\n\nexport type SubmitCustomPostOptions = CommonSubmitPostOptions &\n SubmitCustomPostTextFallbackOptions & {\n preview: JSX.Element;\n userGeneratedContent?: UserGeneratedContent;\n };\n\nexport type CommonSubmitPostOptions = {\n title: string;\n sendreplies?: boolean;\n nsfw?: boolean;\n spoiler?: boolean;\n flairId?: string;\n flairText?: string;\n runAs?: 'USER' | 'APP';\n};\n\nexport type SubmitPostOptions = (\n | SubmitLinkOptions\n | SubmitSelfPostOptions\n | SubmitCustomPostOptions\n | SubmitMediaOptions\n) & {\n subredditName: string;\n};\n\nconst SetCustomPostPreviewRequestBodyType = {\n NONE: 0,\n BLOCKS: 1,\n UNRECOGNIZED: -1,\n} as const;\n\ntype SetCustomPostPreviewRequestBodyType =\n (typeof SetCustomPostPreviewRequest_BodyType)[keyof typeof SetCustomPostPreviewRequest_BodyType];\n\nexport type CrosspostOptions = CommonSubmitPostOptions & {\n subredditName: string;\n postId: string;\n};\n\nexport type LinkFlair = {\n /**\n * One of: \"text\", \"richtext\"\n */\n type?: string;\n /**\n * Flair template ID to use when rendering this flair\n */\n templateId?: string;\n /**\n * Plain text representation of the flair\n */\n text?: string;\n /**\n * RichText object representation of the flair\n */\n richtext: {\n /**\n * Enum of element types. e.g. emoji or text\n */\n elementType?: string;\n /**\n * Text to show up in the flair, e.g. \"Need Advice\"\n */\n text?: string;\n /**\n * Emoji references, e.g. \":rainbow:\"\n */\n emojiRef?: string;\n /**\n * url string, e.g. \"https://reddit.com/\"\n */\n url?: string;\n }[];\n /**\n * Custom CSS classes from the subreddit's stylesheet to apply to the flair if rendered as HTML\n */\n cssClass?: string;\n /**\n * One of: \"light\", \"dark\"\n */\n textColor?: string;\n /**\n * Flair background color as a hex color string (# prefixed)\n * @example \"#FF4500\"\n */\n backgroundColor?: string;\n};\n\n/**\n * oEmbed is a format for allowing an embedded representation of a URL on third party sites.\n * The simple API allows a website to display embedded content (such as photos or videos)\n * when a user posts a link to that resource, without having to parse the resource directly.\n * See: https://oembed.com/\n */\nexport type OEmbed = {\n /** The resource type. Valid values, along with value-specific parameters, are described below. E.g. \"video\" */\n type: string;\n /** A text title, describing the resource. */\n title?: string | undefined;\n /** A URL for the author/owner of the resource. E.g. \"YouTube\" */\n providerName?: string | undefined;\n /** The name of the resource provider. E.g \"https://www.youtube.com/\" */\n providerUrl?: string | undefined;\n /** The oEmbed version number. This must be 1.0. */\n version: string;\n /** The width of the optional thumbnail in pixels */\n thumbnailWidth?: number;\n /** The height of the optional thumbnail in pixels */\n thumbnailHeight?: number;\n /** A URL to a thumbnail image representing the resource. */\n thumbnailUrl?: string | undefined;\n /** The HTML required to embed a video player. The HTML should have no padding or margins. Consumers may wish to load the HTML in an off-domain iframe to avoid XSS vulnerabilities. */\n html: string;\n /** The width in pixels required to display the HTML. */\n height?: number;\n /** The height in pixels required to display the HTML. */\n width?: number;\n /** A URL for the author/owner of the resource. E.g. \"https://www.youtube.com/@Reddit\" */\n authorUrl?: string | undefined;\n /** The name of the author/owner of the resource. E.g. \"Reddit\" */\n authorName?: string | undefined;\n};\n\n/**\n * Contains the data for a video hosted on Reddit that is in a post\n */\nexport type RedditVideo = {\n /** The bitrate of the video in kilobits per second. E.g. 450 */\n bitrateKbps?: number;\n /** The URL to the DASH playlist file. E.g. \"https://v.redd.it/abc123/DASHPlaylist.mpd\" */\n dashUrl?: string;\n /** The duration of the video in seconds. E.g. 30 */\n duration?: number;\n /** The direct URL to the video. E.g. \"https://v.redd.it/abc123/DASH_1080.mp4?source=fallback\" */\n fallbackUrl?: string;\n /** The height of the video in pixels. E.g. 1080 */\n height?: number;\n /** The URL to the HLS playlist file. E.g. \"https://v.redd.it/abc123/HLSPlaylist.m3u8\" */\n hlsUrl?: string;\n /** If `true`, the video is a GIF */\n isGif?: boolean;\n /** The URL to the scrubber media file. E.g. \"https://v.redd.it/abc123/DASH_96.mp4\" */\n scrubberMediaUrl?: string;\n /** The status of the transcoding process. E.g. \"completed\" */\n transcodingStatus?: string;\n /** The width of the video in pixels. E.g. 1920 */\n width?: number;\n};\n\nexport type SecureMedia = {\n /** The type of the OEmbed media, if present (e.g. \"youtube.com\") */\n type?: string;\n oembed?: OEmbed;\n redditVideo?: RedditVideo;\n};\n\n/**\n * Contains data about a post's thumbnail. Also contains a blurred version if the thumbnail is NSFW.\n */\nexport type EnrichedThumbnail = {\n /** Attribution text for the thumbnail */\n attribution?: string;\n /** The image used for the thumbnail. May have different resolution from Post.thumbnail */\n image: {\n url: string;\n height: number;\n width: number;\n };\n /** Whether this thumbnail appears blurred by default */\n isObfuscatedDefault: boolean;\n /** The blurred image for NSFW thumbnails */\n obfuscatedImage?: {\n url: string;\n height: number;\n width: number;\n };\n};\n\nexport class Post {\n #id: T3ID;\n #authorId?: T2ID;\n #authorName: string;\n #createdAt: Date;\n #subredditId: T5ID;\n #subredditName: string;\n #permalink: string;\n #title: string;\n #body?: string;\n #bodyHtml?: string;\n #url: string;\n #score: number;\n #numberOfComments: number;\n #numberOfReports: number;\n #thumbnail?: {\n url: string;\n height: number;\n width: number;\n };\n #approved: boolean;\n #approvedAtUtc: number;\n #bannedAtUtc: number;\n #spam: boolean;\n #stickied: boolean;\n #removed: boolean;\n #removedBy: string | undefined;\n #removedByCategory: string | undefined;\n #archived: boolean;\n #edited: boolean;\n #locked: boolean;\n #nsfw: boolean;\n #quarantined: boolean;\n #spoiler: boolean;\n #hidden: boolean;\n #ignoringReports: boolean;\n #distinguishedBy?: string;\n #flair?: LinkFlair;\n #secureMedia?: SecureMedia;\n #modReportReasons: string[];\n #userReportReasons: string[];\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: RedditObject, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'Post is missing id');\n assertNonNull(data.title, 'Post is missing title');\n assertNonNull(data.createdUtc, 'Post is missing created date');\n assertNonNull(data.author, 'Post is missing author name');\n assertNonNull(data.subreddit, 'Post is missing subreddit name');\n assertNonNull(data.subredditId, 'Post is missing subreddit id');\n assertNonNull(data.url, 'Post is missing url');\n assertNonNull(data.permalink, 'Post is missing permalink');\n\n this.#id = asT3ID(`t3_${data.id}`);\n\n this.#authorName = data.author;\n this.#authorId = data.authorFullname ? asT2ID(data.authorFullname) : undefined;\n this.#subredditId = asT5ID(data.subredditId);\n this.#subredditName = data.subreddit;\n this.#score = data.score ?? 0;\n this.#numberOfComments = data.numComments ?? 0;\n this.#numberOfReports = data.numReports ?? 0;\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#title = data.title;\n this.#body = data.selftext;\n this.#bodyHtml = data.selftextHtml;\n this.#url = data.url;\n this.#permalink = data.permalink;\n\n if (\n data.thumbnail &&\n data.thumbnail !== 'self' &&\n data.thumbnail !== 'nsfw' &&\n data.thumbnailHeight != null &&\n data.thumbnailWidth != null\n ) {\n this.#thumbnail = {\n url: data.thumbnail,\n height: data.thumbnailHeight,\n width: data.thumbnailWidth,\n };\n }\n\n this.#approved = data.approved ?? false;\n this.#approvedAtUtc = data.approvedAtUtc ?? 0;\n this.#bannedAtUtc = data.bannedAtUtc ?? 0;\n this.#removed = data.removed ?? false;\n this.#removedBy = data.removedBy;\n this.#removedByCategory = data.removedByCategory;\n this.#spam = data.spam ?? false;\n this.#stickied = data.stickied ?? false;\n this.#archived = data.archived ?? false;\n this.#edited = data.edited ?? false;\n this.#locked = data.locked ?? false;\n this.#nsfw = data.over18 ?? false;\n this.#quarantined = data.quarantine ?? false;\n this.#spoiler = data.spoiler;\n this.#hidden = data.hidden ?? false;\n this.#ignoringReports = data.ignoreReports ?? false;\n this.#distinguishedBy = data.distinguished;\n this.#secureMedia = data.secureMedia;\n\n this.#modReportReasons = ((data.modReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n this.#userReportReasons = ((data.userReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n\n this.#metadata = metadata;\n\n if (\n data.linkFlairBackgroundColor ||\n data.linkFlairCssClass ||\n data.linkFlairText ||\n data.linkFlairType ||\n data.linkFlairTemplateId ||\n data.linkFlairRichtext ||\n data.linkFlairTextColor\n ) {\n this.#flair = {\n backgroundColor: data.linkFlairBackgroundColor,\n cssClass: data.linkFlairCssClass,\n text: data.linkFlairText,\n type: data.linkFlairType,\n templateId: data.linkFlairTemplateId,\n // Map linkFlairRichtext[] into the objects with more user-friendly property names\n richtext: (data.linkFlairRichtext ?? []).map(({ e, t, a, u }) => ({\n elementType: e,\n text: t,\n emojiRef: a,\n url: u,\n })),\n textColor: data.linkFlairTextColor,\n };\n }\n }\n\n get id(): T3ID {\n return this.#id;\n }\n\n get authorId(): T2ID | undefined {\n return this.#authorId;\n }\n\n get authorName(): string {\n return this.#authorName;\n }\n\n get subredditId(): T5ID {\n return this.#subredditId;\n }\n\n get subredditName(): string {\n return this.#subredditName;\n }\n\n get permalink(): string {\n return this.#permalink;\n }\n\n get title(): string {\n return this.#title;\n }\n\n get body(): string | undefined {\n return this.#body;\n }\n\n get bodyHtml(): string | undefined {\n return this.#bodyHtml;\n }\n\n get url(): string {\n return this.#url;\n }\n\n get thumbnail(): { url: string; height: number; width: number } | undefined {\n return this.#thumbnail;\n }\n\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n get score(): number {\n return this.#score;\n }\n\n get numberOfComments(): number {\n return this.#numberOfComments;\n }\n\n get numberOfReports(): number {\n return this.#numberOfReports;\n }\n\n get approved(): boolean {\n return this.#approved;\n }\n\n get approvedAtUtc(): number {\n return this.#approvedAtUtc;\n }\n\n get bannedAtUtc(): number {\n return this.#bannedAtUtc;\n }\n\n get spam(): boolean {\n return this.#spam;\n }\n\n get stickied(): boolean {\n return this.#stickied;\n }\n\n get removed(): boolean {\n return this.#removed;\n }\n\n /**\n * Who removed this object (username)\n */\n get removedBy(): string | undefined {\n return this.#removedBy;\n }\n\n /**\n * who/what removed this object. It will return one of the following:\n * - \"anti_evil_ops\": object is removed by a aeops member\n * - \"author\": object is removed by author of the post\n * - \"automod_filtered\": object is filtered by automod\n * - \"community_ops\": object is removed by a community team member\n * - \"content_takedown\": object is removed due to content violation\n * - \"copyright_takedown\": object is removed due to copyright violation\n * - \"deleted\": object is deleted\n * - \"moderator\": object is removed by a mod of the sub\n * - \"reddit\": object is removed by anyone else\n * - undefined: object is not removed\n */\n get removedByCategory(): string | undefined {\n return this.#removedByCategory;\n }\n\n get archived(): boolean {\n return this.#archived;\n }\n\n get edited(): boolean {\n return this.#edited;\n }\n\n get locked(): boolean {\n return this.#locked;\n }\n\n get nsfw(): boolean {\n return this.#nsfw;\n }\n\n get quarantined(): boolean {\n return this.#quarantined;\n }\n\n get spoiler(): boolean {\n return this.#spoiler;\n }\n\n get hidden(): boolean {\n return this.#hidden;\n }\n\n get ignoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n get distinguishedBy(): string | undefined {\n return this.#distinguishedBy;\n }\n\n get comments(): Listing<Comment> {\n return Comment.getComments(\n {\n postId: this.id,\n },\n this.#metadata\n );\n }\n\n get flair(): LinkFlair | undefined {\n return this.#flair;\n }\n\n get secureMedia(): SecureMedia | undefined {\n return this.#secureMedia;\n }\n\n get userReportReasons(): string[] {\n return this.#userReportReasons;\n }\n\n get modReportReasons(): string[] {\n return this.#modReportReasons;\n }\n\n toJSON(): Pick<\n Post,\n | 'id'\n | 'authorId'\n | 'authorName'\n | 'subredditId'\n | 'subredditName'\n | 'permalink'\n | 'title'\n | 'body'\n | 'bodyHtml'\n | 'url'\n | 'thumbnail'\n | 'score'\n | 'numberOfComments'\n | 'numberOfReports'\n | 'createdAt'\n | 'approved'\n | 'spam'\n | 'stickied'\n | 'removed'\n | 'removedBy'\n | 'removedByCategory'\n | 'archived'\n | 'edited'\n | 'locked'\n | 'nsfw'\n | 'quarantined'\n | 'spoiler'\n | 'hidden'\n | 'ignoringReports'\n | 'distinguishedBy'\n | 'flair'\n | 'secureMedia'\n | 'userReportReasons'\n | 'modReportReasons'\n > {\n return {\n id: this.id,\n authorId: this.authorId,\n authorName: this.authorName,\n subredditId: this.subredditId,\n subredditName: this.subredditName,\n permalink: this.permalink,\n title: this.title,\n body: this.body,\n bodyHtml: this.bodyHtml,\n url: this.url,\n thumbnail: this.thumbnail,\n score: this.score,\n numberOfComments: this.numberOfComments,\n numberOfReports: this.numberOfReports,\n createdAt: this.createdAt,\n approved: this.approved,\n spam: this.spam,\n stickied: this.stickied,\n removed: this.removed,\n removedBy: this.#removedBy,\n removedByCategory: this.#removedByCategory,\n archived: this.archived,\n edited: this.edited,\n locked: this.locked,\n nsfw: this.nsfw,\n quarantined: this.quarantined,\n spoiler: this.spoiler,\n hidden: this.hidden,\n ignoringReports: this.ignoringReports,\n distinguishedBy: this.distinguishedBy,\n flair: this.flair,\n secureMedia: this.secureMedia,\n modReportReasons: this.#modReportReasons,\n userReportReasons: this.#userReportReasons,\n };\n }\n\n isApproved(): boolean {\n return this.#approved;\n }\n\n isSpam(): boolean {\n return this.#spam;\n }\n\n isStickied(): boolean {\n return this.#stickied;\n }\n\n isRemoved(): boolean {\n return this.#removed;\n }\n\n isArchived(): boolean {\n return this.#archived;\n }\n\n isEdited(): boolean {\n return this.#edited;\n }\n\n isLocked(): boolean {\n return this.#locked;\n }\n\n isNsfw(): boolean {\n return this.#nsfw;\n }\n\n isQuarantined(): boolean {\n return this.#quarantined;\n }\n\n isSpoiler(): boolean {\n return this.#spoiler;\n }\n\n isHidden(): boolean {\n return this.#hidden;\n }\n\n isIgnoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n isDistinguishedBy(): string | undefined {\n return this.#distinguishedBy;\n }\n\n async edit(options: PostTextOptions): Promise<void> {\n const newPost = await Post.edit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n\n this.#body = newPost.body;\n this.#edited = newPost.edited;\n }\n\n /**\n * Set the suggested sort for comments on a Post.\n *\n * @throws {Error} Throws an error if the suggested sort could not be set.\n * @example\n * ```ts\n * const post = await reddit.getPostById(context.postId);\n * await post.setSuggestedCommentSort(\"NEW\");\n * ```\n */\n async setSuggestedCommentSort(suggestedSort: PostSuggestedCommentSort): Promise<void> {\n await Post.setSuggestedCommentSort(\n { id: this.id, subredditId: this.#subredditId, suggestedSort },\n this.#metadata\n );\n }\n\n /**\n * Set a lightweight UI that shows while the custom post renders\n *\n * @param {JSX.ComponentFunction} ui - A JSX component function that returns a simple ui to be rendered.\n * @throws {Error} Throws an error if the preview could not be set.\n * @example\n * ```ts\n * const preview = (\n * <vstack height=\"100%\" width=\"100%\" alignment=\"middle center\">\n * <text size=\"large\">An updated preview!</text>\n * </vstack>\n * );\n * const post = await reddit.getPostById(context.postId);\n * await post.setCustomPostPreview(() => preview);\n * ```\n */\n async setCustomPostPreview(ui: JSX.ComponentFunction): Promise<void> {\n await Post.setCustomPostPreview(\n {\n id: this.id,\n ui,\n },\n this.#metadata\n );\n }\n\n /**\n * Set a text fallback for the custom post\n *\n * @param {CustomPostTextFallbackOptions} options - A text or a richtext to render in a fallback\n * @throws {Error} Throws an error if the fallback could not be set.\n * @example\n * ```ts\n * // from a menu action, form, scheduler, trigger, custom post click event, etc\n * const newTextFallback = { text: 'This is an updated text fallback' };\n * const post = await context.reddit.getPostById(context.postId);\n * await post.setTextFallback(newTextFallback);\n * ```\n */\n async setTextFallback(options: CustomPostTextFallbackOptions): Promise<void> {\n const newPost = await Post.setTextFallback(options, this.id, this.#metadata);\n\n this.#body = newPost.body;\n this.#edited = newPost.edited;\n }\n\n async addComment(options: CommentSubmissionOptions): Promise<Comment> {\n return Comment.submit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n }\n\n async delete(): Promise<void> {\n return Post.delete(this.id, this.#metadata);\n }\n\n async approve(): Promise<void> {\n await Post.approve(this.id, this.#metadata);\n this.#approved = true;\n this.#removed = false;\n }\n\n async remove(isSpam: boolean = false): Promise<void> {\n await Post.remove(this.id, isSpam, this.#metadata);\n this.#removed = true;\n this.#spam = isSpam;\n this.#approved = false;\n }\n\n async lock(): Promise<void> {\n await Post.lock(this.id, this.#metadata);\n this.#locked = true;\n }\n\n async unlock(): Promise<void> {\n await Post.unlock(this.id, this.#metadata);\n this.#locked = false;\n }\n\n async hide(): Promise<void> {\n await Post.hide(this.id, this.#metadata);\n this.#hidden = true;\n }\n\n async unhide(): Promise<void> {\n await Post.unhide(this.id, this.#metadata);\n this.#hidden = false;\n }\n\n async markAsNsfw(): Promise<void> {\n await Post.markAsNsfw(this.id, this.#metadata);\n this.#nsfw = true;\n }\n\n async unmarkAsNsfw(): Promise<void> {\n await Post.unmarkAsNsfw(this.id, this.#metadata);\n this.#nsfw = false;\n }\n\n async markAsSpoiler(): Promise<void> {\n await Post.markAsSpoiler(this.id, this.#metadata);\n this.#spoiler = true;\n }\n\n async unmarkAsSpoiler(): Promise<void> {\n await Post.unmarkAsSpoiler(this.id, this.#metadata);\n this.#spoiler = false;\n }\n\n async sticky(position?: 1 | 2 | 3 | 4): Promise<void> {\n await Post.sticky(this.id, position, this.#metadata);\n }\n\n async unsticky(): Promise<void> {\n await Post.unsticky(this.id, this.#metadata);\n }\n\n async distinguish(): Promise<void> {\n const { distinguishedBy } = await Post.distinguish(this.id, false, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n }\n\n async distinguishAsAdmin(): Promise<void> {\n const { distinguishedBy } = await Post.distinguish(this.id, true, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n }\n\n async undistinguish(): Promise<void> {\n const { distinguishedBy } = await Post.undistinguish(this.id, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n }\n\n async ignoreReports(): Promise<void> {\n await Post.ignoreReports(this.id, this.#metadata);\n this.#ignoringReports = true;\n }\n\n async unignoreReports(): Promise<void> {\n await Post.unignoreReports(this.id, this.#metadata);\n this.#ignoringReports = false;\n }\n\n async getAuthor(): Promise<User | undefined> {\n return User.getByUsername(this.#authorName, this.#metadata);\n }\n\n async crosspost(options: Omit<CrosspostOptions, 'postId'>): Promise<Post> {\n return Post.crosspost(\n {\n ...options,\n postId: this.id,\n },\n this.#metadata\n );\n }\n\n /**\n * Add a mod note for why the post was removed\n *\n * @param options.reasonId id of a Removal Reason - you can leave this as an empty string if you don't have one\n * @param options.modNote the reason for removal (maximum 100 characters) (optional)\n * @returns\n */\n addRemovalNote(options: { reasonId: string; modNote?: string }): Promise<void> {\n return ModNote.addRemovalNote({ itemIds: [this.#id], ...options }, this.#metadata);\n }\n\n /**\n * Get a thumbnail that contains a preview image and also contains a blurred preview for\n * NSFW images. The thumbnail returned has higher resolution than Post.thumbnail.\n * Returns undefined if the post doesn't have a thumbnail\n *\n * @returns {EnrichedThumbnail | undefined}\n * @throws {Error} Throws an error if the thumbnail could not be fetched\n * @example\n * ```ts\n * // from a menu action, form, scheduler, trigger, custom post click event, etc\n * const post = await context.reddit.getPostById(context.postId);\n * const enrichedThumbnail = await post.getEnrichedThumbnail();\n * ```\n */\n async getEnrichedThumbnail(): Promise<EnrichedThumbnail | undefined> {\n return getThumbnailV2({ id: this.id }, this.#metadata);\n }\n\n // TODO: flair methods\n\n /** @internal */\n static async getById(id: T3ID, metadata: Metadata | undefined): Promise<Post> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const postId: T3ID = isT3ID(id) ? id : `t3_${id}`;\n\n const response = await client.Info(\n {\n subreddits: [],\n thingIds: [postId],\n },\n metadata\n );\n\n if (!response.data?.children?.length) {\n throw new Error('could not find post');\n }\n\n const postData = response.data.children[0];\n\n if (!postData?.data) {\n throw new Error('could not find post');\n }\n\n return new Post(postData.data, metadata);\n }\n\n /** @internal */\n static async submit(options: SubmitPostOptions, metadata: Metadata | undefined): Promise<Post> {\n const { runAs = 'APP' } = options;\n const runAsType = RunAs[runAs];\n const client =\n runAsType === RunAs.USER\n ? Devvit.userActionsPlugin\n : Devvit.redditAPIPlugins.LinksAndComments;\n\n let response: SubmitResponse;\n\n if ('preview' in options) {\n assertNonNull(metadata, 'Missing metadata in `SubmitPostOptions`');\n if (runAsType === RunAs.USER) {\n assertNonNull(\n options.userGeneratedContent,\n 'userGeneratedContent must be set in `SubmitPostOptions` when RunAs=USER for experience posts'\n );\n }\n const reconciler = new BlocksReconciler(\n () => options.preview,\n undefined,\n {},\n metadata,\n undefined\n );\n const previewBlock = await reconciler.buildBlocksUI();\n const encodedCached = Block.encode(previewBlock).finish();\n\n const { textFallback, ...sanitizedOptions } = options;\n const richtextFallback = textFallback ? getCustomPostRichTextFallback(textFallback) : '';\n\n const submitRequest: SubmitRequest = {\n kind: 'custom',\n sr: options.subredditName,\n richtextJson: fromByteArray(encodedCached),\n richtextFallback,\n ...sanitizedOptions,\n runAs: runAsType,\n };\n\n response = await client.SubmitCustomPost(submitRequest, metadata);\n } else {\n response = await client.Submit(\n {\n kind: 'kind' in options ? options.kind : 'url' in options ? 'link' : 'self',\n sr: options.subredditName,\n richtextJson: 'richtext' in options ? richtextToString(options.richtext) : undefined,\n ...options,\n runAs: runAsType,\n },\n metadata\n );\n }\n\n // Post Id might not be present as image/video post creation can happen asynchronously\n const isAllowedMediaType =\n 'kind' in options && ['image', 'video', 'videogif'].includes(options.kind);\n if (isAllowedMediaType && !response.json?.data?.id) {\n if (options.kind === 'image' && 'imageUrls' in options) {\n throw new Error(\n `Image post type with ${options.imageUrls} is being created asynchronously and should be updated in the subreddit soon.`\n );\n } else if ('videoPosterUrl' in options) {\n throw new Error(\n `Post of ${options.kind} type with ${options.videoPosterUrl} is being created asynchronously and should be updated in the subreddit soon.`\n );\n }\n }\n\n if (!response.json?.data?.id || response.json?.errors?.length) {\n throw new Error(\n `failed to submit post - either post ID is empty or request failed with errors: ${response.json?.errors}`\n );\n }\n\n return Post.getById(`t3_${response.json.data.id}`, metadata);\n }\n\n /** @internal */\n static async crosspost(options: CrosspostOptions, metadata: Metadata | undefined): Promise<Post> {\n const { runAs = 'APP' } = options;\n const runAsType = RunAs[runAs];\n const client =\n runAsType === RunAs.USER\n ? Devvit.userActionsPlugin\n : Devvit.redditAPIPlugins.LinksAndComments;\n const { postId, subredditName, ...rest } = options;\n\n const response = await client.Submit(\n {\n kind: 'crosspost',\n sr: subredditName,\n crosspostFullname: asT3ID(postId),\n ...rest,\n runAs: runAsType,\n },\n metadata\n );\n\n if (!response.json?.data?.id || response.json?.errors?.length) {\n throw new Error('failed to crosspost post');\n }\n\n return Post.getById(`t3_${response.json.data.id}`, metadata);\n }\n\n /** @internal */\n static async edit(\n options: PostTextOptions & { id: T3ID },\n metadata: Metadata | undefined\n ): Promise<Post> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const { id } = options;\n\n let richtextString: string | undefined;\n if ('richtext' in options) {\n richtextString = richtextToString(options.richtext);\n }\n\n const response = await client.EditUserText(\n {\n thingId: id,\n text: 'text' in options ? options.text : '',\n richtextJson: richtextString,\n runAs: RunAs.APP,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error('Failed to edit post');\n }\n\n // The LinksAndComments.EditUserText response is wrong and assumes that\n // the API is only used to for comments so we fetch the new post here.\n return Post.getById(id, metadata);\n }\n\n /** @internal */\n static async setSuggestedCommentSort(\n options: { suggestedSort: PostSuggestedCommentSort; id: T3ID; subredditId: T5ID },\n metadata: Metadata | undefined\n ): Promise<void> {\n const operationName = 'SetSuggestedSort';\n const persistedQueryHash = 'cf6052acc7fefaa65b710625b81dba8041f258313aafe9730e2a3dc855e5d10d';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n {\n input: {\n subredditId: options.subredditId,\n postId: options.id,\n sort: options.suggestedSort,\n },\n },\n metadata\n );\n\n if (!response.data?.setSuggestedSort?.ok) {\n throw new Error('Failed to set suggested sort');\n }\n }\n\n /** @internal */\n static async setCustomPostPreview(\n options: { id: T3ID; ui: JSX.ComponentFunction },\n metadata: Metadata | undefined\n ): Promise<void> {\n if (!metadata) {\n throw new Error('Failed to set custom post preview. Metadata not found');\n }\n\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const handler = new BlocksHandler(options.ui);\n const handlerResponse = UIResponse.fromJSON(await handler.handle({ events: [] }, metadata));\n const block = handlerResponse.blocks as Block;\n const blocksRenderContent = fromByteArray(Block.encode(block).finish());\n\n await client.SetCustomPostPreview(\n {\n thingId: options.id,\n bodyType: SetCustomPostPreviewRequestBodyType.BLOCKS,\n blocksRenderContent,\n },\n metadata\n );\n }\n\n /** @internal */\n static async setTextFallback(\n options: CustomPostTextFallbackOptions,\n postId: T3ID,\n metadata: Metadata | undefined\n ): Promise<Post> {\n if (!('text' in options) && !('richtext' in options)) {\n throw new Error(`No text fallback provided for post ${postId}.`);\n }\n\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const richtextFallback = getCustomPostRichTextFallback(options);\n\n const response = await client.EditCustomPost(\n {\n thingId: postId,\n richtextFallback,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error('Failed to set post text fallback');\n }\n\n return Post.getById(postId, metadata);\n }\n\n /** @internal */\n static async delete(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Del(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async approve(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Approve(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async remove(\n id: T3ID,\n isSpam: boolean = false,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Remove(\n {\n id,\n spam: isSpam,\n },\n metadata\n );\n }\n\n /** @internal */\n static async hide(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Hide(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unhide(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unhide(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async markAsNsfw(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.MarkNSFW(\n {\n id,\n },\n metadata\n );\n }\n /** @internal */\n static async unmarkAsNsfw(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.UnmarkNSFW(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async markAsSpoiler(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Spoiler(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unmarkAsSpoiler(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unspoiler(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async sticky(\n id: T3ID,\n position: 1 | 2 | 3 | 4 | undefined,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.SetSubredditSticky(\n {\n id,\n state: true,\n num: position,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unsticky(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.SetSubredditSticky(\n {\n id,\n state: false,\n },\n metadata\n );\n }\n\n /** @internal */\n static async lock(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Lock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unlock(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unlock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async distinguish(\n id: T3ID,\n asAdmin: boolean,\n metadata: Metadata | undefined\n ): Promise<{ distinguishedBy: string | undefined }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: asAdmin ? 'admin' : 'yes',\n sticky: false,\n },\n metadata\n );\n\n const post = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(post);\n\n return {\n distinguishedBy: post.distinguished,\n };\n }\n\n /** @internal */\n static async undistinguish(\n id: T3ID,\n metadata: Metadata | undefined\n ): Promise<{ distinguishedBy: string | undefined }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: 'no',\n sticky: false,\n },\n metadata\n );\n\n const post = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(post);\n\n return {\n distinguishedBy: post.distinguished,\n };\n }\n\n /** @internal */\n static async ignoreReports(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.IgnoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unignoreReports(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.UnignoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static getControversialPosts(\n options: GetPostsOptionsWithTimeframe = {},\n metadata: Metadata | undefined\n ): Listing<Post> {\n return this.getSortedPosts(\n {\n ...options,\n sort: 'controversial',\n },\n metadata\n );\n }\n\n /** @internal */\n static getTopPosts(\n options: GetPostsOptionsWithTimeframe = {},\n metadata: Metadata | undefined\n ): Listing<Post> {\n return this.getSortedPosts(\n {\n ...options,\n sort: 'top',\n },\n metadata\n );\n }\n\n /** @internal */\n static getSortedPosts(\n options: GetSortedPostsOptions,\n metadata: Metadata | undefined\n ): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.Sort(\n {\n show: 'all',\n sort: options.sort,\n t: options.timeframe,\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getHotPosts(\n options: GetHotPostsOptions = {\n location: 'GLOBAL',\n },\n metadata: Metadata | undefined\n ): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.Hot(\n {\n g: options.location,\n show: 'all',\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getNewPosts(options: GetPostsOptions, metadata: Metadata | undefined): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.New(\n {\n show: 'all',\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getRisingPosts(options: GetPostsOptions, metadata: Metadata | undefined): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.Rising(\n {\n show: 'all',\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getPostsByUser(\n options: GetPostsByUserOptions,\n metadata: Metadata | undefined\n ): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Users;\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n async fetch(fetchOptions) {\n const response = await client.UserWhere(\n {\n username: options.username,\n where: 'submitted',\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n}\n\nfunction listingProtosToPosts(\n listingProto: ListingProto,\n metadata: Metadata | undefined\n): ListingFetchResponse<Post> {\n if (!listingProto.data?.children) {\n throw new Error('Listing response is missing children');\n }\n\n const children = listingProto.data.children.map((child) => new Post(child.data!, metadata));\n\n return {\n children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n\n/** @internal */\nasync function getThumbnailV2(\n options: { id: T3ID },\n metadata: Metadata | undefined\n): Promise<EnrichedThumbnail | undefined> {\n const operationName = 'GetThumbnailV2';\n const persistedQueryHash = '81580ce4e23d748c5a59a1618489b559bf4518b6a73af41f345d8d074c8b2ce9';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n {\n id: options.id,\n },\n metadata\n );\n\n const thumbnail = response.data?.postInfoById?.thumbnailV2;\n\n if (!thumbnail) {\n throw new Error('Failed to get thumbnail');\n } else if (!thumbnail.image) {\n return undefined;\n }\n\n return {\n attribution: thumbnail.attribution,\n image: {\n url: thumbnail.image.url,\n width: thumbnail.image.dimensions.width,\n height: thumbnail.image.dimensions.height,\n },\n isObfuscatedDefault: thumbnail.isObfuscatedDefault,\n ...(thumbnail.obfuscatedImage && {\n obfuscatedImage: {\n url: thumbnail.obfuscatedImage.url,\n width: thumbnail.obfuscatedImage.dimensions.width,\n height: thumbnail.obfuscatedImage.dimensions.height,\n },\n }),\n };\n}\n", "import { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\n\nimport type { CustomPostRichTextFallback, CustomPostTextFallbackOptions } from '../models/Post.js';\n\n/** @internal */\nexport const getCustomPostRichTextFallback = (\n textFallbackOptions: CustomPostTextFallbackOptions\n): string =>\n 'text' in textFallbackOptions\n ? textFallbackOptions.text\n : richTextToTextFallbackString(textFallbackOptions.richtext);\n\nconst richTextToTextFallbackString = (textFallback: CustomPostRichTextFallback): string => {\n if (textFallback instanceof RichTextBuilder) {\n return textFallback.build();\n } else if (typeof textFallback === 'object') {\n return JSON.stringify(textFallback);\n }\n\n return textFallback;\n};\n", "import type {\n Listing as ListingProto,\n Metadata,\n User as UserProto,\n UserDataByAccountIdsResponse,\n UserDataByAccountIdsResponse_UserAccountData,\n} from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { T2ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, isT2ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { formatModeratorPermissions, validModPermissions } from '../helpers/permissions.js';\nimport type { GetCommentsByUserOptions } from './Comment.js';\nimport { Comment } from './Comment.js';\nimport type { UserFlair } from './Flair.js';\nimport { convertUserFlairProtoToAPI, Flair } from './Flair.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type { GetPostsByUserOptions } from './Post.js';\nimport { Post } from './Post.js';\n\nexport type GetSubredditUsersByTypeOptions = ListingFetchOptions & {\n subredditName: string;\n type: 'banned' | 'muted' | 'wikibanned' | 'contributors' | 'wikicontributors' | 'moderators';\n username?: string;\n};\n\nexport type RelationshipType =\n | 'moderator_invite'\n | 'contributor'\n | 'banned'\n | 'muted'\n | 'wikibanned'\n | 'wikicontributor';\n\nexport type ModeratorPermission =\n | 'all'\n | 'wiki'\n | 'posts'\n | 'access'\n | 'mail'\n | 'config'\n | 'flair'\n | 'chat_operator'\n | 'chat_config'\n | 'channels'\n | 'community_chat';\n\nexport type CreateRelationshipOptions = {\n subredditName: string;\n username: string;\n type: RelationshipType;\n /** The ID of the post or comment that caused the ban. */\n banContext?: string;\n banMessage?: string;\n banReason?: string;\n duration?: number;\n note?: string;\n permissions?: ModeratorPermission[];\n};\n\nexport type RemoveRelationshipOptions = {\n subredditName: string;\n username: string;\n type: RelationshipType | 'moderator';\n};\n\nexport type BanUserOptions = {\n username: string;\n subredditName: string;\n context?: string;\n message?: string;\n reason?: string;\n duration?: number;\n note?: string;\n};\n\nexport type BanWikiContributorOptions = {\n username: string;\n subredditName: string;\n reason?: string;\n duration?: number;\n note?: string;\n};\n\nexport type GetUserOverviewOptions = {\n username: string;\n sort?: 'hot' | 'new' | 'top' | 'controversial';\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n pageSize?: number;\n limit?: number;\n after?: string;\n before?: string;\n};\n\nexport const enum SocialLinkType {\n Custom = 'CUSTOM',\n Reddit = 'REDDIT',\n Instagram = 'INSTAGRAM',\n Twitter = 'TWITTER',\n Tiktok = 'TIKTOK',\n Twitch = 'TWITCH',\n Facebook = 'FACEBOOK',\n Youtube = 'YOUTUBE',\n Tumblr = 'TUMBLR',\n Spotify = 'SPOTIFY',\n Soundcloud = 'SOUNDCLOUD',\n Beacons = 'BEACONS',\n Linktree = 'LINKTREE',\n Discord = 'DISCORD',\n Venmo = 'VENMO',\n CashApp = 'CASH_APP',\n Patreon = 'PATREON',\n Kofi = 'KOFI',\n Paypal = 'PAYPAL',\n Cameo = 'CAMEO',\n Onlyfans = 'ONLYFANS',\n Substack = 'SUBSTACK',\n Kickstarter = 'KICKSTARTER',\n Indiegogo = 'INDIEGOGO',\n BuyMeACoffee = 'BUY_ME_A_COFFEE',\n Shopify = 'SHOPIFY',\n}\n\n/**\n * @field id: ID of the social link.\n *\n * @field handle: Display name of social media link.\n *\n * @field outboundUrl: Outbound url of social media link.\n *\n * @field type: Type of social media link i.e. Instagram, YouTube.\n *\n * @field title: Title or name of social media link.\n */\nexport type UserSocialLink = {\n id: string;\n handle?: string;\n outboundUrl: string;\n type: SocialLinkType;\n title: string;\n};\n\n/**\n * @internal\n */\ntype UserSocialLinkResponse = Omit<UserSocialLink, 'handle'> & { handle: string | null };\n\n/**\n * A class representing a user.\n */\nexport class User {\n #id: T2ID;\n #username: string;\n #createdAt: Date;\n #linkKarma: number;\n #commentKarma: number;\n #nsfw: boolean;\n #isAdmin: boolean;\n #modPermissionsBySubreddit: Map<string, ModeratorPermission[]> = new Map();\n // R2 bug: user.url is a permalink path\n #url: string;\n // R2 bug: user object does not contain a permalink field\n #permalink: string;\n #hasVerifiedEmail: boolean;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(\n data: UserProto & { modPermissions?: { [subredditName: string]: string[] } },\n metadata: Metadata | undefined\n ) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'User ID is missing or undefined');\n assertNonNull(data.name, 'Username is missing or undefined');\n assertNonNull(data.createdUtc, 'User is missing created date');\n\n // UserDataByAccountIds returns the ID without the t2_ prefix\n this.#id = asT2ID(isT2ID(data.id) ? data.id : `t2_${data.id}`);\n this.#username = data.name;\n this.#nsfw = data.over18 ?? false;\n this.#isAdmin = data.isEmployee ?? false;\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#linkKarma = data.linkKarma ?? 0;\n this.#commentKarma = data.commentKarma ?? 0;\n\n if (data.modPermissions) {\n for (const [subredditName, permissions] of Object.entries(data.modPermissions)) {\n this.#modPermissionsBySubreddit.set(subredditName, validModPermissions(permissions));\n }\n }\n\n this.#url = new URL(data.subreddit?.url ?? '', 'https://www.reddit.com').toString();\n this.#permalink = data.subreddit?.url ?? '';\n this.#hasVerifiedEmail = data.hasVerifiedEmail ?? false;\n\n this.#metadata = metadata;\n }\n\n /**\n * The ID (starting with t2_) of the user to retrieve.\n * @example 't2_1w72'\n */\n get id(): T2ID {\n return this.#id;\n }\n\n /**\n * The username of the user omitting the u/.\n * @example 'spez'\n */\n get username(): string {\n return this.#username;\n }\n\n /**\n * The date the user was created.\n */\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n /**\n * The amount of link karma the user has.\n */\n get linkKarma(): number {\n return this.#linkKarma;\n }\n\n /**\n * The amount of comment karma the user has.\n */\n get commentKarma(): number {\n return this.#commentKarma;\n }\n\n /**\n * Whether the user's profile is marked as NSFW (Not Safe For Work).\n */\n get nsfw(): boolean {\n return this.#nsfw;\n }\n\n /**\n * Whether the user is admin.\n */\n get isAdmin(): boolean {\n return this.#isAdmin;\n }\n\n /**\n * The permissions the user has on the subreddit.\n */\n get modPermissions(): Map<string, ModeratorPermission[]> {\n return this.#modPermissionsBySubreddit;\n }\n\n /**\n * Returns the HTTP URL for the user\n */\n get url(): string {\n return this.#url;\n }\n\n /**\n * Returns a permalink path relative to https://www.reddit.com\n */\n get permalink(): string {\n return this.#permalink;\n }\n\n /**\n * Indicates whether or not the user has verified their email address.\n */\n get hasVerifiedEmail(): boolean {\n return this.#hasVerifiedEmail;\n }\n\n toJSON(): Pick<User, 'id' | 'username' | 'createdAt' | 'linkKarma' | 'commentKarma' | 'nsfw'> & {\n modPermissionsBySubreddit: Record<string, ModeratorPermission[]>;\n } {\n return {\n id: this.id,\n username: this.username,\n createdAt: this.createdAt,\n linkKarma: this.linkKarma,\n commentKarma: this.commentKarma,\n nsfw: this.nsfw,\n modPermissionsBySubreddit: Object.fromEntries(this.modPermissions),\n };\n }\n\n /**\n * Get the mod permissions the user has on the subreddit if they are a moderator.\n *\n * @param subredditName - name of the subreddit\n * @returns the moderator permissions the user has on the subreddit\n */\n async getModPermissionsForSubreddit(subredditName: string): Promise<ModeratorPermission[]> {\n if (this.#modPermissionsBySubreddit.has(subredditName)) {\n return this.#modPermissionsBySubreddit.get(subredditName)!;\n }\n\n const mods = await User.getSubredditUsersByType(\n {\n subredditName,\n type: 'moderators',\n username: this.username,\n },\n this.#metadata\n ).all();\n\n if (mods.length === 0) {\n return [];\n }\n\n const permissions = mods[0].modPermissions.get(subredditName) ?? [];\n\n if (permissions.length > 0) {\n this.#modPermissionsBySubreddit.set(subredditName, permissions);\n }\n\n return permissions;\n }\n\n /**\n * Get the user's comments.\n *\n * @param options - Options for the request\n * @param options.sort - The sort order of the comments. e.g. 'new'\n * @param options.timeframe - The timeframe of the comments. e.g. 'all'\n * @param options.limit - The maximum number of comments to return. e.g. 1000\n * @param options.pageSize - The number of comments to return per request. e.g. 100\n * @returns A Listing of Comment objects.\n */\n getComments(options: Omit<GetCommentsByUserOptions, 'username'>): Listing<Comment> {\n return Comment.getCommentsByUser(\n {\n username: this.username,\n ...options,\n },\n this.#metadata\n );\n }\n\n /**\n * Get the user's posts.\n *\n * @param options - Options for the request\n * @param options.sort - The sort order of the posts. e.g. 'new'\n * @param options.timeframe - The timeframe of the posts. e.g. 'all'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n */\n getPosts(options: Omit<GetPostsByUserOptions, 'username'>): Listing<Post> {\n return Post.getPostsByUser(\n {\n username: this.username,\n ...options,\n },\n this.#metadata\n );\n }\n\n /**\n * Retrieve the user's flair for the subreddit.\n *\n * @param subreddit - The name of the subreddit associated with the user's flair.\n *\n * @example\n * ```ts\n * const username = \"badapple\"\n * const subredditName = \"mysubreddit\"\n * const user = await reddit.getUserByUsername(username);\n * const userFlair = await user.getUserFlairBySubreddit(subredditName);\n * ```\n */\n async getUserFlairBySubreddit(subreddit: string): Promise<UserFlair | undefined> {\n const userFlairs = await Flair.getUserFlairBySubreddit(\n {\n subreddit,\n name: this.#username,\n },\n this.#metadata\n );\n return userFlairs.users[0] ? convertUserFlairProtoToAPI(userFlairs.users[0]) : undefined;\n }\n\n getSnoovatarUrl(): Promise<string | undefined> {\n return User.getSnoovatarUrl(this.username, this.#metadata);\n }\n\n /**\n * Gets social links of the user\n *\n * @returns A Promise that resolves an Array of UserSocialLink objects\n * @example\n * ```ts\n * const socialLinks = await user.getSocialLinks();\n * ```\n */\n async getSocialLinks(): Promise<UserSocialLink[]> {\n const operationName = 'GetUserSocialLinks';\n const persistedQueryHash = '2aca18ef5f4fc75fb91cdaace3e9aeeae2cb3843b5c26ad511e6f01b8521593a';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { name: this.username },\n this.#metadata\n );\n\n if (!response.data?.user?.profile?.socialLinks) {\n return [];\n }\n\n return response.data.user.profile.socialLinks.map((link: UserSocialLinkResponse) => ({\n ...link,\n handle: link.handle ?? undefined,\n }));\n }\n\n /** @internal */\n static async getById(id: T2ID, metadata: Metadata | undefined): Promise<User | undefined> {\n const username = await getUsernameById(id, metadata);\n\n return username == null ? undefined : User.getByUsername(username, metadata);\n }\n\n /** @internal */\n static async getByUsername(\n username: string,\n metadata: Metadata | undefined\n ): Promise<User | undefined> {\n const client = Devvit.redditAPIPlugins.Users;\n try {\n const response = await client.UserAbout({ username }, metadata);\n // suspended accounts 404.\n if (response.data?.id) return new User(response.data, metadata);\n } catch (error) {\n if (error instanceof Error && error.message.includes('404 Not Found')) {\n return undefined;\n }\n throw error;\n }\n }\n\n /** @internal */\n static async getFromMetadata(\n key: string,\n metadata: Metadata | undefined\n ): Promise<User | undefined> {\n assertNonNull(metadata);\n const userId = metadata?.[key]?.values[0];\n return userId ? User.getById(asT2ID(userId), metadata) : Promise.resolve(undefined);\n }\n\n /** @internal */\n static getSubredditUsersByType(\n options: GetSubredditUsersByTypeOptions,\n metadata: Metadata | undefined\n ): Listing<User> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n return new Listing({\n hasMore: true,\n pageSize: options.pageSize,\n limit: options.limit,\n after: options.after,\n before: options.before,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.AboutWhere(\n {\n where: options.type,\n user: options.username,\n subreddit: options.subredditName,\n show: 'all',\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToUsers(response, options.subredditName, metadata);\n },\n });\n }\n\n /** @internal */\n static async createRelationship(\n options: CreateRelationshipOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Users;\n\n const { type, subredditName, username, permissions, ...optionalFields } = options;\n\n const response = await client.Friend(\n {\n type,\n subreddit: subredditName,\n name: username,\n permissions: permissions ? formatModeratorPermissions(permissions) : undefined,\n ...optionalFields,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error(response.json.errors.join('\\n'));\n }\n }\n\n /** @internal */\n static async removeRelationship(\n options: RemoveRelationshipOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Users;\n\n await client.Unfriend(\n {\n type: options.type,\n subreddit: options.subredditName,\n name: options.username,\n },\n metadata\n );\n }\n\n /** @internal */\n static async setModeratorPermissions(\n username: string,\n subredditName: string,\n permissions: ModeratorPermission[],\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Users;\n\n const response = await client.SetPermissions(\n {\n subreddit: subredditName,\n name: username,\n type: 'moderator',\n permissions: formatModeratorPermissions(permissions),\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error(response.json.errors.join('\\n'));\n }\n }\n\n /** @internal */\n static async getSnoovatarUrl(\n username: string,\n metadata: Metadata | undefined\n ): Promise<string | undefined> {\n const operationName = 'GetSnoovatarUrlByName';\n const persistedQueryHash = 'c47fd42345af268616d2d8904b56856acdc05cf61d3650380f539ad7d596ac0c';\n const response = await GraphQL.query(operationName, persistedQueryHash, { username }, metadata);\n return response.data?.redditorInfoByName?.snoovatarIcon?.url;\n }\n\n /** @internal */\n static getOverview(\n options: GetUserOverviewOptions,\n metadata: Metadata | undefined\n ): Listing<Post | Comment> {\n const client = Devvit.redditAPIPlugins.Users;\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n async fetch(fetchOptions) {\n const response = await client.UserWhere(\n {\n username: options.username,\n where: 'overview',\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPostsOrComments(response, metadata);\n },\n });\n }\n}\n\nfunction listingProtosToPostsOrComments(\n listingProto: ListingProto,\n metadata: Metadata | undefined\n): ListingFetchResponse<Post | Comment> {\n if (!listingProto.data?.children) {\n throw new Error('Listing response is missing children');\n }\n\n const children = listingProto.data.children.map((child) => {\n if (child.kind === 't3') {\n return new Post(child.data!, metadata);\n } else if (child.kind === 't1') {\n return new Comment(child.data!, metadata);\n }\n\n throw new Error(`Type ${child.kind} is not supported`);\n });\n\n return {\n children: children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n\nasync function listingProtosToUsers(\n listingProto: ListingProto,\n subredditName: string,\n metadata: Metadata | undefined\n): Promise<ListingFetchResponse<User>> {\n const client = Devvit.redditAPIPlugins.Users;\n\n if (!listingProto.data?.children) {\n throw new Error('Listing response is missing children');\n }\n\n const userIds = listingProto.data.children.map((child) => {\n assertNonNull(child.data?.id, 'User id is still from listing data');\n return child.data.id;\n });\n\n // break the ids into chunks since they're passed over a query parameter\n const chunkSize = 100;\n const userIdChunks = [];\n for (let i = 0; i < userIds.length; i += chunkSize) {\n userIdChunks.push(userIds.slice(i, i + chunkSize));\n }\n\n // perform the requests\n const usersMapResponses: UserDataByAccountIdsResponse[] = await Promise.all(\n userIdChunks.map((userIds) =>\n client.UserDataByAccountIds(\n {\n ids: userIds.join(','),\n },\n metadata\n )\n )\n );\n\n // join the responses back into a single map of user data\n const userDataById: { [key: string]: UserDataByAccountIdsResponse_UserAccountData } =\n usersMapResponses.reduce((allUsers, response) => ({ ...allUsers, ...response.users }), {});\n\n const children = listingProto.data.children.map((child) => {\n const id = child.data?.id;\n assertNonNull(id, 'User id is missing from listing');\n\n const userData = userDataById[id];\n\n // Casting to unknown because Typescript assumes that userData is always defined\n // because of how we defined the UserDataByAccountIdsResponse_UserAccountData protobuf.\n assertNonNull(userData as unknown, 'User data is missing from response');\n\n return new User(\n {\n id,\n name: userData.name,\n linkKarma: userData.linkKarma,\n commentKarma: userData.commentKarma,\n createdUtc: userData.createdUtc,\n over18: userData.profileOver18,\n snoovatarSize: [],\n modPermissions: {\n [subredditName]: child.data?.modPermissions ?? [],\n },\n },\n metadata\n );\n });\n\n return {\n children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n\n/** @internal */\nasync function getUsernameById(\n id: string,\n metadata: Metadata | undefined\n): Promise<string | undefined> {\n const client = Devvit.redditAPIPlugins.Users;\n\n const response = await client.UserDataByAccountIds({ ids: id }, metadata);\n\n return response?.users?.[id]?.name;\n}\n\n/** @internal */\nexport async function getCurrentUsernameFromMetadata(\n metadata: Metadata | undefined\n): Promise<string | undefined> {\n assertNonNull(metadata);\n const username = metadata?.[Header.Username]?.values[0];\n if (username) {\n return username;\n }\n\n const userId = metadata?.[Header.User]?.values[0];\n if (!userId) {\n return undefined;\n }\n\n return getUsernameById(userId, metadata);\n}\n\n/** @internal */\nexport async function getCurrentUserFromMetadata(\n metadata: Metadata | undefined\n): Promise<User | undefined> {\n assertNonNull(metadata);\n const username = metadata?.[Header.Username]?.values[0];\n if (username) {\n return User.getByUsername(username, metadata);\n }\n\n const userId = metadata?.[Header.User]?.values[0];\n if (!userId && isT2ID(userId)) {\n return User.getById(userId, metadata);\n }\n\n return undefined;\n}\n", "import type {\n Comment as CommentProto,\n JsonWrappedComment_WrappedComment,\n Metadata,\n RedditObject,\n WrappedRedditObject,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\nimport type { T1ID, T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT1ID, asT2ID, asT3ID, asT5ID, isCommentId, isT1ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { RunAs } from '../common.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { richtextToString } from '../helpers/richtextToString.js';\nimport type { ListingFetchOptions, ListingFetchResponse, MoreObject } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport { ModNote } from './ModNote.js';\nimport { User } from './User.js';\n\nexport type CommentSort =\n | 'confidence'\n | 'top'\n | 'new'\n | 'controversial'\n | 'old'\n | 'random'\n | 'qa'\n | 'live';\n\nexport type GetCommentsOptions = {\n postId: string;\n commentId?: string | undefined;\n depth?: number;\n pageSize?: number;\n limit?: number;\n sort?: CommentSort;\n};\n\ntype GetCommentsListingOptions = {\n postId: T3ID;\n commentId?: T1ID;\n depth?: number;\n pageSize?: number;\n limit?: number;\n sort?: CommentSort;\n};\n\nexport type CommentSubmissionOptions =\n | {\n text: string;\n runAs?: 'USER' | 'APP';\n }\n | {\n richtext: object | RichTextBuilder;\n runAs?: 'USER' | 'APP';\n };\n\nexport type EditCommentOptions = CommentSubmissionOptions;\nexport type ReplyToCommentOptions = CommentSubmissionOptions;\n\nexport type GetCommentsByUserOptions = {\n username: string;\n sort?: 'hot' | 'new' | 'top' | 'controversial';\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n pageSize?: number;\n limit?: number;\n after?: string;\n before?: string;\n};\n\nexport class Comment {\n #id: T1ID;\n #authorId?: T2ID;\n #authorName: string;\n #body: string;\n #createdAt: Date;\n #parentId: T1ID | T3ID;\n #postId: T3ID;\n #subredditId: T5ID;\n #subredditName: string;\n #replies: Listing<Comment>;\n #approved: boolean;\n #approvedAtUtc: number;\n #bannedAtUtc: number;\n #edited: boolean;\n #locked: boolean;\n #removed: boolean;\n #stickied: boolean;\n #spam: boolean;\n #distinguishedBy?: string;\n #numReports: number;\n #collapsedBecauseCrowdControl: boolean;\n #score: number;\n #permalink: string;\n #modReportReasons: string[];\n #userReportReasons: string[];\n #url: string;\n #ignoringReports: boolean;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: RedditObject | CommentProto, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'Comment id is null or undefined');\n assertNonNull(data.body, 'Comment body is null or undefined');\n assertNonNull(data.createdUtc, 'Comment is missing created date');\n assertNonNull(data.author, 'Comment author is null or undefined');\n assertNonNull(data.parentId, 'Comment parentId is null or undefined');\n assertNonNull(data.linkId, 'Comment linkId is null or undefined');\n assertNonNull(data.permalink, 'Comment permalink is null or undefined');\n assertNonNull(data.subreddit, 'Comment is missing subreddit name');\n assertNonNull(data.subredditId, 'Comment is missing subreddit id');\n\n this.#id = asT1ID(`t1_${data.id}`);\n this.#authorId = data.authorFullname ? asT2ID(data.authorFullname) : undefined;\n this.#authorName = data.author;\n this.#body = data.body;\n this.#subredditId = asT5ID(data.subredditId);\n this.#subredditName = data.subreddit;\n this.#parentId = isCommentId(data.parentId) ? asT1ID(data.parentId) : asT3ID(data.parentId);\n this.#postId = asT3ID(data.linkId);\n this.#edited = data.edited ?? false;\n this.#locked = data.locked ?? false;\n this.#removed = data.removed ?? false;\n this.#stickied = data.stickied ?? false;\n this.#approved = data.approved ?? false;\n this.#approvedAtUtc = data.approvedAtUtc ?? 0;\n this.#bannedAtUtc = data.bannedAtUtc ?? 0;\n this.#spam = data.spam ?? false;\n this.#distinguishedBy = data.distinguished;\n this.#numReports = data.numReports ?? 0;\n this.#collapsedBecauseCrowdControl = data.collapsedBecauseCrowdControl ?? false;\n this.#score = data.score ?? 0;\n this.#permalink = data.permalink;\n // R2 API does not include a URL for a comment, just a permalink\n this.#url = new URL(data.permalink ?? '', 'https://www.reddit.com/').toString();\n this.#ignoringReports = data.ignoreReports ?? false;\n\n this.#modReportReasons = ((data.modReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n this.#userReportReasons = ((data.userReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#replies = Comment.#getCommentsListing(\n {\n postId: this.#postId,\n commentId: this.#id,\n },\n metadata\n );\n\n this.#metadata = metadata;\n }\n\n get id(): T1ID {\n return this.#id;\n }\n\n get authorId(): T2ID | undefined {\n return this.#authorId;\n }\n\n get authorName(): string {\n return this.#authorName;\n }\n\n get subredditId(): T5ID {\n return this.#subredditId;\n }\n\n get subredditName(): string {\n return this.#subredditName;\n }\n\n get body(): string {\n return this.#body;\n }\n\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n get parentId(): T1ID | T3ID {\n return this.#parentId;\n }\n\n get postId(): T3ID {\n return this.#postId;\n }\n\n get replies(): Listing<Comment> {\n return this.#replies;\n }\n\n get distinguishedBy(): string | undefined {\n return this.#distinguishedBy;\n }\n\n get locked(): boolean {\n return this.#locked;\n }\n\n get stickied(): boolean {\n return this.#stickied;\n }\n\n get removed(): boolean {\n return this.#removed;\n }\n\n get approved(): boolean {\n return this.#approved;\n }\n\n get approvedAtUtc(): number {\n return this.#approvedAtUtc;\n }\n\n get bannedAtUtc(): number {\n return this.#bannedAtUtc;\n }\n\n get spam(): boolean {\n return this.#spam;\n }\n\n get edited(): boolean {\n return this.#edited;\n }\n\n get numReports(): number {\n return this.#numReports;\n }\n\n get collapsedBecauseCrowdControl(): boolean {\n return this.#collapsedBecauseCrowdControl;\n }\n\n get score(): number {\n return this.#score;\n }\n\n get permalink(): string {\n return this.#permalink;\n }\n\n get userReportReasons(): string[] {\n return this.#userReportReasons;\n }\n\n get modReportReasons(): string[] {\n return this.#modReportReasons;\n }\n\n get url(): string {\n return this.#url;\n }\n\n get ignoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n toJSON(): Pick<\n Comment,\n | 'id'\n | 'authorName'\n | 'subredditId'\n | 'subredditName'\n | 'body'\n | 'createdAt'\n | 'parentId'\n | 'postId'\n | 'replies'\n | 'approved'\n | 'locked'\n | 'removed'\n | 'stickied'\n | 'spam'\n | 'edited'\n | 'distinguishedBy'\n | 'numReports'\n | 'collapsedBecauseCrowdControl'\n | 'score'\n | 'permalink'\n | 'userReportReasons'\n | 'modReportReasons'\n | 'url'\n | 'ignoringReports'\n > {\n return {\n id: this.id,\n authorName: this.authorName,\n subredditId: this.subredditId,\n subredditName: this.subredditName,\n body: this.body,\n createdAt: this.createdAt,\n parentId: this.parentId,\n postId: this.postId,\n replies: this.replies,\n approved: this.approved,\n locked: this.locked,\n removed: this.removed,\n stickied: this.stickied,\n spam: this.spam,\n edited: this.edited,\n distinguishedBy: this.distinguishedBy,\n numReports: this.numReports,\n collapsedBecauseCrowdControl: this.collapsedBecauseCrowdControl,\n score: this.score,\n permalink: this.permalink,\n modReportReasons: this.modReportReasons,\n userReportReasons: this.userReportReasons,\n url: this.url,\n ignoringReports: this.ignoringReports,\n };\n }\n\n isLocked(): boolean {\n return this.#locked;\n }\n\n isApproved(): boolean {\n return this.#approved;\n }\n\n isRemoved(): boolean {\n return this.#removed;\n }\n\n isSpam(): boolean {\n return this.#spam;\n }\n\n isStickied(): boolean {\n return this.#stickied;\n }\n\n isDistinguished(): boolean {\n return Boolean(this.#distinguishedBy);\n }\n\n isEdited(): boolean {\n return this.#edited;\n }\n\n isIgnoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n async delete(): Promise<void> {\n return Comment.delete(this.id, this.#metadata);\n }\n\n async edit(options: EditCommentOptions): Promise<this> {\n const newComment = await Comment.edit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n\n this.#body = newComment.body;\n this.#edited = newComment.edited;\n\n return this;\n }\n\n async approve(): Promise<void> {\n await Comment.approve(this.id, this.#metadata);\n this.#approved = true;\n this.#removed = false;\n }\n\n async remove(isSpam: boolean = false): Promise<void> {\n await Comment.remove(this.id, isSpam, this.#metadata);\n this.#removed = true;\n this.#spam = isSpam;\n this.#approved = false;\n }\n\n async lock(): Promise<void> {\n await Comment.lock(this.id, this.#metadata);\n this.#locked = true;\n }\n\n async unlock(): Promise<void> {\n await Comment.unlock(this.id, this.#metadata);\n this.#locked = false;\n }\n\n async reply(options: ReplyToCommentOptions): Promise<Comment> {\n return Comment.submit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n }\n\n async getAuthor(): Promise<User | undefined> {\n return User.getByUsername(this.#authorName, this.#metadata);\n }\n\n async distinguish(makeSticky: boolean = false): Promise<void> {\n const { distinguishedBy, stickied } = await Comment.distinguish(\n this.id,\n makeSticky,\n false,\n this.#metadata\n );\n this.#distinguishedBy = distinguishedBy;\n this.#stickied = stickied;\n }\n\n async distinguishAsAdmin(makeSticky: boolean = false): Promise<void> {\n const { distinguishedBy, stickied } = await Comment.distinguish(\n this.id,\n makeSticky,\n true,\n this.#metadata\n );\n this.#distinguishedBy = distinguishedBy;\n this.#stickied = stickied;\n }\n\n async undistinguish(): Promise<void> {\n const { distinguishedBy, stickied } = await Comment.undistinguish(this.id, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n this.#stickied = stickied;\n }\n\n async ignoreReports(): Promise<void> {\n await Comment.ignoreReports(this.id, this.#metadata);\n this.#ignoringReports = true;\n }\n\n async unignoreReports(): Promise<void> {\n await Comment.unignoreReports(this.id, this.#metadata);\n this.#ignoringReports = false;\n }\n\n /**\n * Add a mod note for why the comment was removed\n *\n * @param options.reasonId id of a Removal Reason - you can leave this as an empty string if you don't have one\n * @param options.modNote the reason for removal (maximum 100 characters) (optional)\n * @returns\n */\n addRemovalNote(options: { reasonId: string; modNote?: string }): Promise<void> {\n return ModNote.addRemovalNote({ itemIds: [this.#id], ...options }, this.#metadata);\n }\n\n /** @internal */\n static async getById(id: T1ID, metadata: Metadata | undefined): Promise<Comment> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const commentId: T1ID = isT1ID(id) ? id : `t1_${id}`;\n\n const response = await client.Info(\n {\n subreddits: [],\n thingIds: [commentId],\n },\n metadata\n );\n\n if (!response.data?.children?.[0]?.data) {\n throw new Error('not found');\n }\n\n return new Comment(response.data.children[0].data, metadata);\n }\n\n /** @internal */\n static getComments(\n options: GetCommentsOptions,\n metadata: Metadata | undefined\n ): Listing<Comment> {\n const { postId, commentId, ...rest } = options;\n return Comment.#getCommentsListing(\n {\n postId: asT3ID(postId),\n commentId: commentId ? asT1ID(commentId) : undefined,\n ...rest,\n },\n metadata\n );\n }\n\n /** @internal */\n static async edit(\n options: CommentSubmissionOptions & { id: T1ID },\n metadata: Metadata | undefined\n ): Promise<Comment> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const { id } = options;\n\n let richtextString: string | undefined;\n if ('richtext' in options) {\n richtextString = richtextToString(options.richtext);\n }\n\n const response = await client.EditUserText(\n {\n thingId: id,\n text: 'text' in options ? options.text : '',\n richtextJson: richtextString,\n runAs: RunAs.APP,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error('Failed to edit comment');\n }\n\n const comment = response.json?.data?.things?.[0]?.data;\n assertNonNull(comment);\n\n return new Comment(comment, metadata);\n }\n\n /** @internal */\n static async delete(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Del(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async approve(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Approve(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async remove(\n id: T1ID,\n isSpam: boolean = false,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Remove(\n {\n id,\n spam: isSpam,\n },\n metadata\n );\n }\n\n /** @internal */\n static async lock(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Lock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unlock(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unlock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async submit(\n options: CommentSubmissionOptions & { id: T1ID | T3ID },\n metadata: Metadata | undefined\n ): Promise<Comment> {\n const { runAs = 'APP' } = options;\n const runAsType = RunAs[runAs];\n const client =\n runAsType === RunAs.USER\n ? Devvit.userActionsPlugin\n : Devvit.redditAPIPlugins.LinksAndComments;\n const { id } = options;\n\n let richtextString: string | undefined;\n if ('richtext' in options) {\n richtextString = richtextToString(options.richtext);\n }\n\n const response = await client.Comment(\n {\n thingId: id,\n text: 'text' in options ? options.text : '',\n richtextJson: richtextString,\n runAs: runAsType,\n },\n metadata\n );\n\n // TODO: figure out a better errors to throw\n if (response.json?.errors?.length) {\n throw new Error('failed to reply to comment');\n }\n\n const data = response.json?.data?.things?.[0]?.data;\n assertNonNull(data);\n\n return new Comment(data, metadata);\n }\n\n /** @internal */\n static async distinguish(\n id: T1ID,\n sticky: boolean,\n asAdmin: boolean,\n metadata: Metadata | undefined\n ): Promise<{\n distinguishedBy: string | undefined;\n stickied: boolean;\n }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: asAdmin ? 'admin' : 'yes',\n sticky,\n },\n metadata\n );\n\n const comment = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(comment);\n\n return {\n distinguishedBy: comment.distinguished,\n stickied: Boolean(comment.stickied),\n };\n }\n\n /** @internal */\n static async undistinguish(\n id: T1ID,\n metadata: Metadata | undefined\n ): Promise<{\n distinguishedBy: string | undefined;\n stickied: boolean;\n }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: 'no',\n sticky: false,\n },\n metadata\n );\n\n const comment = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(comment);\n\n return {\n distinguishedBy: comment.distinguished,\n stickied: Boolean(comment.stickied),\n };\n }\n\n /** @internal */\n static getCommentsByUser(\n options: GetCommentsByUserOptions,\n metadata: Metadata | undefined\n ): Listing<Comment> {\n const client = Devvit.redditAPIPlugins.Users;\n return new Listing<Comment>({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n async fetch(fetchOptions) {\n const response = await client.UserWhere(\n {\n username: options.username,\n where: 'comments',\n ...fetchOptions,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to get comments for user');\n\n const children =\n response.data.children?.map((child) => new Comment(child.data!, metadata)) || [];\n\n return {\n children,\n before: response.data.before,\n after: response.data.after,\n };\n },\n });\n }\n\n /** @internal */\n static async ignoreReports(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.IgnoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unignoreReports(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.UnignoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n static #getCommentsListing(\n options: GetCommentsListingOptions,\n metadata: Metadata | undefined,\n depthOffset = 0\n ): Listing<Comment> {\n return new Listing<Comment>({\n limit: options.limit,\n pageSize: options.pageSize,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n let limit = fetchOptions.limit;\n\n const listingsClient = Devvit.redditAPIPlugins.Listings;\n const linksAndCommentsClient = Devvit.redditAPIPlugins.LinksAndComments;\n let commentId = options.commentId;\n\n if (fetchOptions.more) {\n if (fetchOptions.more.children.length) {\n const more = fetchOptions.more;\n\n // The maximum page size for MoreChildren is 100\n if (!limit || limit > 100) {\n limit = 100;\n }\n\n const moreIds = more.children.splice(0, limit);\n\n const response = await linksAndCommentsClient.MoreChildren(\n {\n linkId: options.postId,\n children: moreIds,\n sort: options.sort,\n },\n metadata\n );\n\n if (!response.json?.data?.things?.length) {\n return { children: [] };\n }\n\n const { children } = Comment.#buildCommentsTree(\n response.json.data.things,\n options.postId,\n options,\n metadata\n );\n\n return { children, more: more.children.length ? more : undefined };\n } else {\n // parentId is only ever T3 for the MoreChildren case.\n commentId = fetchOptions.more.parentId as T1ID;\n depthOffset = depthOffset + fetchOptions.more.depth;\n }\n }\n\n const response = await listingsClient.Comments(\n {\n article: options.postId.substring(3),\n comment: commentId?.substring(3),\n limit,\n depth: options.depth,\n sort: options.sort,\n },\n metadata\n );\n\n // The first item of `response.listings` is always the post (t3) listing\n // and the second item is the comments (t1) listing.\n let responseChildren = response.listings?.[1]?.data?.children ?? [];\n\n const topLevelComment = responseChildren[0];\n if (commentId && topLevelComment?.data?.replyList?.data) {\n responseChildren = topLevelComment.data.replyList.data.children;\n }\n\n return Comment.#buildCommentsTree(\n responseChildren,\n commentId ?? options.postId,\n options,\n metadata,\n depthOffset\n );\n },\n });\n }\n\n static #buildCommentsTree(\n redditObjects: WrappedRedditObject[] | JsonWrappedComment_WrappedComment[],\n parentId: string,\n options: GetCommentsOptions,\n metadata: Metadata | undefined,\n depthOffset: number = 0\n ): ListingFetchResponse<Comment> {\n const children: Comment[] = [];\n let more: MoreObject | undefined;\n\n // Map of comments to help set parent-child relationship between comments returned by MoreChildren.\n const commentsMap: { [id: string]: Comment } = {};\n\n for (const child of redditObjects) {\n if (!child.data) {\n continue;\n }\n\n if (child.data.depth != null) {\n child.data.depth = child.data.depth + depthOffset;\n }\n\n // Prevent returning comments that are beyond the maximum depth requested.\n if (child.data.depth != null && options.depth != null && child.data.depth >= options.depth) {\n continue;\n }\n\n const parentComment = child.data.parentId ? commentsMap[child.data.parentId] : undefined;\n\n if (child.kind === 't1') {\n // Sometimes MoreChildren API returns a comment that has already been seen.\n if (child.data.name === parentId) {\n continue;\n }\n\n const comment = new Comment(child.data, metadata);\n\n commentsMap[comment.id] = comment;\n\n comment.#replies = Comment.#getCommentsListing(\n {\n ...options,\n postId: comment.postId,\n commentId: comment.id,\n },\n metadata,\n depthOffset\n );\n\n // Preload the comment's replies Listing\n if ('replyList' in child.data && child.data.replyList?.data) {\n const { children, more } = Comment.#buildCommentsTree(\n child.data.replyList.data.children,\n comment.id,\n options,\n metadata,\n depthOffset\n );\n\n if (children.length) {\n comment.replies.children.push(...children);\n }\n\n if (more) {\n comment.replies.setMore(more);\n }\n }\n\n // Since the replies for this comment were already load we can skip the first fetch call\n comment.replies.preventInitialFetch();\n\n if (parentComment) {\n parentComment.replies.children.push(comment);\n } else {\n children.push(comment);\n }\n } else if (child.kind === 'more' && child.data.parentId && child.data.depth != null) {\n const thisMore = {\n parentId: isCommentId(child.data.parentId)\n ? asT1ID(child.data.parentId)\n : asT3ID(child.data.parentId),\n children: child.data.children ?? [],\n depth: child.data.depth,\n };\n\n if (parentComment) {\n parentComment.replies.setMore(thisMore);\n } else if (thisMore.parentId === parentId) {\n more = thisMore;\n }\n }\n }\n\n return { children, more };\n }\n}\n", "import type { AboutLogResponse, Metadata } from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\n\nexport type ModActionTarget = {\n id: string;\n author?: string;\n body?: string;\n permalink?: string;\n title?: string;\n};\n\nexport interface ModAction {\n id: string;\n type: ModActionType;\n moderatorName: string;\n moderatorId: string;\n createdAt: Date;\n subredditName: string;\n subredditId: string;\n description?: string;\n details?: string;\n target?: ModActionTarget;\n}\n\nexport type ModActionType =\n | 'banuser'\n | 'unbanuser'\n | 'spamlink'\n | 'removelink'\n | 'approvelink'\n | 'spamcomment'\n | 'removecomment'\n | 'approvecomment'\n | 'addmoderator'\n | 'showcomment'\n | 'invitemoderator'\n | 'uninvitemoderator'\n | 'acceptmoderatorinvite'\n | 'removemoderator'\n | 'addcontributor'\n | 'removecontributor'\n | 'editsettings'\n | 'editflair'\n | 'distinguish'\n | 'marknsfw'\n | 'wikibanned'\n | 'wikicontributor'\n | 'wikiunbanned'\n | 'wikipagelisted'\n | 'removewikicontributor'\n | 'wikirevise'\n | 'wikipermlevel'\n | 'ignorereports'\n | 'unignorereports'\n | 'setpermissions'\n | 'setsuggestedsort'\n | 'sticky'\n | 'unsticky'\n | 'setcontestmode'\n | 'unsetcontestmode'\n | 'lock'\n | 'unlock'\n | 'muteuser'\n | 'unmuteuser'\n | 'createrule'\n | 'editrule'\n | 'reorderrules'\n | 'deleterule'\n | 'spoiler'\n | 'unspoiler'\n | 'modmail_enrollment'\n | 'community_styling'\n | 'community_widgets'\n | 'markoriginalcontent'\n | 'collections'\n | 'events'\n | 'create_award'\n | 'disable_award'\n | 'delete_award'\n | 'enable_award'\n | 'mod_award_given'\n | 'hidden_award'\n | 'add_community_topics'\n | 'remove_community_topics'\n | 'create_scheduled_post'\n | 'edit_scheduled_post'\n | 'delete_scheduled_post'\n | 'submit_scheduled_post'\n | 'edit_post_requirements'\n | 'invitesubscriber'\n | 'submit_content_rating_survey'\n | 'adjust_post_crowd_control_level'\n | 'enable_post_crowd_control_filter'\n | 'disable_post_crowd_control_filter'\n | 'deleteoverriddenclassification'\n | 'overrideclassification'\n | 'reordermoderators'\n | 'snoozereports'\n | 'unsnoozereports'\n | 'addnote'\n | 'deletenote'\n | 'addremovalreason'\n | 'createremovalreason'\n | 'updateremovalreason'\n | 'deleteremovalreason'\n | 'reorderremovalreason'\n | 'dev_platform_app_changed'\n | 'dev_platform_app_disabled'\n | 'dev_platform_app_enabled'\n | 'dev_platform_app_installed'\n | 'dev_platform_app_uninstalled';\n\nexport type GetModerationLogOptions = ListingFetchOptions & {\n /** Subreddit name */\n subredditName: string;\n /** (optional) A moderator filter. Accepts an array of usernames */\n moderatorUsernames?: string[];\n /** Type of the Moderator action */\n type?: ModActionType;\n};\n\n/** @internal */\nexport function _getModerationLog(\n options: GetModerationLogOptions,\n metadata: Metadata | undefined\n): Listing<ModAction> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n return new Listing({\n hasMore: true,\n after: options.after,\n before: options.before,\n limit: options.limit,\n pageSize: options.pageSize,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.AboutLog(\n {\n subreddit: options.subredditName,\n mod: options.moderatorUsernames ? options.moderatorUsernames.join(',') : undefined,\n type: options.type,\n ...fetchOptions,\n },\n metadata\n );\n\n return aboutLogResponseToModActions(response);\n },\n });\n}\n\nfunction aboutLogResponseToModActions(response: AboutLogResponse): ListingFetchResponse<ModAction> {\n if (!response.data?.children) {\n throw new Error('AboutLogResponse is missing children');\n }\n\n const children = response.data.children.map((child) => {\n if (!child.data) {\n throw new Error('ModAction from AboutLogResponse is missing or invalid');\n }\n\n const {\n id,\n mod,\n modId36,\n createdUtc,\n subreddit,\n subredditNamePrefixed,\n action,\n srId36,\n description,\n details,\n targetAuthor,\n targetBody,\n targetFullname,\n targetPermalink,\n targetTitle,\n } = child.data;\n\n assertNonNull(id, 'ModAction from AboutLogResponse is missing id');\n assertNonNull(mod, 'ModAction from AboutLogResponse is missing mod');\n assertNonNull(modId36, 'ModAction from AboutLogResponse is missing modId36');\n assertNonNull(createdUtc, 'ModAction from AboutLogResponse is missing createdUtc');\n assertNonNull(subreddit, 'ModAction from AboutLogResponse is missing subreddit');\n assertNonNull(\n subredditNamePrefixed,\n 'ModAction from AboutLogResponse is missing subredditNamePrefixed'\n );\n assertNonNull(action, 'ModAction from AboutLogResponse is missing action');\n assertNonNull(srId36, 'ModAction from AboutLogResponse is missing srId36');\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(createdUtc);\n\n const modAction: ModAction = {\n id,\n type: action as ModActionType,\n moderatorName: mod,\n moderatorId: `t2_${modId36}`,\n createdAt,\n subredditName: subreddit,\n subredditId: `t5_${srId36}`,\n description,\n details,\n target: targetFullname\n ? {\n id: targetFullname,\n author: targetAuthor,\n body: targetBody,\n permalink: targetPermalink,\n title: targetTitle,\n }\n : undefined,\n };\n\n return modAction;\n });\n\n return {\n children,\n after: response.data.after,\n before: response.data.before,\n };\n}\n", "import {\n type ConversationData as ProtosConversationData,\n type MessageData as ProtosMessageData,\n type Metadata,\n type ModActionData as ProtosModActionData,\n} from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { asT5ID, type T5ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\n\nexport type SubredditData = {\n id?: string;\n name?: string;\n displayName?: string;\n communityIcon?: string;\n keyColor?: string;\n subscribers?: number;\n primaryColor?: string;\n lastUpdated?: string;\n icon?: string;\n};\n\nexport type GetConversationsRequest = {\n /** modmail conversation id */\n after?: string;\n /** array of subreddit names */\n subreddits?: string[];\n /** an integer between 1 and 100 (default: 25) */\n limit?: number;\n /**\n * Sort by:\n * - `recent` - Order by whenever anyone last updated the conversation, mod or participant\n * - `mod` - Order by the last time a mod updated the conversation\n * - `user` - Order by the last time a participant user updated the conversation\n * - `unread` - Order by the most recent unread message in the conversation for this mod\n */\n sort?: 'recent' | 'mod' | 'user' | 'unread';\n /**\n * Filter by conversation state\n *\n * A conversation can be in more than one state.\n * For example, a conversation may be both 'highlighted' and 'inprogress'.\n */\n state?: ConversationStateFilter;\n};\n\n/**\n * A Conversation State is a way in which conversations may be filtered within the UI.\n *\n * A conversation can be in more than one state.\n * For example, a conversation may be both 'highlighted' and 'inprogress'.\n */\nexport type ConversationStateFilter =\n | 'all'\n | 'new'\n | 'inprogress'\n | 'archived'\n | 'appeals'\n | 'join_requests'\n | 'highlighted'\n | 'mod'\n | 'notifications'\n | 'inbox'\n | 'filtered'\n | 'default';\n\n/**\n * Conversation participant\n */\nexport type Participant = {\n isMod?: boolean;\n isAdmin?: boolean;\n name?: string;\n isOp?: boolean;\n isParticipant?: boolean;\n isApproved?: boolean;\n isHidden?: boolean;\n id?: number;\n isDeleted?: boolean;\n};\n\nexport type ConversationUserData = {\n /** User ID*/\n id?: string;\n /** Username */\n name?: string;\n /** Recent comments */\n recentComments: {\n [id: string]: {\n comment?: string;\n date?: string;\n permalink?: string;\n title?: string;\n };\n };\n /** Recent posts */\n recentPosts: {\n [id: string]: {\n date?: string;\n permalink?: string;\n title?: string;\n };\n };\n /** Recent conversations */\n recentConvos: {\n [id: string]: {\n date?: string;\n permalink?: string;\n id?: string;\n subject?: string;\n };\n };\n isSuspended?: boolean;\n isShadowBanned?: boolean;\n muteStatus?: { isMuted?: boolean; muteCount?: number; endDate?: string; reason?: string };\n banStatus?: { isBanned?: boolean; isPermanent?: boolean; endDate?: string; reason?: string };\n approveStatus?: { isApproved?: boolean };\n /** When was created */\n created?: string;\n};\n\nexport enum ModMailConversationState {\n New = 'New',\n InProgress = 'InProgress',\n Archived = 'Archived',\n Appeals = 'Appeals',\n JoinRequests = 'JoinRequests',\n Filtered = 'Filtered',\n}\n\nconst R2_TO_MODMAIL_CONVERSATION_STATE: { [key: number]: ModMailConversationState } = {\n 0: ModMailConversationState.New,\n 1: ModMailConversationState.InProgress,\n 2: ModMailConversationState.Archived,\n 3: ModMailConversationState.Appeals,\n 4: ModMailConversationState.JoinRequests,\n 5: ModMailConversationState.Filtered,\n};\n\n/**\n * An ActionType describes a particular logged action within a conversation. For example,\n * if a mod highlights a conversation, a ModerationAction record with the type `Highlighted`\n * would be created.\n */\nexport enum ModMailActionType {\n Highlighted = 'Highlighted',\n Unhighlighted = 'Unhighlighted',\n Archived = 'Archived',\n Unarchived = 'Unarchived',\n ReportedToAdmins = 'ReportedToAdmins',\n Muted = 'Muted',\n Unmuted = 'Unmuted',\n Banned = 'Banned',\n Unbanned = 'Unbanned',\n Approved = 'Approved',\n Disapproved = 'Disapproved',\n Filtered = 'Filtered',\n Unfiltered = 'Unfiltered',\n}\n\nconst R2_TO_MOD_ACTION_TYPE: { [key: number]: ModMailActionType } = {\n 0: ModMailActionType.Highlighted,\n 1: ModMailActionType.Unhighlighted,\n 2: ModMailActionType.Archived,\n 3: ModMailActionType.Unarchived,\n 4: ModMailActionType.ReportedToAdmins,\n 5: ModMailActionType.Muted,\n 6: ModMailActionType.Unmuted,\n 7: ModMailActionType.Banned,\n 8: ModMailActionType.Unbanned,\n 9: ModMailActionType.Approved,\n 10: ModMailActionType.Disapproved,\n 11: ModMailActionType.Filtered,\n 12: ModMailActionType.Unfiltered,\n};\n\nexport type ConversationData = {\n /** Conversation ID */\n id?: string;\n /** Suject of the conversation */\n subject?: string;\n /**\n * Subreddit owning the modmail conversation\n */\n subreddit?: {\n displayName?: string;\n id?: string;\n };\n /**\n * A ConversationType specifies whether a conversation is with a subreddit\n * itself, with another user, or with another subreddit entirely.\n * - `internal` - This is a conversation with another user outside of the subreddit. The participant ID is that user's ID.\n * - `sr_user` - This is a Mod Discussion, internal to the subreddit. There is no other participant.\n * - `sr_sr` - This is a conversation is with another subreddit. The participant will have a subreddit ID.\n */\n conversationType?: string;\n /** Is the conversation automatically generated e.g. from automod, u/reddit */\n isAuto?: boolean;\n /** Participant. Is absent for mod discussions */\n participant?: Participant;\n /** The last datetime a user made any interaction with the conversation */\n lastUserUpdate?: string;\n /** Is the conversation internal (i.e. mod only) */\n isInternal?: boolean;\n /**\n * The last datetime a mod from the owning subreddit made any interaction\n * with the conversation.\n *\n * (Note that if this is a subreddit to subreddit conversation, the mods of\n * the participant subreddit are irrelevant and do not affect this field.)\n */\n lastModUpdate?: string;\n /** The authors of each message in the modmail conversation. */\n authors: Participant[];\n /** The datetime of the last time the conversation was update. */\n lastUpdated?: string;\n /** State of the conversation */\n state?: ModMailConversationState;\n /** The datetime of the last unread message within this conversation for the current viewer. */\n lastUnread?: string;\n /** Is the conversation highlighted */\n isHighlighted?: boolean;\n /** Number of messages (not actions) in the conversation */\n numMessages?: number;\n /**\n * Conversation messages\n *\n * @example\n * ```ts\n * const arrayOfMessages = Object.values(conversation.messages);\n * const messageById = conversation.messages[messageId];\n * ```\n */\n messages: { [id: string]: MessageData };\n /**\n * Conversation mod actions\n *\n * @example\n * ```ts\n * const arrayOfModActions = Object.values(conversation.modActions);\n * const modActionById = conversation.modActions[modActionId];\n * ```\n */\n modActions: { [id: string]: ModActionData };\n};\n\nexport type ModActionData = {\n /** Action id */\n id?: string;\n /** Type of the action */\n actionType: ModMailActionType;\n /** When the action happened */\n date?: string;\n /** Action author */\n author?: {\n /** User id */\n id?: number;\n /** User name */\n name?: string;\n isMod?: boolean;\n isAdmin?: boolean;\n isHidden?: boolean;\n isDeleted?: boolean;\n };\n};\n\nexport type MessageData = {\n /** Message ID */\n id?: string;\n /** Message body */\n body?: string;\n /** When was created */\n date?: string;\n author?: Participant;\n isInternal?: boolean;\n bodyMarkdown?: string;\n participatingAs?: string;\n};\n\nexport type ConversationResponse = {\n conversation: ConversationData;\n};\n\nexport type WithUserData = {\n user?: ConversationUserData;\n};\n\nexport type UnreadCountResponse = {\n archived?: number;\n appeals?: number;\n highlighted?: number;\n notifications?: number;\n joinRequests?: number;\n filtered?: number;\n new?: number;\n inprogress?: number;\n mod?: number;\n};\n\ntype ParticipantSubreddit = {\n id: string;\n name: string;\n};\n\nexport type GetConversationResponse = {\n conversation?: ConversationData;\n /** If the conversation is with another subreddit, what subreddit we are communicating with. */\n participantSubreddit?: ParticipantSubreddit;\n} & WithUserData;\n\nexport type GetConversationsResponse = {\n /**\n * Conversations key-value map\n */\n conversations: { [id: string]: ConversationData };\n viewerId?: string;\n /**\n * Array of conversation ids, ordered by the sort parameter specified in {@link GetConversationsRequest}.\n */\n conversationIds: string[];\n};\n\n/**\n * Class providing the methods for working with Mod Mail\n */\nexport class ModMailService {\n readonly #metadata: Metadata;\n readonly notificationSubjectPrefix = '[notification]';\n\n /**\n * @internal\n */\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n /**\n * Marks all conversations read for a particular conversation state within the passed list of subreddits.\n *\n * @param subreddits Array of subreddit names\n * @param state One of the possible conversation states ('all' to read all conversations)\n *\n * @returns conversationIds\n *\n * @example\n * ```ts\n * const conversationIds = await reddit.modMail.bulkReadConversations(\n * ['askReddit', 'myAwesomeSubreddit'],\n * 'filtered'\n * );\n * ```\n */\n async bulkReadConversations(\n subreddits: string[],\n state: ConversationStateFilter\n ): Promise<string[]> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const { conversationIds } = await client.BulkReadConversations(\n {\n entity: subreddits.join(','),\n state,\n },\n this.#metadata\n );\n\n return conversationIds;\n }\n\n /**\n * Get conversations for a logged in user or subreddits\n *\n * @param params.after id of a modmail\n * @param params.subreddits array of subreddit names\n * @param params.limit an integer between 1 and 100 (default: 25)\n * @param params.sort one of (recent, mod, user, unread)\n * @param params.state One of the possible conversation states ('all' to read all conversations)\n *\n * @example\n * ```ts\n * const {viewerId, conversations} = await reddit.modMail.getConversations({\n * after: 'abcdef',\n * limit: 42\n * });\n *\n * const arrayOfConversations = Object.values(conversations);\n * ```\n */\n async getConversations(params: GetConversationsRequest): Promise<GetConversationsResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.GetConversations(\n {\n after: params.after,\n entity: params.subreddits ? params.subreddits.join(',') : undefined,\n limit: params.limit,\n sort: params.sort,\n state: params.state,\n },\n this.#metadata\n );\n\n const conversations: { [id: string]: ConversationData } = {};\n\n for (const id in response.conversations) {\n conversations[id] = this.#transformConversationData({\n protoConversation: response.conversations[id],\n protoMessages: response.messages,\n protoModActions: {},\n });\n }\n\n return {\n conversations,\n viewerId: response.viewerId,\n conversationIds: response.conversationIds,\n };\n }\n\n /**\n * Returns all messages, mod actions and conversation metadata for a given conversation id\n *\n * @param params.conversationId id of a modmail conversation\n * @param params.markRead should be marked as read (default: false)\n *\n * @example\n * ```ts\n * const { conversation, messages, modActions, user } = await reddit.modMail.getConversation({ conversationId: 'abcdef', markRead: true });\n * ```\n */\n async getConversation(params: {\n /** a modmail conversation id */\n conversationId: string;\n /** mark read? */\n markRead?: boolean;\n }): Promise<GetConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.GetConversation(\n { ...params, markRead: !!params.markRead },\n this.#metadata\n );\n\n return {\n participantSubreddit: response.participantSubreddit as ParticipantSubreddit | undefined,\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Returns a list of Subreddits that the user moderates with mail permission\n *\n * @example\n * ```ts\n * const subredditsData = await reddit.modMail.getSubreddits();\n *\n * for (const subreddit of Object.values(subreddits)) {\n * console.log(subreddit.id);\n * console.log(subreddit.name);\n * }\n * ```\n */\n async getSubreddits(): Promise<{ [key: string]: SubredditData }> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const { subreddits } = await client.Subreddits({}, this.#metadata);\n\n return subreddits;\n }\n\n /**\n * Creates a new conversation for a particular SR.\n *\n * This endpoint will create a ModmailConversation object\n * as well as the first ModmailMessage within the ModmailConversation object.\n *\n * @note\n * Note on {param.to}:\n * The to field for this endpoint is somewhat confusing. It can be:\n * - A User, passed like \"username\" or \"u/username\"\n * - A Subreddit, passed like \"r/subreddit\"\n * - null, meaning an internal moderator discussion\n *\n * In this way to is a bit of a misnomer in modmail conversations.\n * What it really means is the participant of the conversation who is not a mod of the subreddit.\n *\n * If you plan to send a message to the app-account or a moderator of the subreddit, use {@link ModMailService.createModDiscussionConversation}, {@link ModMailService.createModInboxConversation}, or {@link ModMailService.createModNotification} instead.\n * Otherwise, messages sent to the app-account or moderator will automatically be routed to Mod Discussions.\n * @param params.body markdown text\n * @param params.isAuthorHidden is author hidden? (default: false)\n * @param params.subredditName subreddit name\n * @param params.subject subject of the conversation. max 100 characters\n * @param params.to a user (e.g. u/username), a subreddit (e.g. r/subreddit) or null\n *\n * @example\n * ```ts\n * const { conversation, messages, modActions } = await reddit.modMail.createConversation({\n * subredditName: 'askReddit',\n * subject: 'Test conversation',\n * body: 'Lorem ipsum sit amet',\n * to: null,\n * });\n * ```\n */\n async createConversation(params: {\n body: string;\n isAuthorHidden?: boolean;\n subredditName: string;\n subject: string;\n to?: string | null;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.CreateConversation(\n {\n body: params.body,\n isAuthorHidden: params.isAuthorHidden ?? false,\n srName: params.subredditName,\n subject: params.subject,\n to: params.to ? params.to : undefined,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Creates a conversation in Mod Discussions with the moderators of the given subredditId.\n *\n * Note: The app must be installed in the subreddit in order to create a conversation in Mod Discussions.\n *\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n * @example\n * ```ts\n * const conversationId = await reddit.modMail.createModDiscussionConversation({\n * subject: 'Test conversation',\n * bodyMarkdown: '**Hello there** \\n\\n _Have a great day!_',\n * subredditId: context.subredditId\n * });\n * ```\n */\n async createModDiscussionConversation(params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: string;\n }): Promise<string> {\n return createModmailConversation(\n {\n subject: params.subject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: asT5ID(params.subredditId),\n isInternal: true,\n participantType: 'MODERATOR',\n conversationType: 'INTERNAL',\n },\n this.#metadata\n );\n }\n\n /**\n * Creates a conversation in the Modmail Inbox with the moderators of the given subredditId.\n *\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n * @example\n * ```ts\n * const conversationId = await reddit.modMail.createModInboxConversation({\n * subject: 'Test conversation',\n * bodyMarkdown: '**Hello there** \\n\\n _Have a great day!_',\n * subredditId: context.subredditId\n * });\n * ```\n */\n async createModInboxConversation(params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: string;\n }): Promise<string> {\n return createModmailConversation(\n {\n subject: params.subject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: asT5ID(params.subredditId),\n isInternal: false,\n participantType: 'PARTICIPANT_USER',\n conversationType: 'SR_USER',\n },\n this.#metadata\n );\n }\n\n /**\n * Creates a notification in the Modmail Inbox.\n * This function is different from {@link ModMailService.createModInboxConversation} in that the conversation also appears in the \"Notifications\" section of Modmail.\n *\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n * @example\n * ```ts\n * const conversationId = await reddit.modMail.createModNotification({\n * subject: 'Test notification',\n * bodyMarkdown: '**Hello there** \\n\\n _This is a notification!_',\n * subredditId: context.subredditId\n * });\n * ```\n */\n async createModNotification(params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: string;\n }): Promise<string> {\n let notificationSubject = params.subject;\n\n if (!params.subject.startsWith(this.notificationSubjectPrefix)) {\n notificationSubject = `${this.notificationSubjectPrefix} ${params.subject}`;\n }\n\n return createModmailConversation(\n {\n subject: notificationSubject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: asT5ID(params.subredditId),\n isInternal: false,\n participantType: 'PARTICIPANT_USER',\n conversationType: 'SR_USER',\n },\n this.#metadata\n );\n }\n\n /**\n * Creates a new message for a particular conversation.\n *\n * @param params.conversationId Id of a modmail conversation\n * @param params.body markdown text\n * @param params.isInternal is internal message? (default: false)\n * @param params.isAuthorHidden is author hidden? (default: false)\n *\n * @example\n * ```ts\n * await reddit.modMail.reply({\n * body: 'Lorem ipsum sit amet',\n * conversationId: 'abcdef',\n * });\n * ```\n */\n async reply(params: {\n body: string;\n isAuthorHidden?: boolean;\n isInternal?: boolean;\n conversationId: string;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.CreateConversationMessage(\n {\n body: params.body,\n conversationId: params.conversationId,\n isAuthorHidden: params.isAuthorHidden ?? false,\n isInternal: params.isInternal ?? false,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: {},\n }),\n user: response.user,\n };\n }\n\n /**\n * Marks a conversation as highlighted.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.highlightConversation('abcdef');\n * ```\n */\n async highlightConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.HighlightConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Removes a highlight from a conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.unhighlightConversation('abcdef');\n * ```\n */\n async unhighlightConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.UnhighlightConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Marks a conversation as archived\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.archive('abcdef');\n * ```\n */\n async archiveConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.ArchiveConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Marks conversation as unarchived.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.unarchiveConversation('abcdef');\n * ```\n */\n async unarchiveConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.UnarchiveConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Marks a conversation as read for the user.\n *\n * @param params.conversationId Id of a modmail conversation\n * @param params.numHours For how many hours the conversation needs to be muted. Must be one of 72, 168, or 672 hours\n *\n * @example\n * ```ts\n * await reddit.modMail.muteConversation({ conversationId: 'abcdef', numHours: 72 });\n * ```\n */\n async muteConversation(params: {\n conversationId: string;\n numHours: 72 | 168 | 672;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.MuteConversation(\n {\n conversationId: params.conversationId,\n numHours: params.numHours,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Unmutes the non mod user associated with a particular conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.unmuteConversation('abcdef');\n * ```\n */\n async unmuteConversation(conversationId: string): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.UnmuteConversation(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Marks a conversations as read for the user.\n *\n * @param conversationIds An array of ids\n *\n * @example\n * ```ts\n * await reddit.modMail.readConversations(['abcdef', 'qwerty']);\n * ```\n */\n async readConversations(conversationIds: string[]): Promise<void> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n await client.Read(\n {\n conversationIds: conversationIds.join(','),\n },\n this.#metadata\n );\n }\n\n /**\n * Marks conversations as unread for the user.\n *\n * @param conversationIds An array of ids\n *\n * @example\n * ```ts\n * await reddit.modMail.unreadConversations(['abcdef', 'qwerty']);\n * ```\n */\n async unreadConversations(conversationIds: string[]): Promise<void> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n await client.Unread(\n {\n conversationIds: conversationIds.join(','),\n },\n this.#metadata\n );\n }\n\n /**\n * Approve the non mod user associated with a particular conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.approveConversation('abcdef');\n * ```\n */\n async approveConversation(conversationId: string): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.ApproveConversation(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Disapprove the non mod user associated with a particular conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await reddit.modMail.disapproveConversation('abcdef');\n * ```\n */\n async disapproveConversation(\n conversationId: string\n ): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.DisapproveConversation(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Temporary ban (switch from permanent to temporary ban) the non mod user associated with a particular conversation.\n *\n * @param params.conversationId a modmail conversation id\n * @param params.duration duration in days, max 999\n *\n * @example\n * ```ts\n * await reddit.modMail.tempBanConversation({ conversationId: 'abcdef', duration: 42 });\n * ```\n */\n async tempBanConversation(params: {\n conversationId: string;\n duration: number;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.TempBan(\n {\n ...params,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Unban the non mod user associated with a particular conversation.\n *\n * @param conversationId a modmail conversation id\n *\n * @example\n * ```ts\n * await reddit.modMail.unbanConversation('abcdef');\n * ```\n */\n async unbanConversation(conversationId: string): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.Unban(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Endpoint to retrieve the unread conversation count by conversation state.\n *\n * @example\n * ```ts\n * const response = await reddit.modMail.getUnreadCount();\n *\n * console.log(response.highlighted);\n * console.log(response.new);\n * ```\n */\n async getUnreadCount(): Promise<UnreadCountResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n return await client.UnreadCount({}, this.#metadata);\n }\n\n /**\n * Returns recent posts, comments and modmail conversations for a given user.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * const data = await reddit.modMail.getUserConversations('abcdef');\n *\n * console.log(data.recentComments);\n * console.log(data.recentPosts);\n * ```\n */\n async getUserConversations(conversationId: string): Promise<ConversationUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n return await client.UserConversations({ conversationId }, this.#metadata);\n }\n\n #transformConversationData({\n protoConversation,\n protoMessages,\n protoModActions,\n }: {\n protoConversation: ProtosConversationData;\n protoMessages: { [id: string]: ProtosMessageData };\n protoModActions: { [id: string]: ProtosModActionData };\n }): ConversationData {\n return {\n ...protoConversation,\n state: R2_TO_MODMAIL_CONVERSATION_STATE[protoConversation.state!],\n messages: this.#getConversationMessages(protoConversation, protoMessages),\n modActions: this.#getConversationModActions(protoConversation, protoModActions),\n };\n }\n\n #getConversationMessages(\n protoConversation: ProtosConversationData,\n protoMessages: { [id: string]: ProtosMessageData }\n ): { [id: string]: MessageData } {\n const messages: { [id: string]: MessageData } = {};\n const messageIds = protoConversation.objIds\n .filter((o) => o.key === 'messages')\n .map(({ id }) => id!);\n\n for (const messageId of messageIds) {\n const protoMessage = protoMessages[messageId];\n if (protoMessage) {\n messages[messageId] = protoMessage;\n }\n }\n\n return messages;\n }\n\n #getConversationModActions(\n protoConversation: ProtosConversationData,\n protoModActions: { [id: string]: ProtosModActionData }\n ): { [id: string]: ModActionData } {\n const modActions: { [id: string]: ModActionData } = {};\n const modActionIds = protoConversation.objIds\n .filter((o) => o.key === 'modActions')\n .map(({ id }) => id!);\n\n for (const modActionId of modActionIds) {\n const protoModAction = protoModActions[modActionId];\n if (protoModAction) {\n modActions[modActionId] = {\n ...protoModAction,\n actionType: R2_TO_MOD_ACTION_TYPE[protoModAction.actionTypeId!],\n };\n }\n }\n\n return modActions;\n }\n}\n\n/**\n * Creates a Modmail conversation with the moderators of the given subredditId.\n * @internal\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @param isInternal - Indicates if the conversation should be internal or not.\n * @param participantType - The type of participant the author is in the conversation.\n * @param conversationType - The type of conversation to create.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n */\nasync function createModmailConversation(\n params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: T5ID;\n isInternal: boolean;\n participantType: string;\n conversationType: string;\n },\n metadata: Metadata\n): Promise<string> {\n const appUserId = metadata[Header.AppUser]?.values[0];\n\n const operationName = 'CreateModmailConversation';\n const persistedQueryHash = '5f9ae20b0c7bdffcafb80241728a72e67cd4239bc09f67284b79d4aa706ee0e5';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n {\n subject: params.subject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: params.subredditId,\n authorId: appUserId,\n isInternal: params.isInternal,\n participantType: params.participantType,\n conversationType: params.conversationType,\n },\n metadata\n );\n\n if (response.data?.createModmailConversationV2?.ok) {\n return response.data?.createModmailConversationV2?.conversationId;\n }\n throw new Error(\n 'modmail conversation creation failed; ${response.data?.createModmailConversationV2?.errors[0].message}'\n );\n}\n", "import type { Metadata, RedditObject } from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { T2ID, T5ID, TID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, asT5ID, asTID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport type { ListingFetchOptions } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type { Subreddit } from './Subreddit.js';\nimport type { User } from './User.js';\n\nexport type SendPrivateMessageOptions = {\n /** Recipient username (without the leading u/), or /r/name for that subreddit's moderators. */\n to: string;\n /** The subject of the message. */\n subject: string;\n /** The body of the message in markdown text format. */\n text: string;\n};\n\nexport type SendPrivateMessageAsSubredditOptions = SendPrivateMessageOptions & {\n /** The name of the subreddit the message is being sent from (without the leading r/) */\n fromSubredditName: string;\n};\n\nexport type GetPrivateMessagesOptions = Prettify<\n {\n type?: 'inbox' | 'unread' | 'sent';\n } & ListingFetchOptions\n>;\n\ntype PrivateMessageAuthor =\n | (Pick<User, 'username'> & { type: 'user'; id?: T2ID })\n | (Pick<Subreddit, 'name'> & { type: 'subreddit'; id?: T5ID });\n\nexport class PrivateMessage {\n readonly #id: TID;\n readonly #from: PrivateMessageAuthor;\n readonly #body: string;\n readonly #bodyHtml: string;\n readonly #created: Date;\n\n readonly #metadata: Metadata | undefined;\n\n /** @internal */\n static async getMessages(\n options: GetPrivateMessagesOptions,\n metadata: Metadata | undefined\n ): Promise<Listing<PrivateMessage>> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n return new Listing({\n ...options,\n fetch: async (fetchOpts: ListingFetchOptions) => {\n const listing = await client.MessageWhere(\n {\n ...fetchOpts,\n where: options.type ?? 'inbox',\n },\n metadata\n );\n return {\n after: listing.data?.after,\n before: listing.data?.before,\n children: (listing.data?.children\n ?.map((child) => {\n return new PrivateMessage(child.data!, metadata);\n })\n .filter(Boolean) || []) as PrivateMessage[],\n };\n },\n });\n }\n\n /** @internal */\n static async send(\n { to, subject, text }: SendPrivateMessageOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n\n await client.Compose(\n {\n to,\n subject,\n text,\n fromSr: '',\n },\n metadata\n );\n }\n\n /** @internal */\n static async sendAsSubreddit(\n { to, fromSubredditName, subject, text }: SendPrivateMessageAsSubredditOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n\n await client.Compose(\n {\n to,\n fromSr: fromSubredditName,\n subject,\n text,\n },\n metadata\n );\n }\n\n /** @internal */\n static async markAllAsRead(metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n await client.ReadAllMessages({ filterTypes: '' }, metadata);\n }\n\n /**\n * @internal\n */\n constructor(data: RedditObject, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'PrivateMessage: Invalid data, no id');\n assertNonNull(data.name, 'PrivateMessage: Invalid data, no name');\n assertNonNull(data.created, 'PrivateMessage: Invalid data, no created date');\n\n this.#id = asTID(data.name);\n\n if (data.author != null) {\n this.#from = {\n type: 'user',\n username: data.author,\n id: data.authorFullname ? asT2ID(data.authorFullname) : undefined,\n };\n } else if (data.subreddit != null) {\n this.#from = {\n type: 'subreddit',\n name: data.subreddit,\n id: data.subredditId ? asT5ID(data.subredditId) : undefined,\n };\n } else {\n throw new Error('PrivateMessage: Invalid data, no author or subreddit');\n }\n\n this.#body = data.body ?? '';\n this.#bodyHtml = data.bodyHtml ?? '';\n\n const created = new Date(0);\n created.setUTCSeconds(data.createdUtc!);\n this.#created = created;\n\n this.#metadata = metadata;\n }\n\n get id(): TID {\n return this.#id;\n }\n\n get from(): PrivateMessageAuthor {\n return this.#from;\n }\n\n get body(): string {\n return this.#body;\n }\n\n get bodyHtml(): string {\n return this.#bodyHtml;\n }\n\n get created(): Date {\n return this.#created;\n }\n\n async markAsRead(): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n await client.ReadMessage({ id: this.#id }, this.#metadata);\n }\n}\n", "import type {\n AboutLocationRequest,\n Listing as ProtoListing,\n Metadata,\n SubredditAboutResponse_AboutData,\n WrappedRedditObject,\n} from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { T5ID } from '@devvit/shared-types/tid.js';\nimport { asT5ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { Comment } from './Comment.js';\nimport type {\n CreateFlairTemplateOptions,\n GetUserFlairBySubredditResponse,\n UserFlairPageOptions,\n} from './Flair.js';\nimport { convertUserFlairProtoToAPI, Flair, FlairTemplate } from './Flair.js';\nimport type { ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type {\n GetModerationLogOptions as _GetModerationLogOptions,\n ModAction,\n} from './ModAction.js';\nimport { _getModerationLog } from './ModAction.js';\nimport type {\n GetPostsOptionsWithTimeframe,\n SubmitLinkOptions,\n SubmitSelfPostOptions,\n} from './Post.js';\nimport { Post } from './Post.js';\nimport type {\n BanUserOptions,\n BanWikiContributorOptions,\n GetSubredditUsersByTypeOptions,\n ModeratorPermission,\n} from './User.js';\nimport { User } from './User.js';\n\ntype GetModerationLogOptions = Omit<_GetModerationLogOptions, 'subredditName'>;\ntype GetUsersOptions = Omit<GetSubredditUsersByTypeOptions, 'subredditName' | 'type'>;\n\nexport type SubredditType =\n | 'public'\n | 'private'\n | 'restricted'\n | 'employees_only'\n | 'gold_only'\n | 'gold_restricted'\n | 'archived'\n | 'user';\n\nexport enum AboutLocations {\n Reports = 'reports',\n Spam = 'spam',\n Modqueue = 'modqueue',\n Unmoderated = 'unmoderated',\n Edited = 'edited',\n}\n\nexport type AboutSubredditTypes = 'comment' | 'post' | 'all';\n\ntype AboutSubredditOptions<T extends AboutSubredditTypes> = Omit<\n AboutSubredditHelperOptions<T>,\n 'location' | 'subreddit'\n>;\n\nexport type ModLogOptions<T extends AboutSubredditTypes> = Omit<\n AboutSubredditHelperOptions<T>,\n 'location'\n>;\n\ntype AboutSubredditHelperOptions<T extends AboutSubredditTypes> = Prettify<\n {\n type: T;\n } & AboutLocationRequest\n>;\n\nexport type CommentMediaTypes = 'giphy' | 'static' | 'animated' | 'expression';\n\nexport type FlairSettings = {\n enabled: boolean;\n usersCanAssign: boolean;\n userFlairBackgroundColor?: string;\n userFlairTextColor?: string;\n};\n\nexport type GetUserFlairOptions = UserFlairPageOptions & {\n /** If provide the method will return the flairs for the provided users, if not provided\n * it will return a list of all users assigned flairs in the subreddit */\n usernames?: string[];\n};\n\n/**\n * An individual Removal Reason object.\n */\nexport type RemovalReason = {\n /**\n * The ID of the removal reason.\n */\n id: string;\n /**\n * The message associated with the removal reason.\n */\n message: string;\n /**\n * The title of the removal reason.\n */\n title: string;\n};\n\nexport type SubredditSettings = {\n /**\n * Whether the subreddit accepts followers or not.\n */\n acceptFollowers: boolean;\n /**\n * Whether all content posted on the subreddit is original.\n */\n allOriginalContent: boolean;\n /**\n * Whether users are allowed to create chat posts on the subreddit.\n */\n allowChatPostCreation: boolean;\n /**\n * Whether the subreddit can be discovered through search.\n */\n allowDiscovery: boolean;\n /**\n * Whether the subreddit allows galleries.\n */\n allowGalleries: boolean;\n /**\n * Whether the subreddit allows images.\n */\n allowImages: boolean;\n /**\n * Whether the subreddit allows polls.\n */\n allowPolls: boolean;\n /**\n * Whether contributors are allowed to make predictions on the subreddit.\n */\n allowPredictionContributors: boolean;\n /**\n * Whether predictions are allowed on the subreddit.\n */\n allowPredictions: boolean;\n /**\n * Whether prediction tournaments are allowed on the subreddit.\n */\n allowPredictionsTournament: boolean;\n /**\n * Whether talks are allowed on the subreddit.\n */\n allowTalks: boolean;\n /**\n * Whether video GIFs are allowed on the subreddit.\n */\n allowVideoGifs: boolean;\n /**\n * Whether videos are allowed on the subreddit.\n */\n allowVideos: boolean;\n /**\n * Whether chat posts are enabled on the subreddit.\n */\n chatPostEnabled: boolean;\n /**\n * Whether collections are enabled on the subreddit.\n */\n collectionsEnabled: boolean;\n /**\n * Whether crossposts can be made to this subreddit.\n */\n crosspostable: boolean;\n /**\n * Whether emojis are enabled on the subreddit.\n */\n emojisEnabled: boolean;\n /**\n * Whether event posts are enabled on the subreddit.\n */\n eventPostsEnabled: boolean;\n /**\n * Whether link flairs are enabled on the subreddit.\n */\n linkFlairEnabled: boolean;\n /**\n * Whether the Original Content tag is enabled.\n */\n originalContentTagEnabled: boolean;\n /**\n * Whether commenting is restricted in the subreddit.\n */\n restrictCommenting: boolean;\n /**\n * Whether posting is restricted in the subreddit.\n */\n restrictPosting: boolean;\n /**\n * Whether posts in the subreddit should be automatically archived after 6 months.\n */\n shouldArchivePosts: boolean;\n /**\n * Whether the Spoiler tag is enabled.\n */\n spoilersEnabled: boolean;\n /**\n * Whether the wiki is enabled for the subreddit.\n */\n wikiEnabled: boolean;\n /**\n * The types of post allowed in this subreddit. Either \"any\", \"link\", or \"self\".\n */\n allowedPostType: 'any' | 'link' | 'self';\n /**\n * List of allowed media types in the comments made in the subreddit.\n */\n allowedMediaInComments: CommentMediaTypes[];\n /**\n * a 6-digit rgb hex color of the banner e.g. `#AABBCC`,\n */\n bannerBackgroundColor?: string;\n /**\n * The background image of the banner.\n */\n bannerBackgroundImage?: string;\n /**\n * The URL of the banner image.\n */\n bannerImage?: string;\n /**\n * The URL of the community icon.\n */\n communityIcon?: string;\n /**\n * The header title.\n */\n headerTitle?: string;\n /**\n * The 6-digit rgb hex color of the subreddit's key color, e.g. `#AABBCC`,\n */\n keyColor?: string;\n /**\n * Banner image used on mobile apps.\n */\n mobileBannerImage?: string;\n /**\n * The 6-digit rgb hex color of the subreddit's primary color, e.g. `#AABBCC`,\n */\n primaryColor?: string;\n /**\n * The user flair settings for the subreddit.\n */\n userFlairs: FlairSettings;\n /**\n * The post flair settings for the subreddit.\n */\n postFlairs: FlairSettings;\n /**\n * HTTP URL to the subreddit\n */\n url: string;\n};\n\nexport type SubredditLeaderboardSummaryRow = {\n title: string;\n key: string;\n value: number;\n};\n\nexport type SubredditLeaderboardSummary = {\n data: SubredditLeaderboardSummaryRow[];\n};\n\n/**\n * An individual Leaderboard object.\n */\nexport type SubredditLeaderboard = {\n id: string;\n summary: SubredditLeaderboardSummary;\n};\n\nexport type BackgroundImagePosition = 'cover' | 'tiled' | 'centered';\nexport type BannerHeight = 'small' | 'medium' | 'large';\nexport type CommunityNameFormat = 'slashtag' | 'pretty' | 'hide';\nexport type CustomizationFlag = 'default' | 'custom';\nexport type ImagePosition = 'cover' | 'tiled';\nexport type MenuPosition = 'default' | 'overlay';\nexport type PositionedImagePosition = 'left' | 'right' | 'centered';\nexport type Visibility = 'show' | 'hide';\n\n/**\n * A class representing the styles of a Subreddit.\n */\nexport type SubredditStyles = {\n backgroundColor?: string;\n backgroundImage?: string;\n backgroundImagePosition?: BackgroundImagePosition;\n bannerBackgroundColor?: string;\n bannerBackgroundImage?: string;\n bannerBackgroundImagePosition?: ImagePosition;\n bannerCommunityName?: string;\n bannerCommunityNameFormat?: CommunityNameFormat;\n bannerHeight?: BannerHeight;\n bannerOverlayColor?: string;\n bannerPositionedImage?: string;\n bannerPositionedImagePosition?: PositionedImagePosition;\n bannerShowCommunityIcon?: Visibility;\n highlightColor?: string;\n icon?: string;\n legacyBannerBackgroundImage?: string;\n legacyPrimaryColor?: string;\n menuBackgroundBlur?: number;\n menuBackgroundColor?: string;\n menuBackgroundImage?: string;\n menuBackgroundOpacity?: number;\n menuLinkColorActive?: string;\n menuLinkColorHover?: string;\n menuLinkColorInactive?: string;\n menuPosition?: MenuPosition;\n mobileBannerImage?: string;\n mobileKeyColor?: string;\n postBackgroundColor?: string;\n postBackgroundImage?: string;\n postBackgroundImagePosition?: ImagePosition;\n postDownvoteCountColor?: string;\n postDownvoteIconActive?: string;\n postDownvoteIconInactive?: string;\n postPlaceholderImage?: string;\n postPlaceholderImagePosition?: ImagePosition;\n postTitleColor?: string;\n postUpvoteCountColor?: string;\n postUpvoteIconActive?: string;\n postUpvoteIconInactive?: string;\n postVoteIcons?: CustomizationFlag;\n primaryColor?: string;\n secondaryBannerPositionedImage?: string;\n sidebarWidgetBackgroundColor?: string;\n sidebarWidgetHeaderColor?: string;\n submenuBackgroundColor?: string;\n submenuBackgroundStyle?: CustomizationFlag;\n};\n\nexport class SubredditDescription {\n markdown?: string;\n}\n\nexport class SubredditWikiSettings {\n wikiEditMode?: WikiEditMode;\n}\n\nexport type WikiEditMode = 'disabled' | 'modonly' | 'anyone';\n\nexport type PostType =\n | 'link'\n | 'image'\n | 'video'\n | 'text'\n | 'spoiler'\n | 'poll'\n | 'gallery'\n | 'talk'\n | 'prediction'\n | 'videogif'\n | 'streaming'\n | 'crosspost';\n\nexport type PostCapabilities = 'ama';\n\nexport class AuthorFlairSettings {\n isEnabled?: boolean;\n isSelfAssignabled?: boolean;\n}\n\nexport class PostFlairSettings {\n isEnabled?: boolean;\n isSelfAssignabled?: boolean;\n}\n\n/**\n * A class representing information about a Subreddit.\n */\nexport type SubredditInfo = {\n id?: T5ID;\n name?: string;\n createdAt?: Date;\n type?: SubredditType;\n title?: string;\n description?: SubredditDescription;\n detectedLanguage?: string;\n subscribersCount?: number;\n activeCount?: number;\n isNsfw?: boolean;\n isQuarantined?: boolean;\n isDiscoveryAllowed?: boolean;\n isPredictionContributorsAllowed?: boolean;\n isPredictionAllowed?: boolean;\n isPredictionsTournamentAllowed?: boolean;\n isChatPostCreationAllowed?: boolean;\n isChatPostFeatureEnabled?: boolean;\n isCrosspostingAllowed?: boolean;\n isEmojisEnabled?: boolean;\n isCommentingRestricted?: boolean;\n isPostingRestricted?: boolean;\n isArchivePostsEnabled?: boolean;\n isSpoilerAvailable?: boolean;\n allAllowedPostTypes?: PostType[];\n allowedPostCapabilities?: PostCapabilities[];\n allowedMediaInComments?: CommentMediaTypes[];\n authorFlairSettings?: AuthorFlairSettings;\n postFlairSettings?: PostFlairSettings;\n wikiSettings?: SubredditWikiSettings;\n};\n\n/**\n * A class representing a subreddit.\n */\nexport class Subreddit {\n #id: T5ID;\n #name: string;\n #createdAt: Date;\n #type: SubredditType;\n #title?: string;\n #description?: string;\n #language: string;\n #numberOfSubscribers: number;\n #numberOfActiveUsers: number;\n #nsfw: boolean;\n #settings: SubredditSettings;\n // R2 bug: subreddit does not contain a permalink field, but uses the url field instead\n #permalink: string;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: Partial<SubredditAboutResponse_AboutData>, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'Subreddit id is missing or undefined');\n assertNonNull(data.displayName, 'Subreddit name is missing or undefined');\n\n this.#id = asT5ID(`t5_${data.id}`);\n this.#name = data.displayName;\n\n assertNonNull(data.createdUtc, 'Subreddit is missing created date');\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#type = asSubredditType(data.subredditType);\n this.#title = data.title;\n this.#description = data.description;\n\n assertNonNull(data.lang, 'Subreddit is missing language');\n this.#language = data.lang;\n\n this.#numberOfSubscribers = data.subscribers ?? 0;\n this.#numberOfActiveUsers = data.activeUserCount ?? 0;\n\n this.#nsfw = data.over18 ?? false;\n\n this.#permalink = data.url ?? '';\n\n this.#settings = {\n acceptFollowers: data.acceptFollowers ?? false,\n allOriginalContent: data.allOriginalContent ?? false,\n allowChatPostCreation: data.allowChatPostCreation ?? false,\n allowDiscovery: data.allowDiscovery ?? false,\n allowGalleries: data.allowGalleries ?? false,\n allowImages: data.allowImages ?? false,\n allowPolls: data.allowPolls ?? false,\n allowPredictionContributors: data.allowPredictionContributors ?? false,\n allowPredictions: data.allowPredictions ?? false,\n allowPredictionsTournament: data.allowPredictionsTournament ?? false,\n allowTalks: data.allowTalks ?? false,\n allowVideoGifs: data.allowVideogifs ?? false,\n allowVideos: data.allowVideos ?? false,\n chatPostEnabled: data.isChatPostFeatureEnabled ?? false,\n collectionsEnabled: data.collectionsEnabled ?? false,\n crosspostable: data.isCrosspostableSubreddit ?? false,\n emojisEnabled: data.emojisEnabled ?? false,\n eventPostsEnabled: data.eventPostsEnabled ?? false,\n linkFlairEnabled: data.linkFlairEnabled ?? false,\n originalContentTagEnabled: data.originalContentTagEnabled ?? false,\n restrictCommenting: data.restrictCommenting ?? false,\n restrictPosting: data.restrictPosting ?? false,\n shouldArchivePosts: data.shouldArchivePosts ?? false,\n spoilersEnabled: data.spoilersEnabled ?? false,\n wikiEnabled: data.wikiEnabled ?? false,\n allowedPostType: asAllowedPostType(data.submissionType),\n allowedMediaInComments: (data.allowedMediaInComments ?? []).map(asCommentMediaTypes),\n bannerBackgroundColor: data.bannerBackgroundColor,\n bannerBackgroundImage: data.bannerBackgroundImage,\n bannerImage: data.bannerImg,\n communityIcon: data.communityIcon,\n headerTitle: data.headerTitle,\n keyColor: data.keyColor,\n mobileBannerImage: data.mobileBannerImage,\n primaryColor: data.primaryColor,\n userFlairs: {\n enabled: data.userFlairEnabledInSr ?? false,\n usersCanAssign: data.canAssignUserFlair ?? false,\n userFlairBackgroundColor: data.userFlairBackgroundColor,\n userFlairTextColor: data.userFlairTextColor,\n },\n postFlairs: {\n enabled: data.linkFlairEnabled ?? false,\n usersCanAssign: data.canAssignLinkFlair ?? false,\n },\n // R2 bug: url is a permalink\n url: new URL(this.#permalink, 'https://www.reddit.com').toString(),\n };\n\n this.#metadata = metadata;\n }\n\n /**\n * The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n */\n get id(): T5ID {\n return this.#id;\n }\n\n /**\n * The name of a subreddit omitting the r/.\n */\n get name(): string {\n return this.#name;\n }\n\n /**\n * The creation date of the subreddit.\n */\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n /**\n * The type of subreddit (public, private, etc.).\n */\n get type(): SubredditType {\n return this.#type;\n }\n\n /**\n * The title of the subreddit.\n */\n get title(): string | undefined {\n return this.#title;\n }\n\n /**\n * The description of the subreddit.\n */\n get description(): string | undefined {\n return this.#description;\n }\n\n /**\n * The language of the subreddit.\n */\n get language(): string {\n return this.#language;\n }\n\n /**\n * The number of subscribers of the subreddit.\n */\n get numberOfSubscribers(): number {\n return this.#numberOfSubscribers;\n }\n\n /**\n * The number of active users of the subreddit.\n */\n get numberOfActiveUsers(): number {\n return this.#numberOfActiveUsers;\n }\n\n /**\n * Whether the subreddit is marked as NSFW (Not Safe For Work).\n */\n get nsfw(): boolean {\n return this.#nsfw;\n }\n\n /**\n * The settings of the subreddit.\n */\n get settings(): SubredditSettings {\n return this.#settings;\n }\n\n /**\n * Whether the user flairs are enabled for this subreddit.\n */\n get userFlairsEnabled(): boolean {\n return this.settings.userFlairs.enabled;\n }\n\n /**\n * Whether the post flairs are enabled for this subreddit.\n */\n get postFlairsEnabled(): boolean {\n return this.settings.postFlairs.enabled;\n }\n\n /**\n * Whether the user can assign user flairs.\n * This is only true if the user flairs are enabled.\n */\n get usersCanAssignUserFlairs(): boolean {\n return this.settings.userFlairs.usersCanAssign;\n }\n\n /**\n * Whether the user can assign post flairs.\n * This is only true if the post flairs are enabled.\n */\n get usersCanAssignPostFlairs(): boolean {\n return this.settings.postFlairs.usersCanAssign;\n }\n\n /**\n * Returns the HTTP URL for the subreddit.\n * (R2 bug: subreddit.url is a permalink path and does not return a fully qualified URL in subreddit.url)\n */\n get url(): string {\n return this.settings.url;\n }\n\n /**\n * Returns a permalink path\n * (R2 bug: subreddit.url is a permalink, and does not have a subreddit.permalink field)\n */\n get permalink(): string {\n return this.#permalink;\n }\n\n toJSON(): Pick<\n Subreddit,\n | 'id'\n | 'name'\n | 'createdAt'\n | 'type'\n | 'title'\n | 'description'\n | 'language'\n | 'nsfw'\n | 'numberOfSubscribers'\n | 'numberOfActiveUsers'\n | 'settings'\n > {\n return {\n id: this.id,\n name: this.name,\n createdAt: this.createdAt,\n type: this.type,\n title: this.title,\n description: this.description,\n language: this.language,\n nsfw: this.nsfw,\n numberOfSubscribers: this.numberOfSubscribers,\n numberOfActiveUsers: this.numberOfActiveUsers,\n settings: this.settings,\n };\n }\n\n async submitPost(options: SubmitLinkOptions | SubmitSelfPostOptions): Promise<Post> {\n const submitPostOptions = {\n ...options,\n subredditName: this.#name,\n };\n\n return Post.submit(submitPostOptions, this.#metadata);\n }\n\n getControversialPosts(\n options: Omit<GetPostsOptionsWithTimeframe, 'subredditName'> = {}\n ): Listing<Post> {\n if (!this.#name) {\n throw new Error('subreddit missing displayName - it might not have been fetched');\n }\n\n return Post.getControversialPosts(\n {\n ...options,\n subredditName: this.#name,\n },\n this.#metadata\n );\n }\n\n getTopPosts(options: Omit<GetPostsOptionsWithTimeframe, 'subredditName'> = {}): Listing<Post> {\n if (!this.#name) {\n throw new Error('subreddit missing displayName - it might not have been fetched');\n }\n\n return Post.getTopPosts(\n {\n ...options,\n subredditName: this.#name,\n },\n this.#metadata\n );\n }\n\n getApprovedUsers(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'contributors',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n approveUser(username: string): Promise<void> {\n return User.createRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'contributor',\n },\n this.#metadata\n );\n }\n\n removeUser(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'contributor',\n },\n this.#metadata\n );\n }\n\n getWikiContributors(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'wikicontributors',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n addWikiContributor(username: string): Promise<void> {\n return User.createRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'wikicontributor',\n },\n this.#metadata\n );\n }\n\n removeWikiContributor(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'wikicontributor',\n },\n this.#metadata\n );\n }\n\n getBannedUsers(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'banned',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n banUser(options: Omit<BanUserOptions, 'subredditName'>): Promise<void> {\n return User.createRelationship(\n {\n username: options.username,\n subredditName: this.#name,\n type: 'banned',\n banReason: options.reason,\n banMessage: options.message,\n note: options.note,\n duration: options.duration,\n banContext: options.context,\n },\n this.#metadata\n );\n }\n\n unbanUser(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'banned',\n },\n this.#metadata\n );\n }\n\n getBannedWikiContributors(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'wikibanned',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n banWikiContributor(options: Omit<BanWikiContributorOptions, 'subredditName'>): Promise<void> {\n return User.createRelationship(\n {\n username: options.username,\n subredditName: this.#name,\n type: 'wikibanned',\n banReason: options.reason,\n note: options.note,\n duration: options.duration,\n },\n this.#metadata\n );\n }\n\n unbanWikiContributor(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'wikibanned',\n },\n this.#metadata\n );\n }\n\n getModerators(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'moderators',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n inviteModerator(username: string, permissions?: ModeratorPermission[]): Promise<void> {\n return User.createRelationship(\n {\n type: 'moderator_invite',\n subredditName: this.#name,\n username,\n permissions: permissions ?? [],\n },\n this.#metadata\n );\n }\n\n revokeModeratorInvite(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'moderator_invite',\n },\n this.#metadata\n );\n }\n\n removeModerator(username: string): Promise<void> {\n return User.removeRelationship(\n {\n type: 'moderator',\n subredditName: this.#name,\n username,\n },\n this.#metadata\n );\n }\n\n setModeratorPermissions(username: string, permissions: ModeratorPermission[]): Promise<void> {\n return User.setModeratorPermissions(username, this.#name, permissions, this.#metadata);\n }\n\n getMutedUsers(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'muted',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n muteUser(username: string, note?: string): Promise<void> {\n return User.createRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'muted',\n note,\n },\n this.#metadata\n );\n }\n\n unmuteUser(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'muted',\n },\n this.#metadata\n );\n }\n\n getModerationLog(options: GetModerationLogOptions): Listing<ModAction> {\n return _getModerationLog(\n {\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n getUserFlairTemplates(): Promise<FlairTemplate[]> {\n return FlairTemplate.getUserFlairTemplates(this.#name, this.#metadata);\n }\n\n getPostFlairTemplates(): Promise<FlairTemplate[]> {\n return FlairTemplate.getPostFlairTemplates(this.#name, this.#metadata);\n }\n\n createPostFlairTemplate(\n options: Omit<CreateFlairTemplateOptions, 'subredditName'>\n ): Promise<FlairTemplate> {\n return FlairTemplate.createPostFlairTemplate(\n {\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n createUserFlairTemplate(\n options: Omit<CreateFlairTemplateOptions, 'subredditName'>\n ): Promise<FlairTemplate> {\n return FlairTemplate.createUserFlairTemplate(\n {\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n /**\n * Get the user flair for the given subreddit. If `usernames` is provided then it will return only the\n * flair for the specified users. If retrieving the list of flair for a given subreddit and the list is long\n * then this method will return a `next` field which can be passed into the `after` field on the next call to\n * retrieve the next slice of data. To retrieve the previous slice of data pass the `prev` field into the `before` field\n * during the subsequent call.\n *\n * @param options See interface\n * @param metadata See interface\n *\n * @example\n * ```ts\n * const subredditName = \"mysubreddit\"\n * const subreddit = await reddit.getSubredditByName(subredditName)\n * const response = await subreddit.getUserFlair();\n * const userFlairList = response.users\n * ```\n * @example\n * ```ts\n * const response = await subreddit.getUserFlair({ after: \"t2_awefae\"});\n * const userFlairList = response.users\n * ```\n *\n * @example\n * ```ts\n * const response = await subreddit.getUserFlair({ usernames: ['toxictoad', 'badapple']});\n * const userFlairList = response.users\n * ```\n */\n async getUserFlair(options?: GetUserFlairOptions): Promise<GetUserFlairBySubredditResponse> {\n if (options?.usernames !== undefined) {\n const users = await Promise.all(\n options.usernames.map(async (name) => {\n const response = await Flair.getUserFlairBySubreddit(\n {\n subreddit: this.#name,\n name,\n },\n this.#metadata\n );\n return convertUserFlairProtoToAPI(response.users[0]);\n })\n );\n\n return { users };\n } else {\n const response = await Flair.getUserFlairBySubreddit(\n {\n ...options,\n subreddit: this.#name,\n },\n this.#metadata\n );\n return {\n next: response.next,\n prev: response.prev,\n users: response.users.map((userFlair) => convertUserFlairProtoToAPI(userFlair)),\n };\n }\n }\n\n /**\n * Return a listing of things requiring moderator review, such as reported things and items.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await reddit.getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getModQueue();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getModQueue({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getModQueue(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getModQueue(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getModQueue(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getModQueue(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Modqueue,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have been reported.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await reddit.getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getReports();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getReports({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getReports(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getReports(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getReports(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getReports(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Reports,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have been marked as spam or otherwise removed.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await reddit.getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getSpam();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getSpam({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getSpam(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getSpam(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getSpam(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getSpam(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Spam,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have yet to be approved/removed by a mod.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await reddit.getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getUnmoderated();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getUnmoderated({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getUnmoderated(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getUnmoderated(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getUnmoderated(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getUnmoderated(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Unmoderated,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have been edited recently.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await reddit.getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getEdited();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getEdited({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getEdited(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getEdited(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getEdited(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getEdited(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Edited,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /** @internal */\n static aboutLocation(\n options: AboutSubredditHelperOptions<AboutSubredditTypes>,\n metadata: Metadata | undefined\n ): Listing<Post | Comment> {\n const client = Devvit.redditAPIPlugins.Moderation;\n let only: string | undefined;\n switch (options.type) {\n case 'post':\n only = 'links';\n break;\n case 'comment':\n only = 'comments';\n break;\n default:\n only = undefined;\n }\n\n return new Listing({\n ...options,\n fetch: async (fetchOptions) => {\n const listing = await client.AboutLocation(\n {\n ...fetchOptions,\n ...options,\n only,\n },\n metadata\n );\n\n return parseListing(listing, metadata);\n },\n });\n }\n\n /**\n * Return a listing of things specified by their fullnames.\n *\n * @param ids Array of thing full ids (e.g. t3_abc123)\n * @example\n * ```ts\n * const subreddit = await reddit.getSubredditByName('askReddit');\n * const listing = subreddit.getCommentsAndPostsByIds(['t3_abc123', 't1_xyz123']);\n * const items = await listing.all();\n * console.log(items) // [Post, Comment]\n * ```\n */\n getCommentsAndPostsByIds(ids: string[]): Listing<Post | Comment> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n return new Listing({\n fetch: async () => {\n const listing = await client.Info(\n { thingIds: ids, subreddits: [this.#id] },\n this.#metadata\n );\n\n return parseListing(listing, this.#metadata);\n },\n });\n }\n\n /** @internal */\n static async addRemovalReason(\n subredditName: string,\n title: string,\n message: string,\n metadata: Metadata | undefined\n ): Promise<string> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n const response = await client.SubredditAddRemovalReason(\n {\n title,\n message,\n subreddit: subredditName,\n },\n metadata\n );\n\n return response.id;\n }\n\n /** @internal */\n static async getRemovalReasons(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<RemovalReason[]> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n const result = await client.SubredditGetRemovalReasons(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n return result.order.map((id) => ({ ...result.data[id] }));\n }\n\n /** @internal */\n static async getFromMetadata(metadata: Metadata | undefined): Promise<Subreddit | undefined> {\n assertNonNull(metadata);\n const subredditName = metadata?.[Header.SubredditName]?.values[0];\n if (subredditName) {\n return Subreddit.getByName(subredditName, metadata);\n }\n\n const subredditId = metadata?.[Header.Subreddit]?.values[0];\n assertNonNull<string | undefined>(subredditId);\n return Subreddit.getById(asT5ID(subredditId), metadata);\n }\n\n /** @internal */\n static async getById(id: T5ID, metadata: Metadata | undefined): Promise<Subreddit | undefined> {\n const subredditName = await _getSubredditNameById(id, metadata);\n if (!subredditName) {\n return;\n }\n\n return Subreddit.getByName(subredditName, metadata);\n }\n\n /** @internal */\n static async getByName(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<Subreddit> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n const response = await client.SubredditAbout(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n if (!response?.data) {\n throw new Error('not found');\n }\n\n return new Subreddit(response.data, metadata);\n }\n}\n\n/**\n * @internal\n * Gets a {@link SubredditInfo} object by ID\n *\n * @param {string} id - The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n * @param metadata - Optional RPC metadata passed with every request.\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n */\nexport async function _getSubredditInfoById(\n subredditId: string,\n metadata: Metadata | undefined\n): Promise<SubredditInfo> {\n const operationName = 'GetSubredditInfoById';\n const persistedQueryHash = '315a9b75c22a017d526afdf2d274616946156451aacfd56dfb91e7ad3f7a2fde';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { id: subredditId },\n metadata\n );\n\n const subredditInfo = response.data?.subredditInfoById;\n\n if (!subredditInfo) throw new Error('subreddit info not found');\n\n return subredditInfo;\n}\n\n/**\n * @internal\n * Gets a {@link SubredditInfo} object by name\n *\n * @param {string} subredditName The name of a subreddit omitting the r/. This is case insensitive.\n * @param metadata - Optional RPC metadata passed with every request.\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n */\nexport async function _getSubredditInfoByName(\n subredditName: string,\n metadata: Metadata | undefined\n): Promise<SubredditInfo> {\n const operationName = 'GetSubredditInfoByName';\n const persistedQueryHash = '4aa69726c7e3f5d33ab2bee22b3d74fce645824fddd5ea3ec6dfe30bdb4295cb';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { name: subredditName },\n metadata\n );\n\n const subredditInfo = response.data?.subredditInfoByName;\n\n if (!subredditInfo) throw new Error('subreddit info not found');\n\n return subredditInfo;\n}\n\n/** @internal */\nexport async function _getSubredditLeaderboard(\n subredditId: string,\n metadata: Metadata | undefined\n): Promise<SubredditLeaderboard> {\n const operationName = 'GetSubredditLeaderboard';\n const persistedQueryHash = '18ead70c46b6446d45ecd8b679b16d9a929a933d6ef25d8262a459cb18b72848';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { id: subredditId },\n metadata\n );\n\n const leaderboard = response.data?.subredditInfoById?.leaderboard;\n\n if (!leaderboard) throw new Error('subreddit leaderboard not found');\n if (!leaderboard.summary) throw new Error('subreddit leaderboard summary not found');\n\n return {\n id: leaderboard.id,\n summary: leaderboard.summary,\n };\n}\n\n/**\n * @internal\n */\nexport async function _getSubredditStyles(\n subredditId: string,\n metadata: Metadata | undefined\n): Promise<SubredditStyles> {\n const operationName = 'GetSubredditStyles';\n const persistedQueryHash = 'd491d17ea8858f563ea578b26b9595d64adecf4bf34557d567c7e53c470f5f22';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { id: subredditId },\n metadata\n );\n\n const styles = response.data?.subredditInfoById?.styles;\n\n if (!styles) throw new Error('subreddit styles not found');\n\n return styles;\n}\n\nfunction asSubredditType(type?: string): SubredditType {\n if (\n type === 'public' ||\n type === 'private' ||\n type === 'restricted' ||\n type === 'employees_only' ||\n type === 'gold_only' ||\n type === 'gold_restricted' ||\n type === 'archived' ||\n type === 'user'\n ) {\n return type;\n }\n\n throw new Error(`invalid subreddit type: ${type}`);\n}\n\nfunction asAllowedPostType(type?: string): 'any' | 'link' | 'self' {\n if (type === 'any' || type === 'link' || type === 'self') {\n return type;\n }\n\n throw new Error(`invalid allowed post type: ${type}`);\n}\n\nfunction asCommentMediaTypes(type: string): CommentMediaTypes {\n if (type === 'animated' || type === 'giphy' || type === 'static' || type === 'expression') {\n return type;\n }\n\n throw new Error(`invalid comment media type: ${type}`);\n}\n\nfunction parseListing(\n listing: ProtoListing,\n metadata: Metadata | undefined\n): ListingFetchResponse<Post | Comment> {\n const children = listing.data?.children ?? [];\n const postsAndComments: (Post | Comment)[] = children\n .map((child) => {\n const post = tryParseAsPost(child);\n if (post != null) {\n return post;\n }\n const comment = tryParseAsComment(child);\n if (comment != null) {\n return comment;\n }\n\n return null;\n })\n .filter(Boolean) as (Post | Comment)[];\n\n return {\n after: listing.data?.after,\n before: listing.data?.before,\n children: postsAndComments,\n };\n\n function tryParseAsPost(obj: WrappedRedditObject): Post | null {\n try {\n return new Post(obj.data!, metadata);\n } catch {\n return null;\n }\n }\n\n function tryParseAsComment(obj: WrappedRedditObject): Comment | null {\n try {\n return new Comment(obj.data!, metadata);\n } catch {\n return null;\n }\n }\n}\n\n/** @internal */\nexport async function _getSubredditNameById(\n id: T5ID,\n metadata: Metadata | undefined\n): Promise<string | undefined> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const response = await client.Info({ thingIds: [id], subreddits: [] }, metadata);\n return response.data?.children[0]?.data?.displayName;\n}\n", "import type {\n AddButtonWidgetRequest,\n AddCalendarWidgetRequest,\n AddCommunityListWidgetRequest,\n AddCustomWidgetRequest,\n AddImageWidgetRequest,\n AddPostFlairWidgetRequest,\n AddTextAreaWidgetRequest,\n CalendarWidgetConfiguration,\n GetWidgetsResponse_WidgetItem_PostFlairTemplate as PostFlairTemplateData,\n Metadata,\n SubredditAboutRulesResponse,\n UpdateButtonWidgetRequest,\n UpdateCalendarWidgetRequest,\n UpdateCommunityListWidgetRequest,\n UpdateCustomWidgetRequest,\n UpdateImageWidgetRequest,\n UpdatePostFlairWidgetRequest,\n UpdateTextAreaWidgetRequest,\n WidgetButton,\n WidgetImage,\n WidgetStyles,\n} from '@devvit/protos';\nimport {\n CommunityListWidget_CommunityData as CommunityData,\n GetWidgetsResponse_WidgetItem as WidgetItem,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\n\nexport type AddWidgetData =\n | (AddImageWidgetRequest & {\n type: 'image';\n })\n | (AddCalendarWidgetRequest & {\n type: 'calendar';\n })\n | (AddTextAreaWidgetRequest & {\n type: 'textarea';\n })\n | (AddButtonWidgetRequest & {\n type: 'button';\n })\n | (AddCommunityListWidgetRequest & {\n type: 'community-list';\n })\n | (AddPostFlairWidgetRequest & {\n type: 'post-flair';\n })\n | (AddCustomWidgetRequest & {\n type: 'custom';\n });\n\nexport class Widget {\n #id: string;\n #name: string;\n #subredditName: string;\n\n #metadata: Metadata | undefined;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n this.#id = widgetData.id;\n this.#name = widgetData.shortName;\n this.#subredditName = subredditName;\n this.#metadata = metadata;\n }\n\n get id(): string {\n return this.#id;\n }\n\n get name(): string {\n return this.#name;\n }\n\n get subredditName(): string {\n return this.#subredditName;\n }\n\n toJSON(): Pick<Widget, 'id' | 'name' | 'subredditName'> {\n return {\n id: this.id,\n name: this.name,\n subredditName: this.subredditName,\n };\n }\n\n delete(): Promise<void> {\n return Widget.delete(this.subredditName, this.id, this.#metadata);\n }\n\n /**\n * @internal\n * @note - This method only returns the widgets listed on the sidebar.\n */\n static async getWidgets(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<Widget[]> {\n const client = Devvit.redditAPIPlugins.Widgets;\n\n const response = await client.GetWidgets(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n assertNonNull(response.layout, 'Failed to load widgets for subreddit');\n\n const widgetsMap = response.items;\n\n const widgets: Widget[] = [];\n\n for (const widgetId of response.layout.sidebar?.order ?? []) {\n const widgetData = widgetsMap[widgetId];\n switch (widgetData?.kind) {\n case 'image':\n widgets.push(new ImageWidget(widgetData, subredditName, metadata));\n break;\n case 'calendar':\n widgets.push(new CalendarWidget(widgetData, subredditName, metadata));\n break;\n case 'textarea':\n widgets.push(new TextAreaWidget(widgetData, subredditName, metadata));\n break;\n case 'button':\n widgets.push(new ButtonWidget(widgetData, subredditName, metadata));\n break;\n case 'community-list':\n widgets.push(new CommunityListWidget(widgetData, subredditName, metadata));\n break;\n case 'post-flair':\n widgets.push(new PostFlairWidget(widgetData, subredditName, metadata));\n break;\n case 'custom':\n widgets.push(new CustomWidget(widgetData, subredditName, metadata));\n break;\n case 'subreddit-rules': {\n // subreddit rule widget does not contain any data\n // we need to fetch it separately\n const rulesRsp = await Devvit.redditAPIPlugins.Subreddits.SubredditAboutRules(\n {\n subreddit: subredditName,\n },\n metadata\n );\n widgets.push(new SubredditRulesWidget(rulesRsp, widgetData, subredditName, metadata));\n break;\n }\n default:\n throw new Error(`Unknown widget type: ${widgetData.kind}`);\n }\n }\n\n return widgets;\n }\n\n /** @internal */\n static async delete(\n subredditName: string,\n id: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Widgets;\n\n await client.DeleteWidget(\n {\n subreddit: subredditName,\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async reorder(\n subredditName: string,\n orderByIds: string[],\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Widgets;\n\n await client.OrderWidgets(\n {\n subreddit: subredditName,\n order: orderByIds,\n },\n metadata\n );\n }\n\n /** @internal */\n static async add(widgetData: AddWidgetData, metadata: Metadata | undefined): Promise<Widget> {\n switch (widgetData?.type) {\n case 'image':\n return ImageWidget.create(widgetData, metadata);\n case 'calendar':\n return CalendarWidget.create(widgetData, metadata);\n case 'textarea':\n return TextAreaWidget.create(widgetData, metadata);\n case 'button':\n return ButtonWidget.create(widgetData, metadata);\n case 'community-list':\n return CommunityListWidget.create(widgetData, metadata);\n case 'post-flair':\n return PostFlairWidget.create(widgetData, metadata);\n case 'custom':\n return CustomWidget.create(widgetData, metadata);\n default:\n throw new Error('Unknown widget type');\n }\n }\n}\n\nexport class ImageWidget extends Widget {\n #images: WidgetImage[];\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n this.#images = widgetData.data.map((data) => {\n assertNonNull(data.url, 'Image widget data is missing url');\n assertNonNull(data.height, 'Image widget data is missing height');\n assertNonNull(data.width, 'Image widget data is missing width');\n assertNonNull(data.linkUrl, 'Image widget data is missing linkUrl');\n\n return {\n url: data.url,\n height: data.height,\n width: data.width,\n linkUrl: data.linkUrl,\n };\n });\n }\n\n get images(): WidgetImage[] {\n return this.#images;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> & Pick<ImageWidget, 'images'> {\n return {\n ...super.toJSON(),\n images: this.images,\n };\n }\n\n /** @internal */\n static async create(\n options: AddImageWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ImageWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddImageWidget(options, metadata);\n\n return new ImageWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateImageWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ImageWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateImageWidget(options, metadata);\n\n return new ImageWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class CalendarWidget extends Widget {\n #googleCalendarId: string;\n #configuration: CalendarWidgetConfiguration;\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.googleCalendarId, 'Calendar widget data is missing googleCalendarId');\n assertNonNull(widgetData.configuration, 'Calendar widget data is missing configuration');\n assertNonNull(widgetData.styles, 'Calendar widget data is missing styles');\n\n this.#googleCalendarId = widgetData.googleCalendarId;\n this.#configuration = widgetData.configuration;\n this.#styles = widgetData.styles;\n }\n\n get googleCalendarId(): string {\n return this.#googleCalendarId;\n }\n\n get configuration(): CalendarWidgetConfiguration {\n return this.#configuration;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<CalendarWidget, 'googleCalendarId' | 'configuration' | 'styles'> {\n return {\n ...super.toJSON(),\n googleCalendarId: this.googleCalendarId,\n configuration: this.configuration,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddCalendarWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CalendarWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddCalendarWidget(options, metadata);\n\n return new CalendarWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateCalendarWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CalendarWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateCalendarWidget(options, metadata);\n\n return new CalendarWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class TextAreaWidget extends Widget {\n #text: string;\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.text, 'Textarea widget data is missing text');\n assertNonNull(widgetData.styles, 'Textarea widget data is missing styles');\n\n this.#text = widgetData.text;\n this.#styles = widgetData.styles;\n }\n\n get text(): string {\n return this.#text;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> & Pick<TextAreaWidget, 'text' | 'styles'> {\n return {\n ...super.toJSON(),\n text: this.text,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddTextAreaWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<TextAreaWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddTextAreaWidget(options, metadata);\n\n return new TextAreaWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateTextAreaWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<TextAreaWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateTextAreaWidget(options, metadata);\n\n return new TextAreaWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class ButtonWidget extends Widget {\n #buttons: WidgetButton[];\n #description: string;\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.styles, 'Button widget data is missing styles');\n\n this.#buttons = widgetData.buttons;\n this.#description = widgetData.description ?? '';\n this.#styles = widgetData.styles;\n }\n\n get buttons(): WidgetButton[] {\n return this.#buttons;\n }\n\n get description(): string {\n return this.#description;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<ButtonWidget, 'buttons' | 'description' | 'styles'> {\n return {\n ...super.toJSON(),\n buttons: this.buttons,\n description: this.description,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddButtonWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ButtonWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddButtonWidget(options, metadata);\n\n return new ButtonWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateButtonWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ButtonWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateButtonWidget(options, metadata);\n\n return new ButtonWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class CommunityListWidget extends Widget {\n #communities: CommunityData[];\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n this.#communities = widgetData.data.map((communityData) =>\n CommunityData.fromJSON(communityData)\n );\n\n assertNonNull(widgetData.styles, 'Community list widget data is missing styles');\n\n this.#styles = widgetData.styles;\n }\n\n get communities(): CommunityData[] {\n return this.#communities;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<CommunityListWidget, 'communities' | 'styles'> {\n return {\n ...super.toJSON(),\n communities: this.communities,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddCommunityListWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CommunityListWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddCommunityListWidget(options, metadata);\n\n return new CommunityListWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateCommunityListWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CommunityListWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateCommunityListWidget(options, metadata);\n\n return new CommunityListWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class PostFlairWidget extends Widget {\n #styles: WidgetStyles;\n #templates: PostFlairTemplateData[];\n #display: 'list' | 'cloud';\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.styles, 'Post flair widget data is missing styles');\n\n this.#styles = widgetData.styles;\n this.#templates = widgetData.order.map((templateId) => widgetData.templates[templateId]);\n\n if (\n !((widgetData.display && widgetData.display === 'list') || widgetData.display === 'cloud')\n ) {\n throw new Error('Post flair widget data is missing display type');\n }\n\n this.#display = widgetData.display;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n get templates(): PostFlairTemplateData[] {\n return this.#templates;\n }\n\n get display(): 'list' | 'cloud' {\n return this.#display;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<PostFlairWidget, 'templates' | 'display' | 'styles'> {\n return {\n ...super.toJSON(),\n styles: this.styles,\n templates: this.templates,\n display: this.display,\n };\n }\n\n /** @internal */\n static async create(\n options: AddPostFlairWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<PostFlairWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddPostFlairWidget(options, metadata);\n\n return new PostFlairWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdatePostFlairWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<PostFlairWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdatePostFlairWidget(options, metadata);\n\n return new PostFlairWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class CustomWidget extends Widget {\n #images: WidgetImage[];\n #text: string;\n #stylesheetUrl: string;\n #height: number;\n #css: string;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.stylesheetUrl, 'Custom widget data is missing stylesheetUrl');\n assertNonNull(widgetData.height, 'Custom widget data is missing height');\n assertNonNull(widgetData.css, 'Custom widget data is missing css');\n\n this.#images = widgetData.imageData ?? [];\n this.#text = widgetData.text ?? '';\n this.#stylesheetUrl = widgetData.stylesheetUrl;\n this.#height = widgetData.height;\n this.#css = widgetData.css;\n }\n\n get images(): WidgetImage[] {\n return this.#images;\n }\n\n get text(): string {\n return this.#text;\n }\n\n get stylesheetUrl(): string {\n return this.#stylesheetUrl;\n }\n\n get height(): number {\n return this.#height;\n }\n\n get css(): string {\n return this.#css;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<CustomWidget, 'images' | 'text' | 'stylesheetUrl' | 'height' | 'css'> {\n return {\n ...super.toJSON(),\n images: this.images,\n text: this.text,\n stylesheetUrl: this.stylesheetUrl,\n height: this.height,\n css: this.css,\n };\n }\n\n /** @internal */\n static async create(\n options: AddCustomWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CustomWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddCustomWidget(options, metadata);\n\n return new CustomWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateCustomWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CustomWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateCustomWidget(options, metadata);\n\n return new CustomWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\ntype SubredditRule = {\n description: string;\n priority: number;\n shortName: string;\n violationReason: string;\n};\n\nexport class SubredditRulesWidget extends Widget {\n readonly #rules: SubredditRule[];\n\n constructor(\n subredditAboutRulesRsp: SubredditAboutRulesResponse,\n widgetData: WidgetItem,\n subredditName: string,\n metadata: Metadata | undefined\n ) {\n super(widgetData, subredditName, metadata);\n\n const rules = subredditAboutRulesRsp.rules.map(\n ({ description, priority, shortName, violationReason }) => {\n assertNonNull(description, 'Subreddit rule is missing description');\n assertNonNull(priority, 'Subreddit rule is missing priority');\n assertNonNull(shortName, 'Subreddit rule is missing shortName');\n assertNonNull(violationReason, 'Subreddit rule is missing violationReason');\n return {\n description,\n priority,\n shortName,\n violationReason,\n };\n }\n );\n\n this.#rules = rules;\n }\n\n get rules(): SubredditRule[] {\n return this.#rules;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> & Pick<SubredditRulesWidget, 'rules'> {\n return {\n ...super.toJSON(),\n rules: this.rules,\n };\n }\n}\n", "import type {\n Metadata,\n WikiPage as WikiPageProto,\n WikiPageRevision as WikiPageRevisionProto,\n WikiPageRevisionListing,\n WikiPageSettings_Data,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { Listing } from './Listing.js';\nimport { User } from './User.js';\n\nexport type CreateWikiPageOptions = {\n /** The name of the subreddit to create the page in. */\n subredditName: string;\n /** The name of the page to create. */\n page: string;\n /** The content of the page. */\n content: string;\n /** The reason for creating the page. */\n reason?: string;\n};\n\nexport type UpdateWikiPageOptions = {\n /** The name of the subreddit the page is in. */\n subredditName: string;\n /** The name of the page to update. */\n page: string;\n /** The new content of the page. */\n content: string;\n /** The reason for updating the page. */\n reason?: string;\n};\n\nexport type GetPageRevisionsOptions = {\n /** The name of the subreddit the page is in. */\n subredditName: string;\n /** The name of the page to get revisions for. */\n page: string;\n /** The number of revisions to get per request. */\n pageSize?: number;\n /** The maximum number of revisions to get. */\n limit?: number;\n /** The ID of the revision to start at. */\n after?: string;\n};\n\nexport enum WikiPagePermissionLevel {\n /** Use subreddit wiki permissions */\n SUBREDDIT_PERMISSIONS = 0,\n /** Only approved wiki contributors for this page may edit */\n APPROVED_CONTRIBUTORS_ONLY = 1,\n /** Only mods may edit and view */\n MODS_ONLY = 2,\n}\n\nexport type UpdatePageSettingsOptions = {\n /** The name of the subreddit the page is in. */\n subredditName: string;\n /** The name of the page to update settings for. */\n page: string;\n /** Whether the page should be listed in the wiki index. */\n listed: boolean;\n /** The permission level for the page. */\n permLevel: WikiPagePermissionLevel;\n};\n\nexport class WikiPage {\n #name: string;\n #subredditName: string;\n #content: string;\n #contentHtml: string;\n #revisionId: string;\n #revisionDate: Date;\n #revisionReason: string;\n #revisionAuthor: User | undefined;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(\n name: string,\n subredditName: string,\n data: WikiPageProto,\n metadata: Metadata | undefined\n ) {\n makeGettersEnumerable(this);\n\n this.#name = name;\n this.#subredditName = subredditName;\n this.#content = data.contentMd;\n this.#contentHtml = data.contentHtml;\n this.#revisionId = data.revisionId;\n this.#revisionDate = new Date(data.revisionDate * 1000); // data.revisionDate is represented in seconds, so multiply by 1000 to get milliseconds\n this.#revisionReason = data.reason ?? '';\n this.#revisionAuthor = data.revisionBy?.data\n ? new User(data.revisionBy.data, metadata)\n : undefined;\n\n this.#metadata = metadata;\n }\n\n /** The name of the page. */\n get name(): string {\n return this.#name;\n }\n\n /** The name of the subreddit the page is in. */\n get subredditName(): string {\n return this.#subredditName;\n }\n\n /** The Markdown content of the page. */\n get content(): string {\n return this.#content;\n }\n\n /** The HTML content of the page. */\n get contentHtml(): string {\n return this.#contentHtml;\n }\n\n /** The ID of the revision. */\n get revisionId(): string {\n return this.#revisionId;\n }\n\n /** The date of the revision. */\n get revisionDate(): Date {\n return this.#revisionDate;\n }\n\n /** The reason for the revision. */\n get revisionReason(): string {\n return this.#revisionReason;\n }\n\n /** The author of this revision. */\n get revisionAuthor(): User | undefined {\n return this.#revisionAuthor;\n }\n\n toJSON(): Pick<\n WikiPage,\n | 'name'\n | 'subredditName'\n | 'content'\n | 'contentHtml'\n | 'revisionId'\n | 'revisionDate'\n | 'revisionReason'\n > & {\n revisionAuthor: ReturnType<User['toJSON']> | undefined;\n } {\n return {\n name: this.#name,\n subredditName: this.#subredditName,\n content: this.#content,\n contentHtml: this.#contentHtml,\n revisionId: this.#revisionId,\n revisionDate: this.#revisionDate,\n revisionReason: this.#revisionReason,\n revisionAuthor: this.#revisionAuthor?.toJSON(),\n };\n }\n\n /** Update this page. */\n async update(content: string, reason?: string): Promise<WikiPage> {\n return WikiPage.updatePage(\n {\n subredditName: this.#subredditName,\n page: this.#name,\n content,\n reason,\n },\n this.#metadata\n );\n }\n\n /** Get the revisions for this page. */\n async getRevisions(\n options: Omit<GetPageRevisionsOptions, 'subredditName' | 'page'>\n ): Promise<Listing<WikiPageRevision>> {\n return WikiPage.getPageRevisions(\n {\n subredditName: this.#subredditName,\n page: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n /** Revert this page to a previous revision. */\n async revertTo(revisionId: string): Promise<void> {\n return WikiPage.revertPage(this.#subredditName, this.#name, revisionId, this.#metadata);\n }\n\n /** Get the settings for this page. */\n async getSettings(): Promise<WikiPageSettings> {\n return WikiPage.getPageSettings(this.#subredditName, this.#name, this.#metadata);\n }\n\n /** Update the settings for this page. */\n async updateSettings(\n options: Omit<UpdatePageSettingsOptions, 'subredditName' | 'page'>\n ): Promise<WikiPageSettings> {\n return WikiPage.updatePageSettings(\n {\n subredditName: this.#subredditName,\n page: this.#name,\n listed: options.listed,\n permLevel: options.permLevel,\n },\n this.#metadata\n );\n }\n\n /** Add an editor to this page. */\n async addEditor(username: string): Promise<void> {\n return WikiPage.addEditor(this.#subredditName, this.#name, username, this.#metadata);\n }\n\n /** Remove an editor from this page. */\n async removeEditor(username: string): Promise<void> {\n return WikiPage.removeEditor(this.#subredditName, this.#name, username, this.#metadata);\n }\n\n /** @internal */\n static async getPage(\n subredditName: string,\n page: string,\n metadata: Metadata | undefined\n ): Promise<WikiPage> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.GetWikiPage(\n {\n subreddit: subredditName,\n page,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to get wiki page');\n\n return new WikiPage(page, subredditName, response.data, metadata);\n }\n\n /** @internal */\n static async getPages(subredditName: string, metadata: Metadata | undefined): Promise<string[]> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.GetWikiPages({ subreddit: subredditName }, metadata);\n\n return response.data || [];\n }\n\n /** @internal */\n static async createPage(\n options: CreateWikiPageOptions,\n metadata: Metadata | undefined\n ): Promise<WikiPage> {\n return WikiPage.updatePage(options, metadata);\n }\n\n /** @internal */\n static async updatePage(\n options: UpdateWikiPageOptions,\n metadata: Metadata | undefined\n ): Promise<WikiPage> {\n const client = Devvit.redditAPIPlugins.Wiki;\n await client.EditWikiPage(\n {\n subreddit: options.subredditName,\n page: options.page,\n content: options.content,\n reason: options.reason ?? '',\n },\n metadata\n );\n\n return WikiPage.getPage(options.subredditName, options.page, metadata);\n }\n\n /** @internal */\n static getPageRevisions(\n options: GetPageRevisionsOptions,\n metadata: Metadata | undefined\n ): Listing<WikiPageRevision> {\n const client = Devvit.redditAPIPlugins.Wiki;\n return new Listing({\n hasMore: true,\n after: options.after,\n limit: options.limit,\n pageSize: options.pageSize,\n async fetch(fetchOptions) {\n const response = await client.GetWikiPageRevisions(\n {\n subreddit: options.subredditName,\n page: options.page,\n limit: fetchOptions.limit,\n after: fetchOptions.after,\n before: fetchOptions.before,\n },\n metadata\n );\n\n return wikiPageRevisionListingProtoToWikiPageRevision(response, metadata);\n },\n });\n }\n\n /** @internal */\n static async revertPage(\n subredditName: string,\n page: string,\n revisionId: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Wiki;\n\n await client.RevertWikiPage(\n {\n subreddit: subredditName,\n page,\n revision: revisionId,\n },\n metadata\n );\n }\n\n /** @internal */\n static async getPageSettings(\n subredditName: string,\n page: string,\n metadata: Metadata | undefined\n ): Promise<WikiPageSettings> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.GetWikiPageSettings(\n {\n subreddit: subredditName,\n page,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to get wiki page settings');\n\n return new WikiPageSettings(response.data, metadata);\n }\n\n /** @internal */\n static async updatePageSettings(\n options: UpdatePageSettingsOptions,\n metadata: Metadata | undefined\n ): Promise<WikiPageSettings> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.UpdateWikiPageSettings(\n {\n subreddit: options.subredditName,\n page: options.page,\n listed: options.listed ? 'on' : '',\n permlevel: options.permLevel,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to update wiki page settings');\n\n return new WikiPageSettings(response.data, metadata);\n }\n\n /** @internal */\n static async addEditor(\n subredditName: string,\n page: string,\n username: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Wiki;\n await client.AllowEditor(\n {\n act: 'add',\n subreddit: subredditName,\n page,\n username,\n },\n metadata\n );\n }\n\n /** @internal */\n static async removeEditor(\n subredditName: string,\n page: string,\n username: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Wiki;\n await client.AllowEditor(\n {\n act: 'del',\n subreddit: subredditName,\n page,\n username,\n },\n metadata\n );\n }\n}\n\nexport class WikiPageRevision {\n #id: string;\n #page: string;\n #date: Date;\n #author: User;\n #reason: string;\n #hidden: boolean;\n\n constructor(data: WikiPageRevisionProto, metadata: Metadata | undefined) {\n this.#id = data.id;\n this.#page = data.page;\n this.#date = new Date(data.timestamp);\n\n assertNonNull(data.author?.data, 'Wiki page revision author details are missing');\n this.#author = new User(data.author.data, metadata);\n\n this.#reason = data.reason ?? '';\n this.#hidden = data.revisionHidden ?? false;\n }\n\n get id(): string {\n return this.#id;\n }\n\n get page(): string {\n return this.#page;\n }\n\n get date(): Date {\n return this.#date;\n }\n\n get author(): User {\n return this.#author;\n }\n\n get reason(): string {\n return this.#reason;\n }\n\n get hidden(): boolean {\n return this.#hidden;\n }\n\n toJSON(): Pick<WikiPageRevision, 'id' | 'page' | 'date' | 'reason' | 'hidden'> & {\n author: ReturnType<User['toJSON']>;\n } {\n return {\n id: this.#id,\n page: this.#page,\n date: this.#date,\n author: this.#author.toJSON(),\n reason: this.#reason,\n hidden: this.#hidden,\n };\n }\n}\n\nexport class WikiPageSettings {\n #listed: boolean;\n #permLevel: WikiPagePermissionLevel;\n #editors: User[];\n\n constructor(data: WikiPageSettings_Data, metadata: Metadata | undefined) {\n this.#listed = data.listed;\n this.#permLevel = data.permLevel;\n this.#editors = data.editors.map((editor) => {\n assertNonNull(editor.data, 'Wiki page editor details are missing');\n return new User(editor.data, metadata);\n });\n }\n\n get listed(): boolean {\n return this.#listed;\n }\n\n get permLevel(): WikiPagePermissionLevel {\n return this.#permLevel;\n }\n\n get editors(): User[] {\n return this.#editors;\n }\n\n toJSON(): Pick<WikiPageSettings, 'listed' | 'permLevel'> & {\n editors: ReturnType<User['toJSON']>[];\n } {\n return {\n listed: this.#listed,\n permLevel: this.#permLevel,\n editors: this.#editors.map((editor) => editor.toJSON()),\n };\n }\n}\n\nfunction wikiPageRevisionListingProtoToWikiPageRevision(\n listingProto: WikiPageRevisionListing,\n metadata: Metadata | undefined\n): { children: WikiPageRevision[]; before: string | undefined; after: string | undefined } {\n assertNonNull(listingProto.data?.children, 'Wiki page revision listing is missing children');\n\n const children = listingProto.data.children.map((child) => {\n return new WikiPageRevision(child, metadata);\n });\n\n return {\n children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\nimport type { T2ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID } from '@devvit/shared-types/tid.js';\n\nimport { GraphQL } from '../graphql/GraphQL.js';\n\n/**\n * A type representing a Vault (crypto wallet).\n */\nexport type Vault = {\n /**\n * The provider of the Vault address.\n * @example 'ethereum'\n */\n provider: string;\n\n /**\n * The ID (starting with t2_) of the user owning the Vault.\n * @example 't2_1w72'\n */\n userId: T2ID;\n\n /**\n * The address of the Vault.\n * @example '0x205ee28744456bDBf180A0Fa7De51e0F116d54Ed'\n */\n address: string;\n\n /**\n * The date the Vault was created.\n */\n createdAt: string;\n\n /**\n * Whether the Vault is active.\n */\n isActive: boolean;\n};\n\n/**\n * @internal\n */\nexport async function getVaultByAddress(\n address: string,\n metadata: Metadata | undefined\n): Promise<Vault> {\n return getVaultByParams(\n 'GetVaultContactByAddress',\n '3e2f7966a5c120e64fd2795d06a46595c52d988185be98d3ed71c3f81ae80d2e',\n {\n provider: 'ethereum', // Only one supported at the moment\n address,\n },\n metadata\n );\n}\n\n/**\n * @internal\n */\nexport async function getVaultByUserId(\n userId: T2ID,\n metadata: Metadata | undefined\n): Promise<Vault> {\n return getVaultByParams(\n 'GetVaultContactByUserId',\n 'a854ddc19d0e22c4f36ed917fdbd568f299f3571427e393aee5e2972080fffe9',\n {\n provider: 'ethereum', // Only one supported at the moment\n userId,\n },\n metadata\n );\n}\n\nasync function getVaultByParams(\n operationName: string,\n queryHash: string,\n params: JSONObject,\n metadata: Metadata | undefined\n): Promise<Vault> {\n const response = await GraphQL.query(operationName, queryHash, params, metadata);\n const contact = response?.data?.vault?.contact;\n\n return {\n provider: contact?.provider,\n userId: asT2ID(contact?.userId),\n address: contact?.address,\n createdAt: contact?.createdAt,\n isActive: contact?.isActive,\n };\n}\n", "// https://github.com/cure53/DOMPurify/blob/f89d72681513d749d303798ced02fa2799340989/src/tags.js#L202\nconst DISALLOWED_ELEMENTS = [\n // We don't allow images bypass reddit safety checks!\n /**\n * 1/31/24: We need to allow images so we can make things happen.\n * https://reddit.atlassian.net/browse/DX-5740\n */\n // 'image',\n 'animate',\n 'color-profile',\n 'cursor',\n 'discard',\n 'font-face',\n 'font-face-format',\n 'font-face-name',\n 'font-face-src',\n 'font-face-uri',\n 'foreignobject',\n 'hatch',\n 'hatchpath',\n 'mesh',\n 'meshgradient',\n 'meshpatch',\n 'meshrow',\n 'missing-glyph',\n 'script',\n 'set',\n 'solidcolor',\n 'unknown',\n 'use',\n];\n// We don't allow images bypass reddit safety checks!\nconst DISALLOWED_STYLES = [\n /**\n * 1/31/24: We need to allow images so we can make things happen.\n * https://reddit.atlassian.net/browse/DX-5740\n */\n // 'background',\n // 'background-image',\n 'border-image',\n 'border-image-source',\n 'behavior',\n 'expression',\n 'list-style-image',\n 'cursor',\n 'content',\n];\nconst DISALLOWED_ATTRIBUTES = [\n 'onload',\n 'onerror',\n 'onclick',\n 'onmouseover',\n /**\n * 1/31/24: We need to allow images so we can make things happen.\n * https://reddit.atlassian.net/browse/DX-5740\n */\n // 'href',\n];\nfunction ensureXmlns(svg) {\n if (/xmlns=[\"']http:\\/\\/www\\.w3\\.org\\/2000\\/svg[\"']/.test(svg)) {\n return svg;\n }\n return svg.replace(/<svg\\b/, '<svg xmlns=\"http://www.w3.org/2000/svg\"');\n}\nexport function sanitizeSvg(svg) {\n if (!svg) {\n return undefined;\n }\n if (!svg.trim().startsWith('<svg')) {\n console.log('The provided string is not a valid SVG.');\n return undefined; // Return an empty string if it's not an SVG\n }\n try {\n // Create regex patterns (case-insensitive)\n // eslint-disable-next-line security/detect-non-literal-regexp\n const disallowedElementsRegex = new RegExp(`<\\\\s*(${DISALLOWED_ELEMENTS.join('|')})\\\\b`, 'gi');\n // eslint-disable-next-line security/detect-non-literal-regexp\n const disallowedStylesRegex = new RegExp(`(${DISALLOWED_STYLES.join('|')})\\\\s*:\\\\s*url\\\\s*\\\\((['\"]?)(.*?)\\\\2\\\\)`, 'gi');\n // eslint-disable-next-line security/detect-non-literal-regexp\n const disallowedAttributesRegex = new RegExp(`(${DISALLOWED_ATTRIBUTES.join('|')})\\\\s*(=\\\\s*(['\"]?)(.*?)\\\\3)?`, 'gi');\n // Remove newlines and excessive whitespace\n svg = svg.trim().replace(/\\s+/g, ' ');\n // SVGs as data URLs require it to be a valid XML file meaning\n // xmlns is required where it is not within most browsers.\n svg = ensureXmlns(svg);\n // Find disallowed elements, styles, and attributes\n const elementMatches = svg.match(disallowedElementsRegex) || [];\n const styleMatches = svg.match(disallowedStylesRegex) || [];\n const attributeMatches = svg.match(disallowedAttributesRegex) || [];\n let isInvalid = false;\n // Log and sanitize if disallowed elements are found\n if (elementMatches.length > 0) {\n isInvalid = true;\n console.warn(`Disallowed elements detected in SVG: ${elementMatches\n .map((x) => x.replace('<', ''))\n .join(', ')}`);\n }\n // Log and sanitize if disallowed styles are found\n if (styleMatches.length > 0) {\n isInvalid = true;\n console.warn(`Disallowed styles detected in SVG: ${styleMatches.map((x) => x.split(':')[0]).join(', ')}`);\n }\n // Log and sanitize if disallowed attributes are found\n if (attributeMatches.length > 0) {\n isInvalid = true;\n console.warn(`Disallowed attributes detected in SVG: ${attributeMatches\n .map((x) => x.split('=')[0])\n .join(', ')}`);\n }\n if (isInvalid) {\n return undefined;\n }\n return svg; // Return the original SVG string if it's clean\n }\n catch {\n return undefined;\n }\n}\n", "import { sanitizeSvg } from '@devvit/shared-types/sanitizeSvg.js';\n\n/**\n * @experimental\n *\n * A helper to allow SVG functionality within image tags.\n *\n * @example\n * ```ts\n * import { Devvit, svg } from '@devvit/public-api';\n * const App = () => {\n * const color = 'gold'\n * return (\n * <hstack>\n * <image\n * url={svg`<svg viewBox=\"0 0 10 10\" xmlns=\"http://www.w3.org/2000/svg\">\n * <circle fill=\"${color}\" cx=\"5\" cy=\"5\" r=\"4\" />\n * </svg>`}\n * imageHeight={100}\n * imageWidth={100}\n * />\n * </hstack>\n * )\n * }\n * ```\n */\nexport function svg(\n strings: TemplateStringsArray,\n ...args: (string | number)[]\n): `data:image/svg+xml;charset=UTF-8,${string}` | '' {\n let str = '';\n\n // Assemble the SVG string\n strings.forEach((string, index) => {\n str += string;\n const arg = args[index];\n\n if (arg !== undefined) {\n // Cast number to string\n str += `${arg}`;\n }\n });\n\n // Sanitize the SVG string\n str = sanitizeSvg(str);\n\n if (str === undefined) {\n return '';\n }\n\n // Return the sanitized SVG string as a data URI\n // This fixes things like hexadecimal colors that URLs treat as special characters!\n return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(str)}`;\n}\n", "import { EffectType, type UIEvent } from '@devvit/protos';\nimport { WebViewVisibility } from '@devvit/protos';\nimport type { WebViewAppMessage } from '@devvit/protos/types/devvit/ui/effects/web_view/v1alpha/post_message.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\nimport { StringUtil } from '@devvit/shared-types/StringUtil.js';\n\nimport type {\n UseWebViewOnMessage,\n UseWebViewOptions,\n UseWebViewResult,\n} from '../../../../index.js';\nimport { webViewMessageIsInternalAndClientScope } from '../../helpers/devvitInternalMessage.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\nclass WebViewHook<From extends JSONValue, To extends JSONValue> implements Hook {\n state: {\n // Auto-incrementing count of the number of WebviewMessage effects called this frame.\n // Used as part of the dedup key for emitEvent to prevent messages from being dedup'd.\n messageCount: number;\n } = { messageCount: 0 };\n #hookId: string;\n // This url is the path to the asset that will be loaded in the web view.\n // It is ensured to be a valid path prior to the effect being emitted.\n #url: string;\n #onMessage: UseWebViewOnMessage<From, To>;\n #onUnmount?: (hook: UseWebViewResult) => void | Promise<void>;\n #renderContext: RenderContext;\n\n constructor(params: HookParams, options: UseWebViewOptions<From, To>) {\n // Default to index.html if there is no URL provided.\n this.#url = options.url ?? 'index.html';\n this.#hookId = params.hookId;\n this.#onMessage = options.onMessage;\n this.#onUnmount = options.onUnmount;\n this.#renderContext = params.context;\n }\n\n /**\n * Handles UI events originating from the web view and calls associated callbacks for the Devvit app to handle.\n */\n async onUIEvent(event: UIEvent): Promise<void> {\n if (event.webView?.fullScreen) {\n const isVisible = event.webView.fullScreen.visibility === WebViewVisibility.WEBVIEW_VISIBLE;\n if (!isVisible && this.#onUnmount) await this.#onUnmount(this);\n } else if (event.webView?.postMessage) {\n // Handle messages sent from web view -> Devvit app\n\n // Fallback to deprecated message field for mobile client backwards compatibility\n const message = event.webView.postMessage.jsonString\n ? JSON.parse(event.webView.postMessage.jsonString)\n : event.webView.postMessage.message;\n\n // TODO: Temporary. Remove this filter once clients are updated.\n if (webViewMessageIsInternalAndClientScope(message)) return;\n\n await this.#onMessage(message, this);\n }\n }\n\n /**\n * Send a message from a Devvit app to a web view (fullscreen).\n */\n postMessage = (message: To): void => {\n try {\n // Encode message as JSON for consistency with the mobile clients\n const jsonString = JSON.stringify(message);\n // Handle messages sent from Devvit app -> web view\n this.#renderContext.emitEffect(`postMessage${this.state.messageCount++}`, {\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n postMessage: {\n webViewId: this.#hookId,\n app: <WebViewAppMessage>{\n message, // This is deprecated, but populated for mobile client backwards compatibility\n jsonString,\n },\n },\n },\n });\n } catch (e) {\n console.error(StringUtil.caughtToString(e));\n // Safety net if something went wrong with JSON.stringify\n throw Error('Something went wrong. Please check the contents of your postMessage.');\n }\n };\n\n /**\n * Triggers the fullscreen effect to show the web view in fullscreen mode.\n */\n mount = (): void => {\n const assets = this.#renderContext?.devvitContext?.assets;\n\n // Get the public URL for the asset. Returns an empty string if the asset is not found.\n const url = assets.getURL(this.#url, { webView: true });\n\n if (!url) {\n throw Error(`useWebView fullscreen request failed; web view asset could not be found`);\n }\n\n this.#emitFullscreenEffect(true, url);\n };\n\n /**\n * Triggers the fullscreen effect to hide the open web view.\n */\n unmount = (): void => {\n this.#emitFullscreenEffect(false, '');\n };\n\n #emitFullscreenEffect = (show: boolean, url: string): void => {\n this.#renderContext.emitEffect('fullscreen', {\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n fullscreen: {\n id: this.#hookId,\n show,\n url,\n },\n },\n });\n };\n}\n\n/**\n * Use this hook to handle a web view's visibility state and any messages sent to your app.\n * */\nexport function useWebView<From extends JSONValue = JSONValue, To extends JSONValue = JSONValue>(\n options: UseWebViewOptions<From, To>\n): UseWebViewResult<To> {\n const hook = registerHook({\n namespace: 'useWebView',\n initializer: (params) => new WebViewHook(params, options),\n });\n return {\n postMessage: hook.postMessage,\n mount: hook.mount,\n unmount: hook.unmount,\n };\n}\n", "import type {\n WebViewInternalMessage,\n WebViewInternalMessageScope,\n} from '@devvit/protos/types/devvit/ui/effects/web_view/v1alpha/post_message.js';\n\n/**\n * Messages which are scoped to \"client\" and are of type \"devvit-internal\" should not be sent to the app.\n * These messages are designed to be handled by client platforms.\n *\n * Not all clients are updated yet, so this check is temporary until clients beging filtering these messages out.\n *\n * @param message - The web view message to check\n * @returns True if the data is a WebViewInternalMessage\n **/\nexport const webViewMessageIsInternalAndClientScope = (\n message: unknown\n): message is WebViewInternalMessage => {\n if (message === null || typeof message !== 'object') return false;\n return (\n 'scope' in message &&\n message.scope === (0 satisfies WebViewInternalMessageScope.CLIENT) &&\n 'type' in message &&\n message.type === 'devvit-internal'\n );\n};\n"],
|
|
4
|
+
"sourcesContent": ["var toString = Object.prototype.toString;\n\nmodule.exports = function kindOf(val) {\n if (val === void 0) return 'undefined';\n if (val === null) return 'null';\n\n var type = typeof val;\n if (type === 'boolean') return 'boolean';\n if (type === 'string') return 'string';\n if (type === 'number') return 'number';\n if (type === 'symbol') return 'symbol';\n if (type === 'function') {\n return isGeneratorFn(val) ? 'generatorfunction' : 'function';\n }\n\n if (isArray(val)) return 'array';\n if (isBuffer(val)) return 'buffer';\n if (isArguments(val)) return 'arguments';\n if (isDate(val)) return 'date';\n if (isError(val)) return 'error';\n if (isRegexp(val)) return 'regexp';\n\n switch (ctorName(val)) {\n case 'Symbol': return 'symbol';\n case 'Promise': return 'promise';\n\n // Set, Map, WeakSet, WeakMap\n case 'WeakMap': return 'weakmap';\n case 'WeakSet': return 'weakset';\n case 'Map': return 'map';\n case 'Set': return 'set';\n\n // 8-bit typed arrays\n case 'Int8Array': return 'int8array';\n case 'Uint8Array': return 'uint8array';\n case 'Uint8ClampedArray': return 'uint8clampedarray';\n\n // 16-bit typed arrays\n case 'Int16Array': return 'int16array';\n case 'Uint16Array': return 'uint16array';\n\n // 32-bit typed arrays\n case 'Int32Array': return 'int32array';\n case 'Uint32Array': return 'uint32array';\n case 'Float32Array': return 'float32array';\n case 'Float64Array': return 'float64array';\n }\n\n if (isGeneratorObj(val)) {\n return 'generator';\n }\n\n // Non-plain objects\n type = toString.call(val);\n switch (type) {\n case '[object Object]': return 'object';\n // iterators\n case '[object Map Iterator]': return 'mapiterator';\n case '[object Set Iterator]': return 'setiterator';\n case '[object String Iterator]': return 'stringiterator';\n case '[object Array Iterator]': return 'arrayiterator';\n }\n\n // other\n return type.slice(8, -1).toLowerCase().replace(/\\s/g, '');\n};\n\nfunction ctorName(val) {\n return typeof val.constructor === 'function' ? val.constructor.name : null;\n}\n\nfunction isArray(val) {\n if (Array.isArray) return Array.isArray(val);\n return val instanceof Array;\n}\n\nfunction isError(val) {\n return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number');\n}\n\nfunction isDate(val) {\n if (val instanceof Date) return true;\n return typeof val.toDateString === 'function'\n && typeof val.getDate === 'function'\n && typeof val.setDate === 'function';\n}\n\nfunction isRegexp(val) {\n if (val instanceof RegExp) return true;\n return typeof val.flags === 'string'\n && typeof val.ignoreCase === 'boolean'\n && typeof val.multiline === 'boolean'\n && typeof val.global === 'boolean';\n}\n\nfunction isGeneratorFn(name, val) {\n return ctorName(name) === 'GeneratorFunction';\n}\n\nfunction isGeneratorObj(val) {\n return typeof val.throw === 'function'\n && typeof val.return === 'function'\n && typeof val.next === 'function';\n}\n\nfunction isArguments(val) {\n try {\n if (typeof val.length === 'number' && typeof val.callee === 'function') {\n return true;\n }\n } catch (err) {\n if (err.message.indexOf('callee') !== -1) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * If you need to support Safari 5-7 (8-10 yr-old browser),\n * take a look at https://github.com/feross/is-buffer\n */\n\nfunction isBuffer(val) {\n if (val.constructor && typeof val.constructor.isBuffer === 'function') {\n return val.constructor.isBuffer(val);\n }\n return false;\n}\n", "/*!\n * shallow-clone <https://github.com/jonschlinkert/shallow-clone>\n *\n * Copyright (c) 2015-present, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n'use strict';\n\nconst valueOf = Symbol.prototype.valueOf;\nconst typeOf = require('kind-of');\n\nfunction clone(val, deep) {\n switch (typeOf(val)) {\n case 'array':\n return val.slice();\n case 'object':\n return Object.assign({}, val);\n case 'date':\n return new val.constructor(Number(val));\n case 'map':\n return new Map(val);\n case 'set':\n return new Set(val);\n case 'buffer':\n return cloneBuffer(val);\n case 'symbol':\n return cloneSymbol(val);\n case 'arraybuffer':\n return cloneArrayBuffer(val);\n case 'float32array':\n case 'float64array':\n case 'int16array':\n case 'int32array':\n case 'int8array':\n case 'uint16array':\n case 'uint32array':\n case 'uint8clampedarray':\n case 'uint8array':\n return cloneTypedArray(val);\n case 'regexp':\n return cloneRegExp(val);\n case 'error':\n return Object.create(val);\n default: {\n return val;\n }\n }\n}\n\nfunction cloneRegExp(val) {\n const flags = val.flags !== void 0 ? val.flags : (/\\w+$/.exec(val) || void 0);\n const re = new val.constructor(val.source, flags);\n re.lastIndex = val.lastIndex;\n return re;\n}\n\nfunction cloneArrayBuffer(val) {\n const res = new val.constructor(val.byteLength);\n new Uint8Array(res).set(new Uint8Array(val));\n return res;\n}\n\nfunction cloneTypedArray(val, deep) {\n return new val.constructor(val.buffer, val.byteOffset, val.length);\n}\n\nfunction cloneBuffer(val) {\n const len = val.length;\n const buf = Buffer.allocUnsafe ? Buffer.allocUnsafe(len) : Buffer.from(len);\n val.copy(buf);\n return buf;\n}\n\nfunction cloneSymbol(val) {\n return valueOf ? Object(valueOf.call(val)) : {};\n}\n\n/**\n * Expose `clone`\n */\n\nmodule.exports = clone;\n", "/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n'use strict';\n\nmodule.exports = function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n", "/*!\n * is-plain-object <https://github.com/jonschlinkert/is-plain-object>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\n'use strict';\n\nvar isObject = require('isobject');\n\nfunction isObjectObject(o) {\n return isObject(o) === true\n && Object.prototype.toString.call(o) === '[object Object]';\n}\n\nmodule.exports = function isPlainObject(o) {\n var ctor,prot;\n\n if (isObjectObject(o) === false) return false;\n\n // If has modified constructor\n ctor = o.constructor;\n if (typeof ctor !== 'function') return false;\n\n // If has modified prototype\n prot = ctor.prototype;\n if (isObjectObject(prot) === false) return false;\n\n // If constructor does not have an Object-specific method\n if (prot.hasOwnProperty('isPrototypeOf') === false) {\n return false;\n }\n\n // Most likely a plain Object\n return true;\n};\n", "'use strict';\n\n/**\n * Module dependenices\n */\n\nconst clone = require('shallow-clone');\nconst typeOf = require('kind-of');\nconst isPlainObject = require('is-plain-object');\n\nfunction cloneDeep(val, instanceClone) {\n switch (typeOf(val)) {\n case 'object':\n return cloneObjectDeep(val, instanceClone);\n case 'array':\n return cloneArrayDeep(val, instanceClone);\n default: {\n return clone(val);\n }\n }\n}\n\nfunction cloneObjectDeep(val, instanceClone) {\n if (typeof instanceClone === 'function') {\n return instanceClone(val);\n }\n if (instanceClone || isPlainObject(val)) {\n const res = new val.constructor();\n for (let key in val) {\n res[key] = cloneDeep(val[key], instanceClone);\n }\n return res;\n }\n return val;\n}\n\nfunction cloneArrayDeep(val, instanceClone) {\n const res = new val.constructor(val.length);\n for (let i = 0; i < val.length; i++) {\n res[i] = cloneDeep(val[i], instanceClone);\n }\n return res;\n}\n\n/**\n * Expose `cloneDeep`\n */\n\nmodule.exports = cloneDeep;\n", "'use strict'\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n var i\n for (i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n", "export const RunAs = {\n APP: 0,\n USER: 1,\n} as const;\n\nexport type UserGeneratedContent = {\n text: string;\n imageUrls: string[];\n};\n", "import { type FlairCsvResult, type JsonStatus } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { T1ID, T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT3ID, asT5ID, asTID, isT1ID, isT3ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport { getMetadata } from '../../devvit/internals/async-metadata.js';\nimport type { BaseContext } from '../../types/index.js';\nimport {\n type AboutSubredditTypes,\n type AddRemovalNoteOptions,\n type AddWidgetData,\n type BanUserOptions,\n type BanWikiContributorOptions,\n type CommentSubmissionOptions,\n type CreateFlairTemplateOptions,\n type CreateModNoteOptions,\n type CreateWikiPageOptions,\n type CrosspostOptions,\n type DeleteNotesOptions,\n type EditFlairTemplateOptions,\n type GetCommentsByUserOptions,\n type GetCommentsOptions,\n getCurrentUserFromMetadata,\n type GetHotPostsOptions,\n type GetModerationLogOptions,\n type GetModNotesOptions,\n type GetPageRevisionsOptions,\n type GetPostsByUserOptions,\n type GetPostsOptions,\n type GetPostsOptionsWithTimeframe,\n type GetPrivateMessagesOptions,\n type GetSubredditUsersByTypeOptions,\n type GetUserOverviewOptions,\n type Listing,\n type ModAction,\n type ModeratorPermission,\n type ModLogOptions,\n type RemovalReason,\n type SendPrivateMessageAsSubredditOptions,\n type SendPrivateMessageOptions,\n type SetPostFlairOptions,\n type SetUserFlairBatchConfig,\n type SetUserFlairOptions,\n type SubmitPostOptions,\n type SubredditInfo,\n type SubredditLeaderboard,\n type SubredditStyles,\n type UpdatePageSettingsOptions,\n type UpdateWikiPageOptions,\n type Vault,\n type WikiPageRevision,\n type WikiPageSettings,\n} from './models/index.js';\nimport {\n _getModerationLog,\n _getSubredditInfoById,\n _getSubredditInfoByName,\n _getSubredditLeaderboard,\n _getSubredditNameById,\n _getSubredditStyles,\n AboutLocations,\n Comment,\n Flair,\n FlairTemplate,\n getCurrentUsernameFromMetadata,\n ModMailService,\n ModNote,\n Post,\n PrivateMessage,\n Subreddit,\n User,\n Widget,\n WikiPage,\n} from './models/index.js';\nimport {\n getVaultByAddress as _getVaultByAddress,\n getVaultByUserId as _getVaultByUserId,\n} from './models/Vault.js';\n\nexport type RedditContext = Pick<BaseContext, 'metadata'>;\n\ntype GetSubredditUsersOptions = Omit<GetSubredditUsersByTypeOptions, 'type'>;\n\nexport type InviteModeratorOptions = {\n /** The name of the subreddit to invite the user to moderate */\n subredditName: string;\n /** The name of the user to invite as a moderator */\n username: string;\n /** The permissions to grant the user */\n permissions?: ModeratorPermission[];\n};\n\nexport type MuteUserOptions = {\n /** The name of the subreddit to mute the user in */\n subredditName: string;\n /** The name of the user to mute */\n username: string;\n /** A mod note on why the user was muted. (optional) */\n note?: string;\n};\n\n/**\n * Get ModMail API object\n *\n * @example\n * ```ts\n * await modMail.reply({\n * body: \"Here is my message\",\n * conversationId: \"abcd42\";\n * })\n * ```\n */\nexport function modMail(): ModMailService {\n const metadata = getMetadata();\n return new ModMailService(metadata);\n}\n\n/**\n * Gets a {@link Subreddit} object by ID\n *\n * @deprecated Use {@link getSubredditInfoById} instead.\n * @param {string} id - The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n * @returns {Promise<Subreddit>} A Promise that resolves a Subreddit object.\n * @example\n * ```ts\n * const memes = await getSubredditById('t5_2qjpg');\n * ```\n */\nexport function getSubredditById(id: string): Promise<Subreddit | undefined> {\n const metadata = getMetadata();\n return Subreddit.getById(asTID<T5ID>(id), metadata);\n}\n\n/**\n * Gets a {@link SubredditInfo} object by ID\n *\n * @param {string} id - The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n * @example\n * ```ts\n * const memes = await getSubredditInfoById('t5_2qjpg');\n * ```\n */\nexport function getSubredditInfoById(id: string): Promise<SubredditInfo> {\n const metadata = getMetadata();\n return _getSubredditInfoById(id, metadata);\n}\n\n/**\n * Gets a {@link Subreddit} object by name\n *\n * @deprecated Use {@link getSubredditInfoByName} instead.\n * @param {string} name The name of a subreddit omitting the r/. This is case-insensitive.\n * @returns {Promise<Subreddit>} A Promise that resolves a Subreddit object.\n * @example\n * ```ts\n * const askReddit = await getSubredditByName('askReddit');\n * ```\n */\nexport function getSubredditByName(name: string): Promise<Subreddit> {\n const metadata = getMetadata();\n return Subreddit.getByName(name, metadata);\n}\n\n/**\n * Gets a {@link SubredditInfo} object by name\n *\n * @param {string} name The name of a subreddit omitting the r/. This is case-insensitive.\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n * @example\n * ```ts\n * const askReddit = await getSubredditInfoByName('askReddit');\n * ```\n */\nexport function getSubredditInfoByName(name: string): Promise<SubredditInfo> {\n const metadata = getMetadata();\n return _getSubredditInfoByName(name, metadata);\n}\n\n/**\n * Add a removal reason to a subreddit\n *\n * @param subredditName Name of the subreddit being removed.\n * @param options Options.\n * @param options.title The title of the removal reason.\n * @param options.message The message associated with the removal reason.\n * @example\n * ```ts\n * const newReason = await addSubredditRemovalReasons(\n * 'askReddit',\n * {\n * title: 'Spam',\n * message: 'This is spam!'\n * }\n * );\n * console.log(newReason.id)\n * ```\n *\n * @returns {string} Removal Reason ID\n */\nexport function addSubredditRemovalReason(\n subredditName: string,\n options: { title: string; message: string }\n): Promise<string> {\n const metadata = getMetadata();\n return Subreddit.addRemovalReason(subredditName, options.title, options.message, metadata);\n}\n\n/**\n * Get the list of subreddit's removal reasons (ordered)\n *\n * @param subredditName\n * @example\n * ```ts\n * const reasons = await getSubredditRemovalReasons('askReddit');\n *\n * for (let reason of reasons) {\n * console.log(reason.id, reason.message, reason.title)\n * }\n * ```\n *\n * @returns Ordered array of Removal Reasons\n */\nexport function getSubredditRemovalReasons(subredditName: string): Promise<RemovalReason[]> {\n const metadata = getMetadata();\n return Subreddit.getRemovalReasons(subredditName, metadata);\n}\n\n/**\n * Retrieves the name of the current subreddit.\n *\n * @returns {Promise<string>} A Promise that resolves a string representing the current subreddit's name.\n * @example\n * ```ts\n * const currentSubredditName = await getCurrentSubredditName();\n * ```\n */\nexport async function getCurrentSubredditName(): Promise<string> {\n const metadata = getMetadata();\n const nameFromMetadata = metadata?.[Header.SubredditName]?.values[0];\n if (nameFromMetadata) {\n return nameFromMetadata;\n }\n\n const subredditId = metadata?.[Header.Subreddit]?.values[0];\n const nameFromId = await _getSubredditNameById(asT5ID(subredditId), metadata);\n if (!nameFromId) {\n throw new Error(\"Couldn't get current subreddit's name\");\n }\n return nameFromId;\n}\n\n/**\n * Retrieves the current subreddit.\n *\n * @returns {Promise<Subreddit>} A Promise that resolves a Subreddit object.\n * @example\n * ```ts\n * const currentSubreddit = await getCurrentSubreddit();\n * ```\n */\nexport async function getCurrentSubreddit(): Promise<Subreddit> {\n const metadata = getMetadata();\n const currentSubreddit = await Subreddit.getFromMetadata(metadata);\n if (!currentSubreddit) {\n throw new Error(\"Couldn't get current subreddit\");\n }\n return currentSubreddit;\n}\n\n/**\n * Gets a {@link Post} object by ID\n *\n * @param id\n * @returns A Promise that resolves to a Post object.\n */\nexport function getPostById(id: string): Promise<Post> {\n const metadata = getMetadata();\n return Post.getById(asTID<T3ID>(id), metadata);\n}\n\n/**\n * Submits a new post to a subreddit.\n *\n * @param options - Either a self post or a link post.\n * @returns A Promise that resolves to a Post object.\n * @example\n * ```ts\n * const post = await submitPost({\n * subredditName: 'devvit',\n * title: 'Hello World',\n * richtext: new RichTextBuilder()\n * .heading({ level: 1 }, (h) => {\n * h.rawText('Hello world');\n * })\n * .codeBlock({}, (cb) => cb.rawText('This post was created via the Devvit API'))\n * .build()\n * });\n * ```\n */\nexport function submitPost(options: SubmitPostOptions): Promise<Post> {\n const metadata = getMetadata();\n return Post.submit(options, metadata);\n}\n\n/**\n * Crossposts a post to a subreddit.\n *\n * @param options - Options for crossposting a post\n * @param options.subredditName - The name of the subreddit to crosspost to\n * @param options.postId - The ID of the post to crosspost\n * @param options.title - The title of the crosspost\n * @returns - A Promise that resolves to a Post object.\n */\nexport function crosspost(options: CrosspostOptions): Promise<Post> {\n const metadata = getMetadata();\n return Post.crosspost(options, metadata);\n}\n\n/**\n * Gets a {@link User} object by ID\n *\n * @param id - The ID (starting with t2_) of the user to retrieve. e.g. t2_1qjpg\n * @returns A Promise that resolves to a User object.\n * @example\n * ```ts\n * const user = await getUserById('t2_1qjpg');\n * ```\n */\nexport function getUserById(id: string): Promise<User | undefined> {\n const metadata = getMetadata();\n return User.getById(asTID<T2ID>(id), metadata);\n}\n\n/**\n * Gets a {@link User} object by username\n *\n * @param username - The username of the user omitting the u/. e.g. 'devvit'\n * @returns A Promise that resolves to a User object or undefined if user is\n * not found (user doesn't exist, account suspended, etc).\n * @example\n * ```ts\n * const user = await getUserByUsername('devvit');\n * if (user) {\n * console.log(user)\n * }\n * ```\n */\nexport function getUserByUsername(username: string): Promise<User | undefined> {\n const metadata = getMetadata();\n return User.getByUsername(username, metadata);\n}\n\n/**\n * Get the current calling user's username.\n * Resolves to undefined for logged-out custom post renders.\n *\n * @returns A Promise that resolves to a string representing the username or undefined\n * @example\n * ```ts\n * const username = await getCurrentUsername();\n * ```\n */\nexport async function getCurrentUsername(): Promise<string | undefined> {\n const metadata = getMetadata();\n return getCurrentUsernameFromMetadata(metadata);\n}\n\n/**\n * Get the current calling user.\n * Resolves to undefined for logged-out custom post renders.\n *\n * @returns A Promise that resolves to a User object or undefined\n * @example\n * ```ts\n * const user = await getCurrentUser();\n * ```\n */\nexport async function getCurrentUser(): Promise<User | undefined> {\n const metadata = getMetadata();\n return getCurrentUserFromMetadata(metadata);\n}\n\n/**\n * Get the user that the app runs as on the provided metadata.\n *\n * @returns A Promise that resolves to a User object.\n * @example\n * ```ts\n * const user = await getAppUser(metadata);\n * ```\n */\nexport function getAppUser(): Promise<User> {\n const metadata = getMetadata();\n return User.getFromMetadata(Header.AppUser, metadata) as Promise<User>;\n}\n\n/**\n * Get the snoovatar URL for a given username.\n *\n * @param username - The username of the snoovatar to retrieve\n * @returns A Promise that resolves to a URL of the snoovatar image if it exists.\n */\nexport function getSnoovatarUrl(username: string): Promise<string | undefined> {\n const metadata = getMetadata();\n return User.getSnoovatarUrl(username, metadata);\n}\n\n/**\n * Get a {@link Comment} object by ID\n *\n * @param id - The ID (starting with t1_) of the comment to retrieve. e.g. t1_1qjpg\n * @returns A Promise that resolves to a Comment object.\n * @example\n * ```ts\n * const comment = await getCommentById('t1_1qjpg');\n * ```\n */\nexport function getCommentById(id: string): Promise<Comment> {\n const metadata = getMetadata();\n return Comment.getById(asTID<T1ID>(id), metadata);\n}\n\n/**\n * Get a list of comments from a specific post or comment.\n *\n * @param options - Options for the request\n * @param options.postId - The ID of the post e.g. 't3_1qjpg'\n * @param options.commentId - The ID of the comment e.g. 't1_1qjpg'\n * @param options.limit - The maximum number of comments to return. e.g. 1000\n * @param options.pageSize - The number of comments to return per request. e.g. 100\n * @param options.sort - The sort order of the comments. e.g. 'new'\n * @returns A Listing of Comment objects.\n * @example\n * ```ts\n * const comments = await getComments({\n * postId: 't3_1qjpg',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getComments(options: GetCommentsOptions): Listing<Comment> {\n const metadata = getMetadata();\n return Comment.getComments(options, metadata);\n}\n\n/**\n * Get a list of comments by a specific user.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user omitting the u/. e.g. 'spez'\n * @param options.sort - The sort order of the comments. e.g. 'new'\n * @param options.timeframe - The timeframe of the comments. e.g. 'all'\n * @param options.limit - The maximum number of comments to return. e.g. 1000\n * @param options.pageSize - The number of comments to return per request. e.g. 100\n * @returns A Listing of Comment objects.\n */\nexport function getCommentsByUser(options: GetCommentsByUserOptions): Listing<Comment> {\n const metadata = getMetadata();\n return Comment.getCommentsByUser(options, metadata);\n}\n\n/**\n * Submit a new comment to a post or comment.\n *\n * @param options - You must provide either `options.text` or `options.richtext` but not both.\n * @param options.id - The ID of the post or comment to comment on. e.g. 't3_1qjpg' for post and 't1_1qgif' for comment\n * @param options.text - The text of the comment\n * @param options.richtext - The rich text of the comment\n * @returns A Promise that resolves to a Comment object.\n */\nexport function submitComment(\n options: CommentSubmissionOptions & { id: string }\n): Promise<Comment> {\n const metadata = getMetadata();\n return Comment.submit(\n {\n ...options,\n id: asTID<T3ID | T1ID>(options.id),\n },\n metadata\n );\n}\n\n/**\n * Get a list of controversial posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getControversialPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getControversialPosts(options: GetPostsOptionsWithTimeframe): Listing<Post> {\n const metadata = getMetadata();\n return Post.getControversialPosts(options, metadata);\n}\n\n/**\n * Get a list of controversial posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getControversialPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getTopPosts(options: GetPostsOptionsWithTimeframe): Listing<Post> {\n const metadata = getMetadata();\n return Post.getTopPosts(options, metadata);\n}\n\n/**\n * Get a list of hot posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getHotPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getHotPosts(options: GetHotPostsOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getHotPosts(options, metadata);\n}\n\n/**\n * Get a list of new posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getNewPosts({\n * subredditName: 'memes',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getNewPosts(options: GetPostsOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getNewPosts(options, metadata);\n}\n\n/**\n * Get a list of hot posts from a specific subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get posts from. e.g. 'memes'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n * @example\n * ```ts\n * const posts = await getRisingPosts({\n * subredditName: 'memes',\n * timeframe: 'day',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getRisingPosts(options: GetPostsOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getRisingPosts(options, metadata);\n}\n\n/**\n * Get a list of posts from a specific user.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user omitting the u/. e.g. 'spez'\n * @param options.sort - The sort method to use. e.g. 'new'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n */\nexport function getPostsByUser(options: GetPostsByUserOptions): Listing<Post> {\n const metadata = getMetadata();\n return Post.getPostsByUser(options, metadata);\n}\n\n/**\n * Get a list of posts and comments from a specific user.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user omitting the u/. e.g. 'spez'\n * @param options.sort - The sort method to use. e.g. 'new'\n * @param options.timeframe - The timeframe to get posts from. e.g. 'day'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of `Post` and `Comment` objects.\n */\nexport function getCommentsAndPostsByUser(\n options: GetUserOverviewOptions\n): Listing<Post | Comment> {\n const metadata = getMetadata();\n return User.getOverview(options, metadata);\n}\n\n/**\n * Get the moderation log for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the moderation log from. e.g. 'memes'\n * @param options.moderatorUsernames (optional) A moderator filter. Accepts an array of usernames\n * @param options.type (optional) Filter the entries by the type of the Moderator action\n * @param options.limit - (optional) The maximum number of ModActions to return. e.g. 1000\n * @param options.pageSize - (optional) The number of ModActions to return per request. e.g. 100\n * @returns A Listing of ModAction objects.\n * @example\n * ```ts\n * const modActions = await getModerationLog({\n * subredditName: 'memes',\n * moderatorUsernames: ['spez'],\n * type: 'banuser',\n * limit: 1000,\n * pageSize: 100\n * }).all();\n * ```\n */\nexport function getModerationLog(options: GetModerationLogOptions): Listing<ModAction> {\n const metadata = getMetadata();\n return _getModerationLog(options, metadata);\n}\n\n/**\n * Get a list of users who have been approved to post in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the approved users from. e.g. 'memes'\n * @param options.username - Use this to see if a user is approved to post in the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getApprovedUsers(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'contributors',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Approve a user to post in a subreddit.\n *\n * @param username - The username of the user to approve. e.g. 'spez'\n * @param subredditName - The name of the subreddit to approve the user in. e.g. 'memes'\n */\nexport function approveUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n username,\n subredditName,\n type: 'contributor',\n },\n metadata\n );\n}\n\n/**\n * Remove a user's approval to post in a subreddit.\n *\n * @param username - The username of the user to remove approval from. e.g. 'spez'\n * @param subredditName - The name of the subreddit to remove the user's approval from. e.g. 'memes'\n */\nexport function removeUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'contributor',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are wiki contributors of a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the wiki contributors from. e.g. 'memes'\n * @param options.username - Use this to see if a user is a wiki contributor for the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getWikiContributors(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'wikicontributors',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Add a user as a wiki contributor for a subreddit.\n *\n * @param username - The username of the user to add as a wiki contributor. e.g. 'spez'\n * @param subredditName - The name of the subreddit to add the user as a wiki contributor. e.g. 'memes'\n */\nexport function addWikiContributor(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n username,\n subredditName,\n type: 'wikicontributor',\n },\n metadata\n );\n}\n\n/**\n * Remove a user's wiki contributor status for a subreddit.\n *\n * @param username - The username of the user to remove wiki contributor status from. e.g. 'spez'\n * @param subredditName - The name of the subreddit to remove the user's wiki contributor status from. e.g. 'memes'\n */\nexport function removeWikiContributor(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'wikicontributor',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are banned from a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the banned users from. e.g. 'memes'\n * @param options.username - Use this to see if a user is banned from the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getBannedUsers(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'banned',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Ban a user from a subreddit.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to ban. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to ban the user from. e.g. 'memes'\n * @param options.note - A mod note for the ban. (optional)\n * @param options.duration - The number of days the user should be banned for. (optional)\n * @param options.message - A message to send to the user when they are banned. (optional)\n * @param options.context - The ID of the post or comment that caused the ban. (optional)\n * @param options.reason - The reason for the ban. (optional)\n */\nexport function banUser(options: BanUserOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n username: options.username,\n subredditName: options.subredditName,\n type: 'banned',\n banReason: options.reason,\n banMessage: options.message,\n note: options.note,\n duration: options.duration,\n banContext: options.context,\n },\n metadata\n );\n}\n\n/**\n * Unban a user from a subreddit.\n *\n * @param username - The username of the user to unban. e.g. 'spez'\n * @param subredditName - The name of the subreddit to unban the user from. e.g. 'memes'\n */\nexport function unbanUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'banned',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are banned from contributing to the wiki on a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the banned wiki contributors from. e.g. 'memes'\n * @param options.username - Use this to see if a user is banned from contributing to the wiki on a subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getBannedWikiContributors(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'wikibanned',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Ban a user from contributing to the wiki on a subreddit.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to ban. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to ban the user from contributing to the wiki on. e.g. 'memes'\n * @param options.reason - The reason for the ban. (optional)\n * @param options.duration - The number of days the user should be banned for. (optional)\n * @param options.note - A mod note for the ban. (optional)\n */\nexport function banWikiContributor(options: BanWikiContributorOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n ...options,\n type: 'wikibanned',\n },\n metadata\n );\n}\n\n/**\n *\n * @param username - The username of the user to unban. e.g. 'spez'\n * @param subredditName - The name of the subreddit to unban the user from contributing to the wiki on. e.g. 'memes'\n */\nexport function unbanWikiContributor(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'wikibanned',\n },\n metadata\n );\n}\n\n/**\n * Get a list of users who are moderators for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the moderators from. e.g. 'memes'\n * @param options.username - Use this to see if a user is a moderator of the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A Listing of User objects.\n */\nexport function getModerators(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'moderators',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Invite a user to become a moderator of a subreddit.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to invite. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to invite the user to moderate. e.g. 'memes'\n * @param options.permissions - The permissions to give the user. (optional) Defaults to 'all'.\n */\nexport function inviteModerator(options: InviteModeratorOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n type: 'moderator_invite',\n subredditName: options.subredditName,\n username: options.username,\n permissions: options.permissions ?? [],\n },\n metadata\n );\n}\n\n/**\n * Revoke a moderator invite for a user to a subreddit.\n *\n * @param username - The username of the user to revoke the invite for. e.g. 'spez'\n * @param subredditName - The name of the subreddit to revoke the invite for. e.g. 'memes'\n */\nexport function revokeModeratorInvite(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'moderator_invite',\n },\n metadata\n );\n}\n\n/**\n * Remove a user as a moderator of a subreddit.\n *\n * @param username - The username of the user to remove as a moderator. e.g. 'spez'\n * @param subredditName - The name of the subreddit to remove the user as a moderator from. e.g. 'memes'\n */\nexport function removeModerator(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n type: 'moderator',\n subredditName,\n username,\n },\n metadata\n );\n}\n\n/**\n * Update the permissions of a moderator of a subreddit.\n *\n * @param username - The username of the user to update the permissions for. e.g. 'spez'\n * @param subredditName - The name of the subreddit. e.g. 'memes'\n * @param permissions - The permissions to give the user. e.g ['posts', 'wiki']\n */\nexport function setModeratorPermissions(\n username: string,\n subredditName: string,\n permissions: ModeratorPermission[]\n): Promise<void> {\n const metadata = getMetadata();\n return User.setModeratorPermissions(username, subredditName, permissions, metadata);\n}\n\n/**\n * Get a list of users who are muted in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the muted users from. e.g. 'memes'\n * @param options.username - Use this to see if a user is muted in the subreddit.\n * @param options.limit - The maximum number of users to return. e.g. 1000\n * @param options.pageSize - The number of users to return per request. e.g. 100\n * @returns A listing of User objects.\n */\nexport function getMutedUsers(options: GetSubredditUsersOptions): Listing<User> {\n const metadata = getMetadata();\n return User.getSubredditUsersByType(\n {\n type: 'muted',\n ...options,\n },\n metadata\n );\n}\n\n/**\n * Mute a user in a subreddit. Muting a user prevents them from sending modmail.\n *\n * @param options - Options for the request\n * @param options.username - The username of the user to mute. e.g. 'spez'\n * @param options.subredditName - The name of the subreddit to mute the user in. e.g. 'memes'\n * @param options.note - A mod note on why the user was muted. (optional)\n */\nexport function muteUser(options: MuteUserOptions): Promise<void> {\n const metadata = getMetadata();\n return User.createRelationship(\n {\n ...options,\n type: 'muted',\n },\n metadata\n );\n}\n\n/**\n * Unmute a user in a subreddit. Unmuting a user allows them to send modmail.\n *\n * @param username - The username of the user to unmute. e.g. 'spez'\n * @param subredditName - The name of the subreddit to unmute the user in. e.g. 'memes'\n */\nexport function unmuteUser(username: string, subredditName: string): Promise<void> {\n const metadata = getMetadata();\n return User.removeRelationship(\n {\n username,\n subredditName,\n type: 'muted',\n },\n metadata\n );\n}\n\n/**\n * Get a list of mod notes related to a user in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to get the mod notes from. e.g. 'memes'\n * @param options.username - The username of the user to get the mod notes for. e.g. 'spez'\n * @param options.filter - Filter the mod notes by type. e.g. 'NOTE', 'BAN', 'APPROVAL'\n * @param options.limit - The maximum number of mod notes to return. e.g. 1000\n * @param options.pageSize - The number of mod notes to return per request. e.g. 100\n * @returns A listing of ModNote objects.\n */\nexport function getModNotes(options: GetModNotesOptions): Listing<ModNote> {\n const metadata = getMetadata();\n return ModNote.get(options, metadata);\n}\n\n/**\n * Delete a mod note.\n *\n * @param options - Options for the request\n * @param options.subreddit - The name of the subreddit to delete the mod note from. e.g. 'memes'\n * @param options.noteId - The ID of the mod note to delete (should have a ModNote_ prefix).\n * @returns True if it was deleted successfully; false otherwise.\n */\nexport function deleteModNote(options: DeleteNotesOptions): Promise<boolean> {\n const metadata = getMetadata();\n return ModNote.delete(options, metadata);\n}\n\n/**\n * Add a mod note.\n *\n * @param options - Options for the request\n * @param options.subreddit - The name of the subreddit to add the mod note to. e.g. 'memes'\n * @param options.user - The username of the user to add the mod note to. e.g. 'spez'\n * @param options.redditId - (optional) The ID of the comment or post to add the mod note to. e.g. 't3_1234'\n * @param options.label - (optional) The label of the mod note. e.g. 'SPAM_WARNING'\n * @param options.note - The text of the mod note.\n * @returns A Promise that resolves if the mod note was successfully added.\n */\nexport function addModNote(options: CreateModNoteOptions): Promise<ModNote> {\n const metadata = getMetadata();\n const req = {\n ...options,\n redditId: options.redditId ? asTID<T1ID | T3ID>(options.redditId) : undefined,\n };\n return ModNote.add(req, metadata);\n}\n\n/**\n * Add a mod note for why a post or comment was removed\n *\n * @param options - The options for adding a removal note.\n * @param options.itemIds list of thing ids\n * @param options.reasonId id of a Removal Reason - you can leave this as an empty string if you don't have one\n * @param options.modNote the reason for removal (maximum 100 characters) (optional)\n */\nexport function addRemovalNote(options: AddRemovalNoteOptions): Promise<void> {\n const metadata = getMetadata();\n return ModNote.addRemovalNote(options, metadata);\n}\n\n/**\n * Sends a private message to a user.\n *\n * @param options - The options for sending the message.\n * @returns A Promise that resolves if the private message was successfully sent.\n */\nexport async function sendPrivateMessage(options: SendPrivateMessageOptions): Promise<void> {\n const metadata = getMetadata();\n return PrivateMessage.send(options, metadata);\n}\n\n/**\n * Sends a private message to a user on behalf of a subreddit.\n *\n * @param options - The options for sending the message as a subreddit.\n * @returns A Promise that resolves if the private message was successfully sent.\n */\nexport async function sendPrivateMessageAsSubreddit(\n options: SendPrivateMessageAsSubredditOptions\n): Promise<void> {\n const metadata = getMetadata();\n return PrivateMessage.sendAsSubreddit(options, metadata);\n}\n\n/**\n * Approve a post or comment.\n *\n * @param id - The id of the post (t3_) or comment (t1_) to approve.\n * @example\n * ```ts\n * await approve('t3_123456');\n * await approve('t1_123456');\n * ```\n */\nexport async function approve(id: string): Promise<void> {\n const metadata = getMetadata();\n if (isT1ID(id)) {\n return Comment.approve(id, metadata);\n } else if (isT3ID(id)) {\n return Post.approve(id, metadata);\n }\n\n throw new Error('id must start with either t1_ or t3_');\n}\n\n/**\n * Remove a post or comment.\n *\n * @param id - The id of the post (t3_) or comment (t1_) to remove.\n * @param isSpam - Is the post or comment being removed because it's spam?\n * @example\n * ```ts\n * await remove('t3_123456', false);\n * await remove('t1_123456', true);\n * ```\n */\nexport async function remove(id: string, isSpam: boolean): Promise<void> {\n const metadata = getMetadata();\n if (isT1ID(id)) {\n return Comment.remove(id, isSpam, metadata);\n } else if (isT3ID(id)) {\n return Post.remove(id, isSpam, metadata);\n }\n\n throw new Error('id must start with either t1_ or t3_');\n}\n\n/**\n * Get the list of post flair templates for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the post flair templates for.\n * @returns A Promise that resolves with an array of FlairTemplate objects.\n */\nexport async function getPostFlairTemplates(subredditName: string): Promise<FlairTemplate[]> {\n const metadata = getMetadata();\n return FlairTemplate.getPostFlairTemplates(subredditName, metadata);\n}\n\n/**\n * Get the list of user flair templates for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the user flair templates for.\n * @returns A Promise that resolves with an array of FlairTemplate objects.\n */\nexport async function getUserFlairTemplates(subredditName: string): Promise<FlairTemplate[]> {\n const metadata = getMetadata();\n return FlairTemplate.getUserFlairTemplates(subredditName, metadata);\n}\n\n/**\n * Create a post flair template for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to create the flair template for.\n * @param options.allowableContent - The content that is allowed to be used with this flair template. e.g. 'all' or 'text' or 'emoji'\n * @param options.backgroundColor - The background color of the flair template. e.g. '#ff0000' or 'transparent'\n * @param options.maxEmojis - The maximum number of emojis that can be used with this flair template.\n * @param options.modOnly - Whether or not this flair template is only available to mods.\n * @param options.text - The text of the flair template.\n * @param options.textColor - The text color of the flair template. Either 'dark' or 'light'.\n * @param options.allowUserEdits - Whether or not users can edit the flair template when selecting a flair.\n * @returns The created FlairTemplate object.\n */\nexport async function createPostFlairTemplate(\n options: CreateFlairTemplateOptions\n): Promise<FlairTemplate> {\n const metadata = getMetadata();\n return FlairTemplate.createPostFlairTemplate(options, metadata);\n}\n\n/**\n * Create a user flair template for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to create the flair template for.\n * @param options.allowableContent - The content that is allowed to be used with this flair template. e.g. 'all' or 'text' or 'emoji'\n * @param options.backgroundColor - The background color of the flair template. e.g. '#ff0000' or 'transparent'\n * @param options.maxEmojis - The maximum number of emojis that can be used with this flair template.\n * @param options.modOnly - Whether or not this flair template is only available to mods.\n * @param options.text - The text of the flair template.\n * @param options.textColor - The text color of the flair template. Either 'dark' or 'light'.\n * @param options.allowUserEdits - Whether or not users can edit the flair template when selecting a flair.\n * @returns The created FlairTemplate object.\n */\nexport async function createUserFlairTemplate(\n options: CreateFlairTemplateOptions\n): Promise<FlairTemplate> {\n const metadata = getMetadata();\n return FlairTemplate.createUserFlairTemplate(options, metadata);\n}\n\n/**\n * Edit a flair template for a subreddit. This can be either a post or user flair template.\n * Note: If you leave any of the options fields as undefined, they will reset to their default values.\n *\n * @param options - Options for the request\n * @param options.id - The ID of the flair template to edit.\n * @param options.subredditName - The name of the subreddit to create the flair template for.\n * @param options.allowableContent - The content that is allowed to be used with this flair template. e.g. 'all' or 'text' or 'emoji'\n * @param options.backgroundColor - The background color of the flair template. e.g. '#ff0000' or 'transparent'\n * @param options.maxEmojis - The maximum number of emojis that can be used with this flair template.\n * @param options.modOnly - Is this flair template only available to mods?\n * @param options.text - The text of the flair template.\n * @param options.textColor - The text color of the flair template. Either 'dark' or 'light'.\n * @param options.allowUserEdits - Can users can edit the flair template when selecting a flair?\n * @returns The edited FlairTemplate object.\n */\nexport async function editFlairTemplate(options: EditFlairTemplateOptions): Promise<FlairTemplate> {\n const metadata = getMetadata();\n return FlairTemplate.editFlairTemplate(options, metadata);\n}\n\n/**\n * Delete a flair template from a subreddit.\n *\n * @param subredditName - The name of the subreddit to delete the flair template from.\n * @param flairTemplateId - The ID of the flair template to delete.\n */\nexport async function deleteFlairTemplate(\n subredditName: string,\n flairTemplateId: string\n): Promise<void> {\n const metadata = getMetadata();\n return FlairTemplate.deleteFlairTemplate(subredditName, flairTemplateId, metadata);\n}\n\n/**\n * Set the flair for a user in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to set the flair for.\n * @param options.username - The username of the user to set the flair for.\n * @param options.flairTemplateId - The ID of the flair template to use.\n * @param options.text - The text of the flair.\n * @param options.cssClass - The CSS class of the flair.\n * @param options.backgroundColor - The background color of the flair.\n * @param options.textColor - The text color of the flair.\n */\nexport async function setUserFlair(options: SetUserFlairOptions): Promise<void> {\n const metadata = getMetadata();\n return Flair.setUserFlair(options, metadata);\n}\n\n/**\n * Set the flair of multiple users in the same subreddit with a single API call.\n * Can process up to 100 entries at once.\n *\n * @param subredditName - The name of the subreddit to edit flairs in.\n * @param {SetUserFlairBatchConfig[]} flairs - Array of user flair configuration objects. If both text and cssClass are empty for a given user the flair will be cleared.\n * @param flairs[].username - The username of the user to edit the flair for.\n * @param flairs[].text - The text of the flair.\n * @param flairs[].cssClass - The CSS class of the flair.\n * @returns {FlairCsvResult[]} - Array of statuses for each entry provided.\n */\nexport async function setUserFlairBatch(\n subredditName: string,\n flairs: SetUserFlairBatchConfig[]\n): Promise<FlairCsvResult[]> {\n const metadata = getMetadata();\n return Flair.setUserFlairBatch(subredditName, flairs, metadata);\n}\n\n/**\n * Remove the flair for a user in a subreddit.\n *\n * @param subredditName - The name of the subreddit to remove the flair from.\n * @param username - The username of the user to remove the flair from.\n */\nexport async function removeUserFlair(subredditName: string, username: string): Promise<void> {\n const metadata = getMetadata();\n return Flair.removeUserFlair(subredditName, username, metadata);\n}\n\n/**\n * Set the flair for a post in a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit to set the flair for.\n * @param options.postId - The ID of the post to set the flair for.\n * @param options.flairTemplateId - The ID of the flair template to use.\n * @param options.text - The text of the flair.\n * @param options.cssClass - The CSS class of the flair.\n * @param options.backgroundColor - The background color of the flair.\n * @param options.textColor - The text color of the flair.\n */\nexport async function setPostFlair(options: SetPostFlairOptions): Promise<void> {\n const metadata = getMetadata();\n return Flair.setPostFlair(options, metadata);\n}\n\n/**\n * Remove the flair for a post in a subreddit.\n *\n * @param subredditName - The name of the subreddit to remove the flair from.\n * @param postId - The ID of the post to remove the flair from.\n */\nexport async function removePostFlair(subredditName: string, postId: string): Promise<void> {\n const metadata = getMetadata();\n return Flair.removePostFlair(subredditName, asT3ID(postId), metadata);\n}\n\n/**\n * Get the widgets for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the widgets for.\n * @returns - An array of Widget objects.\n */\nexport async function getWidgets(subredditName: string): Promise<Widget[]> {\n const metadata = getMetadata();\n return Widget.getWidgets(subredditName, metadata);\n}\n\n/**\n * Delete a widget from a subreddit.\n *\n * @param subredditName - The name of the subreddit to delete the widget from.\n * @param widgetId - The ID of the widget to delete.\n */\nexport async function deleteWidget(subredditName: string, widgetId: string): Promise<void> {\n const metadata = getMetadata();\n return Widget.delete(subredditName, widgetId, metadata);\n}\n\n/**\n * Add a widget to a subreddit.\n *\n * @param widgetData - The data for the widget to add.\n * @returns - The added Widget object.\n */\nexport async function addWidget(widgetData: AddWidgetData): Promise<Widget> {\n const metadata = getMetadata();\n return Widget.add(widgetData, metadata);\n}\n\n/**\n * Reorder the widgets for a subreddit.\n *\n * @param subredditName - The name of the subreddit to reorder the widgets for.\n * @param orderByIds - An array of widget IDs in the order that they should be displayed.\n */\nexport async function reorderWidgets(subredditName: string, orderByIds: string[]): Promise<void> {\n const metadata = getMetadata();\n return Widget.reorder(subredditName, orderByIds, metadata);\n}\n\n/**\n * Get a wiki page from a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the wiki page from.\n * @param page - The name of the wiki page to get.\n * @returns The requested WikiPage object.\n */\nexport async function getWikiPage(subredditName: string, page: string): Promise<WikiPage> {\n const metadata = getMetadata();\n return WikiPage.getPage(subredditName, page, metadata);\n}\n\n/**\n * Get the wiki pages for a subreddit.\n *\n * @param subredditName - The name of the subreddit to get the wiki pages from.\n * @returns A list of the wiki page names for the subreddit.\n */\nexport async function getWikiPages(subredditName: string): Promise<string[]> {\n const metadata = getMetadata();\n return WikiPage.getPages(subredditName, metadata);\n}\n\n/**\n * Create a new wiki page for a subreddit.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to create.\n * @param options.content - The Markdown content of the wiki page.\n * @param options.reason - The reason for creating the wiki page.\n * @returns - The created WikiPage object.\n */\nexport async function createWikiPage(options: CreateWikiPageOptions): Promise<WikiPage> {\n const metadata = getMetadata();\n return WikiPage.createPage(options, metadata);\n}\n\n/**\n * Update a wiki page.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to update.\n * @param options.content - The Markdown content of the wiki page.\n * @param options.reason - The reason for updating the wiki page.\n * @returns The updated WikiPage object.\n */\nexport async function updateWikiPage(options: UpdateWikiPageOptions): Promise<WikiPage> {\n const metadata = getMetadata();\n return WikiPage.updatePage(options, metadata);\n}\n\n/**\n * Get the revisions for a wiki page.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to get the revisions for.\n * @param options.limit - The maximum number of revisions to return.\n * @param options.after - The ID of the revision to start after.\n * @returns A Listing of WikiPageRevision objects.\n */\nexport function getWikiPageRevisions(options: GetPageRevisionsOptions): Listing<WikiPageRevision> {\n const metadata = getMetadata();\n return WikiPage.getPageRevisions(options, metadata);\n}\n\n/**\n * Revert a wiki page to a previous revision.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to revert.\n * @param revisionId - The ID of the revision to revert to.\n */\nexport async function revertWikiPage(\n subredditName: string,\n page: string,\n revisionId: string\n): Promise<void> {\n const metadata = getMetadata();\n return WikiPage.revertPage(subredditName, page, revisionId, metadata);\n}\n\n/**\n * Get the settings for a wiki page.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to get the settings for.\n * @returns A WikiPageSettings object.\n */\nexport async function getWikiPageSettings(\n subredditName: string,\n page: string\n): Promise<WikiPageSettings> {\n const metadata = getMetadata();\n return WikiPage.getPageSettings(subredditName, page, metadata);\n}\n\n/**\n * Update the settings for a wiki page.\n *\n * @param options - Options for the request\n * @param options.subredditName - The name of the subreddit the wiki is in.\n * @param options.page - The name of the wiki page to update the settings for.\n * @param options.listed - Whether the wiki page should be listed in the wiki index.\n * @param options.permLevel - The permission level required to edit the wiki page.\n * @returns A WikiPageSettings object.\n */\nexport async function updateWikiPageSettings(\n options: UpdatePageSettingsOptions\n): Promise<WikiPageSettings> {\n const metadata = getMetadata();\n return WikiPage.updatePageSettings(options, metadata);\n}\n\n/**\n * Add an editor to a wiki page.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to add the editor to.\n * @param username - The username of the user to add as an editor.\n */\nexport async function addEditorToWikiPage(\n subredditName: string,\n page: string,\n username: string\n): Promise<void> {\n const metadata = getMetadata();\n return WikiPage.addEditor(subredditName, page, username, metadata);\n}\n\n/**\n * Remove an editor from a wiki page.\n *\n * @param subredditName - The name of the subreddit the wiki is in.\n * @param page - The name of the wiki page to remove the editor from.\n * @param username - The username of the user to remove as an editor.\n */\nexport async function removeEditorFromWikiPage(\n subredditName: string,\n page: string,\n username: string\n): Promise<void> {\n const metadata = getMetadata();\n return WikiPage.removeEditor(subredditName, page, username, metadata);\n}\n\n/**\n * Get private messages sent to the currently authenticated user.\n *\n * @param options - Options for the request\n * @param options.type - The type of messages to get.\n */\nexport function getMessages(options: GetPrivateMessagesOptions): Promise<Listing<PrivateMessage>> {\n const metadata = getMetadata();\n return PrivateMessage.getMessages(options, metadata);\n}\n\n/**\n * Mark all private messages as read.\n *\n */\nexport function markAllMessagesAsRead(): Promise<void> {\n const metadata = getMetadata();\n return PrivateMessage.markAllAsRead(metadata);\n}\n\n/**\n * Report a Post or Comment\n *\n * The report is sent to the moderators of the subreddit for review.\n *\n * @param thing Post or Comment\n * @param options Options\n * @param options.reason Why the thing is reported\n *\n * @example\n * ```ts\n * await report(post, {\n * reason: 'This is spam!',\n * })\n * ```\n */\nexport function report(thing: Post | Comment, options: { reason: string }): Promise<JsonStatus> {\n const metadata = getMetadata();\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n return client.Report(\n {\n reason: options.reason,\n thingId: thing.id,\n srName: thing.subredditName,\n usernames: thing.authorName,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things requiring moderator review, such as reported things and items.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getModQueue();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getModQueue({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getModQueue(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getModQueue(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getModQueue(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getModQueue(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Modqueue,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have been reported.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getReports();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getReports({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getReports(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getReports(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getReports(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getReports(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Reports,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have been marked as spam or otherwise removed.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getSpam();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getSpam({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getSpam(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getSpam(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getSpam(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getSpam(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Spam,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have yet to be approved/removed by a mod.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getUnmoderated();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getUnmoderated({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getUnmoderated(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getUnmoderated(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getUnmoderated(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getUnmoderated(\n options: ModLogOptions<AboutSubredditTypes>\n): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Unmoderated,\n },\n metadata\n );\n}\n\n/**\n * Return a listing of things that have been edited recently.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getEdited();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getEdited({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\nexport function getEdited(options: ModLogOptions<'comment'>): Listing<Comment>;\nexport function getEdited(options: ModLogOptions<'post'>): Listing<Post>;\nexport function getEdited(options: ModLogOptions<'all'>): Listing<Post | Comment>;\nexport function getEdited(options: ModLogOptions<AboutSubredditTypes>): Listing<Post | Comment> {\n const metadata = getMetadata();\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Edited,\n },\n metadata\n );\n}\n\n/**\n * Gets a {@link Vault} for the specified address.\n *\n * @param {string} address - The address (starting with 0x) of the Vault.\n * @example\n * ```ts\n * const vault = await getVaultByAddress('0x205ee28744456bDBf180A0Fa7De51e0F116d54Ed');\n * ```\n */\nexport function getVaultByAddress(address: string): Promise<Vault> {\n const metadata = getMetadata();\n return _getVaultByAddress(address, metadata);\n}\n\n/**\n * Gets a {@link Vault} for the specified user.\n *\n * @param {string} userId - The ID (starting with t2_) of the Vault owner.\n * @example\n * ```ts\n * const vault = await getVaultByUserId('t2_1w72');\n * ```\n */\nexport function getVaultByUserId(userId: string): Promise<Vault> {\n const metadata = getMetadata();\n return _getVaultByUserId(asTID<T2ID>(userId), metadata);\n}\n\n/**\n * Returns a leaderboard for a given subreddit ID.\n *\n * @param subredditId ID of the subreddit for which the leaderboard is being queried.\n *\n * @returns {SubredditLeaderboard} Leaderboard for the given subreddit.\n */\nexport function getSubredditLeaderboard(subredditId: string): Promise<SubredditLeaderboard> {\n const metadata = getMetadata();\n return _getSubredditLeaderboard(subredditId, metadata);\n}\n\n/**\n * Returns the styles for a given subreddit ID.\n *\n * @param subredditId ID of the subreddit from which to retrieve the styles.\n *\n * @returns {SubredditStyles} Styles for the given subreddit.\n */\nexport function getSubredditStyles(subredditId: string): Promise<SubredditStyles> {\n const metadata = getMetadata();\n return _getSubredditStyles(subredditId, metadata);\n}\n\n/**\n * Subscribes to the subreddit in which the app is installed. No-op if the user is already subscribed.\n * This method will execute as the app account by default.\n * To subscribe on behalf of a user, please contact Reddit.\n */\nexport async function subscribeToCurrentSubreddit(): Promise<void> {\n const metadata = getMetadata();\n const currentSubreddit = await getCurrentSubreddit();\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n await client.Subscribe(\n {\n action: 'sub',\n actionSource: '',\n srName: currentSubreddit.name,\n sr: '',\n skipInitialDefaults: true,\n },\n metadata\n );\n}\n\n/**\n * Unsubscribes from the subreddit in which the app is installed. No-op if the user isn't subscribed.\n * This method will execute as the app account by default.\n * To unsubscribe on behalf of a user, please contact Reddit.\n */\nexport async function unsubscribeFromCurrentSubreddit(): Promise<void> {\n const metadata = getMetadata();\n const currentSubreddit = await getCurrentSubreddit();\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n await client.Subscribe(\n {\n action: 'unsub',\n actionSource: '',\n srName: currentSubreddit.name,\n sr: '',\n skipInitialDefaults: false,\n },\n metadata\n );\n}\n", "/**\n * Metadata header key. Every system header should start with \"devvit-\".\n *\n * Synchronize to headers.md.\n */\nexport const Header = Object.freeze({\n Actor: 'devvit-actor',\n App: 'devvit-app',\n AppUser: 'devvit-app-user',\n AppViewerAuthToken: 'devvit-app-viewer-authorization',\n Caller: 'devvit-caller',\n CallerPortID: 'devvit-caller-port-id',\n Canary: 'devvit-canary',\n Debug: 'devvit-debug',\n GQLHost: 'devvit-gql-host',\n Installation: 'devvit-installation',\n ModPermissions: 'devvit-mod-permissions',\n Post: 'devvit-post',\n PostAuthor: 'devvit-post-author',\n R2Auth: 'devvit-sec-authorization',\n R2Host: 'devvit-r2-host',\n RemoteHostname: 'devvit-remote-hostname',\n SettingsUri: 'devvit-sec-settings-uri',\n StreamID: 'devvit-stream-id',\n Subreddit: 'devvit-subreddit',\n SubredditName: 'devvit-subreddit-name',\n TraceID: 'devvit-trace-id',\n User: 'devvit-user',\n Username: 'devvit-user-name',\n UserAgent: 'devvit-user-agent',\n Version: 'devvit-version',\n Language: 'devvit-accept-language',\n Timezone: 'devvit-accept-timezone',\n Traceparent: 'traceparent',\n AppDependencies: 'devvit-app-dependencies',\n});\n/** See DevvitGlobal and ContextDebugInfo. */\nexport var AppDebug;\n(function (AppDebug) {\n /** Enable debug logging for blocks. */\n AppDebug[\"Blocks\"] = \"blocks\";\n /**\n * Log the entire reified blocks JSX/XML tree on each render. Eg:\n *\n * <hstack><text>hi world</text></hstack>\n */\n AppDebug[\"EmitSnapshots\"] = \"emitSnapshots\";\n /** Log app state changes. */\n AppDebug[\"EmitState\"] = \"emitState\";\n /** Enable debug logging for realtime and useChannel() hook. */\n AppDebug[\"Realtime\"] = \"realtime\";\n /** Enable runtime and dispatcher logging. */\n AppDebug[\"Runtime\"] = \"runtime\";\n /** Enable debug logging for devvit-surface and dispatcher. */\n AppDebug[\"Surface\"] = \"surface\";\n /** Enable debug logging for the useAsync() hook family. */\n AppDebug[\"UseAsync\"] = \"useAsync\";\n /** Enable debug logging for payments APIs */\n AppDebug[\"Payments\"] = \"payments\";\n /** Enable bootstrap logging */\n AppDebug[\"Bootstrap\"] = \"bootstrap\";\n /** WebView debug logs */\n AppDebug[\"WebView\"] = \"webView\";\n})(AppDebug || (AppDebug = {}));\n", "export function assert(condition, msg) {\n if (!condition)\n throw Error(msg);\n}\n", "import { assert } from './assert.js';\n// CALMS!\nexport var T_PREFIX;\n(function (T_PREFIX) {\n T_PREFIX[\"COMMENT\"] = \"t1_\";\n T_PREFIX[\"ACCOUNT\"] = \"t2_\";\n T_PREFIX[\"LINK\"] = \"t3_\";\n T_PREFIX[\"MESSAGE\"] = \"t4_\";\n T_PREFIX[\"SUBREDDIT\"] = \"t5_\";\n T_PREFIX[\"AWARD\"] = \"t6_\";\n})(T_PREFIX || (T_PREFIX = {}));\n// type guards\nexport function isT1ID(id) {\n return id.startsWith(T_PREFIX.COMMENT);\n}\nexport function isT2ID(id) {\n return id.startsWith(T_PREFIX.ACCOUNT);\n}\nexport function isT3ID(id) {\n return id.startsWith(T_PREFIX.LINK);\n}\nexport function isT4ID(id) {\n return id.startsWith(T_PREFIX.MESSAGE);\n}\nexport function isT5ID(id) {\n return id.startsWith(T_PREFIX.SUBREDDIT);\n}\nexport function isT6ID(id) {\n return id.startsWith(T_PREFIX.AWARD);\n}\n// assertion functions\nexport function assertT1ID(id) {\n assert(isT1ID(id), `Expected comment id to start with ${T_PREFIX.COMMENT}, got ${id}}`);\n}\nexport function assertT2ID(id) {\n assert(isT2ID(id), `Expected account id to start with ${T_PREFIX.ACCOUNT}, got ${id}}`);\n}\nexport function assertT3ID(id) {\n assert(isT3ID(id), `Expected link id to start with ${T_PREFIX.LINK}, got ${id}}`);\n}\nexport function assertT4ID(id) {\n assert(isT4ID(id), `Expected message id to start with ${T_PREFIX.MESSAGE}, got ${id}}`);\n}\nexport function assertT5ID(id) {\n assert(isT5ID(id), `Expected subreddit id to start with ${T_PREFIX.SUBREDDIT}, got ${id}}`);\n}\nexport function assertT6ID(id) {\n assert(isT6ID(id), `Expected award id to start with ${T_PREFIX.AWARD}, got ${id}}`);\n}\n// factory functions\nexport function asT1ID(id) {\n assertT1ID(id);\n return id;\n}\nexport function asT2ID(id) {\n assertT2ID(id);\n return id;\n}\nexport function asT3ID(id) {\n assertT3ID(id);\n return id;\n}\nexport function asT4ID(id) {\n assertT4ID(id);\n return id;\n}\nexport function asT5ID(id) {\n assertT5ID(id);\n return id;\n}\nexport function asT6ID(id) {\n assertT6ID(id);\n return id;\n}\nexport function asTID(id) {\n if (isT1ID(id)) {\n return asT1ID(id);\n }\n if (isT2ID(id)) {\n return asT2ID(id);\n }\n if (isT3ID(id)) {\n return asT3ID(id);\n }\n if (isT4ID(id)) {\n return asT4ID(id);\n }\n if (isT5ID(id)) {\n return asT5ID(id);\n }\n if (isT6ID(id)) {\n return asT6ID(id);\n }\n throw new Error(`Expected thing id to start with ${Object.values(T_PREFIX).join(', ')} got ${id}}`);\n}\n// convenience functions\nexport function isCommentId(id) {\n return isT1ID(id);\n}\nexport function isAccountId(id) {\n return isT2ID(id);\n}\nexport function isLinkId(id) {\n return isT3ID(id);\n}\nexport function isMessageId(id) {\n return isT4ID(id);\n}\nexport function isSubredditId(id) {\n return isT5ID(id);\n}\nexport function isAwardId(id) {\n return isT6ID(id);\n}\n", "import type { UnknownMessage } from '@devvit/protos';\nimport * as protos from '@devvit/protos';\nimport type { PaymentsService } from '@devvit/protos/payments.js';\nimport { Actor } from '@devvit/shared-types/Actor.js';\nimport type { AssetMap } from '@devvit/shared-types/Assets.js';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport type { JSONObject, JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport { assertValidFormFields } from '../apis/ui/helpers/assertValidFormFields.js';\nimport type {\n BaseContext,\n Configuration,\n ContextAPIClients,\n CustomPostType,\n Form,\n FormDefinition,\n FormFunction,\n FormOnSubmitEventHandler,\n FormToFormValues,\n IconName,\n MenuItem,\n MultiTriggerDefinition,\n OnTriggerRequest,\n ScheduledJobHandler,\n ScheduledJobType,\n SettingsFormField,\n TriggerContext,\n TriggerDefinition,\n TriggerEvent,\n TriggerEventType,\n TriggerOnEventHandler,\n} from '../types/index.js';\nimport { SettingScope } from '../types/index.js';\nimport { registerAppSettings } from './internals/app-settings.js';\nimport { registerCustomPost } from './internals/custom-post.js';\nimport { registerInstallationSettings } from './internals/installation-settings.js';\nimport { registerMenuItems } from './internals/menu-items.js';\nimport { pluginIsEnabled } from './internals/plugins.js';\nimport { registerScheduler } from './internals/scheduler.js';\nimport { registerTriggers } from './internals/triggers.js';\nimport { registerUIEventHandler } from './internals/ui-event-handler.js';\nimport { registerUIRequestHandlers } from './internals/ui-request-handler.js';\n\ntype UseHandler = {\n [name: string]: (args: UnknownMessage | undefined, metadata?: protos.Metadata) => void;\n};\n\ntype PluginType =\n | protos.HTTP\n | protos.Logger\n | protos.Scheduler\n | protos.ContextAction\n | protos.KVStore\n | protos.SchedulerHandler\n | protos.Flair\n | protos.GraphQL\n | protos.LinksAndComments\n | protos.Listings\n | protos.Moderation\n | protos.ModNote\n | protos.Modlog\n | protos.NewModmail\n | protos.PrivateMessages\n | protos.RedisAPI\n | protos.Settings\n | protos.Subreddits\n | protos.Users\n | protos.Widgets\n | protos.Wiki\n | protos.MediaService\n | protos.Realtime\n | protos.UserActions\n | PaymentsService;\n\n/**\n * Home for debug flags, settings, and other information. Any type removals\n * may cause type errors but not runtime errors.\n *\n * **Favor ContextDebugInfo since request-based state is preferred.**\n */\nexport type DevvitDebug = {\n /**\n * Should debug block rendering in console.log according to the reified JSX/XML output. Example:\n *\n * <hstack><text>hi world</text></hstack>\n *\n */\n emitSnapshots?: boolean | undefined;\n\n /**\n * Should console.log the state of the app after every event.\n *\n */\n emitState?: boolean | undefined;\n};\n\nexport class Devvit extends Actor {\n static debug: DevvitDebug = {};\n\n static #appSettings: SettingsFormField[] | undefined;\n static #assets: AssetMap = {};\n static #config: Configuration = {};\n static #customPostType: CustomPostType | undefined;\n static readonly #formDefinitions: Map<FormKey, FormDefinition> = new Map();\n static #installationSettings: SettingsFormField[] | undefined;\n static readonly #menuItems: MenuItem[] = [];\n static readonly #scheduledJobHandlers: Map<string, ScheduledJobHandler> = new Map();\n static readonly #triggerOnEventHandlers: Map<\n TriggerEvent,\n TriggerOnEventHandler<OnTriggerRequest>[]\n > = new Map();\n static #webViewAssets: AssetMap = {};\n\n static #additionallyProvides: protos.Definition[] = [];\n\n /**\n * To use certain APIs and features of Devvit, you must enable them using this function.\n *\n * @param config - The configuration object.\n * @param config.http - Enables the HTTP API.\n * @param config.redditAPI - Enables the Reddit API.\n * @param config.kvStore - Enables the Key Value Storage API.\n * @example\n * ```ts\n * Devvit.configure({\n * http: true,\n * redditAPI: true,\n * redis: true,\n * media: true\n * });\n * ```\n */\n static configure(config: Configuration): void {\n this.#config = { ...this.#config, ...config };\n\n if (pluginIsEnabled(config.http)) {\n this.use(protos.HTTPDefinition);\n }\n\n // We're now defaulting this to on.\n const redisNotSpecified = config.redis === undefined;\n if (redisNotSpecified || pluginIsEnabled(config.kvStore) || pluginIsEnabled(config.redis)) {\n this.use(protos.KVStoreDefinition);\n this.use(protos.RedisAPIDefinition);\n }\n\n if (pluginIsEnabled(config.media)) {\n this.use(protos.MediaServiceDefinition);\n }\n\n if (pluginIsEnabled(config.modLog)) {\n this.use(protos.ModlogDefinition);\n }\n\n if (pluginIsEnabled(config.redditAPI)) {\n // Loading all Reddit API plugins for now.\n // In the future we can split this by oauth scope or section.\n this.use(protos.FlairDefinition);\n this.use(protos.GraphQLDefinition);\n this.use(protos.LinksAndCommentsDefinition);\n this.use(protos.ListingsDefinition);\n this.use(protos.ModerationDefinition);\n this.use(protos.ModNoteDefinition);\n this.use(protos.NewModmailDefinition);\n this.use(protos.PrivateMessagesDefinition);\n this.use(protos.SubredditsDefinition);\n this.use(protos.UsersDefinition);\n this.use(protos.WidgetsDefinition);\n this.use(protos.WikiDefinition);\n }\n\n if (pluginIsEnabled(config.realtime)) {\n this.use(protos.RealtimeDefinition);\n }\n\n if (pluginIsEnabled(config.userActions)) {\n this.use(protos.UserActionsDefinition);\n }\n }\n\n /**\n * Add a menu item to the Reddit UI.\n * @param menuItem - The menu item to add.\n * @param menuItem.label - The label of the menu item.\n * @example\n * ```ts\n * Devvit.addMenuItem({\n * label: 'My Menu Item',\n * location: 'subreddit',\n * onPress: (event, context) => {\n * const location = event.location;\n * const targetId = event.targetId;\n * context.ui.showToast(`You clicked on ${location} ${targetId}`);\n * }\n * });\n * ```\n */\n static addMenuItem(menuItem: MenuItem): void {\n this.#menuItems.push(menuItem);\n }\n\n /**\n * Add a custom post type for your app.\n * @param customPostType - The custom post type to add.\n * @param customPostType.name - The name of the custom post type.\n * @param customPostType.description - An optional description.\n * @param customPostType.height - An optional parameter to set post height, defaults to 'regular'.\n * @param customPostType.render - A function or `Devvit.CustomPostComponent` that returns the UI for the custom post.\n * @example\n * ```ts\n * import { Devvit, useState } from '@devvit/public-api';\n *\n * Devvit.addCustomPostType({\n * name: 'Counter',\n * description: 'A simple click counter post.',\n * render: (context) => {\n * const [counter, setCounter] = useState();\n *\n * return (\n * <vstack>\n * <text>{counter}</text>\n * <button onPress={() => setCounter((counter) => counter + 1)}>Click me!</button>\n * </vstack>\n * );\n * },\n * });\n * ```\n */\n static addCustomPostType(customPostType: CustomPostType): void {\n this.#customPostType = customPostType;\n }\n\n /**\n * Create a form that can be opened from menu items and custom posts.\n * @param form - The form or a function that returns the form.\n * @param onSubmit - The function to call when the form is submitted.\n * @returns A unique key for the form that can used with `ui.showForm`.\n */\n static createForm<const T extends Form | FormFunction>(\n form: T,\n onSubmit: FormOnSubmitEventHandler<FormToFormValues<T>>\n ): FormKey {\n const formKey: FormKey = `form.${this.#formDefinitions.size}`;\n this.#formDefinitions.set(formKey, {\n form,\n onSubmit,\n } as FormDefinition);\n return formKey;\n }\n\n /**\n * Add a scheduled job type for your app. This will allow you to schedule jobs using the `scheduler` API.\n * @param job - The scheduled job type to add.\n * @param job.name - The name of the scheduled job type.\n * @param job.onRun - The function to call when the scheduled job is run.\n * @example\n * ```ts\n * Devvit.addSchedulerJob({\n * name: 'checkNewPosts',\n * onRun: async (event, context) => {\n * const newPosts = await getNewPosts({ limit: 5 }).all();\n * for (const post of newPosts) {\n * if (post.title.includes('bad word')) {\n * await post.remove();\n * }\n * }\n * }\n * });\n *\n * Devvit.addMenuItem({\n * label: 'Check for new posts',\n * location: 'location',\n * onPress: (event, context) => {\n * const = await context.scheduler.runJob({\n * name: 'checkNewPosts',\n * when: new Date(Date.now() + 5000) // in 5 seconds\n * });\n * }\n * });\n * ```\n */\n static addSchedulerJob<T extends JSONObject | undefined>(job: ScheduledJobType<T>): void {\n if (!this.#pluginClients[protos.SchedulerDefinition.fullName]) {\n this.use(protos.SchedulerDefinition);\n }\n\n if (this.#scheduledJobHandlers.has(job.name)) {\n throw new Error(`Job ${job.name} is already defined`);\n }\n\n this.#scheduledJobHandlers.set(\n job.name,\n job.onRun as ScheduledJobHandler<JSONObject | undefined>\n );\n }\n\n /**\n * Add settings that can be configured to customize the behavior of your app. There are two levels of settings: App settings (scope: 'app') and\n * install settings (scope: 'installation' or unspecified scope). Install settings are meant to be configured by the user that installs your app.\n * This is a good place to add anything that a user might want to change to personalize the app (e.g. the default city to show the weather for or a\n * specific sport team that a subreddit follows). Note that these are good for subreddit level customization but not necessarily good for things\n * that might be different for two users in a subreddit (e.g. setting the default city to show the weather for is only useful at a sub level if\n * the sub is for a specific city or region). Install settings can be viewed and configured here: https://developers.reddit.com/r/subreddit-name/apps/app-name.\n * App settings can be accessed and consumed by all installations of the app. This is mainly useful for developer secrets/API keys that your\n * app needs to function. They can only be changed/viewed by you via the CLI (devvit settings set and devvit settings list). This ensures secrets\n * are persisted in an encrypted store and don't get committed in the source code. You should never paste your actual key into any fields passed into\n * Devvit.addSettings - this is merely where you state what your API key's name and description are. You will be able to set the actual value of the key via CLI.\n * Note: setting names must be unique across all settings.\n * @param fields - Fields for the app and installation settings.\n * @example\n * ```ts\n * Devvit.addSettings([\n * {\n * type: 'string',\n * name: 'weather-api-key',\n * label: 'My weather.com API key',\n * scope: SettingScope.App,\n * isSecret: true\n * },\n * {\n * type: 'string',\n * name: 'Default City',\n * label: 'Default city to show the weather for by default',\n * scope: SettingScope.Installation,\n * onValidate: ({ value }) => {\n * if (!isValidCity(value)) {\n * return 'You must ender a valid city: ${validCities.join(\", \")}';\n * }\n * }\n * },\n * {\n * type: 'number',\n * name: 'Default Forecast Window (in days)',\n * label: 'The number of days to show for forecast for by default',\n * scope: SettingScope.Installation,\n * onValidate: ({ value }) => {\n * if (value > 10 || value < 1) {\n * return 'Forecast window must be from 1 to 10 days';\n * }\n * }\n * },\n * ]);\n * ```\n */\n static addSettings(fields: SettingsFormField[]): void {\n assertValidFormFields(fields);\n const installSettings = fields.filter(\n (field) => field.type === 'group' || !field.scope || field.scope === SettingScope.Installation\n );\n const appSettings = fields.filter(\n (field) => field.type !== 'group' && field.scope === SettingScope.App\n );\n\n if (installSettings.length > 0) {\n this.#installationSettings = installSettings;\n }\n\n if (appSettings.length > 0) {\n this.#appSettings = appSettings;\n }\n\n if (!this.#pluginClients[protos.SettingsDefinition.fullName]) {\n this.use(protos.SettingsDefinition);\n }\n }\n\n /**\n * Add a trigger handler that will be invoked when the given event\n * occurs in a subreddit where the app is installed.\n *\n * @param triggerDefinition - The trigger definition.\n * @param triggerDefinition.event - The event to listen for.\n * @param triggerDefinition.events - The events to listen for.\n * @param triggerDefinition.onEvent - The function to call when the event happens.\n * @example\n * ```ts\n * Devvit.addTrigger({\n * event: 'PostSubmit',\n * async onEvent(event, context) {\n * console.log(\"a new post was created!\")\n * }\n * });\n *\n * Devvit.addTrigger({\n * events: ['PostSubmit', 'PostReport'],\n * async onEvent(event, context){\n * if (event.type === 'PostSubmit') {\n * console.log(\"a new post was created!\")\n * } else if (event.type === 'PostReport') {\n * console.log(\"a post was reported!\")\n * }\n * }\n * });\n * ```\n */\n static addTrigger<T extends keyof TriggerEventType>(definition: {\n event: T;\n onEvent: TriggerOnEventHandler<TriggerEventType[T]>;\n }): typeof Devvit;\n static addTrigger<Event extends TriggerEvent>(\n triggerDefinition: MultiTriggerDefinition<Event>\n ): typeof Devvit;\n static addTrigger(\n triggerDefinition: TriggerDefinition | MultiTriggerDefinition<TriggerEvent>\n ): typeof Devvit {\n if ('events' in triggerDefinition) {\n for (const eventType of triggerDefinition.events) {\n this.addTrigger({\n event: eventType,\n onEvent: (event: OnTriggerRequest, context: TriggerContext) =>\n (triggerDefinition.onEvent as TriggerOnEventHandler<OnTriggerRequest>)(event, context),\n } as any); // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n return this;\n }\n\n if (this.#triggerOnEventHandlers.has(triggerDefinition.event)) {\n this.#triggerOnEventHandlers\n .get(triggerDefinition.event)\n ?.push(triggerDefinition.onEvent as TriggerOnEventHandler<OnTriggerRequest>);\n } else {\n this.#triggerOnEventHandlers.set(triggerDefinition.event, [\n triggerDefinition.onEvent as TriggerOnEventHandler<OnTriggerRequest>,\n ]);\n }\n\n return Devvit;\n }\n\n /**\n * @internal\n * utility static method to register additional actor types without exposing an explicit\n * registration hook such as `addTrigger` or `addMenuItem`\n */\n static provide(def: protos.Definition): void {\n this.#additionallyProvides.push(def);\n }\n\n /** @internal */\n static #uses: {\n [fullName: protos.Definition['fullName']]: {\n def: protos.Definition;\n options: DeepPartial<protos.PackageQuery>;\n handler: Readonly<UseHandler> | undefined;\n };\n } = {};\n\n /** @internal */\n static #pluginClients: {\n [fullName: protos.Definition['fullName']]: PluginType;\n } = {};\n\n /** @internal */\n static use<T>(d: protos.Definition, opts?: DeepPartial<protos.PackageQuery>): T {\n this.#uses[d.fullName] = {\n def: d,\n options: opts ?? {},\n handler: undefined,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const wrapped: any = {};\n for (const method of Object.values(d.methods)) {\n wrapped[method.name] = (args: UnknownMessage | undefined, metadata?: protos.Metadata) =>\n this.#uses[d.fullName].handler?.[method.name]?.(\n // eslint-disable-next-line no-restricted-properties\n method.requestType?.fromPartial(args ?? {}),\n metadata\n );\n }\n\n this.#pluginClients[d.fullName] = wrapped;\n\n return wrapped as T;\n }\n\n /** @internal */\n static get redditAPIPlugins(): {\n NewModmail: protos.NewModmail;\n Widgets: protos.Widgets;\n ModNote: protos.ModNote;\n LinksAndComments: protos.LinksAndComments;\n Moderation: protos.Moderation;\n GraphQL: protos.GraphQL;\n Listings: protos.Listings;\n Flair: protos.Flair;\n Wiki: protos.Wiki;\n Users: protos.Users;\n PrivateMessages: protos.PrivateMessages;\n Subreddits: protos.Subreddits;\n } {\n if (!pluginIsEnabled(this.#config.redditAPI)) {\n throw new Error(\n 'Reddit API is not enabled. You can enable it by passing `redditAPI: true` to `Devvit.configure`.'\n );\n }\n\n return {\n Flair: this.#pluginClients[protos.FlairDefinition.fullName] as protos.Flair,\n GraphQL: this.#pluginClients[protos.GraphQLDefinition.fullName] as protos.GraphQL,\n LinksAndComments: this.#pluginClients[\n protos.LinksAndCommentsDefinition.fullName\n ] as protos.LinksAndComments,\n Listings: this.#pluginClients[protos.ListingsDefinition.fullName] as protos.Listings,\n Moderation: this.#pluginClients[protos.ModerationDefinition.fullName] as protos.Moderation,\n ModNote: this.#pluginClients[protos.ModNoteDefinition.fullName] as protos.ModNote,\n NewModmail: this.#pluginClients[protos.NewModmailDefinition.fullName] as protos.NewModmail,\n PrivateMessages: this.#pluginClients[\n protos.PrivateMessagesDefinition.fullName\n ] as protos.PrivateMessages,\n Subreddits: this.#pluginClients[protos.SubredditsDefinition.fullName] as protos.Subreddits,\n Users: this.#pluginClients[protos.UsersDefinition.fullName] as protos.Users,\n Widgets: this.#pluginClients[protos.WidgetsDefinition.fullName] as protos.Widgets,\n Wiki: this.#pluginClients[protos.WikiDefinition.fullName] as protos.Wiki,\n };\n }\n\n /** @internal */\n static get modLogPlugin(): protos.Modlog {\n const modLog = this.#pluginClients[protos.ModlogDefinition.fullName];\n\n if (!modLog) {\n throw new Error(\n 'ModLog is not enabled. You can enable it by passing `modLog: true` to `Devvit.configure`'\n );\n }\n\n return modLog as protos.Modlog;\n }\n\n /** @internal */\n static get schedulerPlugin(): protos.Scheduler {\n const scheduler = this.#pluginClients[protos.SchedulerDefinition.fullName];\n\n if (!scheduler) {\n // todo: better error with more details\n throw new Error(\n 'Scheduler is not enabled. You can enable it by calling `Devvit.addSchedulerJob` at the top level of your app.'\n );\n }\n\n return scheduler as protos.Scheduler;\n }\n\n /** @internal */\n static get kvStorePlugin(): protos.KVStore {\n const kvStore = this.#pluginClients[protos.KVStoreDefinition.fullName];\n\n if (!kvStore) {\n throw new Error(\n 'Key Value Store is not enabled. You can enable it by passing `kvStore: true` to `Devvit.configure`'\n );\n }\n\n return kvStore as protos.KVStore;\n }\n\n /** @internal */\n static get redisPlugin(): protos.RedisAPI {\n const redis = this.#pluginClients[protos.RedisAPIDefinition.fullName];\n\n if (!redis) {\n throw new Error(\n 'Redis is not enabled. You can enable it by passing `redis: true` to `Devvit.configure`'\n );\n }\n\n return redis as protos.RedisAPI;\n }\n\n /** @internal */\n static get mediaPlugin(): protos.MediaService {\n const media = this.#pluginClients[protos.MediaServiceDefinition.fullName];\n if (!media) {\n throw new Error(\n 'MediaService is not enabled. You can enable it by passing `media: true` to `Devvit.configure`'\n );\n }\n return media as protos.MediaService;\n }\n\n /** @internal */\n static get settingsPlugin(): protos.Settings {\n const settings = this.#pluginClients[protos.SettingsDefinition.fullName];\n\n if (!settings) {\n throw new Error(\n 'Settings must first be configured with `Devvit.addSettings()` before they can be accessed'\n );\n }\n\n return settings as protos.Settings;\n }\n\n /** @internal */\n static get realtimePlugin(): protos.Realtime {\n const realtime = this.#pluginClients[protos.RealtimeDefinition.fullName];\n\n if (!realtime) {\n throw new Error(\n 'Realtime is not enabled. You can enable it by passing `realtime: true` to `Devvit.configure`'\n );\n }\n\n return realtime as protos.Realtime;\n }\n\n /** @internal */\n static get userActionsPlugin(): protos.UserActions {\n const userActionsAndRedditApiEnabled =\n pluginIsEnabled(this.#config.userActions) && pluginIsEnabled(this.#config.redditAPI);\n\n if (!userActionsAndRedditApiEnabled) {\n throw new Error(\n 'UserActions is not enabled. You can enable it by passing both `userActions: true` and `redditAPI: true` to `Devvit.configure`'\n );\n }\n\n return this.#pluginClients[protos.UserActionsDefinition.fullName] as protos.UserActions;\n }\n\n /** @internal */\n static get menuItems(): MenuItem[] {\n return this.#menuItems;\n }\n\n /** @internal */\n static get customPostType(): CustomPostType | undefined {\n return this.#customPostType;\n }\n\n /** @internal */\n static get formDefinitions(): Map<FormKey, FormDefinition> {\n return this.#formDefinitions;\n }\n\n /** @internal */\n static get scheduledJobHandlers(): Map<string, ScheduledJobHandler> {\n return this.#scheduledJobHandlers;\n }\n\n /** @internal */\n static get installationSettings(): SettingsFormField[] | undefined {\n return this.#installationSettings;\n }\n\n /** @internal */\n static get appSettings(): SettingsFormField[] | undefined {\n return this.#appSettings;\n }\n\n /** @internal */\n static get triggerOnEventHandlers(): Map<\n TriggerEvent,\n TriggerOnEventHandler<OnTriggerRequest>[]\n > {\n return this.#triggerOnEventHandlers;\n }\n\n /** @internal */\n static get assets(): AssetMap {\n return this.#assets;\n }\n\n /** @internal */\n static get webViewAssets(): AssetMap {\n return this.#webViewAssets;\n }\n\n /** @internal */\n constructor(config: Config) {\n super(config);\n\n Devvit.#assets = config.assets ?? {};\n Devvit.#webViewAssets = config.webviewAssets ?? {};\n\n for (const fullName in Devvit.#uses) {\n const use = Devvit.#uses[fullName];\n use.handler = config.use<UseHandler>(use.def, use.options);\n }\n\n if (Devvit.#menuItems.length > 0) {\n registerMenuItems(config);\n }\n\n if (Devvit.#scheduledJobHandlers.size > 0) {\n registerScheduler(config);\n }\n\n if (Devvit.#customPostType) {\n registerCustomPost(config);\n /**\n * We're trying to migrate custom posts to generic ui handlers, but they'll\n * both work for now.\n */\n registerUIRequestHandlers(config);\n }\n\n if (Devvit.#customPostType || Devvit.#formDefinitions.size > 0) {\n registerUIEventHandler(config);\n }\n\n if (Devvit.#installationSettings) {\n registerInstallationSettings(config);\n }\n\n if (Devvit.#appSettings) {\n registerAppSettings(config);\n }\n\n if (Devvit.#triggerOnEventHandlers.size > 0) {\n registerTriggers(config);\n }\n\n for (const provides of Devvit.#additionallyProvides) {\n config.provides(provides);\n }\n }\n}\n\nexport namespace Devvit {\n export type Fragment = JSX.Fragment;\n export type ElementChildren = JSX.Element | JSX.Children | undefined;\n export type StringChild = Fragment | string | number;\n export type StringChildren = StringChild | (StringChild | StringChild[])[] | undefined;\n type ComponentFunctionValue = BlockElement | Fragment | undefined;\n\n // Generic createElement to handle Blocks, custom elements, etc...\n export function createElement(\n type: Blocks.IntrinsicElementsType,\n props: { [key: string]: unknown } | undefined,\n ...children: JSX.Children[]\n ): BlockElement;\n export function createElement(\n type: JSX.ComponentFunction | string | undefined,\n props: { [key: string]: unknown } | undefined,\n ...children: JSX.Children[]\n ): ComponentFunctionValue | Promise<ComponentFunctionValue> {\n const blockElement: BlockElement = {\n type,\n props,\n children,\n };\n\n return blockElement;\n }\n\n // Workaround for typing: aliasing global Context as Devvit.Context bundles\n // incorrectly as `type Context = Context`.\n /** The current app context of the event or render. */\n export type Context = ContextAPIClients & BaseContext;\n\n export type BlockComponentProps<P = { [key: string]: unknown }> = P & { children?: JSX.Children };\n export type BlockComponent<P = { [key: string]: unknown }> = (\n props: BlockComponentProps<P>,\n context: Context\n ) => JSX.Element;\n export type CustomPostComponent = (context: Context) => JSX.Element;\n\n export namespace Blocks {\n export interface IntrinsicElements {\n blocks: Devvit.Blocks.RootProps;\n hstack: Devvit.Blocks.StackProps;\n vstack: Devvit.Blocks.StackProps;\n zstack: Devvit.Blocks.StackProps;\n text: Devvit.Blocks.TextProps;\n button: Devvit.Blocks.ButtonProps;\n image: Devvit.Blocks.ImageProps;\n spacer: Devvit.Blocks.SpacerProps;\n icon: Devvit.Blocks.IconProps;\n avatar: Devvit.Blocks.AvatarProps;\n webview: Devvit.Blocks.WebViewProps;\n }\n\n export type IntrinsicElementsType = keyof IntrinsicElements;\n\n //region Attribute Values\n export type SizePixels = `${number}px`;\n export type SizePercent = `${number}%`;\n export type SizeString = SizePixels | SizePercent | number;\n export type Alignment =\n | `${VerticalAlignment}`\n | `${HorizontalAlignment}`\n | `${VerticalAlignment} ${HorizontalAlignment}`\n | `${HorizontalAlignment} ${VerticalAlignment}`;\n export type AvatarBackground = 'light' | 'dark';\n export type AvatarFacing = 'left' | 'right';\n export type AvatarSize =\n | 'xxsmall'\n | 'xsmall'\n | 'small'\n | 'medium'\n | 'large'\n | 'xlarge'\n | 'xxlarge'\n | 'xxxlarge';\n export type ButtonAppearance =\n | 'secondary'\n | 'primary'\n | 'plain'\n | 'bordered'\n | 'media'\n | 'destructive'\n | 'caution'\n | 'success';\n /**\n * Affects the button height.\n * small = 32px;\n * medium = 40px;\n * large = 48px;\n */\n export type ButtonSize = 'small' | 'medium' | 'large';\n export type ColorString = string;\n /**\n * thin = 1px;\n * thick = 2px;\n */\n export type ContainerBorderWidth = Thickness;\n /**\n * small = 8px;\n * medium = 16px;\n * large = 24px;\n */\n export type ContainerCornerRadius = 'none' | 'small' | 'medium' | 'large' | 'full';\n /**\n * small = 8px;\n * medium = 16px;\n * large = 32px;\n */\n export type ContainerGap = 'none' | 'small' | 'medium' | 'large';\n /**\n * xsmall = 4px;\n * small = 8px;\n * medium = 16px;\n * large = 32px;\n */\n export type ContainerPadding = 'none' | 'xsmall' | 'small' | 'medium' | 'large';\n export type HorizontalAlignment = 'start' | 'center' | 'end';\n /**\n * xsmall = 12px;\n * small = 16px;\n * medium = 20px;\n * large = 24px;\n */\n export type IconSize = 'xsmall' | 'small' | 'medium' | 'large';\n export type ImageResizeMode = 'none' | 'fit' | 'fill' | 'cover' | 'scale-down';\n /**\n * xsmall = 4px;\n * small = 8px;\n * medium = 16px;\n * large = 32px;\n */\n export type SpacerSize = 'xsmall' | 'small' | 'medium' | 'large';\n export type SpacerShape = 'invisible' | 'thin' | 'square';\n /**\n * thin = 1px;\n * thick = 2px;\n */\n export type TextOutline = Thickness;\n /**\n * xsmall = 10px;\n * small = 12px;\n * medium = 14px;\n * large = 16px;\n * xlarge = 18px;\n * xxlarge = 24px;\n */\n export type TextSize = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';\n export type TextStyle = 'body' | 'metadata' | 'heading';\n export type TextWeight = 'regular' | 'bold';\n export type TextOverflow = 'clip' | 'ellipsis';\n export type Thickness = 'none' | 'thin' | 'thick';\n export type VerticalAlignment = 'top' | 'middle' | 'bottom';\n export type RootHeight = 'regular' | 'tall';\n //endregion\n\n //region Element Attributes\n export type BaseProps = {\n width?: SizeString;\n height?: SizeString;\n minWidth?: SizeString;\n minHeight?: SizeString;\n maxWidth?: SizeString;\n maxHeight?: SizeString;\n grow?: boolean;\n\n /**\n * This optional field provides some efficiencies around re-ordering elements in a list. Rather\n * Than re-rendering the entire list, the client can use the key to determine if the element has\n * changed. In the example below, if a and b were swapped, the client would know to reuse the\n * existing elements from b, rather than re-creating an expensive tree of elements.\n *\n * Unlike id, key is local to the parent element. This means that the same key can be used in different\n * parts of the tree without conflict.\n *\n * <hstack>\n * <text key=\"a\">hi world</text>\n * <hstack key=\"b\">...deeply nested content...</hstack>\n * </hstack>\n */\n key?: string;\n\n /**\n * This optional field provides a unique identifier for the element. This is useful for ensuring\n * re-use of elements across renders. See the `key` field for more information. Unlike key, id\n * is global. You cannot have two elements with the same id in the same tree.\n */\n id?: string;\n };\n\n export type OnPressEventHandler = (data: JSONObject) => void | Promise<void>;\n\n export type OnWebViewEventHandler = <T extends JSONValue>(message: T) => void | Promise<void>;\n\n export type Actionable = {\n onPress?: OnPressEventHandler | undefined;\n };\n\n export type WebViewActionable = {\n onMessage?: OnWebViewEventHandler | undefined;\n };\n\n export type ActionHandlers = keyof (Actionable & WebViewActionable);\n\n export type HasElementChildren = {\n children?: Devvit.ElementChildren;\n };\n\n export type HasStringChildren = {\n children?: Devvit.StringChildren;\n };\n\n export type RootProps = HasElementChildren & {\n height?: Devvit.Blocks.RootHeight | undefined;\n };\n\n export type StackProps = BaseProps &\n HasElementChildren &\n Actionable & {\n reverse?: boolean | undefined;\n alignment?: Alignment;\n padding?: ContainerPadding | undefined;\n gap?: ContainerGap | undefined;\n border?: ContainerBorderWidth | undefined;\n borderColor?: ColorString | undefined;\n lightBorderColor?: ColorString | undefined;\n darkBorderColor?: ColorString | undefined;\n cornerRadius?: ContainerCornerRadius | undefined;\n backgroundColor?: ColorString | undefined;\n lightBackgroundColor?: ColorString | undefined;\n darkBackgroundColor?: ColorString | undefined;\n };\n\n export type TextProps = BaseProps &\n HasStringChildren &\n Actionable & {\n size?: TextSize | undefined;\n weight?: TextWeight | undefined;\n color?: ColorString | undefined;\n lightColor?: ColorString | undefined;\n darkColor?: ColorString | undefined;\n alignment?: Alignment | undefined;\n outline?: TextOutline | undefined;\n style?: TextStyle | undefined;\n selectable?: boolean | undefined;\n wrap?: boolean | undefined;\n overflow?: TextOverflow | undefined;\n };\n\n export type ButtonProps = BaseProps &\n HasStringChildren &\n Actionable & {\n icon?: IconName | undefined;\n size?: ButtonSize | undefined;\n appearance?: ButtonAppearance | undefined;\n textColor?: ColorString | undefined;\n lightTextColor?: ColorString | undefined;\n darkTextColor?: ColorString | undefined;\n // not available in all platforms yet\n // backgroundColor?: ColorString | undefined;\n disabled?: boolean | undefined;\n };\n\n export type ImageProps = BaseProps &\n Actionable & {\n url: string;\n imageWidth: SizePixels | number;\n imageHeight: SizePixels | number;\n description?: string | undefined;\n resizeMode?: ImageResizeMode | undefined;\n };\n\n export type SpacerProps = BaseProps & {\n size?: SpacerSize | undefined;\n shape?: SpacerShape | undefined;\n };\n\n export type IconProps = BaseProps &\n HasStringChildren &\n Actionable & {\n name: IconName;\n color?: ColorString | undefined;\n lightColor?: ColorString | undefined;\n darkColor?: ColorString | undefined;\n size?: IconSize | undefined;\n };\n\n export type AvatarProps = BaseProps &\n Actionable & {\n thingId: string;\n facing?: AvatarFacing | undefined;\n size?: AvatarSize | undefined;\n background?: AvatarBackground | undefined;\n };\n\n export type WebViewProps = BaseProps &\n WebViewActionable & {\n url: string;\n };\n }\n}\n\nexport type BlockElement = {\n type: JSX.ComponentFunction | string | undefined;\n\n props: { [key: string]: unknown } | undefined;\n children: JSX.Element[];\n};\n\n// Workaround api-extractor https://github.com/microsoft/rushstack/issues/1709\n// type augmentations bug. Everything starting at declare is concatenated onto\n// public-api.d.ts.\ndeclare global {\n namespace JSX {\n interface IntrinsicElements extends Devvit.Blocks.IntrinsicElements {}\n\n type Fragment = Iterable<JSX.Element>;\n type SyncElement = BlockElement | JSX.Fragment | string | number | boolean | null;\n type Element = SyncElement | Promise<SyncElement>;\n type ElementChildrenAttribute = { children: {} };\n type Children = JSX.Element | JSX.Element[];\n type Props<T extends {} = {}> = T & { children?: Devvit.ElementChildren };\n\n type ComponentFunction = (props: JSX.Props, context: Devvit.Context) => JSX.Element;\n }\n}\n", "/**\n * All Bundle programs are Actor subclasses. Actor instances are expected to\n * populate the passed in Config which is used by the builder to compile and\n * link the programs. Bundle programs are expected to export their subclass\n * constructor, either their own Actor subclass or Devvit.\n *\n * Devvit is an Actor subclass singleton with a non-class-based API.\n *\n * `instanceof` and `isPrototypeOf()` tests fail because Devvit and Actor\n * are multiply defined.\n *\n * Developers will, for now, implement this class and\n * utilize the the `cfg.use/provide` api for both:\n *\n * 1. specifying their `DependencySpec` (proto: devvit/runtime/bundle)\n * Build packs will, for now, create an instance of the Actor and use the\n * result of `config.export` for linking\n *\n * 2. When running within the overall system, `Bootstrap` actor will\n * inject a config that is used to setup links within the runtime.\n */\n/**\n * Subclasses are expected to call config.init(), provides(), and uses(). It\n * is erroneous to not override the constructor. Override, invoke\n * `super(config)`, and call the Config APIs.\n */\nexport class Actor {\n constructor(\n // @ts-expect-error ignore unused variable without a _ prefix.\n config) { }\n}\n", "import type {\n FieldConfig_Boolean,\n FieldConfig_Number,\n FieldConfig_Paragraph,\n FieldConfig_Selection,\n FieldConfig_Selection_Item,\n FieldConfig_String,\n} from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { Devvit } from '../devvit/Devvit.js';\nimport type { FormToFormValues } from './index.js';\n\nexport type { FormKey };\n\nexport type FormValues = JSONObject;\n\nexport type FormOnSubmitEvent<T extends Partial<JSONObject>> = {\n /** The form values that were submitted */\n values: T;\n};\n\nexport type FormOnSubmitEventHandler<Data extends Partial<JSONObject>> = (\n /** The event object containing the results of the form submission */\n event: FormOnSubmitEvent<Data>,\n /** The current app context of the form submission event */\n context: Devvit.Context\n) => void | Promise<void>;\n\nexport type Form = {\n /** The fields that will be displayed in the form */\n fields: readonly FormField[];\n /** An optional title for the form */\n title?: string;\n /** An optional description for the form */\n description?: string;\n /** An optional label for the submit button */\n acceptLabel?: string;\n /** An optional label for the cancel button */\n cancelLabel?: string;\n};\n\n/**\n * A function that returns a form. You can use this to dynamically generate a form.\n * @example\n * ```ts\n * const formKey = Devvit.createForm((data) => ({\n * fields: data.fields,\n * title: data.title,\n * }), callback);\n *\n * ...\n *\n * ui.showForm(formKey, {\n * fields: [{ type: 'string', name: 'title', label: 'Title' }]\n * title: 'My dynamic form'\n * });\n * ```\n * */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FormFunction<T extends { [key: string]: any } = { [key: string]: any }> = (\n data: T\n) => Form;\n\nexport type FormDefinition<T extends Form | FormFunction = Form | FormFunction> = {\n /** A form or a function that returns a form */\n form: T;\n /** A callback that will be invoked when the form is submitted */\n onSubmit: FormOnSubmitEventHandler<FormToFormValues<T>>;\n};\n\nexport type BaseField<ValueType> = {\n /**\n * The name of the field. This will be used as the key in the `values` object\n * when the form is submitted.\n */\n name: string;\n /** The label of the field. This will be displayed to the user */\n label: string;\n /** An optional help text that will be displayed below the field */\n helpText?: string | undefined;\n /**\n * If true the field will be required and the user will not be able to submit\n * the form without filling it in.\n */\n required?: boolean | undefined;\n /** If true the field will be disabled */\n disabled?: boolean | undefined;\n /** The default value of the field */\n defaultValue?: ValueType | undefined;\n /**\n * This indicates whether the field (setting) is an app level or install level\n * setting. App setting values can be used by any installation.\n */\n scope?: SettingScopeType | undefined;\n};\n\nexport type SettingScopeType = 'installation' | 'app';\n\nexport enum SettingScope {\n Installation = 'installation',\n App = 'app',\n}\n\n/** A text field */\nexport type StringField = Prettify<\n BaseField<string> &\n Omit<FieldConfig_String, 'minLength' | 'maxLength'> & {\n type: 'string';\n isSecret?: boolean;\n }\n>;\n\n// TODO remove @experimental once we've implemented the image field in the UI on all platforms\n/**\n * Allows a user to upload an image as part of submitting the form. The string value that's\n * given back is the URL of the image.\n * @experimental\n */\nexport type ImageField = Omit<BaseField<string>, 'defaultValue'> & {\n type: 'image';\n};\n\n/** A paragraph or textarea field */\nexport type ParagraphField = Prettify<\n BaseField<string> &\n Omit<FieldConfig_Paragraph, 'maxCharacters'> & {\n type: 'paragraph';\n }\n>;\n\n/** A number field */\nexport type NumberField = Prettify<\n BaseField<number> &\n // TODO: allow \"step\" when <faceplate-text-input> start supporting it\n Omit<FieldConfig_Number, 'min' | 'max' | 'step'> & {\n type: 'number';\n }\n>;\n\n/** A boolean field displayed as a toggle */\nexport type BooleanField = Prettify<\n // Note: 'required' doesn't make sense for a boolean field\n Omit<BaseField<boolean>, 'required'> &\n FieldConfig_Boolean & {\n type: 'boolean';\n }\n>;\n\n/** A dropdown field that allows users to pick from a list of options */\nexport type SelectField = Prettify<\n BaseField<string[]> &\n Omit<FieldConfig_Selection, 'choices' | 'renderAsList' | 'minSelections' | 'maxSelections'> & {\n type: 'select';\n options: FieldConfig_Selection_Item[];\n }\n>;\n\n/** A grouping of fields */\nexport type FormFieldGroup = {\n type: 'group';\n /** The label of the group that will be displayed to the user */\n label: string;\n /** The fields that will be displayed in the group */\n fields: readonly FormField[];\n /** An optional help text that will be displayed below the group */\n helpText?: string | undefined;\n required?: never;\n};\n\nexport type FormField =\n | StringField\n | ImageField\n | ParagraphField\n | NumberField\n | BooleanField\n | SelectField\n | FormFieldGroup;\n", "import type { FormField } from '../../../types/form.js';\nimport { SettingScope } from '../../../types/form.js';\n\n/**\n * Make sure that the form fields have unique names.\n */\nexport function assertValidFormFields(\n fields: readonly FormField[],\n seenNames: Set<string> = new Set()\n): void {\n for (const field of fields) {\n if (field.type === 'group') {\n assertValidFormFields(field.fields, seenNames);\n continue;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldName = (field as any).name as string;\n\n if (seenNames.has(fieldName)) {\n throw new Error(`Duplicate field name: ${fieldName}`);\n }\n\n seenNames.add(fieldName);\n }\n assertAppSecretsOnly(fields);\n}\n\nexport function assertAppSecretsOnly(fields: readonly FormField[]): void {\n for (const field of fields) {\n if (field.type === 'string' && field.isSecret && field.scope !== SettingScope.App) {\n throw `Invalid setting: only app settings can be secrets. Add \"scope: SettingScope.App\" to field \"${field.name}\"`;\n }\n }\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { Context } from './context.js';\nimport type {\n BooleanField,\n Form,\n FormField,\n FormFieldGroup,\n FormFunction,\n ImageField,\n NumberField,\n ParagraphField,\n SelectField,\n StringField,\n} from './form.js';\nimport type { ChannelOptions, ChannelStatus } from './realtime.js';\n\nexport type Dispatch<A> = (value: A) => void;\nexport type SetStateAction<S> = S | ((prevState: S) => S);\nexport type StateSetter<S> = Dispatch<SetStateAction<S>>;\n\n/** A tuple containing the current state and a function to update it */\nexport type UseStateResult<S> = [S, StateSetter<S>];\nexport type UseStateHook = Context['useState'];\nexport type AsyncUseStateInitializer<S> = () => Promise<S>;\nexport type UseStateInitializer<S> = S | (() => S) | AsyncUseStateInitializer<S>;\nexport type AsyncError = { message: string; details: string | null };\nexport type UseAsyncResult<S> = { data: S | null; loading: boolean; error: AsyncError | null };\n\n/** @internal */\nexport const Hook = Object.freeze({\n INTERVAL: 0, // useInterval hook\n FORM: 1, // useForm hook\n STATE: 2, // useState hook\n CHANNEL: 3, // useChannel hook\n WEB_VIEW: 4, // useWebView hook\n});\n\n/** @internal */\nexport type Hook = (typeof Hook)[keyof typeof Hook];\n\n/** @internal */\nexport type UseFormHookState = {\n formKey: FormKey;\n preventSubmit: boolean;\n type: Hook;\n};\n\n/** A hook that returns a form key that can be used in the `ui.showForm` */\nexport type UseFormHook<T extends Form | FormFunction = Form | FormFunction> = (\n form: T,\n onSubmit: (values: FormToFormValues<T>) => void | Promise<void>\n) => FormKey;\n\nexport type FormToFormValues<T extends Form | FormFunction = Form | FormFunction> =\n FormFieldsToFormValues<(T extends FormFunction ? ReturnType<T> : T)['fields']>;\n\n/**\n * Input is a FormField[], output is a\n * {fieldNameA: fieldTypeA, fieldNameB: fieldTypeB}.\n */\ntype FormFieldsToFormValues<T extends readonly FormField[]> = T extends readonly [\n infer Field extends FormField,\n ...infer Rest extends FormField[],\n]\n ? FormFieldToFormValue<Field> & FormFieldsToFormValues<Rest>\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n { [key: string]: any }; // possibly empty but more likely couldn't infer.\n\n/** Input is a FormField, output is a {fieldName: fieldType}. */\ntype FormFieldToFormValue<T extends FormField> = T extends BooleanField\n ? { [_ in T['name']]: boolean }\n : T extends ImageField | ParagraphField | StringField\n ? FormFieldToRequiredFormValue<T, string>\n : T extends NumberField\n ? FormFieldToRequiredFormValue<T, number>\n : T extends SelectField\n ? { [_ in T['name']]: string[] }\n : T extends FormFieldGroup\n ? FormFieldsToFormValues<T['fields']>\n : never;\n\n/**\n * Input is a FormField, output is a {fieldName: fieldType} or\n * {fieldName?: fieldType}.\n */\ntype FormFieldToRequiredFormValue<\n T extends ImageField | ParagraphField | StringField | NumberField,\n V,\n> = T extends { required: true } | { defaultValue: boolean | number | string }\n ? { [_ in T['name']]: V }\n : { [_ in T['name']]?: V };\n\n/** An object that contains functions to start and stop the interval created by the `useInterval` hook */\nexport type UseIntervalResult = {\n /** Start the interval */\n start: () => void;\n /** Stop the interval */\n stop: () => void;\n};\n\n/** A hook that can used to run a callback on an interval between Block renders. Only one useInterval hook may be running at a time. */\nexport type UseIntervalHook = (\n /** The callback to run on an interval */\n callback: () => void | Promise<void>,\n /** The delay between each callback run in milliseconds. Delay must be at least 100ms. */\n delay: number\n) => UseIntervalResult;\n\n/** @internal */\nexport type UseIntervalHookState = {\n lastRun: number | undefined;\n running: boolean;\n preventCallback: boolean;\n type: Hook;\n};\n\nexport type UseChannelHook<Message extends JSONValue = JSONValue> = (\n options: ChannelOptions<Message>\n) => UseChannelResult<Message>;\n\n/** @internal */\nexport type UseChannelHookState = {\n channel: string;\n active: boolean;\n connected: boolean;\n preventCallback: boolean;\n type: Hook;\n};\n\nexport type UseChannelResult<Message extends JSONValue = JSONValue> = {\n /** Subscribe to the channel */\n subscribe(): void;\n /** Unsubscribe from the channel */\n unsubscribe(): void;\n /** Publish a message to the channel */\n send(msg: Message): Promise<void>;\n /** Current subscription status */\n status: ChannelStatus;\n};\n\n/**\n * @template From Message from web view to Devvit Blocks app.\n * @template To Message from Devvit Blocks app to web view.\n */\nexport type UseWebViewOnMessage<\n From extends JSONValue = JSONValue,\n To extends JSONValue = JSONValue,\n> = (message: From, hook: UseWebViewResult<To>) => void | Promise<void>;\n\n/**\n * @template From Message from web view to Devvit Blocks app.\n * @template To Message from Devvit Blocks app to web view.\n */\nexport type UseWebViewOptions<\n From extends JSONValue = JSONValue,\n To extends JSONValue = JSONValue,\n> = {\n /** Relative HTML asset filename like `foo/bar.html`. Defaults to index.html if omitted. */\n url?: string;\n /** Handle UI events originating from the web view to be handled by a Devvit app */\n onMessage: UseWebViewOnMessage<From, To>;\n /**\n * The callback to run when the web view has been unmounted. Might be used to\n * set state, stop or resume timers, or perform other tasks now that the web view is no longer visible.\n * @deprecated use the page visibility API for now.\n */\n onUnmount?: (hook: UseWebViewResult<To>) => void | Promise<void>;\n};\n\n/** @template To Message from Devvit Blocks app to web view. */\nexport type UseWebViewResult<To extends JSONValue = JSONValue> = {\n /** Send a message to the web view */\n postMessage(message: To): void;\n /** Initiate a request for the web view to open */\n mount(): void;\n /** Initiate a request for the web view to be closed */\n unmount(): void;\n};\n", "export const ALL_ICON_NAMES = [\"3rd-party\",\"activity\",\"add-emoji\",\"add\",\"add-media\",\"add-to-feed\",\"admin\",\"ads\",\"ai\",\"align-center\",\"align-left\",\"align-right\",\"all\",\"ama\",\"appearance\",\"approve\",\"archived\",\"aspect-ratio\",\"aspect-rectangle\",\"attach\",\"audience\",\"audio\",\"author\",\"automod\",\"avatar-style\",\"award\",\"back\",\"backup\",\"ban\",\"best\",\"block\",\"blockchain\",\"bold\",\"boost\",\"bot\",\"bounce\",\"brand-awareness\",\"browse\",\"browser\",\"cake\",\"calendar\",\"camera\",\"campaign\",\"caret-down\",\"caret-left\",\"caret-right\",\"caret-up\",\"chat\",\"chat-group\",\"chat-new\",\"chat-private\",\"checkbox-dismiss\",\"checkbox\",\"checkmark\",\"chrome\",\"clear\",\"client-list\",\"close\",\"closed-captioning\",\"code-block\",\"code-inline\",\"coins-color-old\",\"coins\",\"collapse-left\",\"collapse-right\",\"collectible-expressions\",\"collection\",\"comment\",\"comments\",\"communities\",\"community\",\"confidence\",\"contest\",\"controversial\",\"conversion\",\"copy-clipboard\",\"crop\",\"crosspost\",\"crowd-control\",\"custom-feed\",\"customize\",\"dashboard\",\"day\",\"delete-column\",\"delete\",\"delete-row\",\"devvit\",\"discover\",\"dismiss-all\",\"distinguish\",\"down-arrow\",\"down\",\"download\",\"downvote\",\"downvotes\",\"drag\",\"drugs\",\"duplicate\",\"edit\",\"effect\",\"embed\",\"emoji\",\"end-live-chat\",\"error\",\"expand-left\",\"expand-right\",\"external\",\"feed-video\",\"filter\",\"format\",\"forward\",\"funnel\",\"gif-post\",\"gold\",\"hashtag\",\"heart\",\"help\",\"hide\",\"history\",\"home\",\"hot\",\"ignore-reports\",\"image-post\",\"inbox\",\"info\",\"insert-column-left\",\"insert-column-right\",\"insert-row-above\",\"insert-row-below\",\"internet\",\"invite\",\"italic\",\"join\",\"joined\",\"jump-down\",\"jump-up\",\"karma\",\"keyboard\",\"kick\",\"language\",\"leave\",\"left\",\"link\",\"link-post\",\"list-bulleted\",\"list-numbered\",\"live-chat\",\"live\",\"load\",\"location\",\"lock\",\"logout\",\"loop\",\"macro\",\"mark-read\",\"marketplace\",\"mask\",\"media-gallery\",\"meme\",\"menu\",\"message\",\"mic\",\"mic-mute\",\"mod\",\"mod-mail\",\"mod-mode\",\"mod-mute\",\"mod-overflow\",\"mod-queue\",\"mod-unmute\",\"music\",\"mute\",\"new\",\"night\",\"no-internet\",\"notification\",\"notification-frequent\",\"notification-off\",\"nsfw\",\"nsfw-language\",\"nsfw-violence\",\"official\",\"original\",\"overflow-caret\",\"overflow-horizontal\",\"overflow-vertical\",\"pause\",\"payment\",\"peace\",\"pending-posts\",\"phone\",\"pin\",\"play\",\"poll-post\",\"popular\",\"posts\",\"powerup\",\"predictions\",\"premium\",\"privacy\",\"profile\",\"qa\",\"qr-code\",\"quarantined\",\"quote\",\"r-slash\",\"radar\",\"radio-button\",\"raise-hand\",\"random\",\"ratings-everyone\",\"ratings-mature\",\"ratings-nsfw\",\"ratings-violence\",\"recovery-phrase\",\"refresh\",\"removal-reasons\",\"remove\",\"reply\",\"report\",\"reverse\",\"rich-text\",\"right\",\"rising\",\"rotate\",\"rotate-image\",\"rpan\",\"rules\",\"safari\",\"save\",\"save-view\",\"saved\",\"saved-response\",\"search\",\"self\",\"send\",\"settings\",\"severity\",\"share\",\"share-new\",\"show\",\"side-menu\",\"skipback10\",\"skipforward10\",\"sort-az\",\"sort\",\"sort-price\",\"sort-za\",\"spam\",\"spoiler\",\"sponsored\",\"spreadsheet\",\"star\",\"statistics\",\"status-live\",\"sticker\",\"strikethrough\",\"subtract\",\"superscript\",\"swap-camera\",\"swipe-back\",\"swipe-down\",\"swipe\",\"swipe-up\",\"table\",\"tag\",\"tap\",\"text\",\"text-post\",\"text-size\",\"toggle\",\"tools\",\"top\",\"topic-activism\",\"topic-addictionsupport\",\"topic-advice\",\"topic-animals\",\"topic-anime\",\"topic-art\",\"topic-beauty\",\"topic-business\",\"topic-careers\",\"topic-cars\",\"topic-celebrity\",\"topic-craftsdiy\",\"topic-crypto\",\"topic-culture\",\"topic-diy\",\"topic-entertainment\",\"topic-ethics\",\"topic-family\",\"topic-fashion\",\"topic\",\"topic-fitness\",\"topic-food\",\"topic-funny\",\"topic-gender\",\"topic-health\",\"topic-help\",\"topic-history\",\"topic-hobbies\",\"topic-homegarden\",\"topic-internet\",\"topic-law\",\"topic-learning\",\"topic-lifestyle\",\"topic-marketplace\",\"topic-mature\",\"topic-mensfashion\",\"topic-menshealth\",\"topic-meta\",\"topic-military\",\"topic-movies\",\"topic-music\",\"topic-news\",\"topic-other\",\"topic-outdoors\",\"topic-pets\",\"topic-photography\",\"topic-places\",\"topic-podcasts\",\"topic-politics\",\"topic-programming\",\"topic-reading\",\"topic-religion\",\"topic-science\",\"topic-sexorientation\",\"topic-sports\",\"topic-style\",\"topic-tabletop\",\"topic-technology\",\"topic-television\",\"topic-traumasupport\",\"topic-travel\",\"topic-videogaming\",\"topic-womensfashion\",\"topic-womenshealth\",\"translate\",\"translation-off\",\"trim\",\"u-slash\",\"unban\",\"undo\",\"unheart\",\"unlock\",\"unmod\",\"unpin\",\"unstar\",\"unverified\",\"up-arrow\",\"up\",\"upload\",\"upvote\",\"upvotes\",\"user\",\"user-note\",\"users\",\"vault\",\"verified\",\"video-camera\",\"video-feed\",\"video-post\",\"video-thread\",\"video-transcription\",\"view-card\",\"view-classic\",\"view-compact\",\"view-grid\",\"view-sort\",\"views\",\"volume\",\"wallet\",\"warning\",\"webhook\",\"whale\",\"wiki-ban\",\"wiki\",\"wiki-unban\",\"world\",\"coins-color\",\"powerup-color\",\"powerup-fill-color\",\"share-android\",\"share-ios\",\"video-live-1\",\"video-live-fill-1\",\"video-live\",\"volume-mute\",\"binoculars\",\"caret-updown\",\"planet\",\"telescope\"] as const;\nexport type AllIconName = typeof ALL_ICON_NAMES[number];\nexport type IconName = `${AllIconName}` | `${AllIconName}-outline` | `${AllIconName}-fill`;\n", "import type * as protos from '@devvit/protos';\nimport type * as EventTypes from '@devvit/protos/types/devvit/events/v1alpha/events.d.ts';\n\nimport type { Devvit } from '../devvit/Devvit.js';\n\nexport { DeletionReason, EventSource } from '@devvit/protos';\nexport type { EventTypes };\n\n/** The event name for when a post is submitted */\nexport type PostSubmit = 'PostSubmit';\n/** The event name for when a post is created, after safety delay */\nexport type PostCreate = 'PostCreate';\n/** The event name for when a post is updated */\nexport type PostUpdate = 'PostUpdate';\n/** The event name for when a post is reported */\nexport type PostReport = 'PostReport';\n/** The event name for when a post is deleted */\nexport type PostDelete = 'PostDelete';\n/** The event name for when the flair of a post is updated */\nexport type PostFlairUpdate = 'PostFlairUpdate';\n/** The event name for when a comment is submitted */\nexport type CommentSubmit = 'CommentSubmit';\n/** The event name for when a comment is created, after safety delay */\nexport type CommentCreate = 'CommentCreate';\n/** The event name for when a comment is updated */\nexport type CommentUpdate = 'CommentUpdate';\n/** The event name for when a comment is reported */\nexport type CommentReport = 'CommentReport';\n/** The event name for when a comment is deleted */\nexport type CommentDelete = 'CommentDelete';\n/** The event name for when your app is installed */\nexport type AppInstall = 'AppInstall';\n/** The event name for when your app is upgraded */\nexport type AppUpgrade = 'AppUpgrade';\n/** The event name for when a moderator action is recorded to a subreddit's modlog */\nexport type ModActionTrigger = 'ModAction';\n/** The event name for when a mod mail is sent/received */\nexport type ModMailTrigger = 'ModMail';\n/** The event name for when a post is marked/unmarked as nsfw*/\nexport type PostNsfwUpdate = 'PostNsfwUpdate';\n/** The event name for when a post is marked/unmarked as spoiler*/\nexport type PostSpoilerUpdate = 'PostSpoilerUpdate';\n/** The event name for when a post is filtered by automoderator */\nexport type AutomoderatorFilterPost = 'AutomoderatorFilterPost';\n/** The event name for when a comment is filtered by automoderator */\nexport type AutomoderatorFilterComment = 'AutomoderatorFilterComment';\n\n/** Maps a TriggerEvent to a Protobuf message and type. */\nexport type TriggerEventType = {\n PostSubmit: { type: 'PostSubmit' } & protos.PostSubmit;\n PostCreate: { type: 'PostCreate' } & protos.PostCreate;\n PostUpdate: { type: 'PostUpdate' } & protos.PostUpdate;\n PostReport: { type: 'PostReport' } & protos.PostReport;\n PostDelete: { type: 'PostDelete' } & protos.PostDelete;\n PostFlairUpdate: { type: 'PostFlairUpdate' } & protos.PostFlairUpdate;\n CommentSubmit: { type: 'CommentSubmit' } & protos.CommentSubmit;\n CommentCreate: { type: 'CommentCreate' } & protos.CommentCreate;\n CommentUpdate: { type: 'CommentUpdate' } & protos.CommentUpdate;\n CommentReport: { type: 'CommentReport' } & protos.CommentReport;\n CommentDelete: { type: 'CommentDelete' } & protos.CommentDelete;\n AppInstall: { type: 'AppInstall' } & protos.AppInstall;\n AppUpgrade: { type: 'AppUpgrade' } & protos.AppUpgrade;\n ModAction: { type: 'ModAction' } & protos.ModAction;\n ModMail: { type: 'ModMail' } & protos.ModMail;\n PostNsfwUpdate: { type: 'PostNsfwUpdate' } & protos.PostNsfwUpdate;\n PostSpoilerUpdate: { type: 'PostSpoilerUpdate' } & protos.PostSpoilerUpdate;\n AutomoderatorFilterPost: { type: 'AutomoderatorFilterPost' } & protos.AutomoderatorFilterPost;\n AutomoderatorFilterComment: {\n type: 'AutomoderatorFilterComment';\n } & protos.AutomoderatorFilterComment;\n};\n\nexport type TriggerEvent =\n | PostSubmit\n | PostCreate\n | PostUpdate\n | PostReport\n | PostDelete\n | PostFlairUpdate\n | CommentSubmit\n | CommentCreate\n | CommentUpdate\n | CommentReport\n | CommentDelete\n | AppInstall\n | AppUpgrade\n | ModActionTrigger\n | ModMailTrigger\n | PostNsfwUpdate\n | PostSpoilerUpdate\n | AutomoderatorFilterPost\n | AutomoderatorFilterComment;\n\ntype TriggerResult = Promise<void> | void;\n\nexport type TriggerContext = Omit<Devvit.Context, 'ui' | 'dimensions' | 'modLog' | 'uiEnvironment'>;\n\nexport type TriggerOnEventHandler<RequestType> = (\n event: RequestType,\n context: TriggerContext\n) => TriggerResult;\n\nexport type PostSubmitDefinition = {\n event: PostSubmit;\n onEvent: TriggerOnEventHandler<protos.PostSubmit>;\n};\n\nexport type PostCreateDefinition = {\n event: PostCreate;\n onEvent: TriggerOnEventHandler<protos.PostCreate>;\n};\n\nexport type PostUpdateDefinition = {\n event: PostUpdate;\n onEvent: TriggerOnEventHandler<protos.PostUpdate>;\n};\n\nexport type PostReportDefinition = {\n event: PostReport;\n onEvent: TriggerOnEventHandler<protos.PostReport>;\n};\n\nexport type PostDeleteDefinition = {\n event: PostDelete;\n onEvent: TriggerOnEventHandler<protos.PostDelete>;\n};\n\nexport type PostFlairUpdateDefinition = {\n event: PostFlairUpdate;\n onEvent: TriggerOnEventHandler<protos.PostFlairUpdate>;\n};\n\nexport type CommentSubmitDefinition = {\n event: CommentSubmit;\n onEvent: TriggerOnEventHandler<protos.CommentSubmit>;\n};\n\nexport type CommentCreateDefinition = {\n event: CommentCreate;\n onEvent: TriggerOnEventHandler<protos.CommentCreate>;\n};\n\nexport type CommentUpdateDefinition = {\n event: CommentUpdate;\n onEvent: TriggerOnEventHandler<protos.CommentUpdate>;\n};\n\nexport type CommentReportDefinition = {\n event: CommentReport;\n onEvent: TriggerOnEventHandler<protos.CommentReport>;\n};\n\nexport type CommentDeleteDefinition = {\n event: CommentDelete;\n onEvent: TriggerOnEventHandler<protos.CommentDelete>;\n};\n\nexport type AppInstallDefinition = {\n event: AppInstall;\n onEvent: TriggerOnEventHandler<protos.AppInstall>;\n};\n\nexport type AppUpgradeDefinition = {\n event: AppUpgrade;\n onEvent: TriggerOnEventHandler<protos.AppUpgrade>;\n};\n\nexport type ModActionDefinition = {\n event: ModActionTrigger;\n onEvent: TriggerOnEventHandler<protos.ModAction>;\n};\n\nexport type ModMailDefinition = {\n event: ModMailTrigger;\n onEvent: TriggerOnEventHandler<protos.ModMail>;\n};\n\nexport type PostNsfwUpdateDefinition = {\n event: PostNsfwUpdate;\n onEvent: TriggerOnEventHandler<protos.PostNsfwUpdate>;\n};\n\nexport type PostSpoilerUpdateDefinition = {\n event: PostSpoilerUpdate;\n onEvent: TriggerOnEventHandler<protos.PostSpoilerUpdate>;\n};\n\nexport type OnAutomoderatorFilterPostDefinition = {\n event: AutomoderatorFilterPost;\n onEvent: TriggerOnEventHandler<protos.AutomoderatorFilterPost>;\n};\n\nexport type OnAutomoderatorFilterCommentDefinition = {\n event: AutomoderatorFilterComment;\n onEvent: TriggerOnEventHandler<protos.AutomoderatorFilterComment>;\n};\n\nexport type MultiTriggerDefinition<Event extends TriggerEvent> = {\n events: readonly Event[];\n onEvent: TriggerOnEventHandler<TriggerEventType[Event]>;\n};\n\nexport type TriggerDefinition =\n | PostSubmitDefinition\n | PostCreateDefinition\n | PostUpdateDefinition\n | PostFlairUpdateDefinition\n | PostReportDefinition\n | PostDeleteDefinition\n | CommentSubmitDefinition\n | CommentCreateDefinition\n | CommentUpdateDefinition\n | CommentReportDefinition\n | CommentDeleteDefinition\n | AppInstallDefinition\n | AppUpgradeDefinition\n | ModActionDefinition\n | ModMailDefinition\n | PostSpoilerUpdateDefinition\n | PostNsfwUpdateDefinition\n | OnAutomoderatorFilterPostDefinition\n | OnAutomoderatorFilterCommentDefinition;\n\nexport type OnTriggerRequest =\n | protos.PostFlairUpdate\n | protos.PostSubmit\n | protos.PostCreate\n | protos.PostUpdate\n | protos.PostReport\n | protos.PostDelete\n | protos.CommentSubmit\n | protos.CommentCreate\n | protos.CommentUpdate\n | protos.CommentReport\n | protos.CommentDelete\n | protos.AppInstall\n | protos.AppUpgrade\n | protos.ModAction\n | protos.ModMail\n | protos.PostNsfwUpdate\n | protos.PostSpoilerUpdate\n | protos.AutomoderatorFilterPost\n | protos.AutomoderatorFilterComment;\n", "import type { Metadata, ValidateFormRequest, ValidateFormResponse } from '@devvit/protos';\nimport { AppSettingsDefinition, GetFieldsResponse } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { transformFormFields } from '../../apis/ui/helpers/transformForm.js';\nimport { Devvit } from '../Devvit.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport { onValidateFormHelper } from './helpers/settingsUtils.js';\n\nasync function onGetSettingsFields(): Promise<GetFieldsResponse> {\n if (!Devvit.appSettings) {\n throw new Error('App settings were not defined.');\n }\n\n return GetFieldsResponse.fromJSON({\n fields: {\n fields: transformFormFields(Devvit.appSettings),\n },\n });\n}\n\nasync function onValidateForm(\n req: ValidateFormRequest,\n metadata: Metadata\n): Promise<ValidateFormResponse> {\n return onValidateFormHelper(req, Devvit.appSettings, metadata);\n}\n\nexport function registerAppSettings(config: Config): void {\n config.provides(AppSettingsDefinition);\n extendDevvitPrototype('GetAppSettingsFields', onGetSettingsFields);\n extendDevvitPrototype('ValidateAppForm', onValidateForm);\n}\n", "import { FormField as FormFieldProto, FormFieldType } from '@devvit/protos';\n\nimport type {\n BooleanField,\n FormField,\n FormFieldGroup,\n ImageField,\n NumberField,\n ParagraphField,\n SelectField,\n StringField,\n} from '../../../types/form.js';\n\nexport function transformFormFields(fields: readonly FormField[]): FormFieldProto[] {\n return fields.map((field) => {\n switch (field.type) {\n case 'string':\n return transformStringField(field);\n case 'image':\n return transformImageField(field);\n case 'paragraph':\n return transformParagraphField(field);\n case 'number':\n return transformNumberField(field);\n case 'select':\n return transformSelectField(field);\n case 'boolean':\n return transformBooleanField(field);\n case 'group':\n return transformGroupField(field);\n default:\n throw new Error('Unknown field type.');\n }\n });\n}\n\nfunction transformStringField(field: StringField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.STRING,\n stringValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n stringConfig: {\n placeholder: field.placeholder,\n },\n },\n fieldId: field.name,\n fieldType: FormFieldType.STRING,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n isSecret: field.isSecret,\n };\n}\n\nfunction transformImageField(field: ImageField): FormFieldProto {\n return {\n disabled: field.disabled,\n fieldId: field.name,\n fieldType: FormFieldType.IMAGE,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformParagraphField(field: ParagraphField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.PARAGRAPH,\n stringValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n paragraphConfig: {\n lineHeight: field.lineHeight,\n placeholder: field.placeholder,\n },\n },\n fieldId: field.name,\n fieldType: FormFieldType.PARAGRAPH,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformNumberField(field: NumberField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.NUMBER,\n numberValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldConfig: {\n numberConfig: {},\n },\n fieldId: field.name,\n fieldType: FormFieldType.NUMBER,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformSelectField(field: SelectField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.SELECTION,\n selectionValue: {\n values: field.defaultValue ?? [],\n },\n },\n disabled: field.disabled,\n fieldConfig: {\n selectionConfig: {\n choices: field.options,\n multiSelect: field.multiSelect,\n },\n },\n fieldId: field.name,\n fieldType: FormFieldType.SELECTION,\n helpText: field.helpText,\n label: field.label,\n required: field.required,\n };\n}\n\nfunction transformBooleanField(field: BooleanField): FormFieldProto {\n return {\n defaultValue: {\n fieldType: FormFieldType.BOOLEAN,\n boolValue: field.defaultValue,\n },\n disabled: field.disabled,\n fieldId: field.name,\n fieldType: FormFieldType.BOOLEAN,\n helpText: field.helpText,\n label: field.label,\n };\n}\n\nfunction transformGroupField(field: FormFieldGroup): FormFieldProto {\n return {\n fieldId: '',\n fieldType: FormFieldType.GROUP,\n fieldConfig: {\n groupConfig: {\n fields: transformFormFields(field.fields),\n },\n },\n label: field.label,\n helpText: field.helpText,\n };\n}\n", "import type { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { Metadata } from '@devvit/protos';\n\n// Do this as an IIFE so that we can have the storage be a const\nconst asyncLocalStorage: AsyncLocalStorage<Metadata> | undefined = (function () {\n try {\n // This require() will fail during bundling, and on browsers. That's OK. Leave\n // the async local storage undefined in those cases, and we'll let follow on\n // code decide if they're OK running with that, or if they want to CircuitBreak.\n\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { AsyncLocalStorage } = require('node:async_hooks');\n return new AsyncLocalStorage();\n } catch {\n // nop\n }\n return undefined;\n})();\n\n// Only set localMetadata if we're not in a remote runtime. If we are in a remote\n// runtime, then we should be using asyncLocalStorage.\nlet localMetadata: Metadata | undefined;\n\n/**\n * Get the current metadata. If called outside a request, this will throw an\n * error.\n * @internal\n * @returns {Metadata} The current metadata.\n */\nexport function getMetadata(): Metadata {\n // Q: How does this work, without jumbling up the metadata between requests?\n // A: If we're running in a remote runtime, we use the async local storage to\n // get the metadata, which guarantees that we get the correct metadata for the\n // current thread. If we're not in a remote runtime, then we'll use the\n // metadata saved in localMetadata; this is set by `withMetadata()` when\n // invoked in a local runtime, and since local runtimes can't handle\n // multiple requests at once, we can safely use this saved metadata without\n // fear of jumbling them up.\n if (asyncLocalStorage) {\n const metadata = asyncLocalStorage.getStore();\n if (!metadata) {\n throw Error('API error; metadata should never be accessed outside a request');\n }\n return metadata;\n }\n\n if (!localMetadata) {\n throw Error('Devvit metadata can only be accessed within a request');\n }\n\n return localMetadata;\n}\n\n/** @internal */\nexport async function withMetadata<T>(\n metadata: Metadata,\n callback: () => T | Promise<T>\n): Promise<T> {\n if (asyncLocalStorage) {\n // If asyncLocalStorage is available, we use it to run the function with\n // access to the metadata.\n return asyncLocalStorage.run(metadata, callback);\n } else {\n // If asyncLocalStorage is not available, set the value on localMetadata,\n // then run the function directly.\n try {\n localMetadata = metadata;\n return await callback();\n } finally {\n // Unset the localMetadata after we're done, to prevent any leakage.\n localMetadata = undefined;\n }\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport { Devvit } from '../../Devvit.js';\nimport { withMetadata } from '../async-metadata.js';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ntype DevvitPrototypeFunctionWithMetadata = (\n arg1: any,\n arg2: Metadata,\n ...rest: any[]\n) => Promise<any> | void;\ntype DevvitPrototypeFunctionWithOptionalMetadata = (\n arg1: any,\n arg2: Metadata | undefined,\n ...rest: any[]\n) => Promise<any> | void;\ntype DevvitPrototypeFunction =\n | DevvitPrototypeFunctionWithMetadata\n | DevvitPrototypeFunctionWithOptionalMetadata;\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nexport function extendDevvitPrototype(key: string, value: DevvitPrototypeFunction): void {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (Devvit as Function).prototype[key] = ((req, meta, ...rest) => {\n // If the metadata is nullish, we set it to an empty object instead. This\n // makes sure that when we call `getMetadata` later, we only get an error\n // in \"non-sane\" cases, like if it's called outside a request.\n const guaranteedMeta = meta ?? {};\n return withMetadata(guaranteedMeta, () => value(req, guaranteedMeta, ...rest));\n }) satisfies DevvitPrototypeFunctionWithOptionalMetadata;\n}\n", "import type { Metadata, ValidateFormRequest } from '@devvit/protos';\nimport { ValidateFormResponse } from '@devvit/protos';\n\nimport { makeAPIClients } from '../../../apis/makeAPIClients.js';\nimport { getFormValues } from '../../../apis/ui/helpers/getFormValues.js';\nimport type {\n OnValidateHandler,\n SettingsFormField,\n SettingsFormFieldGroup,\n} from '../../../types/settings.js';\nimport { getContextFromMetadata } from '../context.js';\n\ntype SettingsPlainField = Exclude<SettingsFormField, SettingsFormFieldGroup>;\n\nexport function extractSettingsFields(settings: SettingsFormField[]): SettingsPlainField[] {\n return settings.flatMap((field) => {\n if (field.type === 'group') {\n return extractSettingsFields(field.fields);\n }\n return field;\n }) as SettingsPlainField[];\n}\n\nexport async function onValidateFormHelper(\n req: ValidateFormRequest,\n settings: SettingsFormField[] | undefined,\n metadata: Metadata\n): Promise<ValidateFormResponse> {\n if (!settings) {\n throw new Error('Settings were not defined.');\n }\n\n const response: ValidateFormResponse = {\n success: true,\n errors: {},\n };\n\n const formValues = getFormValues(req.fieldValues);\n const flattendFields = extractSettingsFields(settings);\n\n await Promise.all(\n flattendFields.map(async (field) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fieldName = (field as any).name as string | undefined;\n\n if (fieldName && field.onValidate) {\n const value = formValues[fieldName];\n const validator = field.onValidate as OnValidateHandler<unknown>;\n\n const context = Object.assign(\n makeAPIClients({\n metadata,\n }),\n getContextFromMetadata(metadata)\n );\n\n const error = await validator(\n {\n value,\n isEditing: req.editing,\n },\n context\n );\n\n if (error) {\n response.success = false;\n response.errors[fieldName] = error;\n }\n }\n })\n );\n\n return ValidateFormResponse.fromJSON(response);\n}\n", "import type { RealtimeSubscriptionEvent } from '@devvit/protos';\nimport { RealtimeSubscriptionStatus } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport type {\n UseChannelHook,\n UseChannelHookState,\n UseChannelResult,\n} from '../../../types/hooks.js';\nimport { Hook } from '../../../types/hooks.js';\nimport type { ChannelOptions } from '../../../types/realtime.js';\nimport { ChannelStatus } from '../../../types/realtime.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseChannelHook(reconciler: BlocksReconciler): UseChannelHook {\n function useChannel<Message extends JSONValue>(\n options: ChannelOptions<Message>\n ): UseChannelResult<Message> {\n const debug = false;\n const hookIndex = reconciler.currentHookIndex;\n const currentState = reconciler.getCurrentComponentState<UseChannelHookState>();\n const previousState = reconciler.getPreviousComponentState<UseChannelHookState>();\n\n const appId = reconciler.metadata[Header.App]?.values[0];\n assertNonNull<string | undefined>(appId, 'useChannel - app is missing from Context');\n\n const installationId = reconciler.metadata[Header.Installation]?.values[0];\n assertNonNull<string | undefined>(\n installationId,\n 'useChannel - installation is missing from Context'\n );\n\n async function send(msg: Message): Promise<void> {\n if (debug) console.debug('[realtime] sends', msg);\n const name = currentState[hookIndex].channel;\n if (currentState[hookIndex].active) {\n if (currentState[hookIndex].connected) {\n await reconciler.realtime.send(name, msg);\n } else {\n throw Error(`Failed to send to channel '${name}'; it is active but not yet connected`);\n }\n } else {\n throw Error(`Cannot send a message over inactive channel: ${name}`);\n }\n }\n\n function subscribe(): void {\n if (!currentState[hookIndex].active) {\n if (debug) console.debug('[realtime] subscribe');\n const name = currentState[hookIndex].channel;\n currentState[hookIndex].active = true;\n\n reconciler.addRealtimeChannel(name);\n }\n }\n\n function unsubscribe(): void {\n if (currentState[hookIndex].active) {\n if (debug) console.debug('[realtime] unsubscribe');\n const name = currentState[hookIndex].channel;\n currentState[hookIndex].active = false;\n\n reconciler.removeRealtimeChannel(name);\n }\n }\n\n function hook(event: RealtimeSubscriptionEvent): () => Promise<void> {\n return async () => {\n let result;\n switch (event.status) {\n case RealtimeSubscriptionStatus.REALTIME_SUBSCRIBED:\n if (debug) console.debug('[realtime] onSubscribed()');\n currentState[hookIndex].connected = true;\n result = options.onSubscribed?.();\n break;\n case RealtimeSubscriptionStatus.REALTIME_UNSUBSCRIBED:\n if (debug) console.debug('[realtime] onUnsubscribed()');\n currentState[hookIndex].connected = false;\n result = options.onUnsubscribed?.();\n break;\n default:\n if (debug) console.debug('[realtime] receives', event.event?.data);\n // expect msg. this must align to RealtimeClient.send().\n result = options.onMessage(event.event?.data?.msg);\n break;\n }\n\n await result;\n };\n }\n\n let hookState: UseChannelHookState = {\n channel: `${appId}:${installationId}:${options.name}`,\n active: false,\n connected: false,\n preventCallback: false,\n type: Hook.CHANNEL,\n };\n\n if (hookIndex in currentState) {\n hookState = currentState[hookIndex];\n } else if (hookIndex in previousState) {\n hookState = previousState[hookIndex];\n }\n\n const event = reconciler.realtimeEvent;\n\n if (reconciler.isInitialRender) {\n hookState.active = false;\n } else if (event && hookState.active && event.event!.channel === hookState.channel) {\n if (!hookState.preventCallback) {\n reconciler.runHook(hook(event));\n }\n\n reconciler.rerenderIn(0);\n }\n\n currentState[hookIndex] = hookState;\n reconciler.currentHookIndex++;\n\n let status = ChannelStatus.Unknown;\n if (hookState.active && hookState.connected) {\n status = ChannelStatus.Connected;\n } else if (hookState.active && !hookState.connected) {\n status = ChannelStatus.Connecting;\n } else if (!hookState.active && hookState.connected) {\n status = ChannelStatus.Disconnecting;\n } else if (!hookState.active && !hookState.connected) {\n status = ChannelStatus.Disconnected;\n }\n\n return {\n subscribe,\n unsubscribe,\n send,\n status,\n };\n }\n\n return useChannel;\n}\n", "export function assertNonNull(val, msg) {\n if (val == null)\n throw Error(msg ?? 'Expected nonnullish value.');\n}\nexport function NonNull(val, msg) {\n assertNonNull(val, msg);\n return val;\n}\n", "import type { FormFieldValue } from '@devvit/protos';\nimport { FormFieldType } from '@devvit/protos';\n\nimport type { FormValues } from '../../../types/form.js';\n\nexport function flattenFormFieldValue(\n value: FormFieldValue\n): undefined | string | string[] | number | boolean {\n switch (value.fieldType) {\n case FormFieldType.STRING:\n return value.stringValue;\n case FormFieldType.IMAGE:\n // the string value is the URL\n return value.stringValue;\n case FormFieldType.PARAGRAPH:\n return value.stringValue;\n case FormFieldType.NUMBER:\n return value.numberValue;\n case FormFieldType.BOOLEAN:\n return value.boolValue;\n case FormFieldType.SELECTION:\n return value.selectionValue?.values ?? [];\n default:\n return undefined;\n }\n}\n\nexport function getFormValues(results: { [key: string]: FormFieldValue }): FormValues {\n return Object.keys(results).reduce((acc, key) => {\n const val = flattenFormFieldValue(results[key]);\n if (val !== undefined) acc[key] = val;\n return acc;\n }, {} as FormValues);\n}\n", "import type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport { getFormValues } from '../../../apis/ui/helpers/getFormValues.js';\nimport type { Form, FormFunction, FormValues } from '../../../types/form.js';\nimport type { UseFormHook, UseFormHookState } from '../../../types/hooks.js';\nimport { Hook } from '../../../types/hooks.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseFormHook(reconciler: BlocksReconciler): UseFormHook {\n function useForm(\n form: Form | FormFunction,\n onSubmit: (values: FormValues) => void | Promise<void>\n ): FormKey {\n const hookIndex = reconciler.currentHookIndex;\n const componentKey = reconciler.getCurrentComponentKey();\n const currentState = reconciler.getCurrentComponentState<UseFormHookState>();\n const previousState = reconciler.getPreviousComponentState<UseFormHookState>();\n\n const formKey: FormKey = `form.hook.${componentKey}.${hookIndex}`;\n\n let hookState: UseFormHookState = {\n formKey,\n preventSubmit: false,\n type: Hook.FORM,\n };\n\n if (hookIndex in currentState) {\n hookState = currentState[hookIndex];\n } else if (hookIndex in previousState) {\n hookState = previousState[hookIndex];\n }\n\n currentState[hookIndex] = hookState;\n reconciler.forms.set(formKey, form);\n\n const formSubmittedEvent = reconciler.formSubmittedEvent;\n\n if (formSubmittedEvent && !hookState.preventSubmit) {\n if (formSubmittedEvent.formId === formKey) {\n reconciler.runHook(async () => {\n const response = onSubmit(getFormValues(formSubmittedEvent.results));\n\n if (response && response instanceof Promise) {\n await response;\n }\n\n reconciler.rerenderIn(0);\n });\n }\n }\n\n currentState[hookIndex].preventSubmit = false;\n reconciler.currentHookIndex++;\n\n return formKey;\n }\n\n return useForm;\n}\n", "import type {\n UseIntervalHook,\n UseIntervalHookState,\n UseIntervalResult,\n} from '../../../types/hooks.js';\nimport { Hook } from '../../../types/hooks.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseIntervalHook(reconciler: BlocksReconciler): UseIntervalHook {\n function useInterval(\n callback: () => void | Promise<void>,\n requestedDelayMs: number\n ): UseIntervalResult {\n const hookIndex = reconciler.currentHookIndex;\n const currentState = reconciler.getCurrentComponentState<UseIntervalHookState>();\n const previousState = reconciler.getPreviousComponentState<UseIntervalHookState>();\n\n // delayMs may only be a minimum of 100ms\n const minDelay = 100;\n const delayMs = Math.max(minDelay, requestedDelayMs);\n\n let hookState: UseIntervalHookState = {\n lastRun: undefined,\n running: false,\n preventCallback: false,\n type: Hook.INTERVAL,\n };\n\n if (hookIndex in currentState) {\n hookState = currentState[hookIndex];\n } else if (hookIndex in previousState) {\n hookState = previousState[hookIndex];\n }\n\n function start(): void {\n if (requestedDelayMs < minDelay) {\n console.error(\n `useInterval delay must be at least ${minDelay}ms. Your interval of ${requestedDelayMs}ms was automatically extended.`\n );\n }\n\n for (const [i, stateItem] of Object.entries(currentState)) {\n if (i !== hookIndex.toString() && stateItem?.type === Hook.INTERVAL && stateItem?.running) {\n throw new Error('Only one useInterval hook may be running at a time');\n }\n }\n\n currentState[hookIndex] = {\n running: true,\n lastRun: currentState[hookIndex]?.lastRun ?? Date.now(),\n preventCallback: false,\n type: Hook.INTERVAL,\n };\n\n reconciler.rerenderIn(delayMs);\n }\n\n function stop(): void {\n currentState[hookIndex].running = false;\n currentState[hookIndex].lastRun = undefined;\n currentState[hookIndex].preventCallback = false;\n }\n\n if (reconciler.isEffectRender && hookState.running) {\n if (!hookState.preventCallback) {\n if (hookState.lastRun === undefined || hookState.lastRun + delayMs < Date.now()) {\n reconciler.runHook(async () => {\n const response = callback();\n\n if (response && response instanceof Promise) {\n await response;\n }\n\n hookState.lastRun = Date.now();\n });\n }\n }\n\n reconciler.rerenderIn(delayMs);\n }\n\n hookState.preventCallback = false;\n currentState[hookIndex] = hookState;\n reconciler.currentHookIndex++;\n\n return {\n start,\n stop,\n };\n }\n\n return useInterval;\n}\n", "import type { SetStateAction, UseStateHook, UseStateResult } from '../../../types/hooks.js';\nimport type { BlocksReconciler } from './BlocksReconciler.js';\n\nexport function makeUseStateHook(reconciler: BlocksReconciler): UseStateHook {\n function useState<S>(initialState: S): UseStateResult<S> {\n const hookIndex = reconciler.currentHookIndex;\n const currentState = reconciler.getCurrentComponentState<S>();\n const previousState = reconciler.getPreviousComponentState<S>();\n\n if (hookIndex in currentState) {\n reconciler.currentHookIndex++;\n return [currentState[hookIndex], stateSetter];\n }\n\n if (reconciler.isInitialRender || !(hookIndex in previousState)) {\n const value = initialState instanceof Function ? initialState() : initialState;\n\n if (value instanceof Promise) {\n const asyncResolver = async (): Promise<void> => {\n currentState[hookIndex] = await value;\n reconciler.currentHookIndex = 0;\n };\n throw asyncResolver();\n }\n\n currentState[hookIndex] = value;\n } else {\n currentState[hookIndex] = previousState[hookIndex];\n }\n\n function stateSetter(valueOrFunction: SetStateAction<S>): void {\n if (reconciler.isRendering) {\n throw new Error('Cannot call setState while rendering.');\n }\n\n currentState[hookIndex] =\n valueOrFunction instanceof Function\n ? valueOrFunction(currentState[hookIndex])\n : valueOrFunction;\n }\n\n reconciler.currentHookIndex++;\n\n return [currentState[hookIndex], stateSetter];\n }\n return useState;\n}\n", "import type { JSONValue, RedisClient } from '../../index.js';\n\nexport type CacheEntry = {\n value: JSONValue | null;\n expires: number; // Timestamp in milliseconds\n error: string | null;\n errorTime: number | null;\n checkedAt: number;\n errorCount: number;\n};\n\nexport type Clock = {\n now(): Date;\n};\n\nexport const SystemClock: Clock = {\n now() {\n return new Date();\n },\n};\n\nexport type CacheOptions = {\n /**\n * Time to live in milliseconds.\n */\n ttl: number;\n\n /**\n * Key to use for caching.\n */\n key: string;\n};\n\nexport type LocalCache = { [key: string]: CacheEntry };\n\nexport function _namespaced(key: string): string {\n return `__autocache__${key}`;\n}\nexport function _lock(key: string): string {\n return `__lock__${key}`;\n}\n\nconst pollEvery = 300; // milli\nconst maxPollingTimeout = 1000; // milli\nconst minTtlValue = 5000;\nexport const retryLimit = 3;\nconst errorRetryProbability = 0.1;\nexport const clientRetryDelay = 1000;\nexport const allowStaleFor = 30_000;\n\ntype WithLocalCache = {\n __cache?: LocalCache;\n};\n\nfunction _unwrap<T>(entry: CacheEntry): T {\n if (entry.error) {\n throw new Error(entry.error);\n }\n return entry.value as T;\n}\n\n/**\n * Refactored out into a class to allow for easier testing and clarity of purpose.\n *\n * This class is responsible for managing the caching of promises. It is a layered cache, meaning it will first check\n * the local cache, then the redis cache, and finally the source of truth. It will also handle refreshing the cache according\n * to the TTL and error handling.\n *\n * Please note that in order to prevent a stampede of requests to the source of truth, we use a lock in redis to ensure only one\n * request is made to the source of truth at a time. If the lock is obtained, the cache will be updated and the lock will be released.\n *\n * Additionally, we use a polling mechanism to fetch the cache if the lock is not obtained. This is to prevent unnecessary errors.\n *\n * Finally, we also want to prevent stampedes against redis for the lock election and the retries. We use a ramping probability to ease in the\n * attempts to get the lock, and not every error will trigger a retry.\n *\n * This means that the cache will be eventually consistent, but will not be immediately consistent. This is a tradeoff we are willing to make.\n * Additionally, this means that the TTL is not precice. The cache may be updated a bit more often than the TTL, but it will not be updated less often.\n *\n */\nexport class PromiseCache {\n #redis: RedisClient;\n #localCache: LocalCache = {};\n #clock: Clock;\n #state: WithLocalCache;\n\n constructor(redis: RedisClient, state: WithLocalCache, clock: Clock = SystemClock) {\n this.#redis = redis;\n this.#state = state;\n this.#clock = clock;\n }\n\n /**\n * This is the public API for the cache. Call this method to cache a promise.\n *\n * @param closure\n * @param options\n * @returns\n */\n async cache<T extends JSONValue>(closure: () => Promise<T>, options: CacheOptions): Promise<T> {\n this.#localCache = this.#state.__cache = this.#state.__cache ?? {};\n this.#enforceTTL(options);\n\n const localCachedAnswer = this.#localCachedAnswer<T>(options.key);\n if (localCachedAnswer !== undefined) {\n return localCachedAnswer;\n }\n\n const existing = await this.#redisEntry(options.key);\n const entry = await this.#maybeRefreshCache(options, existing, closure);\n\n return _unwrap(entry);\n }\n\n /**\n * Get the value from the local cache if it exists and is not expired. We're willing to retry errors, and we're willing\n * to throw errors if we have them in cache.\n *\n * We don't want to retry excessively, so we have a limit on the number of retries. If someone else has retried in the last\n * clientRetryDelay, let's not retry again. We also have a probability of retrying, so we don't retry every time.\n */\n #localCachedAnswer<T extends JSONValue>(key: string): T | undefined {\n const val = this.#localCache[key];\n if (val) {\n const now = this.#clock.now().getTime();\n const hasRetryableError =\n val?.error &&\n val?.errorTime &&\n val.errorCount < retryLimit &&\n Math.random() < errorRetryProbability &&\n val.errorTime! + clientRetryDelay < now;\n const expired = val?.expires && val.expires < now && val.checkedAt + clientRetryDelay < now;\n if (expired || hasRetryableError) {\n delete this.#localCache[key];\n return undefined;\n } else {\n return _unwrap(val);\n }\n }\n return undefined;\n }\n\n /**\n * If we've bothered to check redis, we're already on the backend. Let's see if the cache either (1) contains an error, (2)\n * is expired, (3) is missing, or (4) is about to expire. If any of these are true, we'll refresh the cache based on heuristics.\n *\n * We'll always refresh if missing or expired, but its probabilistic if we'll refresh if about to expire or if we have an error.\n */\n async #maybeRefreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const expires = entry?.expires;\n const rampProbability = expires ? this.#calculateRamp(expires) : 1;\n if (\n !entry ||\n (entry?.error && entry.errorCount < retryLimit && errorRetryProbability > Math.random()) ||\n rampProbability > Math.random()\n ) {\n return this.#refreshCache(options, entry, closure);\n } else {\n return entry!;\n }\n }\n\n /**\n * The conditions for refreshing the cache are handled in the calling method, which should be\n * #maybeRefreshCache.\n *\n * If you don't win the lock, you'll poll for the cache. If you don't get the cache within maxPollingTimeout, you'll throw an error.\n */\n async #refreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const lockKey = _lock(options.key);\n const now = this.#clock.now().getTime();\n\n /**\n * The write lock should last for a while, but not the full TTL. Hopefully write attempts settle down after a while.\n */\n const lockExpiration = new Date(now + options.ttl / 2);\n\n const lockObtained = await this.#redis.set(lockKey, '1', {\n expiration: lockExpiration,\n nx: true,\n });\n if (lockObtained) {\n return this.#updateCache(options.key, entry, closure, options.ttl);\n } else if (entry) {\n // This entry is still valid, return it\n return entry;\n } else {\n const start = this.#clock.now();\n return this.#pollForCache(start, options.key, options.ttl);\n }\n }\n\n async #pollForCache(start: Date, key: string, ttl: number): Promise<CacheEntry> {\n const pollingTimeout = Math.min(ttl, maxPollingTimeout);\n const existing = await this.#redisEntry(key);\n if (existing) {\n return existing;\n }\n\n if (this.#clock.now().getTime() - start.getTime() >= pollingTimeout) {\n throw new Error(`Cache request timed out trying to get data at key: ${key}`);\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollEvery));\n return this.#pollForCache(start, key, ttl);\n }\n\n /**\n * Actually update the cache. This is the method that will be called if we have the lock.\n */\n async #updateCache<T extends JSONValue>(\n key: string,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>,\n ttl: number\n ): Promise<CacheEntry> {\n const expires = this.#clock.now().getTime() + ttl;\n entry = entry ?? {\n value: null,\n expires,\n errorCount: 0,\n error: null,\n errorTime: null,\n checkedAt: 0,\n };\n try {\n entry.value = await closure();\n entry.error = null;\n entry.errorCount = 0;\n entry.errorTime = null;\n } catch (e) {\n entry.value = null;\n entry.error = (e as Error).message ?? 'Unknown error';\n entry.errorTime = this.#clock.now().getTime();\n entry.errorCount++;\n }\n\n this.#localCache[key] = entry;\n\n await this.#redis.set(_namespaced(key), JSON.stringify(entry), {\n expiration: new Date(expires + allowStaleFor),\n });\n\n /**\n * Unlocking will allow retries to happen if there was an error. Otherwise we don't unlock, because the lock\n * will expire on its own.\n */\n if (entry.error && entry.errorCount < retryLimit) {\n await this.#redis.del(_lock(key));\n }\n\n return entry;\n }\n\n /**\n * This is the schedule for optimistic pre-fetch of an about-to-expire cache. It exponentially ramps in, which hopefully provides\n * a degree of flexibility in the face of varying traffic levels.\n */\n #calculateRamp(expiry: number): number {\n const now = this.#clock.now().getTime();\n const remaining = expiry - now;\n\n if (remaining < 0) {\n return 1;\n } else if (remaining < 1000) {\n return 0.1;\n } else if (remaining < 2000) {\n return 0.01;\n } else if (remaining < 3000) {\n return 0.001;\n } else {\n return 0;\n }\n }\n\n async #redisEntry(key: string): Promise<CacheEntry | undefined> {\n const val = await this.#redis.get(_namespaced(key));\n if (val) {\n const entry = JSON.parse(val) as CacheEntry;\n entry.checkedAt = this.#clock.now().getTime();\n this.#localCache[key] = entry;\n return entry;\n }\n return undefined;\n }\n\n #enforceTTL(options: CacheOptions): void {\n if (options.ttl < minTtlValue) {\n console.warn(\n `Cache TTL cannot be less than ${minTtlValue} milliseconds! Updating ttl value of ${options.ttl} to ${minTtlValue}.`\n );\n options.ttl = minTtlValue;\n }\n }\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { RedisClient } from '../../types/redis.js';\nimport type { BlocksReconciler } from './blocks/BlocksReconciler.js';\nimport type { CacheOptions, Clock, LocalCache } from './promise_cache.js';\nimport { PromiseCache, SystemClock } from './promise_cache.js';\n\nexport type CacheHelper = <T extends JSONValue>(\n fn: () => Promise<T>,\n options: CacheOptions\n) => Promise<T>;\n\nexport function makeCache(\n redis: RedisClient,\n state: Partial<BlocksReconciler['state']> & { __cache?: LocalCache },\n clock: Clock = SystemClock\n): CacheHelper {\n const pc = new PromiseCache(redis, state, clock);\n return pc.cache.bind(pc);\n}\n", "import type { AssetMap } from '@devvit/shared-types/Assets.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\n\nexport type GetURLOptions = {\n webView?: boolean | undefined;\n};\n\nfunction assertValidUrl(path: string): void | never {\n // This will throw an exception if it's an invalid URL such as a relative path\n // NOTE: substring is here to only check up until the data segment if this is a data URL so we don't waste time needlessly parsing data.\n // Technically this will lose the last character if this isn't a data URL but we're just validating structure.\n new URL(path.slice(0, path.indexOf(',')));\n}\n\nexport class AssetsClient {\n readonly #assetMap: AssetMap = {};\n readonly #webViewAssetMap: AssetMap = {};\n\n constructor() {\n this.#assetMap = Devvit.assets;\n this.#webViewAssetMap = Devvit.webViewAssets;\n }\n\n /**\n * Gets the public URLs for an asset.\n * @param assetPath A path, relative to the 'assets/' folder.\n * @param options\n * @returns The public URL for that asset (https://i.redd.it/<id>.<ext>)\n */\n getURL(assetPath: string): string;\n\n getURL(assetPath: string, options: GetURLOptions | undefined): string;\n\n /**\n * Gets the public URLs for multiple assets.\n * @param assetPaths An array of paths, relative to the 'assets/' folder.\n * @returns A map of each asset path to its public URL (https://i.redd.it/<id>.<ext>)\n */\n getURL(assetPaths: string[]): AssetMap;\n\n getURL(assetPaths: string[], options: GetURLOptions | undefined): AssetMap;\n /**\n * Takes one or more asset names, relative to the 'assets/' folder, and returns either the\n * public URL for that one asset, or a map of each asset name to its URL.\n * @param assetPathOrPaths - Either the path you need the public URL for, or an array of paths.\n * @param options\n * @returns Either the public URL for the one asset you asked for, or a map of assets to their URLs.\n */\n getURL(\n assetPathOrPaths: string | string[],\n options?: GetURLOptions | undefined\n ): string | AssetMap {\n if (typeof assetPathOrPaths === 'string') {\n return this.#getURL(assetPathOrPaths, options ?? { webView: false });\n }\n return this.#getURLs(assetPathOrPaths, options ?? { webView: false });\n }\n\n #getURL(assetPath: string, options: GetURLOptions): string {\n // Has the assetPath already been resolved?\n const localUrl = options.webView ? this.#webViewAssetMap[assetPath] : this.#assetMap[assetPath];\n if (localUrl) {\n return localUrl;\n }\n\n try {\n assertValidUrl(assetPath);\n // URL is valid\n return assetPath;\n } catch {\n // Not a fully qualified URL, not an asset, return an empty string\n return '';\n }\n }\n\n #getURLs(assetPaths: string[], options: GetURLOptions): AssetMap {\n const retval: Record<string, string> = {};\n let missingPaths: string[] = [];\n\n // Try and short circuit using the locally available assets list if possible, keeping a list\n // of all the paths that we couldn't find locally to ask the backend about\n const cache = options.webView ? this.#webViewAssetMap : this.#assetMap;\n if (cache) {\n for (const path of assetPaths) {\n if (cache[path]) {\n retval[path] = cache[path];\n } else {\n try {\n assertValidUrl(path);\n retval[path] = path;\n } catch {\n // invalid URL, missing from cache\n missingPaths.push(path);\n }\n }\n }\n } else {\n // No local assets - everything is missing\n missingPaths = assetPaths;\n }\n\n if (missingPaths.length > 0) {\n throw new Error(\n `The following assets were missing from the assets list: ${missingPaths.join(', ')}`\n );\n }\n\n return retval;\n }\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { KVStore } from '../../types/kvStore.js';\n\nexport class KeyValueStorage implements KVStore {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async get<T extends JSONValue = JSONValue>(key: string): Promise<T | undefined> {\n const { messages } = await Devvit.kvStorePlugin.Get({ keys: [key] }, this.#metadata);\n try {\n if (messages[key]) {\n return JSON.parse(messages[key]);\n }\n } catch {\n return undefined;\n }\n\n return undefined;\n }\n\n async put(key: string, value: JSONValue): Promise<void> {\n const messages: { [key: string]: string } = {};\n messages[key] = JSON.stringify(value);\n await Devvit.kvStorePlugin.Put({ messages }, this.#metadata);\n }\n\n async delete(key: string): Promise<void> {\n await Devvit.kvStorePlugin.Del({ keys: [key] }, this.#metadata);\n }\n\n async list(): Promise<string[]> {\n const { keys } = await Devvit.kvStorePlugin.List({ filter: '*' }, this.#metadata);\n return keys;\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { MediaAsset, MediaPlugin, UploadMediaOptions } from '../../types/media.js';\n\nexport class MediaClient implements MediaPlugin {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async upload(opts: UploadMediaOptions): Promise<MediaAsset> {\n const response = await Devvit.mediaPlugin.Upload(opts, this.#metadata);\n if (!response.mediaId) {\n throw new Error('unable to get mediaId via uploads');\n }\n return Promise.resolve({ mediaId: response.mediaId, mediaUrl: response.mediaUrl });\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { ModLog, ModLogAddOptions } from '../../types/modlog.js';\n\nexport class ModLogClient implements ModLog {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async add(options: Readonly<ModLogAddOptions>): Promise<void> {\n await Devvit.modLogPlugin.Add(options, this.#metadata);\n }\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\n\nexport class RealtimeClient {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async send(channel: string, msg: JSONValue): Promise<void> {\n // guarantee an object by wrapping msg. the key must align to useChannel().\n await Devvit.realtimePlugin.Send({ channel, data: { msg } }, this.#metadata);\n }\n}\n", "import type {\n BitfieldCommand as BitfieldCommandProto,\n HScanRequest,\n HScanResponse,\n Metadata,\n RedisAPI,\n TransactionId,\n ZMember,\n ZScanRequest,\n ZScanResponse,\n} from '@devvit/protos';\nimport { BitfieldOverflowBehavior, RedisKeyScope } from '@devvit/protos';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type {\n BitfieldCommand,\n RedisClient as RedisClientLike,\n SetOptions,\n TxClientLike,\n ZRangeOptions,\n} from '../../types/redis.js';\n\nfunction isRedisNilError(e: unknown): boolean {\n // TODO: Replace with impl in a Gatsby-only world\n //return e && e.details === 'redis: nil';\n\n if (\n e &&\n typeof e === 'object' &&\n 'message' in e &&\n e.message !== undefined &&\n typeof e.message === 'string'\n ) {\n return e.message.includes('redis: nil');\n } else {\n return false;\n }\n}\n\nexport class TxClient implements TxClientLike {\n #storage: RedisAPI;\n #transactionId: TransactionId;\n #metadata: Metadata | undefined;\n\n constructor(storage: RedisAPI, transactionId: TransactionId, metadata: Metadata) {\n this.#storage = storage;\n this.#transactionId = transactionId;\n this.#metadata = metadata;\n }\n\n async get(key: string): Promise<TxClientLike> {\n await this.#storage.Get({ key: key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async multi(): Promise<void> {\n await this.#storage.Multi(this.#transactionId, this.#metadata);\n }\n\n async set(key: string, value: string, options?: SetOptions): Promise<TxClientLike> {\n let expiration;\n if (options?.expiration) {\n expiration = Math.floor((options.expiration.getTime() - Date.now()) / 1000); // convert to seconds\n if (expiration < 1) {\n expiration = 1; // minimum expiration is 1 second, clock skew can cause issues, so let's set 1 second.\n }\n }\n await this.#storage.Set(\n {\n key,\n value,\n nx: options?.nx === true,\n xx: options?.xx === true,\n expiration: expiration || 0,\n transactionId: this.#transactionId,\n },\n this.#metadata\n );\n return this;\n }\n\n async del(...keys: string[]): Promise<TxClientLike> {\n await this.#storage.Del({ keys: keys, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async type(key: string): Promise<TxClientLike> {\n await this.#storage.Type({ key: key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async exec(): Promise<any[]> {\n const response = await this.#storage.Exec(this.#transactionId, this.#metadata);\n // eslint-disable-next-line\n let output: any[] = [];\n for (const result of response.response) {\n if (result.members) {\n output.push(result.members);\n } else if (result.nil !== undefined) {\n output.push(null);\n } else if (result.num !== undefined) {\n output.push(result.num);\n } else if (result.values !== undefined) {\n output.push(result.values.values);\n } else if (result.str !== undefined) {\n output.push(result.str);\n } else if (result.dbl !== undefined) {\n output.push(result.dbl);\n }\n }\n return output;\n }\n\n async discard(): Promise<void> {\n await this.#storage.Discard(this.#transactionId, this.#metadata);\n }\n\n async watch(...keys: string[]): Promise<TxClientLike> {\n await this.#storage.Watch({ keys: keys, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async unwatch(): Promise<TxClientLike> {\n await this.#storage.Unwatch(this.#transactionId, this.#metadata);\n return this;\n }\n\n async getRange(key: string, start: number, end: number): Promise<TxClientLike> {\n await this.#storage.GetRange(\n { key, start, end, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n async setRange(key: string, offset: number, value: string): Promise<TxClientLike> {\n await this.#storage.SetRange(\n { key, offset, value, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async strlen(key: string): Promise<TxClientLike> {\n return this.strLen(key);\n }\n\n async strLen(key: string): Promise<TxClientLike> {\n await this.#storage.Strlen({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async mget(keys: string[]): Promise<TxClientLike> {\n return this.mGet(keys);\n }\n\n async mGet(keys: string[]): Promise<TxClientLike> {\n await this.#storage.MGet({ keys, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async mset(keyValues: { [key: string]: string }): Promise<TxClientLike> {\n return this.mSet(keyValues);\n }\n\n async mSet(keyValues: { [key: string]: string }): Promise<TxClientLike> {\n const kv = Object.entries(keyValues).map(([key, value]) => ({ key, value }));\n await this.#storage.MSet({ kv, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async incrBy(key: string, value: number): Promise<TxClientLike> {\n await this.#storage.IncrBy({ key, value, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async expire(key: string, seconds: number): Promise<TxClientLike> {\n await this.#storage.Expire(\n { key, seconds, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async expireTime(key: string): Promise<TxClientLike> {\n await this.#storage.ExpireTime({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async zAdd(key: string, ...members: ZMember[]): Promise<TxClientLike> {\n await this.#storage.ZAdd({ key, members, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async zScore(key: string, member: string): Promise<TxClientLike> {\n await this.#storage.ZScore(\n { key: { key, transactionId: this.#transactionId }, member },\n this.#metadata\n );\n return this;\n }\n\n async zRank(key: string, member: string): Promise<TxClientLike> {\n await this.#storage.ZRank(\n { key: { key, transactionId: this.#transactionId }, member },\n this.#metadata\n );\n return this;\n }\n\n async zIncrBy(key: string, member: string, value: number): Promise<TxClientLike> {\n await this.#storage.ZIncrBy(\n { key, member, value, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async zScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<TxClientLike> {\n const request: ZScanRequest = {\n key,\n cursor,\n pattern,\n count,\n transactionId: this.#transactionId,\n };\n await this.#storage.ZScan(request, this.#metadata);\n return this;\n }\n\n async zCard(key: string): Promise<TxClientLike> {\n await this.#storage.ZCard({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async zRange(\n key: string,\n start: number | string,\n stop: number | string,\n options?: ZRangeOptions\n ): Promise<TxClientLike> {\n // eslint-disable-next-line\n let opts = { rev: false, byLex: false, byScore: false, offset: 0, count: 1000 };\n if (options?.reverse) {\n opts.rev = options.reverse;\n }\n if (options?.by === 'lex') {\n opts.byLex = true;\n } else if (options?.by === 'score') {\n opts.byScore = true;\n }\n if (options?.limit) {\n if (opts.byLex || opts.byScore) {\n opts.offset = options.limit.offset;\n opts.count = options.limit.count;\n } else {\n throw new Error(\n `zRange parsing error: 'limit' only allowed when 'options.by' is 'lex' or 'score'`\n );\n }\n }\n\n await this.#storage.ZRange(\n {\n key: { key: key, transactionId: this.#transactionId },\n start: start + '',\n stop: stop + '',\n ...opts,\n },\n this.#metadata\n );\n return this;\n }\n\n async zRem(key: string, members: string[]): Promise<TxClientLike> {\n await this.#storage.ZRem(\n { key: { key, transactionId: this.#transactionId }, members: members },\n this.#metadata\n );\n return this;\n }\n\n async zRemRangeByLex(key: string, min: string, max: string): Promise<TxClientLike> {\n await this.#storage.ZRemRangeByLex(\n { key: { key, transactionId: this.#transactionId }, min: min, max: max },\n this.#metadata\n );\n return this;\n }\n\n async zRemRangeByRank(key: string, start: number, stop: number): Promise<TxClientLike> {\n await this.#storage.ZRemRangeByRank(\n { key: { key, transactionId: this.#transactionId }, start: start, stop: stop },\n this.#metadata\n );\n return this;\n }\n\n async zRemRangeByScore(key: string, min: number, max: number): Promise<TxClientLike> {\n await this.#storage.ZRemRangeByScore(\n { key: { key, transactionId: this.#transactionId }, min: min, max: max },\n this.#metadata\n );\n return this;\n }\n\n async hgetall(key: string): Promise<TxClientLike> {\n return this.hGetAll(key);\n }\n\n async hGetAll(key: string): Promise<TxClientLike> {\n await this.#storage.HGetAll({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hget(key: string, field: string): Promise<TxClientLike> {\n return this.hGet(key, field);\n }\n\n async hGet(key: string, field: string): Promise<TxClientLike> {\n await this.#storage.HGet(\n { key: key, field: field, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async hMGet(key: string, fields: string[]): Promise<TxClientLike> {\n await this.#storage.HMGet(\n { key: key, fields: fields, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async hset(key: string, fieldValues: { [field: string]: string }): Promise<TxClientLike> {\n return this.hSet(key, fieldValues);\n }\n\n async hSet(key: string, fieldValues: { [field: string]: string }): Promise<TxClientLike> {\n const fv = Object.entries(fieldValues).map(([field, value]) => ({ field, value }));\n await this.#storage.HSet({ key, fv, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hincrby(key: string, field: string, value: number): Promise<TxClientLike> {\n return this.hIncrBy(key, field, value);\n }\n\n async hIncrBy(key: string, field: string, value: number): Promise<TxClientLike> {\n await this.#storage.HIncrBy(\n { key, field, value, transactionId: this.#transactionId },\n this.#metadata\n );\n return this;\n }\n\n async hdel(key: string, fields: string[]): Promise<TxClientLike> {\n return this.hDel(key, fields);\n }\n\n async hDel(key: string, fields: string[]): Promise<TxClientLike> {\n await this.#storage.HDel({ key, fields, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hscan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<TxClientLike> {\n return this.hScan(key, cursor, pattern, count);\n }\n\n async hScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<TxClientLike> {\n const request: HScanRequest = {\n key,\n cursor,\n pattern,\n count,\n transactionId: this.#transactionId,\n };\n await this.#storage.HScan(request, this.#metadata);\n return this;\n }\n\n async hkeys(key: string): Promise<TxClientLike> {\n return this.hKeys(key);\n }\n\n async hKeys(key: string): Promise<TxClientLike> {\n await this.#storage.HKeys({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n\n async hlen(key: string): Promise<TxClientLike> {\n return this.hLen(key);\n }\n\n async hLen(key: string): Promise<TxClientLike> {\n await this.#storage.HLen({ key, transactionId: this.#transactionId }, this.#metadata);\n return this;\n }\n}\n\n/**\n * This is a subset of the overall Redis API. You should be able to look up https://redis.io/commands\n * for more details on each command.\n *\n * For the moment, we've implemented a lot of the basic string/number commands, sorted sets, and transactions.\n * This is the most powerful subset and the safest.\n */\nexport class RedisClient implements RedisClientLike {\n readonly #metadata: Metadata;\n readonly #storage: RedisAPI | undefined;\n readonly scope: RedisKeyScope;\n readonly global: Omit<RedisClientLike, 'global'>;\n\n constructor(\n metadata: Metadata,\n storage: RedisAPI | undefined = undefined,\n scope: RedisKeyScope = RedisKeyScope.INSTALLATION\n ) {\n this.#metadata = metadata;\n this.#storage = storage;\n this.scope = scope;\n this.global =\n scope === RedisKeyScope.INSTALLATION\n ? new RedisClient(this.#metadata, this.#storage, RedisKeyScope.GLOBAL)\n : this;\n }\n\n get storage(): RedisAPI {\n return this.#storage || Devvit.redisPlugin;\n }\n\n async watch(...keys: string[]): Promise<TxClientLike> {\n const txId = await this.storage.Watch({ keys }, this.#metadata);\n return new TxClient(this.storage, txId, this.#metadata);\n }\n\n async get(key: string): Promise<string | undefined> {\n try {\n const response = await this.storage.Get(\n { key, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? (response.value ?? undefined) : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async getBuffer(key: string): Promise<Buffer | undefined> {\n try {\n const response = await this.storage.GetBytes(\n { key, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? Buffer.from(response.value) : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async set(key: string, value: string, options?: SetOptions): Promise<string> {\n let expiration;\n if (options?.expiration) {\n expiration = Math.floor((options.expiration.getTime() - Date.now()) / 1000); // convert to seconds\n if (expiration < 1) {\n expiration = 1; // minimum expiration is 1 second, clock skew can cause issues, so let's set 1 second.\n }\n }\n\n const response = await this.storage.Set(\n {\n key,\n value,\n nx: options?.nx === true && !options.xx,\n xx: options?.xx === true && !options.nx,\n expiration: expiration || 0,\n scope: this.scope,\n },\n this.#metadata\n );\n return response.value;\n }\n\n async exists(...keys: string[]): Promise<number> {\n const response = await this.storage.Exists({ keys, scope: this.scope }, this.#metadata);\n return response.existingKeys;\n }\n\n async del(...keys: string[]): Promise<void> {\n await this.storage.Del({ keys, scope: this.scope }, this.#metadata);\n }\n\n async incrBy(key: string, value: number): Promise<number> {\n const response = await this.storage.IncrBy({ key, value, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async getRange(key: string, start: number, end: number): Promise<string> {\n const response = await this.storage.GetRange(\n { key, start, end, scope: this.scope },\n this.#metadata\n );\n return response !== null ? response.value : response;\n }\n\n async setRange(key: string, offset: number, value: string): Promise<number> {\n const response = await this.storage.SetRange(\n { key, offset, value, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async strlen(key: string): Promise<number> {\n return this.strLen(key);\n }\n\n async strLen(key: string): Promise<number> {\n const response = await this.storage.Strlen({ key, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async expire(key: string, seconds: number): Promise<void> {\n await this.storage.Expire({ key, seconds, scope: this.scope }, this.#metadata);\n }\n\n async expireTime(key: string): Promise<number> {\n const response = await this.storage.ExpireTime({ key, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async zAdd(key: string, ...members: ZMember[]): Promise<number> {\n return (await this.storage.ZAdd({ key, members, scope: this.scope }, this.#metadata)).value;\n }\n\n async zRange(\n key: string,\n start: number | string,\n stop: number | string,\n options?: ZRangeOptions\n ): Promise<{ member: string; score: number }[]> {\n // eslint-disable-next-line\n let opts = { rev: false, byLex: false, byScore: false, offset: 0, count: 1000 };\n if (options?.reverse) {\n opts.rev = options.reverse;\n }\n if (options?.by === 'lex') {\n opts.byLex = true;\n } else if (options?.by === 'score') {\n opts.byScore = true;\n } else {\n // LIMIT requires BYLEX/BYSCORE\n opts.offset = 0;\n opts.count = 0;\n }\n\n if (options?.limit) {\n if (opts.byLex || opts.byScore) {\n opts.offset = options.limit.offset;\n opts.count = options.limit.count;\n } else {\n throw new Error(\n `zRange parsing error: 'limit' only allowed when 'options.by' is 'lex' or 'score'`\n );\n }\n }\n\n return (\n await this.storage.ZRange(\n { key: { key: key }, start: start + '', stop: stop + '', ...opts, scope: this.scope },\n this.#metadata\n )\n ).members;\n }\n\n async zRem(key: string, members: string[]): Promise<number> {\n const response = await this.storage.ZRem(\n { key: { key }, members, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zRemRangeByLex(key: string, min: string, max: string): Promise<number> {\n const response = await this.storage.ZRemRangeByLex(\n { key: { key }, min, max, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zRemRangeByRank(key: string, start: number, stop: number): Promise<number> {\n const response = await this.storage.ZRemRangeByRank(\n { key: { key }, start, stop, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zRemRangeByScore(key: string, min: number, max: number): Promise<number> {\n const response = await this.storage.ZRemRangeByScore(\n { key: { key }, min, max, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async zScore(key: string, member: string): Promise<number | undefined> {\n try {\n const response = await this.storage.ZScore(\n { key: { key }, member, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n\n return response !== null ? response.value : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async zRank(key: string, member: string): Promise<number | undefined> {\n try {\n const response = await this.storage.ZRank(\n { key: { key }, member, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? response.value : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async zIncrBy(key: string, member: string, value: number): Promise<number> {\n const response = await this.storage.ZIncrBy(\n { key, member, value, scope: this.scope },\n this.#metadata\n );\n return response !== null ? response.value : response;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n return this.mGet(keys);\n }\n\n async mGet(keys: string[]): Promise<(string | null)[]> {\n const response = await this.storage.MGet({ keys, scope: this.scope }, this.#metadata);\n return response !== null ? response.values.map((value) => value || null) : response;\n }\n\n async mset(keyValues: { [key: string]: string }): Promise<void> {\n return this.mSet(keyValues);\n }\n\n async mSet(keyValues: { [key: string]: string }): Promise<void> {\n const kv = Object.entries(keyValues).map(([key, value]) => ({ key, value }));\n await this.storage.MSet({ kv, scope: this.scope }, this.#metadata);\n }\n\n async zCard(key: string): Promise<number> {\n const response = await this.storage.ZCard({ key, scope: this.scope }, this.#metadata);\n return response !== null ? response.value : response;\n }\n\n async zScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<ZScanResponse> {\n const request: ZScanRequest = { key, cursor, pattern, count, scope: this.scope };\n return await this.storage.ZScan(request, this.#metadata);\n }\n\n async type(key: string): Promise<string> {\n const response = await this.storage.Type({ key: key, scope: this.scope }, this.#metadata);\n return response !== null ? response.value : response;\n }\n\n async rename(key: string, newKey: string): Promise<string> {\n const response = await this.storage.Rename({ key, newKey, scope: this.scope }, this.#metadata);\n return response.result;\n }\n\n async hget(key: string, field: string): Promise<string | undefined> {\n return this.hGet(key, field);\n }\n\n async hGet(key: string, field: string): Promise<string | undefined> {\n try {\n const response = await this.storage.HGet(\n { key, field, scope: this.scope },\n {\n ...this.#metadata,\n 'throw-redis-nil': { values: ['true'] },\n }\n );\n return response !== null ? (response.value ?? undefined) : response;\n } catch (e) {\n if (isRedisNilError(e)) {\n return undefined;\n }\n\n throw e;\n }\n }\n\n async hMGet(key: string, fields: string[]): Promise<(string | null)[]> {\n const response = await this.storage.HMGet({ key, fields, scope: this.scope }, this.#metadata);\n return response !== null ? response.values.map((value) => value || null) : response;\n }\n\n async hset(key: string, fieldValues: { [field: string]: string }): Promise<number> {\n return this.hSet(key, fieldValues);\n }\n\n async hSet(key: string, fieldValues: { [field: string]: string }): Promise<number> {\n const fv = Object.entries(fieldValues).map(([field, value]) => ({ field, value }));\n const response = await this.storage.HSet({ key, fv, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async hSetNX(key: string, field: string, value: string): Promise<number> {\n const response = await this.storage.HSetNX(\n { key, field, value, scope: this.scope },\n this.#metadata\n );\n return response.success;\n }\n\n async hgetall(key: string): Promise<Record<string, string>> {\n return this.hGetAll(key);\n }\n\n async hGetAll(key: string): Promise<Record<string, string>> {\n const response = await this.storage.HGetAll({ key, scope: this.scope }, this.#metadata);\n return response !== null ? response.fieldValues : response;\n }\n\n async hdel(key: string, fields: string[]): Promise<number> {\n return this.hDel(key, fields);\n }\n\n async hDel(key: string, fields: string[]): Promise<number> {\n const response = await this.storage.HDel({ key, fields, scope: this.scope }, this.#metadata);\n return response.value;\n }\n\n async hscan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<HScanResponse> {\n return this.hScan(key, cursor, pattern, count);\n }\n\n async hScan(\n key: string,\n cursor: number,\n pattern?: string | undefined,\n count?: number | undefined\n ): Promise<HScanResponse> {\n const request: HScanRequest = { key, cursor, pattern, count, scope: this.scope };\n return await this.storage.HScan(request, this.#metadata);\n }\n\n async hkeys(key: string): Promise<string[]> {\n return this.hKeys(key);\n }\n\n async hKeys(key: string): Promise<string[]> {\n const response = await this.storage.HKeys({ key, scope: this.scope }, this.#metadata);\n return response !== null ? response.keys : response;\n }\n\n async hincrby(key: string, field: string, value: number): Promise<number> {\n return this.hIncrBy(key, field, value);\n }\n\n async hIncrBy(key: string, field: string, value: number): Promise<number> {\n const response = await this.storage.HIncrBy(\n { key, field, value, scope: this.scope },\n this.#metadata\n );\n return response.value;\n }\n\n async hlen(key: string): Promise<number> {\n return this.hLen(key);\n }\n\n async hLen(key: string): Promise<number> {\n const response = await this.storage.HLen({\n key,\n scope: this.scope,\n });\n return response.value;\n }\n\n async bitfield(\n key: string,\n ...cmds:\n | []\n | BitfieldCommand\n | [...BitfieldCommand, ...BitfieldCommand]\n | [...BitfieldCommand, ...BitfieldCommand, ...BitfieldCommand, ...(number | string)[]]\n ): Promise<number[]> {\n const commands: BitfieldCommandProto[] = [];\n for (let argIndex = 0; argIndex < cmds.length; ) {\n const currentArg = cmds[argIndex];\n const command: BitfieldCommandProto = {};\n\n switch (currentArg) {\n case 'get': {\n if (argIndex + 2 >= cmds.length) {\n throw Error(`bitfield command parse failed; not enough arguments for 'get' command`);\n }\n command.get = {\n encoding: cmds[argIndex + 1] as string,\n offset: cmds[argIndex + 2].toString(),\n };\n\n argIndex += 3;\n break;\n }\n case 'set': {\n if (argIndex + 3 >= cmds.length) {\n throw Error(`bitfield command parse failed; not enough arguments for 'set' command`);\n }\n command.set = {\n encoding: cmds[argIndex + 1] as string,\n offset: cmds[argIndex + 2].toString(),\n value: cmds[argIndex + 3].toString(),\n };\n\n argIndex += 4;\n break;\n }\n case 'incrBy': {\n if (argIndex + 3 >= cmds.length) {\n throw Error(`bitfield command parse failed; not enough arguments for 'incrBy' command`);\n }\n command.incrBy = {\n encoding: cmds[argIndex + 1] as string,\n offset: cmds[argIndex + 2].toString(),\n increment: cmds[argIndex + 3].toString(),\n };\n\n argIndex += 4;\n break;\n }\n case 'overflow': {\n if (argIndex + 1 >= cmds.length) {\n throw Error(\n `bitfield command parse failed; not enough arguments for 'overflow' command`\n );\n }\n const behavior = cmds[argIndex + 1].toString();\n command.overflow = {\n behavior: toBehaviorProto(behavior),\n };\n\n argIndex += 2;\n break;\n }\n default: {\n throw Error(\n `bitfield command parse failed; ${currentArg} unrecognized (must be 'get', 'set', 'incrBy', or 'overflow')`\n );\n }\n }\n commands.push(command);\n }\n\n const response = await this.storage.Bitfield({\n key,\n commands,\n });\n\n return response.results;\n }\n}\n\nfunction toBehaviorProto(behavior: string): BitfieldOverflowBehavior {\n const lowercase = behavior.toLowerCase();\n switch (lowercase) {\n case 'wrap':\n return BitfieldOverflowBehavior.BITFIELD_OVERFLOW_BEHAVIOR_WRAP;\n case 'sat':\n return BitfieldOverflowBehavior.BITFIELD_OVERFLOW_BEHAVIOR_SAT;\n case 'fail':\n return BitfieldOverflowBehavior.BITFIELD_OVERFLOW_BEHAVIOR_FAIL;\n default:\n throw Error(`unknown bitfield overflow behavior: ${lowercase}`);\n }\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type {\n ScheduledCronJob,\n ScheduledCronJobOptions,\n ScheduledJob,\n ScheduledJobOptions,\n Scheduler,\n} from '../../types/scheduler.js';\n\nexport class SchedulerClient implements Scheduler {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async runJob(job: ScheduledJobOptions | ScheduledCronJobOptions): Promise<string> {\n const response = await Devvit.schedulerPlugin.Schedule(\n {\n action: {\n type: job.name,\n data: job.data,\n },\n cron: 'cron' in job ? job.cron : undefined,\n when: 'runAt' in job ? job.runAt : undefined,\n },\n this.#metadata\n );\n\n return response.id;\n }\n\n async cancelJob(jobId: string): Promise<void> {\n await Devvit.schedulerPlugin.Cancel(\n {\n id: jobId,\n },\n this.#metadata\n );\n }\n\n async listJobs(): Promise<(ScheduledJob | ScheduledCronJob)[]> {\n const response = await Devvit.schedulerPlugin.List(\n /**\n * after and before are required for this API to work\n * so we hardcode after to Unix epoch and before to 10 years from now\n * https://reddit.atlassian.net/browse/DX-3060\n */\n {\n after: new Date(0),\n before: new Date(Date.now() + 10 * 365 * 86400 * 1000),\n },\n this.#metadata\n );\n\n return response.actions.map((action) => {\n assertNonNull(action.request?.action, 'Scheduled job is malformed');\n\n if ('when' in action.request && action.request.when != null) {\n return {\n id: action.id,\n name: action.request.action.type,\n runAt: action.request.when,\n data: action.request.action.data as JSONObject | undefined,\n };\n }\n\n return {\n id: action.id,\n name: action.request.action.type,\n cron: action.request.cron ?? '',\n data: action.request.action as unknown as JSONObject | undefined,\n };\n });\n }\n}\n", "import { FormFieldType, type FormFieldValue, type Metadata } from '@devvit/protos';\nimport type { SettingsResponse } from '@devvit/protos/types/devvit/plugin/settings/v1alpha/settings.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type {\n SettingsClient as _SettingsClient,\n SettingsFormField,\n SettingsValues,\n} from '../../types/settings.js';\nimport { flattenFormFieldValue } from '../ui/helpers/getFormValues.js';\n\nexport class SettingsClient implements _SettingsClient {\n readonly #metadata: Metadata;\n\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n async get<T = string | number | boolean | string[] | undefined>(\n name: string\n ): Promise<T | undefined> {\n const settings = await this.getAll();\n return settings[name] as T;\n }\n\n async getAll<T extends object = SettingsValues>(): Promise<T> {\n const settingsClient = Devvit.settingsPlugin;\n\n const response = await settingsClient.GetSettings({}, this.#metadata);\n\n if (!response.installationSettings) {\n throw new Error('Could not get installation settings');\n }\n\n if (!response.appSettings) {\n throw new Error('Could not get app settings');\n }\n\n cleanAppSettings(response);\n\n return {\n ...getSettingsValues(response.installationSettings.settings, Devvit.installationSettings),\n ...getSettingsValues(response.appSettings.settings, Devvit.appSettings),\n } as T;\n }\n}\n\nexport function cleanAppSettings(response: SettingsResponse): void {\n if (!response.appSettings) {\n throw new Error('Could not get app settings');\n }\n\n // FIX: appSettings don't come back typed correctly for bools or numbers.\n // Fix that. This is a workaround. We intend to make appSettings set this\n // way entirely obsolete in the future; this is just a stopgap.\n for (const [key, value] of Object.entries(response.appSettings.settings)) {\n // Find the matching setting definition, because we can't trust the types in the response.\n const settingDefinition = Devvit.appSettings?.find((s) => s.type !== 'group' && s.name === key);\n if (!settingDefinition) {\n continue;\n }\n\n // If the setting is a boolean or number, we need to convert it from string to the correct type.\n if (\n settingDefinition.type === 'boolean' &&\n value.fieldType === FormFieldType.STRING &&\n value.boolValue == null &&\n value.stringValue != null\n ) {\n value.fieldType = FormFieldType.BOOLEAN;\n value.boolValue = Boolean(value.stringValue);\n delete value.stringValue;\n } else if (\n settingDefinition.type === 'number' &&\n value.fieldType === FormFieldType.STRING &&\n value.numberValue == null &&\n value.stringValue != null\n ) {\n value.fieldType = FormFieldType.NUMBER;\n value.numberValue = Number(value.stringValue);\n delete value.stringValue;\n }\n }\n}\n\nexport function getSettingsValues(\n results: { [key: string]: FormFieldValue },\n settingsDefinitions: SettingsFormField[] | undefined\n): SettingsValues {\n const settingsValues = Object.keys(results).reduce((acc, key) => {\n acc[key] = flattenFormFieldValue(results[key]);\n return acc;\n }, {} as SettingsValues);\n\n if (settingsDefinitions) {\n setDefaultsIfNecessary(settingsValues, settingsDefinitions);\n }\n\n return settingsValues;\n}\n\nexport function setDefaultsIfNecessary(\n settingsValues: SettingsValues,\n settingsDefinitions: SettingsFormField[]\n): void {\n for (const definition of settingsDefinitions) {\n if (definition.type === 'group') {\n // Groups get their defaults set recursively\n setDefaultsIfNecessary(settingsValues, definition.fields);\n } else {\n // Only set the default if the value is not already set - note that this checks if the key is\n // missing from the object, not if the value is falsy. So if the user somehow manually sets a\n // setting to `undefined`, we won't use the default value here. Same goes for 'defaultValue' -\n // if the user didn't set a default value, we won't set it to anything.\n if (!(definition.name in settingsValues) && 'defaultValue' in definition) {\n settingsValues[definition.name] = definition.defaultValue;\n }\n }\n }\n}\n", "import {\n type Effect,\n EffectType,\n Form,\n type Toast as ToastProto,\n ToastAppearance,\n} from '@devvit/protos';\nimport type { JSONObject, JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport { Devvit } from '../../devvit/Devvit.js';\nimport type { BlocksReconciler } from '../../devvit/internals/blocks/BlocksReconciler.js';\nimport type { Toast } from '../../types/toast.js';\nimport type { UIClient as _UIClient } from '../../types/ui-client.js';\nimport type { WebViewUIClient } from '../../types/web-view-ui-client.js';\nimport type { Comment, Post, Subreddit, User } from '../reddit/models/index.js';\nimport { assertValidFormFields } from './helpers/assertValidFormFields.js';\nimport { transformFormFields } from './helpers/transformForm.js';\n\nexport class UIClient implements _UIClient {\n readonly #effects: Effect[] = [];\n readonly #reconciler: BlocksReconciler | undefined;\n readonly #webViewClient: WebViewUIClient;\n\n constructor(reconciler?: BlocksReconciler) {\n this.#reconciler = reconciler;\n this.#webViewClient = {\n postMessage: this.#postMessage,\n };\n }\n\n get webView(): WebViewUIClient {\n return this.#webViewClient;\n }\n\n showForm(formKey: FormKey, data?: JSONObject | undefined): void {\n let formDefinition = Devvit.formDefinitions.get(formKey);\n\n if (!formDefinition && this.#reconciler) {\n const hookForm = this.#reconciler.forms.get(formKey);\n\n if (hookForm) {\n formDefinition = {\n form: hookForm,\n onSubmit: () => {}, // no-op\n };\n }\n }\n\n if (!formDefinition) {\n throw new Error(\n 'Form does not exist. Make sure you have added it using Devvit.createForm at the root of your app.'\n );\n }\n\n const formData =\n formDefinition.form instanceof Function\n ? formDefinition.form(data ?? {})\n : formDefinition.form;\n\n const form: Form = {\n fields: [],\n id: formKey,\n title: formData.title,\n acceptLabel: formData.acceptLabel,\n cancelLabel: formData.cancelLabel,\n shortDescription: formData.description,\n };\n\n assertValidFormFields(formData.fields);\n form.fields = transformFormFields(formData.fields);\n\n this.#effects.push({\n type: EffectType.EFFECT_SHOW_FORM,\n showForm: {\n form,\n },\n });\n }\n\n showToast(text: string): void;\n showToast(toast: Toast): void;\n showToast(textOrToast: string | Toast): void {\n let toast: ToastProto;\n\n if (textOrToast instanceof Object) {\n toast = {\n text: textOrToast.text,\n appearance:\n textOrToast.appearance === 'success' ? ToastAppearance.SUCCESS : ToastAppearance.NEUTRAL,\n };\n } else {\n toast = {\n text: textOrToast,\n };\n }\n\n this.#effects.push({\n type: EffectType.EFFECT_SHOW_TOAST,\n showToast: {\n toast,\n },\n });\n }\n\n navigateTo(url: string): void;\n navigateTo(subreddit: Subreddit): void;\n navigateTo(post: Post): void;\n navigateTo(comment: Comment): void;\n navigateTo(user: User): void;\n navigateTo(thingOrUrl: string | Subreddit | Post | Comment | User): void {\n let url: string;\n\n if (typeof thingOrUrl === 'string') {\n // Validate URL\n url = new URL(thingOrUrl).toString();\n } else {\n url = new URL(thingOrUrl.permalink, 'https://www.reddit.com').toString();\n }\n this.#effects.push({\n type: EffectType.EFFECT_NAVIGATE_TO_URL,\n navigateToUrl: {\n url,\n },\n });\n }\n\n #postMessage: WebViewUIClient['postMessage'] = <T extends JSONValue>(\n webViewIdOrMessage: string | T,\n message?: T | undefined\n ): void => {\n const webViewId = message !== undefined ? (webViewIdOrMessage as string) : '';\n const msg = message !== undefined ? message : webViewIdOrMessage;\n this.#effects.push({\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n postMessage: {\n webViewId,\n app: { message: msg },\n },\n },\n });\n };\n\n /** @internal */\n get __effects(): Effect[] {\n return this.#effects;\n }\n}\n", "import type { Metadata } from '@devvit/protos';\n\nimport type { BlocksReconciler } from '../devvit/internals/blocks/BlocksReconciler.js';\nimport { makeUseChannelHook } from '../devvit/internals/blocks/useChannel.js';\nimport { makeUseFormHook } from '../devvit/internals/blocks/useForm.js';\nimport { makeUseIntervalHook } from '../devvit/internals/blocks/useInterval.js';\nimport { makeUseStateHook } from '../devvit/internals/blocks/useState.js';\nimport { makeCache } from '../devvit/internals/cache.js';\nimport type { ContextAPIClients } from '../index.js';\nimport { AssetsClient } from './AssetsClient/AssetsClient.js';\nimport { KeyValueStorage } from './key-value-storage/KeyValueStorage.js';\nimport { MediaClient } from './media/MediaClient.js';\nimport { ModLogClient } from './modLog/ModLogClient.js';\nimport { RealtimeClient } from './realtime/RealtimeClient.js';\nimport { RedisClient } from './redis/RedisClient.js';\nimport { SchedulerClient } from './scheduler/SchedulerClient.js';\nimport { SettingsClient } from './settings/SettingsClient.js';\nimport { UIClient } from './ui/UIClient.js';\n\nexport type MakeAPIClientsOptions = {\n metadata: Metadata;\n ui?: boolean;\n hooks?: boolean;\n reconciler?: BlocksReconciler;\n};\n\nexport function makeAPIClients({\n metadata,\n ui,\n hooks,\n reconciler,\n}: MakeAPIClientsOptions): ContextAPIClients {\n const modLog = new ModLogClient(metadata);\n const kvStore = new KeyValueStorage(metadata);\n const redis = new RedisClient(metadata);\n const cache = makeCache(redis, reconciler ? reconciler.state : {});\n const scheduler = new SchedulerClient(metadata);\n const settings = new SettingsClient(metadata);\n const uiClient = ui ? new UIClient(reconciler) : undefined;\n const media = new MediaClient(metadata);\n const assets = new AssetsClient();\n const realtime = new RealtimeClient(metadata);\n const useState = hooks && reconciler ? makeUseStateHook(reconciler) : undefined;\n const useInterval = hooks && reconciler ? makeUseIntervalHook(reconciler) : undefined;\n const useForm = hooks && reconciler ? makeUseFormHook(reconciler) : undefined;\n const useChannel = hooks && reconciler ? makeUseChannelHook(reconciler) : undefined;\n\n return {\n modLog,\n kvStore,\n redis,\n scheduler,\n settings,\n media,\n assets,\n realtime,\n get useForm() {\n return (useForm ??\n (() => {\n throw new Error('useForm() is unavailable');\n })) as ContextAPIClients['useForm'];\n },\n get cache() {\n return (\n cache ??\n (() => {\n throw new Error('cache() is unavailable');\n })\n );\n },\n get useState() {\n return (\n useState ??\n (() => {\n throw new Error('useState() is unavailable');\n })\n );\n },\n get useInterval() {\n return (\n useInterval ??\n (() => {\n throw new Error('useInterval() is unavailable');\n })\n );\n },\n get useChannel() {\n return (\n useChannel ??\n (() => {\n throw new Error('useChannel() is unavailable');\n })\n );\n },\n get ui() {\n if (!uiClient) {\n throw new Error('ui client is not available');\n }\n return uiClient;\n },\n };\n}\n", "{\n \"name\": \"@devvit/public-api\",\n \"version\": \"0.12.0-dev\",\n \"license\": \"BSD-3-Clause\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://developers.reddit.com/\"\n },\n \"type\": \"module\",\n \"exports\": {\n \".\": \"./dist/index.js\",\n \"./package.json\": \"./package.json\",\n \"./*\": \"./dist/*\"\n },\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"dist/**\"\n ],\n \"scripts\": {\n \"build\": \"yarn build:icon-types && yarn build:semantic-colors && tsc && cp -af devvit.tsconfig.json dist/ && yarn build:types && yarn build:min && yarn build:unmin\",\n \"build:icon-types\": \"make-icons src/types/icons.ts\",\n \"build:min\": \"esbuild --bundle --sourcemap=linked --target=es2020 --format=esm --metafile=dist/meta.min.json --outfile=dist/public-api.min.js --external:@devvit/protos --minify src/index.ts\",\n \"build:semantic-colors\": \"node scripts/make-semantic-colors.js\",\n \"build:types\": \"api-extractor run && sed -ne '/declare global {/,$ p' src/devvit/Devvit.ts >> dist/public-api.d.ts\",\n \"build:unmin\": \"esbuild --bundle --sourcemap=inline --target=es2020 --format=iife --metafile=dist/meta.json --outfile=dist/public-api.iife.js --global-name=devvitPublicAPI src/index.ts\",\n \"clean\": \"rm -rf .turbo api-extractor coverage dist src/devvit/internals/semanticColors.ts src/types/icons.ts || :\",\n \"clobber\": \"yarn clean && rm -rf node_modules\",\n \"dev\": \"tsc -w\",\n \"dev:build\": \"chokidar ./src ../shared-types/dist --command 'yarn build' --ignore './src/types/icons.ts' --ignore './src/devvit/internals/semanticColors.ts'\",\n \"lint\": \"redlint .\",\n \"lint:fix\": \"yarn lint --fix\",\n \"prepublishOnly\": \"publish-package-json\",\n \"test\": \"yarn test:unit && yarn test:types && yarn test:size\",\n \"test:size\": \"filesize\",\n \"test:types\": \"tsc --noEmit\",\n \"test:unit\": \"vitest run\",\n \"test:unit-with-coverage\": \"vitest run --coverage\"\n },\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@devvit/metrics\": \"0.12.0-dev\",\n \"@devvit/protos\": \"0.12.0-dev\",\n \"@devvit/shared-types\": \"0.12.0-dev\",\n \"base64-js\": \"1.5.1\",\n \"clone-deep\": \"4.0.1\",\n \"moderndash\": \"4.0.0\"\n },\n \"devDependencies\": {\n \"@ampproject/filesize\": \"4.3.0\",\n \"@devvit/repo-tools\": \"0.12.0-dev\",\n \"@devvit/tsconfig\": \"0.12.0-dev\",\n \"@microsoft/api-extractor\": \"7.41.0\",\n \"@reddit/faceplate-ui\": \"18.0.1\",\n \"@types/clone-deep\": \"4.0.1\",\n \"@vitest/coverage-c8\": \"0.32.0\",\n \"chokidar-cli\": \"3.0.0\",\n \"esbuild\": \"0.23.0\",\n \"eslint\": \"9.11.1\",\n \"typescript\": \"5.3.2\",\n \"vitest\": \"1.6.0\"\n },\n \"publishConfig\": {\n \"directory\": \"dist\"\n },\n \"filesize\": {\n \"dist/public-api.min.js\": {\n \"gzip\": \"62 KB\",\n \"none\": \"218 KB\"\n }\n },\n \"source\": \"./src/index.ts\"\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { AppDebug } from '@devvit/shared-types/Header.js';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport type { BaseContext, ContextDebugInfo } from '../../types/context.js';\nimport pkg from '../../version.json' with { type: 'json' };\n\nexport function getContextFromMetadata(\n metadata: Metadata,\n postId?: string,\n commentId?: string\n): BaseContext {\n const subredditId = metadata[Header.Subreddit]?.values[0];\n assertNonNull<string | undefined>(subredditId, 'subreddit is missing from Context');\n\n const subredditName = metadata[Header.SubredditName]?.values[0];\n\n // devvit-app-user is only available in the remote runtime.\n const appAccountId = metadata[Header.AppUser]?.values[0];\n const appName = metadata[Header.App]?.values[0];\n const appVersion = metadata[Header.Version]?.values[0];\n\n const userId = metadata[Header.User]?.values[0];\n const debug = parseDebug(metadata);\n\n return {\n get appAccountId() {\n assertNonNull<string | undefined>(appAccountId, 'appAccountId is missing from Context');\n return appAccountId;\n },\n subredditId,\n subredditName,\n userId,\n postId,\n commentId,\n appName,\n appVersion,\n debug,\n metadata,\n toJSON() {\n return {\n appAccountId,\n appName,\n appVersion,\n subredditId,\n subredditName,\n userId,\n postId,\n commentId,\n debug,\n metadata,\n };\n },\n };\n}\n\n/** @internal */\nexport function parseDebug(meta: Readonly<Metadata>): ContextDebugInfo {\n // Roughly aligns to initDevvitGlobal() except an empty devvit-debug doesn't\n // enable all.\n\n // All known AppDebug keys.\n const keyset: { readonly [key in AppDebug]: undefined } = {\n blocks: undefined,\n emitSnapshots: undefined,\n emitState: undefined,\n realtime: undefined,\n runtime: undefined,\n surface: undefined,\n useAsync: undefined,\n payments: undefined,\n bootstrap: undefined,\n webView: undefined,\n };\n // {[key: Lowercase<AppDebug>]: AppDebug}\n const lowerKeyToKey: { [lower: string]: string } = {};\n for (const key in keyset) lowerKeyToKey[key.toLowerCase()] = key;\n\n const debug: { [key in AppDebug]?: string } = {};\n // hack: gRPC-web header values don't split in compute. always join then split\n // for parity in both local and remote runtimes.\n for (const kv of (meta[Header.Debug]?.values ?? []).join().split(',')) {\n let [k, v] = kv.split('=');\n if (!k) continue;\n\n k = k.trim();\n v = v?.trim();\n\n if (k.toLowerCase() in lowerKeyToKey) k = lowerKeyToKey[k.toLowerCase()];\n\n debug[k as AppDebug] ??= v || `${true}`;\n }\n\n // @devvit/public-api v1.2.3 emitSnapshots=true foo=bar\n if (meta[Header.Debug])\n console.info(\n `[api] @devvit/public-api v${pkg.version} ${Object.entries(debug)\n .map(([k, v]) => `${k}=${v}`)\n .join(' ')}`\n );\n\n return { ...debug };\n}\n", "import type { Metadata, RenderPostRequest } from '@devvit/protos';\nimport { CustomPostDefinition, RenderPostResponse } from '@devvit/protos';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { Devvit } from '../Devvit.js';\nimport { BlocksReconciler } from './blocks/BlocksReconciler.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nasync function renderPost(\n req: RenderPostRequest,\n metadata: Metadata\n): Promise<DeepPartial<RenderPostResponse>> {\n const customPostType = Devvit.customPostType;\n\n if (!customPostType) {\n throw new Error('Custom post type not registered');\n }\n\n const reconciler = new BlocksReconciler(\n (_props, context) => customPostType.render(context),\n req.blocks,\n req.state,\n metadata,\n req.dimensions\n );\n\n const blocksUI = await reconciler.render();\n\n return RenderPostResponse.fromJSON({\n state: reconciler.state,\n blocks: {\n ui: blocksUI,\n },\n effects: reconciler.getEffects(),\n });\n}\n\nexport function registerCustomPost(config: Config): void {\n config.provides(CustomPostDefinition);\n extendDevvitPrototype('RenderPost', renderPost);\n}\n", "import type {\n Block,\n BlockRenderRequest,\n Dimensions,\n Effect,\n FormSubmittedEvent,\n Metadata,\n RealtimeSubscriptionEvent,\n UIEvent,\n} from '@devvit/protos';\nimport { BlockRenderEventType, EffectType } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { JSONObject, PartialJSONObject } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { AssetsClient } from '../../../apis/AssetsClient/AssetsClient.js';\nimport { makeAPIClients } from '../../../apis/makeAPIClients.js';\nimport type { ModLogClient } from '../../../apis/modLog/ModLogClient.js';\nimport type { RealtimeClient } from '../../../apis/realtime/RealtimeClient.js';\nimport { getEffectsFromUIClient } from '../../../apis/ui/helpers/getEffectsFromUIClient.js';\nimport type {\n Form,\n FormFunction,\n MediaPlugin,\n Scheduler,\n SettingsClient,\n UIClient,\n UseChannelHook,\n UseFormHook,\n UseFormHookState,\n UseIntervalHook,\n UseIntervalHookState,\n UseStateHook,\n} from '../../../types/index.js';\nimport type { KVStore } from '../../../types/kvStore.js';\nimport type { RedisClient } from '../../../types/redis.js';\nimport type { BlockElement } from '../../Devvit.js';\nimport { Devvit } from '../../Devvit.js';\nimport type { CacheHelper } from '../cache.js';\nimport { getContextFromMetadata } from '../context.js';\nimport { makeUniqueIdGenerator } from '../helpers/makeUniqueIdGenerator.js';\nimport type { LocalCache } from '../promise_cache.js';\nimport { BlocksTransformer } from './BlocksTransformer.js';\nimport type { EffectEmitter } from './EffectEmitter.js';\n\nexport type ReifiedBlockElement = {\n type: string;\n\n props: { [key: string]: unknown } | undefined;\n children: ReifiedBlockElementOrLiteral[];\n};\n\nexport type ReifiedBlockElementOrLiteral = ReifiedBlockElement | string;\n\n/** Serializable. */\ntype ComponentState = {\n [hookIndex: number]: UseIntervalHookState | UseFormHookState | JSONObject;\n};\n\n/** Serializable. */\ntype RenderState = {\n [componentKey: string]: ComponentState;\n};\n\nexport function assertNotString(\n reified: ReifiedBlockElementOrLiteral\n): asserts reified is ReifiedBlockElement {\n if (typeof reified === 'string') {\n throw new Error('Root element cannot be a string');\n }\n}\n\n// FIXME: don't build XML with a string concatenation :facepalm:\nconst toXML = (node: ReifiedBlockElement): string => {\n let xml = '';\n\n const attributes = node.props\n ? Object.keys(node.props)\n .map((key) => (key === 'onPress' ? '' : `${key}=\"${node.props![key]}\"`))\n .join(' ')\n : '';\n\n xml += `<${node.type}${attributes ? ' ' + attributes : ''}>`;\n\n for (const child of node.children) {\n if (typeof child === 'string') {\n xml += child;\n } else {\n xml += toXML(child);\n }\n }\n\n xml += `</${node.type}>`;\n\n return xml;\n};\n\nfunction getIndentation(level: number): string {\n return ' '.repeat(level);\n}\n\n// FIXME: don't build XML with a string concatenation :facepalm:\nfunction indentXML(xml: string): string {\n let formatted = '';\n let indentLevel = 0;\n const xmlArr = xml.split(/>\\s*</);\n for (let i = 0; i < xmlArr.length; i++) {\n let node = xmlArr[i];\n if (i === 0) {\n node = node.trim();\n }\n if (i === xmlArr.length - 1) {\n node = node.trim();\n }\n const isClosingTag = node.charAt(0) === '/';\n if (isClosingTag) {\n indentLevel--;\n }\n formatted += getIndentation(indentLevel);\n if (i !== 0) {\n formatted += '<';\n }\n formatted += node;\n if (i !== xmlArr.length - 1) {\n formatted += '>\\n';\n }\n if (!isClosingTag && node.indexOf('</') === -1 && node.charAt(node.length - 1) !== '/') {\n indentLevel++;\n }\n }\n return formatted;\n}\n\n/**\n * @internal\n * An instance of this class should be instantiated for each OnRender call.\n * This class is responsible for:\n * - rendering JSX elements into Blocks.\n * - managing state and hooks for each component.\n * - drilling the shared clients into function components.\n */\nexport class BlocksReconciler implements EffectEmitter {\n component: JSX.ComponentFunction;\n event: BlockRenderRequest | UIEvent | undefined;\n state: {\n __renderState: RenderState;\n __postData?: { thingId?: string };\n __realtimeChannels?: string[];\n __cache?: LocalCache;\n };\n metadata: Metadata;\n\n // Common clients for props\n modLog: ModLogClient;\n kvStore: KVStore;\n cache: CacheHelper;\n redis: RedisClient;\n scheduler: Scheduler;\n dimensions?: Dimensions;\n ui: UIClient;\n settings: SettingsClient;\n media: MediaPlugin;\n assets: AssetsClient;\n realtime: RealtimeClient;\n hooks: {\n useState: UseStateHook;\n useInterval: UseIntervalHook;\n useForm: UseFormHook;\n useChannel: UseChannelHook;\n };\n\n emitEffect(_dedupeKey: string, effect: Effect): void {\n this.effects.push(effect);\n }\n\n // hook management\n renderState: RenderState = {};\n currentComponentKey: string[] = [];\n currentHookIndex: number = 0;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n actions: Map<string, Function> = new Map();\n forms: Map<FormKey, Form | FormFunction> = new Map();\n realtimeChannels: string[] = [];\n realtimeUpdated: boolean = false;\n pendingHooks: (() => Promise<void>)[] = [];\n\n isRendering: boolean = false;\n transformer: BlocksTransformer = new BlocksTransformer(() => this.assets);\n\n effects: Effect[] = [];\n\n constructor(\n component: JSX.ComponentFunction,\n event: BlockRenderRequest | UIEvent | undefined,\n state: PartialJSONObject | undefined,\n metadata: Metadata,\n dimensions: Dimensions | undefined\n ) {\n this.component = component;\n this.event = event;\n this.state = {\n __renderState: {},\n ...state,\n };\n this.metadata = metadata;\n if (this.state.__realtimeChannels) {\n this.realtimeChannels = this.state.__realtimeChannels;\n }\n\n const apiClients = makeAPIClients({\n reconciler: this,\n hooks: true,\n ui: true,\n metadata,\n });\n this.cache = apiClients.cache;\n this.modLog = apiClients.modLog;\n this.kvStore = apiClients.kvStore;\n this.redis = apiClients.redis;\n this.settings = apiClients.settings;\n this.scheduler = apiClients.scheduler;\n this.media = apiClients.media;\n this.assets = apiClients.assets;\n this.realtime = apiClients.realtime;\n this.dimensions = dimensions;\n this.ui = apiClients.ui;\n this.hooks = {\n useState: apiClients.useState,\n useInterval: apiClients.useInterval,\n useForm: apiClients.useForm,\n useChannel: apiClients.useChannel,\n };\n }\n\n #reset(): void {\n this.actions.clear();\n this.currentComponentKey = [];\n this.currentHookIndex = 0;\n this.pendingHooks = [];\n this.realtimeChannels = [];\n this.realtimeUpdated = false;\n }\n\n // This return type is an absolute mess here. Let this slide.\n\n #makeContextProps(): Devvit.Context {\n // skip typechecks for useForm which is templatized.\n const props: Omit<Devvit.Context, 'useForm'> = {\n ...getContextFromMetadata(this.metadata, this.state.__postData?.thingId),\n modLog: this.modLog,\n cache: this.cache,\n kvStore: this.kvStore,\n redis: this.redis,\n settings: this.settings,\n scheduler: this.scheduler,\n media: this.media,\n assets: this.assets,\n realtime: this.realtime,\n ui: this.ui,\n dimensions: this.dimensions,\n uiEnvironment: {\n timezone: this.metadata[Header.Timezone]?.values[0],\n locale: this.metadata[Header.Language]?.values[0],\n dimensions: this.dimensions,\n },\n ...this.hooks,\n };\n props.debug.effects = this;\n return props as Devvit.Context;\n }\n\n async render(): Promise<Block> {\n await this.reconcile();\n\n this.isRendering = true;\n const results = await this.buildBlocksUI();\n this.isRendering = false;\n\n return results;\n }\n\n makeUniqueActionID(id: string): string {\n let uniqueId = id;\n let counter = 1;\n while (this.actions.has(uniqueId)) {\n uniqueId = `${id}.${counter++}`;\n }\n return uniqueId;\n }\n\n async reconcile(): Promise<void> {\n const ctx = this.#makeContextProps();\n const blockElement: BlockElement = {\n type: this.component,\n props: ctx,\n children: [],\n };\n\n const reified = await this.processBlock(blockElement);\n assertNotString(reified);\n\n this.transformer.createBlocksElementOrThrow(reified);\n\n if (this.isUserActionRender && this.blockRenderEventId) {\n const handler = this.actions.get(this.blockRenderEventId);\n if (handler) {\n await handler((this.event as BlockRenderRequest).data);\n }\n }\n\n for (const hook of this.pendingHooks) {\n await hook();\n }\n\n this.buildNextState();\n this.#reset();\n }\n\n async buildBlocksUI(): Promise<Block> {\n const ctx = this.#makeContextProps();\n const rootBlockElement: BlockElement = {\n type: this.component,\n props: ctx,\n children: [],\n };\n\n const block = await this.renderElement(ctx, rootBlockElement);\n return this.transformer.ensureRootBlock(block);\n }\n\n async renderElement(ctx: Devvit.Context, element: JSX.Element): Promise<Block> {\n const reified = await this.processBlock(element);\n assertNotString(reified);\n\n if (Devvit.debug.emitSnapshots || ctx.debug.emitSnapshots)\n console.debug(indentXML(toXML(reified)));\n if (Devvit.debug.emitState || ctx.debug.emitState)\n console.debug(JSON.stringify(this.state, undefined, 2));\n\n return this.transformer.createBlocksElementOrThrow(reified);\n }\n\n #flatten(arr: ReifiedBlockElementOrLiteral[]): ReifiedBlockElementOrLiteral[] {\n const out: ReifiedBlockElementOrLiteral[] = [];\n for (const child of arr) {\n if (typeof child === 'string') {\n out.push(child);\n } else if (child.type === '__fragment') {\n out.push(...this.#flatten(child.children));\n } else {\n out.push(child);\n }\n }\n return out;\n }\n\n async processProps(block: BlockElement): Promise<void> {\n const props = block.props ?? {};\n const actionHandlers = ['onPress', 'onMessage'];\n for (const action of actionHandlers) {\n if (action in props) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n const handler = props[action] as Function;\n const name = handler?.name;\n const id = this.makeUniqueActionID(`${block.type}.${name ?? action}`);\n this.actions.set(id, handler);\n props[action] = id;\n }\n }\n }\n\n async processBlock(\n element: JSX.Element | JSX.Element[],\n idGenerator: (id: string) => string = makeUniqueIdGenerator(),\n path: string[] = []\n ): Promise<ReifiedBlockElementOrLiteral> {\n // Strings - return as is\n if (typeof element === 'string') {\n return element;\n }\n\n // Numbers - transform to string\n if (typeof element === 'number') {\n return `${element}`;\n }\n\n const blockElement = element as BlockElement;\n\n // Intrinsic elements\n if (typeof blockElement.type === 'string') {\n const childrens: ReifiedBlockElementOrLiteral[] = [];\n\n /**\n * Resist the urge to clean this up with flatMap. We need to await each processBlock _in order_.\n *\n * Number of times this has caused a major headache: 3\n *\n * Please increment the counter above if you had to revert this.\n */\n for (let i = 0; i < blockElement.children.length; i++) {\n const child = blockElement.children[i];\n if (child === undefined || child === null) continue;\n childrens.push(\n await this.processBlock(child, idGenerator, [\n ...path,\n `${blockElement.type as string}.${i}`,\n ])\n );\n }\n\n const children = childrens.flat();\n const collapsedChildren = this.#flatten(children);\n\n await this.processProps(blockElement);\n\n return {\n type: blockElement.type,\n props: blockElement.props,\n children: collapsedChildren,\n };\n }\n\n // Function components\n if (typeof blockElement.type === 'function') {\n // Create a unique component key to keep track of its state/hooks.\n const componentKey = idGenerator(\n `${path.length ? path.join('.') : 'root'}.${\n blockElement.type.name.length ? blockElement.type.name : 'anonymous'\n }`\n );\n this.currentComponentKey.push(componentKey);\n if (!this.renderState[componentKey]) {\n this.renderState[componentKey] = {};\n }\n\n const children = blockElement.children.flatMap((c) => c);\n\n const ctx = this.#makeContextProps();\n const props = {\n ...ctx,\n ...blockElement.props,\n children,\n };\n\n let result: JSX.Element | undefined;\n while (result === undefined) {\n try {\n result = await blockElement.type(props, ctx);\n } catch (promiseOrError) {\n // If the component throws a promise, wait for it to resolve and try again.\n // This is for components that have async logic in their hook callbacks.\n if (promiseOrError instanceof Promise) {\n result = await promiseOrError;\n } else {\n throw promiseOrError;\n }\n }\n }\n\n // Ensure that the number of hooks are same from the previous render.\n if (!this.isInitialRender) {\n const previousState = this.getPreviousComponentState();\n /**\n * Note: This relies on the magic of {0: \"a\", 1: \"b\"} => [\"a\", \"b\"] and back under many circumstances.\n * It is too magic, but as this component is complicated and deprecated, it is not worth fixing.\n *\n * In fact previousState is an array. This is confusing.\n */\n const prevHookCount = Object.keys(previousState).length;\n if (prevHookCount !== this.currentHookIndex) {\n throw new Error(\n 'Invalid hook call. Hooks can only be called at the top-level of a function component. Make sure that you are not calling hooks inside loops, conditions, or nested functions.'\n );\n }\n }\n\n // Remember to always reset the current component key and hook index.\n this.currentComponentKey.pop();\n this.currentHookIndex = 0;\n\n if (typeof result === 'object') {\n return this.processBlock(result, idGenerator, [...path, blockElement.type.name]);\n }\n }\n\n let children: JSX.Children[] = [];\n let pathPrefix = '';\n\n if (Array.isArray(blockElement)) {\n // Array of elements\n children = blockElement;\n } else if (typeof blockElement.type === 'undefined' && blockElement.children) {\n // Fragment\n children = blockElement.children;\n pathPrefix = 'fragmentChild.';\n }\n return {\n type: '__fragment',\n props: undefined,\n children: await Promise.all(\n children.flatMap(\n async (child, i) =>\n await this.processBlock(child, idGenerator, [...path, `${pathPrefix}${i}`])\n )\n ),\n };\n }\n\n getCurrentComponentKey(): string[] {\n if (!this.currentComponentKey.at(-1)) {\n throw new Error('Current component key is missing');\n }\n\n return this.currentComponentKey;\n }\n\n getCurrentComponentState<S>(): { [hookIndex: number]: S } {\n const componentKey = this.currentComponentKey.at(-1);\n if (!componentKey) {\n throw new Error('Current component key is missing');\n }\n return this.renderState[componentKey] as { [hookIndex: number]: S };\n }\n\n getPreviousComponentState<S>(): { [hookIndex: number]: S } {\n const componentKey = this.currentComponentKey.at(-1);\n if (!componentKey) {\n throw new Error('Current component key is missing');\n }\n\n if (!this.state.__renderState[componentKey]) {\n this.state.__renderState[componentKey] = {};\n }\n\n return this.state.__renderState[componentKey] as { [hookIndex: number]: S };\n }\n\n get isInitialRender(): boolean {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).type === BlockRenderEventType.RENDER_INITIAL;\n }\n\n get isUserActionRender(): boolean {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).type === BlockRenderEventType.RENDER_USER_ACTION;\n }\n\n get isEffectRender(): boolean {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).type === BlockRenderEventType.RENDER_EFFECT_EVENT;\n }\n\n get formSubmittedEvent(): FormSubmittedEvent | undefined | false {\n if (!this.event) {\n return false;\n }\n\n return (this.event as UIEvent).formSubmitted;\n }\n\n get blockRenderEventId(): string | undefined | false {\n if (!this.event) {\n return false;\n }\n\n return (this.event as BlockRenderRequest).id;\n }\n\n get realtimeEvent(): RealtimeSubscriptionEvent | undefined | false {\n if (!this.event) {\n return false;\n }\n\n const realtimeEvent = (this.event as UIEvent).realtimeEvent;\n if (!realtimeEvent?.event?.channel) {\n return false;\n }\n\n return realtimeEvent;\n }\n\n runHook(hook: () => Promise<void>): void {\n this.pendingHooks.push(hook);\n }\n\n #rerenderAlreadyScheduled = false;\n\n rerenderIn(delayMs: number): void {\n if (this.#rerenderAlreadyScheduled) {\n return;\n }\n this.#rerenderAlreadyScheduled = true;\n this.effects.push({\n type: EffectType.EFFECT_RERENDER_UI,\n rerenderUi: {\n delaySeconds: delayMs / 1000,\n },\n });\n }\n\n addRealtimeChannel(channel: string): void {\n this.realtimeChannels.push(channel);\n this.realtimeUpdated = true;\n }\n\n removeRealtimeChannel(channel: string): void {\n this.realtimeChannels = this.realtimeChannels.filter((c) => c !== channel);\n this.realtimeUpdated = true;\n }\n\n get realtimeEffect(): Effect[] {\n if (this.realtimeUpdated && this.realtimeChannels.length > 0) {\n return [\n {\n type: EffectType.EFFECT_REALTIME_SUB,\n realtimeSubscriptions: {\n subscriptionIds: this.realtimeChannels,\n },\n },\n ];\n } else {\n return [];\n }\n }\n\n getEffects(): Effect[] {\n return [...getEffectsFromUIClient(this.ui), ...this.effects, ...this.realtimeEffect];\n }\n\n buildNextState(): void {\n // Flatten the renderStates down to arrays to ensure `undefined` values are maintained through serialization.\n // Input:\n // {\"0\": \"one\", \"1\": undefined, \"2\": \"three\"} (length: 3)\n // Before:\n // {\"0\": \"one\", \"2\": \"three\"} (length: 2; fails consistency check, see line 251)\n // After:\n // [\"one\", undefined, \"three\"] (length: 3)\n /**\n * Note: This relies on the magic of {0: \"a\", 1: \"b\"} => [\"a\", \"b\"] and back under many circumstances.\n * It is too magic, but as this component is complicated and deprecated, it is not worth fixing.\n */\n for (const key of Object.keys(this.renderState)) {\n this.renderState[key] = Object.values(this.renderState[key]);\n }\n this.state = {\n __postData: this.state.__postData,\n __renderState: this.renderState,\n __cache: this.state.__cache,\n ...(this.realtimeChannels.length > 0 ? { __realtimeChannels: this.realtimeChannels } : {}),\n };\n }\n}\n", "import type { Effect } from '@devvit/protos';\n\nimport type { UIClient as UIClientType } from '../../../types/ui-client.js';\nimport type { UIClient } from '../UIClient.js';\n\n/** A simple helper to cast ui into the class instance to get the effects */\nexport function getEffectsFromUIClient(ui: UIClient | UIClientType): Effect[] {\n return (ui as UIClient).__effects;\n}\n", "export function makeUniqueIdGenerator(): (id: string) => string {\n const seenActionIds = new Set<string>();\n return (id: string) => {\n let uniqueId = id;\n let counter = 1;\n while (seenActionIds.has(uniqueId)) {\n uniqueId = `${id}.${counter++}`;\n }\n seenActionIds.add(uniqueId);\n return uniqueId;\n };\n}\n", "import type {\n Block,\n BlockAction,\n BlockAlignment,\n BlockBorder,\n BlockColor,\n BlockConfig,\n} from '@devvit/protos';\nimport {\n BlockActionType,\n BlockAvatarBackground,\n BlockAvatarFacing,\n BlockAvatarSize,\n BlockBorderWidth,\n BlockButtonAppearance,\n BlockButtonSize,\n BlockGap,\n BlockHorizontalAlignment,\n BlockIconSize,\n BlockImageResizeMode,\n BlockPadding,\n BlockRadius,\n BlockSpacerShape,\n BlockSpacerSize,\n BlockStackDirection,\n BlockTextOutline,\n BlockTextOverflow,\n BlockTextSize,\n BlockTextStyle,\n BlockTextWeight,\n BlockType,\n BlockVerticalAlignment,\n} from '@devvit/protos';\n\nimport type { AssetsClient, GetURLOptions } from '../../../apis/AssetsClient/AssetsClient.js';\nimport { Devvit } from '../../Devvit.js';\nimport {\n getHexFromNamedHTMLColor,\n getHexFromRgbaColor,\n getHexFromRPLColor,\n isHexColor,\n isHslColor,\n isNamedHTMLColor,\n isRgbaColor,\n isRPLColor,\n} from '../helpers/color.js';\nimport type { ReifiedBlockElement, ReifiedBlockElementOrLiteral } from './BlocksReconciler.js';\nimport type { TransformContext } from './transformContext.js';\nimport { makeStackDimensionsDetails, ROOT_STACK_TRANSFORM_CONTEXT } from './transformContext.js';\nimport { makeBlockSizes } from './transformerUtils.js';\n\ntype DataSet = Record<string, unknown>;\nconst DATA_PREFIX = 'data-';\n\nconst ACTION_HANDLERS: Set<Devvit.Blocks.ActionHandlers> = new Set(['onPress', 'onMessage']);\nconst ACTION_TYPES: Map<Devvit.Blocks.ActionHandlers, BlockActionType> = new Map([\n ['onPress', BlockActionType.ACTION_CLICK],\n ['onMessage', BlockActionType.ACTION_WEBVIEW],\n]);\n\nexport class BlocksTransformer {\n readonly #assetsClient: () => AssetsClient | undefined;\n\n constructor(getAssetsClient: () => AssetsClient | undefined = () => undefined) {\n this.#assetsClient = getAssetsClient;\n }\n\n createBlocksElementOrThrow({ type, props, children }: ReifiedBlockElement): Block {\n const block = this.createBlocksElement({ type, props, children }, ROOT_STACK_TRANSFORM_CONTEXT);\n if (!block) {\n throw new Error(`Could not create block of type ${type}`);\n }\n return block;\n }\n\n createBlocksElement(\n { type, props, children }: ReifiedBlockElement,\n transformContext: TransformContext\n ): Block | undefined {\n switch (type) {\n case 'blocks':\n return this.makeRoot(props, ...children);\n case 'hstack':\n return this.makeHStack(props, transformContext, ...children);\n case 'vstack':\n return this.makeVStack(props, transformContext, ...children);\n case 'zstack':\n return this.makeZStack(props, transformContext, ...children);\n case 'text':\n return this.makeText(props, transformContext, ...children);\n case 'button':\n return this.makeButton(props, transformContext, ...children);\n case 'image':\n return this.makeImage(props as Devvit.Blocks.ImageProps, transformContext);\n case 'spacer':\n return this.makeSpacer(props, transformContext);\n case 'icon':\n return this.makeIcon(props as Devvit.Blocks.IconProps, transformContext);\n case 'avatar':\n return this.makeAvatar(props as Devvit.Blocks.AvatarProps, transformContext);\n case 'webview':\n return this.makeWebView(props as Devvit.Blocks.WebViewProps, transformContext);\n case '__fragment':\n throw new Error(\"root fragment is not supported - use 'blocks' instead\");\n }\n return undefined;\n }\n\n makeRootHeight(height: Devvit.Blocks.RootHeight): number {\n switch (height) {\n case 'regular':\n return 320;\n case 'tall':\n return 512;\n }\n }\n\n makeBlockPadding(padding: Devvit.Blocks.ContainerPadding | undefined): BlockPadding | undefined {\n switch (padding) {\n case 'none':\n return BlockPadding.PADDING_NONE;\n case 'xsmall':\n return BlockPadding.PADDING_XSMALL;\n case 'small':\n return BlockPadding.PADDING_SMALL;\n case 'medium':\n return BlockPadding.PADDING_MEDIUM;\n case 'large':\n return BlockPadding.PADDING_LARGE;\n }\n return undefined;\n }\n\n makeBlockRadius(\n radius: Devvit.Blocks.ContainerCornerRadius | undefined\n ): BlockRadius | undefined {\n switch (radius) {\n case 'none':\n return BlockRadius.RADIUS_NONE;\n case 'small':\n return BlockRadius.RADIUS_SMALL;\n case 'medium':\n return BlockRadius.RADIUS_MEDIUM;\n case 'large':\n return BlockRadius.RADIUS_LARGE;\n case 'full':\n return BlockRadius.RADIUS_FULL;\n }\n return undefined;\n }\n\n makeBlockGap(gap: Devvit.Blocks.ContainerGap | undefined): BlockGap | undefined {\n switch (gap) {\n case 'none':\n return BlockGap.GAP_NONE;\n case 'small':\n return BlockGap.GAP_SMALL;\n case 'medium':\n return BlockGap.GAP_MEDIUM;\n case 'large':\n return BlockGap.GAP_LARGE;\n }\n return undefined;\n }\n\n makeBlockAlignment(alignment: Devvit.Blocks.Alignment | undefined): BlockAlignment | undefined {\n if (alignment === undefined) return undefined;\n let vertical: BlockVerticalAlignment | undefined = undefined;\n let horizontal: BlockHorizontalAlignment | undefined = undefined;\n if (alignment.includes('top')) {\n vertical = BlockVerticalAlignment.ALIGN_TOP;\n } else if (alignment.includes('middle')) {\n vertical = BlockVerticalAlignment.ALIGN_MIDDLE;\n } else if (alignment.includes('bottom')) {\n vertical = BlockVerticalAlignment.ALIGN_BOTTOM;\n }\n if (alignment.includes('start')) {\n horizontal = BlockHorizontalAlignment.ALIGN_START;\n } else if (alignment.includes('center')) {\n horizontal = BlockHorizontalAlignment.ALIGN_CENTER;\n } else if (alignment.includes('end')) {\n horizontal = BlockHorizontalAlignment.ALIGN_END;\n }\n if (vertical !== undefined || horizontal !== undefined) {\n return {\n vertical,\n horizontal,\n };\n }\n return undefined;\n }\n\n makeBlockBorder(\n borderWidth: Devvit.Blocks.ContainerBorderWidth | undefined,\n color: string | undefined,\n lightColor: string | undefined,\n darkColor: string | undefined\n ): BlockBorder | undefined {\n if (!borderWidth && !color) return undefined;\n\n let width: BlockBorderWidth | undefined = undefined;\n switch (borderWidth) {\n case 'none':\n width = BlockBorderWidth.BORDER_WIDTH_NONE;\n break;\n case 'thin':\n width = BlockBorderWidth.BORDER_WIDTH_THIN;\n break;\n case 'thick':\n width = BlockBorderWidth.BORDER_WIDTH_THICK;\n break;\n default:\n // Default to a thin border when a color was set, but no borderWidth.\n width = BlockBorderWidth.BORDER_WIDTH_THIN;\n break;\n }\n\n // Default to #00000019 when a border was set, but no color.\n const borderColor = color ?? 'neutral-border-weak';\n const colors = this.getThemedColors(borderColor, lightColor, darkColor);\n\n return {\n width,\n color: colors?.light,\n colors,\n };\n }\n\n makeBlockTextSize(textSize: Devvit.Blocks.TextSize | undefined): BlockTextSize | undefined {\n switch (textSize) {\n case 'xsmall':\n return BlockTextSize.TEXT_SIZE_XSMALL;\n case 'small':\n return BlockTextSize.TEXT_SIZE_SMALL;\n case 'medium':\n return BlockTextSize.TEXT_SIZE_MEDIUM;\n case 'large':\n return BlockTextSize.TEXT_SIZE_LARGE;\n case 'xlarge':\n return BlockTextSize.TEXT_SIZE_XLARGE;\n case 'xxlarge':\n return BlockTextSize.TEXT_SIZE_XXLARGE;\n }\n return undefined;\n }\n\n makeBlockTextStyle(style: Devvit.Blocks.TextStyle | undefined): BlockTextStyle | undefined {\n switch (style) {\n case 'body':\n return BlockTextStyle.TEXT_STYLE_BODY;\n case 'metadata':\n return BlockTextStyle.TEXT_STYLE_METADATA;\n case 'heading':\n return BlockTextStyle.TEXT_STYLE_HEADING;\n }\n return undefined;\n }\n\n makeBlockTextOutline(\n outline: Devvit.Blocks.TextOutline | undefined\n ): BlockTextOutline | undefined {\n switch (outline) {\n case 'none':\n return BlockTextOutline.TEXT_OUTLINE_NONE;\n case 'thin':\n return BlockTextOutline.TEXT_OUTLINE_THIN;\n case 'thick':\n return BlockTextOutline.TEXT_OUTLINE_THICK;\n }\n return undefined;\n }\n\n makeBlockTextWeight(weight: Devvit.Blocks.TextWeight | undefined): BlockTextWeight | undefined {\n switch (weight) {\n case 'regular':\n return BlockTextWeight.TEXT_WEIGHT_REGULAR;\n case 'bold':\n return BlockTextWeight.TEXT_WEIGHT_BOLD;\n }\n return undefined;\n }\n\n makeBlockTextOverflow(\n overflow: Devvit.Blocks.TextOverflow | undefined\n ): BlockTextOverflow | undefined {\n switch (overflow) {\n case 'clip':\n return BlockTextOverflow.TEXT_OVERFLOW_CLIP;\n case 'ellipsis':\n return BlockTextOverflow.TEXT_OVERFLOW_ELLIPSE;\n }\n return BlockTextOverflow.TEXT_OVERFLOW_ELLIPSE;\n }\n\n makeBlockButtonAppearance(\n appearance: Devvit.Blocks.ButtonAppearance | undefined\n ): BlockButtonAppearance | undefined {\n switch (appearance) {\n case 'secondary':\n return BlockButtonAppearance.BUTTON_APPEARANCE_SECONDARY;\n case 'primary':\n return BlockButtonAppearance.BUTTON_APPEARANCE_PRIMARY;\n case 'plain':\n return BlockButtonAppearance.BUTTON_APPEARANCE_PLAIN;\n case 'bordered':\n return BlockButtonAppearance.BUTTON_APPEARANCE_BORDERED;\n case 'media':\n return BlockButtonAppearance.BUTTON_APPEARANCE_MEDIA;\n case 'destructive':\n return BlockButtonAppearance.BUTTON_APPEARANCE_DESTRUCTIVE;\n case 'caution':\n return BlockButtonAppearance.BUTTON_APPEARANCE_CAUTION;\n case 'success':\n return BlockButtonAppearance.BUTTON_APPEARANCE_SUCCESS;\n }\n return undefined;\n }\n\n makeBlockButtonSize(size: Devvit.Blocks.ButtonSize | undefined): BlockButtonSize | undefined {\n switch (size) {\n case 'small':\n return BlockButtonSize.BUTTON_SIZE_SMALL;\n case 'medium':\n return BlockButtonSize.BUTTON_SIZE_MEDIUM;\n case 'large':\n return BlockButtonSize.BUTTON_SIZE_LARGE;\n }\n return undefined;\n }\n\n makeBlockImageResizeMode(\n resize: Devvit.Blocks.ImageResizeMode | undefined\n ): BlockImageResizeMode | undefined {\n switch (resize) {\n case 'none':\n return BlockImageResizeMode.IMAGE_RESIZE_NONE;\n case 'fit':\n return BlockImageResizeMode.IMAGE_RESIZE_FIT;\n case 'fill':\n return BlockImageResizeMode.IMAGE_RESIZE_FILL;\n case 'cover':\n return BlockImageResizeMode.IMAGE_RESIZE_COVER;\n case 'scale-down':\n return BlockImageResizeMode.IMAGE_RESIZE_SCALE_DOWN;\n }\n return undefined;\n }\n\n makeBlockSpacerSize(size: Devvit.Blocks.SpacerSize | undefined): BlockSpacerSize | undefined {\n switch (size) {\n case 'xsmall':\n return BlockSpacerSize.SPACER_XSMALL;\n case 'small':\n return BlockSpacerSize.SPACER_SMALL;\n case 'medium':\n return BlockSpacerSize.SPACER_MEDIUM;\n case 'large':\n return BlockSpacerSize.SPACER_LARGE;\n }\n return undefined;\n }\n\n makeBlockSpacerShape(size: Devvit.Blocks.SpacerShape | undefined): BlockSpacerShape | undefined {\n switch (size) {\n case 'invisible':\n return BlockSpacerShape.SPACER_INVISIBLE;\n case 'thin':\n return BlockSpacerShape.SPACER_THIN;\n case 'square':\n return BlockSpacerShape.SPACER_SQUARE;\n }\n return undefined;\n }\n\n makeBlockIconSize(size: Devvit.Blocks.IconSize | undefined): BlockIconSize | undefined {\n switch (size) {\n case 'xsmall':\n return BlockIconSize.ICON_SIZE_XSMALL;\n case 'small':\n return BlockIconSize.ICON_SIZE_SMALL;\n case 'medium':\n return BlockIconSize.ICON_SIZE_MEDIUM;\n case 'large':\n return BlockIconSize.ICON_SIZE_LARGE;\n }\n return undefined;\n }\n\n makeBlockAvatarSize(size: Devvit.Blocks.AvatarSize | undefined): BlockAvatarSize | undefined {\n switch (size) {\n case 'xxsmall':\n return BlockAvatarSize.AVATAR_SIZE_XXSMALL;\n case 'xsmall':\n return BlockAvatarSize.AVATAR_SIZE_XSMALL;\n case 'small':\n return BlockAvatarSize.AVATAR_SIZE_SMALL;\n case 'medium':\n return BlockAvatarSize.AVATAR_SIZE_MEDIUM;\n case 'large':\n return BlockAvatarSize.AVATAR_SIZE_LARGE;\n case 'xlarge':\n return BlockAvatarSize.AVATAR_SIZE_XXLARGE;\n case 'xxlarge':\n return BlockAvatarSize.AVATAR_SIZE_XXLARGE;\n case 'xxxlarge':\n return BlockAvatarSize.AVATAR_SIZE_XXXLARGE;\n }\n return undefined;\n }\n\n makeBlockAvatarFacing(\n facing: Devvit.Blocks.AvatarFacing | undefined\n ): BlockAvatarFacing | undefined {\n switch (facing) {\n case 'left':\n return BlockAvatarFacing.AVATAR_FACING_LEFT;\n case 'right':\n return BlockAvatarFacing.AVATAR_FACING_RIGHT;\n }\n return undefined;\n }\n\n makeBlockAvatarBackground(\n background: Devvit.Blocks.AvatarBackground | undefined\n ): BlockAvatarBackground | undefined {\n switch (background) {\n case 'dark':\n return BlockAvatarBackground.AVATAR_BG_DARK;\n case 'light':\n return BlockAvatarBackground.AVATAR_BG_LIGHT;\n }\n return undefined;\n }\n\n getDataSet(props: DataSet): DataSet {\n return Object.keys(props)\n .filter((key) => key.startsWith(DATA_PREFIX))\n .reduce((p, c) => {\n p[c.substring(DATA_PREFIX.length)] = props[c];\n return p;\n }, {} as DataSet);\n }\n\n makeActions(_type: BlockType, props: { [key: string]: unknown }): BlockAction[] {\n const actions: BlockAction[] = [];\n const dataSet = this.getDataSet(props as DataSet);\n ACTION_HANDLERS.forEach((action) => {\n if (action in props) {\n const id = props[action]!;\n actions.push({\n type: ACTION_TYPES.get(action) ?? BlockActionType.UNRECOGNIZED,\n id: id.toString(),\n data: dataSet,\n });\n }\n });\n return actions;\n }\n\n blockColorToHex(\n color: Devvit.Blocks.ColorString | undefined,\n theme: 'light' | 'dark' = 'light'\n ): Devvit.Blocks.ColorString | undefined {\n if (!color) return undefined;\n\n color = color.toLowerCase();\n if (isHexColor(color)) {\n return color;\n } else if (isRPLColor(color)) {\n return getHexFromRPLColor(color, theme);\n } else if (isNamedHTMLColor(color)) {\n return getHexFromNamedHTMLColor(color);\n } else if (isRgbaColor(color)) {\n return getHexFromRgbaColor(color);\n } else if (isHslColor(color)) {\n return color;\n }\n\n // Color could not be parsed, return red as fallback.\n console.warn(`Could not parse color: ${color}.`);\n return getHexFromNamedHTMLColor('red');\n }\n\n childrenToBlocks(\n children: ReifiedBlockElementOrLiteral[],\n transformContext: TransformContext\n ): Block[] {\n return children.flatMap(\n (child) =>\n (typeof child !== 'string'\n ? this.createBlocksElement(child, transformContext)\n : undefined) ?? []\n );\n }\n\n getThemedColors(\n color: Devvit.Blocks.ColorString | undefined,\n light?: Devvit.Blocks.ColorString | undefined,\n dark?: Devvit.Blocks.ColorString | undefined\n ): BlockColor | undefined {\n let lightColor = this.blockColorToHex(light, 'light');\n let darkColor = this.blockColorToHex(dark, 'dark');\n\n const tokens: string[] = [];\n\n // don't spend time parsing color if light/dark are already provided\n if (color && (!lightColor || !darkColor)) {\n // split color string, preserving color functions with spaces, such as rgb(r, g, b)\n // eslint-disable-next-line security/detect-unsafe-regex\n const matches = Array.from(color?.matchAll(/[\\w#-]+(?:\\([\\w\\t ,.#-]+\\))?/g) ?? []);\n tokens.push(...matches.map((group) => group[0]));\n }\n\n if (!lightColor) {\n lightColor = this.blockColorToHex(tokens?.at(0), 'light');\n }\n if (!darkColor) {\n // if only one color was provided, use it for both light and dark colors\n darkColor = this.blockColorToHex(tokens?.at(1) ?? tokens?.at(0), 'dark');\n }\n\n return lightColor || darkColor\n ? {\n light: lightColor,\n dark: darkColor,\n }\n : undefined;\n }\n\n parsePixels(input: Devvit.Blocks.SizePixels | number): number {\n if (typeof input === 'string') {\n return Number(input.slice(0, -2));\n }\n return input;\n }\n\n resolveAssetUrl(url: string, options?: GetURLOptions): string {\n // try and resolve the URL but allow the client to decide if unknown URLs are allowed\n return this.#assetsClient()?.getURL(url, options) ?? url;\n }\n\n childrenToString(children: ReifiedBlockElementOrLiteral[]): string {\n return children.map((c) => c.toString()).join('');\n }\n\n makeRoot(\n props: Devvit.Blocks.BaseProps | undefined,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.wrapRoot(props, this.childrenToBlocks(children, ROOT_STACK_TRANSFORM_CONTEXT));\n }\n\n wrapRoot(props: Devvit.Blocks.BaseProps | undefined, children: Block[]): Block {\n return this.makeBlock(\n BlockType.BLOCK_ROOT,\n {},\n {},\n {\n rootConfig: {\n children: children,\n height: this.makeRootHeight(\n Devvit.customPostType?.height ??\n (props as unknown as Devvit.Blocks.RootProps)?.height ??\n 'regular'\n ),\n },\n }\n );\n }\n\n makeStackBlock(\n direction: BlockStackDirection,\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n children: ReifiedBlockElementOrLiteral[]\n ): Block {\n const backgroundColors = this.getThemedColors(\n props?.backgroundColor,\n props?.lightBackgroundColor,\n props?.darkBackgroundColor\n );\n const alignment = this.makeBlockAlignment(props?.alignment);\n const blockSizes = makeBlockSizes(props, transformContext);\n\n const blockDimensionsDetails = makeStackDimensionsDetails(\n props,\n transformContext.stackParentLayout,\n blockSizes\n );\n\n return this.makeBlock(BlockType.BLOCK_STACK, props, transformContext, {\n stackConfig: {\n alignment,\n backgroundColor: backgroundColors?.light,\n backgroundColors,\n border: this.makeBlockBorder(\n props?.border,\n props?.borderColor,\n props?.lightBorderColor,\n props?.darkBorderColor\n ),\n children: this.childrenToBlocks(children, {\n stackParentLayout: { ...blockDimensionsDetails, direction, alignment },\n }),\n cornerRadius: this.makeBlockRadius(props?.cornerRadius),\n direction: direction,\n gap: this.makeBlockGap(props?.gap),\n padding: this.makeBlockPadding(props?.padding),\n reverse: props?.reverse,\n },\n });\n }\n\n makeHStack(\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.makeStackBlock(\n BlockStackDirection.STACK_HORIZONTAL,\n props,\n transformContext,\n children\n );\n }\n\n makeVStack(\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.makeStackBlock(\n BlockStackDirection.STACK_VERTICAL,\n props,\n transformContext,\n children\n );\n }\n\n makeZStack(\n props: Devvit.Blocks.StackProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n return this.makeStackBlock(BlockStackDirection.STACK_DEPTH, props, transformContext, children);\n }\n\n makeText(\n props: Devvit.Blocks.TextProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n const colors = this.getThemedColors(props?.color, props?.lightColor, props?.darkColor);\n return this.makeBlock(BlockType.BLOCK_TEXT, props, transformContext, {\n textConfig: {\n alignment: this.makeBlockAlignment(props?.alignment),\n color: colors?.light,\n colors,\n outline: this.makeBlockTextOutline(props?.outline),\n size: this.makeBlockTextSize(props?.size),\n style: this.makeBlockTextStyle(props?.style),\n text: this.childrenToString(children),\n weight: this.makeBlockTextWeight(props?.weight),\n selectable: props?.selectable,\n wrap: props?.wrap,\n overflow: this.makeBlockTextOverflow(props?.overflow),\n },\n });\n }\n\n makeButton(\n props: Devvit.Blocks.ButtonProps | undefined,\n transformContext: TransformContext,\n ...children: ReifiedBlockElementOrLiteral[]\n ): Block {\n const textColors = this.getThemedColors(\n props?.textColor,\n props?.lightTextColor,\n props?.darkTextColor\n );\n return this.makeBlock(BlockType.BLOCK_BUTTON, props, transformContext, {\n buttonConfig: {\n buttonAppearance: this.makeBlockButtonAppearance(props?.appearance),\n // not available in all platforms yet\n // backgroundColor: props?.backgroundColor,\n icon: props?.icon,\n buttonSize: this.makeBlockButtonSize(props?.size),\n text: this.childrenToString(children),\n textColor: textColors?.light,\n textColors,\n disabled: props?.disabled,\n },\n });\n }\n\n makeImage(\n props: Devvit.Blocks.ImageProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_IMAGE, props, transformContext, {\n imageConfig: {\n description: props?.description,\n resizeMode: this.makeBlockImageResizeMode(props.resizeMode),\n url: this.resolveAssetUrl(props.url),\n width: this.parsePixels(props.imageWidth),\n height: this.parsePixels(props.imageHeight),\n },\n })\n );\n }\n\n makeSpacer(\n props: Devvit.Blocks.SpacerProps | undefined,\n transformContext: TransformContext\n ): Block {\n return this.makeBlock(BlockType.BLOCK_SPACER, props, transformContext, {\n spacerConfig: {\n size: this.makeBlockSpacerSize(props?.size),\n shape: this.makeBlockSpacerShape(props?.shape),\n },\n });\n }\n\n makeIcon(\n props: Devvit.Blocks.IconProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n const colors = this.getThemedColors(props?.color, props?.lightColor, props?.darkColor);\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_ICON, props, transformContext, {\n iconConfig: {\n icon: props.name,\n color: colors?.light,\n colors,\n size: this.makeBlockIconSize(props.size),\n },\n })\n );\n }\n\n makeAvatar(\n props: Devvit.Blocks.AvatarProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_AVATAR, props, transformContext, {\n avatarConfig: {\n thingId: props.thingId,\n size: this.makeBlockAvatarSize(props.size),\n facing: this.makeBlockAvatarFacing(props.facing),\n background: this.makeBlockAvatarBackground(props.background),\n },\n })\n );\n }\n\n makeWebView(\n props: Devvit.Blocks.WebViewProps | undefined,\n transformContext: TransformContext\n ): Block | undefined {\n return (\n props &&\n this.makeBlock(BlockType.BLOCK_WEBVIEW, props, transformContext, {\n webviewConfig: {\n url: this.resolveAssetUrl(props.url, { webView: true }),\n },\n })\n );\n }\n\n makeBlock(\n type: BlockType,\n props: Devvit.Blocks.BaseProps | undefined,\n transformContext: TransformContext,\n config?: BlockConfig | undefined\n ): Block {\n return {\n type,\n sizes: makeBlockSizes(props, transformContext),\n config: config,\n actions: (props && this.makeActions(type, props)) ?? [],\n id: props?.id,\n key: props?.key,\n };\n }\n\n ensureRootBlock(block: Block): Block {\n let root: Block;\n\n if (block.type === BlockType.BLOCK_ROOT) {\n if (block.config?.rootConfig && Devvit.customPostType?.height) {\n block.config.rootConfig.height = this.makeRootHeight(Devvit.customPostType?.height);\n }\n root = block;\n } else {\n root = this.wrapRoot(undefined, [block]);\n }\n\n if (!root) {\n throw new Error('Could not create root block');\n }\n\n return root;\n }\n}\n", "export const semanticColors = {\n \"alienblue-100\": \"#DCE2FB\",\n \"alienblue-200\": \"#B8C5FC\",\n \"alienblue-25\": \"#F7F8FD\",\n \"alienblue-300\": \"#90A9FD\",\n \"alienblue-400\": \"#648EFC\",\n \"alienblue-50\": \"#EEF0FB\",\n \"alienblue-500\": \"#1870F4\",\n \"alienblue-600\": \"#115BCA\",\n \"alienblue-700\": \"#0A449B\",\n \"alienblue-75\": \"#E6E9FB\",\n \"alienblue-800\": \"#0A2F6C\",\n \"alienblue-900\": \"#0A1A3F\",\n \"alienblue-950\": \"#07102B\",\n \"berrypurple-100\": \"#F3DAFB\",\n \"berrypurple-200\": \"#EAB3FD\",\n \"berrypurple-25\": \"#FBF6FD\",\n \"berrypurple-300\": \"#DE8BFF\",\n \"berrypurple-400\": \"#CF5FFF\",\n \"berrypurple-50\": \"#F8EDFC\",\n \"berrypurple-500\": \"#BC0EFF\",\n \"berrypurple-600\": \"#9B00D4\",\n \"berrypurple-700\": \"#7600A3\",\n \"berrypurple-75\": \"#F6E4FB\",\n \"berrypurple-800\": \"#520472\",\n \"berrypurple-900\": \"#300643\",\n \"berrypurple-950\": \"#20042A\",\n \"black\": \"#000000\",\n \"black-opacity-0\": \"#00000000\",\n \"black-opacity-10\": \"#00000019\",\n \"black-opacity-15\": \"#00000026\",\n \"black-opacity-20\": \"#00000033\",\n \"black-opacity-25\": \"#0000003F\",\n \"black-opacity-33\": \"#00000054\",\n \"black-opacity-5\": \"#0000000C\",\n \"black-opacity-50\": \"#0000007F\",\n \"black-opacity-60\": \"#00000099\",\n \"black-opacity-67\": \"#000000AA\",\n \"black-opacity-80\": \"#000000CC\",\n \"black-opacity-90\": \"#000000E5\",\n \"brown-100\": \"#F1DFCF\",\n \"brown-200\": \"#E9BE95\",\n \"brown-25\": \"#FBF7F4\",\n \"brown-300\": \"#DC9D5D\",\n \"brown-400\": \"#BA854E\",\n \"brown-50\": \"#F7EFE9\",\n \"brown-500\": \"#9A6D3F\",\n \"brown-600\": \"#7B5631\",\n \"brown-700\": \"#5D4023\",\n \"brown-75\": \"#F4E7DC\",\n \"brown-800\": \"#3F2C19\",\n \"brown-900\": \"#221A11\",\n \"brown-950\": \"#15100A\",\n \"coolgray-100\": \"#DBE4E9\",\n \"coolgray-150\": \"#C9D7DE\",\n \"coolgray-200\": \"#B7CAD4\",\n \"coolgray-25\": \"#F6F8F9\",\n \"coolgray-250\": \"#A4BDCA\",\n \"coolgray-300\": \"#97AFBC\",\n \"coolgray-350\": \"#8BA2AD\",\n \"coolgray-400\": \"#7F949F\",\n \"coolgray-450\": \"#748791\",\n \"coolgray-50\": \"#EEF1F3\",\n \"coolgray-500\": \"#667780\",\n \"coolgray-525\": \"#576F76\",\n \"coolgray-550\": \"#5C6C74\",\n \"coolgray-600\": \"#536168\",\n \"coolgray-650\": \"#48545B\",\n \"coolgray-700\": \"#3D494E\",\n \"coolgray-75\": \"#E5EBEE\",\n \"coolgray-750\": \"#333D42\",\n \"coolgray-800\": \"#2A3236\",\n \"coolgray-850\": \"#21272A\",\n \"coolgray-900\": \"#181C1F\",\n \"coolgray-950\": \"#0E1113\",\n \"global-black\": {\n \"light\": \"#000000\",\n \"dark\": \"#000000\"\n },\n \"global-brand-orangered\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"global-white\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"kiwigreen-100\": \"#A8F5A0\",\n \"kiwigreen-200\": \"#58E15B\",\n \"kiwigreen-25\": \"#EBFDE7\",\n \"kiwigreen-300\": \"#00C61C\",\n \"kiwigreen-400\": \"#01A816\",\n \"kiwigreen-50\": \"#DDF8D7\",\n \"kiwigreen-500\": \"#008A10\",\n \"kiwigreen-600\": \"#016E0B\",\n \"kiwigreen-700\": \"#005306\",\n \"kiwigreen-75\": \"#CAF5C2\",\n \"kiwigreen-800\": \"#033902\",\n \"kiwigreen-900\": \"#0D2005\",\n \"kiwigreen-950\": \"#081404\",\n \"lightblue-100\": \"#CAE7FB\",\n \"lightblue-200\": \"#87D0FD\",\n \"lightblue-25\": \"#F3F9FD\",\n \"lightblue-300\": \"#02B9FB\",\n \"lightblue-400\": \"#029DD5\",\n \"lightblue-50\": \"#E6F3FC\",\n \"lightblue-500\": \"#007FAE\",\n \"lightblue-600\": \"#01668D\",\n \"lightblue-700\": \"#014D6B\",\n \"lightblue-75\": \"#D9EDFB\",\n \"lightblue-800\": \"#03354B\",\n \"lightblue-900\": \"#051E2B\",\n \"lightblue-950\": \"#04131A\",\n \"lime-100\": \"#B7F28E\",\n \"lime-200\": \"#90DA58\",\n \"lime-25\": \"#EEFDDC\",\n \"lime-300\": \"#6DBF01\",\n \"lime-400\": \"#5BA200\",\n \"lime-50\": \"#E0F8C8\",\n \"lime-500\": \"#4A8500\",\n \"lime-600\": \"#3A6A00\",\n \"lime-700\": \"#2A5000\",\n \"lime-75\": \"#CEF5AD\",\n \"lime-800\": \"#1E3702\",\n \"lime-900\": \"#151E05\",\n \"lime-950\": \"#0D1304\",\n \"mintgreen-100\": \"#9BF5D9\",\n \"mintgreen-200\": \"#00E2B7\",\n \"mintgreen-25\": \"#E7FDF5\",\n \"mintgreen-300\": \"#00C29D\",\n \"mintgreen-400\": \"#01A484\",\n \"mintgreen-50\": \"#D7F8EC\",\n \"mintgreen-500\": \"#01876D\",\n \"mintgreen-600\": \"#006C56\",\n \"mintgreen-700\": \"#015140\",\n \"mintgreen-75\": \"#C0F5E3\",\n \"mintgreen-800\": \"#00382B\",\n \"mintgreen-900\": \"#032019\",\n \"mintgreen-950\": \"#03140F\",\n \"orangered-100\": \"#FCDBCF\",\n \"orangered-200\": \"#FDB498\",\n \"orangered-25\": \"#FDF6F4\",\n \"orangered-300\": \"#FF895D\",\n \"orangered-400\": \"#FF4500\",\n \"orangered-50\": \"#FCEEE8\",\n \"orangered-500\": \"#D93900\",\n \"orangered-600\": \"#AE2C00\",\n \"orangered-700\": \"#842100\",\n \"orangered-75\": \"#FBE5DC\",\n \"orangered-800\": \"#591B02\",\n \"orangered-900\": \"#2F1405\",\n \"orangered-950\": \"#1C0D04\",\n \"periwinkle-100\": \"#E6DFFB\",\n \"periwinkle-200\": \"#CDBEFD\",\n \"periwinkle-25\": \"#F9F7FD\",\n \"periwinkle-300\": \"#B29FFF\",\n \"periwinkle-400\": \"#9580FF\",\n \"periwinkle-50\": \"#F2EFFC\",\n \"periwinkle-500\": \"#6A5CFF\",\n \"periwinkle-600\": \"#523DFF\",\n \"periwinkle-700\": \"#4001EA\",\n \"periwinkle-75\": \"#ECE7FB\",\n \"periwinkle-800\": \"#250AA6\",\n \"periwinkle-900\": \"#160E5B\",\n \"periwinkle-950\": \"#0C083F\",\n \"poopbrown-100\": \"#FEEEDD\",\n \"poopbrown-200\": \"#F6DDC3\",\n \"poopbrown-300\": \"#EECCAA\",\n \"poopbrown-400\": \"#CAA075\",\n \"poopbrown-50\": \"#FEF7EE\",\n \"poopbrown-500\": \"#9A6D3F\",\n \"poopbrown-600\": \"#6E4924\",\n \"poopbrown-700\": \"#54371A\",\n \"poopbrown-800\": \"#3B2510\",\n \"poopbrown-900\": \"#29190A\",\n \"poopbrown-950\": \"#110B04\",\n \"puregray-100\": \"#F2F2F2\",\n \"puregray-150\": \"#EBEBEB\",\n \"puregray-200\": \"#E4E4E4\",\n \"puregray-250\": \"#DDDDDD\",\n \"puregray-300\": \"#D6D6D6\",\n \"puregray-350\": \"#C3C3C3\",\n \"puregray-400\": \"#ACACAC\",\n \"puregray-450\": \"#919191\",\n \"puregray-50\": \"#F8F8F8\",\n \"puregray-500\": \"#767676\",\n \"puregray-550\": \"#5C5C5C\",\n \"puregray-600\": \"#434343\",\n \"puregray-650\": \"#393939\",\n \"puregray-700\": \"#303030\",\n \"puregray-750\": \"#272727\",\n \"puregray-800\": \"#1E1E1E\",\n \"puregray-850\": \"#181818\",\n \"puregray-900\": \"#131313\",\n \"puregray-950\": \"#080808\",\n \"red-100\": \"#FBDBD4\",\n \"red-200\": \"#FDB3A4\",\n \"red-25\": \"#FDF6F5\",\n \"red-300\": \"#FF8773\",\n \"red-400\": \"#FF4F40\",\n \"red-50\": \"#FCEEEA\",\n \"red-500\": \"#EB001F\",\n \"red-600\": \"#BC0117\",\n \"red-700\": \"#90000F\",\n \"red-75\": \"#FBE4DF\",\n \"red-800\": \"#650405\",\n \"red-900\": \"#340F05\",\n \"red-950\": \"#1F0B04\",\n \"sakurapink-100\": \"#FBD9EB\",\n \"sakurapink-200\": \"#FDAEDA\",\n \"sakurapink-25\": \"#FDF6F9\",\n \"sakurapink-300\": \"#FF7ECA\",\n \"sakurapink-400\": \"#FF38BB\",\n \"sakurapink-50\": \"#FCEDF4\",\n \"sakurapink-500\": \"#DE019F\",\n \"sakurapink-600\": \"#B2007F\",\n \"sakurapink-700\": \"#880060\",\n \"sakurapink-75\": \"#FBE3EF\",\n \"sakurapink-800\": \"#5F0443\",\n \"sakurapink-900\": \"#380626\",\n \"sakurapink-950\": \"#250419\",\n \"transparent\": \"transparent\",\n \"white\": \"#ffffff\",\n \"white-opacity-0\": \"#FFFFFF00\",\n \"white-opacity-10\": \"#FFFFFF19\",\n \"white-opacity-15\": \"#FFFFFF26\",\n \"white-opacity-20\": \"#FFFFFF33\",\n \"white-opacity-25\": \"#FFFFFF3F\",\n \"white-opacity-5\": \"#FFFFFF0C\",\n \"white-opacity-50\": \"#FFFFFF7F\",\n \"yellow-100\": \"#FFE284\",\n \"yellow-200\": \"#FFBF0B\",\n \"yellow-25\": \"#FFF9E0\",\n \"yellow-300\": \"#D8A100\",\n \"yellow-400\": \"#B78800\",\n \"yellow-50\": \"#FFF3C0\",\n \"yellow-500\": \"#977000\",\n \"yellow-600\": \"#785800\",\n \"yellow-700\": \"#5B4200\",\n \"yellow-75\": \"#FFEAA2\",\n \"yellow-800\": \"#3F2D00\",\n \"yellow-900\": \"#231A03\",\n \"yellow-950\": \"#161003\",\n \"yelloworange-100\": \"#FCDCC8\",\n \"yelloworange-200\": \"#FDB586\",\n \"yelloworange-25\": \"#FDF7F3\",\n \"yelloworange-300\": \"#FF8A35\",\n \"yelloworange-400\": \"#E46C00\",\n \"yelloworange-50\": \"#FCEEE6\",\n \"yelloworange-500\": \"#BD5800\",\n \"yelloworange-600\": \"#974500\",\n \"yelloworange-700\": \"#733300\",\n \"yelloworange-75\": \"#FBE5D7\",\n \"yelloworange-800\": \"#4E2402\",\n \"yelloworange-900\": \"#2A1705\",\n \"yelloworange-950\": \"#1A0E04\",\n \"action-downvote\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"action-upvote\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"ai-background-weaker\": {\n \"light\": \"#E7FDF5\",\n \"dark\": \"#00382B\"\n },\n \"ai-plain\": {\n \"light\": \"#006C56\",\n \"dark\": \"#01A484\"\n },\n \"ai-plain-hover\": {\n \"light\": \"#015140\",\n \"dark\": \"#00C29D\"\n },\n \"alert-caution\": {\n \"light\": \"#977000\",\n \"dark\": \"#977000\"\n },\n \"avatar-gradient\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-background\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"brand-background-hover\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#AE2C00\"\n },\n \"brand-gradient-active\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-active-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-default\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-default-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-hover\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-gradient-hover-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"brand-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"category-live\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"category-nsfw\": {\n \"light\": \"#DE019F\",\n \"dark\": \"#DE019F\"\n },\n \"category-spoiler\": {\n \"light\": \"#131313\",\n \"dark\": \"#F2F2F2\"\n },\n \"caution-background\": {\n \"light\": \"#FFBF0B\",\n \"dark\": \"#D8A100\"\n },\n \"caution-background-hover\": {\n \"light\": \"#D8A100\",\n \"dark\": \"#FFBF0B\"\n },\n \"caution-onBackground\": {\n \"light\": \"#000000\",\n \"dark\": \"#000000\"\n },\n \"caution-plain\": {\n \"light\": \"#785800\",\n \"dark\": \"#FFBF0B\"\n },\n \"caution-plain-hover\": {\n \"light\": \"#5B4200\",\n \"dark\": \"#FFE284\"\n },\n \"danger-background\": {\n \"light\": \"#EB001F\",\n \"dark\": \"#BC0117\"\n },\n \"danger-background-disabled\": {\n \"light\": \"#FDB3A4\",\n \"dark\": \"#340F05\"\n },\n \"danger-background-hover\": {\n \"light\": \"#BC0117\",\n \"dark\": \"#EB001F\"\n },\n \"danger-background-weaker\": {\n \"light\": \"#FBDBD4\",\n \"dark\": \"#650405\"\n },\n \"danger-content\": {\n \"light\": \"#BC0117\",\n \"dark\": \"#FF4F40\"\n },\n \"danger-content-default\": {\n \"light\": \"#ffffff\",\n \"dark\": \"#ffffff\"\n },\n \"danger-content-hover\": {\n \"light\": \"#90000F\",\n \"dark\": \"#FF8773\"\n },\n \"danger-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"danger-plain\": {\n \"light\": \"#BC0117\",\n \"dark\": \"#FF8773\"\n },\n \"danger-plain-hover\": {\n \"light\": \"#90000F\",\n \"dark\": \"#FDB3A4\"\n },\n \"downvote-background\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"downvote-background-disabled\": {\n \"light\": \"#6a5cff4d\",\n \"dark\": \"#6a5cff4d\"\n },\n \"downvote-background-hover\": {\n \"light\": \"#523DFF\",\n \"dark\": \"#523DFF\"\n },\n \"downvote-content\": {\n \"light\": \"#523DFF\",\n \"dark\": \"#9580FF\"\n },\n \"downvote-content-weak\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"downvote-disabled\": {\n \"light\": \"#523DFF4c\",\n \"dark\": \"#9580FF4c\"\n },\n \"downvote-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"downvote-onStrongScrim\": {\n \"light\": \"#B29FFF\",\n \"dark\": \"#B29FFF\"\n },\n \"downvote-onStrongScrim-disabled\": {\n \"light\": \"#b29fff4d\",\n \"dark\": \"#b29fff4d\"\n },\n \"downvote-onStrongScrim-weaker\": {\n \"light\": \"#9580FF\",\n \"dark\": \"#9580FF\"\n },\n \"downvote-plain\": {\n \"light\": \"#523DFF\",\n \"dark\": \"#9580FF\"\n },\n \"downvote-plain-disabled\": {\n \"light\": \"#523dff4d\",\n \"dark\": \"#9580ff4d\"\n },\n \"downvote-plain-weaker\": {\n \"light\": \"#6A5CFF\",\n \"dark\": \"#6A5CFF\"\n },\n \"elevation-large1\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#0000007F\"\n },\n \"elevation-large2\": {\n \"light\": \"#00000026\",\n \"dark\": \"#00000033\"\n },\n \"elevation-medium1\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#0000007F\"\n },\n \"elevation-medium2\": {\n \"light\": \"#00000019\",\n \"dark\": \"#00000033\"\n },\n \"elevation-small\": {\n \"light\": \"#00000026\",\n \"dark\": \"#00000054\"\n },\n \"elevation-xsmall\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#000000AA\"\n },\n \"favorite\": {\n \"light\": \"#977000\",\n \"dark\": \"#B78800\"\n },\n \"global-admin\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"global-alienblue\": {\n \"light\": \"#1870F4\",\n \"dark\": \"#1870F4\"\n },\n \"global-darkblue\": {\n \"light\": \"#2A3236\",\n \"dark\": \"#2A3236\"\n },\n \"global-focus\": {\n \"light\": \"#029DD5\",\n \"dark\": \"#007FAE\"\n },\n \"global-gold\": {\n \"light\": \"#B78800\",\n \"dark\": \"#D8A100\"\n },\n \"global-moderator\": {\n \"light\": \"#008A10\",\n \"dark\": \"#008A10\"\n },\n \"global-nsfw\": {\n \"light\": \"#DE019F\",\n \"dark\": \"#DE019F\"\n },\n \"global-offline\": {\n \"light\": \"#667780\",\n \"dark\": \"#667780\"\n },\n \"global-online\": {\n \"light\": \"#00C61C\",\n \"dark\": \"#00C61C\"\n },\n \"global-orangered\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"global-stars\": {\n \"light\": \"#977000\",\n \"dark\": \"#D8A100\"\n },\n \"gradient-media\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"gradient-media-strong\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"identity-admin\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"identity-coins\": {\n \"light\": \"#B78800\",\n \"dark\": \"#B78800\"\n },\n \"identity-moderator\": {\n \"light\": \"#008A10\",\n \"dark\": \"#008A10\"\n },\n \"identity-self\": {\n \"light\": \"#01876D\",\n \"dark\": \"#01876D\"\n },\n \"interactive-background-disabled\": {\n \"light\": \"#0000000C\",\n \"dark\": \"#FFFFFF0C\"\n },\n \"interactive-content-disabled\": {\n \"light\": \"#0000003F\",\n \"dark\": \"#FFFFFF3F\"\n },\n \"interactive-focused\": {\n \"light\": \"#007FAE\",\n \"dark\": \"#007FAE\"\n },\n \"interactive-pressed\": {\n \"light\": \"#00000026\",\n \"dark\": \"#FFFFFF26\"\n },\n \"inverted-interactive-background-disabled\": {\n \"light\": \"#FFFFFF0C\",\n \"dark\": \"#0000000C\"\n },\n \"inverted-interactive-content-disabled\": {\n \"light\": \"#FFFFFF3F\",\n \"dark\": \"#0000003F\"\n },\n \"inverted-interactive-pressed\": {\n \"light\": \"#FFFFFF26\",\n \"dark\": \"#00000026\"\n },\n \"inverted-neutral-background\": {\n \"light\": \"#0E1113\",\n \"dark\": \"#FFFFFF\"\n },\n \"inverted-neutral-background-hover\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#F6F8F9\"\n },\n \"inverted-neutral-border\": {\n \"light\": \"#FFFFFF33\",\n \"dark\": \"#00000033\"\n },\n \"inverted-neutral-content\": {\n \"light\": \"#B7CAD4\",\n \"dark\": \"#333D42\"\n },\n \"inverted-neutral-content-strong\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#181C1F\"\n },\n \"inverted-secondary-background\": {\n \"light\": \"#2A3236\",\n \"dark\": \"#E5EBEE\"\n },\n \"inverted-secondary-background-hover\": {\n \"light\": \"#333D42\",\n \"dark\": \"#DBE4E9\"\n },\n \"inverted-secondary-background-selected\": {\n \"light\": \"#3D494E\",\n \"dark\": \"#C9D7DE\"\n },\n \"inverted-secondary-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#000000\"\n },\n \"inverted-secondary-plain\": {\n \"light\": \"#DBE4E9\",\n \"dark\": \"#181C1F\"\n },\n \"inverted-secondary-plain-hover\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#000000\"\n },\n \"media-background\": {\n \"light\": \"#00000099\",\n \"dark\": \"#00000099\"\n },\n \"media-background-hover\": {\n \"light\": \"#000000cc\",\n \"dark\": \"#000000cc\"\n },\n \"media-background-selected\": {\n \"light\": \"#000000cc\",\n \"dark\": \"#000000cc\"\n },\n \"media-border-selected\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"media-border-weak\": {\n \"light\": \"#FFFFFF19\",\n \"dark\": \"#FFFFFF19\"\n },\n \"media-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"media-onBackground-disabled\": {\n \"light\": \"#FFFFFF3F\",\n \"dark\": \"#FFFFFF3F\"\n },\n \"media-onBackground-weak\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#E5EBEE\"\n },\n \"media-onbackground\": {\n \"light\": \"#ffffff\",\n \"dark\": \"#ffffff\"\n },\n \"media-onbackground-disabled\": {\n \"light\": \"#ffffff3f\",\n \"dark\": \"#ffffff3f\"\n },\n \"media-onbackground-weak\": {\n \"light\": \"#B7CAD4\",\n \"dark\": \"#B7CAD4\"\n },\n \"neutral-background\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#0E1113\"\n },\n \"neutral-background-container\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-container-hover\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-container-strong\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-container-strong-hover\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#2A3236\"\n },\n \"neutral-background-gilded\": {\n \"light\": \"#FFF9E0\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-gilded-hover\": {\n \"light\": \"#FFF3C0\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-hover\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-medium\": {\n \"light\": \"#F8F8F8\",\n \"dark\": \"#131313\"\n },\n \"neutral-background-pinned\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#0E1113\"\n },\n \"neutral-background-selected\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#2A3236\"\n },\n \"neutral-background-strong\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#181C1F\"\n },\n \"neutral-background-strong-hover\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#21272A\"\n },\n \"neutral-background-weak\": {\n \"light\": \"#F6F8F9\",\n \"dark\": \"#000000\"\n },\n \"neutral-background-weak-hover\": {\n \"light\": \"#EEF1F3\",\n \"dark\": \"#0E1113\"\n },\n \"neutral-border\": {\n \"light\": \"#00000033\",\n \"dark\": \"#FFFFFF33\"\n },\n \"neutral-border-medium\": {\n \"light\": \"#0000007F\",\n \"dark\": \"#FFFFFF7F\"\n },\n \"neutral-border-strong\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#F6F8F9\"\n },\n \"neutral-border-weak\": {\n \"light\": \"#00000019\",\n \"dark\": \"#FFFFFF19\"\n },\n \"neutral-content\": {\n \"light\": \"#333D42\",\n \"dark\": \"#B7CAD4\"\n },\n \"neutral-content-disabled\": {\n \"light\": \"#D6D6D6\",\n \"dark\": \"#303030\"\n },\n \"neutral-content-strong\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#EEF1F3\"\n },\n \"neutral-content-weak\": {\n \"light\": \"#5C6C74\",\n \"dark\": \"#8BA2AD\"\n },\n \"offline\": {\n \"light\": \"#767676\",\n \"dark\": \"#767676\"\n },\n \"online\": {\n \"light\": \"#01A816\",\n \"dark\": \"#01A816\"\n },\n \"opacity-08\": {\n \"light\": \"#13131314\",\n \"dark\": \"#13131314\"\n },\n \"opacity-50\": {\n \"light\": \"#0000007f\",\n \"dark\": \"#F2F2F27f\"\n },\n \"opacity-highlight\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"pizzaRed\": {\n \"light\": \"#ef5350\",\n \"dark\": \"#c62828\"\n },\n \"primary\": {\n \"light\": \"#115BCA\",\n \"dark\": \"#648EFC\"\n },\n \"primary-background\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#115BCA\"\n },\n \"primary-background-hover\": {\n \"light\": \"#0A2F6C\",\n \"dark\": \"#1870F4\"\n },\n \"primary-background-selected\": {\n \"light\": \"#0A1A3F\",\n \"dark\": \"#648EFC\"\n },\n \"primary-border\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#648EFC\"\n },\n \"primary-border-hover\": {\n \"light\": \"#0A2F6C\",\n \"dark\": \"#90A9FD\"\n },\n \"primary-hover\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#90A9FD\"\n },\n \"primary-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"primary-onBackground-selected\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#000000\"\n },\n \"primary-plain\": {\n \"light\": \"#0A449B\",\n \"dark\": \"#648EFC\"\n },\n \"primary-plain-hover\": {\n \"light\": \"#0A2F6C\",\n \"dark\": \"#90A9FD\"\n },\n \"primary-plain-visited\": {\n \"light\": \"#7600A3\",\n \"dark\": \"#CF5FFF\"\n },\n \"primary-visited\": {\n \"light\": \"#9B00D4\",\n \"dark\": \"#CF5FFF\"\n },\n \"scrim\": {\n \"light\": \"#00000099\",\n \"dark\": \"#00000099\"\n },\n \"scrim-background\": {\n \"light\": \"#00000099\",\n \"dark\": \"#00000099\"\n },\n \"scrim-background-strong\": {\n \"light\": \"#000000CC\",\n \"dark\": \"#000000CC\"\n },\n \"scrim-gradient\": {\n \"light\": \"\",\n \"dark\": \"\"\n },\n \"scrim-strong\": {\n \"light\": \"#000000cc\",\n \"dark\": \"#000000cc\"\n },\n \"secondary\": {\n \"light\": \"#21272A\",\n \"dark\": \"#DBE4E9\"\n },\n \"secondary-background\": {\n \"light\": \"#E5EBEE\",\n \"dark\": \"#2A3236\"\n },\n \"secondary-background-hover\": {\n \"light\": \"#DBE4E9\",\n \"dark\": \"#333D42\"\n },\n \"secondary-background-selected\": {\n \"light\": \"#C9D7DE\",\n \"dark\": \"#3D494E\"\n },\n \"secondary-hover\": {\n \"light\": \"#000000\",\n \"dark\": \"#ffffff\"\n },\n \"secondary-onBackground\": {\n \"light\": \"#000000\",\n \"dark\": \"#FFFFFF\"\n },\n \"secondary-plain\": {\n \"light\": \"#181C1F\",\n \"dark\": \"#DBE4E9\"\n },\n \"secondary-plain-hover\": {\n \"light\": \"#000000\",\n \"dark\": \"#FFFFFF\"\n },\n \"secondary-plain-weak\": {\n \"light\": \"#5C6C74\",\n \"dark\": \"#8BA2AD\"\n },\n \"secondary-weak\": {\n \"light\": \"#576F76\",\n \"dark\": \"#748791\"\n },\n \"success-background\": {\n \"light\": \"#008A10\",\n \"dark\": \"#016E0B\"\n },\n \"success-background-hover\": {\n \"light\": \"#016E0B\",\n \"dark\": \"#008A10\"\n },\n \"success-content\": {\n \"light\": \"#016E0B\",\n \"dark\": \"#01A816\"\n },\n \"success-hover\": {\n \"light\": \"#005306\",\n \"dark\": \"#00C61C\"\n },\n \"success-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"success-plain\": {\n \"light\": \"#016E0B\",\n \"dark\": \"#00C61C\"\n },\n \"success-plain-hover\": {\n \"light\": \"#005306\",\n \"dark\": \"#58E15B\"\n },\n \"tone-1\": {\n \"light\": \"#131313\",\n \"dark\": \"#F2F2F2\"\n },\n \"tone-2\": {\n \"light\": \"#434343\",\n \"dark\": \"#ACACAC\"\n },\n \"tone-3\": {\n \"light\": \"#ACACAC\",\n \"dark\": \"#434343\"\n },\n \"tone-4\": {\n \"light\": \"#E4E4E4\",\n \"dark\": \"#303030\"\n },\n \"tone-5\": {\n \"light\": \"#F2F2F2\",\n \"dark\": \"#1E1E1E\"\n },\n \"tone-6\": {\n \"light\": \"#F8F8F8\",\n \"dark\": \"#131313\"\n },\n \"tone-7\": {\n \"light\": \"#ffffff\",\n \"dark\": \"#080808\"\n },\n \"transparent-background-hover\": {\n \"light\": \"#74879119\",\n \"dark\": \"#66778019\"\n },\n \"ui-canvas\": {\n \"light\": \"#F2F2F2\",\n \"dark\": \"#080808\"\n },\n \"ui-modalbackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#181C1F\"\n },\n \"upvote-background\": {\n \"light\": \"#D93900\",\n \"dark\": \"#D93900\"\n },\n \"upvote-background-disabled\": {\n \"light\": \"#d939004d\",\n \"dark\": \"#d939004d\"\n },\n \"upvote-background-hover\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#AE2C00\"\n },\n \"upvote-content\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#FF4500\"\n },\n \"upvote-content-weak\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"upvote-disabled\": {\n \"light\": \"#AE2C004c\",\n \"dark\": \"#FF45004c\"\n },\n \"upvote-onBackground\": {\n \"light\": \"#FFFFFF\",\n \"dark\": \"#FFFFFF\"\n },\n \"upvote-onStrongScrim\": {\n \"light\": \"#FF895D\",\n \"dark\": \"#FF895D\"\n },\n \"upvote-onStrongScrim-disabled\": {\n \"light\": \"#ff895d4d\",\n \"dark\": \"#ff895d4d\"\n },\n \"upvote-onStrongScrim-weaker\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"upvote-plain\": {\n \"light\": \"#AE2C00\",\n \"dark\": \"#FF895D\"\n },\n \"upvote-plain-disabled\": {\n \"light\": \"#ae2c004d\",\n \"dark\": \"#ff895d4d\"\n },\n \"upvote-plain-weaker\": {\n \"light\": \"#FF4500\",\n \"dark\": \"#FF4500\"\n },\n \"warning-background\": {\n \"light\": \"#B78800\",\n \"dark\": \"#B78800\"\n },\n \"warning-background-hover\": {\n \"light\": \"#977000\",\n \"dark\": \"#D8A100\"\n },\n \"warning-content\": {\n \"light\": \"#785800\",\n \"dark\": \"#B78800\"\n },\n \"warning-content-hover\": {\n \"light\": \"#5B4200\",\n \"dark\": \"#D8A100\"\n },\n \"warning-onBackground\": {\n \"light\": \"#000000\",\n \"dark\": \"#000000\"\n }\n};\n", "import { semanticColors } from '../semanticColors.js';\n\nexport const namedHTMLColorToHex: Record<string, string> = {\n aliceblue: '#f0f8ff',\n antiquewhite: '#faebd7',\n aqua: '#00ffff',\n aquamarine: '#7fffd4',\n azure: '#f0ffff',\n beige: '#f5f5dc',\n bisque: '#ffe4c4',\n black: '#000000',\n blanchedalmond: '#ffebcd',\n blue: '#0000ff',\n blueviolet: '#8a2be2',\n brown: '#a52a2a',\n burlywood: '#deb887',\n cadetblue: '#5f9ea0',\n chartreuse: '#7fff00',\n chocolate: '#d2691e',\n coral: '#ff7f50',\n cornflowerblue: '#6495ed',\n cornsilk: '#fff8dc',\n crimson: '#dc143c',\n cyan: '#00ffff',\n darkblue: '#00008b',\n darkcyan: '#008b8b',\n darkgoldenrod: '#b8860b',\n darkgray: '#a9a9a9',\n darkgreen: '#006400',\n darkgrey: '#a9a9a9',\n darkkhaki: '#bdb76b',\n darkmagenta: '#8b008b',\n darkolivegreen: '#556b2f',\n darkorange: '#ff8c00',\n darkorchid: '#9932cc',\n darkred: '#8b0000',\n darksalmon: '#e9967a',\n darkseagreen: '#8fbc8f',\n darkslateblue: '#483d8b',\n darkslategray: '#2f4f4f',\n darkslategrey: '#2f4f4f',\n darkturquoise: '#00ced1',\n darkviolet: '#9400d3',\n deeppink: '#ff1493',\n deepskyblue: '#00bfff',\n dimgray: '#696969',\n dimgrey: '#696969',\n dodgerblue: '#1e90ff',\n firebrick: '#b22222',\n floralwhite: '#fffaf0',\n forestgreen: '#228b22',\n fuchsia: '#ff00ff',\n gainsboro: '#dcdcdc',\n ghostwhite: '#f8f8ff',\n gold: '#ffd700',\n goldenrod: '#daa520',\n gray: '#808080',\n green: '#008000',\n greenyellow: '#adff2f',\n grey: '#808080',\n honeydew: '#f0fff0',\n hotpink: '#ff69b4',\n indianred: '#cd5c5c',\n indigo: '#4b0082',\n ivory: '#fffff0',\n khaki: '#f0e68c',\n lavender: '#e6e6fa',\n lavenderblush: '#fff0f5',\n lawngreen: '#7cfc00',\n lemonchiffon: '#fffacd',\n lightblue: '#add8e6',\n lightcoral: '#f08080',\n lightcyan: '#e0ffff',\n lightgoldenrodyellow: '#fafad2',\n lightgray: '#d3d3d3',\n lightgreen: '#90ee90',\n lightgrey: '#d3d3d3',\n lightpink: '#ffb6c1',\n lightsalmon: '#ffa07a',\n lightseagreen: '#20b2aa',\n lightskyblue: '#87cefa',\n lightslategray: '#778899',\n lightslategrey: '#778899',\n lightsteelblue: '#b0c4de',\n lightyellow: '#ffffe0',\n lime: '#00ff00',\n limegreen: '#32cd32',\n linen: '#faf0e6',\n magenta: '#ff00ff',\n maroon: '#800000',\n mediumaquamarine: '#66cdaa',\n mediumblue: '#0000cd',\n mediumorchid: '#ba55d3',\n mediumpurple: '#9370db',\n mediumseagreen: '#3cb371',\n mediumslateblue: '#7b68ee',\n mediumspringgreen: '#00fa9a',\n mediumturquoise: '#48d1cc',\n mediumvioletred: '#c71585',\n midnightblue: '#191970',\n mintcream: '#f5fffa',\n mistyrose: '#ffe4e1',\n moccasin: '#ffe4b5',\n navajowhite: '#ffdead',\n navy: '#000080',\n oldlace: '#fdf5e6',\n olive: '#808000',\n olivedrab: '#6b8e23',\n orange: '#ffa500',\n orangered: '#ff4500',\n orchid: '#da70d6',\n palegoldenrod: '#eee8aa',\n palegreen: '#98fb98',\n paleturquoise: '#afeeee',\n palevioletred: '#db7093',\n papayawhip: '#ffefd5',\n peachpuff: '#ffdab9',\n peru: '#cd853f',\n pink: '#ffc0cb',\n plum: '#dda0dd',\n powderblue: '#b0e0e6',\n purple: '#800080',\n rebeccapurple: '#663399',\n red: '#ff0000',\n rosybrown: '#bc8f8f',\n royalblue: '#4169e1',\n saddlebrown: '#8b4513',\n salmon: '#fa8072',\n sandybrown: '#f4a460',\n seagreen: '#2e8b57',\n seashell: '#fff5ee',\n sienna: '#a0522d',\n silver: '#c0c0c0',\n skyblue: '#87ceeb',\n slateblue: '#6a5acd',\n slategray: '#708090',\n slategrey: '#708090',\n snow: '#fffafa',\n springgreen: '#00ff7f',\n steelblue: '#4682b4',\n tan: '#d2b48c',\n teal: '#008080',\n thistle: '#d8bfd8',\n tomato: '#ff6347',\n turquoise: '#40e0d0',\n transparent: '#FFFFFF00',\n violet: '#ee82ee',\n wheat: '#f5deb3',\n white: '#ffffff',\n whitesmoke: '#f5f5f5',\n yellow: '#ffff00',\n yellowgreen: '#9acd32',\n};\n\nexport const isHexColor = (color: string): boolean =>\n Boolean(color.match(/^(#[0-9a-fA-F]{3,8}).*/));\n\nexport const isRPLColor = (color: string): boolean => color in semanticColors;\n\nexport const isNamedHTMLColor = (color: string): boolean => color in namedHTMLColorToHex;\n\n// Matches on rgb or rgba colors\nexport const isRgbaColor = (color: string): boolean => Boolean(color.match(/^rgba?\\(.*/));\n\nexport const isHslColor = (color: string): boolean => Boolean(color.match(/^hsla?\\(.*/));\n\nexport const getHexFromRPLColor = (color: string, theme: 'light' | 'dark' = 'light'): string => {\n // Prefer named HTML colors\n if (isNamedHTMLColor(color)) {\n return getHexFromNamedHTMLColor(color);\n }\n\n const colors = semanticColors as Record<string, { light: string; dark: string } | string>;\n const finalColor = colors[color];\n return typeof finalColor === 'string' ? finalColor : finalColor[theme];\n};\n\nexport const getHexFromNamedHTMLColor = (color: string): string =>\n namedHTMLColorToHex[color] ?? color;\n\nexport const getHexFromRgbaColor = (color: string): string => {\n // Works for rgb or rgba colors\n const [r, g, b, a] = color.replace(/^rgba?\\(|\\s+|\\)$/g, '').split(',');\n\n // Not valid rgb\n if (!r || !g || !b) return color;\n\n const rgbfloatValues = [r, g, b].map((val) => parseFloat(val));\n\n const convertedValues = [...rgbfloatValues];\n if (a) {\n const aFloat = parseFloat(a);\n const alphaNumberValue = Math.round(aFloat * 255);\n convertedValues.push(alphaNumberValue);\n }\n\n const finalHexValues = convertedValues\n .map((number) => number.toString(16).padStart(2, '0'))\n .join('');\n\n return `#${finalHexValues}`;\n};\n", "import type { BlockAlignment, BlockSizes } from '@devvit/protos';\nimport { BlockStackDirection } from '@devvit/protos';\n\nimport type { Devvit } from '../../Devvit.js';\n\nexport const ROOT_STACK_TRANSFORM_CONTEXT: TransformContext = {\n stackParentLayout: {\n hasHeight: true,\n hasWidth: true,\n direction: BlockStackDirection.UNRECOGNIZED,\n alignment: undefined,\n },\n};\n\nexport interface TransformContext {\n stackParentLayout?: StackParentLayout;\n}\n\nexport interface StackParentLayout {\n hasHeight: boolean;\n hasWidth: boolean;\n direction: BlockStackDirection;\n alignment: BlockAlignment | undefined;\n}\n\nenum ExpandDirection {\n NONE,\n HORIZONTAL,\n VERTICAL,\n}\n\nexport interface BlockGrowStretchDirection {\n growDirection: ExpandDirection;\n stretchDirection: ExpandDirection;\n}\n\nexport interface BlockDimensionsDetails {\n hasHeight: boolean;\n hasWidth: boolean;\n}\n\n/*\n Determine if a block has height and/or width based on its sizing, and its parent grow/stretch direction.\n*/\nexport function makeStackDimensionsDetails(\n props: Devvit.Blocks.StackProps | undefined,\n stackParentLayout: StackParentLayout | undefined,\n blockSizes: BlockSizes | undefined\n): BlockDimensionsDetails {\n if (!stackParentLayout) return { hasHeight: false, hasWidth: false };\n\n const { growDirection, stretchDirection } = makeBlockGrowStretchDetails(\n props?.grow,\n stackParentLayout.direction,\n stackParentLayout.alignment\n );\n\n const hasHeight =\n blockSizes?.height?.value?.value ||\n blockSizes?.height?.min?.value ||\n isExpandingOnConstrainedRespectiveAxis(\n ExpandDirection.VERTICAL,\n growDirection,\n stretchDirection,\n stackParentLayout.hasHeight\n );\n\n const hasWidth =\n blockSizes?.width?.value?.value ||\n blockSizes?.width?.min?.value ||\n isExpandingOnConstrainedRespectiveAxis(\n ExpandDirection.HORIZONTAL,\n growDirection,\n stretchDirection,\n stackParentLayout.hasWidth\n );\n\n return {\n hasHeight: Boolean(hasHeight),\n hasWidth: Boolean(hasWidth),\n };\n}\n\n/*\n Determine if the parent is growing or stretching on the defined axis.\n If true, tells us that the child may have height/width, even if its parent is not explicitly set.\n*/\nfunction isExpandingOnConstrainedRespectiveAxis(\n axis: ExpandDirection,\n growDirection: ExpandDirection,\n stretchDirection: ExpandDirection,\n parentHasDimensionSet: boolean\n): boolean {\n return (\n (growDirection === axis && parentHasDimensionSet) ||\n (stretchDirection === axis && parentHasDimensionSet)\n );\n}\n\n/*\n Determine the grow/stretch direction of a block based on its parent stack direction and alignment.\n*/\nfunction makeBlockGrowStretchDetails(\n isGrowing: boolean | undefined,\n parentStackDirection: BlockStackDirection,\n parentAlignment: BlockAlignment | undefined\n): BlockGrowStretchDirection {\n const parentIsVerticalOrRoot =\n parentStackDirection === BlockStackDirection.STACK_VERTICAL ||\n parentStackDirection === BlockStackDirection.UNRECOGNIZED;\n const parentIsHoritzontal = parentStackDirection === BlockStackDirection.STACK_HORIZONTAL;\n\n let growDirection = ExpandDirection.NONE;\n if (parentIsHoritzontal && isGrowing) {\n growDirection = ExpandDirection.HORIZONTAL;\n } else if (parentIsVerticalOrRoot && isGrowing) {\n growDirection = ExpandDirection.VERTICAL;\n }\n\n const hnone = parentAlignment === undefined || parentAlignment.horizontal === undefined;\n const vnone = parentAlignment === undefined || parentAlignment.vertical === undefined;\n\n const isStretching = parentIsHoritzontal\n ? Boolean(vnone)\n : parentIsVerticalOrRoot\n ? Boolean(hnone)\n : false;\n\n let stretchDirection = ExpandDirection.NONE;\n if (parentIsHoritzontal && isStretching) {\n stretchDirection = ExpandDirection.VERTICAL;\n } else if (parentIsVerticalOrRoot && isStretching) {\n stretchDirection = ExpandDirection.HORIZONTAL;\n }\n\n return {\n growDirection,\n stretchDirection,\n };\n}\n", "import type { BlockSizes, BlockSizes_Dimension_Value } from '@devvit/protos';\nimport { BlockSizeUnit, BlockStackDirection } from '@devvit/protos';\n\nimport type { Devvit } from '../../Devvit.js';\nimport type { StackParentLayout, TransformContext } from './transformContext.js';\n\nexport function parseSize(\n size: Devvit.Blocks.SizeString | undefined\n): BlockSizes_Dimension_Value | undefined {\n if (size == null) return undefined;\n if (typeof size === 'number') {\n return { value: size as number, unit: BlockSizeUnit.SIZE_UNIT_PERCENT };\n }\n\n // Regex:\n // Group 1: Digits with optional decimal trailer\n // Group 2: Optional suffix: 'px' or '%' (defaults to %)\n // eslint-disable-next-line security/detect-unsafe-regex\n const parts = size.match(/^(\\d+(?:\\.\\d+)?)(px|%)?$/);\n if (parts == null) {\n return undefined;\n }\n let unit = BlockSizeUnit.SIZE_UNIT_PERCENT;\n if (parts.at(2) === 'px') {\n unit = BlockSizeUnit.SIZE_UNIT_PIXELS;\n }\n const value = Number.parseFloat(parts[1]);\n return { value, unit };\n}\n\n/**\n * If a child has a relative size along its parent's main axis, but that parent's axis is not set, omit the dimension.\n * This is done to enforce consistency between web and Yoga (the layout engine used by mobile).\n * */\nexport function omitRelativeSizes(\n blockSizes: BlockSizes,\n stackParentLayout: StackParentLayout\n): BlockSizes {\n if (\n blockSizes.width?.value?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT ||\n blockSizes.width?.min?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT\n ) {\n if (\n stackParentLayout.direction === BlockStackDirection.STACK_HORIZONTAL ||\n stackParentLayout.direction === BlockStackDirection.STACK_DEPTH\n ) {\n if (!stackParentLayout.hasWidth) {\n blockSizes.width = undefined;\n }\n }\n }\n\n if (\n blockSizes.height?.value?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT ||\n blockSizes.height?.min?.unit === BlockSizeUnit.SIZE_UNIT_PERCENT\n ) {\n if (\n stackParentLayout.direction === BlockStackDirection.STACK_VERTICAL ||\n stackParentLayout.direction === BlockStackDirection.STACK_DEPTH\n ) {\n if (!stackParentLayout.hasHeight) {\n blockSizes.height = undefined;\n }\n }\n }\n\n return blockSizes;\n}\n\nexport function makeBlockSizes(\n props: Devvit.Blocks.BaseProps | undefined,\n transformContext: TransformContext\n): BlockSizes | undefined {\n if (props) {\n const hasWidth = props.width != null || props.minWidth != null || props.maxWidth != null;\n const hasHeight = props.height != null || props.minHeight != null || props.maxHeight != null;\n if (hasWidth || hasHeight || props.grow != null) {\n let blockSizes: BlockSizes = {\n width: hasWidth\n ? {\n value: parseSize(props.width),\n min: parseSize(props.minWidth),\n max: parseSize(props.maxWidth),\n }\n : undefined,\n height: hasHeight\n ? {\n value: parseSize(props.height),\n min: parseSize(props.minHeight),\n max: parseSize(props.maxHeight),\n }\n : undefined,\n grow: props.grow,\n };\n\n if (transformContext.stackParentLayout) {\n blockSizes = omitRelativeSizes(blockSizes, transformContext.stackParentLayout);\n }\n\n return blockSizes;\n }\n }\n return undefined;\n}\n", "import type { Metadata, ValidateFormRequest, ValidateFormResponse } from '@devvit/protos';\nimport { GetFieldsResponse, InstallationSettingsDefinition } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { transformFormFields } from '../../apis/ui/helpers/transformForm.js';\nimport { Devvit } from '../Devvit.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport { onValidateFormHelper } from './helpers/settingsUtils.js';\n\nasync function onGetSettingsFields(): Promise<GetFieldsResponse> {\n if (!Devvit.installationSettings) {\n throw new Error('Installation settings were not defined.');\n }\n\n return GetFieldsResponse.fromJSON({\n fields: {\n fields: transformFormFields(Devvit.installationSettings),\n },\n });\n}\n\nasync function onValidateForm(\n req: ValidateFormRequest,\n metadata: Metadata\n): Promise<ValidateFormResponse> {\n return onValidateFormHelper(req, Devvit.installationSettings, metadata);\n}\n\nexport function registerInstallationSettings(config: Config): void {\n config.provides(InstallationSettingsDefinition);\n extendDevvitPrototype('GetSettingsFields', onGetSettingsFields);\n extendDevvitPrototype('ValidateForm', onValidateForm);\n}\n", "import type { ContextActionDescription, ContextActionRequest, Metadata } from '@devvit/protos';\nimport { ContextActionDefinition, ContextActionList, ContextActionResponse } from '@devvit/protos';\nimport type { Empty } from '@devvit/protos/types/google/protobuf/empty.js';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport { getEffectsFromUIClient } from '../../apis/ui/helpers/getEffectsFromUIClient.js';\nimport type { MenuItem, MenuItemOnPressEvent } from '../../types/index.js';\nimport { Devvit } from '../Devvit.js';\nimport { getContextFromMetadata } from './context.js';\nimport { addCSRFTokenToContext } from './csrf.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nconst getActionId = (index: number): string => {\n return `menuItem.${index}`;\n};\n\nexport function getMenuItemById(id: string): MenuItem | undefined {\n return Devvit.menuItems.find((_, index) => getActionId(index) === id);\n}\n\nasync function getActions(\n _: Empty,\n _metadata: Metadata | undefined\n): Promise<DeepPartial<ContextActionList>> {\n const menuItems = Devvit.menuItems;\n\n if (!menuItems.length) {\n throw new Error('No menu items registered.');\n }\n\n const actions: DeepPartial<ContextActionDescription>[] = menuItems.map((item, index) => {\n return {\n actionId: getActionId(index),\n name: item.label,\n description: item.description,\n contexts: {\n subreddit: item.location.includes('subreddit'),\n post: item.location.includes('post'),\n comment: item.location.includes('comment'),\n },\n users: {\n loggedOut: item.forUserType?.includes('loggedOut'),\n moderator: item.forUserType?.includes('moderator'),\n },\n postFilters: item.postFilter === 'currentApp' ? { currentApp: true } : undefined,\n };\n });\n\n return ContextActionList.fromJSON({ actions });\n}\n\nasync function onAction(\n req: ContextActionRequest,\n metadata: Metadata\n): Promise<ContextActionResponse> {\n const menuItem = getMenuItemById(req.actionId);\n\n if (!menuItem) {\n throw new Error(`MenuItem ${req.actionId} not found`);\n }\n\n const commentId = req.comment?.id && `t1_${req.comment.id}`;\n const postId = req.post?.id && `t3_${req.post.id}`;\n const subredditId = req.subreddit?.id && `t5_${req.subreddit.id}`;\n const targetId = commentId || postId || subredditId;\n assertNonNull(targetId, 'targetId is missing from ContextActionRequest');\n\n const event: MenuItemOnPressEvent = {\n targetId,\n location: req.comment ? 'comment' : req.post ? 'post' : 'subreddit',\n };\n\n const context = Object.assign(\n makeAPIClients({\n ui: true,\n metadata,\n }),\n getContextFromMetadata(metadata, postId, commentId),\n {\n uiEnvironment: {\n timezone: metadata[Header.Timezone]?.values[0],\n locale: metadata[Header.Language]?.values[0],\n },\n }\n );\n\n await addCSRFTokenToContext(context, req);\n await menuItem.onPress(event, context);\n\n return ContextActionResponse.fromJSON({\n effects: getEffectsFromUIClient(context.ui),\n });\n}\n\nexport function registerMenuItems(config: Config): void {\n config.provides(ContextActionDefinition);\n extendDevvitPrototype('GetActions', getActions);\n extendDevvitPrototype('OnAction', onAction);\n}\n", "import type { ContextActionRequest, HandleUIEventRequest } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\n\nimport {\n getCommentById,\n getModerators,\n getPostById,\n getSubredditInfoById,\n} from '../../apis/reddit/index.js';\nimport type { BaseContext, ContextAPIClients } from '../../types/context.js';\nimport { getMenuItemById } from './menu-items.js';\n\ntype CSRF = {\n needsModCheck: boolean;\n};\n\nfunction getUserHeader(context: BaseContext & ContextAPIClients): string {\n const userHeader = context.metadata[Header.User].values[0];\n if (!userHeader) {\n throw new Error('User missing from context');\n }\n return userHeader;\n}\n\nfunction getSubredditHeader(context: BaseContext & ContextAPIClients): string {\n const subredditHeader = context.metadata[Header.Subreddit].values[0];\n if (!subredditHeader) {\n throw new Error('Subreddit missing from context');\n }\n return subredditHeader;\n}\n\nexport async function addCSRFTokenToContext(\n context: BaseContext & ContextAPIClients,\n req: ContextActionRequest\n) {\n /**\n * These headers represent the canonical user and subreddit for the current context. The user is validated by gateway against\n * the edge context, so we trust it.\n */\n const userHeader = getUserHeader(context);\n const subredditHeader = getSubredditHeader(context);\n\n if (!userHeader || !subredditHeader) {\n throw new Error('User or subreddit missing from context');\n }\n\n /**\n * These are the target ids for the action. They are provided by the user, but we need to validate them.\n */\n const commentId = req.comment?.id && `t1_${req.comment.id}`;\n const postId = req.post?.id && `t3_${req.post.id}`;\n const subredditId = req.subreddit?.id && `t5_${req.subreddit.id}`;\n const targetId = commentId || postId || subredditId;\n\n if (!targetId) {\n throw new Error('targetId is missing from ContextActionRequest');\n }\n\n /**\n * Validate user-provided commentId\n */\n if (commentId) {\n const comment = await getCommentById(commentId);\n if (!comment) {\n throw new Error(`Comment ${commentId} not found`);\n }\n if (comment.subredditId !== subredditHeader) {\n throw new Error(`Comment does not belong to the subreddit`);\n }\n }\n\n /**\n * Validate user-provided postId\n */\n if (postId) {\n const post = await getPostById(postId);\n if (!post) {\n throw new Error(`Post ${postId} not found`);\n }\n if (post.subredditId !== subredditHeader) {\n throw new Error(`Post does not belong to the subreddit`);\n }\n }\n\n /**\n * Validate user-provided subredditId\n */\n if (subredditId && subredditId !== subredditHeader) {\n throw new Error(`Subreddit ${subredditId} ${subredditHeader} not found`);\n }\n\n /**\n * Validate moderator status if needed\n */\n const thisItem = getMenuItemById(req.actionId);\n if (!thisItem) {\n throw new Error('Action not found');\n }\n const needsModCheck = !!thisItem.forUserType?.includes('moderator');\n if (needsModCheck && !isModerator(context)) {\n throw new Error('User is not a moderator');\n }\n\n const val: CSRF = {\n needsModCheck,\n };\n\n /**\n * Store a CSRF token in redis for this action. There's no way to pass this along, so we need to store it in redis.\n */\n await context.redis.set(\n `${userHeader}${subredditHeader}${targetId}${req.actionId}`,\n JSON.stringify(val)\n );\n await context.redis.expire(`${userHeader}${subredditHeader}${targetId}${req.actionId}`, 600);\n}\n\nasync function isModerator(context: BaseContext & ContextAPIClients) {\n const userHeader = getUserHeader(context);\n const subredditHeader = getSubredditHeader(context);\n\n const subreddit = await getSubredditInfoById(subredditHeader);\n const mods = await getModerators({ subredditName: subreddit.name! }).all();\n return !!mods.find((mod) => mod.id === userHeader);\n}\n\nexport async function validateCSRFToken(\n context: BaseContext & ContextAPIClients,\n req: HandleUIEventRequest\n) {\n const userHeader = getUserHeader(context);\n const subredditHeader = getSubredditHeader(context);\n const { actionId, thingId } = req.state?.__contextAction ?? {};\n const csrfData = await context.redis.get(`${userHeader}${subredditHeader}${thingId}${actionId}`);\n if (!csrfData) {\n throw new Error('CSRF token not found');\n }\n const csrf = JSON.parse(csrfData) as CSRF;\n if (csrf.needsModCheck && !(await isModerator(context))) {\n throw new Error('User is not a moderator: ' + userHeader + '; ' + subredditHeader);\n }\n}\n", "import type { PluginSettings } from '../../types/index.js';\n\nexport function pluginIsEnabled(settings: PluginSettings | boolean | undefined): boolean {\n if (!settings) {\n return false;\n }\n\n if (settings === true) {\n return true;\n }\n\n return settings.enabled;\n}\n", "import type { Metadata, ScheduledAction } from '@devvit/protos';\nimport { SchedulerHandlerDefinition } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport { Devvit } from '../Devvit.js';\nimport { getContextFromMetadata } from './context.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nasync function handleScheduledAction(args: ScheduledAction, metadata: Metadata): Promise<void> {\n const jobName = args.type;\n const scheduledJobHandler = Devvit.scheduledJobHandlers.get(jobName);\n\n if (!scheduledJobHandler) {\n throw new Error(`Job ${jobName} not found`);\n }\n\n const event = {\n name: jobName,\n data: args.data,\n };\n\n const context = Object.assign(\n makeAPIClients({\n metadata,\n }),\n getContextFromMetadata(metadata)\n );\n\n await scheduledJobHandler(event, context);\n}\n\nexport function registerScheduler(config: Config): void {\n config.provides(SchedulerHandlerDefinition);\n extendDevvitPrototype('HandleScheduledAction', handleScheduledAction);\n}\n", "import type { Metadata } from '@devvit/protos';\nimport {\n HandlerResult,\n OnAppInstallDefinition,\n OnAppUpgradeDefinition,\n OnAutomoderatorFilterCommentDefinition,\n OnAutomoderatorFilterPostDefinition,\n OnCommentCreateDefinition,\n OnCommentDeleteDefinition,\n OnCommentReportDefinition,\n OnCommentSubmitDefinition,\n OnCommentUpdateDefinition,\n OnModActionDefinition,\n OnModMailDefinition,\n OnPostCreateDefinition,\n OnPostDeleteDefinition,\n OnPostFlairUpdateDefinition,\n OnPostNsfwUpdateDefinition,\n OnPostReportDefinition,\n OnPostSpoilerUpdateDefinition,\n OnPostSubmitDefinition,\n OnPostUpdateDefinition,\n} from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport { StringUtil } from '@devvit/shared-types/StringUtil.js';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport type { TriggerEvent, TriggerOnEventHandler } from '../../types/triggers.js';\nimport { Devvit } from '../Devvit.js';\nimport { getContextFromMetadata } from './context.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\n\nfunction createCombinedHandler<Arg>(\n eventType: TriggerEvent,\n handlers: TriggerOnEventHandler<Arg>[] | undefined\n): (arg: Arg, metadata: Metadata) => Promise<HandlerResult> {\n assertNonNull(handlers);\n return async (arg, metadata) => {\n const event = {\n ...arg,\n type: eventType,\n };\n\n const context = Object.assign(\n makeAPIClients({\n metadata,\n }),\n getContextFromMetadata(metadata)\n );\n\n // Users can set multiple triggers for a single event. An error in one\n // shouldn't technically impact another but there's no actual guarantees\n // made around this.\n const results = await Promise.allSettled(handlers.map((fn) => fn(event, context)));\n // Throw any failed promises so that the EnvelopeClient will surface them to\n // the user.\n const errResult = joinSettledErrors(results);\n if (errResult) throw errResult.err;\n\n return {};\n };\n}\n\nexport function registerTriggers(config: Config): void {\n for (const event of Devvit.triggerOnEventHandlers.keys()) {\n switch (event) {\n case 'PostSubmit':\n config.provides(OnPostSubmitDefinition);\n extendDevvitPrototype(\n 'OnPostSubmit',\n createCombinedHandler('PostSubmit', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostCreate':\n config.provides(OnPostCreateDefinition);\n extendDevvitPrototype(\n 'OnPostCreate',\n createCombinedHandler('PostCreate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostUpdate':\n config.provides(OnPostUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostUpdate',\n createCombinedHandler('PostUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostReport':\n config.provides(OnPostReportDefinition);\n extendDevvitPrototype(\n 'OnPostReport',\n createCombinedHandler('PostReport', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostDelete':\n config.provides(OnPostDeleteDefinition);\n extendDevvitPrototype(\n 'OnPostDelete',\n createCombinedHandler('PostDelete', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostFlairUpdate':\n config.provides(OnPostFlairUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostFlairUpdate',\n createCombinedHandler('PostFlairUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentSubmit':\n config.provides(OnCommentSubmitDefinition);\n extendDevvitPrototype(\n 'OnCommentSubmit',\n createCombinedHandler('CommentSubmit', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentCreate':\n config.provides(OnCommentCreateDefinition);\n extendDevvitPrototype(\n 'OnCommentCreate',\n createCombinedHandler('CommentCreate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentUpdate':\n config.provides(OnCommentUpdateDefinition);\n extendDevvitPrototype(\n 'OnCommentUpdate',\n createCombinedHandler('CommentUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentReport':\n config.provides(OnCommentReportDefinition);\n extendDevvitPrototype(\n 'OnCommentReport',\n createCombinedHandler('CommentReport', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'CommentDelete':\n config.provides(OnCommentDeleteDefinition);\n extendDevvitPrototype(\n 'OnCommentDelete',\n createCombinedHandler('CommentDelete', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AppInstall':\n config.provides(OnAppInstallDefinition);\n extendDevvitPrototype(\n 'OnAppInstall',\n createCombinedHandler('AppInstall', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AppUpgrade':\n config.provides(OnAppUpgradeDefinition);\n extendDevvitPrototype(\n 'OnAppUpgrade',\n createCombinedHandler('AppUpgrade', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'ModAction':\n config.provides(OnModActionDefinition);\n extendDevvitPrototype(\n 'OnModAction',\n createCombinedHandler('ModAction', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'ModMail':\n config.provides(OnModMailDefinition);\n extendDevvitPrototype(\n 'OnModMail',\n createCombinedHandler('ModMail', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostNsfwUpdate':\n config.provides(OnPostNsfwUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostNsfwUpdate',\n createCombinedHandler('PostNsfwUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'PostSpoilerUpdate':\n config.provides(OnPostSpoilerUpdateDefinition);\n extendDevvitPrototype(\n 'OnPostSpoilerUpdate',\n createCombinedHandler('PostSpoilerUpdate', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AutomoderatorFilterPost':\n config.provides(OnAutomoderatorFilterPostDefinition);\n extendDevvitPrototype(\n 'OnAutomoderatorFilterPost',\n createCombinedHandler('AutomoderatorFilterPost', Devvit.triggerOnEventHandlers.get(event))\n );\n break;\n case 'AutomoderatorFilterComment':\n config.provides(OnAutomoderatorFilterCommentDefinition);\n extendDevvitPrototype(\n 'OnAutomoderatorFilterComment',\n createCombinedHandler(\n 'AutomoderatorFilterComment',\n Devvit.triggerOnEventHandlers.get(event)\n )\n );\n break;\n\n default:\n throw new Error(`Unknown trigger event: ${event}`);\n }\n }\n}\n\nfunction joinSettledErrors(results: PromiseSettledResult<unknown>[]): { err: unknown } | undefined {\n const errs = results.reduce(\n (sum, result) => (result.status === 'rejected' ? [...sum, result.reason] : sum),\n [] as unknown[]\n );\n if (errs.length === 0) return;\n if (errs.length === 1) return { err: errs[0] }; // Return original error.\n // The return type is a wrapper just to prevent ambiguity over nullish\n // rejections which some parts of our code have mistakenly done historically.\n return { err: new Error(errs.map((err) => StringUtil.caughtToString(err)).join('\\n')) };\n}\n", "export var StringUtil;\n(function (StringUtil) {\n /**\n * Returns a string, truncated as needed, of length limit or less. When\n * truncated, appends \"\u2026\".\n */\n function ellipsize(str, limit) {\n return str.length <= limit ? str : `${str.slice(0, limit - 1)}\u2026`;\n }\n StringUtil.ellipsize = ellipsize;\n /** Converts the first character of str to uppercase. */\n function capitalize(str) {\n if (str[0] == null)\n return str;\n return `${str[0].toLocaleUpperCase()}${str.slice(1)}`;\n }\n StringUtil.capitalize = capitalize;\n /** Returns true if string is nullish, empty, or whitespace-only. */\n function isBlank(str) {\n return str == null || /^\\s*$/.test(str);\n }\n StringUtil.isBlank = isBlank;\n /**\n * Converts an unknown type to a verbose stacktrace if an error or string form\n * otherwise.\n */\n function caughtToString(val, preferredErrorProperty = 'stack') {\n return val instanceof Error\n ? `${val[preferredErrorProperty] || val.stack || val.message || val.name}`\n : String(val);\n }\n StringUtil.caughtToString = caughtToString;\n /**\n * Converts an unknown type to a verbose stacktrace if an error or string form\n * otherwise. Prefer caughtToString where possible for typing reasons.\n */\n function caughtToStringUntyped(val, preferredErrorProperty = 'stack') {\n return val instanceof Error\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n `${val[preferredErrorProperty] || val.stack || val.message || val.name}`\n : String(val);\n }\n StringUtil.caughtToStringUntyped = caughtToStringUntyped;\n})(StringUtil || (StringUtil = {}));\n", "import type { HandleUIEventRequest, Metadata } from '@devvit/protos';\nimport { EffectType, HandleUIEventResponse, UIEventHandlerDefinition } from '@devvit/protos';\nimport type { DeepPartial } from '@devvit/shared-types/BuiltinTypes.js';\nimport type { Config } from '@devvit/shared-types/Config.js';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\nimport cloneDeep from 'clone-deep';\nimport { isEqual } from 'moderndash';\n\nimport { makeAPIClients } from '../../apis/makeAPIClients.js';\nimport { getEffectsFromUIClient } from '../../apis/ui/helpers/getEffectsFromUIClient.js';\nimport { getFormValues } from '../../apis/ui/helpers/getFormValues.js';\nimport { Devvit } from '../Devvit.js';\nimport { BlocksReconciler } from './blocks/BlocksReconciler.js';\nimport { getContextFromMetadata } from './context.js';\nimport { validateCSRFToken } from './csrf.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport { getMenuItemById } from './menu-items.js';\n\nasync function handleUIEvent(\n req: HandleUIEventRequest,\n metadata: Metadata\n): Promise<DeepPartial<HandleUIEventResponse>> {\n // Keep track of the original state so we can check if it was updated.\n const originalState = req.state ?? {};\n const state = cloneDeep(originalState);\n\n const apiClients = makeAPIClients({\n ui: true,\n metadata,\n });\n\n if (req.event?.formSubmitted && req.event.formSubmitted.formId) {\n const formKey = req.event.formSubmitted.formId as FormKey;\n\n if (formKey.includes('form.hook.')) {\n if (Devvit.customPostType) {\n const blocksReconciler = new BlocksReconciler(\n (_props: {}, context: Devvit.Context) => Devvit.customPostType?.render(context) ?? null,\n req.event,\n req.state,\n metadata,\n undefined\n );\n\n await blocksReconciler.reconcile();\n\n return HandleUIEventResponse.fromJSON({\n state: blocksReconciler.state,\n effects: blocksReconciler.getEffects(),\n });\n }\n }\n\n const formDefinition = Devvit.formDefinitions.get(formKey);\n\n if (!formDefinition) {\n throw new Error(`Form with key ${formKey} not found`);\n }\n\n let postId: string | undefined;\n let commentId: string | undefined;\n\n if (state.__contextAction) {\n const { actionId, thingId } = state.__contextAction;\n const menuItem = getMenuItemById(actionId);\n\n if (menuItem?.location === 'post') {\n postId = thingId;\n } else if (menuItem?.location === 'comment') {\n commentId = thingId;\n }\n }\n\n const context: Devvit.Context = Object.assign(\n apiClients,\n getContextFromMetadata(metadata, postId, commentId),\n {\n uiEnvironment: {\n timezone: metadata[Header.Timezone]?.values[0],\n locale: metadata[Header.Language]?.values[0],\n },\n }\n );\n\n await validateCSRFToken(context, req);\n\n await formDefinition.onSubmit(\n {\n values: getFormValues(req.event.formSubmitted.results),\n },\n context\n );\n } else if (req.event?.realtimeEvent) {\n if (Devvit.customPostType) {\n const blocksReconciler = new BlocksReconciler(\n (_props: {}, context: Devvit.Context) => Devvit.customPostType?.render(context) ?? null,\n req.event,\n req.state,\n metadata,\n undefined\n );\n\n await blocksReconciler.reconcile();\n\n return HandleUIEventResponse.fromJSON({\n state: blocksReconciler.state,\n effects: blocksReconciler.getEffects(),\n });\n }\n } else if (req.event?.toastAction) {\n throw new Error('Toast actions not yet implemented');\n }\n\n // Check if the state was updated to determine if we need to rerender.\n const stateWasUpdated = !isEqual(originalState, state);\n\n const uiEffects = getEffectsFromUIClient(apiClients.ui);\n const effects = stateWasUpdated\n ? [\n ...uiEffects,\n {\n type: EffectType.EFFECT_RERENDER_UI,\n rerenderUi: {\n delaySeconds: 0,\n },\n },\n ]\n : uiEffects;\n\n return HandleUIEventResponse.fromJSON({\n state,\n effects,\n });\n}\n\nexport function registerUIEventHandler(config: Config): void {\n config.provides(UIEventHandlerDefinition);\n extendDevvitPrototype('HandleUIEvent', handleUIEvent);\n}\n", "/**\n * Creates an array of elements split into groups the length of size. If array can't be split evenly, the final chunk will be the remaining elements.\n *\n * @example\n * chunk(['a', 'b', 'c', 'd'], 2)\n * // => [['a', 'b'], ['c', 'd']]\n *\n * chunk(['a', 'b', 'c', 'd'], 3)\n * // => [['a', 'b', 'c'], ['d']]\n * @param chunkSize The length of each chunk\n * @param array The array to chunk\n * @template TElem The type of the array elements\n * @returns Returns the new array of chunks\n */\n\nexport function chunk<TElem>(array: readonly TElem[], chunkSize: number): TElem[][] {\n const intSize = Math.trunc(chunkSize);\n\n if (array.length === 0 || intSize < 1)\n return [];\n \n let index = 0;\n let resultIndex = 0;\n const result = new Array(Math.ceil(array.length / intSize)) as TElem[][];\n\n while (index < array.length) {\n result[resultIndex++] = array.slice(index, (index += intSize));\n }\n\n return result;\n}", "/**\n * Creates an object with counts of occurrences of items in the array.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': true, age: 36 },\n * { 'user': 'betty', 'active': false, age: 36 },\n * { 'user': 'fred', 'active': true, age: 40 }\n * ]\n *\n * count(users, value => value.active ? 'active' : 'inactive');\n * // => { 'active': 2, 'inactive': 1 }\n *\n * count(users, value => value.age);\n * // => { 36: 2, 40: 1 }\n *\n * @param criteria The criteria to count by\n * @param array The array or record to iterate over\n * @template TElem The type of the array elements\n * @returns Returns the composed aggregate object\n */\n\nexport function count<TElem, TKey extends PropertyKey>(array: readonly TElem[], criteria: (value: TElem) => TKey): Record<TKey, number> {\n const result = {} as Record<TKey, number>;\n for (const value of array) {\n const key = criteria(value);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n result[key] = (result[key] ?? 0) + 1;\n }\n return result;\n}", "// native Array.flat is much slower than this - node 19\nexport function fastArrayFlat<TElem>(arrays: (readonly TElem[])[]): readonly TElem[] {\n let result = arrays.shift() ?? [];\n\n for (const array of arrays) {\n result = [...result, ...array];\n }\n\n return result;\n}", "import type { CompareFunction } from \"@helpers/ArrayTypeUtils.js\";\nimport type { ArrayMinLength } from \"@type/ArrayMinLength.js\";\n\nimport { fastArrayFlat } from \"@helpers/fastArrayFlat.js\";\n\n/**\n * Create a new array with values from the first array that are not present in the other arrays.\n * Optionally, use a compare function to determine the comparison of elements (default is `===`).\n * \n * **Consider using the native [Set.prototype.difference()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/difference) function instead.**\n * \n * @example\n * difference([2, 1], [2, 3], [6])\n * // => [1]\n *\n * // ---- Custom compare function ----\n * const compareByFloor = (a, b) => Math.floor(a) === Math.floor(b);\n * difference([1.2, 3.1], [1.3, 2.4], compareByFloor)\n * // => [3.1]\n *\n * // ---- Only compare by id ----\n * const arr1 = [{ id: 1, name: 'Yeet' }, { id: 3, name: 'John' }];\n * const arr2 = [{ id: 3, name: 'Carl' }, { id: 4, name: 'Max' }];\n *\n * difference(arr1, arr2, (a, b) => a.id === b.id)\n * // => [{ id: 1, name: 'Yeet' }]\n * \n * @param arraysOrCompareFn Two or more arrays with an optional compare function at the end\n * @template TElem The type of the array elements\n * @template TArrays The type of the arrays provided\n * @returns Returns a new array of filtered values\n */\nexport function difference<TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2>): TElem[];\nexport function difference<TArrays extends ArrayMinLength<unknown[], 2>>(...arraysOrCompareFn: [...TArrays, CompareFunction<TArrays>]): TArrays[0];\nexport function difference<TArrays extends ArrayMinLength<unknown[], 2>, TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2> | [...TArrays, CompareFunction<TArrays>]): TArrays[0] {\n const compareFnProvided = typeof arraysOrCompareFn.at(-1) === \"function\";\n const compareFunction = compareFnProvided && arraysOrCompareFn.pop() as CompareFunction<TArrays>;\n\n const arrays = arraysOrCompareFn as TArrays;\n const firstArray = arrays.shift()!;\n const combinedRestArray = fastArrayFlat(arrays);\n\n if (!compareFunction) {\n const restSet = new Set(combinedRestArray);\n return firstArray.filter(element => !restSet.has(element));\n }\n\n const difference: TArrays[0] = [];\n for (const element of firstArray) {\n if (combinedRestArray.every(item => !compareFunction(element, item))) {\n difference.push(element);\n }\n }\n\n return difference;\n}\n", "/**\n * Creates a slice of `array` excluding elements dropped from the end. \n * Elements are dropped until `predicate` returns falsy.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': false },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': true }\n * ]\n *\n * dropRightWhile(users, user => user.active)\n * // => objects for ['barney']\n * @param predicate The function invoked per iteration\n * @param array The array to query\n * @template TElem The type of the array elements\n * @returns Returns the slice of `array`\n */\n\nexport function dropRightWhile<TElem>(array: readonly TElem[], predicate: (value: TElem) => boolean) {\n let i = array.length;\n while (i > 0 && predicate(array[i - 1])) {\n i--;\n }\n return array.slice(0, i);\n}\n", "/**\n * Creates a slice of `array` excluding elements dropped from the beginning. \n * Elements are dropped until `predicate` returns falsy.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': true },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': false }\n * ]\n *\n * dropWhile(users, user => user.active)\n * // => objects for ['pebbles']\n * @param predicate The function invoked per iteration\n * @param array The array to query\n * @template TElem The type of the array elements\n * @returns Returns the slice of `array`\n */\n\nexport function dropWhile<TElem>(array: readonly TElem[], predicate: (value: TElem) => boolean): TElem[] {\n const index = array.findIndex(x => !predicate(x));\n return array.slice(index === -1 ? array.length : index);\n}\n", "/**\n * Creates an object with grouped items in the array.\n * \n * @deprecated\n * **Deprecated: Use the native \"Object.groupBy()\" function instead.**\n * \n * @example\n * group([6.1, 4.2, 6.3], Math.floor)\n * // => { 4: [4.2], 6: [6.1, 6.3] }\n *\n * group([6.1, 4.2, 6.3], value => value > 5 ? '>5' : '<=5')\n * // => { '<=5': [4.2], '>5': [6.1, 6.3] }\n * \n * @param collection The array or object to iterate over\n * @param getGroupKey A function that returns the group id for each item\n * @template TElem The type of the array elements\n * @returns An object with grouped items\n */\n\nexport function group<TElem, TKey extends PropertyKey>(array: readonly TElem[], getGroupKey: (elem: TElem) => TKey): Record<TKey, TElem[]> {\n const result = {} as Record<TKey, TElem[]>;\n for (const elem of array) {\n const key = getGroupKey(elem);\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (result[key] ??= []).push(elem);\n }\n return result;\n}", "/**\n * Creates unique array retaining first occurrence of elements.\n *\n * A compare function is optional (default is `===`).\n * \n * @example\n * unique([2, 1, 2])\n * // => [2, 1]\n * \n * // compare by object values\n * const users = [\n * { id: 1, name: 'john' },\n * { id: 2, name: 'john' },\n * { id: 2, name: 'john' },\n * ]\n * \n * unique(users, isEqual)\n * // => [{ id: 1, name: 'john' }, { id: 2, name: 'john' }]\n * \n * // compare by id\n * unique(users, (a, b) => a.name === b.name)\n * // => [{ id: 1, name: 'john' }]\n *\n * @param array Array to inspect\n * @param iteratee Iteratee invoked per element\n * @template TElem Type of the array elements\n * @returns A new unique array\n */\n\nexport function unique<TElem>(array: readonly TElem[], compareFn?: (a: TElem, b: TElem) => boolean): TElem[] {\n if (!compareFn)\n return [...new Set(array)];\n\n // Custom compare function can't be optimized with Set\n const uniqueArray: TElem[] = [];\n\n for (const value of array) {\n if (!uniqueArray.some(uniqueValue => compareFn(value, uniqueValue)))\n uniqueArray.push(value);\n }\n \n return uniqueArray;\n}\n", "import type { CompareFunction } from \"@helpers/ArrayTypeUtils.js\";\nimport type { ArrayMinLength } from \"@type/ArrayMinLength.js\";\n\nimport { fastArrayFlat } from \"@helpers/fastArrayFlat.js\";\n\nimport { unique } from \"./unique.js\";\n\n/**\n * Create an array with unique values that are present in all arrays. \n * The order of the values is based on the first array. \n * \n * Optionally, use a compare function for element comparison (default is `===`).\n * \n * **Consider using the native [Set.prototype.intersection()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/intersection) function instead.**\n * \n * @example\n * intersection([2, 1], [2, 3], [6, 2])\n * // => [2]\n *\n * // ---- Custom compare function ----\n * const compareFn = (a, b) => Math.floor(a) === Math.floor(b);\n * \n * intersection([1.2, 1.1], [1.3, 2.4], compareFn)\n * // => [1.2]\n *\n * // ---- Only compare by id ----\n * const arr1 = [{ id: 1, name: 'Yeet' }, { id: 3, name: 'John' }];\n * const arr2 = [{ id: 3, name: 'Carl' }, { id: 4, name: 'Max' }];\n *\n * intersection(arr1, arr2, (a, b) => a.id === b.id)\n * // => [{ id: 3, name: 'John' }]\n * \n * @param arraysOrCompareFn Two or more arrays with an optional compare function at the end\n * @template TElem Type of the array elements\n * @template TArrays Type of the arrays provided\n * @returns New array of intersecting values\n */\n\nexport function intersection<TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2>): TElem[];\nexport function intersection<TArrays extends ArrayMinLength<unknown[], 2>>(...arraysOrCompareFn: [...TArrays, CompareFunction<TArrays>]): TArrays[0];\nexport function intersection<TArrays extends ArrayMinLength<unknown[], 2>, TElem>(...arraysOrCompareFn: ArrayMinLength<TElem[], 2> | [...TArrays, CompareFunction<TArrays>]): TArrays[0] {\n const compareFnProvided = typeof arraysOrCompareFn.at(-1) === \"function\";\n const compareFunction = compareFnProvided && arraysOrCompareFn.pop() as CompareFunction<TArrays>;\n\n const arrays = arraysOrCompareFn as TArrays;\n const firstArray = unique(arrays.shift()!);\n const combinedRestArray = fastArrayFlat(arrays);\n\n if (!compareFunction) {\n const restSet = new Set(combinedRestArray);\n return firstArray.filter(element => restSet.has(element));\n }\n \n const intersection: TArrays[0] = [];\n\n for (const element of firstArray) {\n if (combinedRestArray.some(item => compareFunction(element, item))) {\n intersection.push(element);\n }\n }\n\n return intersection;\n}\n", "/**\n * Moves an element within an array.\n * \n * @example\n * ```typescript\n * move([1, 2, 3, 4, 5], 0, 2);\n * // => [2, 3, 1, 4, 5]\n * ```\n * \n * @param array The input array\n * @param fromIndex Index of the element to move\n * @param toIndex Target index for the element\n * @throws If index is out of bounds\n * @template TArr Type of the array elements\n * @returns The modified array with the moved element\n */\nexport function move<TArr>(array: TArr[], fromIndex: number, toIndex: number): TArr[] {\n if (fromIndex < 0 || fromIndex >= array.length)\n throw new Error(`Invalid 'fromIndex': ${fromIndex}. Must be between 0 and ${array.length - 1}.`);\n\n if (toIndex < 0 || toIndex >= array.length)\n throw new Error(`Invalid 'toIndex': ${toIndex}. Must be between 0 and ${array.length - 1}.`);\n\n if (fromIndex === toIndex)\n return array;\n\n const item = array[fromIndex];\n\n if (fromIndex < toIndex)\n for (let index = fromIndex; index < toIndex; index++)\n array[index] = array[index + 1];\n else\n for (let index = fromIndex; index > toIndex; index--)\n array[index] = array[index - 1];\n\n array[toIndex] = item;\n\n return array;\n}", "/**\n * Creates an array from start to end (inclusive), stepping by step. \n * If start is larger than end, the array is generated in reverse\n *\n * @example\n * for (const num of range(1, 5)) {\n * console.log(num);\n * }\n * // => 1 2 3 4 5\n * \n * // Array of even numbers between 0 and 10:\n * range(0, 10, 2);\n * // => [0, 2, 4, 6, 8, 10]\n * \n * // Descending range:\n * range(5, 0, 2);\n * // => [5, 3, 1]\n * \n * @param start Start number of sequence\n * @param end End number of sequence\n * @param step Step between numbers, default: 1\n * @throws If range is negative or step is 0\n * @returns An array of numbers\n */\nexport function range(start: number, end: number, step = 1): number[] {\n if (step <= 0)\n throw new Error(\"The step must be greater than 0.\");\n\n step = start > end ? -step : step;\n const length = Math.floor(Math.abs((end - start) / step)) + 1;\n\n const result = new Array(length) as number[];\n \n for (let i = 0; i < length; i++) {\n result[i] = start + (i * step);\n }\n\n return result;\n}", "/**\n * Creates a new array of shuffled values, using the Fisher-Yates-Durstenfeld Shuffle algorithm.\n *\n * @example\n * shuffle([1, 2, 3, 4])\n * // => [4, 1, 3, 2]\n * @param array Array to shuffle\n * @template TElem The type of the array elements\n * @returns A new shuffled array\n */\n\nexport function shuffle<TElem>(array: readonly TElem[]): TElem[] {\n const shuffledArray = [...array];\n \n for (let index = shuffledArray.length - 1; index > 0; index--) {\n const randomIndex = Math.floor(Math.random() * (index + 1));\n [shuffledArray[index], shuffledArray[randomIndex]] = [shuffledArray[randomIndex], shuffledArray[index]];\n }\n \n return shuffledArray;\n}", "\n/**\n * Creates new array sorted in ascending/descending order with single or multiple criteria.\n * \n * @example\n * sort([1, 2, 3, 4], { order: 'desc' })\n * // => [4, 3, 2, 1]\n * \n * // --- Sorting by multiple properties ---\n * const array = [{ a: 2, b: 1 }, { a: 1, b: 2 }, { a: 1, b: 1 }];\n * \n * sort(array,\n * { order: 'asc', by: item => item.a },\n * { order: 'desc', by: item => item.b }\n * )\n * // => [{ a: 1, b: 2 }, { a: 1, b: 1 }, { a: 2, b: 1 }]\n * \n * @param array Array to sort\n * @param criteria Criteria to sort by\n * @param criteria.order Order to sort in, either 'asc' or 'desc'\n * @param criteria.by Iteratee function to sort based on a specific property\n * @template TElem Type of the array elements\n * @returns New sorted array\n*/\nexport function sort<TElem>(array: readonly TElem[], ...criteria: { order?: \"asc\" | \"desc\", by?: (item: TElem) => number | bigint | Date | string }[]): TElem[] {\n return [...array].sort((a, b) => {\n for (const { order = \"asc\", by = (item: TElem) => item } of criteria) {\n const aValue = by(a);\n const bValue = by(b);\n if (aValue !== bValue) {\n const compare = aValue < bValue ? -1 : 1;\n return order === \"asc\" ? compare : -compare;\n }\n }\n return 0;\n });\n}\n", "/**\n * Creates a slice of `array` with elements taken from the end. \n * Elements are taken until `predicate` returns falsy.\n * \n * @example\n * const users = [\n * { 'user': 'barney', 'active': false },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': true }\n * ]\n *\n * takeRightWhile(users, user => user.active)\n * // => objects for ['fred', 'pebbles']\n * @param predicate The function invoked per iteration.\n * @param array The array to query.\n * @template TElem The type of the array elements.\n * @returns Returns the slice of `array`.\n */\n\nexport function takeRightWhile<TElem>(array: readonly TElem[], predicate: (elem: TElem) => boolean): TElem[] {\n const result: TElem[] = [];\n\n for (let i = array.length - 1; i >= 0; i--) {\n if (predicate(array[i])) {\n result.unshift(array[i]);\n } else {\n break;\n }\n }\n\n return result;\n}\n", "/**\n * Creates a slice of `array` with elements taken from the beginning. \n * Elements are taken until `predicate` returns falsy.\n *\n * @example\n * const users = [\n * { 'user': 'barney', 'active': true },\n * { 'user': 'fred', 'active': true },\n * { 'user': 'pebbles', 'active': false }\n * ]\n *\n * takeWhile(users, user => user.active)\n * // => objects for ['barney', 'fred']\n * @param predicate The function invoked per iteration.\n * @param array The array to query.\n * @template TElem The type of the array elements.\n * @returns A new array of taken elements.\n */\n\nexport function takeWhile<TElem>(array: readonly TElem[], predicate: (elem: TElem) => boolean): TElem[] {\n const result: TElem[] = [];\n\n for (const element of array) {\n if (predicate(element)) {\n result.push(element);\n } else {\n break;\n }\n }\n\n return result;\n}\n", "import type { Jsonifiable } from \"@type/Jsonifiable.js\";\n\ntype SupportedAlgorithms = \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\nlet textEncoder: TextEncoder | undefined;\n\n/**\n * Generates a hash of the given data using the specified algorithm.\n *\n * It uses the Web Crypto API to generate the hash.\n * \n * *Note: If you need a secure hash use a specialized library like [crypto-js](https://www.npmjs.com/package/crypto-js) instead.*\n * \n * @example\n * // Hash a string using the default algorithm (SHA-256)\n * await hash('hello world'); \n * // => \"b94d27b9934d3e08a52e52d7da7dabfac484efe37a53...\"\n *\n * // Hash an object using the SHA-512 algorithm\n * await hash({ foo: 'bar', baz: 123 }, 'SHA-512');\n * // => \"d8f3c752c6820e580977099368083f4266b569660558...\"\n * \n * @param data The data to hash, either as a string or a JSON-serializable object.\n * @param algorithm The hashing algorithm to use. Defaults to 'SHA-256'.\n * @returns A Promise that resolves to the hexadecimal representation of the hash.\n *\n * @throws {DOMException} If the specified algorithm is not supported by the Web Crypto API.\n */\n\nexport async function hash(data: Jsonifiable, algorithm: SupportedAlgorithms = \"SHA-256\"): Promise<string> {\n textEncoder ??= new TextEncoder();\n\n const dataBuffer = typeof data === \"string\"\n ? textEncoder.encode(data) \n : textEncoder.encode(JSON.stringify(data));\n \n const hashBuffer = await crypto.subtle.digest(algorithm, dataBuffer);\n const hashArray = [...new Uint8Array(hashBuffer)];\n const hexValues = hashArray.map(b => b.toString(16).padStart(2, \"0\"));\n return hexValues.join(\"\");\n}\n", "\n/**\n * Generates a random integer between two given numbers, including those numbers.\n * \n * It uses `crypto.getRandomValues` to generate the random number.\n * @example\n * randomInt(1, 10) \n * // => 5\n * \n * @param min The smallest integer that can be generated.\n * @param max The largest integer that can be generated.\n * \n * @returns A random integer between `min` and `max`, including `min` and `max`.\n */\n\nexport function randomInt(min: number, max: number): number {\n // Taken from https://stackoverflow.com/a/41452318\n if (!Number.isInteger(min) || !Number.isInteger(max))\n throw new TypeError(\"min and max must be integers\");\n\n if (min >= max) \n throw new Error(\"max must be greater than min\");\n\n const range = max - min + 1;\n const randomBytes = Math.ceil(Math.log2(range) / 8);\n const maxRandNumber = Math.pow(256, randomBytes);\n const randomBuffer = new Uint8Array(randomBytes);\n\n let randomValue: number;\n do {\n crypto.getRandomValues(randomBuffer);\n randomValue = 0;\n for (let index = 0; index < randomBytes; index++) {\n // eslint-disable-next-line no-bitwise\n randomValue = (randomValue << 8) + randomBuffer[index];\n }\n // rerun if randomValue is bigger than range\n } while (randomValue >= maxRandNumber - (maxRandNumber % range));\n\n return min + (randomValue % range);\n}\n", "import { randomInt } from \"./randomInt.js\";\n\n/**\n * Gets a random element an array. A single element is returned by default. \n * Specify the `multi` parameter to get an array of multiple random elements.\n *\n * If the array is empty, `undefined` is returned. \n * If `multi` is defined it returns an empty array.\n * \n * It uses `crypto.getRandomValues` to get the random element.\n * @example\n * randomElem([1, 2, 3, 4])\n * // => 2\n *\n * randomElem([1, 2, 3, 4], 2)\n * // => [3, 1]\n * @param array The array to sample.\n * @returns Returns the random element.\n */\n\nexport function randomElem<TArr>(array: TArr[]): TArr | undefined;\nexport function randomElem<TArr>(array: TArr[], multi: number): TArr[];\nexport function randomElem<TArr>(array: TArr[], multi?: number): TArr | undefined | TArr[] {\n if (multi === undefined) {\n if (array.length === 0) return undefined;\n return getSingleElement(array);\n }\n\n if (multi && array.length === 0) return [];\n\n // Multiple samples\n const result = new Array<TArr>(multi);\n for (let i = 0; i < multi; i++) {\n result[i] = getSingleElement(array);\n }\n return result;\n}\n\nfunction getSingleElement<TArr>(array: TArr[]): TArr {\n const randomIndex = randomInt(0, array.length - 1);\n return array[randomIndex];\n}", "/* eslint-disable no-bitwise */\n\n/**\n * Generates a random float between two given numbers, including those numbers.\n * \n * It uses `crypto.getRandomValues` to generate the random number.\n * @example\n * randomFloat(1, 10) \n * // => 1.123456789\n * \n * @param min The smallest float that can be generated.\n * @param max The largest float that can be generated.\n * \n * @returns A random float between `min` and `max`, including `min` and `max`.\n */\nexport function randomFloat(min: number, max: number): number {\n if (min >= max) \n throw new Error(\"max must be greater than min\");\n\n // TODO: Switch to UInt64Array when safari support is better (https://caniuse.com/mdn-javascript_builtins_bigint64array)\n const randomBuffer = new Uint32Array(2);\n crypto.getRandomValues(randomBuffer);\n\n // keep all 32 bits of the the first, top 21 of the second for 53 random bits\n const randomBigInt = (BigInt(randomBuffer[0]) << 21n) | (BigInt(randomBuffer[1]) >> 11n);\n \n // fraction between 0 and 1 with full 53bit precision\n const fraction = Number(randomBigInt) / Number.MAX_SAFE_INTEGER; // (2 ** 53)\n return min + (fraction * (max - min));\n}\n", "import { randomInt } from \"./randomInt.js\";\n\nconst DEFAULT_CHARSET = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\n/**\n * Generates a random string of the specified length.\n * The default charset is alphanumeric characters.\n * \n * It uses `crypto.getRandomValues` to generate the random string.\n * \n * @example\n * randomString(8);\n * // => \"JWw1p6rD\"\n *\n * randomString(16, 'abc');\n * // => \"cbaacbabcabccabc\"\n * @param length The length of the string to generate.\n * @param charSet The set of characters to use when generating the string. Defaults to alphanumeric characters.\n * @returns A random string of the specified length.\n */\n\nexport function randomString(length: number, charSet = DEFAULT_CHARSET): string {\n if (charSet.length <= 0) return \"\";\n\n let result = \"\";\n for (let index = 0; index < length; index++) {\n const randomIndex = randomInt(0, charSet.length - 1);\n result += charSet[randomIndex];\n }\n\n return result;\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\ntype Tail<T extends unknown[]> = T extends [infer _Head, ...infer Tail] ? Tail : never;\n\n/**\n * Transforms a function into a decorator function.\n * \n * @example\n * ```typescript\n * function log(func: Function, message: string) {\n * return function (...args: unknown[]) {\n * console.log(message);\n * return func(...args);\n * };\n * }\n * \n * const logger = toDecorator(log);\n * \n * class TestClass {\n * @logger(\"Hello world!\")\n * testMethod() {\n * return 1; \n * }\n * }\n * \n * const instance = new TestClass();\n * instance.testMethod(); \n * // => Log \"Hello World\" and return 1\n * ```\n * @param func The function to transform.\n * @returns A decorator function that can be used to decorate a method.\n */\n// waiting for https://github.com/evanw/esbuild/issues/104\nexport function toDecorator<TFunc extends GenericFunction<TFunc>>(func: TFunc) {\n return function (...args: Tail<Parameters<TFunc>>) {\n return function (originalMethod: unknown, _context: ClassMethodDecoratorContext) {\n const funcArgs = [originalMethod, ...args] as Parameters<TFunc>;\n return func(...funcArgs);\n };\n };\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Creates a debounced version of a function. Only calling it after a specified amount of time has passed without any new calls.\n * \n * **Methods:** \n * - `cancel()` will cancel the next invocation of the debounced function. \n * - `flush()` will immediately invoke the debounced function and cancel any pending invocations.\n * - `pending()` returns true if the debounced function is set to invoke.\n * \n * This function can be used as a decorator with {@link decDebounce}.\n * \n * @example\n * const sayHello = (name: string) => console.log(`Hello, ${name}!`);\n * const debouncedSayHello = debounce(sayHello, 200);\n * \n * debouncedSayHello(\"John\");\n * debouncedSayHello(\"Jane\");\n * // => Only the second invocation of `debouncedSayHello` is executed, after a delay of 200ms.\n * @param func The function to debounce.\n * @param wait The number of milliseconds to wait before invoking `func`.\n * @returns A debounced version of `func` with `cancel` and `flush` methods.\n */\n\nexport function debounce<TFunc extends GenericFunction<TFunc>>(func: TFunc, wait: number): TFunc & {\n cancel: () => void;\n flush: () => void;\n pending: () => boolean;\n} {\n let timeoutId: NodeJS.Timeout | undefined;\n const debounced = function (this: unknown, ...args: Parameters<TFunc>) {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n func.apply(this, args);\n timeoutId = undefined;\n }, wait);\n };\n\n debounced.cancel = function () {\n clearTimeout(timeoutId);\n timeoutId = undefined;\n };\n\n debounced.flush = function (this: unknown, ...args: Parameters<TFunc>) {\n debounced.cancel();\n func.apply(this, args);\n };\n\n debounced.pending = function () {\n return timeoutId !== undefined;\n };\n\n return debounced as TFunc & { cancel: () => void; flush: () => void; pending: () => boolean };\n}\n", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { debounce } from \"@function/debounce.js\";\n\n/**\n * Debounces the decorated function. Only calling it after a specified amount of time has passed without any new calls.\n * \n * Look at {@link debounce} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decDebounce(100)\n * testMethod(str: string) {\n * console.log(\"Debounced:\", str);\n * }\n * }\n * \n * const instance = new TestClass();\n * instance.testMethod(\"Hello\");\n * instance.testMethod(\"World\");\n * // => Only the second invocation of `debouncedSayHello` is executed, after a delay of 1000ms.\n * ```\n * @param wait Milliseconds to wait before invoking the decorated function after the last invocation.\n */\n\nexport function decDebounce(wait: number) {\n return toDecorator(debounce)(wait);\n}\n", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Creates a function that invokes the given function as long as it's called `<= n` times.\n * \n * Subsequent calls to the created function return the result of the last `func` invocation.\n *\n * This function can be used as a decorator with {@link decMaxCalls}.\n * @example\n * let count = 0;\n * const addCount = () => ++count;\n *\n * // Allow addCount to be invoked twice.\n * const limitAddCount = maxCalls(addCount, 2)\n *\n * limitAddCount() // => 1\n * limitAddCount() // => 2\n * limitAddCount() // => 2\n * // => `limitAddCount` is invoked twice and the result is cached.\n * @param n The number of calls before the cached result is returned.\n * @param func The function to restrict.\n * @returns Returns the new restricted function.\n */\n\nexport function maxCalls<TFunc extends GenericFunction<TFunc>>(func: TFunc, n: number): TFunc {\n let count = 0;\n let result: ReturnType<TFunc>;\n return function (this: unknown, ...args: Parameters<TFunc>): ReturnType<TFunc> {\n if (count < n) {\n count += 1;\n result = func.apply(this, args);\n }\n return result;\n } as TFunc;\n}\n", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { maxCalls } from \"@function/maxCalls.js\";\n\n/**\n * Only invokes the decorated function as long as it's called `<= n` times. \n * Subsequent calls to the decorated function return the result of the last invocation.\n * \n * Look at {@link maxCalls} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * private count = 0;\n * @decMaxCalls(2)\n * testMethod() {\n * return ++this.count;\n * }\n * }\n * const instance = new TestClass();\n * instance.testMethod(); // => 1 \n * instance.testMethod(); // => 2\n * instance.testMethod(); // => 2\n * ```\n * @param n The number of calls before the cached result is returned.\n */\n\nexport function decMaxCalls(n: number) {\n return toDecorator(maxCalls)(n);\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\nconst defaultResolver = (...args: unknown[]) => JSON.stringify(args);\n\n/**\n * Creates a function that memoizes the result of a given function.\n * \n * The cache key is determined by the `resolver` or by the arguments from the function call.\n *\n * **Options:**\n * - `resolver` A function that determines the cache key based on the arguments provided.\n * - `ttl` the time to live for the cache entries in milliseconds.\n * \n * **Properties:**\n * - `cache` The cache is an instance of `Map` and can be used to clear or inspect the cache. \n * It can be replaced by a custom cache that matches the `Map` interface.\n * \n * \n * This function can be used as a decorator with {@link decMemoize}.\n * \n * @example\n * ```typescript\n * function fibonacci(n: number) {\n * if (n <= 1) return n;\n * return fibonacci(n - 1) + fibonacci(n - 2);\n * }\n *\n * const memoizedFib = memoize(fibonacci, { ttl: 1000 })\n * \n * memoizedFib(40) // => 102334155\n * memoizedFib(40) // => 102334155 (cache hit)\n * setTimeout(() => memoizedFib(40), 1000) // => 102334155 (cache miss)\n * \n * // Cached values are exposed as the `cache` property.\n * memoizedFib.cache.get(\"40\") // => [value, timestamp]\n * memoizedFib.cache.set(\"40\", [1234, Date.now()])\n * memoizedFib.cache.clear()\n * \n * // This is the default way to create cache keys.\n * const defaultResolver = (...args: unknown[]) => JSON.stringify(args)\n * ```\n * @param func The function to have its output memoized.\n * @param options The options object with optional `resolver` and `ttl` parameters.\n * @param options.resolver - A function that determines the cache key for storing the result based on the arguments provided.\n * @param options.ttl - The time to live for the cache in milliseconds.\n * @template TFunc The type of the function to memoize.\n * @template Cache The type of the cache storage.\n * @returns Returns the new memoized function.\n */\n\nexport function memoize<TFunc extends GenericFunction<TFunc>, Cache extends Map<string, [ReturnType<TFunc>, number]>>(\n func: TFunc, options: { resolver?: (...args: Parameters<TFunc>) => string, ttl?: number; } = {}\n): TFunc & { cache: Cache } {\n const resolver = options.resolver ?? defaultResolver;\n const ttl = options.ttl;\n const cache = new Map() as Cache;\n\n const memoizedFunc = function (this: unknown, ...args: Parameters<TFunc>): ReturnType<TFunc> {\n const key = resolver(...args);\n if (cache.has(key)) {\n const [cacheResult, cacheTime] = cache.get(key)!;\n if (ttl === undefined || (Date.now() - cacheTime < ttl)) {\n return cacheResult;\n }\n }\n const result = func.apply(this, args);\n cache.set(key, [result, Date.now()]);\n return result;\n };\n\n memoizedFunc.cache = cache;\n return memoizedFunc as TFunc & { cache: Cache };\n} ", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { memoize } from \"@function/memoize.js\";\n\n/**\n * Memoizes the decorated function. \n * The cache key is either determined by the provided resolver or by the arguments used in the memoized function.\n * \n * **Options:**\n * - `resolver` A function that determines the cache key for storing the result based on the arguments provided.\n * - `ttl` sets the time to live for the cache in milliseconds. After `ttl` milliseconds, the next call to the memoized function will result in a cache miss.\n * \n * Look at {@link memoize} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decMemoize({ ttl: 1000 })\n * testMethod(a: number, b: number) {\n * return a + b;\n * }\n * }\n * const instance = new TestClass();\n * instance.testMethod(1, 2); // => 3\n * instance.testMethod(1, 2); // => 3 (cached)\n * \n * // After 1 second:\n * instance.testMethod(1, 2); // => 3 (cache miss)\n * ```\n * @param options The options object.\n * @param options.resolver - A function that determines the cache key for storing the result based on the arguments provided.\n * @param options.ttl - The time to live for the cache in milliseconds.\n */\n\nexport function decMemoize(options: Parameters<typeof memoize>[1] = {}) {\n return toDecorator(memoize)(options);\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Creates a function that invokes the given function once it's called more than `n` times. \n * Returns undefined until the minimum call count is reached.\n * \n * This function can be used as a decorator with {@link decMinCalls}.\n * @example\n * const caution = () => console.log(\"Caution!\");\n * const limitedCaution = minCalls(caution, 2);\n *\n * limitedCaution()\n * limitedCaution()\n * limitedCaution()\n * // => `caution` is invoked on the third call.\n * @param n The number of calls before the given function is invoked.\n * @param func The function to restrict.\n * @returns Returns the new restricted function.\n */\n\nexport function minCalls<TFunc extends GenericFunction<TFunc>>(func: TFunc, n: number) {\n let count = 1;\n return function (this: unknown, ...args: Parameters<TFunc>): ReturnType<TFunc> | undefined {\n if (count > n) {\n return func.apply(this, args);\n }\n count += 1;\n };\n}", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { minCalls } from \"@function/minCalls.js\";\n\n/** \n * Only invokes the decorated function after it's called more than `n` times.\n * \n * Look at {@link minCalls} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decMinCalls(2)\n * testMethod() {\n * return 1;\n * }\n * }\n * const instance = new TestClass();\n * instance.testMethod(); // => undefined\n * instance.testMethod(); // => undefined\n * instance.testMethod(); // => 1\n * ```\n * @param n The number of calls before the decorated function is invoked.\n */\n\nexport function decMinCalls(n: number) {\n return toDecorator(minCalls)(n);\n}", "import type { GenericFunction } from \"@type/GenericFunction.js\";\n\n/**\n * Generates a function that invokes the given function `func` at most once per every `wait` milliseconds. \n * The throttled function always returns the result of the last `func` invocation.\n * \n * This function can be used as a decorator with {@link decThrottle}.\n * @example\n * const throttled = throttle(() => console.log(\"Throttled!\"), 1000);\n * \n * throttled();\n * throttled();\n * // => \"Throttled!\" is logged once per second.\n * @param func The function to throttle.\n * @param wait The number of milliseconds to throttle invocations to.\n * @returns Returns the new throttled function.\n */\n\nexport function throttle<TFunc extends GenericFunction<TFunc>>(func: TFunc, wait: number): TFunc {\n let inThrottle = false;\n let lastResult: ReturnType<TFunc>;\n return function (this: unknown, ...args: Parameters<TFunc>) {\n if (!inThrottle) {\n lastResult = func.apply(this, args);\n inThrottle = true;\n setTimeout(() => (inThrottle = false), wait);\n }\n\n return lastResult;\n } as TFunc;\n}\n \n", "import { toDecorator } from \"@decorator/toDecorator.js\";\nimport { throttle } from \"@function/throttle.js\";\n\n/**\n * The decorated function is invoked at most once per every `wait` milliseconds.\n * \n * Look at {@link throttle} for the non-decorator version.\n * \n * @example\n * ```typescript\n * class TestClass {\n * @decThrottle(1000)\n * testMethod() {\n * console.log(\"Throttled!\");\n * }\n * }\n * \n * const instance = new TestClass();\n * instance.testMethod(); // => \"Throttled!\" is logged once per second.\n * instance.testMethod(); // nothing happens\n * ```\n * @param wait The number of milliseconds to wait between invocations.\n */\n\nexport function decThrottle(wait: number) {\n return toDecorator(throttle)(wait);\n}", "/**\n * Invokes a function `n` times, returning an array of the results of\n * each invocation.\n * \n * @example\n * times(index => console.log(\"Run\", index), 3)\n * // => \"Run 0\" | \"Run 1\" | \"Run 2\"\n * times(Math.random, 3)\n * // => [0.123, 0.456, 0.789]\n * times(() => 0, 4)\n * // => [0, 0, 0, 0]\n * @param n The number of times to invoke `func`.\n * @param func The function invoked per iteration.\n * @returns Returns an array of results.\n */\n\nexport function times<TInput>(func: (index: number) => TInput, n: number): TInput[] {\n const result: TInput[] = [];\n for (let i = 0; i < n; i++) {\n result.push(func(i));\n }\n return result;\n}", "\n/**\n * Calculates the sum of an array of numbers.\n * \n * Returns `NaN` if the input array is empty.\n * @example\n * sum([1, 2, 3, 4, 5]) // => 15\n * \n * @param numbers The input array of numbers\n * @returns The sum of the input array \n */\n\nexport function sum(numbers: readonly number[]): number {\n if (numbers.length === 0)\n return NaN;\n return numbers.reduce((total, current) => total + current, 0);\n}", "import { sum } from \"@number/sum.js\";\n\n/**\n * Calculates the average of an array of numbers\n * \n * Returns `NaN` if the input array is empty.\n * @example\n * average([1, 2, 3, 4, 5]) // => 3\n * \n * @param numbers The input array of numbers\n * @returns The average of the input array, or NaN if the input array is empty\n */\n\nexport function average(numbers: readonly number[]): number {\n if (numbers.length === 0)\n return NaN;\n return sum(numbers) / numbers.length;\n}", "/**\n * Calculates the median of an array of numbers\n * \n * Returns `NaN` if the input array is empty.\n * @example\n * median([1, 2, 3, 4, 5]) // => 3\n * median([1, 2, 3, 4, 5, 6]) // => 3.5\n * \n * @param numbers The input array of numbers\n * @returns The median of the input array\n */\n\nexport function median(numbers: readonly number[]): number {\n if (numbers.length === 0)\n return NaN;\n const sortedArray = [...numbers].sort((a, b) => a - b);\n const mid = Math.floor(sortedArray.length / 2);\n return sortedArray.length % 2 === 0 ? ((sortedArray[mid - 1] + sortedArray[mid]) / 2) : sortedArray[mid];\n}", "/**\n * Rounds a number to the given precision.\n *\n * @example\n * round(1.23456, 2); // => 1.23\n * round(1.235, 1); // => 1.2\n * round(1234.56); // => 1234.56\n * \n * @param number The number to be rounded.\n * @param precision The number of decimal places to round to. Defaults to 2.\n * @returns The rounded number.\n */\n\nexport function round(number: number, precision = 2): number {\n const factor = Math.pow(10, precision);\n return Math.round((number + Number.EPSILON) * factor) / factor;\n}", "import type { PlainObject } from \"@type/PlainObject.js\";\n\n/**\n * Checks if the value is a plain object.\n * \n * Refers to the {@link PlainObject} type.\n * @example\n * isPlainObject({}) // => true\n * isPlainObject({ a: 1 }) // => true\n * isPlainObject(null) // => false\n * isPlainObject('1') // => false\n * isPlainObject([]) // => false\n * isPlainObject(new Function()) // => false\n * isPlainObject(new Date()) // => false\n * @param value The value to check\n * @returns Boolean indicating if the value is a plain object\n */\n\nexport function isPlainObject(value: unknown): value is PlainObject {\n return value?.constructor === Object;\n}", "import type { GenericObject } from \"@type/GenericObject\";\nimport type { Paths } from \"type-fest\";\n\nimport { isPlainObject } from \"@validate/isPlainObject.js\";\n\ntype StringIfNever<Type> = [Type] extends [never] ? string : Type;\ntype PathOrString<TObj> = StringIfNever<Paths<TObj, { bracketNotation: true, maxRecursionDepth: 20 }>>;\n\n/**\n * Flattens an object into a single level object.\n * \n * @example\n * const obj = { a: { b: 2, c: [{ d: 3 }, { d: 4 }] } };\n * flatKeys(obj);\n * // => { 'a.b': 2, 'a.c[0].d': 3, 'a.c[1].d': 4 }\n * \n * @param obj The object to flatten.\n * @template TObj The type of the object to flatten.\n * @returns A new object with flattened keys.\n */\n\nexport function flatKeys<TObj extends GenericObject>(obj: TObj): Record<PathOrString<TObj>, unknown> {\n const flatObject: Record<string, unknown> = {};\n \n for (const [key, value] of Object.entries(obj)) {\n addToResult(key, value, flatObject);\n }\n \n return flatObject;\n}\n\nfunction addToResult(prefix: string, value: unknown, flatObject: Record<string, unknown>) {\n if (isPlainObject(value)) {\n const flatObj = flatKeys(value);\n for (const [flatKey, flatValue] of Object.entries(flatObj)) {\n flatObject[`${prefix}.${flatKey}`] = flatValue;\n }\n } else if (Array.isArray(value)) {\n for (const [index, element] of value.entries()) {\n addToResult(`${prefix}[${index}]`, element, flatObject);\n }\n\n } else {\n flatObject[prefix] = value;\n }\n}", "import type { ArrayMinLength } from \"@type/ArrayMinLength.js\";\nimport type { GenericObject } from \"@type/GenericObject\";\nimport type { PlainObject } from \"@type/PlainObject.js\";\n\nimport { isPlainObject } from \"@validate/isPlainObject.js\";\n\n/**\n * This function combines two or more objects into a single new object. Arrays and other types are overwritten.\n * \n * @example\n * // ---- Nested objects are merged ----\n * merge({ a: 1 }, { b: 2 }, { c: 3, d: { e: 4 } }) \n * // => { a: 1, b: 2, c: 3, d: { e: 4 } }\n *\n * // ---- Other types are overwritten ----\n * merge({ a: [1, 2] }, { a: [3, 4] })\n * // => { a: [3, 4] }\n * \n * merge({ a: 1 }, { a: \"Yes\" })\n * // => { a: \"Yes\" }\n * @param target The target object\n * @param sources The source objects\n * @template TTarget The type of the target object\n * @template TSources The type of the source objects\n * @returns A new merged object\n */\n\nexport function merge<TTarget extends GenericObject, TSources extends ArrayMinLength<GenericObject, 1>>(target: TTarget, ...sources: TSources): MergeDeepObjects<[TTarget, ...TSources]> {\n const targetCopy = { ...target };\n for (const source of sources) {\n for (const [key, value] of Object.entries(source)) {\n (targetCopy as PlainObject)[key] = isPlainObject(value) && isPlainObject(targetCopy[key]) \n ? merge(targetCopy[key], value) \n : value;\n }\n }\n return targetCopy as MergeDeepObjects<[TTarget, ...TSources]>; \n}\n\ntype OptionalPropertyNames<T> =\n { [K in keyof T]-?: (PlainObject extends { [P in K]: T[K] } ? K : never) }[keyof T];\n\ntype SpreadProperties<L, R, K extends keyof L & keyof R> =\n { [P in K]: L[P] | Exclude<R[P], undefined> };\n\ntype Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;\n\ntype SpreadTwo<L, R> = Id<\n& Pick<L, Exclude<keyof L, keyof R>>\n& Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>\n& Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>\n& SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>\n>;\n\ntype MergeDeepObjects<A extends readonly [...unknown[]]> = A extends [infer L, ...infer R] ?\n SpreadTwo<L, MergeDeepObjects<R>> : unknown;\n", "import type { GenericObject } from \"@type/GenericObject\";\n\n/**\n * Creates an object composed of the picked `object` properties.\n *\n * @example\n * const object = { 'a': 1, 'b': '2', 'c': 3 }\n *\n * pick(object, ['a', 'c'])\n * // => { 'a': 1, 'c': 3 }\n * @param object The source object.\n * @param keysToPick The property paths to pick.\n * @template TObj The type of the object.\n * @returns Returns the new object.\n */\n\nexport function pick<TObj extends GenericObject, Key extends keyof TObj>(object: TObj, keysToPick: Key[]): Pick<TObj, Key> {\n const result = {} as Pick<TObj, Key>;\n for (const key of keysToPick) {\n result[key] = object[key];\n }\n return result;\n}\n", "import type { GenericObject } from \"@type/GenericObject\";\n\nimport { difference } from \"@array/difference.js\";\nimport { pick } from \"@object/pick.js\";\n\n/**\n * Omit specified keys from an object\n *\n * @example\n * const obj = {a: 1, b: 2, c: 3};\n * omit(obj, ['a', 'b']);\n * // => {c: 3}\n *\n * @param object The object to filter\n * @param keysToOmit The keys to exclude from the returned object\n * @template TObj The type of the object\n * @returns - An object without the specified keys\n */\n\nexport function omit<TObj extends GenericObject, Key extends keyof TObj>(object: TObj, keysToOmit: Key[]): Omit<TObj, Key> {\n const allKeys = Object.keys(object);\n const filteredKeys = difference(allKeys, keysToOmit as string[]) as Exclude<keyof TObj, Key>[];\n\n return pick(object, filteredKeys);\n}", "import type { GenericObject } from \"@type/GenericObject\";\nimport type { PlainObject } from \"@type/PlainObject.js\";\nimport type { Call, Objects } from \"hotscript\";\n\nimport { isPlainObject } from \"@validate/isPlainObject.js\";\n\nconst validPathRegex = /^[^.[\\]]+(?:\\.[^.[\\]]+)*(?:\\[\\d+])*(?:\\.[^.[\\]]+(?:\\[\\d+])*)*$/;\nconst pathSplitRegex = /\\.|(?=\\[)/g;\nconst matchBracketsRegex = /[[\\]]/g;\n\n// eslint-disable-next-line @typescript-eslint/ban-types\ntype Paths<TObj> = Call<Objects.AllPaths, TObj> | string & {};\ntype UpdateObj<TObj extends PlainObject, TPath extends string, TVal> = Call<Objects.Update<TPath, TVal>, TObj>;\n\n/**\n * Sets the value at path of object. If a portion of path doesn’t exist, it’s created.\n * \n * @example\n * const obj = { a: { b: 2 } };\n * set(obj, 'a.c', 1);\n * // => { a: { b: 2, c: 1 } }\n * \n * // `[number]` can be used to access array elements\n * set(obj, 'a.c[0]', 'hello');\n * // => { a: { b: 2, c: ['hello'] } }\n * \n * // numbers with dots are treated as keys\n * set(obj, 'a.c.0.d', 'world');\n * // => { a: { b: 2, c: { 0: { d: 'world' } } }\n * \n * // supports numbers in keys\n * set(obj, 'a.e0.a', 1);\n * // => { a: { e0: { a: 1 } } }\n * \n * @param obj The object to modify.\n * @param path The path of the property to set.\n * @param value The value to set.\n * @template TObj The type of the object.\n * @template TPath The type of the object path.\n * @template TVal The type of the value to set.\n * @returns The modified object.\n */\n\nexport function set<TObj extends GenericObject, TPath extends Paths<TObj>, TVal>(obj: TObj, path: TPath, value: TVal): UpdateObj<TObj, TPath, TVal> {\n if (!validPathRegex.test(path))\n throw new Error(\"Invalid path, look at the examples for the correct format.\");\n\n const pathParts = (path as string).split(pathSplitRegex);\n let currentObj: PlainObject = obj;\n\n for (let index = 0; index < pathParts.length; index++) {\n const key = pathParts[index].replace(matchBracketsRegex, \"\");\n\n if (index === pathParts.length - 1) {\n currentObj[key] = value;\n break;\n }\n\n const nextElementInArray = pathParts[index + 1].startsWith(\"[\");\n const nextElementInObject = !nextElementInArray;\n\n if (nextElementInArray && !Array.isArray(currentObj[key])) {\n currentObj[key] = [];\n }\n\n if (nextElementInObject && !isPlainObject(currentObj[key])) {\n currentObj[key] = {};\n }\n \n currentObj = currentObj[key] as PlainObject;\n }\n\n return obj as UpdateObj<TObj, TPath, TVal>;\n}", "/**\n * A class managing an async function queue with limited concurrency (e.g., 10 functions with 3 running at a time).\n * \n * **Methods:**\n * - `add` - Adds an async function or array of functions to the queue. Returns a promise that resolves or rejects when the added function(s) finish.\n * - `clear` - Clears the queue.\n * - `pause` - Pauses the queue.\n * - `resume` - Resumes the queue. \n * - `getQueue` - Returns the current queue.\n * - `isPaused` - Returns whether the queue is paused.\n * - `done` - Returns a promise resolving when all added tasks are finished. Individual rejections don't affect the done() promise.\n * \n * @example\n * // Create a queue that can run 3 tasks concurrently\n * const queue = new Queue(3);\n * \n * queue.add(() => fetch('https://example.com'));\n * \n * queue.add(async () => {\n * const response = await fetch('https://example.com');\n * return response.json();\n * });\n * \n * await queue.done();\n * console.log(\"All tasks finished\");\n * \n * // Add an array of tasks to the queue and wait for them to resolve\n * await queue.add([\n * () => fetch('https://apple.com'),\n * () => fetch('https://microsoft.com')\n * ]);\n * // => [Response, Response]\n */\n\nexport class Queue {\n private running = 0;\n private maxConcurrent: number;\n private paused = false;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private queue: { asyncFn: () => Promise<any>, resolve: (value: any) => void, reject: (reason?: any) => void }[] = [];\n private finishedPromise: Promise<boolean> | undefined;\n private finishedResolver: (() => void) | undefined;\n\n /**\n * @constructor\n * @param maxConcurrent The maximum number of async functions to run concurrently.\n */\n constructor(maxConcurrent: number) {\n this.maxConcurrent = maxConcurrent;\n }\n\n /**\n * Add async functions or an array of async functions to the queue.\n * \n * @param asyncFn The async function(s) to add to the queue.\n * @returns A promise that resolves when the added function(s) finishes.\n */\n add<TProm, TAsyncFn extends () => Promise<TProm>>(asyncFn: TAsyncFn): Promise<TProm>;\n add<TProm, TAsyncFn extends () => Promise<TProm>>(asyncFn: TAsyncFn[]): Promise<TProm[]>;\n add<TProm, TAsyncFn extends () => Promise<TProm>>(asyncFn: TAsyncFn | TAsyncFn[]): Promise<TProm> | Promise<TProm[]> {\n if (Array.isArray(asyncFn)) {\n const promises = asyncFn.map((fn) => this.buildWaitingPromise(fn));\n return Promise.all(promises);\n } else {\n return this.buildWaitingPromise(asyncFn);\n } \n }\n\n private buildWaitingPromise<TProm>(asyncFn: () => Promise<TProm>): Promise<TProm> {\n return new Promise((resolve, reject) => {\n this.queue.push({ asyncFn, resolve, reject });\n this.run();\n });\n } \n\n private run() {\n while (this.queue.length > 0 && this.running < this.maxConcurrent && !this.paused) {\n this.running++;\n const queueElement = this.queue.shift()!;\n void queueElement.asyncFn()\n .then((result) => {\n queueElement.resolve(result);\n }).catch((error) => {\n queueElement.reject(error);\n }).finally(() => {\n this.running--;\n this.run();\n });\n }\n\n this.checkIfDone();\n }\n\n /** Removes all the tasks from the queue */\n clear() {\n for (const queueElement of this.queue) {\n queueElement.reject(new Error(\"Queue cleared\"));\n }\n this.queue = [];\n }\n\n /** Pauses the execution of the queue */\n pause() {\n this.paused = true;\n }\n\n /** Resumes the execution of the tasks in the queue */\n resume() {\n this.paused = false;\n this.run();\n }\n\n /** Return the tasks added to the queue */\n getQueue() {\n return this.queue.map((queueElement) => queueElement.asyncFn);\n }\n\n /** Returns whether the queue is paused */\n isPaused() {\n return this.paused;\n }\n\n /** Returns a shared promise that resolves when the queue is empty and all tasks have finished executing. */\n done() {\n if (this.queue.length === 0 && this.running === 0)\n return Promise.resolve(true);\n \n this.finishedPromise ??= new Promise(resolve => this.finishedResolver = () => resolve(true));\n return this.finishedPromise;\n }\n\n private checkIfDone() {\n if (this.queue.length === 0 && this.running === 0 && this.finishedResolver) {\n this.finishedResolver();\n this.finishedPromise = undefined;\n this.finishedResolver = undefined;\n }\n }\n}\n", "/**\n * Similar to [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race?retiredLocale=de) \n * but allows to specify how many promises to wait for.\n *\n * @example\n * const prom1 = Promise.resolve(1);\n * const prom2 = new Promise(resolve => setTimeout(resolve, 100, 2));\n * const prom3 = Promise.resolve(3);\n * \n * const firstTwo = await races(2, prom1, prom2, prom3);\n * // => [1, 3]\n * \n * @template TRes The type of the result of the promises.\n * @param waitFor The number of promises to wait for.\n * @param promises The promises to wait for.\n * @returns A promise that resolves an array of the results of the first n promises.\n */\n\nexport function races<TRes>(waitFor: number, ...promises: Promise<TRes>[]): Promise<TRes[]> {\n return new Promise((resolve, reject) => {\n if (promises.length < waitFor)\n waitFor = promises.length;\n\n const results: TRes[] = [];\n let resolved = 0;\n for (const promise of promises) {\n promise.then((value) => {\n results.push(value);\n resolved++;\n if (resolved >= waitFor) {\n resolve(results);\n }\n }).catch((error) => {\n reject(error);\n });\n }\n });\n}", "/**\n * Sleeps for the given amount of time.\n *\n * @example\n * await sleep(1000);\n * // => Waits for 1 second.\n * @param ms Amount of time to sleep in milliseconds.\n * @returns A promise that resolves after the given amount of time.\n */\nexport function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}", "/* eslint-disable no-await-in-loop */\nimport { sleep } from \"@promise/sleep.js\";\n\n/**\n * Retry a function until it succeeds or the maximum number of retries is reached.\n * \n * Default maxRetries: `5`. \n * Default backoff: `2^retries * 100ms` (100, 200, 400, 800, 1600, 3200, ...)\n *\n * @example\n * await retry(() => fetch('https://example.com'));\n * \n * // ---- Advanced example ----\n * const fetchSite = async () => {\n * const response = await fetch('https://example.com');\n * if(!response.ok)\n * throw new Error('Failed to fetch');\n * }\n * \n * const logger = (error: unknown, retry?: number) => console.log(\"Retrying\", retry, error);\n * \n * await retry(fetchSite, { maxRetries: 3, backoff: retries => retries * 1000, onRetry: logger });\n * // => Will retry 3 times with a 1 second delay between each retry.\n * // => Will log the error and retry number.\n * \n * @param func The function to retry.\n * @param options The options for the retry.\n * @param options.maxRetries The maximum number of retries. Defaults to `5`.\n * @param options.backoff The backoff function to use. Defaults to `2^retries * 100`.\n * @param options.onRetry The function to call when a retry is attempted. It will be called with the error and the attempt number.\n * @template TRes The type of the result of the function.\n * @returns A promise that resolves when the function succeeds.\n */\n\nexport async function retry<TRes>(\n func: () => Promise<TRes>, \n options?: { \n maxRetries?: number,\n backoff?: ((retries: number) => number),\n onRetry?: (error?: unknown, attempted?: number) => void\n }\n): Promise<TRes> {\n const backOffFn = options?.backoff ?? (retries => (2 ** retries) * 100);\n const maxRetries = options?.maxRetries ?? 5;\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const onRetry = options?.onRetry ?? (() => {});\n let retries = 0;\n let lastError: unknown;\n\n while (retries <= maxRetries) {\n try {\n if (retries > 0)\n onRetry(lastError, retries);\n return await func();\n } catch (error) {\n lastError = error;\n retries++;\n if (retries > maxRetries) {\n throw error;\n }\n await sleep(backOffFn(retries));\n }\n /* c8 ignore next 3 */\n }\n throw new Error(\"Retry terminated without success, this should never happen\");\n}", "/**\n * Returns a new promise that will reject with an error after a specified timeout. \n *\n * @example\n * try {\n * await timeout(fetch('https://example.com'), 1000);\n * } catch (error) {\n * console.log(error.message);\n * // => 'Promise timed out after 1000ms'\n * }\n * @template TRes - The type of the resolved value.\n * @param promise The promise to wrap.\n * @param timeout The timeout in milliseconds.\n * \n * @returns A new promise that will reject with an error after the specified timeout.\n */\nexport function timeout<TRes>(promise: Promise<TRes>, timeout: number): Promise<TRes> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Promise timed out after ${timeout}ms`));\n }, timeout);\n \n promise.then(\n (result) => {\n clearTimeout(timeoutId);\n resolve(result);\n },\n (error) => {\n clearTimeout(timeoutId);\n reject(error);\n }\n );\n });\n}", "/**\n * Attempts to execute a promise and returns an array with the result or error.\n * \n * This is useful for handling errors in async functions without try/catch blocks.\n * \n * @example\n * ```typescript\n * const [data, error] = await tryCatch(fetch('https://example.com/api'));\n * if (error)\n * console.error(`Error: ${error.message}`);\n * ```\n * @param promise A Promise to be executed.\n * @returns A Promise that resolves to an array containing the result or error. \n * If the Promise executes successfully, the array contains the result and a null error. \n * If the Promise throws an error, the array contains undefined for the result and the error object.\n *\n * @template TRes The type of the result.\n */\n\nexport async function tryCatch<TRes>(promise: Promise<TRes>): Promise<[TRes, undefined] | [undefined, Error]> {\n try {\n const data = await promise;\n return [data, undefined];\n } catch (error) {\n if (error instanceof Error)\n return [undefined, error];\n\n throw error;\n }\n}", "const wordsRegex = /(?:\\d*[a-z]+)|(?:[A-Z][a-z]+)|(?:\\d*[A-Z]+(?=[^a-z]|$))|\\d+/g;\n\n/**\n * Split a string into words. Can deal with camelCase, PascalCase & snake_case.\n * \n * @example\n * splitWords('camelCase')\n * // => ['camel', 'Case']\n * \n * splitWords('PascalCase')\n * // => ['Pascal', 'Case']\n * \n * splitWords('hello_world-123')\n * // => ['hello', 'world', '123']\n * \n * @param str The string to split into words.\n * @returns An array of words.\n */\n\nexport function splitWords(str: string): string[] {\n return str.match(wordsRegex) ?? [];\n}", "/**\n * Converts the first character of a string to upper case and the remaining to lower case.\n *\n * @example\n * capitalize('FRED')\n * // => 'Fred'\n * @param str The string to capitalize.\n * @returns Returns the capitalized string.\n */\n\nexport function capitalize(str: string): string {\n if (str === \"\") return \"\";\n return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();\n}\n", "const accentControlRegex = /[\\u0300-\\u036F]/g;\n\n/**\n * Deburrs a string by converting\n * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)\n * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)\n * letters to basic Latin letters and removing\n * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).\n *\n * @example\n * deburr('déjà vu')\n * // => 'deja vu'\n * @param str The string to deburr.\n * @returns Returns the deburred string.\n */\n\nexport function deburr(str: string): string {\n return str.normalize(\"NFD\").replace(accentControlRegex, \"\");\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { capitalize } from \"./capitalize.js\";\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts `string` to camelCase.\n *\n * @example\n * camelCase('Foo Bar')\n * // => 'fooBar'\n * camelCase('--foo-bar--')\n * // => 'fooBar'\n * camelCase('__FOO_BAR__')\n * // => 'fooBar'\n * @param str The string to convert.\n * @returns Returns the camel cased string.\n */\n\nexport function camelCase(str: string): string {\n if (str === \"\") return \"\";\n\n str = deburr(str);\n const words = splitWords(str);\n\n if (words.length === 0) return \"\";\n let camelCase = words[0].toLowerCase();\n\n // Start the loop from the second word\n for (let index = 1; index < words.length; index++) {\n const word = words[index];\n camelCase += capitalize(word);\n }\n\n return camelCase;\n}", "const charRegex = /[\"&'<>]/g;\nconst escapeChars = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n [\"'\", \"'\"],\n ['\"', \""\"]\n]);\n\n/**\n * Converts the characters `&`, `<`, `>`, `\"` and `'` in a string to their corresponding HTML entities.\n *\n * @example\n * escapeHtml('fred, barney, & pebbles')\n * // => 'fred, barney, & pebbles'\n * @param str The string to escape.\n * @returns Returns the escaped string.\n */\n\nexport function escapeHtml(str: string): string {\n return str.replace(charRegex, char => escapeChars.get(char)!);\n}\n", "const escapeCharsRegex = /[$()*+.?[\\\\\\]^{|}]/g;\n\n/**\n * Escapes the `RegExp` special characters `^`, `$`, `\\`, `.`, `*`, `+`,\n * `?`, `(`, `)`, `[`, `]`, `{`, `}`, and `|` in a string.\n *\n * @example\n * escapeRegExp('[moderndash](https://moderndash.io/)')\n * // => '\\[moderndash\\]\\(https://moderndash\\.io/\\)'\n * @param str The string to escape.\n * @returns Returns the escaped string.\n */\n\nexport function escapeRegExp(str: string): string {\n return str.replace(escapeCharsRegex, \"\\\\$&\");\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts a string to kebab-case.\n *\n * @example\n * kebabCase('Foo Bar')\n * // => 'foo-bar'\n * kebabCase('fooBar')\n * // => 'foo-bar'\n * kebabCase('__FOO_BAR__')\n * // => 'foo-bar'\n * \n * @param str The string to convert.\n * @returns Returns the kebab cased string.\n */\n\nexport function kebabCase(str: string): string {\n if (str === \"\") return \"\";\n \n str = deburr(str);\n const words = splitWords(str);\n let kebabCase = \"\";\n for (const word of words) {\n kebabCase += word.toLowerCase() + \"-\";\n }\n return kebabCase.slice(0, -1);\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { capitalize } from \"./capitalize.js\";\nimport { deburr } from \"./deburr.js\";\n\n\n/**\n * Converts a string to PascalCase.\n *\n * @example\n * pascalCase('Foo Bar')\n * // => 'FooBar'\n * pascalCase('fooBar')\n * // => 'FooBar'\n * pascalCase('__FOO_BAR__')\n * // => 'FooBar'\n * \n * @param str The string to convert.\n * @returns Returns the pascal cased string.\n */\n\nexport function pascalCase(str: string): string {\n if (str === \"\") return \"\";\n \n str = deburr(str);\n const words = splitWords(str);\n let pascalCase = \"\";\n for (const word of words) {\n pascalCase += capitalize(word);\n }\n return pascalCase;\n}\n", "/**\n * Replaces the last occurrence of a string.\n * \n * @example\n * ```typescript\n * replaceLast(\"Foo Bar Bar\", \"Bar\", \"Boo\"); \n * // => \"Foo Bar Boo\"\n * ```\n * \n * @param str The string to replace in.\n * @param searchFor The string to search for.\n * @param replaceWith The string to replace with.\n * @returns The replaced string.\n */\n\nexport function replaceLast(str: string, searchFor: string, replaceWith: string): string {\n const index = str.lastIndexOf(searchFor);\n\n if (index === -1)\n return str;\n\n return str.slice(0, index) + replaceWith + str.slice(index + searchFor.length);\n}", "import { splitWords } from \"@string/splitWords\";\n\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts a string to snake_case.\n *\n * @example\n * snakeCase('Foo Bar')\n * // => 'foo_bar'\n * snakeCase('fooBar')\n * // => 'foo_bar'\n * snakeCase('--FOO-BAR--')\n * // => 'foo_bar'\n * snakeCase('foo2bar')\n * // => 'foo_2_bar'\n * \n * @param str The string to convert.\n * @returns Returns the snake cased string.\n */\n\nexport function snakeCase(str: string): string {\n if (str === \"\") return \"\";\n \n str = deburr(str);\n const words = splitWords(str);\n let snakeCase = \"\";\n for (const word of words) {\n if (snakeCase.length > 0) {\n snakeCase += \"_\";\n }\n snakeCase += word.toLowerCase();\n }\n return snakeCase;\n}\n", "import { splitWords } from \"@string/splitWords\";\n\nimport { capitalize } from \"./capitalize.js\";\nimport { deburr } from \"./deburr.js\";\n\n/**\n * Converts a string to Title Case.\n *\n * @example\n * titleCase('--foo-bar--')\n * // => 'Foo Bar'\n * titleCase('fooBar')\n * // => 'Foo Bar'\n * titleCase('__FOO_BAR__')\n * // => 'Foo Bar'\n * titleCase('HélloWorld')\n * // => 'Hello World'\n * @param str The string to convert.\n * @returns Returns the title cased string.\n */\n\nexport function titleCase(str: string): string {\n if (str === \"\") return \"\";\n\n str = deburr(str);\n const words = splitWords(str);\n let titleCase = \"\";\n for (const word of words) {\n titleCase += capitalize(word) + \" \";\n }\n return titleCase.trimEnd();\n}\n", "/**\n * Trim the string from the left and right by the given characters\n * \n * *Use the native [trim](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim) method if you want to trim whitespace.*\n * @example\n * ```ts\n * trim('$$abc$', '$') // => 'abc'\n * trim('!!abc_!', '_!') // => 'abc'\n * ```\n * @param str The string to trim\n * @param chars The characters to trim\n * @returns The trimmed string\n */\n\nexport function trim(str: string, chars: string): string {\n let startIndex = 0;\n while (startIndex < str.length && chars.includes(str[startIndex])) {\n startIndex++;\n }\n \n let endIndex = str.length - 1;\n while (endIndex >= startIndex && chars.includes(str[endIndex])) {\n endIndex--;\n }\n \n return str.slice(startIndex, endIndex + 1);\n}", "/**\n * Trims the specified characters from the end of the string.\n * \n * *Use the native [trimEnd](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd) method if you want to trim whitespace.*\n * @example\n * ```ts\n * trimEnd('abc$$$', '$') // => 'abc'\n * trimEnd('abc_!!_', '_!') // => 'abc'\n * ```\n * @param str The string to trim\n * @param chars The characters to trim\n * @returns The trimmed string\n */\n\nexport function trimEnd(str: string, chars: string): string {\n let lastIndex = str.length - 1;\n while (lastIndex >= 0 && chars.includes(str[lastIndex])) {\n lastIndex--;\n }\n return str.slice(0, lastIndex + 1);\n\n}", "/**\n * Trims specified characters from the start of the string.\n * \n * *Use the native [trimStart](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart) method if you want to trim whitespace.*\n * @example\n * ```ts\n * trimStart('$$$abc', '$') // => 'abc'\n * trimStart('_!!_abc', '_!') // => 'abc'\n * ```\n * @param str The string to trim\n * @param chars The characters to trim\n * @returns The trimmed string\n */\n\nexport function trimStart(str: string, chars: string): string {\n let startIndex = 0;\n while (startIndex < str.length && chars.includes(str[startIndex])) {\n startIndex++;\n }\n return str.slice(startIndex);\n}", "/**\n * Truncates a string if it's longer than the given maximum length.\n * The last characters of the truncated string are replaced with the ellipsis\n * string which defaults to \"...\".\n *\n * @example\n * truncate(\"Hello, world!\", { length: 5 })\n * // => \"Hello...\"\n * \n * truncate(\"Hello, world!\", { length: 5, ellipsis: \" [...]\" })\n * // => \"Hello [...]\"\n * \n * truncate(\"Hello, world!\", { length: 5, separator: \" \" })\n * // => \"Hello, ...\"\n * \n * @param str The string to truncate\n * @param options The options object\n * @param options.length The maximum string length (default: 30)\n * @param options.ellipsis The string to indicate text is omitted (default: \"...\")\n * @param options.separator The separator pattern to truncate to (default: none)\n * @returns The truncated string\n */\n\nexport function truncate(str: string, options?: { length?: number; ellipsis?: string; separator?: string }): string {\n const { length = 30, ellipsis = \"...\", separator } = options ?? {};\n if (str.length <= length) return str;\n\n const end = length - ellipsis.length;\n\n if (end < 1) \n return ellipsis;\n\n // Actually long enough to truncate the string\n let truncated = str.slice(0, end);\n\n if (separator) {\n const sepIndex = truncated.lastIndexOf(separator);\n if (sepIndex > -1) {\n truncated = truncated.slice(0, sepIndex);\n }\n }\n\n return truncated + ellipsis;\n}\n", "const htmlEntitiesRegex = /&(?:amp|lt|gt|quot|#39);/g;\nconst entityMap = new Map([\n [\"&\", \"&\"],\n [\"<\", \"<\"],\n [\">\", \">\"],\n [\""\", '\"'],\n [\"'\", \"'\"]\n]);\n\n/**\n * Converts the HTML entities `&`, `<`, `>`, `"` and `'`\n * in a string to their corresponding characters.\n *\n * @example\n * unescapeHtml('fred, barney, & pebbles')\n * // => 'fred, barney, & pebbles'\n * @param str The string to unescape.\n * @returns Returns the unescaped string.\n */\n\nexport function unescapeHtml(str: string): string {\n return str.replace(htmlEntitiesRegex, (entity: string) => entityMap.get(entity)!);\n}\n", "\n/**\n * Checks if a value is empty.\n * \n * Supports: strings, arrays, objects, maps, sets, typed arrays.\n * @example\n * isEmpty(null)\n * // => true\n *\n * isEmpty({})\n * // => true\n *\n * isEmpty(\"\")\n * // => true\n *\n * isEmpty([1, 2, 3])\n * // => false\n *\n * isEmpty('abc')\n * // => false\n *\n * isEmpty({ 'a': 1 })\n * // => false\n * @param value The value to check.\n * @returns Returns `true` if `value` is empty, else `false`.\n */\n\nexport function isEmpty(value: string | object | null | undefined): boolean {\n if (value === null || value === undefined)\n return true;\n\n if (typeof value === \"string\" || Array.isArray(value))\n return value.length === 0;\n\n if (value instanceof Map || value instanceof Set)\n return value.size === 0;\n\n if (ArrayBuffer.isView(value))\n return value.byteLength === 0;\n\n if (typeof value === \"object\")\n return Object.keys(value).length === 0;\n\n return false;\n}\n", "/* eslint-disable complexity */\nimport type { PlainObject } from \"@type/PlainObject.js\";\n\nimport { isPlainObject } from \"./isPlainObject.js\";\n\ntype TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;\n\n/**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n * \n * Supports: primitives, arrays, objects, dates, regexes, maps, sets, buffers, typed arrays\n * \n * @example\n * const object = { a: { b: 2 } };\n * const other = { a: { b: 2 } };\n *\n * isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n * @param a The value to compare.\n * @param b The other value to compare.\n * @returns Returns `true` if the values are equivalent, else `false`.\n */\n\nexport function isEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n \n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b))\n return isSameArray(a, b);\n\n if (a instanceof Date && b instanceof Date)\n return a.getTime() === b.getTime();\n\n if (a instanceof RegExp && b instanceof RegExp)\n return a.toString() === b.toString();\n\n if (isPlainObject(a) && isPlainObject(b))\n return isSameObject(a, b);\n\n if (a instanceof ArrayBuffer && b instanceof ArrayBuffer)\n return dataViewsAreEqual(new DataView(a), new DataView(b));\n\n if (a instanceof DataView && b instanceof DataView)\n return dataViewsAreEqual(a, b);\n\n if (isTypedArray(a) && isTypedArray(b)) {\n if (a.byteLength !== b.byteLength) return false;\n return isSameArray(a, b);\n }\n\n return false;\n}\n\nfunction isSameObject(a: PlainObject, b: PlainObject) {\n // check if the objects have the same keys\n const keys1 = Object.keys(a);\n const keys2 = Object.keys(b);\n if (!isEqual(keys1, keys2)) return false;\n\n // check if the values of each key in the objects are equal\n for (const key of keys1) {\n if (!isEqual(a[key], b[key])) return false;\n }\n\n // the objects are deeply equal\n return true;\n}\n\nfunction isSameArray(a: unknown[] | TypedArray, b: unknown[] | TypedArray) {\n if (a.length !== b.length) return false;\n return a.every((element, index) => isEqual(element, b[index]));\n}\n\nfunction dataViewsAreEqual(a: DataView, b: DataView) {\n if (a.byteLength !== b.byteLength) return false;\n for (let offset = 0; offset < a.byteLength; offset++) {\n if (a.getUint8(offset) !== b.getUint8(offset)) return false;\n }\n return true;\n}\n\nfunction isTypedArray(value: unknown): value is TypedArray {\n return ArrayBuffer.isView(value) && !(value instanceof DataView);\n}", "/**\n * Checks if given string is a valid URL\n * \n * @deprecated \n * **Deprecated: Use the native \"URL.canParse\" method instead.**\n * \n * @example\n * isUrl('https://google.com')\n * // => true\n * isUrl('google.com')\n * // => false\n * @param str The string to check.\n * @returns Returns `true` if given string is a valid URL, else `false`.\n */\n\nexport function isUrl(str: string): boolean {\n try {\n new URL(str);\n return true;\n } catch {\n return false;\n }\n}", "import type { Definition, Metadata, MethodDefinition, UIRequest } from '@devvit/protos';\nimport { CustomPostDefinition, UIResponse } from '@devvit/protos';\nimport type { Config } from '@devvit/shared-types/Config.js';\n\nimport { Devvit } from '../Devvit.js';\nimport { BlocksHandler } from './blocks/handler/BlocksHandler.js';\nimport { extendDevvitPrototype } from './helpers/extendDevvitPrototype.js';\nimport {\n makeUpgradeAppComponent,\n parseDevvitUserAgent,\n shouldShowUpgradeAppScreen,\n} from './upgrade-app-shim.js';\n\nconst FeatureUnavailable: Devvit.BlockComponent = () => (\n <vstack alignment=\"center middle\" width=\"100%\" height=\"100%\">\n <text>This feature is not available yet</text>\n </vstack>\n);\n\n/**\n * Extend me to add new surfaces to Devvit.\n */\nconst UIComponentBindings: [Definition, MethodDefinition, JSX.ComponentFunction][] = [\n [\n CustomPostDefinition,\n CustomPostDefinition.methods['renderPostContent'],\n (_props: {}, context: Devvit.Context) => Devvit.customPostType?.render(context) ?? null,\n ],\n [\n CustomPostDefinition,\n CustomPostDefinition.methods['renderPostComposer'],\n (_props: {}, _context: Devvit.Context) => <FeatureUnavailable />,\n ],\n];\n\nexport function makeHandler(\n component: JSX.ComponentFunction\n): (req: UIRequest, metadata: Metadata) => Promise<UIResponse> {\n return async (req: UIRequest, metadata: Metadata) => {\n const parsedUserAgent = parseDevvitUserAgent(metadata['devvit-user-agent']?.values?.[0] ?? '');\n if (parsedUserAgent && shouldShowUpgradeAppScreen(parsedUserAgent)) {\n const handler = new BlocksHandler(makeUpgradeAppComponent(parsedUserAgent.platform));\n return UIResponse.fromJSON(await handler.handle(req, metadata));\n }\n\n const handler = new BlocksHandler(component);\n return UIResponse.fromJSON(await handler.handle(req, metadata));\n };\n}\n\nexport function registerUIRequestHandlers(config: Config): void {\n for (const [definition, method, component] of UIComponentBindings) {\n config.provides(definition);\n extendDevvitPrototype(method.name, makeHandler(component));\n }\n}\n", "import {\n type Effect,\n type Metadata,\n type UIEvent,\n UIEventScope,\n type UIRequest,\n type UIResponse,\n} from '@devvit/protos';\nimport { isCircuitBreaker } from '@devvit/shared-types/CircuitBreaker.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { BlockElement } from '../../../Devvit.js';\nimport type { ReifiedBlockElement, ReifiedBlockElementOrLiteral } from '../BlocksReconciler.js';\nimport { BlocksTransformer } from '../BlocksTransformer.js';\nimport type { EffectEmitter } from '../EffectEmitter.js';\nimport { ContextBuilder } from './ContextBuilder.js';\nimport { _isTombstone, RenderContext } from './RenderContext.js';\nimport type { BlocksState, Hook, HookParams, HookSegment, Props } from './types.js';\nimport { RenderInterruptError } from './types.js';\n\n/**\n * This can be a global/singleton because render is synchronous.\n *\n * If you want to use this from somewhere else, please consider using one of the\n * functions like isRendering or registerHook, and then try to add additional\n * functions here if needed. Don't use this directly.\n */\nexport let _activeRenderContext: RenderContext | null = null;\n\nexport function useEffectEmitter(): EffectEmitter {\n if (!_activeRenderContext) {\n throw new Error('Hooks can only be declared at the top of a component.');\n }\n return _activeRenderContext;\n}\n\nexport function isRendering(): boolean {\n return _activeRenderContext !== null;\n}\n\nfunction _structuredClone<T extends JSONValue>(obj: T): T {\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * See [HookSegment](./types.ts) for more information.\n */\nexport function assertValidNamespace(input: string): void {\n const regex = /[-.]/;\n const valid = input !== '' && !regex.test(input);\n\n if (!valid) {\n throw new Error(\n `Hook with namespace '${input}' is invalid. Hook namespaces cannot be empty string or contain dashes/dots because they are used as delimiters internally. Please update the hook namespace and try again.`\n );\n }\n}\n\ntype RegisterHookOptions<H extends Hook> = HookSegment & {\n /**\n * Factory function to build the hook. Only called once for the entire lifecycle of a\n * BlocksHandler.handle() invocation.\n */\n initializer: (p: HookParams) => H;\n};\n\n/**\n * This can get called multiple times in a given render. Initialize is only called once!\n *\n * This is the recommended low-level interface for creating hooks like useState or useAsync.\n *\n * Practically, this initializes your hook if it doesn't already exist, and makes sure\n * that its state gets all sync'd up.\n *\n * @param HookSegment -- A name for this hook. This is used to dedupe hooks.\n * @param initializer\n * factory for building this hook\n * @returns\n */\nexport function registerHook<H extends Hook>({\n initializer,\n ...hookSegment\n}: RegisterHookOptions<H>): H {\n if (!_activeRenderContext) {\n throw new Error(\n \"Hooks can only be declared at the top of a component. You cannot declare hooks outside of components or inside of event handlers. It's almost always a mistake to declare hooks inside of loops or conditionals.\"\n );\n }\n\n assertValidNamespace(hookSegment.namespace);\n\n const hookId = _activeRenderContext.nextHookId(hookSegment);\n const context = _activeRenderContext;\n const params: HookParams = {\n hookId,\n invalidate: () => {\n context._changed[hookId] = true;\n context._state[hookId] = context?._hooks[hookId]?.state;\n },\n context: _activeRenderContext,\n };\n const fromNull =\n _activeRenderContext._state[hookId] === undefined ||\n _isTombstone(_activeRenderContext._state[hookId]);\n _activeRenderContext._hooks[hookId] = _activeRenderContext._hooks[hookId] ?? initializer(params);\n const hook: H = _activeRenderContext._hooks[hookId] as H;\n\n if (!fromNull) {\n hook.state = _activeRenderContext._state[hookId];\n }\n hook.onStateLoaded?.();\n if (fromNull && hook.state !== undefined && hook.state !== null) {\n params.invalidate();\n }\n return hook;\n}\n\nexport let _latestBlocksHandler: BlocksHandler | null = null;\n\n/**\n * Limit the number of render cycles to prevent infinite loops.\n */\nconst MaxIterations = 128;\n\n/**\n * Replacing BlocksReconciler, the model is now less of a \"reconciliation\", and more\n * of a handling a request/response lifecycle.\n *\n */\nexport class BlocksHandler {\n #root: JSX.ComponentFunction;\n #contextBuilder: ContextBuilder = new ContextBuilder();\n #blocksTransformer: BlocksTransformer = new BlocksTransformer(\n () => this._latestRenderContext?.devvitContext?.assets\n );\n _latestRenderContext: RenderContext | null = null;\n\n constructor(root: JSX.ComponentFunction) {\n if (this.#debug) console.debug('[blocks] BlocksHandler v1');\n this.#root = root;\n _latestBlocksHandler = this;\n }\n\n async handle(request: UIRequest, metadata: Metadata): Promise<UIResponse> {\n const context = new RenderContext(request, metadata);\n context.devvitContext = this.#contextBuilder.buildContext(context, request, metadata);\n\n let blocks;\n\n /**\n * Events on the main queue must be handled in order, so that state is updated in the correct order. Events\n * on other queues can be handled in parallel, because they only emit effects.\n *\n * There is an optimization here to process SendEventEffects locally, instead of letting them bubble up to the\n * platform. This prevents a round trip to the platform for every event.\n *\n * This also means we need to respect execution queues here, and not just in the platform.\n */\n const drop = request.events.length - MaxIterations;\n let eventsToProcess = request.events;\n if (drop > 0) {\n eventsToProcess = [];\n\n // Filter out old realtime messages until drop is met.\n {\n let dropped = 0;\n for (const ev of request.events) {\n if (dropped < drop && ev.realtimeEvent) dropped++;\n else eventsToProcess.push(ev);\n }\n }\n\n // Filter out _any_ old remaining messages until drop is met.\n while (eventsToProcess.length > MaxIterations) eventsToProcess.shift();\n\n console.warn(`dropped ${drop} events`);\n }\n const noEvents = !eventsToProcess.length;\n const isMainQueue = noEvents || eventsToProcess.some((e) => !e.async);\n\n const isBlockingSSR = eventsToProcess.some((e) => e.blocking);\n\n let changed: { [hookID: string]: true };\n let progress:\n | {\n _state: BlocksState;\n _effects: { [key: string]: Effect };\n }\n | undefined;\n let remaining: UIEvent[] = [...eventsToProcess];\n\n /**\n * When purely rendering, we now add one synthetic event. This enables us to loop over the events that an initial render may generate\n * in an async world. This is a bit of a hack, but it's the simplest way to handle this.\n */\n if (eventsToProcess.length === 0) {\n eventsToProcess.push({\n scope: UIEventScope.ALL,\n });\n }\n\n if (this.#debug) console.debug('[blocks] starting processing events');\n\n let iterations = 0;\n while (eventsToProcess.length > 0) {\n if (iterations++ > MaxIterations) {\n throw new Error(`Exceeded maximum iterations of ${MaxIterations}`);\n }\n if (this.#debug)\n console.debug('[blocks] processing events loop iteration', eventsToProcess.length);\n /**\n * A concurrently executable batch is a set of events that can be executed in parallel. This either one main queue event,\n * or any number of other queue events.\n */\n const batch = [];\n if (!eventsToProcess[0].async) {\n batch.push(eventsToProcess.shift()!);\n } else {\n while (eventsToProcess[0]?.async) {\n batch.push(eventsToProcess.shift()!);\n }\n }\n if (!batch.length) throw Error('batch must have at least one event');\n try {\n if (batch[0].async) {\n const stateCopy = _structuredClone(context._state);\n await this.#handleAsyncQueues(context, ...batch);\n // enforce that state updates are only allowed on the main queue.\n context._state = stateCopy;\n } else {\n await this.#handleMainQueue(context, ...batch);\n }\n } catch (e) {\n /**\n * Guard clause, retries are only applicable for circuit breakers. If we're not a circuit breaker, we need to throw the error.\n */\n if (!isCircuitBreaker(e)) {\n throw e;\n }\n\n if (this.#debug) console.debug('[blocks] caught in handler', e);\n context._latestRenderContent = undefined;\n /**\n * If we have a progress, we can recover from an error by rolling back to the last progress, and then letting the\n * remaining events be reprocessed.\n */\n if (progress) {\n context._state = progress._state;\n context._changed = changed!;\n context._effects = progress._effects;\n\n const requeueable = remaining.map((e) => {\n const requeueEvent = { ...e };\n requeueEvent.retry = true;\n return requeueEvent;\n });\n context.addToRequeueEvents(...requeueable);\n break;\n } else {\n if (!isCircuitBreaker(e)) {\n console.error('[blocks] unhandled error in handler', e);\n }\n throw e;\n }\n }\n\n if (this.#debug) console.debug('[blocks] remaining events', context._requeueEvents);\n const remainingRequeueEvents: UIEvent[] = [];\n for (const event of context._requeueEvents) {\n if (!isMainQueue && !event.async) {\n if (this.#debug)\n console.debug(\n '[blocks] NOT reprocessing event in BlocksHandler, sync mismatch A',\n event\n );\n // We're async, this is a main queue event. We need to send it back to the platform to let\n // the platform synchronize it.\n remainingRequeueEvents.push(event);\n continue;\n }\n if (isMainQueue && event.async && !isBlockingSSR) {\n if (this.#debug)\n console.debug(\n '[blocks] NOT reprocessing event in BlocksHandler, sync mismatch B',\n event\n );\n // We're main queue, and this is an async event. We're not in SSR mode, so let's prioritize\n // returning control quickly to the platform so we don't block event loops.\n remainingRequeueEvents.push(event);\n continue;\n }\n if (this.#debug) console.debug('[blocks] reprocessing event in BlocksHandler', event);\n eventsToProcess.push(event);\n }\n context._requeueEvents = remainingRequeueEvents; //\n\n /**\n * If we're going back through this again, we need to capture the progress, and the remaining events.\n */\n if (eventsToProcess.length > 0) {\n changed = { ...context._changed };\n progress = {\n _state: _structuredClone(context._state),\n _effects: { ...context._effects },\n };\n remaining = [...eventsToProcess];\n }\n } // End of while loop\n\n // Rendering only happens on the main queue.\n if (isMainQueue) {\n if (!context._latestRenderContent) {\n context._latestRenderContent = this.#renderRoot(\n this.#root,\n context.request.props ?? {},\n context\n );\n }\n const tags = context._latestRenderContent;\n\n if (tags) {\n blocks = this.#blocksTransformer.createBlocksElementOrThrow(tags);\n blocks = this.#blocksTransformer.ensureRootBlock(blocks);\n }\n }\n\n return {\n state: context._changedState,\n effects: context.effects,\n blocks: blocks,\n events: context._requeueEvents,\n };\n }\n\n get #debug(): boolean {\n return !!this._latestRenderContext?.devvitContext.debug.blocks;\n }\n\n #loadHooks(context: RenderContext, ..._events: UIEvent[]): void {\n context._hooks = {};\n\n this.#renderRoot(this.#root, context.request.props ?? {}, context);\n }\n\n /**\n * These can all run in parallel, because they only emit effects\n */\n async #handleAsyncQueues(context: RenderContext, ...batch: UIEvent[]): Promise<void> {\n this.#loadHooks(context, ...batch);\n\n await Promise.all(\n batch.map(async (event) => {\n if (!event.async) {\n throw new Error(\n \"You can't mix main and other queues in one batch. This is likely a platform bug. Please file an issue in the Discord for someone to help! https://discord.com/channels/1050224141732687912/1115441897079574620\"\n );\n }\n await this.#attemptHook(context, event);\n })\n );\n }\n\n async #attemptHook(context: RenderContext, event: UIEvent): Promise<void> {\n const hook = context._hooks[event.hook!];\n if (hook?.onUIEvent) {\n try {\n await hook.onUIEvent(event, context);\n } catch (e) {\n if (isCircuitBreaker(e)) {\n if (this.#debug) {\n console.error('Server call required', e);\n }\n } else {\n console.error('Error in event handler', e);\n }\n\n throw e;\n }\n } else {\n await context.handleUndeliveredEvent(event);\n }\n }\n\n async #handleMainQueue(context: RenderContext, ...batch: UIEvent[]): Promise<void> {\n // We need to handle events in order, so that the state is updated in the correct order.\n for (const event of batch) {\n if (this.#debug) console.log('[blocks] handling main queue event', event);\n if (this.#debug) console.log('[blocks] before', context._state);\n this.#loadHooks(context, event);\n await this.#attemptHook(context, event);\n if (this.#debug) console.log('[blocks] after', context._state);\n }\n // We need this to process possible unmounts given the most recent event\n this.#loadHooks(context);\n }\n\n #renderRoot(\n component: JSX.ComponentFunction,\n props: Props,\n context: RenderContext\n ): ReifiedBlockElement | undefined {\n if (this.#debug) console.debug('[blocks] renderRoot');\n context._generated = {};\n _activeRenderContext = context;\n this._latestRenderContext = context;\n try {\n const roots = this.#render(component, props, context);\n\n if (roots.length !== 1) {\n throw new Error(\n `There can only be one root element, received: ${roots.length}. Please wrap these elements in a <vstack></vstack> or <hstack></hstack> to continue. Fragments cannot be used as a root element.`\n );\n }\n const root = roots[0];\n\n // By the time it gets to this point the promise has been serialized.\n // That's why the check is not root instanceof Promise\n if (root === '[object Promise]') {\n throw new Error(\n `Root elements cannot be async. To use data from an async endpoint, please use \"const [data] = useState(async () => {/** your async code */})\".`\n );\n }\n\n if (typeof root === 'string') {\n throw new Error(\n `The root element must return a valid block, not a string. Try wrapping the element in a <text>your content</text> tag to continue.`\n );\n }\n\n context._latestRenderContent = root;\n\n return root;\n } catch (e) {\n if (e instanceof RenderInterruptError) {\n return undefined;\n } else {\n throw e;\n }\n } finally {\n _activeRenderContext = null;\n }\n }\n\n #render(\n component: JSX.ComponentFunction,\n props: Props,\n context: RenderContext\n ): ReifiedBlockElementOrLiteral[] {\n // Anonymous functions don't have a name\n // use || instead of ?? due to empty string\n context.push({ namespace: component.name || 'anonymous', ...props });\n try {\n const element = component(props, context.devvitContext);\n\n if (element instanceof Promise) {\n throw new Error(\n `Components (found: ${component.name ?? 'unknown component'}) cannot be async. To use data from an async endpoint, please use \"const [data] = useState(async () => {/** your async code */})\".`\n );\n }\n\n return this.#renderElement(element, context);\n } finally {\n context.pop();\n }\n }\n\n #renderList(list: JSX.Element[], context: RenderContext): ReifiedBlockElementOrLiteral[] {\n list = list.flat(Infinity);\n return list.flatMap((e, i) => {\n if (e && typeof e === 'object' && 'props' in e) {\n if (!e.props?.key) {\n e.props = e.props ?? {};\n e.props.key = `${i}`;\n }\n }\n return this.#renderElement(e, context);\n });\n }\n\n #renderElement(element: JSX.Element, context: RenderContext): ReifiedBlockElementOrLiteral[] {\n if (Array.isArray(element)) {\n return this.#renderList(element, context);\n } else if (isBlockElement(element)) {\n // This means the element is a fragment\n if (element.type === undefined) {\n try {\n context.push({ namespace: 'fragment', ...element.props });\n // Since it's a fragment, don't reifyProps because you can't have props on a fragment\n return this.#renderList(element.children, context);\n } finally {\n context.pop();\n }\n } else if (typeof element.type === 'function') {\n const propsWithChildren = { ...element.props, children: element.children.flat(Infinity) };\n return this.#render(element.type, propsWithChildren, context);\n } else {\n try {\n context.push({ namespace: element.type, ...element.props });\n const reifiedChildren = this.#renderList(element.children, context);\n const reifiedProps = this.#reifyProps(element.props ?? {});\n\n return [{ type: element.type, children: reifiedChildren, props: reifiedProps }];\n } finally {\n context.pop();\n }\n }\n } else {\n return [(element ?? '').toString()];\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n #reifyProps(props: { [key: string]: any }): { [key: string]: JSONValue } {\n const reifiedProps: { [key: string]: JSONValue } = {};\n for (const key in props) {\n if (typeof props[key] === 'undefined') {\n // skip\n } else if (typeof props[key] === 'function') {\n const hook = registerHook({\n namespace: key,\n key: false,\n initializer: ({ hookId }) => ({\n hookId,\n state: null,\n onUIEvent: (event) => {\n if (event.userAction) {\n return props[key](event.userAction.data);\n } else if (event.webView?.postMessage) {\n // Fallback to deprecated message field for mobile client backwards compatibility\n const message = event.webView.postMessage.jsonString\n ? JSON.parse(event.webView.postMessage.jsonString)\n : event.webView.postMessage.message;\n return props[key](message);\n }\n },\n }),\n });\n reifiedProps[key] = hook.hookId;\n if ('captureHookRef' in props[key]) {\n props[key].captureHookRef();\n }\n } else {\n // push value through the JSON parser to filter incompatible types\n const value = JSON.parse(JSON.stringify(props[key]));\n if (value !== undefined && value !== null) {\n reifiedProps[key] = value;\n }\n }\n }\n return reifiedProps;\n }\n}\n\nfunction isBlockElement(e: JSX.Element): e is BlockElement {\n return typeof e === 'object' && e != null && 'type' in e;\n}\n", "// This message is referenced in LocalRuntimeJSEngine.kt.\nexport const CIRCUIT_BREAKER_MSG = 'ServerCallRequired';\n// to-do: use TypeScript to reference this function in SandboxedRuntimeLite.\n/** @arg method API method called for debugging. Eg, UserDataByAccountIds. */\nexport function CircuitBreak(method) {\n // Warning: this function is serialized by SandboxRuntimeLite. Don't use\n // import dependencies.\n // Error must be postable.\n const err = Error('ServerCallRequired');\n err.cause = { method, stack: err.stack };\n return err;\n}\nexport class CircuitBreakerResponse extends Error {\n constructor(response, cause) {\n super(CIRCUIT_BREAKER_MSG);\n this.response = response;\n this.cause = cause;\n this.name = 'CircuitBreakerResponse';\n }\n}\nexport function isCircuitBreaker(err) {\n return err?.message === CIRCUIT_BREAKER_MSG;\n}\n", "import type { JSONValue, RedisClient } from '../../../../index.js';\n\nexport type CacheEntry = {\n value: JSONValue | null;\n expires: number; // Timestamp in milliseconds\n error: string | null;\n errorTime: number | null;\n checkedAt: number;\n errorCount: number;\n};\n\nexport type Clock = {\n now(): Date;\n};\n\nexport const SystemClock: Clock = {\n now() {\n return new Date();\n },\n};\n\nexport type CacheOptions = {\n /**\n * Time to live in milliseconds.\n */\n ttl: number;\n\n /**\n * Key to use for caching.\n */\n key: string;\n};\n\nexport type LocalCache = { [key: string]: CacheEntry };\n\nexport function _namespaced(key: string): string {\n return `__autocache__${key}`;\n}\nexport function _lock(key: string): string {\n return `__lock__${key}`;\n}\n\nconst pollEvery = 300; // milli\nconst maxPollingTimeout = 1000; // milli\nconst minTtlValue = 5000;\nexport const retryLimit = 3;\nconst errorRetryProbability = 0.1;\nexport const clientRetryDelay = 1000;\nexport const allowStaleFor = 30_000;\n\ntype WithLocalCache = {\n __cache?: LocalCache;\n};\n\nfunction _unwrap<T>(entry: CacheEntry): T {\n if (entry.error) {\n throw new Error(entry.error);\n }\n return entry.value as T;\n}\n\n/**\n * Refactored out into a class to allow for easier testing and clarity of purpose.\n *\n * This class is responsible for managing the caching of promises. It is a layered cache, meaning it will first check\n * the local cache, then the redis cache, and finally the source of truth. It will also handle refreshing the cache according\n * to the TTL and error handling.\n *\n * Please note that in order to prevent a stampede of requests to the source of truth, we use a lock in redis to ensure only one\n * request is made to the source of truth at a time. If the lock is obtained, the cache will be updated and the lock will be released.\n *\n * Additionally, we use a polling mechanism to fetch the cache if the lock is not obtained. This is to prevent unnecessary errors.\n *\n * Finally, we also want to prevent stampedes against redis for the lock election and the retries. We use a ramping probability to ease in the\n * attempts to get the lock, and not every error will trigger a retry.\n *\n * This means that the cache will be eventually consistent, but will not be immediately consistent. This is a tradeoff we are willing to make.\n * Additionally, this means that the TTL is not precice. The cache may be updated a bit more often than the TTL, but it will not be updated less often.\n *\n */\nexport class PromiseCache {\n #redis: RedisClient;\n /**\n * LocalCache is just an aliased reference to this.#state. Mutations to\n * this object will also mutate this.#state\n */\n #localCache: LocalCache = {};\n #clock: Clock;\n #state: WithLocalCache;\n\n constructor(redis: RedisClient, state: WithLocalCache, clock: Clock = SystemClock) {\n this.#redis = redis;\n this.#state = state;\n this.#clock = clock;\n }\n\n /**\n * This is the public API for the cache. Call this method to cache a promise.\n *\n * @param closure\n * @param options\n * @returns\n */\n async cache<T extends JSONValue>(closure: () => Promise<T>, options: CacheOptions): Promise<T> {\n this.#state.__cache ??= {};\n this.#localCache = this.#state.__cache;\n\n this.#enforceTTL(options);\n\n const localCachedAnswer = this.#localCachedAnswer<T>(options.key);\n if (localCachedAnswer !== undefined) {\n return localCachedAnswer;\n }\n\n const existing = await this.#redisEntry(options.key);\n const entry = await this.#maybeRefreshCache(options, existing, closure);\n\n return _unwrap(entry);\n }\n\n /**\n * Get the value from the local cache if it exists and is not expired. We're willing to retry errors, and we're willing\n * to throw errors if we have them in cache.\n *\n * We don't want to retry excessively, so we have a limit on the number of retries. If someone else has retried in the last\n * clientRetryDelay, let's not retry again. We also have a probability of retrying, so we don't retry every time.\n */\n #localCachedAnswer<T extends JSONValue>(key: string): T | undefined {\n const val = this.#localCache[key];\n if (val) {\n const now = this.#clock.now().getTime();\n const hasRetryableError =\n val?.error &&\n val?.errorTime &&\n val.errorCount < retryLimit &&\n Math.random() < errorRetryProbability &&\n val.errorTime! + clientRetryDelay < now;\n const expired = val?.expires && val.expires < now && val.checkedAt + clientRetryDelay < now;\n if (expired || hasRetryableError) {\n delete this.#localCache[key];\n return undefined;\n } else {\n return _unwrap(val);\n }\n }\n return undefined;\n }\n\n /**\n * If we've bothered to check redis, we're already on the backend. Let's see if the cache either (1) contains an error, (2)\n * is expired, (3) is missing, or (4) is about to expire. If any of these are true, we'll refresh the cache based on heuristics.\n *\n * We'll always refresh if missing or expired, but its probabilistic if we'll refresh if about to expire or if we have an error.\n */\n async #maybeRefreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const expires = entry?.expires;\n const rampProbability = expires ? this.#calculateRamp(expires) : 1;\n if (\n !entry ||\n (entry?.error && entry.errorCount < retryLimit && errorRetryProbability > Math.random()) ||\n rampProbability > Math.random()\n ) {\n return this.#refreshCache(options, entry, closure);\n } else {\n return entry!;\n }\n }\n\n /**\n * The conditions for refreshing the cache are handled in the calling method, which should be\n * #maybeRefreshCache.\n *\n * If you don't win the lock, you'll poll for the cache. If you don't get the cache within maxPollingTimeout, you'll throw an error.\n */\n async #refreshCache<T extends JSONValue>(\n options: CacheOptions,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>\n ): Promise<CacheEntry> {\n const lockKey = _lock(options.key);\n const now = this.#clock.now().getTime();\n\n /**\n * The write lock should last for a while, but not the full TTL. Hopefully write attempts settle down after a while.\n */\n const lockExpiration = new Date(now + options.ttl / 2);\n\n const lockObtained = await this.#redis.set(lockKey, '1', {\n expiration: lockExpiration,\n nx: true,\n });\n if (lockObtained) {\n return this.#updateCache(options.key, entry, closure, options.ttl);\n } else if (entry) {\n // This entry is still valid, return it\n return entry;\n } else {\n const start = this.#clock.now();\n return this.#pollForCache(start, options.key, options.ttl);\n }\n }\n\n async #pollForCache(start: Date, key: string, ttl: number): Promise<CacheEntry> {\n const pollingTimeout = Math.min(ttl, maxPollingTimeout);\n const existing = await this.#redisEntry(key);\n if (existing) {\n return existing;\n }\n\n if (this.#clock.now().getTime() - start.getTime() >= pollingTimeout) {\n throw new Error(`Cache request timed out trying to get data at key: ${key}`);\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollEvery));\n return this.#pollForCache(start, key, ttl);\n }\n\n /**\n * Actually update the cache. This is the method that will be called if we have the lock.\n */\n async #updateCache<T extends JSONValue>(\n key: string,\n entry: CacheEntry | undefined,\n closure: () => Promise<T>,\n ttl: number\n ): Promise<CacheEntry> {\n const expires = this.#clock.now().getTime() + ttl;\n entry = entry ?? {\n value: null,\n expires,\n errorCount: 0,\n error: null,\n errorTime: null,\n checkedAt: 0,\n };\n try {\n entry.value = await closure();\n entry.error = null;\n entry.errorCount = 0;\n entry.errorTime = null;\n } catch (e) {\n entry.value = null;\n entry.error = (e as Error).message ?? 'Unknown error';\n entry.errorTime = this.#clock.now().getTime();\n entry.errorCount++;\n }\n\n this.#localCache[key] = entry;\n\n await this.#redis.set(_namespaced(key), JSON.stringify(entry), {\n expiration: new Date(expires + allowStaleFor),\n });\n\n /**\n * Unlocking will allow retries to happen if there was an error. Otherwise we don't unlock, because the lock\n * will expire on its own.\n */\n if (entry.error && entry.errorCount < retryLimit) {\n await this.#redis.del(_lock(key));\n }\n\n return entry;\n }\n\n /**\n * This is the schedule for optimistic pre-fetch of an about-to-expire cache. It exponentially ramps in, which hopefully provides\n * a degree of flexibility in the face of varying traffic levels.\n */\n #calculateRamp(expiry: number): number {\n const now = this.#clock.now().getTime();\n const remaining = expiry - now;\n\n if (remaining < 0) {\n return 1;\n } else if (remaining < 1000) {\n return 0.1;\n } else if (remaining < 2000) {\n return 0.01;\n } else if (remaining < 3000) {\n return 0.001;\n } else {\n return 0;\n }\n }\n\n async #redisEntry(key: string): Promise<CacheEntry | undefined> {\n const val = await this.#redis.get(_namespaced(key));\n if (val) {\n const entry = JSON.parse(val) as CacheEntry;\n entry.checkedAt = this.#clock.now().getTime();\n this.#localCache[key] = entry;\n return entry;\n }\n return undefined;\n }\n\n #enforceTTL(options: CacheOptions): void {\n if (options.ttl < minTtlValue) {\n console.warn(\n `Cache TTL cannot be less than ${minTtlValue} milliseconds! Updating ttl value of ${options.ttl} to ${minTtlValue}.`\n );\n options.ttl = minTtlValue;\n }\n }\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { RedisClient } from '../../../../types/redis.js';\nimport type { CacheOptions, Clock, LocalCache } from './promise_cache.js';\nimport { PromiseCache, SystemClock } from './promise_cache.js';\nimport type { RenderContext } from './RenderContext.js';\n\nexport type CacheHelper = <T extends JSONValue>(\n fn: () => Promise<T>,\n options: CacheOptions\n) => Promise<T>;\n\nexport function makeCache(\n redis: RedisClient,\n state: RenderContext['_state'] & { __cache?: LocalCache },\n clock: Clock = SystemClock\n): CacheHelper {\n const pc = new PromiseCache(redis, state, clock);\n return pc.cache.bind(pc);\n}\n", "import { EffectType, Form, type Toast as ToastProto, ToastAppearance } from '@devvit/protos';\nimport type { JSONObject, JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\n\nimport type { Comment, Post, Subreddit, User } from '../../../../apis/reddit/models/index.js';\nimport { assertValidFormFields } from '../../../../apis/ui/helpers/assertValidFormFields.js';\nimport { transformFormFields } from '../../../../apis/ui/helpers/transformForm.js';\nimport type { Toast } from '../../../../types/toast.js';\nimport type { UIClient as _UIClient } from '../../../../types/ui-client.js';\nimport type { WebViewUIClient } from '../../../../types/web-view-ui-client.js';\nimport { _activeRenderContext } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport { getFormDefinition } from './useForm.js';\n\nexport function useUI(): _UIClient {\n const renderContext = _activeRenderContext;\n if (!renderContext) {\n throw new Error('useUI can only be called from within the top level of a component.');\n }\n return new UIClient(renderContext);\n}\n\nexport class UIClient implements _UIClient {\n readonly #renderContext: RenderContext;\n // Auto-incrementing count of the number of WebviewMessage effects called this frame.\n // Used as part of the dedup key for emitEvent to prevent messages from being dedup'd.\n #webViewMessageCount: number = 0;\n readonly #webViewClient: WebViewUIClient;\n\n constructor(renderContext: RenderContext) {\n this.#renderContext = renderContext;\n this.#webViewClient = {\n postMessage: this.#webViewPostMessage,\n };\n }\n\n get webView(): WebViewUIClient {\n return this.#webViewClient;\n }\n\n showForm(formKey: FormKey, data?: JSONObject | undefined): void {\n const formDefinition = getFormDefinition(this.#renderContext, formKey);\n\n if (!formDefinition) {\n throw new Error('Form does not exist. Make sure you have added it using useForm.');\n }\n\n const formData =\n formDefinition.form instanceof Function\n ? formDefinition.form(data ?? {})\n : formDefinition.form;\n\n const form: Form = {\n fields: [],\n id: formKey,\n title: formData.title,\n acceptLabel: formData.acceptLabel,\n cancelLabel: formData.cancelLabel,\n shortDescription: formData.description,\n };\n\n assertValidFormFields(formData.fields);\n form.fields = transformFormFields(formData.fields);\n\n this.#renderContext.emitEffect(formKey, {\n type: EffectType.EFFECT_SHOW_FORM,\n showForm: {\n form,\n },\n });\n }\n\n showToast(text: string): void;\n showToast(toast: Toast): void;\n showToast(textOrToast: string | Toast): void {\n let toast: ToastProto;\n\n if (textOrToast instanceof Object) {\n toast = {\n text: textOrToast.text,\n appearance:\n textOrToast.appearance === 'success' ? ToastAppearance.SUCCESS : ToastAppearance.NEUTRAL,\n };\n } else {\n toast = {\n text: textOrToast,\n };\n }\n\n this.#renderContext.emitEffect(textOrToast.toString(), {\n type: EffectType.EFFECT_SHOW_TOAST,\n showToast: {\n toast,\n },\n });\n }\n\n navigateTo(url: string): void;\n navigateTo(subreddit: Subreddit): void;\n navigateTo(post: Post): void;\n navigateTo(comment: Comment): void;\n navigateTo(user: User): void;\n navigateTo(thingOrUrl: string | Subreddit | Post | Comment | User): void {\n let url: string;\n\n if (typeof thingOrUrl === 'string') {\n // Validate URL\n url = new URL(thingOrUrl).toString();\n } else {\n url = new URL(thingOrUrl.permalink, 'https://www.reddit.com').toString();\n }\n this.#renderContext.emitEffect(url, {\n type: EffectType.EFFECT_NAVIGATE_TO_URL,\n navigateToUrl: {\n url,\n },\n });\n }\n\n #webViewPostMessage: WebViewUIClient['postMessage'] = <T extends JSONValue>(\n webViewIdOrMessage: string | T,\n message?: T | undefined\n ): void => {\n const webViewId = message !== undefined ? (webViewIdOrMessage as string) : '';\n const msg = message !== undefined ? message : webViewIdOrMessage;\n this.#renderContext.emitEffect(`postMessage${this.#webViewMessageCount++}`, {\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n postMessage: {\n webViewId,\n app: {\n message: msg, // This is deprecated, but populated for backwards compatibility\n jsonString: JSON.stringify(msg), // Encode as JSON for consistency with the mobile clients\n },\n },\n },\n });\n };\n}\n", "export function formKeyToHookId(formKey) {\n // extract the hook id from the form key with a regex\n const match = formKey.match(/form\\.hook\\.(.+)\\.0/);\n /**\n * It's tempting to throw here, but it will be RenderPost. All events for RenderPost flow to RenderPostContent\n * lands in `Devvit.ts`. That means form ids flow through here and in the new world we've changed what a hook ID looks\n * like. For now, we warn until the migration is complete and can later on turn this into a throw.\n */\n if (!match?.[1]) {\n // This shows on every form submit in RenderPost so just going to leave it out\n // console.warn(\n // `Could not parse a hook ref from form key '${formKey}'. Please make sure you are passing in a string that starts with 'form.hook.' and ends with '0'. If you are seeing this warning using RenderPost, it is safe to ignore.`\n // );\n return;\n }\n return match[1];\n}\n", "import type { JSONValue } from '@devvit/shared-types/json.js';\nimport type { FormKey } from '@devvit/shared-types/useForm.js';\nimport { formKeyToHookId } from '@devvit/shared-types/useForm.js';\n\nimport { getFormValues } from '../../../../apis/ui/helpers/getFormValues.js';\nimport type {\n Form,\n FormDefinition,\n FormFunction,\n FormToFormValues,\n FormValues,\n} from '../../../../index.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { EventHandler, Hook, HookRef } from './types.js';\n\nclass UseFormHook implements Hook, FormDefinition {\n hookId: string;\n state: JSONValue = null;\n onUIEvent?: EventHandler | undefined;\n onStateLoaded?: (() => void) | undefined;\n form: Form | FormFunction;\n onSubmit: (values: FormValues) => void | Promise<void>;\n\n constructor(\n params: { hookId: string },\n form: Form | FormFunction,\n onSubmit: (values: FormValues) => void | Promise<void>\n ) {\n this.hookId = params.hookId;\n this.form = form;\n this.onSubmit = onSubmit;\n this.onUIEvent = async (event) => {\n if (event.formSubmitted) {\n await onSubmit(getFormValues(event.formSubmitted.results));\n }\n };\n }\n}\n\nexport function useForm<const T extends Form | FormFunction>(\n form: T,\n onSubmit: (values: FormToFormValues<T>) => void | Promise<void>\n): FormKey {\n const hook = registerHook({\n namespace: 'useForm',\n initializer: (params) =>\n new UseFormHook(params, form, onSubmit as (values: FormValues) => void | Promise<void>),\n });\n return hookRefToFormKey({ id: hook.hookId });\n}\n\nexport function hookRefToFormKey(hookRef: HookRef): FormKey {\n return `form.hook.${hookRef.id}.0`;\n}\n\nexport function getFormDefinition(renderContext: RenderContext, formKey: FormKey): UseFormHook {\n const hookId = formKeyToHookId(formKey);\n return renderContext.getHook({ id: hookId }) as UseFormHook;\n}\n", "import { EffectType, RealtimeSubscriptionStatus, type UIEvent } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { UseChannelResult } from '../../../../types/hooks.js';\nimport type { ChannelOptions } from '../../../../types/realtime.js';\nimport { ChannelStatus } from '../../../../types/realtime.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\ntype ChannelHookState = {\n /** `<app ID>:<install ID>:<channel name>`. */\n readonly channel: string;\n connected: boolean;\n /** Hook subscribe state, not RPC connection state. */\n subscribed: boolean;\n} & Hook['state'];\n\nclass ChannelHook<Message extends JSONValue> implements UseChannelResult<Message>, Hook {\n state: ChannelHookState;\n\n readonly #context: RenderContext;\n readonly #debug: boolean;\n /** Record state in BlocksHandler. */\n readonly #invalidate: () => void;\n readonly #opts: Readonly<ChannelOptions<Message>>;\n\n constructor(opts: Readonly<ChannelOptions<Message>>, params: Readonly<HookParams>) {\n this.#context = params.context;\n this.#debug = !!params.context._devvitContext?.debug.realtime;\n this.#opts = opts;\n this.#invalidate = params.invalidate;\n\n const appID = params.context.meta[Header.App]?.values[0];\n if (!appID) throw Error('useChannel missing app ID metadata');\n\n const installID = params.context.meta[Header.Installation]?.values[0];\n if (!installID) throw Error('useChannel missing install ID from metadata');\n\n const channel = `${appID}:${installID}:${opts.name}`;\n const duplicate = Object.values(this.#context._hooks)\n .filter((hook) => hook instanceof ChannelHook)\n .some((hook) => (hook as ChannelHook<Message>).state.channel === channel);\n if (duplicate) throw Error(`useChannel channel names must be unique; \"${channel}\" duplicated`);\n\n this.state = {\n channel,\n connected: false,\n subscribed: false,\n };\n }\n\n async onUIEvent(ev: UIEvent): Promise<void> {\n const realtime = ev.realtimeEvent;\n if (!realtime || !this.state.subscribed) return;\n switch (realtime.status) {\n case RealtimeSubscriptionStatus.REALTIME_SUBSCRIBED:\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" connected`);\n this.state.connected = true;\n this.#invalidate();\n await this.#opts.onSubscribed?.();\n break;\n case RealtimeSubscriptionStatus.REALTIME_UNSUBSCRIBED:\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" disconnected`);\n this.state.connected = false;\n this.#invalidate();\n await this.#opts.onUnsubscribed?.();\n break;\n default:\n if (this.#debug)\n console.debug(\n `[realtime] \"${this.state.channel}\" received message: ${JSON.stringify(\n ev,\n undefined,\n 2\n )}`\n );\n // to-do: define a RealtimeSubscriptionStatus.MESSAGE. this could have\n // been a oneOf but the current approach allows for status + data\n // and this default case will break if another new type is added.\n // expect msg. this must align to RealtimeClient.send().\n this.#opts.onMessage(realtime.event?.data?.msg);\n break;\n }\n }\n\n async send(msg: Message): Promise<void> {\n if (this.#debug)\n console.debug(\n `[realtime] \"${this.state.channel}\" send message: ${JSON.stringify(msg, undefined, 2)}`\n );\n if (!this.state.subscribed || !this.state.connected) {\n console.debug(`[realtime] \"${this.state.channel}\" send failed; channel not connected`);\n throw Error(`useChannel send failed; \"${this.state.channel}\" channel not connected`);\n }\n await this.#context.devvitContext.realtime.send(this.state.channel, msg);\n }\n\n get status(): ChannelStatus {\n if (this.state.subscribed && this.state.connected) return ChannelStatus.Connected;\n else if (this.state.subscribed && !this.state.connected) return ChannelStatus.Connecting;\n else if (!this.state.subscribed && this.state.connected) return ChannelStatus.Disconnecting;\n return ChannelStatus.Disconnected;\n }\n\n subscribe(): void {\n if (this.state.subscribed) return;\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" subscribed`);\n this.state.subscribed = true;\n this.#invalidate();\n this.#emitSubscribed();\n }\n\n unsubscribe(): void {\n if (!this.state.subscribed) return;\n if (this.#debug) console.debug(`[realtime] \"${this.state.channel}\" unsubscribed`);\n this.state.subscribed = false;\n this.#invalidate();\n this.#emitSubscribed();\n }\n\n #emitSubscribed(): void {\n const channels = Object.values(this.#context._hooks)\n .filter((hook) => hook instanceof ChannelHook && hook.state.subscribed)\n .map((hook) => (hook as ChannelHook<Message>).state.channel);\n this.#context.emitEffect(this.state.channel, {\n type: EffectType.EFFECT_REALTIME_SUB,\n realtimeSubscriptions: { subscriptionIds: channels },\n });\n }\n}\n\nexport function useChannel<Message extends JSONValue>(\n opts: Readonly<ChannelOptions<Message>>\n): UseChannelResult<Message> {\n if (!opts.name || /[^a-zA-Z0-9_]/.test(opts.name))\n throw Error(\n `useChannel error: The name \"${opts.name}\" you provided for the hook is invalid. Valid names can only contain letters, numbers, and underscores (_).`\n );\n\n // allow RealtimeEffectHandler to compute hook ID. maintain compatibility with\n // realtimeChannelToHookID().\n const id = `useChannel:${opts.name}`;\n return registerHook({\n id,\n namespace: id,\n initializer: (params) => new ChannelHook(opts, params),\n });\n}\n", "import type { IntervalDetails, UIEvent } from '@devvit/protos';\nimport { EffectType } from '@devvit/protos';\n\nimport type { UseIntervalResult } from '../../../../types/hooks.js';\nimport { registerHook } from './BlocksHandler.js';\nimport { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\n/**\n * Keeps track of all the intervals that are currently running in a convenient global.\n */\nconst intervals: { [hookId: string]: IntervalDetails } = {};\n\nRenderContext.addGlobalUndeliveredEventHandler('intervals', async (event, context) => {\n if (event.timer && event.hook) {\n delete intervals[event.hook];\n context.emitEffect('timers', {\n type: EffectType.EFFECT_SET_INTERVALS,\n interval: { intervals },\n });\n }\n});\n\nclass IntervalHook implements UseIntervalResult, Hook {\n #hookId: string;\n #invalidate: () => void;\n #callback: () => void | Promise<void>;\n #context: RenderContext;\n state = { duration: { seconds: 0, nanos: 0 }, running: false };\n constructor(callback: () => void | Promise<void>, requestedDelayMs: number, params: HookParams) {\n this.#invalidate = params.invalidate;\n this.#hookId = params.hookId;\n this.#callback = callback;\n this.#context = params.context;\n const seconds = Math.floor(requestedDelayMs / 1000);\n const nanos = (requestedDelayMs % 1000) * 1_000_000;\n this.state.duration = { seconds, nanos };\n }\n\n onStateLoaded(): void {\n if (this.state.running) {\n intervals[this.#hookId] = { duration: this.state.duration };\n } else {\n delete intervals[this.#hookId];\n }\n }\n\n async onUIEvent(_event: UIEvent): Promise<void> {\n await this.#callback();\n }\n\n start(): void {\n intervals[this.#hookId] = { duration: this.state.duration };\n this.state.running = true;\n this.#invalidate();\n this.#context.emitEffect('timers', {\n type: EffectType.EFFECT_SET_INTERVALS,\n interval: { intervals },\n });\n }\n\n stop(): void {\n delete intervals[this.#hookId];\n this.state.running = false;\n this.#invalidate();\n this.#context.emitEffect('timers', {\n type: EffectType.EFFECT_SET_INTERVALS,\n interval: { intervals },\n });\n }\n}\n\nexport function useInterval(\n callback: () => void | Promise<void>,\n requestedDelayMs: number\n): UseIntervalResult {\n const hook = registerHook({\n namespace: 'useInterval',\n initializer: (params) => {\n return new IntervalHook(callback, requestedDelayMs, params);\n },\n });\n\n return {\n start: () => hook.start(),\n stop: () => hook.stop(),\n };\n}\n", "import type { Effect, Metadata, UIEvent, UIRequest } from '@devvit/protos';\n\nimport type { Devvit } from '../../../Devvit.js';\nimport type { ReifiedBlockElement } from '../BlocksReconciler.js';\nimport type { EffectEmitter } from '../EffectEmitter.js';\nimport type { BlocksState, EventHandler, Hook, HookRef, HookSegment } from './types.js';\n\n/**\n * @internal\n *\n * Tombstone is a special object that indicates that a hook has been unmounted, and\n * therefore can be removed from the state.\n * */\nexport type Tombstone = { __deleted: true };\n\n/** @internal */\nexport function _isTombstone(value: unknown): boolean {\n return typeof value === 'object' && value !== null && '__deleted' in value;\n}\n\n/**\n * The RenderContext is a class that holds the state of the rendering process.\n *\n * There are many properties that start with an underscore, which is a convention we use to\n * indicate that they are private and should not be accessed directly. They are used internally\n * in tests and in the implementation of the BlocksHandler.\n *\n * DO your best to avoid adding new properties to this class to support new features. It will be tempting\n * to add special cases for new features, but we should strive to work within the existing framework.\n */\nexport class RenderContext implements EffectEmitter {\n readonly request: Readonly<UIRequest>;\n readonly meta: Readonly<Metadata>;\n #state: BlocksState;\n _segments: (HookSegment & { next: number })[] = [];\n #hooks: { [hookID: string]: Hook } = {};\n _prevHooks: { [hookID: string]: Hook } = {};\n _prevHookId: string = '';\n _effects: { [key: string]: Effect } = {};\n\n /**\n * While processing events, we do some renders to load hooks. If those renders produce valid content, then\n * we won't have to render at the end of the event processing, rather we can hang onto the last render and\n * reuse it.\n */\n _latestRenderContent: ReifiedBlockElement | undefined;\n\n /**\n * Has this state been mutated since initially loaded?\n *\n * _changed is used to determine the state deltas to report in the response\n * of the handle function inside of BlocksHandler. It's very important\n * that this list contains a comprehensive list of all hooks that have\n * changed during the entire invocation of BlocksHandler.handle.\n *\n * It may be tempting to clear the state deltas on every iteration via the\n * loop, but in doing so you'll create many roundtrips from the client to\n * the runtime resulting in the UI flickering.\n */\n _changed: { [hookID: string]: true } = {};\n /** Does this hook still exist in the most recent render? */\n _touched: { [hookID: string]: true } = {};\n /** Events that will re-enter the dispatcher queue */\n _requeueEvents: UIEvent[] = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n _rootProps: { [key: string]: any } = {};\n _generated: { [key: string]: boolean } = {};\n static _staticUndeliveredHandlers: { [key: string]: EventHandler } = {};\n _undeliveredHandlers: { [key: string]: EventHandler } = {};\n _devvitContext?: Devvit.Context;\n\n get devvitContext(): Devvit.Context {\n if (!this._devvitContext) {\n throw new Error('Devvit context not available');\n }\n return this._devvitContext;\n }\n\n set devvitContext(context: Devvit.Context) {\n this._devvitContext = context;\n }\n\n constructor(request: UIRequest, meta: Metadata) {\n this.request = request;\n this.meta = meta;\n this.#state = request.state ?? {\n __cache: {},\n };\n this._rootProps = request.props ?? {};\n }\n\n /** The state delta new to this render. */\n get _changedState(): BlocksState {\n const changed: BlocksState = {\n __cache: this.#state.__cache ?? {},\n };\n for (const key in this._changed) changed[key] = this._state[key];\n const unmounted = new Set(Object.keys(this._state));\n Object.keys(this.#hooks).forEach((key) => {\n if (key === '__cache') {\n return;\n }\n unmounted.delete(key);\n });\n unmounted.forEach((key) => {\n if (key === '__cache') {\n return;\n }\n\n const t: Tombstone = { __deleted: true };\n this._state[key] = changed[key] = t;\n });\n\n return changed;\n }\n\n get _hooks(): { [hookID: string]: Hook } {\n return this.#hooks;\n }\n\n set _hooks(hooks: { [hookID: string]: Hook }) {\n this._prevHooks = this.#hooks;\n this.#hooks = hooks;\n }\n\n /** The complete render state. */\n get _state(): BlocksState {\n return this.#state;\n }\n\n /** Replacing state resets the delta for the next render. */\n set _state(state: BlocksState) {\n // You may be tempted to put `this._changed = {}` here, please DON'T!\n // There are many times we may choose to reset the state while processing\n // events. Remember that the BlocksHandler can do N number of passes before\n // it determines it is time to send a response back to the client. _changed\n // needs to encompass all of those changes.\n\n // You may also be tempted to put `this._hooks = {}` here, please DON'T!\n // Some hooks (useState) may rely on values held on the previous hook. If you\n // set this._hooks = {} and then call #loadHooks from BlocksHandler, you will\n // accidentally remove all state held in prevHooks since the first step of\n // #loadHooks is to set hooks values to {}. Setting hooks to {} is a concern that\n // should live outside of RenderContext and inside of BlocksHandler on a case\n // by case basis.\n this.#state = state;\n }\n\n push(options: HookSegment): void {\n this._segments.push({ ...options, next: 0 });\n }\n\n pop(): void {\n this._segments.pop();\n }\n\n addUndeliveredEventHandler(id: string, handler: EventHandler): void {\n this._undeliveredHandlers[id] = handler;\n }\n\n addGlobalUndeliveredEventHandler(id: string, handler: EventHandler): void {\n RenderContext.addGlobalUndeliveredEventHandler(id, handler);\n }\n\n getHook(ref: HookRef): Hook {\n return this.#hooks[ref.id!];\n }\n /** Catches events with no active handler and routes to the corresponding hook to detach/unsubscribe/etc **/\n static addGlobalUndeliveredEventHandler(id: string, handler: EventHandler): void {\n RenderContext._staticUndeliveredHandlers[id] = handler;\n }\n\n async handleUndeliveredEvent(ev: UIEvent): Promise<Effect[] | void> {\n const allHandlers = {\n ...RenderContext._staticUndeliveredHandlers,\n ...this._undeliveredHandlers,\n };\n for (const [_, handler] of Object.entries(allHandlers)) {\n await handler(ev, this);\n }\n }\n\n emitEffect(dedupeKey: string, effect: Effect): void {\n this._effects[dedupeKey] = effect;\n }\n\n /**\n * Adds event that will re-enter the dispatcher queue.\n */\n addToRequeueEvents(...events: UIEvent[]): void {\n if (this._devvitContext?.debug.blocks) console.debug('[blocks] requeueing events', events);\n\n const grouped = events.reduce(\n (acc, event) => {\n if (event.retry) {\n acc.retry.push(event);\n } else {\n acc.normal.push(event);\n }\n return acc;\n },\n { retry: [], normal: [] } as { retry: UIEvent[]; normal: UIEvent[] }\n );\n\n // We need to maintain the order of the events, so we need to add the retry events first\n this._requeueEvents = [...grouped.retry, ...this._requeueEvents, ...grouped.normal];\n }\n\n get effects(): Effect[] {\n return Object.values(this._effects);\n }\n\n nextHookId(options: HookSegment): string {\n if (options.key === undefined) {\n options.key = this._segments[this._segments.length - 1].next++ + '';\n }\n this.push(options);\n try {\n const builder = [];\n /**\n * We need to build the hook id from the segments in reverse order, because an explicit id\n * overrides parent path info.\n */\n for (let i = this._segments.length - 1; i >= 0; i--) {\n const segment = this._segments[i];\n if (segment.id) {\n builder.unshift(segment.id);\n break;\n }\n\n const tag = [];\n if (segment.namespace) {\n tag.push(segment.namespace);\n }\n\n if (segment.key !== undefined && segment.key !== false) {\n tag.push(segment.key);\n }\n\n builder.unshift(tag.join('-'));\n }\n const id = builder.join('.');\n if (this._generated[id] && !options.shared) {\n throw new Error(\n `Hook id ${id} already used, cannot register another hook with the same id`\n );\n }\n this._generated[id] = true;\n this._prevHookId = id;\n return id;\n } finally {\n this.pop();\n }\n }\n}\n", "import { type UIEvent, UIEventScope } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type {\n SetStateAction,\n UseStateInitializer,\n UseStateResult,\n} from '../../../../types/hooks.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\nimport { RenderInterruptError } from './types.js';\nimport { type LoadState, toSerializableErrorOrCircuitBreak } from './useAsync.js';\n\n/**\n * Implementation of the useState hook.\n */\nclass UseStateHook<S extends JSONValue> implements Hook {\n state: {\n value: S | null;\n load_state: LoadState;\n error: { message: string; details: string } | null;\n } = { value: null, load_state: 'initial', error: null };\n #changed: () => void;\n #ctx: RenderContext;\n #initializer: UseStateInitializer<S>;\n #hookId: string;\n _promise: Promise<S> | undefined;\n\n constructor(initializer: UseStateInitializer<S>, params: HookParams) {\n this.#initializer = initializer;\n this.#hookId = params.hookId;\n this.#changed = params.invalidate;\n this.#ctx = params.context;\n }\n\n /**\n * For state initialized with a promise, this function is called when the promise resolves. This should\n * happen inside the same event loop as the original call to useState, assuming circuit-breaking didn't happen.\n *\n * If circuit-breaking did happen, this function will be called on the server and the state will propagate to\n * the client.\n *\n * This is intended to be synchronous in the event loop, so will block and waterfall. useAsync is the non-waterfalling\n * version, but this version is provided for backwards compatibility.\n */\n async onUIEvent(): Promise<void> {\n if (this.state.load_state === 'loading' && this._promise) {\n try {\n this.state.value = await this._promise;\n this.state.load_state = 'loaded';\n } catch (e) {\n this.state.load_state = 'error';\n this.state.error = toSerializableErrorOrCircuitBreak(e);\n }\n this.#changed();\n } else {\n /**\n * This would probably be some sort of concurrent access bug. It's not clear what would put us\n * in this state, but it's worth logging so we can investigate.\n */\n console.warn(\n `Invalid state for hook (${this.#hookId}):`,\n this.state.load_state,\n this._promise\n );\n }\n }\n\n setter(action: SetStateAction<S>): void {\n this.state.value = action instanceof Function ? action(this.state.value as S) : action;\n this.state.load_state = 'loaded';\n this.#changed();\n }\n\n /**\n * After the existing state is loaded, we need to run the initializer if there was no state found.\n */\n onStateLoaded(): void {\n this._promise = (this.#ctx._prevHooks[this.#hookId] as UseStateHook<S> | undefined)?._promise;\n\n /**\n * If the state is still loading, we need to throw an error to prevent using the null state.\n */\n if (this.state.load_state === 'loading') {\n throw new RenderInterruptError();\n }\n if (this.state.load_state === 'error') {\n throw new Error(this.state.error?.message ?? 'Unknown error');\n }\n if (this.state.load_state === 'initial') {\n let initialValue: S | Promise<S>;\n try {\n initialValue =\n this.#initializer instanceof Function ? this.#initializer() : this.#initializer;\n } catch (e) {\n console.log('error in loading async', e);\n this.state.load_state = 'error';\n this.#changed();\n return;\n }\n if (initialValue instanceof Promise) {\n this._promise = initialValue;\n this.state.load_state = 'loading';\n const requeueEvent: UIEvent = {\n scope: UIEventScope.ALL,\n asyncRequest: { requestId: this.#hookId },\n hook: this.#hookId,\n };\n this.#ctx.addToRequeueEvents(requeueEvent);\n this.#changed();\n throw new RenderInterruptError();\n } else {\n this.state.value = initialValue;\n this.state.load_state = 'loaded';\n }\n this.#changed();\n }\n }\n}\n\nexport function useState(initialState: UseStateInitializer<boolean>): UseStateResult<boolean>;\nexport function useState(initialState: UseStateInitializer<number>): UseStateResult<number>;\nexport function useState(initialState: UseStateInitializer<string>): UseStateResult<string>;\nexport function useState<S extends JSONValue>(\n initialState: UseStateInitializer<S>\n): UseStateResult<S>;\nexport function useState<S extends JSONValue>(\n initialState: UseStateInitializer<S>\n): UseStateResult<S> {\n const hook = registerHook({\n namespace: 'useState',\n initializer: (params) => new UseStateHook(initialState, params),\n });\n\n return [hook.state.value as S, hook.setter.bind(hook)];\n}\n", "import type { UIEvent } from '@devvit/protos';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\n\nimport type { RenderContext, Tombstone } from './RenderContext.js';\n\nexport type BlocksState = { [hookID: string]: JSONValue | Tombstone };\n\nexport type Props = { [key: string]: unknown };\n\nexport type HookSegment = {\n /**\n * This is usually the name of the hook: useAsync, useAsyncState,\n * useChannel:channelName, useForm, useInterval, useState, the block element,\n * or the component name (eg, AppToolbar or FooBar).\n *\n * Namespaces can be used to encode additional data such as logically shared\n * instances that would otherwise have to be gathered from Hook instances.\n *\n * Dashes (-) and dots (.) delimit hook IDs so don't use those in your\n * namespace.\n */\n namespace: string;\n\n /**\n * If the ordering is known, you can provide it here. Else, the key will be a\n * generated auto-incrementing number.\n *\n * The exception is that if key === false, then no key will be added to the hook.\n */\n\n key?: string | false;\n\n /**\n * If you want to use a specific id, you can provide it here. Else, the id will be\n * generated based on the namespace and key.\n */\n\n id?: string;\n\n /**\n * If the hook id & hook state is shared across multiple components, you can set this to true. The only\n * example of this that I can think of is something like createContext and useContext.\n */\n shared?: boolean;\n};\n\nexport type EventHandler = (event: UIEvent, context: RenderContext) => Promise<void>;\n\n/**\n * Syncs state between client and server, responds to events, provides user\n * API, and transitions state across renders.\n */\nexport type Hook = {\n /**\n * State to carry across renders. Hook constructor arguments are recreated on\n * render but state may be passed in the render request and used to prime that\n * render's hook before onLoad().\n */\n state: JSONValue | Tombstone;\n\n /**\n * What event callbacks want to hit.\n */\n onUIEvent?: EventHandler;\n\n /**\n * Invoked when hook has been updated with the last saved state. The lifecycle\n * is:\n *\n * 1. constructor.\n * 2. state gets copied into the hook.\n * 3. onStateLoaded().\n * 4. possible changes to the state.\n * 5. state gets serialized.\n */\n onStateLoaded?(): void;\n};\n\nexport type HookRef = { id?: string };\n\nexport type HookParams = {\n hookId: string;\n /**\n * Record state mutation in BlocksHandler. Caller is responsible for filtering\n * out a nop transition where previous state is equivalent to next as wanted.\n * This state will be provided right before Hook.onStateLoaded().\n */\n invalidate(): void;\n context: RenderContext;\n};\n\n/**\n * Circuit-breaking triggers a {scope=REMOTE, async=false, retry=true} event.\n * This triggers a {scope=ALL, async=true, retry=false} event. In\n * practice, that has some slight implications for how it flows through the\n * event loop. If we wanted to, we could change this out, if we fully understand\n * the semantics. This aligns more with useAsync than circuit-breaking today,\n * though either is likely just fine.\n */\nexport class RenderInterruptError extends Error {}\n", "import { type AsyncResponse, type UIEvent, UIEventScope } from '@devvit/protos';\nimport { CIRCUIT_BREAKER_MSG } from '@devvit/shared-types/CircuitBreaker.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\nimport { StringUtil } from '@devvit/shared-types/StringUtil.js';\nimport { isEqual } from 'moderndash';\n\nimport type { AsyncUseStateInitializer, UseAsyncResult } from '../../../../types/hooks.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\nexport type AsyncOptions<S extends JSONValue> = {\n /**\n * The data loader will re-run if the value of `depends` changes.\n */\n depends?: JSONValue;\n\n /**\n * A callback that will be called after the data is loaded, regardless of whether it succeeds or fails.\n * This is a good place to run setStates or other side effects, because calling a setState in the main\n * body is not allowed.\n *\n * useAsync(async () => {\n * const response = await fetch(`https://date.api/today?timezone=${timezone}`);\n * return response.json();\n * }, {\n * depends: [timezone],\n * finally: (data, error) => {\n * if (error) {\n * console.error(\"Failed to load date data:\", error);\n * } else {\n * setTodayDate(data['currentDate']);\n * }\n * },\n * });\n *\n */\n finally?: (data: S | null, error: Error | null) => void;\n};\n\nexport type LoadState = 'initial' | 'loading' | 'loaded' | 'error';\n\n/**\n * This tries to save an error into the state. If the error is a circuit breaker, it will throw the error instead,\n * as those errors are not meant to be saved in state.\n *\n * @param e -- an original error type\n * @returns A JSONValue that can be saved in states\n */\nexport function toSerializableErrorOrCircuitBreak(e: unknown): {\n message: string;\n details: string;\n} {\n // This is a little side-effecty, so open to suggestions on how to improve.\n if (e instanceof Error) {\n if (e.message === CIRCUIT_BREAKER_MSG) {\n throw e;\n }\n console.error(e);\n return { message: e.message, details: e.stack ?? '' };\n } else {\n console.error(e);\n return { message: 'Unknown error', details: StringUtil.caughtToString(e) };\n }\n}\n\ntype AsyncState<S extends JSONValue> = {\n depends: JSONValue;\n load_state: LoadState;\n data: S | null;\n error: { message: string; details: string } | null;\n};\n\nclass AsyncHook<S extends JSONValue> implements Hook {\n state: AsyncState<S>;\n readonly #debug: boolean;\n #hookId: string;\n initializer: AsyncUseStateInitializer<S>;\n #invalidate: () => void;\n #ctx: RenderContext;\n localDepends: JSONValue;\n #options: AsyncOptions<S>;\n\n constructor(\n initializer: AsyncUseStateInitializer<S>,\n options: AsyncOptions<S>,\n params: HookParams\n ) {\n this.#debug = !!params.context.devvitContext.debug.useAsync;\n if (this.#debug) console.debug('[useAsync] v1', options);\n this.state = { data: null, load_state: 'initial', error: null, depends: null };\n this.#hookId = params.hookId;\n this.initializer = initializer;\n this.#invalidate = params.invalidate;\n this.#ctx = params.context;\n this.localDepends = options.depends ?? null;\n this.#options = options;\n }\n\n /**\n * After we look at our state, we need to decide if we need to dispatch a request to load the data.\n */\n onStateLoaded(): void {\n if (this.#debug) console.debug('[useAsync] async onLoad ', this.#hookId, this.state);\n if (this.#debug)\n console.debug('[useAsync] async onLoad have ', this.localDepends, 'and', this.state.depends);\n if (!isEqual(this.localDepends, this.state.depends) || this.state.load_state === 'initial') {\n if (this.#debug) console.debug(`[useAsync] attempting to resolve for hookId`, this.#hookId);\n this.state.load_state = 'loading';\n this.state.depends = this.localDepends;\n this.#invalidate();\n\n const requeueEvent: UIEvent = {\n scope: UIEventScope.ALL,\n hook: this.#hookId,\n async: true,\n asyncRequest: {\n requestId: this.#hookId + '-' + JSON.stringify(this.state.depends),\n },\n };\n if (this.#debug) console.debug('[useAsync] onLoad requeue');\n this.#ctx.addToRequeueEvents(requeueEvent);\n }\n }\n\n async onUIEvent(event: UIEvent, context: RenderContext): Promise<void> {\n /**\n * Requests and responses are both handled here. If we have a request, we need to load the data\n * and then send a response. If we have a response, we need to update our state and re-render.\n *\n * This is a very event-driven way to handle state, but it's the only way to handle async state.\n */\n if (event.asyncRequest) {\n const asyncResponse: AsyncResponse = { requestId: event.asyncRequest.requestId };\n try {\n asyncResponse.data = {\n value: await this.initializer(),\n };\n } catch (e) {\n asyncResponse.error = toSerializableErrorOrCircuitBreak(e);\n }\n\n const requeueEvent: UIEvent = {\n scope: UIEventScope.ALL,\n asyncResponse: asyncResponse,\n hook: this.#hookId,\n };\n if (this.#debug) console.debug('[useAsync] onReq requeue');\n context.addToRequeueEvents(requeueEvent);\n } else if (event.asyncResponse) {\n const anticipatedRequestId = this.#hookId + '-' + JSON.stringify(this.state.depends);\n if (event.asyncResponse.requestId === anticipatedRequestId) {\n this.state = {\n ...this.state,\n data: (event.asyncResponse.data?.value as S | undefined) ?? null,\n error: event.asyncResponse.error ?? null,\n load_state: event.asyncResponse.error ? 'error' : 'loaded',\n };\n if (this.#options.finally) {\n await this.#options.finally(\n this.state.data,\n this.state.error ? new Error(this.state.error?.message ?? 'Unknown error') : null\n );\n }\n this.#invalidate();\n } else {\n if (this.#debug)\n console.debug(\n '[useAsync] onResp skip, stale event',\n event.asyncResponse.requestId,\n ' !== ',\n anticipatedRequestId\n );\n }\n } else {\n throw new Error('Unknown event type');\n }\n }\n}\n\n/**\n * This is the preferred way to handle async state in Devvit.\n *\n * @param initializer -- any async function that returns a JSONValue\n * @returns UseAsyncResult<S>\n */\nexport function useAsync<S extends JSONValue>(\n initializer: AsyncUseStateInitializer<S>,\n options: AsyncOptions<S> = {}\n): UseAsyncResult<S> {\n const hook = registerHook({\n namespace: 'useAsync',\n initializer: (params) => {\n return new AsyncHook(initializer, options, params);\n },\n });\n\n return {\n data: hook.state.data,\n error: hook.state.error,\n loading: hook.state.load_state === 'loading',\n };\n}\n", "import type { Metadata, UIRequest } from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\n\nimport { AssetsClient } from '../../../../apis/AssetsClient/AssetsClient.js';\nimport { KeyValueStorage } from '../../../../apis/key-value-storage/KeyValueStorage.js';\nimport { MediaClient } from '../../../../apis/media/MediaClient.js';\nimport { ModLogClient } from '../../../../apis/modLog/ModLogClient.js';\nimport { RealtimeClient } from '../../../../apis/realtime/RealtimeClient.js';\nimport { RedisClient } from '../../../../apis/redis/RedisClient.js';\nimport { SchedulerClient } from '../../../../apis/scheduler/SchedulerClient.js';\nimport { SettingsClient } from '../../../../apis/settings/SettingsClient.js';\nimport type { ContextAPIClients } from '../../../../types/context.js';\nimport type { Devvit } from '../../../Devvit.js';\nimport { getContextFromMetadata } from '../../context.js';\nimport { makeCache } from './cache.js';\nimport type { RenderContext } from './RenderContext.js';\nimport { UIClient } from './UIClient.js';\nimport { useChannel } from './useChannel.js';\nimport { useForm } from './useForm.js';\nimport { useInterval } from './useInterval.js';\nimport { useState } from './useState.js';\n\nexport class ContextBuilder {\n public buildContext(\n renderContext: RenderContext,\n request: UIRequest,\n metadata: Metadata\n ): Devvit.Context {\n const modLog = new ModLogClient(metadata);\n const kvStore = new KeyValueStorage(metadata);\n const redis = new RedisClient(metadata);\n const scheduler = new SchedulerClient(metadata);\n const settings = new SettingsClient(metadata);\n const ui = new UIClient(renderContext);\n const media = new MediaClient(metadata);\n const assets = new AssetsClient();\n const realtime = new RealtimeClient(metadata);\n const cache = makeCache(redis, renderContext._state);\n const apiClients: ContextAPIClients = {\n modLog,\n kvStore,\n redis,\n scheduler,\n settings,\n media,\n assets,\n realtime,\n ui,\n useState,\n useChannel,\n useInterval,\n useForm: useForm as ContextAPIClients['useForm'],\n cache,\n dimensions: request.env?.dimensions,\n uiEnvironment: {\n timezone: metadata[Header.Timezone]?.values[0],\n locale: metadata[Header.Language]?.values[0],\n dimensions: request.env?.dimensions,\n colorScheme: request.env?.colorScheme,\n },\n };\n\n // TODO: Would commentId ever be needed for the blocks handler context?\n // You'd want something like....\n // const baseContext = getContextFromMetadata(metadata, request.props?.postId, request.props?.commentId);\n const baseContext = getContextFromMetadata(metadata, request.props?.postId);\n baseContext.debug.effects = renderContext;\n return { ...baseContext, ...apiClients };\n }\n}\n", "/** @jsx Devvit.createElement */\n/** @jsxFrag Devvit.Fragment */\nimport { Devvit } from '../Devvit.js';\n\nexport type ParsedDevvitUserAgent =\n | {\n company: 'Reddit';\n platform: 'iOS';\n rawVersion: string;\n versionNumber: number;\n }\n | {\n company: 'Reddit';\n platform: 'Android';\n rawVersion: string;\n versionNumber: number;\n }\n | {\n company: 'Reddit';\n platform: 'Shreddit';\n rawVersion: string;\n }\n | {\n company: 'Reddit';\n platform: 'Play';\n rawVersion: string;\n };\n\nconst getVersionNumberFromRawVersion = (rawVersion: string): number | undefined => {\n const versionNumber = Number(rawVersion.trim().split('.').pop());\n return isNaN(versionNumber) ? undefined : versionNumber;\n};\n\nexport const parseDevvitUserAgent = (input: string): ParsedDevvitUserAgent | undefined => {\n const [company, platform, rawVersion] = input.trim().split(';');\n\n if (!company || !platform || !rawVersion) {\n console.warn(`Received a malformed devvit-user-agent! Received: '${input}'`);\n return;\n }\n\n if (company !== 'Reddit') {\n console.warn(`Received unknown company name in user agent! Received: '${input}'`);\n return;\n }\n\n if (platform === 'iOS') {\n const versionNumber = getVersionNumberFromRawVersion(rawVersion);\n\n if (versionNumber === undefined) {\n console.warn(`Could not parse version number from user agent! Received: '${input}'`);\n return;\n }\n\n return {\n company: 'Reddit',\n platform: 'iOS',\n rawVersion,\n versionNumber,\n };\n }\n\n if (platform === 'Android') {\n const versionNumber = getVersionNumberFromRawVersion(rawVersion);\n\n if (versionNumber === undefined) {\n console.warn(`Could not parse version number from user agent! Received: '${input}'`);\n return;\n }\n\n return {\n company: 'Reddit',\n platform: 'Android',\n rawVersion,\n versionNumber,\n };\n }\n\n if (platform === 'Shreddit') {\n return {\n company: 'Reddit',\n platform: 'Shreddit',\n rawVersion,\n };\n }\n\n if (platform === 'Play') {\n return {\n company: 'Reddit',\n platform: 'Play',\n rawVersion,\n };\n }\n\n console.warn(`Received unknown platform:`, platform);\n};\n\nexport const shouldShowUpgradeAppScreen = (\n parsedDevvitUserAgent: ParsedDevvitUserAgent | undefined\n): boolean => {\n // If we couldn't parse, default to trying to render the app\n if (!parsedDevvitUserAgent) {\n console.warn(\n `Could not parse devvit-user-agent! Received: '${JSON.stringify(parsedDevvitUserAgent)}'`\n );\n return false;\n }\n\n if (parsedDevvitUserAgent.platform === 'Android') {\n return parsedDevvitUserAgent.versionNumber < 1875012;\n }\n\n if (parsedDevvitUserAgent.platform === 'iOS') {\n return parsedDevvitUserAgent.versionNumber < 614973;\n }\n\n // Default to trying to render since we couldn't explicitly get the version number\n return false;\n};\n\nconst getUpgradeLinkForPlatform = (\n platform: ParsedDevvitUserAgent['platform']\n): string | undefined => {\n switch (platform) {\n case 'Android':\n return 'https://play.google.com/store/apps/details?id=com.reddit.frontpage';\n case 'iOS':\n return 'https://apps.apple.com/us/app/reddit/id1064216828';\n case 'Play':\n case 'Shreddit':\n break;\n default:\n console.error(`No upgrade link for platform: ${platform satisfies never}`);\n }\n};\n\nexport const UpgradeAppComponent: Devvit.BlockComponent<{\n platform: ParsedDevvitUserAgent['platform'];\n}> = ({ platform }, context) => {\n return (\n <blocks>\n <vstack alignment=\"middle center\" height={100} width={100}>\n <vstack maxWidth={'300px'} gap=\"large\" alignment=\"middle center\">\n <vstack gap=\"medium\" alignment=\"middle center\">\n <image imageHeight={100} imageWidth={100} url=\"https://i.redd.it/p1vmc5ulmpib1.png\" />\n <text size=\"large\" weight=\"bold\" wrap alignment=\"center\">\n Uh oh, the app you're trying to use requires the latest version of Reddit. Please\n upgrade your app to continue.\n </text>\n </vstack>\n <hstack alignment=\"middle center\">\n <button\n onPress={() => {\n const upgradeLink = getUpgradeLinkForPlatform(platform);\n if (upgradeLink) {\n context.ui.navigateTo(upgradeLink);\n } else {\n console.warn(`No upgrade link found for platform:`, platform);\n }\n }}\n >\n Upgrade\n </button>\n </hstack>\n </vstack>\n </vstack>\n </blocks>\n );\n};\n\n// A builder to make a component so that we don't have to rename `ui-request.handler.ts` to `.tsx`\n// Not that there's really anything wrong with that, but I like the separation of concerns since\n// we also has to set the pragma at the top of files that use Devvit-y stuff\nexport const makeUpgradeAppComponent = (platform: ParsedDevvitUserAgent['platform']) => () => (\n <UpgradeAppComponent platform={platform} />\n);\n", "export function makeGettersEnumerable(obj: object): void {\n // Get a list of all the properties on this class's constructor.\n const descriptors = Object.getOwnPropertyDescriptors(obj.constructor.prototype);\n // For each property on the constructor...\n for (const [key, descriptor] of Object.entries(descriptors)) {\n // If it's a getter...\n if (descriptor.get) {\n // Create a property **on this object directly** that mirrors the one on the prototype, except it's\n // enumerable. It has to be on the object directly for things like Object.keys() to find it. For details,\n // see the MSDN documentation on how this works:\n // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties#traversing_object_properties\n // This feels silly to me, but it's the only way that actually works.\n Object.defineProperty(obj, key, {\n ...descriptor,\n enumerable: true,\n });\n }\n }\n}\n", "// Rich-Text-JSON\n//\n// Rich-Text-JSON or (RTF|J) for short, is a declarative JSON format that encodes\n// Reddit content. You can think of it as AST designed for rendering content.\n// Historically, Reddit has encoded all of its content as Markdown. With RTF,\n// clients will submit and receive content in a JSON tree instead. This\n// removes the burden of parsing from clients, while providing a data-structure\n// that's more suited to rendering interactive content. e.g. media galleries,\n// sortable tables, etc\n//\n// The high level idea of the format, is that every part of the \"document\"\n// can be represented by element nodes. Each node is a plain old JS Object.\n// To differentiate each object, nodes specify their element kind. Some example\n// element kinds are `par` for paragraphs, `h` for headers, `link` for links,\n// `table` for tables, etc. Note: not all elements are allowable at the top-level,\n// some are used to differentiate nested data inside more complicated\n// constructs, such as tables, or nested lists.\n//\n// Since payload size is a concern, all of the keys are shortened to be\n// one character in length. There's some common conventions that will make\n// it easier to grok an element's json.\n//\n// e - `Element`, the element type of a node. Should always be a string.\n// t - `text content`, the text content of a node.\n// f - `formatting`, the formatting, or text styles, applied to the node\n// c - `content`, the content of the element. In most cases this will be an array\n// other objects. e.g. a paragraph's content (`c`) is a list of `Text` nodes\n// and `Link` nodes (simplified).\nexport var FormattingFlag;\n(function (FormattingFlag) {\n FormattingFlag[FormattingFlag[\"bold\"] = 1] = \"bold\";\n FormattingFlag[FormattingFlag[\"italic\"] = 2] = \"italic\";\n FormattingFlag[FormattingFlag[\"underline\"] = 4] = \"underline\";\n FormattingFlag[FormattingFlag[\"strikethrough\"] = 8] = \"strikethrough\";\n FormattingFlag[FormattingFlag[\"subscript\"] = 16] = \"subscript\";\n FormattingFlag[FormattingFlag[\"superscript\"] = 32] = \"superscript\";\n FormattingFlag[FormattingFlag[\"monospace\"] = 64] = \"monospace\";\n})(FormattingFlag || (FormattingFlag = {}));\nexport const TEXT_ELEMENT = 'text';\nexport const RAW_TEXT_ELEMENT = 'raw';\nexport const LINEBREAK_ELEMENT = 'br';\n// Links -----------------------------------------------------------------------\nexport const LINK_ELEMENT = 'link';\n// Reddit Links ----------------------------------------------------------------\nexport const COMMENT_LINK_ELEMENT = 'c/';\nexport const POST_LINK_ELEMENT = 'p/';\nexport const SUBREDDIT_LINK_ELEMENT = 'r/';\nexport const USER_LINK_ELEMENT = 'u/';\nexport const USER_MENTION_ELEMENT = '@';\nexport const SPOILER_TEXT_ELEMENT = 'spoilertext';\nexport const PARAGRAPH_ELEMENT = 'par';\nexport const HEADING_ELEMENT = 'h';\nexport const HORIZONTAL_RULE_ELEMENT = 'hr';\nexport const BLOCK_QUOTE_ELEMENT = 'blockquote';\nexport const CODE_BLOCK_ELEMENT = 'code';\n// Lists -----------------------------------------------------------------------\n// lists are stored as flat list of list items.\nexport const LIST_ITEM_ELEMENT = 'li';\nexport const LIST_ELEMENT = 'list';\nexport const COLUMN_ALIGN_LEFT = 'L';\nexport const COLUMN_ALIGN_RIGHT = 'R';\nexport const COLUMN_ALIGN_CENTER = 'C';\nexport const TABLE_ELEMENT = 'table';\n// Embeds ----------------------------------------------------------------------\n// embeds are essentially a nested iframe embed that previews a link.\nexport const EMBED_ELEMENT = 'embed';\nexport const IMAGE_ELEMENT = 'img';\n// Animated images, gifs, or html5 videos are similar to images, they just have\n// slightly different descriptos given the different urls required to render them.\n// Having a separate node type lets clients know what component should be\n// used to render\nexport const ANIMATED_IMAGE_ELEMENT = 'gif';\n// Videos are rendered via dynamic streaming. so instead of having different urls\n// for each size we just store two urls for different streaming formats.\n// Additionally, we store an array of placeholder image previews .\nexport const VIDEO_ELEMENT = 'video';\n", "import { mixinBlockQuoteContext, mixinCodeBlockContext, mixinHeadingContext, mixinHorizontalRuleContext, mixinImageContext, mixinLineBreakContext, mixinLinkContext, mixinListContext, mixinListItemContext, mixinParagraphContext, mixinRawTextContext, mixinSpoilerContext, mixinTableContentContext, mixinTableContext, mixinTableRowContext, mixinTextContext, } from './mixins.js';\nimport { ANIMATED_IMAGE_ELEMENT, BLOCK_QUOTE_ELEMENT, CODE_BLOCK_ELEMENT, COLUMN_ALIGN_CENTER, COLUMN_ALIGN_LEFT, COLUMN_ALIGN_RIGHT, COMMENT_LINK_ELEMENT, EMBED_ELEMENT, FormattingFlag, HEADING_ELEMENT, HORIZONTAL_RULE_ELEMENT, IMAGE_ELEMENT, LINEBREAK_ELEMENT, LINK_ELEMENT, LIST_ELEMENT, LIST_ITEM_ELEMENT, PARAGRAPH_ELEMENT, POST_LINK_ELEMENT, RAW_TEXT_ELEMENT, SPOILER_TEXT_ELEMENT, SUBREDDIT_LINK_ELEMENT, TABLE_ELEMENT, TEXT_ELEMENT, USER_LINK_ELEMENT, USER_MENTION_ELEMENT, VIDEO_ELEMENT, } from './types.js';\n/**\n *\n * @param opts {@link ImageOptions}\n * @returns AnimatedImage\n */\nexport function makeAnimatedImage(opts) {\n return {\n e: ANIMATED_IMAGE_ELEMENT,\n id: opts.mediaId,\n ...(opts.caption && { c: opts.caption }),\n ...(opts.blur && { o: opts.blur }),\n };\n}\nexport function makeBlockQuote(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinBlockQuoteContext(context, content), mixinCodeBlockContext(context, content), mixinHeadingContext(context, content), mixinListContext(context, content), mixinParagraphContext(context, content), mixinTableContext(context, content));\n cb(context);\n return {\n e: BLOCK_QUOTE_ELEMENT,\n c: content,\n ...(opts.author && { a: opts.author }),\n };\n}\nexport function makeCodeBlock(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinRawTextContext(context, content));\n cb(context);\n return {\n e: CODE_BLOCK_ELEMENT,\n c: content,\n ...(opts.language && { l: opts.language }),\n };\n}\nexport function makeCommentLink(opts) {\n return {\n e: COMMENT_LINK_ELEMENT,\n t: opts.permalink,\n };\n}\nexport function makeHeadingContext(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinRawTextContext(context, content), mixinLinkContext(context, content));\n cb(context);\n return {\n e: HEADING_ELEMENT,\n l: opts.level,\n c: content,\n };\n}\nexport function makeEmbed(opts) {\n return {\n e: EMBED_ELEMENT,\n u: opts.sourceUrl,\n c: opts.contentUrl,\n x: opts.width,\n y: opts.height,\n };\n}\nexport function makeFormatting(opts) {\n let spec = 0;\n if (opts.bold) {\n spec |= FormattingFlag.bold;\n }\n if (opts.italic) {\n spec |= FormattingFlag.italic;\n }\n if (opts.underline) {\n spec |= FormattingFlag.underline;\n }\n if (opts.strikethrough) {\n spec |= FormattingFlag.strikethrough;\n }\n if (opts.subscript) {\n spec |= FormattingFlag.subscript;\n }\n if (opts.superscript) {\n spec |= FormattingFlag.superscript;\n }\n if (opts.monospace) {\n spec |= FormattingFlag.monospace;\n }\n return [spec, opts.startIndex, opts.length];\n}\nexport function makeHorizontalRule() {\n return { e: HORIZONTAL_RULE_ELEMENT };\n}\nexport function makeImage(opts) {\n return {\n e: IMAGE_ELEMENT,\n id: opts.mediaId,\n ...(opts.caption && { c: opts.caption }),\n ...(opts.blur && { o: opts.blur }),\n };\n}\nexport function makeLineBreak() {\n return {\n e: LINEBREAK_ELEMENT,\n };\n}\nexport function makeLink(opts) {\n return {\n e: LINK_ELEMENT,\n t: opts.text,\n u: opts.url,\n ...(opts.formatting && { f: opts.formatting }),\n ...(opts.tooltip && { a: opts.tooltip }),\n };\n}\nexport function makeList(opts, cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinListItemContext(context, content));\n cb(context);\n return {\n e: LIST_ELEMENT,\n o: opts.ordered,\n c: content,\n };\n}\nexport function makeListItem(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinBlockQuoteContext(context, content), mixinCodeBlockContext(context, content), mixinHeadingContext(context, content), mixinHorizontalRuleContext(context, content), mixinListContext(context, content), mixinParagraphContext(context, content), mixinTableContext(context, content));\n cb(context);\n return {\n e: LIST_ITEM_ELEMENT,\n c: content,\n };\n}\nexport function makeParagraph(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinTextContext(context, content), mixinLinkContext(context, content), mixinLineBreakContext(context, content), mixinSpoilerContext(context, content), mixinImageContext(context, content));\n cb(context);\n return {\n e: PARAGRAPH_ELEMENT,\n c: content,\n };\n}\nexport function makePostLink(opts) {\n return {\n e: POST_LINK_ELEMENT,\n t: opts.permalink,\n };\n}\nexport function makeRawText(text) {\n return {\n e: RAW_TEXT_ELEMENT,\n t: text,\n };\n}\nexport function makeSpoilerText(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinTextContext(context, content), mixinLinkContext(context, content), mixinLineBreakContext(context, content));\n cb(context);\n return {\n e: SPOILER_TEXT_ELEMENT,\n c: content,\n };\n}\nexport function makeSubredditLink(opts) {\n return {\n e: SUBREDDIT_LINK_ELEMENT,\n t: opts.subredditName,\n l: opts.showPrefix,\n };\n}\nexport function makeTable(cb) {\n const context = {};\n const headerContent = [];\n const rowContent = [];\n Object.assign(context, mixinTableContentContext(context, headerContent, rowContent));\n cb(context);\n return {\n e: TABLE_ELEMENT,\n h: headerContent,\n c: rowContent,\n };\n}\nexport function makeTableCell(cb) {\n const [context, content] = tableCellTextContext();\n cb(context);\n return {\n c: content,\n };\n}\nexport function makeTableHeaderCell(opts, cb) {\n const [context, content] = tableCellTextContext();\n cb(context);\n let alignment;\n switch (opts.columnAlignment) {\n case 'left':\n alignment = COLUMN_ALIGN_LEFT;\n break;\n case 'right':\n alignment = COLUMN_ALIGN_RIGHT;\n break;\n case 'center':\n alignment = COLUMN_ALIGN_CENTER;\n break;\n }\n return {\n ...(alignment && { a: alignment }),\n ...(content && { c: content }),\n };\n}\nexport function makeTableRow(cb) {\n const context = {};\n const content = [];\n Object.assign(context, mixinTableRowContext(context, content));\n cb(context);\n return content;\n}\nexport function makeText(opts) {\n return {\n e: TEXT_ELEMENT,\n t: opts.text,\n ...(opts.formatting && { f: opts.formatting }),\n };\n}\nexport function makeUserLink(opts) {\n return {\n e: USER_LINK_ELEMENT,\n t: opts.username,\n l: opts.showPrefix,\n };\n}\nexport function makeUserMention(opts) {\n return {\n e: USER_MENTION_ELEMENT,\n t: opts.username,\n l: opts.showPrefix,\n };\n}\nexport function makeVideo(opts) {\n return {\n e: VIDEO_ELEMENT,\n id: opts.mediaId,\n ...(opts.caption && { c: opts.caption }),\n ...(opts.blur && { o: opts.blur }),\n ...(opts.thumbnail && { p: opts.thumbnail }),\n ...(opts.convertToGif && { gifify: opts.convertToGif }),\n };\n}\nfunction tableCellTextContext() {\n const context = {};\n const content = [];\n Object.assign(context, mixinTextContext(context, content), mixinLinkContext(context, content), mixinSpoilerContext(context, content), mixinImageContext(context, content));\n return [context, content];\n}\n", "import { makeAnimatedImage, makeBlockQuote, makeCodeBlock, makeCommentLink, makeEmbed, makeHeadingContext, makeHorizontalRule, makeImage, makeLineBreak, makeLink, makeList, makeListItem, makeParagraph, makePostLink, makeRawText, makeSpoilerText, makeSubredditLink, makeTable, makeTableCell, makeTableHeaderCell, makeTableRow, makeText, makeUserLink, makeUserMention, makeVideo, } from './elements.js';\nexport function mixinBlockQuoteContext(ctx, c) {\n return {\n blockQuote(opts, cb) {\n c.push(makeBlockQuote(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinCodeBlockContext(ctx, c) {\n return {\n codeBlock(opts, cb) {\n c.push(makeCodeBlock(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinEmbedContext(ctx, c) {\n return {\n embed(opts) {\n c.push(makeEmbed(opts));\n return ctx;\n },\n };\n}\nexport function mixinHeadingContext(ctx, c) {\n return {\n heading(opts, cb) {\n c.push(makeHeadingContext(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinHorizontalRuleContext(ctx, c) {\n return {\n horizontalRule() {\n c.push(makeHorizontalRule());\n return ctx;\n },\n };\n}\nexport function mixinImageContext(ctx, c) {\n return {\n image(opts) {\n c.push(makeImage(opts));\n return ctx;\n },\n animatedImage(opts) {\n c.push(makeAnimatedImage(opts));\n return ctx;\n },\n };\n}\nexport function mixinLineBreakContext(ctx, c) {\n return {\n linebreak() {\n c.push(makeLineBreak());\n return ctx;\n },\n };\n}\nexport function mixinLinkContext(ctx, c) {\n return {\n link(opts) {\n c.push(makeLink(opts));\n return ctx;\n },\n commentLink(opts) {\n c.push(makeCommentLink(opts));\n return ctx;\n },\n postLink(opts) {\n c.push(makePostLink(opts));\n return ctx;\n },\n subredditLink(opts) {\n c.push(makeSubredditLink(opts));\n return ctx;\n },\n userLink(opts) {\n c.push(makeUserLink(opts));\n return ctx;\n },\n userMention(opts) {\n c.push(makeUserMention(opts));\n return ctx;\n },\n };\n}\nexport function mixinListContext(ctx, c) {\n return {\n list(opts, cb) {\n c.push(makeList(opts, cb));\n return ctx;\n },\n };\n}\nexport function mixinListItemContext(ctx, c) {\n return {\n item(cb) {\n c.push(makeListItem(cb));\n return ctx;\n },\n };\n}\nexport function mixinParagraphContext(ctx, c) {\n return {\n paragraph(cb) {\n c.push(makeParagraph(cb));\n return ctx;\n },\n };\n}\nexport function mixinRawTextContext(ctx, c) {\n return {\n rawText(text) {\n c.push(makeRawText(text));\n return ctx;\n },\n };\n}\nexport function mixinSpoilerContext(ctx, c) {\n return {\n spoiler(cb) {\n c.push(makeSpoilerText(cb));\n return ctx;\n },\n };\n}\nexport function mixinTableContentContext(ctx, h, c) {\n return {\n headerCell(opts, cb) {\n h.push(makeTableHeaderCell(opts, cb));\n return ctx;\n },\n row(cb) {\n c.push(makeTableRow(cb));\n return ctx;\n },\n };\n}\nexport function mixinTableContext(ctx, c) {\n return {\n table(cb) {\n c.push(makeTable(cb));\n return ctx;\n },\n };\n}\nexport function mixinTableRowContext(ctx, c) {\n return {\n cell(cb) {\n c.push(makeTableCell(cb));\n return ctx;\n },\n };\n}\nexport function mixinTextContext(ctx, c) {\n return {\n text(opts) {\n c.push(makeText(opts));\n return ctx;\n },\n };\n}\nexport function mixinVideoContext(ctx, c) {\n return {\n video(opts) {\n c.push(makeVideo(opts));\n return ctx;\n },\n };\n}\n", "var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n};\nvar __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n};\nvar _RichTextBuilder_content;\nimport { mixinBlockQuoteContext, mixinCodeBlockContext, mixinEmbedContext, mixinHeadingContext, mixinHorizontalRuleContext, mixinImageContext, mixinListContext, mixinParagraphContext, mixinTableContext, mixinVideoContext, } from './mixins.js';\n/**\n * @mixes ParagraphContainer\n * @mixes HeadingContainer\n * @mixes HorizontalRuleContainer\n * @mixes BlockQuoteContainer\n * @mixes CodeBlockContainer\n * @mixes EmbedContainer\n * @mixes ListContainer\n * @mixes TableContainer\n * @mixes ImageContainer\n * @mixes VideoContainer\n */\nexport class RichTextBuilder {\n constructor() {\n _RichTextBuilder_content.set(this, void 0);\n const content = [];\n Object.assign(this, mixinParagraphContext(this, content), mixinHeadingContext(this, content), mixinHorizontalRuleContext(this, content), mixinBlockQuoteContext(this, content), mixinCodeBlockContext(this, content), mixinEmbedContext(this, content), mixinListContext(this, content), mixinTableContext(this, content), mixinImageContext(this, content), mixinVideoContext(this, content));\n __classPrivateFieldSet(this, _RichTextBuilder_content, {\n document: content,\n }, \"f\");\n }\n /**\n * Serializes the document to a JSON string\n */\n build() {\n return JSON.stringify(__classPrivateFieldGet(this, _RichTextBuilder_content, \"f\"));\n }\n // #region Empty interface methods\n /* These are here to satisfy the interfaces but are overwritten by the mixins in the constructor */\n paragraph(_cb) {\n return this;\n }\n heading(_opts, _cb) {\n return this;\n }\n horizontalRule() {\n return this;\n }\n blockQuote(_opts, _cb) {\n return this;\n }\n codeBlock(_opts, _cb) {\n return this;\n }\n embed(_opts) {\n return this;\n }\n list(_opts, _cb) {\n return this;\n }\n table(_cb) {\n return this;\n }\n image(_opts) {\n return this;\n }\n animatedImage(_opts) {\n return this;\n }\n video(_opts) {\n return this;\n }\n}\n_RichTextBuilder_content = new WeakMap();\n", "import { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\n\nexport function richtextToString(richtext: RichTextBuilder | object | string): string {\n let richtextString: string;\n\n if (richtext instanceof RichTextBuilder) {\n richtextString = richtext.build();\n } else if (typeof richtext === 'object') {\n richtextString = JSON.stringify(richtext);\n } else {\n richtextString = richtext;\n }\n\n return richtextString;\n}\n", "import type { T1ID, T3ID } from '@devvit/shared-types/tid.js';\n\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\n\nexport type MoreObject = {\n parentId: T1ID | T3ID;\n children: T1ID[];\n depth: number;\n};\n\nexport type ListingFetchOptions = {\n after?: string;\n before?: string;\n limit?: number;\n pageSize?: number;\n more?: MoreObject;\n};\n\nexport type ListingFetchResponse<T> = {\n children: T[];\n before?: string;\n after?: string;\n more?: MoreObject;\n};\n\nexport interface Listing<T> {\n /** @internal */\n children: T[];\n /** @internal */\n _fetch: (options: ListingFetchOptions) => Promise<ListingFetchResponse<T>>;\n}\n\ntype ListingOptions<T> = {\n hasMore?: boolean;\n after?: string;\n before?: string;\n pageSize?: number;\n limit?: number;\n children?: T[];\n more?: MoreObject;\n fetch: (options: ListingFetchOptions) => Promise<ListingFetchResponse<T>>;\n};\n\nconst DEFAULT_PAGE_SIZE = 100;\nconst DEFAULT_LIMIT = Infinity;\n\nexport class Listing<T> {\n #before?: string;\n #after?: string;\n #more?: MoreObject;\n #started: boolean = false;\n\n pageSize: number = DEFAULT_PAGE_SIZE;\n limit: number = DEFAULT_LIMIT;\n children: T[] = [];\n\n /**\n * @internal\n */\n constructor(options: ListingOptions<T>) {\n makeGettersEnumerable(this);\n\n this._fetch = options.fetch;\n this.pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;\n this.limit = options.limit ?? DEFAULT_LIMIT;\n this.#after = options.after;\n this.#before = options.before;\n this.#more = options.more;\n\n if (options.children) {\n this.children = options.children;\n }\n }\n\n get hasMore(): boolean {\n return !this.#started || Boolean(this.#after || this.#before || this.#more);\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n let currentIndex = 0;\n\n while (true) {\n if (currentIndex === this.children.length) {\n if (this.hasMore) {\n const nextPage = await this.#next();\n // r2 api sometimes returns an empty page\n if (nextPage.length === 0) {\n break;\n }\n } else {\n break;\n }\n }\n\n yield this.children[currentIndex];\n currentIndex++;\n\n if (currentIndex === this.limit) {\n break;\n }\n }\n }\n\n setMore(more: MoreObject | undefined): void {\n this.#more = more;\n }\n\n preventInitialFetch(): void {\n this.#started = true;\n }\n\n async #next(): Promise<T[]> {\n if (!this.hasMore) {\n throw new Error('This listing does not have any more items to load.');\n }\n\n const { children, before, after, more } = await this._fetch({\n before: this.#before,\n after: this.#after,\n limit: this.pageSize,\n more: this.#more,\n });\n\n this.children.push(...children);\n this.#before = before;\n this.#after = after;\n this.#more = more;\n\n this.#started = true;\n\n return children;\n }\n\n async all(): Promise<T[]> {\n while (this.hasMore && this.children.length < this.limit) {\n await this.#next();\n }\n\n return this.children.slice(0, this.limit);\n }\n\n async get(count: number): Promise<T[]> {\n const limit = count <= this.limit ? count : this.limit;\n\n while (this.hasMore && this.children.length < limit) {\n await this.#next();\n }\n\n return this.children.slice(0, limit);\n }\n}\n", "import type {\n DeleteNotesRequest,\n GetNotesRequest,\n Metadata,\n ModNoteObject,\n PostNotesRequest,\n PostRemovalNoteRequest,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { T1ID, T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, asT5ID, asTID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type { ModAction } from './ModAction.js';\n\nexport type ModNoteType =\n | 'NOTE'\n | 'APPROVAL'\n | 'REMOVAL'\n | 'BAN'\n | 'MUTE'\n | 'INVITE'\n | 'SPAM'\n | 'CONTENT_CHANGE'\n | 'MOD_ACTION'\n | 'ALL';\n\nexport type UserNoteLabel =\n | 'BOT_BAN'\n | 'PERMA_BAN'\n | 'BAN'\n | 'ABUSE_WARNING'\n | 'SPAM_WARNING'\n | 'SPAM_WATCH'\n | 'SOLID_CONTRIBUTOR'\n | 'HELPFUL_USER';\n\nexport type UserNote = {\n note?: string;\n redditId?: T1ID | T3ID | T5ID;\n label?: UserNoteLabel;\n};\n\nexport interface ModNote {\n id: string;\n operator: {\n id?: T2ID | undefined;\n name?: string | undefined;\n };\n user: {\n id?: T2ID | undefined;\n name?: string | undefined;\n };\n subreddit: {\n id?: T5ID | undefined;\n name?: string | undefined;\n };\n type: ModNoteType;\n createdAt: Date;\n userNote?: UserNote;\n modAction?: ModAction;\n}\n\nexport type GetModNotesOptions = Prettify<\n Pick<GetNotesRequest, 'subreddit' | 'user'> & {\n filter?: ModNoteType;\n } & Pick<ListingFetchOptions, 'limit' | 'before'>\n>;\n\nexport type CreateModNoteOptions = Prettify<\n PostNotesRequest & {\n redditId?: T1ID | T3ID;\n label?: UserNoteLabel;\n }\n>;\n\nexport type DeleteNotesOptions = Prettify<DeleteNotesRequest>;\n\nexport type AddRemovalNoteOptions = Prettify<PostRemovalNoteRequest>;\n\nexport class ModNote {\n /**\n * @internal\n */\n private constructor() {}\n\n static #fromProto(protoModNote: ModNoteObject): ModNote {\n // check that all required fields of protoModNote needed to create a ModNote are present\n assertNonNull(protoModNote.id, 'Mod note ID is null or undefined');\n assertNonNull(protoModNote.createdAt, 'Mod note createdAt is null or undefined');\n assertNonNull(protoModNote.type, 'Mod note type is null or undefined');\n assertNonNull(protoModNote.subreddit, 'Mod note subreddit is null or undefined');\n assertNonNull(protoModNote.subredditId, 'Mod note subredditId is null or undefined');\n assertNonNull(protoModNote.operator, 'Mod note operator is null or undefined');\n assertNonNull(protoModNote.operatorId, 'Mod note operatorId is null or undefined');\n assertNonNull(protoModNote.user, 'Mod note user is null or undefined');\n assertNonNull(protoModNote.userId, 'Mod note userId is null or undefined');\n assertNonNull(protoModNote.userNoteData, 'Mod note userNote is null or undefined');\n assertNonNull(protoModNote.modActionData, 'Mod note modAction is null or undefined');\n\n return {\n id: protoModNote.id,\n user: {\n id: asT2ID(protoModNote.userId ?? ''),\n name: protoModNote.user,\n },\n subreddit: {\n id: asT5ID(protoModNote.subredditId ?? ''),\n name: protoModNote.subreddit,\n },\n operator: {\n id: asT2ID(protoModNote.operatorId ?? ''),\n name: protoModNote.operator,\n },\n createdAt: new Date(protoModNote.createdAt! * 1000), // convert to ms\n userNote: {\n note: protoModNote.userNoteData?.note,\n redditId: protoModNote.userNoteData?.redditId\n ? asTID<T1ID | T3ID | T5ID>(protoModNote.userNoteData?.redditId)\n : undefined,\n label: protoModNote.userNoteData?.label as UserNoteLabel,\n },\n type: protoModNote.type as ModNoteType,\n };\n }\n\n /** @internal */\n static get(options: GetModNotesOptions, metadata: Metadata | undefined): Listing<ModNote> {\n const client = Devvit.redditAPIPlugins.ModNote;\n\n return new Listing<ModNote>({\n hasMore: true,\n before: options.before,\n limit: options.limit,\n pageSize: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const protoRes = await client.GetNotes(\n {\n subreddit: options.subreddit,\n user: options.user,\n filter: options.filter,\n before: fetchOptions.before,\n limit: fetchOptions.limit,\n },\n metadata\n );\n\n return {\n children: protoRes.modNotes?.map((protoModNote) => this.#fromProto(protoModNote)) || [],\n // if the response says that there are no more pages, then we should set before to undefined\n // to prevent more requests from being made\n before: protoRes.hasNextPage ? protoRes.endCursor : undefined,\n hasMore: protoRes.hasNextPage,\n } as ListingFetchResponse<ModNote>;\n },\n });\n }\n\n /** @internal */\n static async delete(\n options: DeleteNotesOptions,\n metadata: Metadata | undefined\n ): Promise<boolean> {\n const client = Devvit.redditAPIPlugins.ModNote;\n const { deleted } = await client.DeleteNotes(options, metadata);\n return !!deleted;\n }\n\n /** @internal */\n static async add(\n options: CreateModNoteOptions,\n metadata: Metadata | undefined\n ): Promise<ModNote> {\n const client = Devvit.redditAPIPlugins.ModNote;\n const res = await client.PostNotes(options, metadata);\n if (!res?.created) {\n throw new Error('Failed to create mod note');\n }\n return this.#fromProto(res.created);\n }\n\n /** @internal */\n static async addRemovalNote(\n options: AddRemovalNoteOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.ModNote;\n\n await client.PostRemovalNote(options, metadata);\n }\n}\n", "import type { Metadata, QueryResponse } from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\n\nexport class GraphQL {\n /** @internal */\n static queryWithQueryString(q: string, metadata: Metadata | undefined): Promise<QueryResponse> {\n return Devvit.redditAPIPlugins.GraphQL.Query(\n {\n query: q,\n },\n metadata\n );\n }\n\n /** @internal */\n static query(\n operationName: string,\n id: string,\n variables: JSONObject,\n metadata: Metadata | undefined\n ): Promise<QueryResponse> {\n return Devvit.redditAPIPlugins.GraphQL.PersistedQuery(\n {\n operationName,\n id,\n variables,\n },\n metadata\n );\n }\n}\n", "import type { ModeratorPermission } from '../models/User.js';\n\nexport const MODERATOR_PERMISSIONS: ModeratorPermission[] = [\n 'all',\n 'wiki',\n 'posts',\n 'access',\n 'mail',\n 'config',\n 'flair',\n 'channels',\n 'chat_config',\n 'chat_operator',\n 'community_chat',\n];\n\nexport function formatPermissions(permissions: string[], allPermissions: string[]): string {\n return allPermissions\n .map((permission) => (permissions.includes(permission) ? '+' : '-') + permission)\n .join(',');\n}\n\nexport function formatModeratorPermissions(permissions: string[]): string {\n return formatPermissions(permissions, MODERATOR_PERMISSIONS);\n}\n\nexport function validModPermissions(permissions: string[]): ModeratorPermission[] {\n return permissions.filter((permission) =>\n MODERATOR_PERMISSIONS.includes(permission as ModeratorPermission)\n ) as ModeratorPermission[];\n}\n", "import {\n type FlairCsvResult,\n type FlairObject,\n type Metadata,\n type UserFlair as UserFlairProto,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { T3ID } from '@devvit/shared-types/tid.js';\nimport { asT3ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\n\nexport enum FlairType {\n User = 'USER_FLAIR',\n Post = 'LINK_FLAIR',\n}\n\nexport type AllowableFlairContent = 'all' | 'emoji' | 'text';\nexport type FlairTextColor = 'light' | 'dark';\nexport type FlairBackgroundColor = `#${string}` | 'transparent';\n\nexport type CreateFlairTemplateOptions = {\n /** The name of the subreddit to create the flair template in. */\n subredditName: string;\n /** The flair template's allowable content. Either 'all', 'emoji', or 'text'. */\n allowableContent?: AllowableFlairContent;\n /** The background color of the flair. Either 'transparent' or a hex color code. e.g. #FFC0CB */\n backgroundColor?: string;\n maxEmojis?: number;\n /** Whether or not this flair template is only available to moderators. */\n modOnly?: boolean;\n /** The text to display in the flair. */\n text: string;\n /** Either 'dark' or 'light'. */\n textColor?: FlairTextColor;\n /** Whether or not users are allowed to edit this flair template before using it. */\n allowUserEdits?: boolean;\n};\n\nexport type EditFlairTemplateOptions = CreateFlairTemplateOptions & {\n id: string;\n};\n\nexport class FlairTemplate {\n #id: string;\n #subredditName: string;\n #text: string;\n #textColor: FlairTextColor;\n #backgroundColor: FlairBackgroundColor;\n #allowableContent: AllowableFlairContent;\n #modOnly: boolean;\n #maxEmojis: number;\n #allowUserEdits: boolean;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: FlairObject, subredditName: string, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id);\n assertNonNull(data.text);\n\n this.#id = data.id;\n this.#subredditName = subredditName;\n this.#text = data.text;\n this.#textColor = asFlairTextColor(data.textColor);\n this.#backgroundColor = asFlairBackgroundColor(data.backgroundColor);\n this.#allowableContent = asAllowableContent(data.allowableContent);\n this.#modOnly = data.modOnly;\n this.#maxEmojis = data.maxEmojis;\n this.#allowUserEdits = data.textEditable;\n\n this.#metadata = metadata;\n }\n\n /** The flair template's ID */\n get id(): string {\n return this.#id;\n }\n\n /** The flair template's text */\n get text(): string {\n return this.#text;\n }\n\n /** The flair template's text color. Either 'dark' or 'light'. */\n get textColor(): FlairTextColor {\n return this.#textColor;\n }\n\n /** The flair template's background color. Either 'transparent' or a hex color code. e.g. #FFC0CB */\n get backgroundColor(): FlairBackgroundColor {\n return this.#backgroundColor;\n }\n\n /** The flair template's allowable content. Either 'all', 'emoji', or 'text'. */\n get allowableContent(): AllowableFlairContent {\n return this.#allowableContent;\n }\n\n /** Is the flair template only available to moderators? */\n get modOnly(): boolean {\n return this.#modOnly;\n }\n\n /** The flair template's maximum number of emojis. */\n get maxEmojis(): number {\n return this.#maxEmojis;\n }\n\n /** Does the flair template allow users to edit their flair? */\n get allowUserEdits(): boolean {\n return this.#allowUserEdits;\n }\n\n /** Delete this flair template */\n async delete(): Promise<void> {\n return FlairTemplate.deleteFlairTemplate(this.#id, this.#subredditName, this.#metadata);\n }\n\n /** Edit this flair template */\n async edit(\n options: Partial<Omit<EditFlairTemplateOptions, 'id' | 'subredditName'>>\n ): Promise<FlairTemplate> {\n return FlairTemplate.editFlairTemplate(\n {\n id: this.#id,\n subredditName: this.#subredditName,\n text: options.text ?? this.#text,\n allowableContent: options.allowableContent ?? this.#allowableContent,\n backgroundColor: options.backgroundColor ?? this.#backgroundColor,\n maxEmojis: options.maxEmojis ?? this.#maxEmojis,\n modOnly: options.modOnly ?? this.#modOnly,\n textColor: options.textColor ?? this.#textColor,\n allowUserEdits: options.allowUserEdits ?? this.#allowUserEdits,\n },\n this.#metadata\n );\n }\n\n /** @internal */\n static async createPostFlairTemplate(\n options: CreateFlairTemplateOptions,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n return FlairTemplate.#createOrUpdateFlairTemplate(\n { ...options, flairType: FlairType.Post },\n metadata\n );\n }\n\n /** @internal */\n static async createUserFlairTemplate(\n options: CreateFlairTemplateOptions,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n return FlairTemplate.#createOrUpdateFlairTemplate(\n { ...options, flairType: FlairType.User },\n metadata\n );\n }\n\n /** @internal */\n static async editFlairTemplate(\n editOptions: EditFlairTemplateOptions,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n return FlairTemplate.#createOrUpdateFlairTemplate(editOptions, metadata);\n }\n\n /** @internal */\n static async #createOrUpdateFlairTemplate(\n options: CreateFlairTemplateOptions & { flairType?: FlairType; id?: string },\n metadata: Metadata | undefined\n ): Promise<FlairTemplate> {\n const {\n subredditName: subreddit,\n allowableContent = 'all',\n backgroundColor = 'transparent',\n flairType = '',\n maxEmojis = 10,\n modOnly = false,\n text,\n textColor = 'dark',\n allowUserEdits: textEditable = false,\n id: flairTemplateId = '',\n } = options;\n\n if (modOnly && textEditable) {\n throw new Error('Cannot have a mod only flair that is editable by users');\n }\n\n const client = Devvit.redditAPIPlugins.Flair;\n\n const response = await client.FlairTemplate(\n {\n subreddit,\n allowableContent,\n backgroundColor,\n flairType,\n maxEmojis,\n modOnly,\n text,\n textColor,\n textEditable,\n flairTemplateId,\n cssClass: '',\n overrideCss: false,\n },\n metadata\n );\n\n return new FlairTemplate(response, subreddit, metadata);\n }\n\n /** @internal */\n static async getPostFlairTemplates(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate[]> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n const response = await client.LinkFlair(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n return response.flair?.map((flair) => new FlairTemplate(flair, subredditName, metadata)) || [];\n }\n\n /** @internal */\n static async getUserFlairTemplates(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<FlairTemplate[]> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n const response = await client.UserFlair(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n return response.flair?.map((flair) => new FlairTemplate(flair, subredditName, metadata)) || [];\n }\n\n /** @internal */\n static async deleteFlairTemplate(\n subredditName: string,\n flairTemplateId: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n await client.DeleteFlairTemplate(\n {\n subreddit: subredditName,\n flairTemplateId,\n },\n metadata\n );\n }\n}\n\nexport type SetFlairOptions = {\n /** The name of the subreddit of the item to set the flair on */\n subredditName: string;\n /** The flair template's ID */\n flairTemplateId?: string;\n /** The flair text */\n text?: string;\n /** The flair CSS class */\n cssClass?: string;\n /** The flair text color. Either 'dark' or 'light'. */\n textColor?: FlairTextColor;\n /** The flair background color. Either 'transparent' or a hex color code. e.g. #FFC0CB */\n backgroundColor?: string;\n};\n\nexport type SetUserFlairOptions = SetFlairOptions & {\n /** The username of the user to set the flair on */\n username: string;\n};\n\nexport type SetPostFlairOptions = SetFlairOptions & {\n /** The ID of the post to set the flair on */\n postId: string;\n};\n\nexport type InternalSetPostFlairOptions = SetFlairOptions & {\n postId: T3ID;\n};\n\nexport type SetUserFlairBatchConfig = {\n /** The username of the user to edit the flair on */\n username: string;\n /** The flair text. Can't contain the comma character (\",\") */\n text?: string | undefined;\n /** The flair CSS class */\n cssClass?: string | undefined;\n};\n\nexport type UserFlairPageOptions = {\n /** A user id optionally provided which will result in a slice of user flairs, starting after this user, to be returned. */\n after?: string;\n /** A user id optionally provided which will result in a slice of user flairs, starting before this user, to be returned. */\n before?: string;\n /** A limit to the number of flairs that will be returned. Default: 25, Max: 1000 */\n limit?: number;\n};\n\nexport type GetUserFlairBySubredditOptions = UserFlairPageOptions & {\n /** The subreddit associated with the flair being retrieved. */\n subreddit: string;\n /** The username associated with the flair being retrieved. */\n name?: string;\n};\n\nexport type UserFlair = {\n /** The CSS class applied to this flair in the UI. */\n flairCssClass?: string;\n /** The username of the user to which this flair is assigned.*/\n user?: string;\n /** The text displayed in the UI for this flair. */\n flairText?: string;\n};\n\nexport type GetUserFlairBySubredditResponse = {\n /** The list of user flair */\n users: UserFlair[];\n /** The user id of the last user flair in this slice. Its presence indicates\n * that there are more items that can be fetched. Pass this into the \"after\" parameter\n * in the next call to get the next slice of data */\n next?: string;\n /** The user id of the first user flair in this slice. Its presence indicates\n * that there are items before this item that can be fetched. Pass this into the \"before\" parameter\n * in the next call to get the previous slice of data */\n prev?: string;\n};\n\n/** @internal */\nexport function convertUserFlairProtoToAPI(userFlair: UserFlairProto): UserFlair {\n return {\n flairCssClass: userFlair.flairCssClass,\n user: userFlair.user,\n flairText: userFlair.flairText,\n };\n}\n\nexport class Flair {\n /**\n * Exposes the ListFlair API. This method will return the list of user flair for the subreddit. If name\n * is specified then it will return the user flair for the given user.\n *\n * @param { GetUserFlairBySubredditOptions } options See the interface\n * @param { Metadata | undefined } metadata See the interface\n *\n * @returns { Promise<GetUserFlairBySubredditResponse> }\n *\n * @example\n * ```ts\n * const response = await flair.getUserFlairBySubreddit({\n * subreddit: \"EyeBleach\",\n * name: \"badapple\"\n * },\n * metadata\n * );\n * ```\n * @internal\n */\n static async getUserFlairBySubreddit(\n options: GetUserFlairBySubredditOptions,\n metadata: Metadata | undefined\n ): Promise<GetUserFlairBySubredditResponse> {\n const client = Devvit.redditAPIPlugins.Flair;\n return client.FlairList(options, metadata);\n }\n\n /** @internal */\n static setUserFlair(options: SetUserFlairOptions, metadata: Metadata | undefined): Promise<void> {\n return Flair.#setFlair(options, metadata);\n }\n\n /** @internal */\n static setUserFlairBatch(\n subredditName: string,\n flairs: SetUserFlairBatchConfig[],\n metadata: Metadata | undefined\n ): Promise<FlairCsvResult[]> {\n return Flair.#setUserFlairBatch(subredditName, flairs, metadata);\n }\n\n /** @internal */\n static setPostFlair(options: SetPostFlairOptions, metadata: Metadata | undefined): Promise<void> {\n return Flair.#setFlair(\n {\n ...options,\n postId: asT3ID(options.postId),\n },\n metadata\n );\n }\n\n /** @internal */\n static async #setFlair(\n options: (Omit<SetPostFlairOptions, 'postId'> & { postId: T3ID }) | SetUserFlairOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n await client.SelectFlair(\n {\n subreddit: options.subredditName,\n flairTemplateId: options.flairTemplateId ?? '',\n text: options.text ?? '',\n name: (options as SetUserFlairOptions).username,\n link: (options as InternalSetPostFlairOptions).postId,\n backgroundColor: options.backgroundColor ?? '',\n textColor: options.textColor ?? 'dark',\n cssClass: options.cssClass ?? '',\n returnRtjson: 'none',\n },\n metadata\n );\n }\n\n /** @internal */\n static async #setUserFlairBatch(\n subredditName: string,\n flairs: SetUserFlairBatchConfig[],\n metadata: Metadata | undefined\n ): Promise<FlairCsvResult[]> {\n if (!flairs.length) {\n return [];\n }\n\n const maxFlairsPerRequest = 100;\n if (flairs.length > maxFlairsPerRequest) {\n throw new Error('Unexpected input: flairs array cannot be longer than 100 entries.');\n }\n\n const csvDelimiter = ',';\n\n const flairCsv = flairs\n .map((userConfig) => {\n for (const propertyName in userConfig) {\n if (userConfig[propertyName as keyof SetUserFlairBatchConfig]?.includes(csvDelimiter)) {\n throw new Error(`Unexpected input: ${propertyName} cannot contain the \",\" character`);\n }\n }\n return [userConfig.username, userConfig.text || '', userConfig.cssClass || ''].join(\n csvDelimiter\n );\n })\n .join('\\n');\n\n const client = Devvit.redditAPIPlugins.Flair;\n const response = await client.FlairCsv(\n {\n subreddit: subredditName,\n flairCsv,\n },\n metadata\n );\n\n return response.result;\n }\n\n /** @internal */\n static async removePostFlair(\n subredditName: string,\n postId: T3ID,\n metadata: Metadata | undefined\n ): Promise<void> {\n return Flair.#removeFlair(subredditName, postId, undefined, metadata);\n }\n\n /** @internal */\n static async removeUserFlair(\n subredditName: string,\n username: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n return Flair.#removeFlair(subredditName, undefined, username, metadata);\n }\n\n static async #removeFlair(\n subredditName: string,\n postId: string | undefined,\n username: string | undefined,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Flair;\n\n await client.SelectFlair(\n {\n subreddit: subredditName,\n name: username ?? '',\n link: postId ?? '',\n flairTemplateId: '',\n backgroundColor: '',\n text: '',\n textColor: '',\n cssClass: '',\n returnRtjson: 'none',\n },\n metadata\n );\n }\n}\n\nfunction asFlairTextColor(color?: string): FlairTextColor {\n assertNonNull(color, 'Flair text color is required');\n\n if (color === 'light' || color === 'dark') {\n return color;\n }\n\n throw new Error(`Invalid flair text color: ${color}`);\n}\n\nfunction asFlairBackgroundColor(color?: string): FlairBackgroundColor {\n if (!color || color.length === 0 || color === 'transparent') {\n return 'transparent';\n }\n\n if (/^#([A-Fa-f0-9]{6})$/.test(color)) {\n return color as FlairBackgroundColor;\n }\n\n throw new Error(`Invalid flair background color: ${color}`);\n}\n\nfunction asAllowableContent(allowableContent?: string): AllowableFlairContent {\n if (allowableContent === 'all' || allowableContent === 'text' || allowableContent === 'emoji') {\n return allowableContent;\n }\n\n throw new Error(`Invalid allowable content: ${allowableContent}`);\n}\n", "import type {\n Listing as ListingProto,\n Metadata,\n RedditObject,\n SubmitRequest,\n SubmitResponse,\n} from '@devvit/protos';\nimport { type SetCustomPostPreviewRequest_BodyType } from '@devvit/protos';\nimport { Block, UIResponse } from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\nimport type { T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, asT3ID, asT5ID, isT3ID } from '@devvit/shared-types/tid.js';\nimport { fromByteArray } from 'base64-js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { BlocksReconciler } from '../../../devvit/internals/blocks/BlocksReconciler.js';\nimport { BlocksHandler } from '../../../devvit/internals/blocks/handler/BlocksHandler.js';\nimport { RunAs, type UserGeneratedContent } from '../common.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { richtextToString } from '../helpers/richtextToString.js';\nimport { getCustomPostRichTextFallback } from '../helpers/textFallbackToRichtext.js';\nimport type { CommentSubmissionOptions } from './Comment.js';\nimport { Comment } from './Comment.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport { ModNote } from './ModNote.js';\nimport { User } from './User.js';\n\nexport type GetPostsOptions = ListingFetchOptions & {\n subredditName?: string;\n};\n\nexport type GetPostsOptionsWithTimeframe = GetPostsOptions & {\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n};\n\nexport type GetSortedPostsOptions = GetPostsOptionsWithTimeframe & {\n sort: 'top' | 'controversial';\n};\n\nexport type GetHotPostsOptions = GetPostsOptions & {\n location?:\n | 'GLOBAL'\n | 'US'\n | 'AR'\n | 'AU'\n | 'BG'\n | 'CA'\n | 'CL'\n | 'CO'\n | 'HR'\n | 'CZ'\n | 'FI'\n | 'FR'\n | 'DE'\n | 'GR'\n | 'HU'\n | 'IS'\n | 'IN'\n | 'IE'\n | 'IT'\n | 'JP'\n | 'MY'\n | 'MX'\n | 'NZ'\n | 'PH'\n | 'PL'\n | 'PT'\n | 'PR'\n | 'RO'\n | 'RS'\n | 'SG'\n | 'ES'\n | 'SE'\n | 'TW'\n | 'TH'\n | 'TR'\n | 'GB'\n | 'US_WA'\n | 'US_DE'\n | 'US_DC'\n | 'US_WI'\n | 'US_WV'\n | 'US_HI'\n | 'US_FL'\n | 'US_WY'\n | 'US_NH'\n | 'US_NJ'\n | 'US_NM'\n | 'US_TX'\n | 'US_LA'\n | 'US_NC'\n | 'US_ND'\n | 'US_NE'\n | 'US_TN'\n | 'US_NY'\n | 'US_PA'\n | 'US_CA'\n | 'US_NV'\n | 'US_VA'\n | 'US_CO'\n | 'US_AK'\n | 'US_AL'\n | 'US_AR'\n | 'US_VT'\n | 'US_IL'\n | 'US_GA'\n | 'US_IN'\n | 'US_IA'\n | 'US_OK'\n | 'US_AZ'\n | 'US_ID'\n | 'US_CT'\n | 'US_ME'\n | 'US_MD'\n | 'US_MA'\n | 'US_OH'\n | 'US_UT'\n | 'US_MO'\n | 'US_MN'\n | 'US_MI'\n | 'US_RI'\n | 'US_KS'\n | 'US_MT'\n | 'US_MS'\n | 'US_SC'\n | 'US_KY'\n | 'US_OR'\n | 'US_SD';\n};\n\nexport type GetPostsByUserOptions = {\n username: string;\n sort?: 'hot' | 'new' | 'top' | 'controversial';\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n pageSize?: number;\n limit?: number;\n after?: string;\n before?: string;\n};\n\nexport type PostSuggestedCommentSort =\n | 'BLANK'\n /** \"Best\" sort. */\n | 'CONFIDENCE'\n | 'CONTROVERSIAL'\n | 'LIVE'\n /** Sort comments by creation time. */\n | 'NEW'\n | 'OLD'\n /** Similar to the \"best\" (confidence) sort, but specially designed for\n Q&A-type threads to highlight good question/answer pairs. */\n | 'QA'\n | 'RANDOM'\n /** Sort by top upvoted comments. */\n | 'TOP';\n\nexport type PostTextOptions =\n | {\n text: string;\n }\n | {\n richtext: object | RichTextBuilder;\n };\n\nexport type CustomPostRichTextFallback = RichTextBuilder | string;\n\nexport type CustomPostTextFallbackOptions =\n | {\n /**\n * May also include markdown. See https://www.reddit.com/r/reddit.com/wiki/markdown/\n */\n text: string;\n }\n | {\n richtext: CustomPostRichTextFallback;\n };\n\n// TODO - refactor submit post options\nexport type SubmitLinkOptions = CommonSubmitPostOptions & {\n url: string;\n /**\n * @deprecated Unsupported. This property is for backwards compatibility and\n * has no effect. It will removed in a future version. New code should not\n * use it.\n */\n resubmit?: boolean;\n};\n\nexport type SubmitMediaOptions = CommonSubmitPostOptions & {\n kind: 'image' | 'video' | 'videogif';\n // If `kind` is \"video\" or \"videogif\" this must be set to the thumbnail URL\n // https://www.reddit.com/dev/api/#POST_api_submit\n videoPosterUrl?: string;\n // If `kind` is \"image\" this must be set to the image URL\n // Currently Devvit only supports posts with a single image\n imageUrls?: [string];\n};\n\nexport type SubmitSelfPostOptions = PostTextOptions & CommonSubmitPostOptions;\n\nexport type SubmitCustomPostTextFallbackOptions = {\n textFallback?: CustomPostTextFallbackOptions;\n};\n\nexport type SubmitCustomPostOptions = CommonSubmitPostOptions &\n SubmitCustomPostTextFallbackOptions & {\n preview: JSX.Element;\n userGeneratedContent?: UserGeneratedContent;\n };\n\nexport type CommonSubmitPostOptions = {\n title: string;\n sendreplies?: boolean;\n nsfw?: boolean;\n spoiler?: boolean;\n flairId?: string;\n flairText?: string;\n runAs?: 'USER' | 'APP';\n};\n\nexport type SubmitPostOptions = (\n | SubmitLinkOptions\n | SubmitSelfPostOptions\n | SubmitCustomPostOptions\n | SubmitMediaOptions\n) & {\n subredditName: string;\n};\n\nconst SetCustomPostPreviewRequestBodyType = {\n NONE: 0,\n BLOCKS: 1,\n UNRECOGNIZED: -1,\n} as const;\n\ntype SetCustomPostPreviewRequestBodyType =\n (typeof SetCustomPostPreviewRequest_BodyType)[keyof typeof SetCustomPostPreviewRequest_BodyType];\n\nexport type CrosspostOptions = CommonSubmitPostOptions & {\n subredditName: string;\n postId: string;\n};\n\nexport type LinkFlair = {\n /**\n * One of: \"text\", \"richtext\"\n */\n type?: string;\n /**\n * Flair template ID to use when rendering this flair\n */\n templateId?: string;\n /**\n * Plain text representation of the flair\n */\n text?: string;\n /**\n * RichText object representation of the flair\n */\n richtext: {\n /**\n * Enum of element types. e.g. emoji or text\n */\n elementType?: string;\n /**\n * Text to show up in the flair, e.g. \"Need Advice\"\n */\n text?: string;\n /**\n * Emoji references, e.g. \":rainbow:\"\n */\n emojiRef?: string;\n /**\n * url string, e.g. \"https://reddit.com/\"\n */\n url?: string;\n }[];\n /**\n * Custom CSS classes from the subreddit's stylesheet to apply to the flair if rendered as HTML\n */\n cssClass?: string;\n /**\n * One of: \"light\", \"dark\"\n */\n textColor?: string;\n /**\n * Flair background color as a hex color string (# prefixed)\n * @example \"#FF4500\"\n */\n backgroundColor?: string;\n};\n\n/**\n * oEmbed is a format for allowing an embedded representation of a URL on third party sites.\n * The simple API allows a website to display embedded content (such as photos or videos)\n * when a user posts a link to that resource, without having to parse the resource directly.\n * See: https://oembed.com/\n */\nexport type OEmbed = {\n /** The resource type. Valid values, along with value-specific parameters, are described below. E.g. \"video\" */\n type: string;\n /** A text title, describing the resource. */\n title?: string | undefined;\n /** A URL for the author/owner of the resource. E.g. \"YouTube\" */\n providerName?: string | undefined;\n /** The name of the resource provider. E.g \"https://www.youtube.com/\" */\n providerUrl?: string | undefined;\n /** The oEmbed version number. This must be 1.0. */\n version: string;\n /** The width of the optional thumbnail in pixels */\n thumbnailWidth?: number;\n /** The height of the optional thumbnail in pixels */\n thumbnailHeight?: number;\n /** A URL to a thumbnail image representing the resource. */\n thumbnailUrl?: string | undefined;\n /** The HTML required to embed a video player. The HTML should have no padding or margins. Consumers may wish to load the HTML in an off-domain iframe to avoid XSS vulnerabilities. */\n html: string;\n /** The width in pixels required to display the HTML. */\n height?: number;\n /** The height in pixels required to display the HTML. */\n width?: number;\n /** A URL for the author/owner of the resource. E.g. \"https://www.youtube.com/@Reddit\" */\n authorUrl?: string | undefined;\n /** The name of the author/owner of the resource. E.g. \"Reddit\" */\n authorName?: string | undefined;\n};\n\n/**\n * Contains the data for a video hosted on Reddit that is in a post\n */\nexport type RedditVideo = {\n /** The bitrate of the video in kilobits per second. E.g. 450 */\n bitrateKbps?: number;\n /** The URL to the DASH playlist file. E.g. \"https://v.redd.it/abc123/DASHPlaylist.mpd\" */\n dashUrl?: string;\n /** The duration of the video in seconds. E.g. 30 */\n duration?: number;\n /** The direct URL to the video. E.g. \"https://v.redd.it/abc123/DASH_1080.mp4?source=fallback\" */\n fallbackUrl?: string;\n /** The height of the video in pixels. E.g. 1080 */\n height?: number;\n /** The URL to the HLS playlist file. E.g. \"https://v.redd.it/abc123/HLSPlaylist.m3u8\" */\n hlsUrl?: string;\n /** If `true`, the video is a GIF */\n isGif?: boolean;\n /** The URL to the scrubber media file. E.g. \"https://v.redd.it/abc123/DASH_96.mp4\" */\n scrubberMediaUrl?: string;\n /** The status of the transcoding process. E.g. \"completed\" */\n transcodingStatus?: string;\n /** The width of the video in pixels. E.g. 1920 */\n width?: number;\n};\n\nexport type SecureMedia = {\n /** The type of the OEmbed media, if present (e.g. \"youtube.com\") */\n type?: string;\n oembed?: OEmbed;\n redditVideo?: RedditVideo;\n};\n\n/**\n * Contains data about a post's thumbnail. Also contains a blurred version if the thumbnail is NSFW.\n */\nexport type EnrichedThumbnail = {\n /** Attribution text for the thumbnail */\n attribution?: string;\n /** The image used for the thumbnail. May have different resolution from Post.thumbnail */\n image: {\n url: string;\n height: number;\n width: number;\n };\n /** Whether this thumbnail appears blurred by default */\n isObfuscatedDefault: boolean;\n /** The blurred image for NSFW thumbnails */\n obfuscatedImage?: {\n url: string;\n height: number;\n width: number;\n };\n};\n\nexport class Post {\n #id: T3ID;\n #authorId?: T2ID;\n #authorName: string;\n #createdAt: Date;\n #subredditId: T5ID;\n #subredditName: string;\n #permalink: string;\n #title: string;\n #body?: string;\n #bodyHtml?: string;\n #url: string;\n #score: number;\n #numberOfComments: number;\n #numberOfReports: number;\n #thumbnail?: {\n url: string;\n height: number;\n width: number;\n };\n #approved: boolean;\n #approvedAtUtc: number;\n #bannedAtUtc: number;\n #spam: boolean;\n #stickied: boolean;\n #removed: boolean;\n #removedBy: string | undefined;\n #removedByCategory: string | undefined;\n #archived: boolean;\n #edited: boolean;\n #locked: boolean;\n #nsfw: boolean;\n #quarantined: boolean;\n #spoiler: boolean;\n #hidden: boolean;\n #ignoringReports: boolean;\n #distinguishedBy?: string;\n #flair?: LinkFlair;\n #secureMedia?: SecureMedia;\n #modReportReasons: string[];\n #userReportReasons: string[];\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: RedditObject, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'Post is missing id');\n assertNonNull(data.title, 'Post is missing title');\n assertNonNull(data.createdUtc, 'Post is missing created date');\n assertNonNull(data.author, 'Post is missing author name');\n assertNonNull(data.subreddit, 'Post is missing subreddit name');\n assertNonNull(data.subredditId, 'Post is missing subreddit id');\n assertNonNull(data.url, 'Post is missing url');\n assertNonNull(data.permalink, 'Post is missing permalink');\n\n this.#id = asT3ID(`t3_${data.id}`);\n\n this.#authorName = data.author;\n this.#authorId = data.authorFullname ? asT2ID(data.authorFullname) : undefined;\n this.#subredditId = asT5ID(data.subredditId);\n this.#subredditName = data.subreddit;\n this.#score = data.score ?? 0;\n this.#numberOfComments = data.numComments ?? 0;\n this.#numberOfReports = data.numReports ?? 0;\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#title = data.title;\n this.#body = data.selftext;\n this.#bodyHtml = data.selftextHtml;\n this.#url = data.url;\n this.#permalink = data.permalink;\n\n if (\n data.thumbnail &&\n data.thumbnail !== 'self' &&\n data.thumbnail !== 'nsfw' &&\n data.thumbnailHeight != null &&\n data.thumbnailWidth != null\n ) {\n this.#thumbnail = {\n url: data.thumbnail,\n height: data.thumbnailHeight,\n width: data.thumbnailWidth,\n };\n }\n\n this.#approved = data.approved ?? false;\n this.#approvedAtUtc = data.approvedAtUtc ?? 0;\n this.#bannedAtUtc = data.bannedAtUtc ?? 0;\n this.#removed = data.removed ?? false;\n this.#removedBy = data.removedBy;\n this.#removedByCategory = data.removedByCategory;\n this.#spam = data.spam ?? false;\n this.#stickied = data.stickied ?? false;\n this.#archived = data.archived ?? false;\n this.#edited = data.edited ?? false;\n this.#locked = data.locked ?? false;\n this.#nsfw = data.over18 ?? false;\n this.#quarantined = data.quarantine ?? false;\n this.#spoiler = data.spoiler;\n this.#hidden = data.hidden ?? false;\n this.#ignoringReports = data.ignoreReports ?? false;\n this.#distinguishedBy = data.distinguished;\n this.#secureMedia = data.secureMedia;\n\n this.#modReportReasons = ((data.modReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n this.#userReportReasons = ((data.userReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n\n this.#metadata = metadata;\n\n if (\n data.linkFlairBackgroundColor ||\n data.linkFlairCssClass ||\n data.linkFlairText ||\n data.linkFlairType ||\n data.linkFlairTemplateId ||\n data.linkFlairRichtext ||\n data.linkFlairTextColor\n ) {\n this.#flair = {\n backgroundColor: data.linkFlairBackgroundColor,\n cssClass: data.linkFlairCssClass,\n text: data.linkFlairText,\n type: data.linkFlairType,\n templateId: data.linkFlairTemplateId,\n // Map linkFlairRichtext[] into the objects with more user-friendly property names\n richtext: (data.linkFlairRichtext ?? []).map(({ e, t, a, u }) => ({\n elementType: e,\n text: t,\n emojiRef: a,\n url: u,\n })),\n textColor: data.linkFlairTextColor,\n };\n }\n }\n\n get id(): T3ID {\n return this.#id;\n }\n\n get authorId(): T2ID | undefined {\n return this.#authorId;\n }\n\n get authorName(): string {\n return this.#authorName;\n }\n\n get subredditId(): T5ID {\n return this.#subredditId;\n }\n\n get subredditName(): string {\n return this.#subredditName;\n }\n\n get permalink(): string {\n return this.#permalink;\n }\n\n get title(): string {\n return this.#title;\n }\n\n get body(): string | undefined {\n return this.#body;\n }\n\n get bodyHtml(): string | undefined {\n return this.#bodyHtml;\n }\n\n get url(): string {\n return this.#url;\n }\n\n get thumbnail(): { url: string; height: number; width: number } | undefined {\n return this.#thumbnail;\n }\n\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n get score(): number {\n return this.#score;\n }\n\n get numberOfComments(): number {\n return this.#numberOfComments;\n }\n\n get numberOfReports(): number {\n return this.#numberOfReports;\n }\n\n get approved(): boolean {\n return this.#approved;\n }\n\n get approvedAtUtc(): number {\n return this.#approvedAtUtc;\n }\n\n get bannedAtUtc(): number {\n return this.#bannedAtUtc;\n }\n\n get spam(): boolean {\n return this.#spam;\n }\n\n get stickied(): boolean {\n return this.#stickied;\n }\n\n get removed(): boolean {\n return this.#removed;\n }\n\n /**\n * Who removed this object (username)\n */\n get removedBy(): string | undefined {\n return this.#removedBy;\n }\n\n /**\n * who/what removed this object. It will return one of the following:\n * - \"anti_evil_ops\": object is removed by a aeops member\n * - \"author\": object is removed by author of the post\n * - \"automod_filtered\": object is filtered by automod\n * - \"community_ops\": object is removed by a community team member\n * - \"content_takedown\": object is removed due to content violation\n * - \"copyright_takedown\": object is removed due to copyright violation\n * - \"deleted\": object is deleted\n * - \"moderator\": object is removed by a mod of the sub\n * - \"reddit\": object is removed by anyone else\n * - undefined: object is not removed\n */\n get removedByCategory(): string | undefined {\n return this.#removedByCategory;\n }\n\n get archived(): boolean {\n return this.#archived;\n }\n\n get edited(): boolean {\n return this.#edited;\n }\n\n get locked(): boolean {\n return this.#locked;\n }\n\n get nsfw(): boolean {\n return this.#nsfw;\n }\n\n get quarantined(): boolean {\n return this.#quarantined;\n }\n\n get spoiler(): boolean {\n return this.#spoiler;\n }\n\n get hidden(): boolean {\n return this.#hidden;\n }\n\n get ignoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n get distinguishedBy(): string | undefined {\n return this.#distinguishedBy;\n }\n\n get comments(): Listing<Comment> {\n return Comment.getComments(\n {\n postId: this.id,\n },\n this.#metadata\n );\n }\n\n get flair(): LinkFlair | undefined {\n return this.#flair;\n }\n\n get secureMedia(): SecureMedia | undefined {\n return this.#secureMedia;\n }\n\n get userReportReasons(): string[] {\n return this.#userReportReasons;\n }\n\n get modReportReasons(): string[] {\n return this.#modReportReasons;\n }\n\n toJSON(): Pick<\n Post,\n | 'id'\n | 'authorId'\n | 'authorName'\n | 'subredditId'\n | 'subredditName'\n | 'permalink'\n | 'title'\n | 'body'\n | 'bodyHtml'\n | 'url'\n | 'thumbnail'\n | 'score'\n | 'numberOfComments'\n | 'numberOfReports'\n | 'createdAt'\n | 'approved'\n | 'spam'\n | 'stickied'\n | 'removed'\n | 'removedBy'\n | 'removedByCategory'\n | 'archived'\n | 'edited'\n | 'locked'\n | 'nsfw'\n | 'quarantined'\n | 'spoiler'\n | 'hidden'\n | 'ignoringReports'\n | 'distinguishedBy'\n | 'flair'\n | 'secureMedia'\n | 'userReportReasons'\n | 'modReportReasons'\n > {\n return {\n id: this.id,\n authorId: this.authorId,\n authorName: this.authorName,\n subredditId: this.subredditId,\n subredditName: this.subredditName,\n permalink: this.permalink,\n title: this.title,\n body: this.body,\n bodyHtml: this.bodyHtml,\n url: this.url,\n thumbnail: this.thumbnail,\n score: this.score,\n numberOfComments: this.numberOfComments,\n numberOfReports: this.numberOfReports,\n createdAt: this.createdAt,\n approved: this.approved,\n spam: this.spam,\n stickied: this.stickied,\n removed: this.removed,\n removedBy: this.#removedBy,\n removedByCategory: this.#removedByCategory,\n archived: this.archived,\n edited: this.edited,\n locked: this.locked,\n nsfw: this.nsfw,\n quarantined: this.quarantined,\n spoiler: this.spoiler,\n hidden: this.hidden,\n ignoringReports: this.ignoringReports,\n distinguishedBy: this.distinguishedBy,\n flair: this.flair,\n secureMedia: this.secureMedia,\n modReportReasons: this.#modReportReasons,\n userReportReasons: this.#userReportReasons,\n };\n }\n\n isApproved(): boolean {\n return this.#approved;\n }\n\n isSpam(): boolean {\n return this.#spam;\n }\n\n isStickied(): boolean {\n return this.#stickied;\n }\n\n isRemoved(): boolean {\n return this.#removed;\n }\n\n isArchived(): boolean {\n return this.#archived;\n }\n\n isEdited(): boolean {\n return this.#edited;\n }\n\n isLocked(): boolean {\n return this.#locked;\n }\n\n isNsfw(): boolean {\n return this.#nsfw;\n }\n\n isQuarantined(): boolean {\n return this.#quarantined;\n }\n\n isSpoiler(): boolean {\n return this.#spoiler;\n }\n\n isHidden(): boolean {\n return this.#hidden;\n }\n\n isIgnoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n isDistinguishedBy(): string | undefined {\n return this.#distinguishedBy;\n }\n\n async edit(options: PostTextOptions): Promise<void> {\n const newPost = await Post.edit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n\n this.#body = newPost.body;\n this.#edited = newPost.edited;\n }\n\n /**\n * Set the suggested sort for comments on a Post.\n *\n * @throws {Error} Throws an error if the suggested sort could not be set.\n * @example\n * ```ts\n * const post = await getPostById(context.postId);\n * await post.setSuggestedCommentSort(\"NEW\");\n * ```\n */\n async setSuggestedCommentSort(suggestedSort: PostSuggestedCommentSort): Promise<void> {\n await Post.setSuggestedCommentSort(\n { id: this.id, subredditId: this.#subredditId, suggestedSort },\n this.#metadata\n );\n }\n\n /**\n * Set a lightweight UI that shows while the custom post renders\n *\n * @param {JSX.ComponentFunction} ui - A JSX component function that returns a simple ui to be rendered.\n * @throws {Error} Throws an error if the preview could not be set.\n * @example\n * ```ts\n * const preview = (\n * <vstack height=\"100%\" width=\"100%\" alignment=\"middle center\">\n * <text size=\"large\">An updated preview!</text>\n * </vstack>\n * );\n * const post = await getPostById(context.postId);\n * await post.setCustomPostPreview(() => preview);\n * ```\n */\n async setCustomPostPreview(ui: JSX.ComponentFunction): Promise<void> {\n await Post.setCustomPostPreview(\n {\n id: this.id,\n ui,\n },\n this.#metadata\n );\n }\n\n /**\n * Set a text fallback for the custom post\n *\n * @param {CustomPostTextFallbackOptions} options - A text or a richtext to render in a fallback\n * @throws {Error} Throws an error if the fallback could not be set.\n * @example\n * ```ts\n * // from a menu action, form, scheduler, trigger, custom post click event, etc\n * const newTextFallback = { text: 'This is an updated text fallback' };\n * const post = await getPostById(context.postId);\n * await post.setTextFallback(newTextFallback);\n * ```\n */\n async setTextFallback(options: CustomPostTextFallbackOptions): Promise<void> {\n const newPost = await Post.setTextFallback(options, this.id, this.#metadata);\n\n this.#body = newPost.body;\n this.#edited = newPost.edited;\n }\n\n async addComment(options: CommentSubmissionOptions): Promise<Comment> {\n return Comment.submit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n }\n\n async delete(): Promise<void> {\n return Post.delete(this.id, this.#metadata);\n }\n\n async approve(): Promise<void> {\n await Post.approve(this.id, this.#metadata);\n this.#approved = true;\n this.#removed = false;\n }\n\n async remove(isSpam: boolean = false): Promise<void> {\n await Post.remove(this.id, isSpam, this.#metadata);\n this.#removed = true;\n this.#spam = isSpam;\n this.#approved = false;\n }\n\n async lock(): Promise<void> {\n await Post.lock(this.id, this.#metadata);\n this.#locked = true;\n }\n\n async unlock(): Promise<void> {\n await Post.unlock(this.id, this.#metadata);\n this.#locked = false;\n }\n\n async hide(): Promise<void> {\n await Post.hide(this.id, this.#metadata);\n this.#hidden = true;\n }\n\n async unhide(): Promise<void> {\n await Post.unhide(this.id, this.#metadata);\n this.#hidden = false;\n }\n\n async markAsNsfw(): Promise<void> {\n await Post.markAsNsfw(this.id, this.#metadata);\n this.#nsfw = true;\n }\n\n async unmarkAsNsfw(): Promise<void> {\n await Post.unmarkAsNsfw(this.id, this.#metadata);\n this.#nsfw = false;\n }\n\n async markAsSpoiler(): Promise<void> {\n await Post.markAsSpoiler(this.id, this.#metadata);\n this.#spoiler = true;\n }\n\n async unmarkAsSpoiler(): Promise<void> {\n await Post.unmarkAsSpoiler(this.id, this.#metadata);\n this.#spoiler = false;\n }\n\n async sticky(position?: 1 | 2 | 3 | 4): Promise<void> {\n await Post.sticky(this.id, position, this.#metadata);\n }\n\n async unsticky(): Promise<void> {\n await Post.unsticky(this.id, this.#metadata);\n }\n\n async distinguish(): Promise<void> {\n const { distinguishedBy } = await Post.distinguish(this.id, false, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n }\n\n async distinguishAsAdmin(): Promise<void> {\n const { distinguishedBy } = await Post.distinguish(this.id, true, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n }\n\n async undistinguish(): Promise<void> {\n const { distinguishedBy } = await Post.undistinguish(this.id, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n }\n\n async ignoreReports(): Promise<void> {\n await Post.ignoreReports(this.id, this.#metadata);\n this.#ignoringReports = true;\n }\n\n async unignoreReports(): Promise<void> {\n await Post.unignoreReports(this.id, this.#metadata);\n this.#ignoringReports = false;\n }\n\n async getAuthor(): Promise<User | undefined> {\n return User.getByUsername(this.#authorName, this.#metadata);\n }\n\n async crosspost(options: Omit<CrosspostOptions, 'postId'>): Promise<Post> {\n return Post.crosspost(\n {\n ...options,\n postId: this.id,\n },\n this.#metadata\n );\n }\n\n /**\n * Add a mod note for why the post was removed\n *\n * @param options.reasonId id of a Removal Reason - you can leave this as an empty string if you don't have one\n * @param options.modNote the reason for removal (maximum 100 characters) (optional)\n * @returns\n */\n addRemovalNote(options: { reasonId: string; modNote?: string }): Promise<void> {\n return ModNote.addRemovalNote({ itemIds: [this.#id], ...options }, this.#metadata);\n }\n\n /**\n * Get a thumbnail that contains a preview image and also contains a blurred preview for\n * NSFW images. The thumbnail returned has higher resolution than Post.thumbnail.\n * Returns undefined if the post doesn't have a thumbnail\n *\n * @returns {EnrichedThumbnail | undefined}\n * @throws {Error} Throws an error if the thumbnail could not be fetched\n * @example\n * ```ts\n * // from a menu action, form, scheduler, trigger, custom post click event, etc\n * const post = await getPostById(context.postId);\n * const enrichedThumbnail = await post.getEnrichedThumbnail();\n * ```\n */\n async getEnrichedThumbnail(): Promise<EnrichedThumbnail | undefined> {\n return getThumbnailV2({ id: this.id }, this.#metadata);\n }\n\n // TODO: flair methods\n\n /** @internal */\n static async getById(id: T3ID, metadata: Metadata | undefined): Promise<Post> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const postId: T3ID = isT3ID(id) ? id : `t3_${id}`;\n\n const response = await client.Info(\n {\n subreddits: [],\n thingIds: [postId],\n },\n metadata\n );\n\n if (!response.data?.children?.length) {\n throw new Error('could not find post');\n }\n\n const postData = response.data.children[0];\n\n if (!postData?.data) {\n throw new Error('could not find post');\n }\n\n return new Post(postData.data, metadata);\n }\n\n /** @internal */\n static async submit(options: SubmitPostOptions, metadata: Metadata | undefined): Promise<Post> {\n const { runAs = 'APP' } = options;\n const runAsType = RunAs[runAs];\n const client =\n runAsType === RunAs.USER\n ? Devvit.userActionsPlugin\n : Devvit.redditAPIPlugins.LinksAndComments;\n\n let response: SubmitResponse;\n\n if ('preview' in options) {\n assertNonNull(metadata, 'Missing metadata in `SubmitPostOptions`');\n if (runAsType === RunAs.USER) {\n assertNonNull(\n options.userGeneratedContent,\n 'userGeneratedContent must be set in `SubmitPostOptions` when RunAs=USER for experience posts'\n );\n }\n const reconciler = new BlocksReconciler(\n () => options.preview,\n undefined,\n {},\n metadata,\n undefined\n );\n const previewBlock = await reconciler.buildBlocksUI();\n const encodedCached = Block.encode(previewBlock).finish();\n\n const { textFallback, ...sanitizedOptions } = options;\n const richtextFallback = textFallback ? getCustomPostRichTextFallback(textFallback) : '';\n\n const submitRequest: SubmitRequest = {\n kind: 'custom',\n sr: options.subredditName,\n richtextJson: fromByteArray(encodedCached),\n richtextFallback,\n ...sanitizedOptions,\n runAs: runAsType,\n };\n\n response = await client.SubmitCustomPost(submitRequest, metadata);\n } else {\n response = await client.Submit(\n {\n kind: 'kind' in options ? options.kind : 'url' in options ? 'link' : 'self',\n sr: options.subredditName,\n richtextJson: 'richtext' in options ? richtextToString(options.richtext) : undefined,\n ...options,\n runAs: runAsType,\n },\n metadata\n );\n }\n\n // Post Id might not be present as image/video post creation can happen asynchronously\n const isAllowedMediaType =\n 'kind' in options && ['image', 'video', 'videogif'].includes(options.kind);\n if (isAllowedMediaType && !response.json?.data?.id) {\n if (options.kind === 'image' && 'imageUrls' in options) {\n throw new Error(\n `Image post type with ${options.imageUrls} is being created asynchronously and should be updated in the subreddit soon.`\n );\n } else if ('videoPosterUrl' in options) {\n throw new Error(\n `Post of ${options.kind} type with ${options.videoPosterUrl} is being created asynchronously and should be updated in the subreddit soon.`\n );\n }\n }\n\n if (!response.json?.data?.id || response.json?.errors?.length) {\n throw new Error(\n `failed to submit post - either post ID is empty or request failed with errors: ${response.json?.errors}`\n );\n }\n\n return Post.getById(`t3_${response.json.data.id}`, metadata);\n }\n\n /** @internal */\n static async crosspost(options: CrosspostOptions, metadata: Metadata | undefined): Promise<Post> {\n const { runAs = 'APP' } = options;\n const runAsType = RunAs[runAs];\n const client =\n runAsType === RunAs.USER\n ? Devvit.userActionsPlugin\n : Devvit.redditAPIPlugins.LinksAndComments;\n const { postId, subredditName, ...rest } = options;\n\n const response = await client.Submit(\n {\n kind: 'crosspost',\n sr: subredditName,\n crosspostFullname: asT3ID(postId),\n ...rest,\n runAs: runAsType,\n },\n metadata\n );\n\n if (!response.json?.data?.id || response.json?.errors?.length) {\n throw new Error('failed to crosspost post');\n }\n\n return Post.getById(`t3_${response.json.data.id}`, metadata);\n }\n\n /** @internal */\n static async edit(\n options: PostTextOptions & { id: T3ID },\n metadata: Metadata | undefined\n ): Promise<Post> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const { id } = options;\n\n let richtextString: string | undefined;\n if ('richtext' in options) {\n richtextString = richtextToString(options.richtext);\n }\n\n const response = await client.EditUserText(\n {\n thingId: id,\n text: 'text' in options ? options.text : '',\n richtextJson: richtextString,\n runAs: RunAs.APP,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error('Failed to edit post');\n }\n\n // The LinksAndComments.EditUserText response is wrong and assumes that\n // the API is only used to for comments so we fetch the new post here.\n return Post.getById(id, metadata);\n }\n\n /** @internal */\n static async setSuggestedCommentSort(\n options: { suggestedSort: PostSuggestedCommentSort; id: T3ID; subredditId: T5ID },\n metadata: Metadata | undefined\n ): Promise<void> {\n const operationName = 'SetSuggestedSort';\n const persistedQueryHash = 'cf6052acc7fefaa65b710625b81dba8041f258313aafe9730e2a3dc855e5d10d';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n {\n input: {\n subredditId: options.subredditId,\n postId: options.id,\n sort: options.suggestedSort,\n },\n },\n metadata\n );\n\n if (!response.data?.setSuggestedSort?.ok) {\n throw new Error('Failed to set suggested sort');\n }\n }\n\n /** @internal */\n static async setCustomPostPreview(\n options: { id: T3ID; ui: JSX.ComponentFunction },\n metadata: Metadata | undefined\n ): Promise<void> {\n if (!metadata) {\n throw new Error('Failed to set custom post preview. Metadata not found');\n }\n\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const handler = new BlocksHandler(options.ui);\n const handlerResponse = UIResponse.fromJSON(await handler.handle({ events: [] }, metadata));\n const block = handlerResponse.blocks as Block;\n const blocksRenderContent = fromByteArray(Block.encode(block).finish());\n\n await client.SetCustomPostPreview(\n {\n thingId: options.id,\n bodyType: SetCustomPostPreviewRequestBodyType.BLOCKS,\n blocksRenderContent,\n },\n metadata\n );\n }\n\n /** @internal */\n static async setTextFallback(\n options: CustomPostTextFallbackOptions,\n postId: T3ID,\n metadata: Metadata | undefined\n ): Promise<Post> {\n if (!('text' in options) && !('richtext' in options)) {\n throw new Error(`No text fallback provided for post ${postId}.`);\n }\n\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const richtextFallback = getCustomPostRichTextFallback(options);\n\n const response = await client.EditCustomPost(\n {\n thingId: postId,\n richtextFallback,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error('Failed to set post text fallback');\n }\n\n return Post.getById(postId, metadata);\n }\n\n /** @internal */\n static async delete(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Del(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async approve(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Approve(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async remove(\n id: T3ID,\n isSpam: boolean = false,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Remove(\n {\n id,\n spam: isSpam,\n },\n metadata\n );\n }\n\n /** @internal */\n static async hide(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Hide(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unhide(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unhide(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async markAsNsfw(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.MarkNSFW(\n {\n id,\n },\n metadata\n );\n }\n /** @internal */\n static async unmarkAsNsfw(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.UnmarkNSFW(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async markAsSpoiler(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Spoiler(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unmarkAsSpoiler(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unspoiler(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async sticky(\n id: T3ID,\n position: 1 | 2 | 3 | 4 | undefined,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.SetSubredditSticky(\n {\n id,\n state: true,\n num: position,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unsticky(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.SetSubredditSticky(\n {\n id,\n state: false,\n },\n metadata\n );\n }\n\n /** @internal */\n static async lock(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Lock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unlock(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unlock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async distinguish(\n id: T3ID,\n asAdmin: boolean,\n metadata: Metadata | undefined\n ): Promise<{ distinguishedBy: string | undefined }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: asAdmin ? 'admin' : 'yes',\n sticky: false,\n },\n metadata\n );\n\n const post = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(post);\n\n return {\n distinguishedBy: post.distinguished,\n };\n }\n\n /** @internal */\n static async undistinguish(\n id: T3ID,\n metadata: Metadata | undefined\n ): Promise<{ distinguishedBy: string | undefined }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: 'no',\n sticky: false,\n },\n metadata\n );\n\n const post = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(post);\n\n return {\n distinguishedBy: post.distinguished,\n };\n }\n\n /** @internal */\n static async ignoreReports(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.IgnoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unignoreReports(id: T3ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.UnignoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static getControversialPosts(\n options: GetPostsOptionsWithTimeframe = {},\n metadata: Metadata | undefined\n ): Listing<Post> {\n return this.getSortedPosts(\n {\n ...options,\n sort: 'controversial',\n },\n metadata\n );\n }\n\n /** @internal */\n static getTopPosts(\n options: GetPostsOptionsWithTimeframe = {},\n metadata: Metadata | undefined\n ): Listing<Post> {\n return this.getSortedPosts(\n {\n ...options,\n sort: 'top',\n },\n metadata\n );\n }\n\n /** @internal */\n static getSortedPosts(\n options: GetSortedPostsOptions,\n metadata: Metadata | undefined\n ): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.Sort(\n {\n show: 'all',\n sort: options.sort,\n t: options.timeframe,\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getHotPosts(\n options: GetHotPostsOptions = {\n location: 'GLOBAL',\n },\n metadata: Metadata | undefined\n ): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.Hot(\n {\n g: options.location,\n show: 'all',\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getNewPosts(options: GetPostsOptions, metadata: Metadata | undefined): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.New(\n {\n show: 'all',\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getRisingPosts(options: GetPostsOptions, metadata: Metadata | undefined): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Listings;\n\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.Rising(\n {\n show: 'all',\n subreddit: options.subredditName,\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n\n /** @internal */\n static getPostsByUser(\n options: GetPostsByUserOptions,\n metadata: Metadata | undefined\n ): Listing<Post> {\n const client = Devvit.redditAPIPlugins.Users;\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n async fetch(fetchOptions) {\n const response = await client.UserWhere(\n {\n username: options.username,\n where: 'submitted',\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPosts(response, metadata);\n },\n });\n }\n}\n\nfunction listingProtosToPosts(\n listingProto: ListingProto,\n metadata: Metadata | undefined\n): ListingFetchResponse<Post> {\n if (!listingProto.data?.children) {\n throw new Error('Listing response is missing children');\n }\n\n const children = listingProto.data.children.map((child) => new Post(child.data!, metadata));\n\n return {\n children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n\n/** @internal */\nasync function getThumbnailV2(\n options: { id: T3ID },\n metadata: Metadata | undefined\n): Promise<EnrichedThumbnail | undefined> {\n const operationName = 'GetThumbnailV2';\n const persistedQueryHash = '81580ce4e23d748c5a59a1618489b559bf4518b6a73af41f345d8d074c8b2ce9';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n {\n id: options.id,\n },\n metadata\n );\n\n const thumbnail = response.data?.postInfoById?.thumbnailV2;\n\n if (!thumbnail) {\n throw new Error('Failed to get thumbnail');\n } else if (!thumbnail.image) {\n return undefined;\n }\n\n return {\n attribution: thumbnail.attribution,\n image: {\n url: thumbnail.image.url,\n width: thumbnail.image.dimensions.width,\n height: thumbnail.image.dimensions.height,\n },\n isObfuscatedDefault: thumbnail.isObfuscatedDefault,\n ...(thumbnail.obfuscatedImage && {\n obfuscatedImage: {\n url: thumbnail.obfuscatedImage.url,\n width: thumbnail.obfuscatedImage.dimensions.width,\n height: thumbnail.obfuscatedImage.dimensions.height,\n },\n }),\n };\n}\n", "import { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\n\nimport type { CustomPostRichTextFallback, CustomPostTextFallbackOptions } from '../models/Post.js';\n\n/** @internal */\nexport const getCustomPostRichTextFallback = (\n textFallbackOptions: CustomPostTextFallbackOptions\n): string =>\n 'text' in textFallbackOptions\n ? textFallbackOptions.text\n : richTextToTextFallbackString(textFallbackOptions.richtext);\n\nconst richTextToTextFallbackString = (textFallback: CustomPostRichTextFallback): string => {\n if (textFallback instanceof RichTextBuilder) {\n return textFallback.build();\n } else if (typeof textFallback === 'object') {\n return JSON.stringify(textFallback);\n }\n\n return textFallback;\n};\n", "import type {\n Listing as ListingProto,\n Metadata,\n User as UserProto,\n UserDataByAccountIdsResponse,\n UserDataByAccountIdsResponse_UserAccountData,\n} from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { T2ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, isT2ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { formatModeratorPermissions, validModPermissions } from '../helpers/permissions.js';\nimport type { GetCommentsByUserOptions } from './Comment.js';\nimport { Comment } from './Comment.js';\nimport type { UserFlair } from './Flair.js';\nimport { convertUserFlairProtoToAPI, Flair } from './Flair.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type { GetPostsByUserOptions } from './Post.js';\nimport { Post } from './Post.js';\n\nexport type GetSubredditUsersByTypeOptions = ListingFetchOptions & {\n subredditName: string;\n type: 'banned' | 'muted' | 'wikibanned' | 'contributors' | 'wikicontributors' | 'moderators';\n username?: string;\n};\n\nexport type RelationshipType =\n | 'moderator_invite'\n | 'contributor'\n | 'banned'\n | 'muted'\n | 'wikibanned'\n | 'wikicontributor';\n\nexport type ModeratorPermission =\n | 'all'\n | 'wiki'\n | 'posts'\n | 'access'\n | 'mail'\n | 'config'\n | 'flair'\n | 'chat_operator'\n | 'chat_config'\n | 'channels'\n | 'community_chat';\n\nexport type CreateRelationshipOptions = {\n subredditName: string;\n username: string;\n type: RelationshipType;\n /** The ID of the post or comment that caused the ban. */\n banContext?: string;\n banMessage?: string;\n banReason?: string;\n duration?: number;\n note?: string;\n permissions?: ModeratorPermission[];\n};\n\nexport type RemoveRelationshipOptions = {\n subredditName: string;\n username: string;\n type: RelationshipType | 'moderator';\n};\n\nexport type BanUserOptions = {\n username: string;\n subredditName: string;\n context?: string;\n message?: string;\n reason?: string;\n duration?: number;\n note?: string;\n};\n\nexport type BanWikiContributorOptions = {\n username: string;\n subredditName: string;\n reason?: string;\n duration?: number;\n note?: string;\n};\n\nexport type GetUserOverviewOptions = {\n username: string;\n sort?: 'hot' | 'new' | 'top' | 'controversial';\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n pageSize?: number;\n limit?: number;\n after?: string;\n before?: string;\n};\n\nexport const enum SocialLinkType {\n Custom = 'CUSTOM',\n Reddit = 'REDDIT',\n Instagram = 'INSTAGRAM',\n Twitter = 'TWITTER',\n Tiktok = 'TIKTOK',\n Twitch = 'TWITCH',\n Facebook = 'FACEBOOK',\n Youtube = 'YOUTUBE',\n Tumblr = 'TUMBLR',\n Spotify = 'SPOTIFY',\n Soundcloud = 'SOUNDCLOUD',\n Beacons = 'BEACONS',\n Linktree = 'LINKTREE',\n Discord = 'DISCORD',\n Venmo = 'VENMO',\n CashApp = 'CASH_APP',\n Patreon = 'PATREON',\n Kofi = 'KOFI',\n Paypal = 'PAYPAL',\n Cameo = 'CAMEO',\n Onlyfans = 'ONLYFANS',\n Substack = 'SUBSTACK',\n Kickstarter = 'KICKSTARTER',\n Indiegogo = 'INDIEGOGO',\n BuyMeACoffee = 'BUY_ME_A_COFFEE',\n Shopify = 'SHOPIFY',\n}\n\n/**\n * @field id: ID of the social link.\n *\n * @field handle: Display name of social media link.\n *\n * @field outboundUrl: Outbound url of social media link.\n *\n * @field type: Type of social media link i.e. Instagram, YouTube.\n *\n * @field title: Title or name of social media link.\n */\nexport type UserSocialLink = {\n id: string;\n handle?: string;\n outboundUrl: string;\n type: SocialLinkType;\n title: string;\n};\n\n/**\n * @internal\n */\ntype UserSocialLinkResponse = Omit<UserSocialLink, 'handle'> & { handle: string | null };\n\n/**\n * A class representing a user.\n */\nexport class User {\n #id: T2ID;\n #username: string;\n #createdAt: Date;\n #linkKarma: number;\n #commentKarma: number;\n #nsfw: boolean;\n #isAdmin: boolean;\n #modPermissionsBySubreddit: Map<string, ModeratorPermission[]> = new Map();\n // R2 bug: user.url is a permalink path\n #url: string;\n // R2 bug: user object does not contain a permalink field\n #permalink: string;\n #hasVerifiedEmail: boolean;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(\n data: UserProto & { modPermissions?: { [subredditName: string]: string[] } },\n metadata: Metadata | undefined\n ) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'User ID is missing or undefined');\n assertNonNull(data.name, 'Username is missing or undefined');\n assertNonNull(data.createdUtc, 'User is missing created date');\n\n // UserDataByAccountIds returns the ID without the t2_ prefix\n this.#id = asT2ID(isT2ID(data.id) ? data.id : `t2_${data.id}`);\n this.#username = data.name;\n this.#nsfw = data.over18 ?? false;\n this.#isAdmin = data.isEmployee ?? false;\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#linkKarma = data.linkKarma ?? 0;\n this.#commentKarma = data.commentKarma ?? 0;\n\n if (data.modPermissions) {\n for (const [subredditName, permissions] of Object.entries(data.modPermissions)) {\n this.#modPermissionsBySubreddit.set(subredditName, validModPermissions(permissions));\n }\n }\n\n this.#url = new URL(data.subreddit?.url ?? '', 'https://www.reddit.com').toString();\n this.#permalink = data.subreddit?.url ?? '';\n this.#hasVerifiedEmail = data.hasVerifiedEmail ?? false;\n\n this.#metadata = metadata;\n }\n\n /**\n * The ID (starting with t2_) of the user to retrieve.\n * @example 't2_1w72'\n */\n get id(): T2ID {\n return this.#id;\n }\n\n /**\n * The username of the user omitting the u/.\n * @example 'spez'\n */\n get username(): string {\n return this.#username;\n }\n\n /**\n * The date the user was created.\n */\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n /**\n * The amount of link karma the user has.\n */\n get linkKarma(): number {\n return this.#linkKarma;\n }\n\n /**\n * The amount of comment karma the user has.\n */\n get commentKarma(): number {\n return this.#commentKarma;\n }\n\n /**\n * Whether the user's profile is marked as NSFW (Not Safe For Work).\n */\n get nsfw(): boolean {\n return this.#nsfw;\n }\n\n /**\n * Whether the user is admin.\n */\n get isAdmin(): boolean {\n return this.#isAdmin;\n }\n\n /**\n * The permissions the user has on the subreddit.\n */\n get modPermissions(): Map<string, ModeratorPermission[]> {\n return this.#modPermissionsBySubreddit;\n }\n\n /**\n * Returns the HTTP URL for the user\n */\n get url(): string {\n return this.#url;\n }\n\n /**\n * Returns a permalink path relative to https://www.reddit.com\n */\n get permalink(): string {\n return this.#permalink;\n }\n\n /**\n * Indicates whether or not the user has verified their email address.\n */\n get hasVerifiedEmail(): boolean {\n return this.#hasVerifiedEmail;\n }\n\n toJSON(): Pick<User, 'id' | 'username' | 'createdAt' | 'linkKarma' | 'commentKarma' | 'nsfw'> & {\n modPermissionsBySubreddit: Record<string, ModeratorPermission[]>;\n } {\n return {\n id: this.id,\n username: this.username,\n createdAt: this.createdAt,\n linkKarma: this.linkKarma,\n commentKarma: this.commentKarma,\n nsfw: this.nsfw,\n modPermissionsBySubreddit: Object.fromEntries(this.modPermissions),\n };\n }\n\n /**\n * Get the mod permissions the user has on the subreddit if they are a moderator.\n *\n * @param subredditName - name of the subreddit\n * @returns the moderator permissions the user has on the subreddit\n */\n async getModPermissionsForSubreddit(subredditName: string): Promise<ModeratorPermission[]> {\n if (this.#modPermissionsBySubreddit.has(subredditName)) {\n return this.#modPermissionsBySubreddit.get(subredditName)!;\n }\n\n const mods = await User.getSubredditUsersByType(\n {\n subredditName,\n type: 'moderators',\n username: this.username,\n },\n this.#metadata\n ).all();\n\n if (mods.length === 0) {\n return [];\n }\n\n const permissions = mods[0].modPermissions.get(subredditName) ?? [];\n\n if (permissions.length > 0) {\n this.#modPermissionsBySubreddit.set(subredditName, permissions);\n }\n\n return permissions;\n }\n\n /**\n * Get the user's comments.\n *\n * @param options - Options for the request\n * @param options.sort - The sort order of the comments. e.g. 'new'\n * @param options.timeframe - The timeframe of the comments. e.g. 'all'\n * @param options.limit - The maximum number of comments to return. e.g. 1000\n * @param options.pageSize - The number of comments to return per request. e.g. 100\n * @returns A Listing of Comment objects.\n */\n getComments(options: Omit<GetCommentsByUserOptions, 'username'>): Listing<Comment> {\n return Comment.getCommentsByUser(\n {\n username: this.username,\n ...options,\n },\n this.#metadata\n );\n }\n\n /**\n * Get the user's posts.\n *\n * @param options - Options for the request\n * @param options.sort - The sort order of the posts. e.g. 'new'\n * @param options.timeframe - The timeframe of the posts. e.g. 'all'\n * @param options.limit - The maximum number of posts to return. e.g. 1000\n * @param options.pageSize - The number of posts to return per request. e.g. 100\n * @returns A Listing of Post objects.\n */\n getPosts(options: Omit<GetPostsByUserOptions, 'username'>): Listing<Post> {\n return Post.getPostsByUser(\n {\n username: this.username,\n ...options,\n },\n this.#metadata\n );\n }\n\n /**\n * Retrieve the user's flair for the subreddit.\n *\n * @param subreddit - The name of the subreddit associated with the user's flair.\n *\n * @example\n * ```ts\n * const username = \"badapple\"\n * const subredditName = \"mysubreddit\"\n * const user = await getUserByUsername(username);\n * const userFlair = await user.getUserFlairBySubreddit(subredditName);\n * ```\n */\n async getUserFlairBySubreddit(subreddit: string): Promise<UserFlair | undefined> {\n const userFlairs = await Flair.getUserFlairBySubreddit(\n {\n subreddit,\n name: this.#username,\n },\n this.#metadata\n );\n return userFlairs.users[0] ? convertUserFlairProtoToAPI(userFlairs.users[0]) : undefined;\n }\n\n getSnoovatarUrl(): Promise<string | undefined> {\n return User.getSnoovatarUrl(this.username, this.#metadata);\n }\n\n /**\n * Gets social links of the user\n *\n * @returns A Promise that resolves an Array of UserSocialLink objects\n * @example\n * ```ts\n * const socialLinks = await user.getSocialLinks();\n * ```\n */\n async getSocialLinks(): Promise<UserSocialLink[]> {\n const operationName = 'GetUserSocialLinks';\n const persistedQueryHash = '2aca18ef5f4fc75fb91cdaace3e9aeeae2cb3843b5c26ad511e6f01b8521593a';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { name: this.username },\n this.#metadata\n );\n\n if (!response.data?.user?.profile?.socialLinks) {\n return [];\n }\n\n return response.data.user.profile.socialLinks.map((link: UserSocialLinkResponse) => ({\n ...link,\n handle: link.handle ?? undefined,\n }));\n }\n\n /** @internal */\n static async getById(id: T2ID, metadata: Metadata | undefined): Promise<User | undefined> {\n const username = await getUsernameById(id, metadata);\n\n return username == null ? undefined : User.getByUsername(username, metadata);\n }\n\n /** @internal */\n static async getByUsername(\n username: string,\n metadata: Metadata | undefined\n ): Promise<User | undefined> {\n const client = Devvit.redditAPIPlugins.Users;\n try {\n const response = await client.UserAbout({ username }, metadata);\n // suspended accounts 404.\n if (response.data?.id) return new User(response.data, metadata);\n } catch (error) {\n if (error instanceof Error && error.message.includes('404 Not Found')) {\n return undefined;\n }\n throw error;\n }\n }\n\n /** @internal */\n static async getFromMetadata(\n key: string,\n metadata: Metadata | undefined\n ): Promise<User | undefined> {\n assertNonNull(metadata);\n const userId = metadata?.[key]?.values[0];\n return userId ? User.getById(asT2ID(userId), metadata) : Promise.resolve(undefined);\n }\n\n /** @internal */\n static getSubredditUsersByType(\n options: GetSubredditUsersByTypeOptions,\n metadata: Metadata | undefined\n ): Listing<User> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n return new Listing({\n hasMore: true,\n pageSize: options.pageSize,\n limit: options.limit,\n after: options.after,\n before: options.before,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.AboutWhere(\n {\n where: options.type,\n user: options.username,\n subreddit: options.subredditName,\n show: 'all',\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToUsers(response, options.subredditName, metadata);\n },\n });\n }\n\n /** @internal */\n static async createRelationship(\n options: CreateRelationshipOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Users;\n\n const { type, subredditName, username, permissions, ...optionalFields } = options;\n\n const response = await client.Friend(\n {\n type,\n subreddit: subredditName,\n name: username,\n permissions: permissions ? formatModeratorPermissions(permissions) : undefined,\n ...optionalFields,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error(response.json.errors.join('\\n'));\n }\n }\n\n /** @internal */\n static async removeRelationship(\n options: RemoveRelationshipOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Users;\n\n await client.Unfriend(\n {\n type: options.type,\n subreddit: options.subredditName,\n name: options.username,\n },\n metadata\n );\n }\n\n /** @internal */\n static async setModeratorPermissions(\n username: string,\n subredditName: string,\n permissions: ModeratorPermission[],\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Users;\n\n const response = await client.SetPermissions(\n {\n subreddit: subredditName,\n name: username,\n type: 'moderator',\n permissions: formatModeratorPermissions(permissions),\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error(response.json.errors.join('\\n'));\n }\n }\n\n /** @internal */\n static async getSnoovatarUrl(\n username: string,\n metadata: Metadata | undefined\n ): Promise<string | undefined> {\n const operationName = 'GetSnoovatarUrlByName';\n const persistedQueryHash = 'c47fd42345af268616d2d8904b56856acdc05cf61d3650380f539ad7d596ac0c';\n const response = await GraphQL.query(operationName, persistedQueryHash, { username }, metadata);\n return response.data?.redditorInfoByName?.snoovatarIcon?.url;\n }\n\n /** @internal */\n static getOverview(\n options: GetUserOverviewOptions,\n metadata: Metadata | undefined\n ): Listing<Post | Comment> {\n const client = Devvit.redditAPIPlugins.Users;\n return new Listing({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n async fetch(fetchOptions) {\n const response = await client.UserWhere(\n {\n username: options.username,\n where: 'overview',\n ...fetchOptions,\n },\n metadata\n );\n\n return listingProtosToPostsOrComments(response, metadata);\n },\n });\n }\n}\n\nfunction listingProtosToPostsOrComments(\n listingProto: ListingProto,\n metadata: Metadata | undefined\n): ListingFetchResponse<Post | Comment> {\n if (!listingProto.data?.children) {\n throw new Error('Listing response is missing children');\n }\n\n const children = listingProto.data.children.map((child) => {\n if (child.kind === 't3') {\n return new Post(child.data!, metadata);\n } else if (child.kind === 't1') {\n return new Comment(child.data!, metadata);\n }\n\n throw new Error(`Type ${child.kind} is not supported`);\n });\n\n return {\n children: children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n\nasync function listingProtosToUsers(\n listingProto: ListingProto,\n subredditName: string,\n metadata: Metadata | undefined\n): Promise<ListingFetchResponse<User>> {\n const client = Devvit.redditAPIPlugins.Users;\n\n if (!listingProto.data?.children) {\n throw new Error('Listing response is missing children');\n }\n\n const userIds = listingProto.data.children.map((child) => {\n assertNonNull(child.data?.id, 'User id is still from listing data');\n return child.data.id;\n });\n\n // break the ids into chunks since they're passed over a query parameter\n const chunkSize = 100;\n const userIdChunks = [];\n for (let i = 0; i < userIds.length; i += chunkSize) {\n userIdChunks.push(userIds.slice(i, i + chunkSize));\n }\n\n // perform the requests\n const usersMapResponses: UserDataByAccountIdsResponse[] = await Promise.all(\n userIdChunks.map((userIds) =>\n client.UserDataByAccountIds(\n {\n ids: userIds.join(','),\n },\n metadata\n )\n )\n );\n\n // join the responses back into a single map of user data\n const userDataById: { [key: string]: UserDataByAccountIdsResponse_UserAccountData } =\n usersMapResponses.reduce((allUsers, response) => ({ ...allUsers, ...response.users }), {});\n\n const children = listingProto.data.children.map((child) => {\n const id = child.data?.id;\n assertNonNull(id, 'User id is missing from listing');\n\n const userData = userDataById[id];\n\n // Casting to unknown because Typescript assumes that userData is always defined\n // because of how we defined the UserDataByAccountIdsResponse_UserAccountData protobuf.\n assertNonNull(userData as unknown, 'User data is missing from response');\n\n return new User(\n {\n id,\n name: userData.name,\n linkKarma: userData.linkKarma,\n commentKarma: userData.commentKarma,\n createdUtc: userData.createdUtc,\n over18: userData.profileOver18,\n snoovatarSize: [],\n modPermissions: {\n [subredditName]: child.data?.modPermissions ?? [],\n },\n },\n metadata\n );\n });\n\n return {\n children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n\n/** @internal */\nasync function getUsernameById(\n id: string,\n metadata: Metadata | undefined\n): Promise<string | undefined> {\n const client = Devvit.redditAPIPlugins.Users;\n\n const response = await client.UserDataByAccountIds({ ids: id }, metadata);\n\n return response?.users?.[id]?.name;\n}\n\n/** @internal */\nexport async function getCurrentUsernameFromMetadata(\n metadata: Metadata | undefined\n): Promise<string | undefined> {\n assertNonNull(metadata);\n const username = metadata?.[Header.Username]?.values[0];\n if (username) {\n return username;\n }\n\n const userId = metadata?.[Header.User]?.values[0];\n if (!userId) {\n return undefined;\n }\n\n return getUsernameById(userId, metadata);\n}\n\n/** @internal */\nexport async function getCurrentUserFromMetadata(\n metadata: Metadata | undefined\n): Promise<User | undefined> {\n assertNonNull(metadata);\n const username = metadata?.[Header.Username]?.values[0];\n if (username) {\n return User.getByUsername(username, metadata);\n }\n\n const userId = metadata?.[Header.User]?.values[0];\n if (!userId && isT2ID(userId)) {\n return User.getById(userId, metadata);\n }\n\n return undefined;\n}\n", "import type {\n Comment as CommentProto,\n JsonWrappedComment_WrappedComment,\n Metadata,\n RedditObject,\n WrappedRedditObject,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { RichTextBuilder } from '@devvit/shared-types/richtext/RichTextBuilder.js';\nimport type { T1ID, T2ID, T3ID, T5ID } from '@devvit/shared-types/tid.js';\nimport { asT1ID, asT2ID, asT3ID, asT5ID, isCommentId, isT1ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { RunAs } from '../common.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { richtextToString } from '../helpers/richtextToString.js';\nimport type { ListingFetchOptions, ListingFetchResponse, MoreObject } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport { ModNote } from './ModNote.js';\nimport { User } from './User.js';\n\nexport type CommentSort =\n | 'confidence'\n | 'top'\n | 'new'\n | 'controversial'\n | 'old'\n | 'random'\n | 'qa'\n | 'live';\n\nexport type GetCommentsOptions = {\n postId: string;\n commentId?: string | undefined;\n depth?: number;\n pageSize?: number;\n limit?: number;\n sort?: CommentSort;\n};\n\ntype GetCommentsListingOptions = {\n postId: T3ID;\n commentId?: T1ID;\n depth?: number;\n pageSize?: number;\n limit?: number;\n sort?: CommentSort;\n};\n\nexport type CommentSubmissionOptions =\n | {\n text: string;\n runAs?: 'USER' | 'APP';\n }\n | {\n richtext: object | RichTextBuilder;\n runAs?: 'USER' | 'APP';\n };\n\nexport type EditCommentOptions = CommentSubmissionOptions;\nexport type ReplyToCommentOptions = CommentSubmissionOptions;\n\nexport type GetCommentsByUserOptions = {\n username: string;\n sort?: 'hot' | 'new' | 'top' | 'controversial';\n timeframe?: 'hour' | 'day' | 'week' | 'month' | 'year' | 'all';\n pageSize?: number;\n limit?: number;\n after?: string;\n before?: string;\n};\n\nexport class Comment {\n #id: T1ID;\n #authorId?: T2ID;\n #authorName: string;\n #body: string;\n #createdAt: Date;\n #parentId: T1ID | T3ID;\n #postId: T3ID;\n #subredditId: T5ID;\n #subredditName: string;\n #replies: Listing<Comment>;\n #approved: boolean;\n #approvedAtUtc: number;\n #bannedAtUtc: number;\n #edited: boolean;\n #locked: boolean;\n #removed: boolean;\n #stickied: boolean;\n #spam: boolean;\n #distinguishedBy?: string;\n #numReports: number;\n #collapsedBecauseCrowdControl: boolean;\n #score: number;\n #permalink: string;\n #modReportReasons: string[];\n #userReportReasons: string[];\n #url: string;\n #ignoringReports: boolean;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: RedditObject | CommentProto, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'Comment id is null or undefined');\n assertNonNull(data.body, 'Comment body is null or undefined');\n assertNonNull(data.createdUtc, 'Comment is missing created date');\n assertNonNull(data.author, 'Comment author is null or undefined');\n assertNonNull(data.parentId, 'Comment parentId is null or undefined');\n assertNonNull(data.linkId, 'Comment linkId is null or undefined');\n assertNonNull(data.permalink, 'Comment permalink is null or undefined');\n assertNonNull(data.subreddit, 'Comment is missing subreddit name');\n assertNonNull(data.subredditId, 'Comment is missing subreddit id');\n\n this.#id = asT1ID(`t1_${data.id}`);\n this.#authorId = data.authorFullname ? asT2ID(data.authorFullname) : undefined;\n this.#authorName = data.author;\n this.#body = data.body;\n this.#subredditId = asT5ID(data.subredditId);\n this.#subredditName = data.subreddit;\n this.#parentId = isCommentId(data.parentId) ? asT1ID(data.parentId) : asT3ID(data.parentId);\n this.#postId = asT3ID(data.linkId);\n this.#edited = data.edited ?? false;\n this.#locked = data.locked ?? false;\n this.#removed = data.removed ?? false;\n this.#stickied = data.stickied ?? false;\n this.#approved = data.approved ?? false;\n this.#approvedAtUtc = data.approvedAtUtc ?? 0;\n this.#bannedAtUtc = data.bannedAtUtc ?? 0;\n this.#spam = data.spam ?? false;\n this.#distinguishedBy = data.distinguished;\n this.#numReports = data.numReports ?? 0;\n this.#collapsedBecauseCrowdControl = data.collapsedBecauseCrowdControl ?? false;\n this.#score = data.score ?? 0;\n this.#permalink = data.permalink;\n // R2 API does not include a URL for a comment, just a permalink\n this.#url = new URL(data.permalink ?? '', 'https://www.reddit.com/').toString();\n this.#ignoringReports = data.ignoreReports ?? false;\n\n this.#modReportReasons = ((data.modReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n this.#userReportReasons = ((data.userReports as unknown as [string, string]) ?? []).map(\n ([reason]) => reason\n );\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#replies = Comment.#getCommentsListing(\n {\n postId: this.#postId,\n commentId: this.#id,\n },\n metadata\n );\n\n this.#metadata = metadata;\n }\n\n get id(): T1ID {\n return this.#id;\n }\n\n get authorId(): T2ID | undefined {\n return this.#authorId;\n }\n\n get authorName(): string {\n return this.#authorName;\n }\n\n get subredditId(): T5ID {\n return this.#subredditId;\n }\n\n get subredditName(): string {\n return this.#subredditName;\n }\n\n get body(): string {\n return this.#body;\n }\n\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n get parentId(): T1ID | T3ID {\n return this.#parentId;\n }\n\n get postId(): T3ID {\n return this.#postId;\n }\n\n get replies(): Listing<Comment> {\n return this.#replies;\n }\n\n get distinguishedBy(): string | undefined {\n return this.#distinguishedBy;\n }\n\n get locked(): boolean {\n return this.#locked;\n }\n\n get stickied(): boolean {\n return this.#stickied;\n }\n\n get removed(): boolean {\n return this.#removed;\n }\n\n get approved(): boolean {\n return this.#approved;\n }\n\n get approvedAtUtc(): number {\n return this.#approvedAtUtc;\n }\n\n get bannedAtUtc(): number {\n return this.#bannedAtUtc;\n }\n\n get spam(): boolean {\n return this.#spam;\n }\n\n get edited(): boolean {\n return this.#edited;\n }\n\n get numReports(): number {\n return this.#numReports;\n }\n\n get collapsedBecauseCrowdControl(): boolean {\n return this.#collapsedBecauseCrowdControl;\n }\n\n get score(): number {\n return this.#score;\n }\n\n get permalink(): string {\n return this.#permalink;\n }\n\n get userReportReasons(): string[] {\n return this.#userReportReasons;\n }\n\n get modReportReasons(): string[] {\n return this.#modReportReasons;\n }\n\n get url(): string {\n return this.#url;\n }\n\n get ignoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n toJSON(): Pick<\n Comment,\n | 'id'\n | 'authorName'\n | 'subredditId'\n | 'subredditName'\n | 'body'\n | 'createdAt'\n | 'parentId'\n | 'postId'\n | 'replies'\n | 'approved'\n | 'locked'\n | 'removed'\n | 'stickied'\n | 'spam'\n | 'edited'\n | 'distinguishedBy'\n | 'numReports'\n | 'collapsedBecauseCrowdControl'\n | 'score'\n | 'permalink'\n | 'userReportReasons'\n | 'modReportReasons'\n | 'url'\n | 'ignoringReports'\n > {\n return {\n id: this.id,\n authorName: this.authorName,\n subredditId: this.subredditId,\n subredditName: this.subredditName,\n body: this.body,\n createdAt: this.createdAt,\n parentId: this.parentId,\n postId: this.postId,\n replies: this.replies,\n approved: this.approved,\n locked: this.locked,\n removed: this.removed,\n stickied: this.stickied,\n spam: this.spam,\n edited: this.edited,\n distinguishedBy: this.distinguishedBy,\n numReports: this.numReports,\n collapsedBecauseCrowdControl: this.collapsedBecauseCrowdControl,\n score: this.score,\n permalink: this.permalink,\n modReportReasons: this.modReportReasons,\n userReportReasons: this.userReportReasons,\n url: this.url,\n ignoringReports: this.ignoringReports,\n };\n }\n\n isLocked(): boolean {\n return this.#locked;\n }\n\n isApproved(): boolean {\n return this.#approved;\n }\n\n isRemoved(): boolean {\n return this.#removed;\n }\n\n isSpam(): boolean {\n return this.#spam;\n }\n\n isStickied(): boolean {\n return this.#stickied;\n }\n\n isDistinguished(): boolean {\n return Boolean(this.#distinguishedBy);\n }\n\n isEdited(): boolean {\n return this.#edited;\n }\n\n isIgnoringReports(): boolean {\n return this.#ignoringReports;\n }\n\n async delete(): Promise<void> {\n return Comment.delete(this.id, this.#metadata);\n }\n\n async edit(options: EditCommentOptions): Promise<this> {\n const newComment = await Comment.edit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n\n this.#body = newComment.body;\n this.#edited = newComment.edited;\n\n return this;\n }\n\n async approve(): Promise<void> {\n await Comment.approve(this.id, this.#metadata);\n this.#approved = true;\n this.#removed = false;\n }\n\n async remove(isSpam: boolean = false): Promise<void> {\n await Comment.remove(this.id, isSpam, this.#metadata);\n this.#removed = true;\n this.#spam = isSpam;\n this.#approved = false;\n }\n\n async lock(): Promise<void> {\n await Comment.lock(this.id, this.#metadata);\n this.#locked = true;\n }\n\n async unlock(): Promise<void> {\n await Comment.unlock(this.id, this.#metadata);\n this.#locked = false;\n }\n\n async reply(options: ReplyToCommentOptions): Promise<Comment> {\n return Comment.submit(\n {\n id: this.id,\n ...options,\n },\n this.#metadata\n );\n }\n\n async getAuthor(): Promise<User | undefined> {\n return User.getByUsername(this.#authorName, this.#metadata);\n }\n\n async distinguish(makeSticky: boolean = false): Promise<void> {\n const { distinguishedBy, stickied } = await Comment.distinguish(\n this.id,\n makeSticky,\n false,\n this.#metadata\n );\n this.#distinguishedBy = distinguishedBy;\n this.#stickied = stickied;\n }\n\n async distinguishAsAdmin(makeSticky: boolean = false): Promise<void> {\n const { distinguishedBy, stickied } = await Comment.distinguish(\n this.id,\n makeSticky,\n true,\n this.#metadata\n );\n this.#distinguishedBy = distinguishedBy;\n this.#stickied = stickied;\n }\n\n async undistinguish(): Promise<void> {\n const { distinguishedBy, stickied } = await Comment.undistinguish(this.id, this.#metadata);\n this.#distinguishedBy = distinguishedBy;\n this.#stickied = stickied;\n }\n\n async ignoreReports(): Promise<void> {\n await Comment.ignoreReports(this.id, this.#metadata);\n this.#ignoringReports = true;\n }\n\n async unignoreReports(): Promise<void> {\n await Comment.unignoreReports(this.id, this.#metadata);\n this.#ignoringReports = false;\n }\n\n /**\n * Add a mod note for why the comment was removed\n *\n * @param options.reasonId id of a Removal Reason - you can leave this as an empty string if you don't have one\n * @param options.modNote the reason for removal (maximum 100 characters) (optional)\n * @returns\n */\n addRemovalNote(options: { reasonId: string; modNote?: string }): Promise<void> {\n return ModNote.addRemovalNote({ itemIds: [this.#id], ...options }, this.#metadata);\n }\n\n /** @internal */\n static async getById(id: T1ID, metadata: Metadata | undefined): Promise<Comment> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const commentId: T1ID = isT1ID(id) ? id : `t1_${id}`;\n\n const response = await client.Info(\n {\n subreddits: [],\n thingIds: [commentId],\n },\n metadata\n );\n\n if (!response.data?.children?.[0]?.data) {\n throw new Error('not found');\n }\n\n return new Comment(response.data.children[0].data, metadata);\n }\n\n /** @internal */\n static getComments(\n options: GetCommentsOptions,\n metadata: Metadata | undefined\n ): Listing<Comment> {\n const { postId, commentId, ...rest } = options;\n return Comment.#getCommentsListing(\n {\n postId: asT3ID(postId),\n commentId: commentId ? asT1ID(commentId) : undefined,\n ...rest,\n },\n metadata\n );\n }\n\n /** @internal */\n static async edit(\n options: CommentSubmissionOptions & { id: T1ID },\n metadata: Metadata | undefined\n ): Promise<Comment> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const { id } = options;\n\n let richtextString: string | undefined;\n if ('richtext' in options) {\n richtextString = richtextToString(options.richtext);\n }\n\n const response = await client.EditUserText(\n {\n thingId: id,\n text: 'text' in options ? options.text : '',\n richtextJson: richtextString,\n runAs: RunAs.APP,\n },\n metadata\n );\n\n if (response.json?.errors?.length) {\n throw new Error('Failed to edit comment');\n }\n\n const comment = response.json?.data?.things?.[0]?.data;\n assertNonNull(comment);\n\n return new Comment(comment, metadata);\n }\n\n /** @internal */\n static async delete(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Del(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async approve(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Approve(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async remove(\n id: T1ID,\n isSpam: boolean = false,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.Remove(\n {\n id,\n spam: isSpam,\n },\n metadata\n );\n }\n\n /** @internal */\n static async lock(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Lock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unlock(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n await client.Unlock(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async submit(\n options: CommentSubmissionOptions & { id: T1ID | T3ID },\n metadata: Metadata | undefined\n ): Promise<Comment> {\n const { runAs = 'APP' } = options;\n const runAsType = RunAs[runAs];\n const client =\n runAsType === RunAs.USER\n ? Devvit.userActionsPlugin\n : Devvit.redditAPIPlugins.LinksAndComments;\n const { id } = options;\n\n let richtextString: string | undefined;\n if ('richtext' in options) {\n richtextString = richtextToString(options.richtext);\n }\n\n const response = await client.Comment(\n {\n thingId: id,\n text: 'text' in options ? options.text : '',\n richtextJson: richtextString,\n runAs: runAsType,\n },\n metadata\n );\n\n // TODO: figure out a better errors to throw\n if (response.json?.errors?.length) {\n throw new Error('failed to reply to comment');\n }\n\n const data = response.json?.data?.things?.[0]?.data;\n assertNonNull(data);\n\n return new Comment(data, metadata);\n }\n\n /** @internal */\n static async distinguish(\n id: T1ID,\n sticky: boolean,\n asAdmin: boolean,\n metadata: Metadata | undefined\n ): Promise<{\n distinguishedBy: string | undefined;\n stickied: boolean;\n }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: asAdmin ? 'admin' : 'yes',\n sticky,\n },\n metadata\n );\n\n const comment = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(comment);\n\n return {\n distinguishedBy: comment.distinguished,\n stickied: Boolean(comment.stickied),\n };\n }\n\n /** @internal */\n static async undistinguish(\n id: T1ID,\n metadata: Metadata | undefined\n ): Promise<{\n distinguishedBy: string | undefined;\n stickied: boolean;\n }> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n const response = await client.Distinguish(\n {\n id,\n how: 'no',\n sticky: false,\n },\n metadata\n );\n\n const comment = response.json?.data?.things?.[0]?.data;\n\n assertNonNull(comment);\n\n return {\n distinguishedBy: comment.distinguished,\n stickied: Boolean(comment.stickied),\n };\n }\n\n /** @internal */\n static getCommentsByUser(\n options: GetCommentsByUserOptions,\n metadata: Metadata | undefined\n ): Listing<Comment> {\n const client = Devvit.redditAPIPlugins.Users;\n return new Listing<Comment>({\n hasMore: true,\n before: options.before,\n after: options.after,\n pageSize: options.pageSize,\n limit: options.limit,\n async fetch(fetchOptions) {\n const response = await client.UserWhere(\n {\n username: options.username,\n where: 'comments',\n ...fetchOptions,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to get comments for user');\n\n const children =\n response.data.children?.map((child) => new Comment(child.data!, metadata)) || [];\n\n return {\n children,\n before: response.data.before,\n after: response.data.after,\n };\n },\n });\n }\n\n /** @internal */\n static async ignoreReports(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.IgnoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async unignoreReports(id: T1ID, metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n await client.UnignoreReports(\n {\n id,\n },\n metadata\n );\n }\n\n static #getCommentsListing(\n options: GetCommentsListingOptions,\n metadata: Metadata | undefined,\n depthOffset = 0\n ): Listing<Comment> {\n return new Listing<Comment>({\n limit: options.limit,\n pageSize: options.pageSize,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n let limit = fetchOptions.limit;\n\n const listingsClient = Devvit.redditAPIPlugins.Listings;\n const linksAndCommentsClient = Devvit.redditAPIPlugins.LinksAndComments;\n let commentId = options.commentId;\n\n if (fetchOptions.more) {\n if (fetchOptions.more.children.length) {\n const more = fetchOptions.more;\n\n // The maximum page size for MoreChildren is 100\n if (!limit || limit > 100) {\n limit = 100;\n }\n\n const moreIds = more.children.splice(0, limit);\n\n const response = await linksAndCommentsClient.MoreChildren(\n {\n linkId: options.postId,\n children: moreIds,\n sort: options.sort,\n },\n metadata\n );\n\n if (!response.json?.data?.things?.length) {\n return { children: [] };\n }\n\n const { children } = Comment.#buildCommentsTree(\n response.json.data.things,\n options.postId,\n options,\n metadata\n );\n\n return { children, more: more.children.length ? more : undefined };\n } else {\n // parentId is only ever T3 for the MoreChildren case.\n commentId = fetchOptions.more.parentId as T1ID;\n depthOffset = depthOffset + fetchOptions.more.depth;\n }\n }\n\n const response = await listingsClient.Comments(\n {\n article: options.postId.substring(3),\n comment: commentId?.substring(3),\n limit,\n depth: options.depth,\n sort: options.sort,\n },\n metadata\n );\n\n // The first item of `response.listings` is always the post (t3) listing\n // and the second item is the comments (t1) listing.\n let responseChildren = response.listings?.[1]?.data?.children ?? [];\n\n const topLevelComment = responseChildren[0];\n if (commentId && topLevelComment?.data?.replyList?.data) {\n responseChildren = topLevelComment.data.replyList.data.children;\n }\n\n return Comment.#buildCommentsTree(\n responseChildren,\n commentId ?? options.postId,\n options,\n metadata,\n depthOffset\n );\n },\n });\n }\n\n static #buildCommentsTree(\n redditObjects: WrappedRedditObject[] | JsonWrappedComment_WrappedComment[],\n parentId: string,\n options: GetCommentsOptions,\n metadata: Metadata | undefined,\n depthOffset: number = 0\n ): ListingFetchResponse<Comment> {\n const children: Comment[] = [];\n let more: MoreObject | undefined;\n\n // Map of comments to help set parent-child relationship between comments returned by MoreChildren.\n const commentsMap: { [id: string]: Comment } = {};\n\n for (const child of redditObjects) {\n if (!child.data) {\n continue;\n }\n\n if (child.data.depth != null) {\n child.data.depth = child.data.depth + depthOffset;\n }\n\n // Prevent returning comments that are beyond the maximum depth requested.\n if (child.data.depth != null && options.depth != null && child.data.depth >= options.depth) {\n continue;\n }\n\n const parentComment = child.data.parentId ? commentsMap[child.data.parentId] : undefined;\n\n if (child.kind === 't1') {\n // Sometimes MoreChildren API returns a comment that has already been seen.\n if (child.data.name === parentId) {\n continue;\n }\n\n const comment = new Comment(child.data, metadata);\n\n commentsMap[comment.id] = comment;\n\n comment.#replies = Comment.#getCommentsListing(\n {\n ...options,\n postId: comment.postId,\n commentId: comment.id,\n },\n metadata,\n depthOffset\n );\n\n // Preload the comment's replies Listing\n if ('replyList' in child.data && child.data.replyList?.data) {\n const { children, more } = Comment.#buildCommentsTree(\n child.data.replyList.data.children,\n comment.id,\n options,\n metadata,\n depthOffset\n );\n\n if (children.length) {\n comment.replies.children.push(...children);\n }\n\n if (more) {\n comment.replies.setMore(more);\n }\n }\n\n // Since the replies for this comment were already load we can skip the first fetch call\n comment.replies.preventInitialFetch();\n\n if (parentComment) {\n parentComment.replies.children.push(comment);\n } else {\n children.push(comment);\n }\n } else if (child.kind === 'more' && child.data.parentId && child.data.depth != null) {\n const thisMore = {\n parentId: isCommentId(child.data.parentId)\n ? asT1ID(child.data.parentId)\n : asT3ID(child.data.parentId),\n children: child.data.children ?? [],\n depth: child.data.depth,\n };\n\n if (parentComment) {\n parentComment.replies.setMore(thisMore);\n } else if (thisMore.parentId === parentId) {\n more = thisMore;\n }\n }\n }\n\n return { children, more };\n }\n}\n", "import type { AboutLogResponse, Metadata } from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport type { ListingFetchOptions, ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\n\nexport type ModActionTarget = {\n id: string;\n author?: string;\n body?: string;\n permalink?: string;\n title?: string;\n};\n\nexport interface ModAction {\n id: string;\n type: ModActionType;\n moderatorName: string;\n moderatorId: string;\n createdAt: Date;\n subredditName: string;\n subredditId: string;\n description?: string;\n details?: string;\n target?: ModActionTarget;\n}\n\nexport type ModActionType =\n | 'banuser'\n | 'unbanuser'\n | 'spamlink'\n | 'removelink'\n | 'approvelink'\n | 'spamcomment'\n | 'removecomment'\n | 'approvecomment'\n | 'addmoderator'\n | 'showcomment'\n | 'invitemoderator'\n | 'uninvitemoderator'\n | 'acceptmoderatorinvite'\n | 'removemoderator'\n | 'addcontributor'\n | 'removecontributor'\n | 'editsettings'\n | 'editflair'\n | 'distinguish'\n | 'marknsfw'\n | 'wikibanned'\n | 'wikicontributor'\n | 'wikiunbanned'\n | 'wikipagelisted'\n | 'removewikicontributor'\n | 'wikirevise'\n | 'wikipermlevel'\n | 'ignorereports'\n | 'unignorereports'\n | 'setpermissions'\n | 'setsuggestedsort'\n | 'sticky'\n | 'unsticky'\n | 'setcontestmode'\n | 'unsetcontestmode'\n | 'lock'\n | 'unlock'\n | 'muteuser'\n | 'unmuteuser'\n | 'createrule'\n | 'editrule'\n | 'reorderrules'\n | 'deleterule'\n | 'spoiler'\n | 'unspoiler'\n | 'modmail_enrollment'\n | 'community_styling'\n | 'community_widgets'\n | 'markoriginalcontent'\n | 'collections'\n | 'events'\n | 'create_award'\n | 'disable_award'\n | 'delete_award'\n | 'enable_award'\n | 'mod_award_given'\n | 'hidden_award'\n | 'add_community_topics'\n | 'remove_community_topics'\n | 'create_scheduled_post'\n | 'edit_scheduled_post'\n | 'delete_scheduled_post'\n | 'submit_scheduled_post'\n | 'edit_post_requirements'\n | 'invitesubscriber'\n | 'submit_content_rating_survey'\n | 'adjust_post_crowd_control_level'\n | 'enable_post_crowd_control_filter'\n | 'disable_post_crowd_control_filter'\n | 'deleteoverriddenclassification'\n | 'overrideclassification'\n | 'reordermoderators'\n | 'snoozereports'\n | 'unsnoozereports'\n | 'addnote'\n | 'deletenote'\n | 'addremovalreason'\n | 'createremovalreason'\n | 'updateremovalreason'\n | 'deleteremovalreason'\n | 'reorderremovalreason'\n | 'dev_platform_app_changed'\n | 'dev_platform_app_disabled'\n | 'dev_platform_app_enabled'\n | 'dev_platform_app_installed'\n | 'dev_platform_app_uninstalled';\n\nexport type GetModerationLogOptions = ListingFetchOptions & {\n /** Subreddit name */\n subredditName: string;\n /** (optional) A moderator filter. Accepts an array of usernames */\n moderatorUsernames?: string[];\n /** Type of the Moderator action */\n type?: ModActionType;\n};\n\n/** @internal */\nexport function _getModerationLog(\n options: GetModerationLogOptions,\n metadata: Metadata | undefined\n): Listing<ModAction> {\n const client = Devvit.redditAPIPlugins.Moderation;\n\n return new Listing({\n hasMore: true,\n after: options.after,\n before: options.before,\n limit: options.limit,\n pageSize: options.pageSize,\n fetch: async (fetchOptions: ListingFetchOptions) => {\n const response = await client.AboutLog(\n {\n subreddit: options.subredditName,\n mod: options.moderatorUsernames ? options.moderatorUsernames.join(',') : undefined,\n type: options.type,\n ...fetchOptions,\n },\n metadata\n );\n\n return aboutLogResponseToModActions(response);\n },\n });\n}\n\nfunction aboutLogResponseToModActions(response: AboutLogResponse): ListingFetchResponse<ModAction> {\n if (!response.data?.children) {\n throw new Error('AboutLogResponse is missing children');\n }\n\n const children = response.data.children.map((child) => {\n if (!child.data) {\n throw new Error('ModAction from AboutLogResponse is missing or invalid');\n }\n\n const {\n id,\n mod,\n modId36,\n createdUtc,\n subreddit,\n subredditNamePrefixed,\n action,\n srId36,\n description,\n details,\n targetAuthor,\n targetBody,\n targetFullname,\n targetPermalink,\n targetTitle,\n } = child.data;\n\n assertNonNull(id, 'ModAction from AboutLogResponse is missing id');\n assertNonNull(mod, 'ModAction from AboutLogResponse is missing mod');\n assertNonNull(modId36, 'ModAction from AboutLogResponse is missing modId36');\n assertNonNull(createdUtc, 'ModAction from AboutLogResponse is missing createdUtc');\n assertNonNull(subreddit, 'ModAction from AboutLogResponse is missing subreddit');\n assertNonNull(\n subredditNamePrefixed,\n 'ModAction from AboutLogResponse is missing subredditNamePrefixed'\n );\n assertNonNull(action, 'ModAction from AboutLogResponse is missing action');\n assertNonNull(srId36, 'ModAction from AboutLogResponse is missing srId36');\n\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(createdUtc);\n\n const modAction: ModAction = {\n id,\n type: action as ModActionType,\n moderatorName: mod,\n moderatorId: `t2_${modId36}`,\n createdAt,\n subredditName: subreddit,\n subredditId: `t5_${srId36}`,\n description,\n details,\n target: targetFullname\n ? {\n id: targetFullname,\n author: targetAuthor,\n body: targetBody,\n permalink: targetPermalink,\n title: targetTitle,\n }\n : undefined,\n };\n\n return modAction;\n });\n\n return {\n children,\n after: response.data.after,\n before: response.data.before,\n };\n}\n", "import {\n type ConversationData as ProtosConversationData,\n type MessageData as ProtosMessageData,\n type Metadata,\n type ModActionData as ProtosModActionData,\n} from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { asT5ID, type T5ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\n\nexport type SubredditData = {\n id?: string;\n name?: string;\n displayName?: string;\n communityIcon?: string;\n keyColor?: string;\n subscribers?: number;\n primaryColor?: string;\n lastUpdated?: string;\n icon?: string;\n};\n\nexport type GetConversationsRequest = {\n /** modmail conversation id */\n after?: string;\n /** array of subreddit names */\n subreddits?: string[];\n /** an integer between 1 and 100 (default: 25) */\n limit?: number;\n /**\n * Sort by:\n * - `recent` - Order by whenever anyone last updated the conversation, mod or participant\n * - `mod` - Order by the last time a mod updated the conversation\n * - `user` - Order by the last time a participant user updated the conversation\n * - `unread` - Order by the most recent unread message in the conversation for this mod\n */\n sort?: 'recent' | 'mod' | 'user' | 'unread';\n /**\n * Filter by conversation state\n *\n * A conversation can be in more than one state.\n * For example, a conversation may be both 'highlighted' and 'inprogress'.\n */\n state?: ConversationStateFilter;\n};\n\n/**\n * A Conversation State is a way in which conversations may be filtered within the UI.\n *\n * A conversation can be in more than one state.\n * For example, a conversation may be both 'highlighted' and 'inprogress'.\n */\nexport type ConversationStateFilter =\n | 'all'\n | 'new'\n | 'inprogress'\n | 'archived'\n | 'appeals'\n | 'join_requests'\n | 'highlighted'\n | 'mod'\n | 'notifications'\n | 'inbox'\n | 'filtered'\n | 'default';\n\n/**\n * Conversation participant\n */\nexport type Participant = {\n isMod?: boolean;\n isAdmin?: boolean;\n name?: string;\n isOp?: boolean;\n isParticipant?: boolean;\n isApproved?: boolean;\n isHidden?: boolean;\n id?: number;\n isDeleted?: boolean;\n};\n\nexport type ConversationUserData = {\n /** User ID*/\n id?: string;\n /** Username */\n name?: string;\n /** Recent comments */\n recentComments: {\n [id: string]: {\n comment?: string;\n date?: string;\n permalink?: string;\n title?: string;\n };\n };\n /** Recent posts */\n recentPosts: {\n [id: string]: {\n date?: string;\n permalink?: string;\n title?: string;\n };\n };\n /** Recent conversations */\n recentConvos: {\n [id: string]: {\n date?: string;\n permalink?: string;\n id?: string;\n subject?: string;\n };\n };\n isSuspended?: boolean;\n isShadowBanned?: boolean;\n muteStatus?: { isMuted?: boolean; muteCount?: number; endDate?: string; reason?: string };\n banStatus?: { isBanned?: boolean; isPermanent?: boolean; endDate?: string; reason?: string };\n approveStatus?: { isApproved?: boolean };\n /** When was created */\n created?: string;\n};\n\nexport enum ModMailConversationState {\n New = 'New',\n InProgress = 'InProgress',\n Archived = 'Archived',\n Appeals = 'Appeals',\n JoinRequests = 'JoinRequests',\n Filtered = 'Filtered',\n}\n\nconst R2_TO_MODMAIL_CONVERSATION_STATE: { [key: number]: ModMailConversationState } = {\n 0: ModMailConversationState.New,\n 1: ModMailConversationState.InProgress,\n 2: ModMailConversationState.Archived,\n 3: ModMailConversationState.Appeals,\n 4: ModMailConversationState.JoinRequests,\n 5: ModMailConversationState.Filtered,\n};\n\n/**\n * An ActionType describes a particular logged action within a conversation. For example,\n * if a mod highlights a conversation, a ModerationAction record with the type `Highlighted`\n * would be created.\n */\nexport enum ModMailActionType {\n Highlighted = 'Highlighted',\n Unhighlighted = 'Unhighlighted',\n Archived = 'Archived',\n Unarchived = 'Unarchived',\n ReportedToAdmins = 'ReportedToAdmins',\n Muted = 'Muted',\n Unmuted = 'Unmuted',\n Banned = 'Banned',\n Unbanned = 'Unbanned',\n Approved = 'Approved',\n Disapproved = 'Disapproved',\n Filtered = 'Filtered',\n Unfiltered = 'Unfiltered',\n}\n\nconst R2_TO_MOD_ACTION_TYPE: { [key: number]: ModMailActionType } = {\n 0: ModMailActionType.Highlighted,\n 1: ModMailActionType.Unhighlighted,\n 2: ModMailActionType.Archived,\n 3: ModMailActionType.Unarchived,\n 4: ModMailActionType.ReportedToAdmins,\n 5: ModMailActionType.Muted,\n 6: ModMailActionType.Unmuted,\n 7: ModMailActionType.Banned,\n 8: ModMailActionType.Unbanned,\n 9: ModMailActionType.Approved,\n 10: ModMailActionType.Disapproved,\n 11: ModMailActionType.Filtered,\n 12: ModMailActionType.Unfiltered,\n};\n\nexport type ConversationData = {\n /** Conversation ID */\n id?: string;\n /** Suject of the conversation */\n subject?: string;\n /**\n * Subreddit owning the modmail conversation\n */\n subreddit?: {\n displayName?: string;\n id?: string;\n };\n /**\n * A ConversationType specifies whether a conversation is with a subreddit\n * itself, with another user, or with another subreddit entirely.\n * - `internal` - This is a conversation with another user outside of the subreddit. The participant ID is that user's ID.\n * - `sr_user` - This is a Mod Discussion, internal to the subreddit. There is no other participant.\n * - `sr_sr` - This is a conversation is with another subreddit. The participant will have a subreddit ID.\n */\n conversationType?: string;\n /** Is the conversation automatically generated e.g. from automod, u/reddit */\n isAuto?: boolean;\n /** Participant. Is absent for mod discussions */\n participant?: Participant;\n /** The last datetime a user made any interaction with the conversation */\n lastUserUpdate?: string;\n /** Is the conversation internal (i.e. mod only) */\n isInternal?: boolean;\n /**\n * The last datetime a mod from the owning subreddit made any interaction\n * with the conversation.\n *\n * (Note that if this is a subreddit to subreddit conversation, the mods of\n * the participant subreddit are irrelevant and do not affect this field.)\n */\n lastModUpdate?: string;\n /** The authors of each message in the modmail conversation. */\n authors: Participant[];\n /** The datetime of the last time the conversation was update. */\n lastUpdated?: string;\n /** State of the conversation */\n state?: ModMailConversationState;\n /** The datetime of the last unread message within this conversation for the current viewer. */\n lastUnread?: string;\n /** Is the conversation highlighted */\n isHighlighted?: boolean;\n /** Number of messages (not actions) in the conversation */\n numMessages?: number;\n /**\n * Conversation messages\n *\n * @example\n * ```ts\n * const arrayOfMessages = Object.values(conversation.messages);\n * const messageById = conversation.messages[messageId];\n * ```\n */\n messages: { [id: string]: MessageData };\n /**\n * Conversation mod actions\n *\n * @example\n * ```ts\n * const arrayOfModActions = Object.values(conversation.modActions);\n * const modActionById = conversation.modActions[modActionId];\n * ```\n */\n modActions: { [id: string]: ModActionData };\n};\n\nexport type ModActionData = {\n /** Action id */\n id?: string;\n /** Type of the action */\n actionType: ModMailActionType;\n /** When the action happened */\n date?: string;\n /** Action author */\n author?: {\n /** User id */\n id?: number;\n /** User name */\n name?: string;\n isMod?: boolean;\n isAdmin?: boolean;\n isHidden?: boolean;\n isDeleted?: boolean;\n };\n};\n\nexport type MessageData = {\n /** Message ID */\n id?: string;\n /** Message body */\n body?: string;\n /** When was created */\n date?: string;\n author?: Participant;\n isInternal?: boolean;\n bodyMarkdown?: string;\n participatingAs?: string;\n};\n\nexport type ConversationResponse = {\n conversation: ConversationData;\n};\n\nexport type WithUserData = {\n user?: ConversationUserData;\n};\n\nexport type UnreadCountResponse = {\n archived?: number;\n appeals?: number;\n highlighted?: number;\n notifications?: number;\n joinRequests?: number;\n filtered?: number;\n new?: number;\n inprogress?: number;\n mod?: number;\n};\n\ntype ParticipantSubreddit = {\n id: string;\n name: string;\n};\n\nexport type GetConversationResponse = {\n conversation?: ConversationData;\n /** If the conversation is with another subreddit, what subreddit we are communicating with. */\n participantSubreddit?: ParticipantSubreddit;\n} & WithUserData;\n\nexport type GetConversationsResponse = {\n /**\n * Conversations key-value map\n */\n conversations: { [id: string]: ConversationData };\n viewerId?: string;\n /**\n * Array of conversation ids, ordered by the sort parameter specified in {@link GetConversationsRequest}.\n */\n conversationIds: string[];\n};\n\n/**\n * Class providing the methods for working with Mod Mail\n */\nexport class ModMailService {\n readonly #metadata: Metadata;\n readonly notificationSubjectPrefix = '[notification]';\n\n /**\n * @internal\n */\n constructor(metadata: Metadata) {\n this.#metadata = metadata;\n }\n\n /**\n * Marks all conversations read for a particular conversation state within the passed list of subreddits.\n *\n * @param subreddits Array of subreddit names\n * @param state One of the possible conversation states ('all' to read all conversations)\n *\n * @returns conversationIds\n *\n * @example\n * ```ts\n * const conversationIds = await modMail().bulkReadConversations(\n * ['askReddit', 'myAwesomeSubreddit'],\n * 'filtered'\n * );\n * ```\n */\n async bulkReadConversations(\n subreddits: string[],\n state: ConversationStateFilter\n ): Promise<string[]> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const { conversationIds } = await client.BulkReadConversations(\n {\n entity: subreddits.join(','),\n state,\n },\n this.#metadata\n );\n\n return conversationIds;\n }\n\n /**\n * Get conversations for a logged in user or subreddits\n *\n * @param params.after id of a modmail\n * @param params.subreddits array of subreddit names\n * @param params.limit an integer between 1 and 100 (default: 25)\n * @param params.sort one of (recent, mod, user, unread)\n * @param params.state One of the possible conversation states ('all' to read all conversations)\n *\n * @example\n * ```ts\n * const {viewerId, conversations} = await modMail().getConversations({\n * after: 'abcdef',\n * limit: 42\n * });\n *\n * const arrayOfConversations = Object.values(conversations);\n * ```\n */\n async getConversations(params: GetConversationsRequest): Promise<GetConversationsResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.GetConversations(\n {\n after: params.after,\n entity: params.subreddits ? params.subreddits.join(',') : undefined,\n limit: params.limit,\n sort: params.sort,\n state: params.state,\n },\n this.#metadata\n );\n\n const conversations: { [id: string]: ConversationData } = {};\n\n for (const id in response.conversations) {\n conversations[id] = this.#transformConversationData({\n protoConversation: response.conversations[id],\n protoMessages: response.messages,\n protoModActions: {},\n });\n }\n\n return {\n conversations,\n viewerId: response.viewerId,\n conversationIds: response.conversationIds,\n };\n }\n\n /**\n * Returns all messages, mod actions and conversation metadata for a given conversation id\n *\n * @param params.conversationId id of a modmail conversation\n * @param params.markRead should be marked as read (default: false)\n *\n * @example\n * ```ts\n * const { conversation, messages, modActions, user } = await modMail().getConversation({ conversationId: 'abcdef', markRead: true });\n * ```\n */\n async getConversation(params: {\n /** a modmail conversation id */\n conversationId: string;\n /** mark read? */\n markRead?: boolean;\n }): Promise<GetConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.GetConversation(\n { ...params, markRead: !!params.markRead },\n this.#metadata\n );\n\n return {\n participantSubreddit: response.participantSubreddit as ParticipantSubreddit | undefined,\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Returns a list of Subreddits that the user moderates with mail permission\n *\n * @example\n * ```ts\n * const subredditsData = await modMail().getSubreddits();\n *\n * for (const subreddit of Object.values(subreddits)) {\n * console.log(subreddit.id);\n * console.log(subreddit.name);\n * }\n * ```\n */\n async getSubreddits(): Promise<{ [key: string]: SubredditData }> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const { subreddits } = await client.Subreddits({}, this.#metadata);\n\n return subreddits;\n }\n\n /**\n * Creates a new conversation for a particular SR.\n *\n * This endpoint will create a ModmailConversation object\n * as well as the first ModmailMessage within the ModmailConversation object.\n *\n * @note\n * Note on {param.to}:\n * The to field for this endpoint is somewhat confusing. It can be:\n * - A User, passed like \"username\" or \"u/username\"\n * - A Subreddit, passed like \"r/subreddit\"\n * - null, meaning an internal moderator discussion\n *\n * In this way to is a bit of a misnomer in modmail conversations.\n * What it really means is the participant of the conversation who is not a mod of the subreddit.\n *\n * If you plan to send a message to the app-account or a moderator of the subreddit, use {@link ModMailService.createModDiscussionConversation}, {@link ModMailService.createModInboxConversation}, or {@link ModMailService.createModNotification} instead.\n * Otherwise, messages sent to the app-account or moderator will automatically be routed to Mod Discussions.\n * @param params.body markdown text\n * @param params.isAuthorHidden is author hidden? (default: false)\n * @param params.subredditName subreddit name\n * @param params.subject subject of the conversation. max 100 characters\n * @param params.to a user (e.g. u/username), a subreddit (e.g. r/subreddit) or null\n *\n * @example\n * ```ts\n * const { conversation, messages, modActions } = await modMail().createConversation({\n * subredditName: 'askReddit',\n * subject: 'Test conversation',\n * body: 'Lorem ipsum sit amet',\n * to: null,\n * });\n * ```\n */\n async createConversation(params: {\n body: string;\n isAuthorHidden?: boolean;\n subredditName: string;\n subject: string;\n to?: string | null;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.CreateConversation(\n {\n body: params.body,\n isAuthorHidden: params.isAuthorHidden ?? false,\n srName: params.subredditName,\n subject: params.subject,\n to: params.to ? params.to : undefined,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Creates a conversation in Mod Discussions with the moderators of the given subredditId.\n *\n * Note: The app must be installed in the subreddit in order to create a conversation in Mod Discussions.\n *\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n * @example\n * ```ts\n * const conversationId = await modMail().createModDiscussionConversation({\n * subject: 'Test conversation',\n * bodyMarkdown: '**Hello there** \\n\\n _Have a great day!_',\n * subredditId: context.subredditId\n * });\n * ```\n */\n async createModDiscussionConversation(params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: string;\n }): Promise<string> {\n return createModmailConversation(\n {\n subject: params.subject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: asT5ID(params.subredditId),\n isInternal: true,\n participantType: 'MODERATOR',\n conversationType: 'INTERNAL',\n },\n this.#metadata\n );\n }\n\n /**\n * Creates a conversation in the Modmail Inbox with the moderators of the given subredditId.\n *\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n * @example\n * ```ts\n * const conversationId = await modMail().createModInboxConversation({\n * subject: 'Test conversation',\n * bodyMarkdown: '**Hello there** \\n\\n _Have a great day!_',\n * subredditId: context.subredditId\n * });\n * ```\n */\n async createModInboxConversation(params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: string;\n }): Promise<string> {\n return createModmailConversation(\n {\n subject: params.subject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: asT5ID(params.subredditId),\n isInternal: false,\n participantType: 'PARTICIPANT_USER',\n conversationType: 'SR_USER',\n },\n this.#metadata\n );\n }\n\n /**\n * Creates a notification in the Modmail Inbox.\n * This function is different from {@link ModMailService.createModInboxConversation} in that the conversation also appears in the \"Notifications\" section of Modmail.\n *\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n * @example\n * ```ts\n * const conversationId = await modMail().createModNotification({\n * subject: 'Test notification',\n * bodyMarkdown: '**Hello there** \\n\\n _This is a notification!_',\n * subredditId: context.subredditId\n * });\n * ```\n */\n async createModNotification(params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: string;\n }): Promise<string> {\n let notificationSubject = params.subject;\n\n if (!params.subject.startsWith(this.notificationSubjectPrefix)) {\n notificationSubject = `${this.notificationSubjectPrefix} ${params.subject}`;\n }\n\n return createModmailConversation(\n {\n subject: notificationSubject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: asT5ID(params.subredditId),\n isInternal: false,\n participantType: 'PARTICIPANT_USER',\n conversationType: 'SR_USER',\n },\n this.#metadata\n );\n }\n\n /**\n * Creates a new message for a particular conversation.\n *\n * @param params.conversationId Id of a modmail conversation\n * @param params.body markdown text\n * @param params.isInternal is internal message? (default: false)\n * @param params.isAuthorHidden is author hidden? (default: false)\n *\n * @example\n * ```ts\n * await modMail().reply({\n * body: 'Lorem ipsum sit amet',\n * conversationId: 'abcdef',\n * });\n * ```\n */\n async reply(params: {\n body: string;\n isAuthorHidden?: boolean;\n isInternal?: boolean;\n conversationId: string;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.CreateConversationMessage(\n {\n body: params.body,\n conversationId: params.conversationId,\n isAuthorHidden: params.isAuthorHidden ?? false,\n isInternal: params.isInternal ?? false,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: {},\n }),\n user: response.user,\n };\n }\n\n /**\n * Marks a conversation as highlighted.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().highlightConversation('abcdef');\n * ```\n */\n async highlightConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.HighlightConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Removes a highlight from a conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().unhighlightConversation('abcdef');\n * ```\n */\n async unhighlightConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.UnhighlightConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Marks a conversation as archived\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().archive('abcdef');\n * ```\n */\n async archiveConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.ArchiveConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Marks conversation as unarchived.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().unarchiveConversation('abcdef');\n * ```\n */\n async unarchiveConversation(conversationId: string): Promise<ConversationResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.UnarchiveConversation(\n {\n conversationId: conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversation!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n };\n }\n\n /**\n * Marks a conversation as read for the user.\n *\n * @param params.conversationId Id of a modmail conversation\n * @param params.numHours For how many hours the conversation needs to be muted. Must be one of 72, 168, or 672 hours\n *\n * @example\n * ```ts\n * await modMail().muteConversation({ conversationId: 'abcdef', numHours: 72 });\n * ```\n */\n async muteConversation(params: {\n conversationId: string;\n numHours: 72 | 168 | 672;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.MuteConversation(\n {\n conversationId: params.conversationId,\n numHours: params.numHours,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Unmutes the non mod user associated with a particular conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().unmuteConversation('abcdef');\n * ```\n */\n async unmuteConversation(conversationId: string): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.UnmuteConversation(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Marks a conversations as read for the user.\n *\n * @param conversationIds An array of ids\n *\n * @example\n * ```ts\n * await modMail().readConversations(['abcdef', 'qwerty']);\n * ```\n */\n async readConversations(conversationIds: string[]): Promise<void> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n await client.Read(\n {\n conversationIds: conversationIds.join(','),\n },\n this.#metadata\n );\n }\n\n /**\n * Marks conversations as unread for the user.\n *\n * @param conversationIds An array of ids\n *\n * @example\n * ```ts\n * await modMail().unreadConversations(['abcdef', 'qwerty']);\n * ```\n */\n async unreadConversations(conversationIds: string[]): Promise<void> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n await client.Unread(\n {\n conversationIds: conversationIds.join(','),\n },\n this.#metadata\n );\n }\n\n /**\n * Approve the non mod user associated with a particular conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().approveConversation('abcdef');\n * ```\n */\n async approveConversation(conversationId: string): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.ApproveConversation(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Disapprove the non mod user associated with a particular conversation.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * await modMail().disapproveConversation('abcdef');\n * ```\n */\n async disapproveConversation(\n conversationId: string\n ): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.DisapproveConversation(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Temporary ban (switch from permanent to temporary ban) the non mod user associated with a particular conversation.\n *\n * @param params.conversationId a modmail conversation id\n * @param params.duration duration in days, max 999\n *\n * @example\n * ```ts\n * await modMail().tempBanConversation({ conversationId: 'abcdef', duration: 42 });\n * ```\n */\n async tempBanConversation(params: {\n conversationId: string;\n duration: number;\n }): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.TempBan(\n {\n ...params,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Unban the non mod user associated with a particular conversation.\n *\n * @param conversationId a modmail conversation id\n *\n * @example\n * ```ts\n * await modMail().unbanConversation('abcdef');\n * ```\n */\n async unbanConversation(conversationId: string): Promise<ConversationResponse & WithUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n const response = await client.Unban(\n {\n conversationId,\n },\n this.#metadata\n );\n\n return {\n conversation: this.#transformConversationData({\n protoConversation: response.conversations!,\n protoMessages: response.messages,\n protoModActions: response.modActions,\n }),\n user: response.user,\n };\n }\n\n /**\n * Endpoint to retrieve the unread conversation count by conversation state.\n *\n * @example\n * ```ts\n * const response = await modMail().getUnreadCount();\n *\n * console.log(response.highlighted);\n * console.log(response.new);\n * ```\n */\n async getUnreadCount(): Promise<UnreadCountResponse> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n return await client.UnreadCount({}, this.#metadata);\n }\n\n /**\n * Returns recent posts, comments and modmail conversations for a given user.\n *\n * @param conversationId Id of a modmail conversation\n *\n * @example\n * ```ts\n * const data = await modMail().getUserConversations('abcdef');\n *\n * console.log(data.recentComments);\n * console.log(data.recentPosts);\n * ```\n */\n async getUserConversations(conversationId: string): Promise<ConversationUserData> {\n const client = Devvit.redditAPIPlugins.NewModmail;\n\n return await client.UserConversations({ conversationId }, this.#metadata);\n }\n\n #transformConversationData({\n protoConversation,\n protoMessages,\n protoModActions,\n }: {\n protoConversation: ProtosConversationData;\n protoMessages: { [id: string]: ProtosMessageData };\n protoModActions: { [id: string]: ProtosModActionData };\n }): ConversationData {\n return {\n ...protoConversation,\n state: R2_TO_MODMAIL_CONVERSATION_STATE[protoConversation.state!],\n messages: this.#getConversationMessages(protoConversation, protoMessages),\n modActions: this.#getConversationModActions(protoConversation, protoModActions),\n };\n }\n\n #getConversationMessages(\n protoConversation: ProtosConversationData,\n protoMessages: { [id: string]: ProtosMessageData }\n ): { [id: string]: MessageData } {\n const messages: { [id: string]: MessageData } = {};\n const messageIds = protoConversation.objIds\n .filter((o) => o.key === 'messages')\n .map(({ id }) => id!);\n\n for (const messageId of messageIds) {\n const protoMessage = protoMessages[messageId];\n if (protoMessage) {\n messages[messageId] = protoMessage;\n }\n }\n\n return messages;\n }\n\n #getConversationModActions(\n protoConversation: ProtosConversationData,\n protoModActions: { [id: string]: ProtosModActionData }\n ): { [id: string]: ModActionData } {\n const modActions: { [id: string]: ModActionData } = {};\n const modActionIds = protoConversation.objIds\n .filter((o) => o.key === 'modActions')\n .map(({ id }) => id!);\n\n for (const modActionId of modActionIds) {\n const protoModAction = protoModActions[modActionId];\n if (protoModAction) {\n modActions[modActionId] = {\n ...protoModAction,\n actionType: R2_TO_MOD_ACTION_TYPE[protoModAction.actionTypeId!],\n };\n }\n }\n\n return modActions;\n }\n}\n\n/**\n * Creates a Modmail conversation with the moderators of the given subredditId.\n * @internal\n * @param subject - The subject of the message.\n * @param bodyMarkdown - The body of the message in markdown format, e.g. `Hello world \\n\\n **Have a great day**`.\n * @param subredditId - The ID (starting with `t5_`) of the subreddit to which to send the message, e.g. `t5_2qjpg`.\n * @param isInternal - Indicates if the conversation should be internal or not.\n * @param participantType - The type of participant the author is in the conversation.\n * @param conversationType - The type of conversation to create.\n * @returns A Promise that resolves a string representing the conversationId of the message.\n */\nasync function createModmailConversation(\n params: {\n subject: string;\n bodyMarkdown: string;\n subredditId: T5ID;\n isInternal: boolean;\n participantType: string;\n conversationType: string;\n },\n metadata: Metadata\n): Promise<string> {\n const appUserId = metadata[Header.AppUser]?.values[0];\n\n const operationName = 'CreateModmailConversation';\n const persistedQueryHash = '5f9ae20b0c7bdffcafb80241728a72e67cd4239bc09f67284b79d4aa706ee0e5';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n {\n subject: params.subject,\n bodyMarkdown: params.bodyMarkdown,\n subredditId: params.subredditId,\n authorId: appUserId,\n isInternal: params.isInternal,\n participantType: params.participantType,\n conversationType: params.conversationType,\n },\n metadata\n );\n\n if (response.data?.createModmailConversationV2?.ok) {\n return response.data?.createModmailConversationV2?.conversationId;\n }\n throw new Error(\n 'modmail conversation creation failed; ${response.data?.createModmailConversationV2?.errors[0].message}'\n );\n}\n", "import type { Metadata, RedditObject } from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { T2ID, T5ID, TID } from '@devvit/shared-types/tid.js';\nimport { asT2ID, asT5ID, asTID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport type { ListingFetchOptions } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type { Subreddit } from './Subreddit.js';\nimport type { User } from './User.js';\n\nexport type SendPrivateMessageOptions = {\n /** Recipient username (without the leading u/), or /r/name for that subreddit's moderators. */\n to: string;\n /** The subject of the message. */\n subject: string;\n /** The body of the message in markdown text format. */\n text: string;\n};\n\nexport type SendPrivateMessageAsSubredditOptions = SendPrivateMessageOptions & {\n /** The name of the subreddit the message is being sent from (without the leading r/) */\n fromSubredditName: string;\n};\n\nexport type GetPrivateMessagesOptions = Prettify<\n {\n type?: 'inbox' | 'unread' | 'sent';\n } & ListingFetchOptions\n>;\n\ntype PrivateMessageAuthor =\n | (Pick<User, 'username'> & { type: 'user'; id?: T2ID })\n | (Pick<Subreddit, 'name'> & { type: 'subreddit'; id?: T5ID });\n\nexport class PrivateMessage {\n readonly #id: TID;\n readonly #from: PrivateMessageAuthor;\n readonly #body: string;\n readonly #bodyHtml: string;\n readonly #created: Date;\n\n readonly #metadata: Metadata | undefined;\n\n /** @internal */\n static async getMessages(\n options: GetPrivateMessagesOptions,\n metadata: Metadata | undefined\n ): Promise<Listing<PrivateMessage>> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n return new Listing({\n ...options,\n fetch: async (fetchOpts: ListingFetchOptions) => {\n const listing = await client.MessageWhere(\n {\n ...fetchOpts,\n where: options.type ?? 'inbox',\n },\n metadata\n );\n return {\n after: listing.data?.after,\n before: listing.data?.before,\n children: (listing.data?.children\n ?.map((child) => {\n return new PrivateMessage(child.data!, metadata);\n })\n .filter(Boolean) || []) as PrivateMessage[],\n };\n },\n });\n }\n\n /** @internal */\n static async send(\n { to, subject, text }: SendPrivateMessageOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n\n await client.Compose(\n {\n to,\n subject,\n text,\n fromSr: '',\n },\n metadata\n );\n }\n\n /** @internal */\n static async sendAsSubreddit(\n { to, fromSubredditName, subject, text }: SendPrivateMessageAsSubredditOptions,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n\n await client.Compose(\n {\n to,\n fromSr: fromSubredditName,\n subject,\n text,\n },\n metadata\n );\n }\n\n /** @internal */\n static async markAllAsRead(metadata: Metadata | undefined): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n await client.ReadAllMessages({ filterTypes: '' }, metadata);\n }\n\n /**\n * @internal\n */\n constructor(data: RedditObject, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'PrivateMessage: Invalid data, no id');\n assertNonNull(data.name, 'PrivateMessage: Invalid data, no name');\n assertNonNull(data.created, 'PrivateMessage: Invalid data, no created date');\n\n this.#id = asTID(data.name);\n\n if (data.author != null) {\n this.#from = {\n type: 'user',\n username: data.author,\n id: data.authorFullname ? asT2ID(data.authorFullname) : undefined,\n };\n } else if (data.subreddit != null) {\n this.#from = {\n type: 'subreddit',\n name: data.subreddit,\n id: data.subredditId ? asT5ID(data.subredditId) : undefined,\n };\n } else {\n throw new Error('PrivateMessage: Invalid data, no author or subreddit');\n }\n\n this.#body = data.body ?? '';\n this.#bodyHtml = data.bodyHtml ?? '';\n\n const created = new Date(0);\n created.setUTCSeconds(data.createdUtc!);\n this.#created = created;\n\n this.#metadata = metadata;\n }\n\n get id(): TID {\n return this.#id;\n }\n\n get from(): PrivateMessageAuthor {\n return this.#from;\n }\n\n get body(): string {\n return this.#body;\n }\n\n get bodyHtml(): string {\n return this.#bodyHtml;\n }\n\n get created(): Date {\n return this.#created;\n }\n\n async markAsRead(): Promise<void> {\n const client = Devvit.redditAPIPlugins.PrivateMessages;\n await client.ReadMessage({ id: this.#id }, this.#metadata);\n }\n}\n", "import type {\n AboutLocationRequest,\n Listing as ProtoListing,\n Metadata,\n SubredditAboutResponse_AboutData,\n WrappedRedditObject,\n} from '@devvit/protos';\nimport { Header } from '@devvit/shared-types/Header.js';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\nimport type { Prettify } from '@devvit/shared-types/Prettify.js';\nimport type { T5ID } from '@devvit/shared-types/tid.js';\nimport { asT5ID } from '@devvit/shared-types/tid.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { GraphQL } from '../graphql/GraphQL.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { Comment } from './Comment.js';\nimport type {\n CreateFlairTemplateOptions,\n GetUserFlairBySubredditResponse,\n UserFlairPageOptions,\n} from './Flair.js';\nimport { convertUserFlairProtoToAPI, Flair, FlairTemplate } from './Flair.js';\nimport type { ListingFetchResponse } from './Listing.js';\nimport { Listing } from './Listing.js';\nimport type {\n GetModerationLogOptions as _GetModerationLogOptions,\n ModAction,\n} from './ModAction.js';\nimport { _getModerationLog } from './ModAction.js';\nimport type {\n GetPostsOptionsWithTimeframe,\n SubmitLinkOptions,\n SubmitSelfPostOptions,\n} from './Post.js';\nimport { Post } from './Post.js';\nimport type {\n BanUserOptions,\n BanWikiContributorOptions,\n GetSubredditUsersByTypeOptions,\n ModeratorPermission,\n} from './User.js';\nimport { User } from './User.js';\n\ntype GetModerationLogOptions = Omit<_GetModerationLogOptions, 'subredditName'>;\ntype GetUsersOptions = Omit<GetSubredditUsersByTypeOptions, 'subredditName' | 'type'>;\n\nexport type SubredditType =\n | 'public'\n | 'private'\n | 'restricted'\n | 'employees_only'\n | 'gold_only'\n | 'gold_restricted'\n | 'archived'\n | 'user';\n\nexport enum AboutLocations {\n Reports = 'reports',\n Spam = 'spam',\n Modqueue = 'modqueue',\n Unmoderated = 'unmoderated',\n Edited = 'edited',\n}\n\nexport type AboutSubredditTypes = 'comment' | 'post' | 'all';\n\ntype AboutSubredditOptions<T extends AboutSubredditTypes> = Omit<\n AboutSubredditHelperOptions<T>,\n 'location' | 'subreddit'\n>;\n\nexport type ModLogOptions<T extends AboutSubredditTypes> = Omit<\n AboutSubredditHelperOptions<T>,\n 'location'\n>;\n\ntype AboutSubredditHelperOptions<T extends AboutSubredditTypes> = Prettify<\n {\n type: T;\n } & AboutLocationRequest\n>;\n\nexport type CommentMediaTypes = 'giphy' | 'static' | 'animated' | 'expression';\n\nexport type FlairSettings = {\n enabled: boolean;\n usersCanAssign: boolean;\n userFlairBackgroundColor?: string;\n userFlairTextColor?: string;\n};\n\nexport type GetUserFlairOptions = UserFlairPageOptions & {\n /** If provide the method will return the flairs for the provided users, if not provided\n * it will return a list of all users assigned flairs in the subreddit */\n usernames?: string[];\n};\n\n/**\n * An individual Removal Reason object.\n */\nexport type RemovalReason = {\n /**\n * The ID of the removal reason.\n */\n id: string;\n /**\n * The message associated with the removal reason.\n */\n message: string;\n /**\n * The title of the removal reason.\n */\n title: string;\n};\n\nexport type SubredditSettings = {\n /**\n * Whether the subreddit accepts followers or not.\n */\n acceptFollowers: boolean;\n /**\n * Whether all content posted on the subreddit is original.\n */\n allOriginalContent: boolean;\n /**\n * Whether users are allowed to create chat posts on the subreddit.\n */\n allowChatPostCreation: boolean;\n /**\n * Whether the subreddit can be discovered through search.\n */\n allowDiscovery: boolean;\n /**\n * Whether the subreddit allows galleries.\n */\n allowGalleries: boolean;\n /**\n * Whether the subreddit allows images.\n */\n allowImages: boolean;\n /**\n * Whether the subreddit allows polls.\n */\n allowPolls: boolean;\n /**\n * Whether contributors are allowed to make predictions on the subreddit.\n */\n allowPredictionContributors: boolean;\n /**\n * Whether predictions are allowed on the subreddit.\n */\n allowPredictions: boolean;\n /**\n * Whether prediction tournaments are allowed on the subreddit.\n */\n allowPredictionsTournament: boolean;\n /**\n * Whether talks are allowed on the subreddit.\n */\n allowTalks: boolean;\n /**\n * Whether video GIFs are allowed on the subreddit.\n */\n allowVideoGifs: boolean;\n /**\n * Whether videos are allowed on the subreddit.\n */\n allowVideos: boolean;\n /**\n * Whether chat posts are enabled on the subreddit.\n */\n chatPostEnabled: boolean;\n /**\n * Whether collections are enabled on the subreddit.\n */\n collectionsEnabled: boolean;\n /**\n * Whether crossposts can be made to this subreddit.\n */\n crosspostable: boolean;\n /**\n * Whether emojis are enabled on the subreddit.\n */\n emojisEnabled: boolean;\n /**\n * Whether event posts are enabled on the subreddit.\n */\n eventPostsEnabled: boolean;\n /**\n * Whether link flairs are enabled on the subreddit.\n */\n linkFlairEnabled: boolean;\n /**\n * Whether the Original Content tag is enabled.\n */\n originalContentTagEnabled: boolean;\n /**\n * Whether commenting is restricted in the subreddit.\n */\n restrictCommenting: boolean;\n /**\n * Whether posting is restricted in the subreddit.\n */\n restrictPosting: boolean;\n /**\n * Whether posts in the subreddit should be automatically archived after 6 months.\n */\n shouldArchivePosts: boolean;\n /**\n * Whether the Spoiler tag is enabled.\n */\n spoilersEnabled: boolean;\n /**\n * Whether the wiki is enabled for the subreddit.\n */\n wikiEnabled: boolean;\n /**\n * The types of post allowed in this subreddit. Either \"any\", \"link\", or \"self\".\n */\n allowedPostType: 'any' | 'link' | 'self';\n /**\n * List of allowed media types in the comments made in the subreddit.\n */\n allowedMediaInComments: CommentMediaTypes[];\n /**\n * a 6-digit rgb hex color of the banner e.g. `#AABBCC`,\n */\n bannerBackgroundColor?: string;\n /**\n * The background image of the banner.\n */\n bannerBackgroundImage?: string;\n /**\n * The URL of the banner image.\n */\n bannerImage?: string;\n /**\n * The URL of the community icon.\n */\n communityIcon?: string;\n /**\n * The header title.\n */\n headerTitle?: string;\n /**\n * The 6-digit rgb hex color of the subreddit's key color, e.g. `#AABBCC`,\n */\n keyColor?: string;\n /**\n * Banner image used on mobile apps.\n */\n mobileBannerImage?: string;\n /**\n * The 6-digit rgb hex color of the subreddit's primary color, e.g. `#AABBCC`,\n */\n primaryColor?: string;\n /**\n * The user flair settings for the subreddit.\n */\n userFlairs: FlairSettings;\n /**\n * The post flair settings for the subreddit.\n */\n postFlairs: FlairSettings;\n /**\n * HTTP URL to the subreddit\n */\n url: string;\n};\n\nexport type SubredditLeaderboardSummaryRow = {\n title: string;\n key: string;\n value: number;\n};\n\nexport type SubredditLeaderboardSummary = {\n data: SubredditLeaderboardSummaryRow[];\n};\n\n/**\n * An individual Leaderboard object.\n */\nexport type SubredditLeaderboard = {\n id: string;\n summary: SubredditLeaderboardSummary;\n};\n\nexport type BackgroundImagePosition = 'cover' | 'tiled' | 'centered';\nexport type BannerHeight = 'small' | 'medium' | 'large';\nexport type CommunityNameFormat = 'slashtag' | 'pretty' | 'hide';\nexport type CustomizationFlag = 'default' | 'custom';\nexport type ImagePosition = 'cover' | 'tiled';\nexport type MenuPosition = 'default' | 'overlay';\nexport type PositionedImagePosition = 'left' | 'right' | 'centered';\nexport type Visibility = 'show' | 'hide';\n\n/**\n * A class representing the styles of a Subreddit.\n */\nexport type SubredditStyles = {\n backgroundColor?: string;\n backgroundImage?: string;\n backgroundImagePosition?: BackgroundImagePosition;\n bannerBackgroundColor?: string;\n bannerBackgroundImage?: string;\n bannerBackgroundImagePosition?: ImagePosition;\n bannerCommunityName?: string;\n bannerCommunityNameFormat?: CommunityNameFormat;\n bannerHeight?: BannerHeight;\n bannerOverlayColor?: string;\n bannerPositionedImage?: string;\n bannerPositionedImagePosition?: PositionedImagePosition;\n bannerShowCommunityIcon?: Visibility;\n highlightColor?: string;\n icon?: string;\n legacyBannerBackgroundImage?: string;\n legacyPrimaryColor?: string;\n menuBackgroundBlur?: number;\n menuBackgroundColor?: string;\n menuBackgroundImage?: string;\n menuBackgroundOpacity?: number;\n menuLinkColorActive?: string;\n menuLinkColorHover?: string;\n menuLinkColorInactive?: string;\n menuPosition?: MenuPosition;\n mobileBannerImage?: string;\n mobileKeyColor?: string;\n postBackgroundColor?: string;\n postBackgroundImage?: string;\n postBackgroundImagePosition?: ImagePosition;\n postDownvoteCountColor?: string;\n postDownvoteIconActive?: string;\n postDownvoteIconInactive?: string;\n postPlaceholderImage?: string;\n postPlaceholderImagePosition?: ImagePosition;\n postTitleColor?: string;\n postUpvoteCountColor?: string;\n postUpvoteIconActive?: string;\n postUpvoteIconInactive?: string;\n postVoteIcons?: CustomizationFlag;\n primaryColor?: string;\n secondaryBannerPositionedImage?: string;\n sidebarWidgetBackgroundColor?: string;\n sidebarWidgetHeaderColor?: string;\n submenuBackgroundColor?: string;\n submenuBackgroundStyle?: CustomizationFlag;\n};\n\nexport class SubredditDescription {\n markdown?: string;\n}\n\nexport class SubredditWikiSettings {\n wikiEditMode?: WikiEditMode;\n}\n\nexport type WikiEditMode = 'disabled' | 'modonly' | 'anyone';\n\nexport type PostType =\n | 'link'\n | 'image'\n | 'video'\n | 'text'\n | 'spoiler'\n | 'poll'\n | 'gallery'\n | 'talk'\n | 'prediction'\n | 'videogif'\n | 'streaming'\n | 'crosspost';\n\nexport type PostCapabilities = 'ama';\n\nexport class AuthorFlairSettings {\n isEnabled?: boolean;\n isSelfAssignabled?: boolean;\n}\n\nexport class PostFlairSettings {\n isEnabled?: boolean;\n isSelfAssignabled?: boolean;\n}\n\n/**\n * A class representing information about a Subreddit.\n */\nexport type SubredditInfo = {\n id?: T5ID;\n name?: string;\n createdAt?: Date;\n type?: SubredditType;\n title?: string;\n description?: SubredditDescription;\n detectedLanguage?: string;\n subscribersCount?: number;\n activeCount?: number;\n isNsfw?: boolean;\n isQuarantined?: boolean;\n isDiscoveryAllowed?: boolean;\n isPredictionContributorsAllowed?: boolean;\n isPredictionAllowed?: boolean;\n isPredictionsTournamentAllowed?: boolean;\n isChatPostCreationAllowed?: boolean;\n isChatPostFeatureEnabled?: boolean;\n isCrosspostingAllowed?: boolean;\n isEmojisEnabled?: boolean;\n isCommentingRestricted?: boolean;\n isPostingRestricted?: boolean;\n isArchivePostsEnabled?: boolean;\n isSpoilerAvailable?: boolean;\n allAllowedPostTypes?: PostType[];\n allowedPostCapabilities?: PostCapabilities[];\n allowedMediaInComments?: CommentMediaTypes[];\n authorFlairSettings?: AuthorFlairSettings;\n postFlairSettings?: PostFlairSettings;\n wikiSettings?: SubredditWikiSettings;\n};\n\n/**\n * A class representing a subreddit.\n */\nexport class Subreddit {\n #id: T5ID;\n #name: string;\n #createdAt: Date;\n #type: SubredditType;\n #title?: string;\n #description?: string;\n #language: string;\n #numberOfSubscribers: number;\n #numberOfActiveUsers: number;\n #nsfw: boolean;\n #settings: SubredditSettings;\n // R2 bug: subreddit does not contain a permalink field, but uses the url field instead\n #permalink: string;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(data: Partial<SubredditAboutResponse_AboutData>, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n assertNonNull(data.id, 'Subreddit id is missing or undefined');\n assertNonNull(data.displayName, 'Subreddit name is missing or undefined');\n\n this.#id = asT5ID(`t5_${data.id}`);\n this.#name = data.displayName;\n\n assertNonNull(data.createdUtc, 'Subreddit is missing created date');\n const createdAt = new Date(0);\n createdAt.setUTCSeconds(data.createdUtc);\n this.#createdAt = createdAt;\n\n this.#type = asSubredditType(data.subredditType);\n this.#title = data.title;\n this.#description = data.description;\n\n assertNonNull(data.lang, 'Subreddit is missing language');\n this.#language = data.lang;\n\n this.#numberOfSubscribers = data.subscribers ?? 0;\n this.#numberOfActiveUsers = data.activeUserCount ?? 0;\n\n this.#nsfw = data.over18 ?? false;\n\n this.#permalink = data.url ?? '';\n\n this.#settings = {\n acceptFollowers: data.acceptFollowers ?? false,\n allOriginalContent: data.allOriginalContent ?? false,\n allowChatPostCreation: data.allowChatPostCreation ?? false,\n allowDiscovery: data.allowDiscovery ?? false,\n allowGalleries: data.allowGalleries ?? false,\n allowImages: data.allowImages ?? false,\n allowPolls: data.allowPolls ?? false,\n allowPredictionContributors: data.allowPredictionContributors ?? false,\n allowPredictions: data.allowPredictions ?? false,\n allowPredictionsTournament: data.allowPredictionsTournament ?? false,\n allowTalks: data.allowTalks ?? false,\n allowVideoGifs: data.allowVideogifs ?? false,\n allowVideos: data.allowVideos ?? false,\n chatPostEnabled: data.isChatPostFeatureEnabled ?? false,\n collectionsEnabled: data.collectionsEnabled ?? false,\n crosspostable: data.isCrosspostableSubreddit ?? false,\n emojisEnabled: data.emojisEnabled ?? false,\n eventPostsEnabled: data.eventPostsEnabled ?? false,\n linkFlairEnabled: data.linkFlairEnabled ?? false,\n originalContentTagEnabled: data.originalContentTagEnabled ?? false,\n restrictCommenting: data.restrictCommenting ?? false,\n restrictPosting: data.restrictPosting ?? false,\n shouldArchivePosts: data.shouldArchivePosts ?? false,\n spoilersEnabled: data.spoilersEnabled ?? false,\n wikiEnabled: data.wikiEnabled ?? false,\n allowedPostType: asAllowedPostType(data.submissionType),\n allowedMediaInComments: (data.allowedMediaInComments ?? []).map(asCommentMediaTypes),\n bannerBackgroundColor: data.bannerBackgroundColor,\n bannerBackgroundImage: data.bannerBackgroundImage,\n bannerImage: data.bannerImg,\n communityIcon: data.communityIcon,\n headerTitle: data.headerTitle,\n keyColor: data.keyColor,\n mobileBannerImage: data.mobileBannerImage,\n primaryColor: data.primaryColor,\n userFlairs: {\n enabled: data.userFlairEnabledInSr ?? false,\n usersCanAssign: data.canAssignUserFlair ?? false,\n userFlairBackgroundColor: data.userFlairBackgroundColor,\n userFlairTextColor: data.userFlairTextColor,\n },\n postFlairs: {\n enabled: data.linkFlairEnabled ?? false,\n usersCanAssign: data.canAssignLinkFlair ?? false,\n },\n // R2 bug: url is a permalink\n url: new URL(this.#permalink, 'https://www.reddit.com').toString(),\n };\n\n this.#metadata = metadata;\n }\n\n /**\n * The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n */\n get id(): T5ID {\n return this.#id;\n }\n\n /**\n * The name of a subreddit omitting the r/.\n */\n get name(): string {\n return this.#name;\n }\n\n /**\n * The creation date of the subreddit.\n */\n get createdAt(): Date {\n return this.#createdAt;\n }\n\n /**\n * The type of subreddit (public, private, etc.).\n */\n get type(): SubredditType {\n return this.#type;\n }\n\n /**\n * The title of the subreddit.\n */\n get title(): string | undefined {\n return this.#title;\n }\n\n /**\n * The description of the subreddit.\n */\n get description(): string | undefined {\n return this.#description;\n }\n\n /**\n * The language of the subreddit.\n */\n get language(): string {\n return this.#language;\n }\n\n /**\n * The number of subscribers of the subreddit.\n */\n get numberOfSubscribers(): number {\n return this.#numberOfSubscribers;\n }\n\n /**\n * The number of active users of the subreddit.\n */\n get numberOfActiveUsers(): number {\n return this.#numberOfActiveUsers;\n }\n\n /**\n * Whether the subreddit is marked as NSFW (Not Safe For Work).\n */\n get nsfw(): boolean {\n return this.#nsfw;\n }\n\n /**\n * The settings of the subreddit.\n */\n get settings(): SubredditSettings {\n return this.#settings;\n }\n\n /**\n * Whether the user flairs are enabled for this subreddit.\n */\n get userFlairsEnabled(): boolean {\n return this.settings.userFlairs.enabled;\n }\n\n /**\n * Whether the post flairs are enabled for this subreddit.\n */\n get postFlairsEnabled(): boolean {\n return this.settings.postFlairs.enabled;\n }\n\n /**\n * Whether the user can assign user flairs.\n * This is only true if the user flairs are enabled.\n */\n get usersCanAssignUserFlairs(): boolean {\n return this.settings.userFlairs.usersCanAssign;\n }\n\n /**\n * Whether the user can assign post flairs.\n * This is only true if the post flairs are enabled.\n */\n get usersCanAssignPostFlairs(): boolean {\n return this.settings.postFlairs.usersCanAssign;\n }\n\n /**\n * Returns the HTTP URL for the subreddit.\n * (R2 bug: subreddit.url is a permalink path and does not return a fully qualified URL in subreddit.url)\n */\n get url(): string {\n return this.settings.url;\n }\n\n /**\n * Returns a permalink path\n * (R2 bug: subreddit.url is a permalink, and does not have a subreddit.permalink field)\n */\n get permalink(): string {\n return this.#permalink;\n }\n\n toJSON(): Pick<\n Subreddit,\n | 'id'\n | 'name'\n | 'createdAt'\n | 'type'\n | 'title'\n | 'description'\n | 'language'\n | 'nsfw'\n | 'numberOfSubscribers'\n | 'numberOfActiveUsers'\n | 'settings'\n > {\n return {\n id: this.id,\n name: this.name,\n createdAt: this.createdAt,\n type: this.type,\n title: this.title,\n description: this.description,\n language: this.language,\n nsfw: this.nsfw,\n numberOfSubscribers: this.numberOfSubscribers,\n numberOfActiveUsers: this.numberOfActiveUsers,\n settings: this.settings,\n };\n }\n\n async submitPost(options: SubmitLinkOptions | SubmitSelfPostOptions): Promise<Post> {\n const submitPostOptions = {\n ...options,\n subredditName: this.#name,\n };\n\n return Post.submit(submitPostOptions, this.#metadata);\n }\n\n getControversialPosts(\n options: Omit<GetPostsOptionsWithTimeframe, 'subredditName'> = {}\n ): Listing<Post> {\n if (!this.#name) {\n throw new Error('subreddit missing displayName - it might not have been fetched');\n }\n\n return Post.getControversialPosts(\n {\n ...options,\n subredditName: this.#name,\n },\n this.#metadata\n );\n }\n\n getTopPosts(options: Omit<GetPostsOptionsWithTimeframe, 'subredditName'> = {}): Listing<Post> {\n if (!this.#name) {\n throw new Error('subreddit missing displayName - it might not have been fetched');\n }\n\n return Post.getTopPosts(\n {\n ...options,\n subredditName: this.#name,\n },\n this.#metadata\n );\n }\n\n getApprovedUsers(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'contributors',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n approveUser(username: string): Promise<void> {\n return User.createRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'contributor',\n },\n this.#metadata\n );\n }\n\n removeUser(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'contributor',\n },\n this.#metadata\n );\n }\n\n getWikiContributors(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'wikicontributors',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n addWikiContributor(username: string): Promise<void> {\n return User.createRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'wikicontributor',\n },\n this.#metadata\n );\n }\n\n removeWikiContributor(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'wikicontributor',\n },\n this.#metadata\n );\n }\n\n getBannedUsers(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'banned',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n banUser(options: Omit<BanUserOptions, 'subredditName'>): Promise<void> {\n return User.createRelationship(\n {\n username: options.username,\n subredditName: this.#name,\n type: 'banned',\n banReason: options.reason,\n banMessage: options.message,\n note: options.note,\n duration: options.duration,\n banContext: options.context,\n },\n this.#metadata\n );\n }\n\n unbanUser(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'banned',\n },\n this.#metadata\n );\n }\n\n getBannedWikiContributors(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'wikibanned',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n banWikiContributor(options: Omit<BanWikiContributorOptions, 'subredditName'>): Promise<void> {\n return User.createRelationship(\n {\n username: options.username,\n subredditName: this.#name,\n type: 'wikibanned',\n banReason: options.reason,\n note: options.note,\n duration: options.duration,\n },\n this.#metadata\n );\n }\n\n unbanWikiContributor(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'wikibanned',\n },\n this.#metadata\n );\n }\n\n getModerators(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'moderators',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n inviteModerator(username: string, permissions?: ModeratorPermission[]): Promise<void> {\n return User.createRelationship(\n {\n type: 'moderator_invite',\n subredditName: this.#name,\n username,\n permissions: permissions ?? [],\n },\n this.#metadata\n );\n }\n\n revokeModeratorInvite(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'moderator_invite',\n },\n this.#metadata\n );\n }\n\n removeModerator(username: string): Promise<void> {\n return User.removeRelationship(\n {\n type: 'moderator',\n subredditName: this.#name,\n username,\n },\n this.#metadata\n );\n }\n\n setModeratorPermissions(username: string, permissions: ModeratorPermission[]): Promise<void> {\n return User.setModeratorPermissions(username, this.#name, permissions, this.#metadata);\n }\n\n getMutedUsers(options: GetUsersOptions = {}): Listing<User> {\n return User.getSubredditUsersByType(\n {\n type: 'muted',\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n muteUser(username: string, note?: string): Promise<void> {\n return User.createRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'muted',\n note,\n },\n this.#metadata\n );\n }\n\n unmuteUser(username: string): Promise<void> {\n return User.removeRelationship(\n {\n username,\n subredditName: this.#name,\n type: 'muted',\n },\n this.#metadata\n );\n }\n\n getModerationLog(options: GetModerationLogOptions): Listing<ModAction> {\n return _getModerationLog(\n {\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n getUserFlairTemplates(): Promise<FlairTemplate[]> {\n return FlairTemplate.getUserFlairTemplates(this.#name, this.#metadata);\n }\n\n getPostFlairTemplates(): Promise<FlairTemplate[]> {\n return FlairTemplate.getPostFlairTemplates(this.#name, this.#metadata);\n }\n\n createPostFlairTemplate(\n options: Omit<CreateFlairTemplateOptions, 'subredditName'>\n ): Promise<FlairTemplate> {\n return FlairTemplate.createPostFlairTemplate(\n {\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n createUserFlairTemplate(\n options: Omit<CreateFlairTemplateOptions, 'subredditName'>\n ): Promise<FlairTemplate> {\n return FlairTemplate.createUserFlairTemplate(\n {\n subredditName: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n /**\n * Get the user flair for the given subreddit. If `usernames` is provided then it will return only the\n * flair for the specified users. If retrieving the list of flair for a given subreddit and the list is long\n * then this method will return a `next` field which can be passed into the `after` field on the next call to\n * retrieve the next slice of data. To retrieve the previous slice of data pass the `prev` field into the `before` field\n * during the subsequent call.\n *\n * @param options See interface\n * @param metadata See interface\n *\n * @example\n * ```ts\n * const subredditName = \"mysubreddit\"\n * const subreddit = await getSubredditByName(subredditName)\n * const response = await subreddit.getUserFlair();\n * const userFlairList = response.users\n * ```\n * @example\n * ```ts\n * const response = await subreddit.getUserFlair({ after: \"t2_awefae\"});\n * const userFlairList = response.users\n * ```\n *\n * @example\n * ```ts\n * const response = await subreddit.getUserFlair({ usernames: ['toxictoad', 'badapple']});\n * const userFlairList = response.users\n * ```\n */\n async getUserFlair(options?: GetUserFlairOptions): Promise<GetUserFlairBySubredditResponse> {\n if (options?.usernames !== undefined) {\n const users = await Promise.all(\n options.usernames.map(async (name) => {\n const response = await Flair.getUserFlairBySubreddit(\n {\n subreddit: this.#name,\n name,\n },\n this.#metadata\n );\n return convertUserFlairProtoToAPI(response.users[0]);\n })\n );\n\n return { users };\n } else {\n const response = await Flair.getUserFlairBySubreddit(\n {\n ...options,\n subreddit: this.#name,\n },\n this.#metadata\n );\n return {\n next: response.next,\n prev: response.prev,\n users: response.users.map((userFlair) => convertUserFlairProtoToAPI(userFlair)),\n };\n }\n }\n\n /**\n * Return a listing of things requiring moderator review, such as reported things and items.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getModQueue();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getModQueue({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getModQueue(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getModQueue(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getModQueue(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getModQueue(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Modqueue,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have been reported.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getReports();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getReports({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getReports(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getReports(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getReports(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getReports(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Reports,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have been marked as spam or otherwise removed.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getSpam();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getSpam({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getSpam(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getSpam(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getSpam(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getSpam(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Spam,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have yet to be approved/removed by a mod.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getUnmoderated();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getUnmoderated({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getUnmoderated(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getUnmoderated(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getUnmoderated(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getUnmoderated(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Unmoderated,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /**\n * Return a listing of things that have been edited recently.\n *\n * @param options\n *\n * @example\n * ```ts\n * const subreddit = await getSubredditByName(\"mysubreddit\")\n * let listing = await subreddit.getEdited();\n * console.log(\"Posts and Comments: \", await listing.all())\n * listing = await subreddit.getEdited({ type: \"post\"});\n * console.log(\"Posts: \", await listing.all())\n * ```\n */\n getEdited(options: AboutSubredditOptions<'comment'>): Listing<Comment>;\n getEdited(options: AboutSubredditOptions<'post'>): Listing<Post>;\n getEdited(options?: AboutSubredditOptions<'all'>): Listing<Post | Comment>;\n getEdited(\n options: AboutSubredditOptions<AboutSubredditTypes> = { type: 'all' }\n ): Listing<Post | Comment> {\n return Subreddit.aboutLocation(\n {\n ...options,\n location: AboutLocations.Edited,\n subreddit: this.#name,\n },\n this.#metadata\n );\n }\n\n /** @internal */\n static aboutLocation(\n options: AboutSubredditHelperOptions<AboutSubredditTypes>,\n metadata: Metadata | undefined\n ): Listing<Post | Comment> {\n const client = Devvit.redditAPIPlugins.Moderation;\n let only: string | undefined;\n switch (options.type) {\n case 'post':\n only = 'links';\n break;\n case 'comment':\n only = 'comments';\n break;\n default:\n only = undefined;\n }\n\n return new Listing({\n ...options,\n fetch: async (fetchOptions) => {\n const listing = await client.AboutLocation(\n {\n ...fetchOptions,\n ...options,\n only,\n },\n metadata\n );\n\n return parseListing(listing, metadata);\n },\n });\n }\n\n /**\n * Return a listing of things specified by their fullnames.\n *\n * @param ids Array of thing full ids (e.g. t3_abc123)\n * @example\n * ```ts\n * const subreddit = await getSubredditByName('askReddit');\n * const listing = subreddit.getCommentsAndPostsByIds(['t3_abc123', 't1_xyz123']);\n * const items = await listing.all();\n * console.log(items) // [Post, Comment]\n * ```\n */\n getCommentsAndPostsByIds(ids: string[]): Listing<Post | Comment> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n return new Listing({\n fetch: async () => {\n const listing = await client.Info(\n { thingIds: ids, subreddits: [this.#id] },\n this.#metadata\n );\n\n return parseListing(listing, this.#metadata);\n },\n });\n }\n\n /** @internal */\n static async addRemovalReason(\n subredditName: string,\n title: string,\n message: string,\n metadata: Metadata | undefined\n ): Promise<string> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n const response = await client.SubredditAddRemovalReason(\n {\n title,\n message,\n subreddit: subredditName,\n },\n metadata\n );\n\n return response.id;\n }\n\n /** @internal */\n static async getRemovalReasons(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<RemovalReason[]> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n const result = await client.SubredditGetRemovalReasons(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n return result.order.map((id) => ({ ...result.data[id] }));\n }\n\n /** @internal */\n static async getFromMetadata(metadata: Metadata | undefined): Promise<Subreddit | undefined> {\n assertNonNull(metadata);\n const subredditName = metadata?.[Header.SubredditName]?.values[0];\n if (subredditName) {\n return Subreddit.getByName(subredditName, metadata);\n }\n\n const subredditId = metadata?.[Header.Subreddit]?.values[0];\n assertNonNull<string | undefined>(subredditId);\n return Subreddit.getById(asT5ID(subredditId), metadata);\n }\n\n /** @internal */\n static async getById(id: T5ID, metadata: Metadata | undefined): Promise<Subreddit | undefined> {\n const subredditName = await _getSubredditNameById(id, metadata);\n if (!subredditName) {\n return;\n }\n\n return Subreddit.getByName(subredditName, metadata);\n }\n\n /** @internal */\n static async getByName(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<Subreddit> {\n const client = Devvit.redditAPIPlugins.Subreddits;\n\n const response = await client.SubredditAbout(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n if (!response?.data) {\n throw new Error('not found');\n }\n\n return new Subreddit(response.data, metadata);\n }\n}\n\n/**\n * @internal\n * Gets a {@link SubredditInfo} object by ID\n *\n * @param {string} id - The ID (starting with t5_) of the subreddit to retrieve. e.g. t5_2qjpg\n * @param metadata - Optional RPC metadata passed with every request.\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n */\nexport async function _getSubredditInfoById(\n subredditId: string,\n metadata: Metadata | undefined\n): Promise<SubredditInfo> {\n const operationName = 'GetSubredditInfoById';\n const persistedQueryHash = '315a9b75c22a017d526afdf2d274616946156451aacfd56dfb91e7ad3f7a2fde';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { id: subredditId },\n metadata\n );\n\n const subredditInfo = response.data?.subredditInfoById;\n\n if (!subredditInfo) throw new Error('subreddit info not found');\n\n return subredditInfo;\n}\n\n/**\n * @internal\n * Gets a {@link SubredditInfo} object by name\n *\n * @param {string} subredditName The name of a subreddit omitting the r/. This is case insensitive.\n * @param metadata - Optional RPC metadata passed with every request.\n * @returns {Promise<SubredditInfo>} A Promise that resolves a SubredditInfo object.\n */\nexport async function _getSubredditInfoByName(\n subredditName: string,\n metadata: Metadata | undefined\n): Promise<SubredditInfo> {\n const operationName = 'GetSubredditInfoByName';\n const persistedQueryHash = '4aa69726c7e3f5d33ab2bee22b3d74fce645824fddd5ea3ec6dfe30bdb4295cb';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { name: subredditName },\n metadata\n );\n\n const subredditInfo = response.data?.subredditInfoByName;\n\n if (!subredditInfo) throw new Error('subreddit info not found');\n\n return subredditInfo;\n}\n\n/** @internal */\nexport async function _getSubredditLeaderboard(\n subredditId: string,\n metadata: Metadata | undefined\n): Promise<SubredditLeaderboard> {\n const operationName = 'GetSubredditLeaderboard';\n const persistedQueryHash = '18ead70c46b6446d45ecd8b679b16d9a929a933d6ef25d8262a459cb18b72848';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { id: subredditId },\n metadata\n );\n\n const leaderboard = response.data?.subredditInfoById?.leaderboard;\n\n if (!leaderboard) throw new Error('subreddit leaderboard not found');\n if (!leaderboard.summary) throw new Error('subreddit leaderboard summary not found');\n\n return {\n id: leaderboard.id,\n summary: leaderboard.summary,\n };\n}\n\n/**\n * @internal\n */\nexport async function _getSubredditStyles(\n subredditId: string,\n metadata: Metadata | undefined\n): Promise<SubredditStyles> {\n const operationName = 'GetSubredditStyles';\n const persistedQueryHash = 'd491d17ea8858f563ea578b26b9595d64adecf4bf34557d567c7e53c470f5f22';\n const response = await GraphQL.query(\n operationName,\n persistedQueryHash,\n { id: subredditId },\n metadata\n );\n\n const styles = response.data?.subredditInfoById?.styles;\n\n if (!styles) throw new Error('subreddit styles not found');\n\n return styles;\n}\n\nfunction asSubredditType(type?: string): SubredditType {\n if (\n type === 'public' ||\n type === 'private' ||\n type === 'restricted' ||\n type === 'employees_only' ||\n type === 'gold_only' ||\n type === 'gold_restricted' ||\n type === 'archived' ||\n type === 'user'\n ) {\n return type;\n }\n\n throw new Error(`invalid subreddit type: ${type}`);\n}\n\nfunction asAllowedPostType(type?: string): 'any' | 'link' | 'self' {\n if (type === 'any' || type === 'link' || type === 'self') {\n return type;\n }\n\n throw new Error(`invalid allowed post type: ${type}`);\n}\n\nfunction asCommentMediaTypes(type: string): CommentMediaTypes {\n if (type === 'animated' || type === 'giphy' || type === 'static' || type === 'expression') {\n return type;\n }\n\n throw new Error(`invalid comment media type: ${type}`);\n}\n\nfunction parseListing(\n listing: ProtoListing,\n metadata: Metadata | undefined\n): ListingFetchResponse<Post | Comment> {\n const children = listing.data?.children ?? [];\n const postsAndComments: (Post | Comment)[] = children\n .map((child) => {\n const post = tryParseAsPost(child);\n if (post != null) {\n return post;\n }\n const comment = tryParseAsComment(child);\n if (comment != null) {\n return comment;\n }\n\n return null;\n })\n .filter(Boolean) as (Post | Comment)[];\n\n return {\n after: listing.data?.after,\n before: listing.data?.before,\n children: postsAndComments,\n };\n\n function tryParseAsPost(obj: WrappedRedditObject): Post | null {\n try {\n return new Post(obj.data!, metadata);\n } catch {\n return null;\n }\n }\n\n function tryParseAsComment(obj: WrappedRedditObject): Comment | null {\n try {\n return new Comment(obj.data!, metadata);\n } catch {\n return null;\n }\n }\n}\n\n/** @internal */\nexport async function _getSubredditNameById(\n id: T5ID,\n metadata: Metadata | undefined\n): Promise<string | undefined> {\n const client = Devvit.redditAPIPlugins.LinksAndComments;\n\n const response = await client.Info({ thingIds: [id], subreddits: [] }, metadata);\n return response.data?.children[0]?.data?.displayName;\n}\n", "import type {\n AddButtonWidgetRequest,\n AddCalendarWidgetRequest,\n AddCommunityListWidgetRequest,\n AddCustomWidgetRequest,\n AddImageWidgetRequest,\n AddPostFlairWidgetRequest,\n AddTextAreaWidgetRequest,\n CalendarWidgetConfiguration,\n GetWidgetsResponse_WidgetItem_PostFlairTemplate as PostFlairTemplateData,\n Metadata,\n SubredditAboutRulesResponse,\n UpdateButtonWidgetRequest,\n UpdateCalendarWidgetRequest,\n UpdateCommunityListWidgetRequest,\n UpdateCustomWidgetRequest,\n UpdateImageWidgetRequest,\n UpdatePostFlairWidgetRequest,\n UpdateTextAreaWidgetRequest,\n WidgetButton,\n WidgetImage,\n WidgetStyles,\n} from '@devvit/protos';\nimport {\n CommunityListWidget_CommunityData as CommunityData,\n GetWidgetsResponse_WidgetItem as WidgetItem,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\n\nexport type AddWidgetData =\n | (AddImageWidgetRequest & {\n type: 'image';\n })\n | (AddCalendarWidgetRequest & {\n type: 'calendar';\n })\n | (AddTextAreaWidgetRequest & {\n type: 'textarea';\n })\n | (AddButtonWidgetRequest & {\n type: 'button';\n })\n | (AddCommunityListWidgetRequest & {\n type: 'community-list';\n })\n | (AddPostFlairWidgetRequest & {\n type: 'post-flair';\n })\n | (AddCustomWidgetRequest & {\n type: 'custom';\n });\n\nexport class Widget {\n #id: string;\n #name: string;\n #subredditName: string;\n\n #metadata: Metadata | undefined;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n makeGettersEnumerable(this);\n\n this.#id = widgetData.id;\n this.#name = widgetData.shortName;\n this.#subredditName = subredditName;\n this.#metadata = metadata;\n }\n\n get id(): string {\n return this.#id;\n }\n\n get name(): string {\n return this.#name;\n }\n\n get subredditName(): string {\n return this.#subredditName;\n }\n\n toJSON(): Pick<Widget, 'id' | 'name' | 'subredditName'> {\n return {\n id: this.id,\n name: this.name,\n subredditName: this.subredditName,\n };\n }\n\n delete(): Promise<void> {\n return Widget.delete(this.subredditName, this.id, this.#metadata);\n }\n\n /**\n * @internal\n * @note - This method only returns the widgets listed on the sidebar.\n */\n static async getWidgets(\n subredditName: string,\n metadata: Metadata | undefined\n ): Promise<Widget[]> {\n const client = Devvit.redditAPIPlugins.Widgets;\n\n const response = await client.GetWidgets(\n {\n subreddit: subredditName,\n },\n metadata\n );\n\n assertNonNull(response.layout, 'Failed to load widgets for subreddit');\n\n const widgetsMap = response.items;\n\n const widgets: Widget[] = [];\n\n for (const widgetId of response.layout.sidebar?.order ?? []) {\n const widgetData = widgetsMap[widgetId];\n switch (widgetData?.kind) {\n case 'image':\n widgets.push(new ImageWidget(widgetData, subredditName, metadata));\n break;\n case 'calendar':\n widgets.push(new CalendarWidget(widgetData, subredditName, metadata));\n break;\n case 'textarea':\n widgets.push(new TextAreaWidget(widgetData, subredditName, metadata));\n break;\n case 'button':\n widgets.push(new ButtonWidget(widgetData, subredditName, metadata));\n break;\n case 'community-list':\n widgets.push(new CommunityListWidget(widgetData, subredditName, metadata));\n break;\n case 'post-flair':\n widgets.push(new PostFlairWidget(widgetData, subredditName, metadata));\n break;\n case 'custom':\n widgets.push(new CustomWidget(widgetData, subredditName, metadata));\n break;\n case 'subreddit-rules': {\n // subreddit rule widget does not contain any data\n // we need to fetch it separately\n const rulesRsp = await Devvit.redditAPIPlugins.Subreddits.SubredditAboutRules(\n {\n subreddit: subredditName,\n },\n metadata\n );\n widgets.push(new SubredditRulesWidget(rulesRsp, widgetData, subredditName, metadata));\n break;\n }\n default:\n throw new Error(`Unknown widget type: ${widgetData.kind}`);\n }\n }\n\n return widgets;\n }\n\n /** @internal */\n static async delete(\n subredditName: string,\n id: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Widgets;\n\n await client.DeleteWidget(\n {\n subreddit: subredditName,\n id,\n },\n metadata\n );\n }\n\n /** @internal */\n static async reorder(\n subredditName: string,\n orderByIds: string[],\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Widgets;\n\n await client.OrderWidgets(\n {\n subreddit: subredditName,\n order: orderByIds,\n },\n metadata\n );\n }\n\n /** @internal */\n static async add(widgetData: AddWidgetData, metadata: Metadata | undefined): Promise<Widget> {\n switch (widgetData?.type) {\n case 'image':\n return ImageWidget.create(widgetData, metadata);\n case 'calendar':\n return CalendarWidget.create(widgetData, metadata);\n case 'textarea':\n return TextAreaWidget.create(widgetData, metadata);\n case 'button':\n return ButtonWidget.create(widgetData, metadata);\n case 'community-list':\n return CommunityListWidget.create(widgetData, metadata);\n case 'post-flair':\n return PostFlairWidget.create(widgetData, metadata);\n case 'custom':\n return CustomWidget.create(widgetData, metadata);\n default:\n throw new Error('Unknown widget type');\n }\n }\n}\n\nexport class ImageWidget extends Widget {\n #images: WidgetImage[];\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n this.#images = widgetData.data.map((data) => {\n assertNonNull(data.url, 'Image widget data is missing url');\n assertNonNull(data.height, 'Image widget data is missing height');\n assertNonNull(data.width, 'Image widget data is missing width');\n assertNonNull(data.linkUrl, 'Image widget data is missing linkUrl');\n\n return {\n url: data.url,\n height: data.height,\n width: data.width,\n linkUrl: data.linkUrl,\n };\n });\n }\n\n get images(): WidgetImage[] {\n return this.#images;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> & Pick<ImageWidget, 'images'> {\n return {\n ...super.toJSON(),\n images: this.images,\n };\n }\n\n /** @internal */\n static async create(\n options: AddImageWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ImageWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddImageWidget(options, metadata);\n\n return new ImageWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateImageWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ImageWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateImageWidget(options, metadata);\n\n return new ImageWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class CalendarWidget extends Widget {\n #googleCalendarId: string;\n #configuration: CalendarWidgetConfiguration;\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.googleCalendarId, 'Calendar widget data is missing googleCalendarId');\n assertNonNull(widgetData.configuration, 'Calendar widget data is missing configuration');\n assertNonNull(widgetData.styles, 'Calendar widget data is missing styles');\n\n this.#googleCalendarId = widgetData.googleCalendarId;\n this.#configuration = widgetData.configuration;\n this.#styles = widgetData.styles;\n }\n\n get googleCalendarId(): string {\n return this.#googleCalendarId;\n }\n\n get configuration(): CalendarWidgetConfiguration {\n return this.#configuration;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<CalendarWidget, 'googleCalendarId' | 'configuration' | 'styles'> {\n return {\n ...super.toJSON(),\n googleCalendarId: this.googleCalendarId,\n configuration: this.configuration,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddCalendarWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CalendarWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddCalendarWidget(options, metadata);\n\n return new CalendarWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateCalendarWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CalendarWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateCalendarWidget(options, metadata);\n\n return new CalendarWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class TextAreaWidget extends Widget {\n #text: string;\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.text, 'Textarea widget data is missing text');\n assertNonNull(widgetData.styles, 'Textarea widget data is missing styles');\n\n this.#text = widgetData.text;\n this.#styles = widgetData.styles;\n }\n\n get text(): string {\n return this.#text;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> & Pick<TextAreaWidget, 'text' | 'styles'> {\n return {\n ...super.toJSON(),\n text: this.text,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddTextAreaWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<TextAreaWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddTextAreaWidget(options, metadata);\n\n return new TextAreaWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateTextAreaWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<TextAreaWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateTextAreaWidget(options, metadata);\n\n return new TextAreaWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class ButtonWidget extends Widget {\n #buttons: WidgetButton[];\n #description: string;\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.styles, 'Button widget data is missing styles');\n\n this.#buttons = widgetData.buttons;\n this.#description = widgetData.description ?? '';\n this.#styles = widgetData.styles;\n }\n\n get buttons(): WidgetButton[] {\n return this.#buttons;\n }\n\n get description(): string {\n return this.#description;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<ButtonWidget, 'buttons' | 'description' | 'styles'> {\n return {\n ...super.toJSON(),\n buttons: this.buttons,\n description: this.description,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddButtonWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ButtonWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddButtonWidget(options, metadata);\n\n return new ButtonWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateButtonWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<ButtonWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateButtonWidget(options, metadata);\n\n return new ButtonWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class CommunityListWidget extends Widget {\n #communities: CommunityData[];\n #styles: WidgetStyles;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n this.#communities = widgetData.data.map((communityData) =>\n CommunityData.fromJSON(communityData)\n );\n\n assertNonNull(widgetData.styles, 'Community list widget data is missing styles');\n\n this.#styles = widgetData.styles;\n }\n\n get communities(): CommunityData[] {\n return this.#communities;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<CommunityListWidget, 'communities' | 'styles'> {\n return {\n ...super.toJSON(),\n communities: this.communities,\n styles: this.styles,\n };\n }\n\n /** @internal */\n static async create(\n options: AddCommunityListWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CommunityListWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddCommunityListWidget(options, metadata);\n\n return new CommunityListWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateCommunityListWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CommunityListWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateCommunityListWidget(options, metadata);\n\n return new CommunityListWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class PostFlairWidget extends Widget {\n #styles: WidgetStyles;\n #templates: PostFlairTemplateData[];\n #display: 'list' | 'cloud';\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.styles, 'Post flair widget data is missing styles');\n\n this.#styles = widgetData.styles;\n this.#templates = widgetData.order.map((templateId) => widgetData.templates[templateId]);\n\n if (\n !((widgetData.display && widgetData.display === 'list') || widgetData.display === 'cloud')\n ) {\n throw new Error('Post flair widget data is missing display type');\n }\n\n this.#display = widgetData.display;\n }\n\n get styles(): WidgetStyles {\n return this.#styles;\n }\n\n get templates(): PostFlairTemplateData[] {\n return this.#templates;\n }\n\n get display(): 'list' | 'cloud' {\n return this.#display;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<PostFlairWidget, 'templates' | 'display' | 'styles'> {\n return {\n ...super.toJSON(),\n styles: this.styles,\n templates: this.templates,\n display: this.display,\n };\n }\n\n /** @internal */\n static async create(\n options: AddPostFlairWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<PostFlairWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddPostFlairWidget(options, metadata);\n\n return new PostFlairWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdatePostFlairWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<PostFlairWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdatePostFlairWidget(options, metadata);\n\n return new PostFlairWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\nexport class CustomWidget extends Widget {\n #images: WidgetImage[];\n #text: string;\n #stylesheetUrl: string;\n #height: number;\n #css: string;\n\n constructor(widgetData: WidgetItem, subredditName: string, metadata: Metadata | undefined) {\n super(widgetData, subredditName, metadata);\n\n assertNonNull(widgetData.stylesheetUrl, 'Custom widget data is missing stylesheetUrl');\n assertNonNull(widgetData.height, 'Custom widget data is missing height');\n assertNonNull(widgetData.css, 'Custom widget data is missing css');\n\n this.#images = widgetData.imageData ?? [];\n this.#text = widgetData.text ?? '';\n this.#stylesheetUrl = widgetData.stylesheetUrl;\n this.#height = widgetData.height;\n this.#css = widgetData.css;\n }\n\n get images(): WidgetImage[] {\n return this.#images;\n }\n\n get text(): string {\n return this.#text;\n }\n\n get stylesheetUrl(): string {\n return this.#stylesheetUrl;\n }\n\n get height(): number {\n return this.#height;\n }\n\n get css(): string {\n return this.#css;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> &\n Pick<CustomWidget, 'images' | 'text' | 'stylesheetUrl' | 'height' | 'css'> {\n return {\n ...super.toJSON(),\n images: this.images,\n text: this.text,\n stylesheetUrl: this.stylesheetUrl,\n height: this.height,\n css: this.css,\n };\n }\n\n /** @internal */\n static async create(\n options: AddCustomWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CustomWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.AddCustomWidget(options, metadata);\n\n return new CustomWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n\n /** @internal */\n static async update(\n options: UpdateCustomWidgetRequest,\n metadata: Metadata | undefined\n ): Promise<CustomWidget> {\n const client = Devvit.redditAPIPlugins.Widgets;\n const response = await client.UpdateCustomWidget(options, metadata);\n\n return new CustomWidget(WidgetItem.fromJSON(response), options.subreddit, metadata);\n }\n}\n\ntype SubredditRule = {\n description: string;\n priority: number;\n shortName: string;\n violationReason: string;\n};\n\nexport class SubredditRulesWidget extends Widget {\n readonly #rules: SubredditRule[];\n\n constructor(\n subredditAboutRulesRsp: SubredditAboutRulesResponse,\n widgetData: WidgetItem,\n subredditName: string,\n metadata: Metadata | undefined\n ) {\n super(widgetData, subredditName, metadata);\n\n const rules = subredditAboutRulesRsp.rules.map(\n ({ description, priority, shortName, violationReason }) => {\n assertNonNull(description, 'Subreddit rule is missing description');\n assertNonNull(priority, 'Subreddit rule is missing priority');\n assertNonNull(shortName, 'Subreddit rule is missing shortName');\n assertNonNull(violationReason, 'Subreddit rule is missing violationReason');\n return {\n description,\n priority,\n shortName,\n violationReason,\n };\n }\n );\n\n this.#rules = rules;\n }\n\n get rules(): SubredditRule[] {\n return this.#rules;\n }\n\n override toJSON(): ReturnType<Widget['toJSON']> & Pick<SubredditRulesWidget, 'rules'> {\n return {\n ...super.toJSON(),\n rules: this.rules,\n };\n }\n}\n", "import type {\n Metadata,\n WikiPage as WikiPageProto,\n WikiPageRevision as WikiPageRevisionProto,\n WikiPageRevisionListing,\n WikiPageSettings_Data,\n} from '@devvit/protos';\nimport { assertNonNull } from '@devvit/shared-types/NonNull.js';\n\nimport { Devvit } from '../../../devvit/Devvit.js';\nimport { makeGettersEnumerable } from '../helpers/makeGettersEnumerable.js';\nimport { Listing } from './Listing.js';\nimport { User } from './User.js';\n\nexport type CreateWikiPageOptions = {\n /** The name of the subreddit to create the page in. */\n subredditName: string;\n /** The name of the page to create. */\n page: string;\n /** The content of the page. */\n content: string;\n /** The reason for creating the page. */\n reason?: string;\n};\n\nexport type UpdateWikiPageOptions = {\n /** The name of the subreddit the page is in. */\n subredditName: string;\n /** The name of the page to update. */\n page: string;\n /** The new content of the page. */\n content: string;\n /** The reason for updating the page. */\n reason?: string;\n};\n\nexport type GetPageRevisionsOptions = {\n /** The name of the subreddit the page is in. */\n subredditName: string;\n /** The name of the page to get revisions for. */\n page: string;\n /** The number of revisions to get per request. */\n pageSize?: number;\n /** The maximum number of revisions to get. */\n limit?: number;\n /** The ID of the revision to start at. */\n after?: string;\n};\n\nexport enum WikiPagePermissionLevel {\n /** Use subreddit wiki permissions */\n SUBREDDIT_PERMISSIONS = 0,\n /** Only approved wiki contributors for this page may edit */\n APPROVED_CONTRIBUTORS_ONLY = 1,\n /** Only mods may edit and view */\n MODS_ONLY = 2,\n}\n\nexport type UpdatePageSettingsOptions = {\n /** The name of the subreddit the page is in. */\n subredditName: string;\n /** The name of the page to update settings for. */\n page: string;\n /** Whether the page should be listed in the wiki index. */\n listed: boolean;\n /** The permission level for the page. */\n permLevel: WikiPagePermissionLevel;\n};\n\nexport class WikiPage {\n #name: string;\n #subredditName: string;\n #content: string;\n #contentHtml: string;\n #revisionId: string;\n #revisionDate: Date;\n #revisionReason: string;\n #revisionAuthor: User | undefined;\n\n #metadata: Metadata | undefined;\n\n /**\n * @internal\n */\n constructor(\n name: string,\n subredditName: string,\n data: WikiPageProto,\n metadata: Metadata | undefined\n ) {\n makeGettersEnumerable(this);\n\n this.#name = name;\n this.#subredditName = subredditName;\n this.#content = data.contentMd;\n this.#contentHtml = data.contentHtml;\n this.#revisionId = data.revisionId;\n this.#revisionDate = new Date(data.revisionDate * 1000); // data.revisionDate is represented in seconds, so multiply by 1000 to get milliseconds\n this.#revisionReason = data.reason ?? '';\n this.#revisionAuthor = data.revisionBy?.data\n ? new User(data.revisionBy.data, metadata)\n : undefined;\n\n this.#metadata = metadata;\n }\n\n /** The name of the page. */\n get name(): string {\n return this.#name;\n }\n\n /** The name of the subreddit the page is in. */\n get subredditName(): string {\n return this.#subredditName;\n }\n\n /** The Markdown content of the page. */\n get content(): string {\n return this.#content;\n }\n\n /** The HTML content of the page. */\n get contentHtml(): string {\n return this.#contentHtml;\n }\n\n /** The ID of the revision. */\n get revisionId(): string {\n return this.#revisionId;\n }\n\n /** The date of the revision. */\n get revisionDate(): Date {\n return this.#revisionDate;\n }\n\n /** The reason for the revision. */\n get revisionReason(): string {\n return this.#revisionReason;\n }\n\n /** The author of this revision. */\n get revisionAuthor(): User | undefined {\n return this.#revisionAuthor;\n }\n\n toJSON(): Pick<\n WikiPage,\n | 'name'\n | 'subredditName'\n | 'content'\n | 'contentHtml'\n | 'revisionId'\n | 'revisionDate'\n | 'revisionReason'\n > & {\n revisionAuthor: ReturnType<User['toJSON']> | undefined;\n } {\n return {\n name: this.#name,\n subredditName: this.#subredditName,\n content: this.#content,\n contentHtml: this.#contentHtml,\n revisionId: this.#revisionId,\n revisionDate: this.#revisionDate,\n revisionReason: this.#revisionReason,\n revisionAuthor: this.#revisionAuthor?.toJSON(),\n };\n }\n\n /** Update this page. */\n async update(content: string, reason?: string): Promise<WikiPage> {\n return WikiPage.updatePage(\n {\n subredditName: this.#subredditName,\n page: this.#name,\n content,\n reason,\n },\n this.#metadata\n );\n }\n\n /** Get the revisions for this page. */\n async getRevisions(\n options: Omit<GetPageRevisionsOptions, 'subredditName' | 'page'>\n ): Promise<Listing<WikiPageRevision>> {\n return WikiPage.getPageRevisions(\n {\n subredditName: this.#subredditName,\n page: this.#name,\n ...options,\n },\n this.#metadata\n );\n }\n\n /** Revert this page to a previous revision. */\n async revertTo(revisionId: string): Promise<void> {\n return WikiPage.revertPage(this.#subredditName, this.#name, revisionId, this.#metadata);\n }\n\n /** Get the settings for this page. */\n async getSettings(): Promise<WikiPageSettings> {\n return WikiPage.getPageSettings(this.#subredditName, this.#name, this.#metadata);\n }\n\n /** Update the settings for this page. */\n async updateSettings(\n options: Omit<UpdatePageSettingsOptions, 'subredditName' | 'page'>\n ): Promise<WikiPageSettings> {\n return WikiPage.updatePageSettings(\n {\n subredditName: this.#subredditName,\n page: this.#name,\n listed: options.listed,\n permLevel: options.permLevel,\n },\n this.#metadata\n );\n }\n\n /** Add an editor to this page. */\n async addEditor(username: string): Promise<void> {\n return WikiPage.addEditor(this.#subredditName, this.#name, username, this.#metadata);\n }\n\n /** Remove an editor from this page. */\n async removeEditor(username: string): Promise<void> {\n return WikiPage.removeEditor(this.#subredditName, this.#name, username, this.#metadata);\n }\n\n /** @internal */\n static async getPage(\n subredditName: string,\n page: string,\n metadata: Metadata | undefined\n ): Promise<WikiPage> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.GetWikiPage(\n {\n subreddit: subredditName,\n page,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to get wiki page');\n\n return new WikiPage(page, subredditName, response.data, metadata);\n }\n\n /** @internal */\n static async getPages(subredditName: string, metadata: Metadata | undefined): Promise<string[]> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.GetWikiPages({ subreddit: subredditName }, metadata);\n\n return response.data || [];\n }\n\n /** @internal */\n static async createPage(\n options: CreateWikiPageOptions,\n metadata: Metadata | undefined\n ): Promise<WikiPage> {\n return WikiPage.updatePage(options, metadata);\n }\n\n /** @internal */\n static async updatePage(\n options: UpdateWikiPageOptions,\n metadata: Metadata | undefined\n ): Promise<WikiPage> {\n const client = Devvit.redditAPIPlugins.Wiki;\n await client.EditWikiPage(\n {\n subreddit: options.subredditName,\n page: options.page,\n content: options.content,\n reason: options.reason ?? '',\n },\n metadata\n );\n\n return WikiPage.getPage(options.subredditName, options.page, metadata);\n }\n\n /** @internal */\n static getPageRevisions(\n options: GetPageRevisionsOptions,\n metadata: Metadata | undefined\n ): Listing<WikiPageRevision> {\n const client = Devvit.redditAPIPlugins.Wiki;\n return new Listing({\n hasMore: true,\n after: options.after,\n limit: options.limit,\n pageSize: options.pageSize,\n async fetch(fetchOptions) {\n const response = await client.GetWikiPageRevisions(\n {\n subreddit: options.subredditName,\n page: options.page,\n limit: fetchOptions.limit,\n after: fetchOptions.after,\n before: fetchOptions.before,\n },\n metadata\n );\n\n return wikiPageRevisionListingProtoToWikiPageRevision(response, metadata);\n },\n });\n }\n\n /** @internal */\n static async revertPage(\n subredditName: string,\n page: string,\n revisionId: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Wiki;\n\n await client.RevertWikiPage(\n {\n subreddit: subredditName,\n page,\n revision: revisionId,\n },\n metadata\n );\n }\n\n /** @internal */\n static async getPageSettings(\n subredditName: string,\n page: string,\n metadata: Metadata | undefined\n ): Promise<WikiPageSettings> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.GetWikiPageSettings(\n {\n subreddit: subredditName,\n page,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to get wiki page settings');\n\n return new WikiPageSettings(response.data, metadata);\n }\n\n /** @internal */\n static async updatePageSettings(\n options: UpdatePageSettingsOptions,\n metadata: Metadata | undefined\n ): Promise<WikiPageSettings> {\n const client = Devvit.redditAPIPlugins.Wiki;\n const response = await client.UpdateWikiPageSettings(\n {\n subreddit: options.subredditName,\n page: options.page,\n listed: options.listed ? 'on' : '',\n permlevel: options.permLevel,\n },\n metadata\n );\n\n assertNonNull(response.data, 'Failed to update wiki page settings');\n\n return new WikiPageSettings(response.data, metadata);\n }\n\n /** @internal */\n static async addEditor(\n subredditName: string,\n page: string,\n username: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Wiki;\n await client.AllowEditor(\n {\n act: 'add',\n subreddit: subredditName,\n page,\n username,\n },\n metadata\n );\n }\n\n /** @internal */\n static async removeEditor(\n subredditName: string,\n page: string,\n username: string,\n metadata: Metadata | undefined\n ): Promise<void> {\n const client = Devvit.redditAPIPlugins.Wiki;\n await client.AllowEditor(\n {\n act: 'del',\n subreddit: subredditName,\n page,\n username,\n },\n metadata\n );\n }\n}\n\nexport class WikiPageRevision {\n #id: string;\n #page: string;\n #date: Date;\n #author: User;\n #reason: string;\n #hidden: boolean;\n\n constructor(data: WikiPageRevisionProto, metadata: Metadata | undefined) {\n this.#id = data.id;\n this.#page = data.page;\n this.#date = new Date(data.timestamp);\n\n assertNonNull(data.author?.data, 'Wiki page revision author details are missing');\n this.#author = new User(data.author.data, metadata);\n\n this.#reason = data.reason ?? '';\n this.#hidden = data.revisionHidden ?? false;\n }\n\n get id(): string {\n return this.#id;\n }\n\n get page(): string {\n return this.#page;\n }\n\n get date(): Date {\n return this.#date;\n }\n\n get author(): User {\n return this.#author;\n }\n\n get reason(): string {\n return this.#reason;\n }\n\n get hidden(): boolean {\n return this.#hidden;\n }\n\n toJSON(): Pick<WikiPageRevision, 'id' | 'page' | 'date' | 'reason' | 'hidden'> & {\n author: ReturnType<User['toJSON']>;\n } {\n return {\n id: this.#id,\n page: this.#page,\n date: this.#date,\n author: this.#author.toJSON(),\n reason: this.#reason,\n hidden: this.#hidden,\n };\n }\n}\n\nexport class WikiPageSettings {\n #listed: boolean;\n #permLevel: WikiPagePermissionLevel;\n #editors: User[];\n\n constructor(data: WikiPageSettings_Data, metadata: Metadata | undefined) {\n this.#listed = data.listed;\n this.#permLevel = data.permLevel;\n this.#editors = data.editors.map((editor) => {\n assertNonNull(editor.data, 'Wiki page editor details are missing');\n return new User(editor.data, metadata);\n });\n }\n\n get listed(): boolean {\n return this.#listed;\n }\n\n get permLevel(): WikiPagePermissionLevel {\n return this.#permLevel;\n }\n\n get editors(): User[] {\n return this.#editors;\n }\n\n toJSON(): Pick<WikiPageSettings, 'listed' | 'permLevel'> & {\n editors: ReturnType<User['toJSON']>[];\n } {\n return {\n listed: this.#listed,\n permLevel: this.#permLevel,\n editors: this.#editors.map((editor) => editor.toJSON()),\n };\n }\n}\n\nfunction wikiPageRevisionListingProtoToWikiPageRevision(\n listingProto: WikiPageRevisionListing,\n metadata: Metadata | undefined\n): { children: WikiPageRevision[]; before: string | undefined; after: string | undefined } {\n assertNonNull(listingProto.data?.children, 'Wiki page revision listing is missing children');\n\n const children = listingProto.data.children.map((child) => {\n return new WikiPageRevision(child, metadata);\n });\n\n return {\n children,\n before: listingProto.data.before,\n after: listingProto.data.after,\n };\n}\n", "import type { Metadata } from '@devvit/protos';\nimport type { JSONObject } from '@devvit/shared-types/json.js';\nimport type { T2ID } from '@devvit/shared-types/tid.js';\nimport { asT2ID } from '@devvit/shared-types/tid.js';\n\nimport { GraphQL } from '../graphql/GraphQL.js';\n\n/**\n * A type representing a Vault (crypto wallet).\n */\nexport type Vault = {\n /**\n * The provider of the Vault address.\n * @example 'ethereum'\n */\n provider: string;\n\n /**\n * The ID (starting with t2_) of the user owning the Vault.\n * @example 't2_1w72'\n */\n userId: T2ID;\n\n /**\n * The address of the Vault.\n * @example '0x205ee28744456bDBf180A0Fa7De51e0F116d54Ed'\n */\n address: string;\n\n /**\n * The date the Vault was created.\n */\n createdAt: string;\n\n /**\n * Whether the Vault is active.\n */\n isActive: boolean;\n};\n\n/**\n * @internal\n */\nexport async function getVaultByAddress(\n address: string,\n metadata: Metadata | undefined\n): Promise<Vault> {\n return getVaultByParams(\n 'GetVaultContactByAddress',\n '3e2f7966a5c120e64fd2795d06a46595c52d988185be98d3ed71c3f81ae80d2e',\n {\n provider: 'ethereum', // Only one supported at the moment\n address,\n },\n metadata\n );\n}\n\n/**\n * @internal\n */\nexport async function getVaultByUserId(\n userId: T2ID,\n metadata: Metadata | undefined\n): Promise<Vault> {\n return getVaultByParams(\n 'GetVaultContactByUserId',\n 'a854ddc19d0e22c4f36ed917fdbd568f299f3571427e393aee5e2972080fffe9',\n {\n provider: 'ethereum', // Only one supported at the moment\n userId,\n },\n metadata\n );\n}\n\nasync function getVaultByParams(\n operationName: string,\n queryHash: string,\n params: JSONObject,\n metadata: Metadata | undefined\n): Promise<Vault> {\n const response = await GraphQL.query(operationName, queryHash, params, metadata);\n const contact = response?.data?.vault?.contact;\n\n return {\n provider: contact?.provider,\n userId: asT2ID(contact?.userId),\n address: contact?.address,\n createdAt: contact?.createdAt,\n isActive: contact?.isActive,\n };\n}\n", "// https://github.com/cure53/DOMPurify/blob/f89d72681513d749d303798ced02fa2799340989/src/tags.js#L202\nconst DISALLOWED_ELEMENTS = [\n // We don't allow images bypass reddit safety checks!\n /**\n * 1/31/24: We need to allow images so we can make things happen.\n * https://reddit.atlassian.net/browse/DX-5740\n */\n // 'image',\n 'animate',\n 'color-profile',\n 'cursor',\n 'discard',\n 'font-face',\n 'font-face-format',\n 'font-face-name',\n 'font-face-src',\n 'font-face-uri',\n 'foreignobject',\n 'hatch',\n 'hatchpath',\n 'mesh',\n 'meshgradient',\n 'meshpatch',\n 'meshrow',\n 'missing-glyph',\n 'script',\n 'set',\n 'solidcolor',\n 'unknown',\n 'use',\n];\n// We don't allow images bypass reddit safety checks!\nconst DISALLOWED_STYLES = [\n /**\n * 1/31/24: We need to allow images so we can make things happen.\n * https://reddit.atlassian.net/browse/DX-5740\n */\n // 'background',\n // 'background-image',\n 'border-image',\n 'border-image-source',\n 'behavior',\n 'expression',\n 'list-style-image',\n 'cursor',\n 'content',\n];\nconst DISALLOWED_ATTRIBUTES = [\n 'onload',\n 'onerror',\n 'onclick',\n 'onmouseover',\n /**\n * 1/31/24: We need to allow images so we can make things happen.\n * https://reddit.atlassian.net/browse/DX-5740\n */\n // 'href',\n];\nfunction ensureXmlns(svg) {\n if (/xmlns=[\"']http:\\/\\/www\\.w3\\.org\\/2000\\/svg[\"']/.test(svg)) {\n return svg;\n }\n return svg.replace(/<svg\\b/, '<svg xmlns=\"http://www.w3.org/2000/svg\"');\n}\nexport function sanitizeSvg(svg) {\n if (!svg) {\n return undefined;\n }\n if (!svg.trim().startsWith('<svg')) {\n console.log('The provided string is not a valid SVG.');\n return undefined; // Return an empty string if it's not an SVG\n }\n try {\n // Create regex patterns (case-insensitive)\n // eslint-disable-next-line security/detect-non-literal-regexp\n const disallowedElementsRegex = new RegExp(`<\\\\s*(${DISALLOWED_ELEMENTS.join('|')})\\\\b`, 'gi');\n // eslint-disable-next-line security/detect-non-literal-regexp\n const disallowedStylesRegex = new RegExp(`(${DISALLOWED_STYLES.join('|')})\\\\s*:\\\\s*url\\\\s*\\\\((['\"]?)(.*?)\\\\2\\\\)`, 'gi');\n // eslint-disable-next-line security/detect-non-literal-regexp\n const disallowedAttributesRegex = new RegExp(`(${DISALLOWED_ATTRIBUTES.join('|')})\\\\s*(=\\\\s*(['\"]?)(.*?)\\\\3)?`, 'gi');\n // Remove newlines and excessive whitespace\n svg = svg.trim().replace(/\\s+/g, ' ');\n // SVGs as data URLs require it to be a valid XML file meaning\n // xmlns is required where it is not within most browsers.\n svg = ensureXmlns(svg);\n // Find disallowed elements, styles, and attributes\n const elementMatches = svg.match(disallowedElementsRegex) || [];\n const styleMatches = svg.match(disallowedStylesRegex) || [];\n const attributeMatches = svg.match(disallowedAttributesRegex) || [];\n let isInvalid = false;\n // Log and sanitize if disallowed elements are found\n if (elementMatches.length > 0) {\n isInvalid = true;\n console.warn(`Disallowed elements detected in SVG: ${elementMatches\n .map((x) => x.replace('<', ''))\n .join(', ')}`);\n }\n // Log and sanitize if disallowed styles are found\n if (styleMatches.length > 0) {\n isInvalid = true;\n console.warn(`Disallowed styles detected in SVG: ${styleMatches.map((x) => x.split(':')[0]).join(', ')}`);\n }\n // Log and sanitize if disallowed attributes are found\n if (attributeMatches.length > 0) {\n isInvalid = true;\n console.warn(`Disallowed attributes detected in SVG: ${attributeMatches\n .map((x) => x.split('=')[0])\n .join(', ')}`);\n }\n if (isInvalid) {\n return undefined;\n }\n return svg; // Return the original SVG string if it's clean\n }\n catch {\n return undefined;\n }\n}\n", "import { sanitizeSvg } from '@devvit/shared-types/sanitizeSvg.js';\n\n/**\n * @experimental\n *\n * A helper to allow SVG functionality within image tags.\n *\n * @example\n * ```ts\n * import { Devvit, svg } from '@devvit/public-api';\n * const App = () => {\n * const color = 'gold'\n * return (\n * <hstack>\n * <image\n * url={svg`<svg viewBox=\"0 0 10 10\" xmlns=\"http://www.w3.org/2000/svg\">\n * <circle fill=\"${color}\" cx=\"5\" cy=\"5\" r=\"4\" />\n * </svg>`}\n * imageHeight={100}\n * imageWidth={100}\n * />\n * </hstack>\n * )\n * }\n * ```\n */\nexport function svg(\n strings: TemplateStringsArray,\n ...args: (string | number)[]\n): `data:image/svg+xml;charset=UTF-8,${string}` | '' {\n let str = '';\n\n // Assemble the SVG string\n strings.forEach((string, index) => {\n str += string;\n const arg = args[index];\n\n if (arg !== undefined) {\n // Cast number to string\n str += `${arg}`;\n }\n });\n\n // Sanitize the SVG string\n str = sanitizeSvg(str);\n\n if (str === undefined) {\n return '';\n }\n\n // Return the sanitized SVG string as a data URI\n // This fixes things like hexadecimal colors that URLs treat as special characters!\n return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(str)}`;\n}\n", "import { EffectType, type UIEvent } from '@devvit/protos';\nimport { WebViewVisibility } from '@devvit/protos';\nimport type { WebViewAppMessage } from '@devvit/protos/types/devvit/ui/effects/web_view/v1alpha/post_message.js';\nimport type { JSONValue } from '@devvit/shared-types/json.js';\nimport { StringUtil } from '@devvit/shared-types/StringUtil.js';\n\nimport type {\n UseWebViewOnMessage,\n UseWebViewOptions,\n UseWebViewResult,\n} from '../../../../index.js';\nimport { webViewMessageIsInternalAndClientScope } from '../../helpers/devvitInternalMessage.js';\nimport { registerHook } from './BlocksHandler.js';\nimport type { RenderContext } from './RenderContext.js';\nimport type { Hook, HookParams } from './types.js';\n\nclass WebViewHook<From extends JSONValue, To extends JSONValue> implements Hook {\n state: {\n // Auto-incrementing count of the number of WebviewMessage effects called this frame.\n // Used as part of the dedup key for emitEvent to prevent messages from being dedup'd.\n messageCount: number;\n } = { messageCount: 0 };\n #hookId: string;\n // This url is the path to the asset that will be loaded in the web view.\n // It is ensured to be a valid path prior to the effect being emitted.\n #url: string;\n #onMessage: UseWebViewOnMessage<From, To>;\n #onUnmount?: (hook: UseWebViewResult) => void | Promise<void>;\n #renderContext: RenderContext;\n\n constructor(params: HookParams, options: UseWebViewOptions<From, To>) {\n // Default to index.html if there is no URL provided.\n this.#url = options.url ?? 'index.html';\n this.#hookId = params.hookId;\n this.#onMessage = options.onMessage;\n this.#onUnmount = options.onUnmount;\n this.#renderContext = params.context;\n }\n\n /**\n * Handles UI events originating from the web view and calls associated callbacks for the Devvit app to handle.\n */\n async onUIEvent(event: UIEvent): Promise<void> {\n if (event.webView?.fullScreen) {\n const isVisible = event.webView.fullScreen.visibility === WebViewVisibility.WEBVIEW_VISIBLE;\n if (!isVisible && this.#onUnmount) await this.#onUnmount(this);\n } else if (event.webView?.postMessage) {\n // Handle messages sent from web view -> Devvit app\n\n // Fallback to deprecated message field for mobile client backwards compatibility\n const message = event.webView.postMessage.jsonString\n ? JSON.parse(event.webView.postMessage.jsonString)\n : event.webView.postMessage.message;\n\n // TODO: Temporary. Remove this filter once clients are updated.\n if (webViewMessageIsInternalAndClientScope(message)) return;\n\n await this.#onMessage(message, this);\n }\n }\n\n /**\n * Send a message from a Devvit app to a web view (fullscreen).\n */\n postMessage = (message: To): void => {\n try {\n // Encode message as JSON for consistency with the mobile clients\n const jsonString = JSON.stringify(message);\n // Handle messages sent from Devvit app -> web view\n this.#renderContext.emitEffect(`postMessage${this.state.messageCount++}`, {\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n postMessage: {\n webViewId: this.#hookId,\n app: <WebViewAppMessage>{\n message, // This is deprecated, but populated for mobile client backwards compatibility\n jsonString,\n },\n },\n },\n });\n } catch (e) {\n console.error(StringUtil.caughtToString(e));\n // Safety net if something went wrong with JSON.stringify\n throw Error('Something went wrong. Please check the contents of your postMessage.');\n }\n };\n\n /**\n * Triggers the fullscreen effect to show the web view in fullscreen mode.\n */\n mount = (): void => {\n const assets = this.#renderContext?.devvitContext?.assets;\n\n // Get the public URL for the asset. Returns an empty string if the asset is not found.\n const url = assets.getURL(this.#url, { webView: true });\n\n if (!url) {\n throw Error(`useWebView fullscreen request failed; web view asset could not be found`);\n }\n\n this.#emitFullscreenEffect(true, url);\n };\n\n /**\n * Triggers the fullscreen effect to hide the open web view.\n */\n unmount = (): void => {\n this.#emitFullscreenEffect(false, '');\n };\n\n #emitFullscreenEffect = (show: boolean, url: string): void => {\n this.#renderContext.emitEffect('fullscreen', {\n type: EffectType.EFFECT_WEB_VIEW,\n webView: {\n fullscreen: {\n id: this.#hookId,\n show,\n url,\n },\n },\n });\n };\n}\n\n/**\n * Use this hook to handle a web view's visibility state and any messages sent to your app.\n * */\nexport function useWebView<From extends JSONValue = JSONValue, To extends JSONValue = JSONValue>(\n options: UseWebViewOptions<From, To>\n): UseWebViewResult<To> {\n const hook = registerHook({\n namespace: 'useWebView',\n initializer: (params) => new WebViewHook(params, options),\n });\n return {\n postMessage: hook.postMessage,\n mount: hook.mount,\n unmount: hook.unmount,\n };\n}\n", "import type {\n WebViewInternalMessage,\n WebViewInternalMessageScope,\n} from '@devvit/protos/types/devvit/ui/effects/web_view/v1alpha/post_message.js';\n\n/**\n * Messages which are scoped to \"client\" and are of type \"devvit-internal\" should not be sent to the app.\n * These messages are designed to be handled by client platforms.\n *\n * Not all clients are updated yet, so this check is temporary until clients beging filtering these messages out.\n *\n * @param message - The web view message to check\n * @returns True if the data is a WebViewInternalMessage\n **/\nexport const webViewMessageIsInternalAndClientScope = (\n message: unknown\n): message is WebViewInternalMessage => {\n if (message === null || typeof message !== 'object') return false;\n return (\n 'scope' in message &&\n message.scope === (0 satisfies WebViewInternalMessageScope.CLIENT) &&\n 'type' in message &&\n message.type === 'devvit-internal'\n );\n};\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["require_kind_of", "__commonJSMin", "exports", "module", "toString", "val", "type", "isGeneratorFn", "isArray", "isBuffer", "isArguments", "isDate", "isError", "isRegexp", "ctorName", "isGeneratorObj", "name", "err", "require_shallow_clone", "__commonJSMin", "exports", "module", "valueOf", "typeOf", "clone", "val", "deep", "cloneBuffer", "cloneSymbol", "cloneArrayBuffer", "cloneTypedArray", "cloneRegExp", "flags", "re", "res", "len", "buf", "require_isobject", "__commonJSMin", "exports", "module", "val", "require_is_plain_object", "__commonJSMin", "exports", "module", "isObject", "isObjectObject", "o", "ctor", "prot", "require_clone_deep", "__commonJSMin", "exports", "module", "clone", "typeOf", "isPlainObject", "cloneDeep", "val", "instanceClone", "cloneObjectDeep", "cloneArrayDeep", "res", "key", "i", "require_base64_js", "__commonJSMin", "exports", "byteLength", "toByteArray", "fromByteArray", "lookup", "revLookup", "Arr", "code", "i", "len", "getLens", "b64", "validLen", "placeHoldersLen", "lens", "_byteLength", "tmp", "arr", "curByte", "tripletToBase64", "num", "encodeChunk", "uint8", "start", "end", "output", "extraBytes", "parts", "maxChunkLength", "len2", "RunAs", "Header", "AppDebug", "assert", "condition", "msg", "T_PREFIX", "isT1ID", "id", "isT2ID", "isT3ID", "isT4ID", "isT5ID", "isT6ID", "assertT1ID", "assert", "assertT2ID", "assertT3ID", "assertT4ID", "assertT5ID", "assertT6ID", "asT1ID", "asT2ID", "asT3ID", "asT4ID", "asT5ID", "asT6ID", "asTID", "isCommentId", "protos", "Actor", "config", "SettingScope", "assertValidFormFields", "fields", "seenNames", "field", "fieldName", "assertAppSecretsOnly", "Hook", "ALL_ICON_NAMES", "DeletionReason", "EventSource", "AppSettingsDefinition", "GetFieldsResponse", "FormFieldType", "transformFormFields", "fields", "field", "transformStringField", "transformImageField", "transformParagraphField", "transformNumberField", "transformSelectField", "transformBooleanField", "transformGroupField", "asyncLocalStorage", "AsyncLocalStorage", "localMetadata", "getMetadata", "metadata", "withMetadata", "callback", "extendDevvitPrototype", "key", "value", "Devvit", "req", "meta", "rest", "guaranteedMeta", "withMetadata", "ValidateFormResponse", "RealtimeSubscriptionStatus", "assertNonNull", "val", "msg", "makeUseChannelHook", "reconciler", "useChannel", "options", "hookIndex", "currentState", "previousState", "appId", "Header", "assertNonNull", "installationId", "send", "msg", "name", "subscribe", "unsubscribe", "hook", "event", "result", "RealtimeSubscriptionStatus", "hookState", "Hook", "status", "FormFieldType", "flattenFormFieldValue", "value", "getFormValues", "results", "acc", "key", "val", "makeUseFormHook", "reconciler", "useForm", "form", "onSubmit", "hookIndex", "componentKey", "currentState", "previousState", "formKey", "hookState", "Hook", "formSubmittedEvent", "response", "getFormValues", "makeUseIntervalHook", "reconciler", "useInterval", "callback", "requestedDelayMs", "hookIndex", "currentState", "previousState", "minDelay", "delayMs", "hookState", "Hook", "start", "i", "stateItem", "stop", "response", "makeUseStateHook", "reconciler", "useState", "initialState", "hookIndex", "currentState", "previousState", "stateSetter", "value", "valueOrFunction", "SystemClock", "_namespaced", "key", "_lock", "pollEvery", "maxPollingTimeout", "minTtlValue", "retryLimit", "errorRetryProbability", "clientRetryDelay", "allowStaleFor", "_unwrap", "entry", "_redis", "_localCache", "_clock", "_state", "_PromiseCache_instances", "localCachedAnswer_fn", "maybeRefreshCache_fn", "refreshCache_fn", "pollForCache_fn", "updateCache_fn", "calculateRamp_fn", "redisEntry_fn", "enforceTTL_fn", "PromiseCache", "redis", "state", "clock", "__privateAdd", "__privateSet", "closure", "options", "__privateGet", "__privateMethod", "localCachedAnswer", "existing", "val", "now", "hasRetryableError", "expires", "rampProbability", "lockKey", "lockExpiration", "start", "ttl", "pollingTimeout", "resolve", "e", "expiry", "remaining", "makeCache", "redis", "state", "clock", "SystemClock", "pc", "PromiseCache", "assertValidUrl", "path", "_assetMap", "_webViewAssetMap", "_AssetsClient_instances", "getURL_fn", "getURLs_fn", "AssetsClient", "__privateAdd", "__privateSet", "Devvit", "assetPathOrPaths", "options", "__privateMethod", "assetPath", "localUrl", "__privateGet", "assetPaths", "retval", "missingPaths", "cache", "_metadata", "KeyValueStorage", "metadata", "__privateAdd", "__privateSet", "key", "messages", "Devvit", "__privateGet", "value", "keys", "_metadata", "MediaClient", "metadata", "__privateAdd", "__privateSet", "opts", "response", "Devvit", "__privateGet", "_metadata", "ModLogClient", "metadata", "__privateAdd", "__privateSet", "options", "Devvit", "__privateGet", "_metadata", "RealtimeClient", "metadata", "__privateAdd", "__privateSet", "channel", "msg", "Devvit", "__privateGet", "BitfieldOverflowBehavior", "RedisKeyScope", "isRedisNilError", "e", "_storage", "_transactionId", "_metadata", "TxClient", "storage", "transactionId", "metadata", "__privateAdd", "__privateSet", "key", "__privateGet", "value", "options", "expiration", "keys", "response", "output", "result", "start", "end", "offset", "keyValues", "kv", "seconds", "members", "member", "cursor", "pattern", "count", "request", "stop", "opts", "min", "max", "field", "fields", "fieldValues", "fv", "_RedisClient", "scope", "RedisKeyScope", "Devvit", "txId", "newKey", "cmds", "commands", "argIndex", "currentArg", "command", "behavior", "toBehaviorProto", "RedisClient", "lowercase", "BitfieldOverflowBehavior", "_metadata", "SchedulerClient", "metadata", "__privateAdd", "__privateSet", "job", "Devvit", "__privateGet", "jobId", "action", "assertNonNull", "FormFieldType", "_metadata", "SettingsClient", "metadata", "__privateAdd", "__privateSet", "name", "response", "Devvit", "__privateGet", "cleanAppSettings", "getSettingsValues", "key", "value", "settingDefinition", "s", "FormFieldType", "results", "settingsDefinitions", "settingsValues", "acc", "flattenFormFieldValue", "setDefaultsIfNecessary", "definition", "EffectType", "ToastAppearance", "_effects", "_reconciler", "_webViewClient", "_postMessage", "UIClient", "reconciler", "__privateAdd", "webViewIdOrMessage", "message", "webViewId", "msg", "__privateGet", "EffectType", "__privateSet", "formKey", "data", "formDefinition", "Devvit", "hookForm", "formData", "form", "assertValidFormFields", "transformFormFields", "textOrToast", "toast", "ToastAppearance", "thingOrUrl", "url", "makeAPIClients", "metadata", "ui", "hooks", "reconciler", "modLog", "ModLogClient", "kvStore", "KeyValueStorage", "redis", "RedisClient", "cache", "makeCache", "scheduler", "SchedulerClient", "settings", "SettingsClient", "uiClient", "UIClient", "media", "MediaClient", "assets", "AssetsClient", "realtime", "RealtimeClient", "useState", "makeUseStateHook", "useInterval", "makeUseIntervalHook", "useForm", "makeUseFormHook", "useChannel", "makeUseChannelHook", "package_default", "getContextFromMetadata", "metadata", "postId", "commentId", "subredditId", "Header", "assertNonNull", "subredditName", "appAccountId", "appName", "appVersion", "userId", "debug", "parseDebug", "meta", "keyset", "lowerKeyToKey", "key", "kv", "k", "v", "package_default", "extractSettingsFields", "settings", "field", "onValidateFormHelper", "req", "metadata", "response", "formValues", "getFormValues", "flattendFields", "fieldName", "value", "validator", "context", "makeAPIClients", "getContextFromMetadata", "error", "ValidateFormResponse", "onGetSettingsFields", "Devvit", "GetFieldsResponse", "transformFormFields", "onValidateForm", "req", "metadata", "onValidateFormHelper", "registerAppSettings", "config", "AppSettingsDefinition", "extendDevvitPrototype", "CustomPostDefinition", "RenderPostResponse", "BlockRenderEventType", "EffectType", "getEffectsFromUIClient", "ui", "makeUniqueIdGenerator", "seenActionIds", "id", "uniqueId", "counter", "BlockActionType", "BlockAvatarBackground", "BlockAvatarFacing", "BlockAvatarSize", "BlockBorderWidth", "BlockButtonAppearance", "BlockButtonSize", "BlockGap", "BlockHorizontalAlignment", "BlockIconSize", "BlockImageResizeMode", "BlockPadding", "BlockRadius", "BlockSpacerShape", "BlockSpacerSize", "BlockStackDirection", "BlockTextOutline", "BlockTextOverflow", "BlockTextSize", "BlockTextStyle", "BlockTextWeight", "BlockType", "BlockVerticalAlignment", "semanticColors", "namedHTMLColorToHex", "isHexColor", "color", "isRPLColor", "semanticColors", "isNamedHTMLColor", "isRgbaColor", "isHslColor", "getHexFromRPLColor", "theme", "getHexFromNamedHTMLColor", "finalColor", "getHexFromRgbaColor", "r", "g", "b", "a", "convertedValues", "val", "aFloat", "alphaNumberValue", "number", "BlockStackDirection", "ROOT_STACK_TRANSFORM_CONTEXT", "makeStackDimensionsDetails", "props", "stackParentLayout", "blockSizes", "growDirection", "stretchDirection", "makeBlockGrowStretchDetails", "hasHeight", "isExpandingOnConstrainedRespectiveAxis", "hasWidth", "axis", "parentHasDimensionSet", "isGrowing", "parentStackDirection", "parentAlignment", "parentIsVerticalOrRoot", "BlockStackDirection", "parentIsHoritzontal", "hnone", "vnone", "isStretching", "BlockSizeUnit", "BlockStackDirection", "parseSize", "size", "parts", "unit", "omitRelativeSizes", "blockSizes", "stackParentLayout", "makeBlockSizes", "props", "transformContext", "hasWidth", "hasHeight", "DATA_PREFIX", "ACTION_HANDLERS", "ACTION_TYPES", "BlockActionType", "_assetsClient", "BlocksTransformer", "getAssetsClient", "__privateAdd", "__privateSet", "type", "props", "children", "block", "ROOT_STACK_TRANSFORM_CONTEXT", "transformContext", "height", "padding", "BlockPadding", "radius", "BlockRadius", "gap", "BlockGap", "alignment", "vertical", "horizontal", "BlockVerticalAlignment", "BlockHorizontalAlignment", "borderWidth", "color", "lightColor", "darkColor", "width", "BlockBorderWidth", "borderColor", "colors", "textSize", "BlockTextSize", "style", "BlockTextStyle", "outline", "BlockTextOutline", "weight", "BlockTextWeight", "overflow", "BlockTextOverflow", "appearance", "BlockButtonAppearance", "size", "BlockButtonSize", "resize", "BlockImageResizeMode", "BlockSpacerSize", "BlockSpacerShape", "BlockIconSize", "BlockAvatarSize", "facing", "BlockAvatarFacing", "background", "BlockAvatarBackground", "key", "p", "c", "_type", "actions", "dataSet", "action", "id", "theme", "isHexColor", "isRPLColor", "getHexFromRPLColor", "isNamedHTMLColor", "getHexFromNamedHTMLColor", "isRgbaColor", "getHexFromRgbaColor", "isHslColor", "child", "light", "dark", "tokens", "matches", "group", "input", "url", "options", "__privateGet", "BlockType", "Devvit", "direction", "backgroundColors", "blockSizes", "makeBlockSizes", "blockDimensionsDetails", "makeStackDimensionsDetails", "BlockStackDirection", "textColors", "config", "root", "assertNotString", "reified", "toXML", "node", "xml", "attributes", "key", "child", "getIndentation", "level", "indentXML", "formatted", "indentLevel", "xmlArr", "i", "isClosingTag", "_BlocksReconciler_instances", "reset_fn", "makeContextProps_fn", "flatten_fn", "_rerenderAlreadyScheduled", "BlocksReconciler", "component", "event", "state", "metadata", "dimensions", "__privateAdd", "BlocksTransformer", "apiClients", "makeAPIClients", "_dedupeKey", "effect", "results", "id", "uniqueId", "counter", "ctx", "__privateMethod", "blockElement", "handler", "hook", "rootBlockElement", "block", "element", "Devvit", "props", "actionHandlers", "action", "name", "idGenerator", "makeUniqueIdGenerator", "path", "childrens", "children", "collapsedChildren", "componentKey", "c", "result", "promiseOrError", "previousState", "pathPrefix", "BlockRenderEventType", "realtimeEvent", "delayMs", "__privateGet", "__privateSet", "EffectType", "channel", "getEffectsFromUIClient", "getContextFromMetadata", "Header", "arr", "out", "renderPost", "req", "metadata", "customPostType", "Devvit", "reconciler", "BlocksReconciler", "_props", "context", "blocksUI", "RenderPostResponse", "registerCustomPost", "config", "CustomPostDefinition", "extendDevvitPrototype", "GetFieldsResponse", "InstallationSettingsDefinition", "onGetSettingsFields", "Devvit", "GetFieldsResponse", "transformFormFields", "onValidateForm", "req", "metadata", "onValidateFormHelper", "registerInstallationSettings", "config", "InstallationSettingsDefinition", "extendDevvitPrototype", "ContextActionDefinition", "ContextActionList", "ContextActionResponse", "getUserHeader", "context", "userHeader", "Header", "getSubredditHeader", "subredditHeader", "addCSRFTokenToContext", "req", "commentId", "postId", "subredditId", "targetId", "comment", "getCommentById", "post", "getPostById", "thisItem", "getMenuItemById", "needsModCheck", "isModerator", "val", "subreddit", "getSubredditInfoById", "getModerators", "mod", "validateCSRFToken", "actionId", "thingId", "csrfData", "getActionId", "index", "getMenuItemById", "id", "Devvit", "_", "getActions", "_metadata", "menuItems", "actions", "item", "ContextActionList", "onAction", "req", "metadata", "menuItem", "commentId", "postId", "subredditId", "targetId", "assertNonNull", "event", "context", "makeAPIClients", "getContextFromMetadata", "Header", "addCSRFTokenToContext", "ContextActionResponse", "getEffectsFromUIClient", "registerMenuItems", "config", "ContextActionDefinition", "extendDevvitPrototype", "pluginIsEnabled", "settings", "SchedulerHandlerDefinition", "handleScheduledAction", "args", "metadata", "jobName", "scheduledJobHandler", "Devvit", "event", "context", "makeAPIClients", "getContextFromMetadata", "registerScheduler", "config", "SchedulerHandlerDefinition", "extendDevvitPrototype", "OnAppInstallDefinition", "OnAppUpgradeDefinition", "OnAutomoderatorFilterCommentDefinition", "OnAutomoderatorFilterPostDefinition", "OnCommentCreateDefinition", "OnCommentDeleteDefinition", "OnCommentReportDefinition", "OnCommentSubmitDefinition", "OnCommentUpdateDefinition", "OnModActionDefinition", "OnModMailDefinition", "OnPostCreateDefinition", "OnPostDeleteDefinition", "OnPostFlairUpdateDefinition", "OnPostNsfwUpdateDefinition", "OnPostReportDefinition", "OnPostSpoilerUpdateDefinition", "OnPostSubmitDefinition", "OnPostUpdateDefinition", "StringUtil", "ellipsize", "str", "limit", "capitalize", "isBlank", "caughtToString", "val", "preferredErrorProperty", "caughtToStringUntyped", "createCombinedHandler", "eventType", "handlers", "assertNonNull", "arg", "metadata", "event", "context", "makeAPIClients", "getContextFromMetadata", "results", "fn", "errResult", "joinSettledErrors", "registerTriggers", "config", "Devvit", "OnPostSubmitDefinition", "extendDevvitPrototype", "OnPostCreateDefinition", "OnPostUpdateDefinition", "OnPostReportDefinition", "OnPostDeleteDefinition", "OnPostFlairUpdateDefinition", "OnCommentSubmitDefinition", "OnCommentCreateDefinition", "OnCommentUpdateDefinition", "OnCommentReportDefinition", "OnCommentDeleteDefinition", "OnAppInstallDefinition", "OnAppUpgradeDefinition", "OnModActionDefinition", "OnModMailDefinition", "OnPostNsfwUpdateDefinition", "OnPostSpoilerUpdateDefinition", "OnAutomoderatorFilterPostDefinition", "OnAutomoderatorFilterCommentDefinition", "errs", "sum", "result", "err", "StringUtil", "EffectType", "HandleUIEventResponse", "UIEventHandlerDefinition", "import_clone_deep", "isPlainObject", "value", "isEqual", "a", "b", "isSameArray", "isPlainObject", "isSameObject", "dataViewsAreEqual", "isTypedArray", "keys1", "keys2", "key", "element", "index", "offset", "value", "handleUIEvent", "req", "metadata", "originalState", "state", "cloneDeep", "apiClients", "makeAPIClients", "formKey", "Devvit", "blocksReconciler", "BlocksReconciler", "_props", "context", "HandleUIEventResponse", "formDefinition", "postId", "commentId", "actionId", "thingId", "menuItem", "getMenuItemById", "getContextFromMetadata", "Header", "validateCSRFToken", "getFormValues", "stateWasUpdated", "isEqual", "uiEffects", "getEffectsFromUIClient", "effects", "EffectType", "registerUIEventHandler", "config", "UIEventHandlerDefinition", "extendDevvitPrototype", "CustomPostDefinition", "UIResponse", "UIEventScope", "CIRCUIT_BREAKER_MSG", "isCircuitBreaker", "err", "CIRCUIT_BREAKER_MSG", "SystemClock", "_namespaced", "key", "_lock", "pollEvery", "maxPollingTimeout", "minTtlValue", "retryLimit", "errorRetryProbability", "clientRetryDelay", "allowStaleFor", "_unwrap", "entry", "_redis", "_localCache", "_clock", "_state", "_PromiseCache_instances", "localCachedAnswer_fn", "maybeRefreshCache_fn", "refreshCache_fn", "pollForCache_fn", "updateCache_fn", "calculateRamp_fn", "redisEntry_fn", "enforceTTL_fn", "PromiseCache", "redis", "state", "clock", "__privateAdd", "__privateSet", "closure", "options", "_a", "__privateGet", "__privateMethod", "localCachedAnswer", "existing", "val", "now", "hasRetryableError", "expires", "rampProbability", "lockKey", "lockExpiration", "start", "ttl", "pollingTimeout", "resolve", "e", "expiry", "remaining", "makeCache", "redis", "state", "clock", "SystemClock", "pc", "PromiseCache", "EffectType", "ToastAppearance", "formKeyToHookId", "formKey", "match", "UseFormHook", "params", "form", "onSubmit", "event", "getFormValues", "useForm", "hook", "registerHook", "hookRefToFormKey", "hookRef", "getFormDefinition", "renderContext", "formKey", "hookId", "formKeyToHookId", "_renderContext", "_webViewMessageCount", "_webViewClient", "_webViewPostMessage", "UIClient", "renderContext", "__privateAdd", "webViewIdOrMessage", "message", "webViewId", "msg", "__privateGet", "__privateWrapper", "EffectType", "__privateSet", "formKey", "data", "formDefinition", "getFormDefinition", "formData", "form", "assertValidFormFields", "transformFormFields", "textOrToast", "toast", "ToastAppearance", "thingOrUrl", "url", "EffectType", "RealtimeSubscriptionStatus", "_context", "_debug", "_invalidate", "_opts", "_ChannelHook_instances", "emitSubscribed_fn", "_ChannelHook", "opts", "params", "__privateAdd", "__privateSet", "appID", "Header", "installID", "channel", "__privateGet", "hook", "ev", "realtime", "RealtimeSubscriptionStatus", "msg", "__privateMethod", "channels", "EffectType", "ChannelHook", "useChannel", "id", "registerHook", "EffectType", "_isTombstone", "value", "_state", "_hooks", "_RenderContext", "request", "meta", "__privateAdd", "__privateSet", "context", "changed", "__privateGet", "key", "unmounted", "t", "hooks", "state", "options", "id", "handler", "ref", "ev", "allHandlers", "_", "dedupeKey", "effect", "events", "grouped", "acc", "event", "builder", "i", "segment", "tag", "RenderContext", "intervals", "RenderContext", "event", "context", "EffectType", "_hookId", "_invalidate", "_callback", "_context", "IntervalHook", "callback", "requestedDelayMs", "params", "__privateAdd", "__privateSet", "seconds", "nanos", "__privateGet", "_event", "useInterval", "hook", "registerHook", "UIEventScope", "RenderInterruptError", "UIEventScope", "toSerializableErrorOrCircuitBreak", "e", "CIRCUIT_BREAKER_MSG", "StringUtil", "_debug", "_hookId", "_invalidate", "_ctx", "_options", "AsyncHook", "initializer", "options", "params", "__privateAdd", "__privateSet", "__privateGet", "isEqual", "requeueEvent", "UIEventScope", "event", "context", "asyncResponse", "anticipatedRequestId", "useAsync", "hook", "registerHook", "_changed", "_ctx", "_initializer", "_hookId", "UseStateHook", "initializer", "params", "__privateAdd", "__privateSet", "toSerializableErrorOrCircuitBreak", "__privateGet", "action", "RenderInterruptError", "initialValue", "e", "requeueEvent", "UIEventScope", "useState", "initialState", "hook", "registerHook", "ContextBuilder", "renderContext", "request", "metadata", "modLog", "ModLogClient", "kvStore", "KeyValueStorage", "redis", "RedisClient", "scheduler", "SchedulerClient", "settings", "SettingsClient", "ui", "UIClient", "media", "MediaClient", "assets", "AssetsClient", "realtime", "RealtimeClient", "cache", "makeCache", "apiClients", "useState", "useChannel", "useInterval", "useForm", "Header", "baseContext", "getContextFromMetadata", "_activeRenderContext", "_structuredClone", "obj", "assertValidNamespace", "input", "registerHook", "initializer", "hookSegment", "_activeRenderContext", "hookId", "context", "params", "fromNull", "_isTombstone", "hook", "_latestBlocksHandler", "MaxIterations", "_root", "_contextBuilder", "_blocksTransformer", "_BlocksHandler_instances", "debug_get", "loadHooks_fn", "handleAsyncQueues_fn", "attemptHook_fn", "handleMainQueue_fn", "renderRoot_fn", "render_fn", "renderList_fn", "renderElement_fn", "reifyProps_fn", "BlocksHandler", "root", "__privateAdd", "ContextBuilder", "BlocksTransformer", "__privateGet", "__privateSet", "request", "metadata", "RenderContext", "blocks", "drop", "eventsToProcess", "dropped", "ev", "isMainQueue", "e", "isBlockingSSR", "changed", "progress", "remaining", "UIEventScope", "iterations", "batch", "stateCopy", "__privateMethod", "isCircuitBreaker", "requeueable", "requeueEvent", "remainingRequeueEvents", "event", "tags", "_events", "component", "props", "roots", "RenderInterruptError", "element", "list", "i", "isBlockElement", "propsWithChildren", "reifiedChildren", "reifiedProps", "key", "message", "value", "getVersionNumberFromRawVersion", "rawVersion", "versionNumber", "parseDevvitUserAgent", "input", "company", "platform", "shouldShowUpgradeAppScreen", "parsedDevvitUserAgent", "getUpgradeLinkForPlatform", "UpgradeAppComponent", "context", "Devvit", "upgradeLink", "makeUpgradeAppComponent", "FeatureUnavailable", "Devvit", "UIComponentBindings", "CustomPostDefinition", "_props", "context", "_context", "makeHandler", "component", "req", "metadata", "parsedUserAgent", "parseDevvitUserAgent", "shouldShowUpgradeAppScreen", "handler", "BlocksHandler", "makeUpgradeAppComponent", "UIResponse", "registerUIRequestHandlers", "config", "definition", "method", "extendDevvitPrototype", "_appSettings", "_assets", "_config", "_customPostType", "_formDefinitions", "_installationSettings", "_menuItems", "_scheduledJobHandlers", "_triggerOnEventHandlers", "_webViewAssets", "_additionallyProvides", "_uses", "_pluginClients", "_Devvit", "Actor", "config", "__privateSet", "__privateGet", "pluginIsEnabled", "menuItem", "customPostType", "form", "onSubmit", "formKey", "job", "fields", "assertValidFormFields", "installSettings", "field", "appSettings", "triggerDefinition", "eventType", "event", "context", "def", "d", "opts", "wrapped", "method", "args", "metadata", "modLog", "scheduler", "kvStore", "redis", "media", "settings", "realtime", "fullName", "use", "registerMenuItems", "registerScheduler", "registerCustomPost", "registerUIRequestHandlers", "registerUIEventHandler", "registerInstallationSettings", "registerAppSettings", "registerTriggers", "provides", "__privateAdd", "Devvit", "createElement", "type", "props", "children", "makeGettersEnumerable", "obj", "descriptors", "key", "descriptor", "FormattingFlag", "TEXT_ELEMENT", "RAW_TEXT_ELEMENT", "LINEBREAK_ELEMENT", "LINK_ELEMENT", "COMMENT_LINK_ELEMENT", "POST_LINK_ELEMENT", "SUBREDDIT_LINK_ELEMENT", "USER_LINK_ELEMENT", "USER_MENTION_ELEMENT", "SPOILER_TEXT_ELEMENT", "PARAGRAPH_ELEMENT", "HEADING_ELEMENT", "HORIZONTAL_RULE_ELEMENT", "BLOCK_QUOTE_ELEMENT", "CODE_BLOCK_ELEMENT", "LIST_ITEM_ELEMENT", "LIST_ELEMENT", "COLUMN_ALIGN_LEFT", "COLUMN_ALIGN_RIGHT", "COLUMN_ALIGN_CENTER", "TABLE_ELEMENT", "EMBED_ELEMENT", "IMAGE_ELEMENT", "ANIMATED_IMAGE_ELEMENT", "VIDEO_ELEMENT", "makeAnimatedImage", "opts", "ANIMATED_IMAGE_ELEMENT", "makeBlockQuote", "cb", "context", "content", "mixinBlockQuoteContext", "mixinCodeBlockContext", "mixinHeadingContext", "mixinListContext", "mixinParagraphContext", "mixinTableContext", "BLOCK_QUOTE_ELEMENT", "makeCodeBlock", "mixinRawTextContext", "CODE_BLOCK_ELEMENT", "makeCommentLink", "COMMENT_LINK_ELEMENT", "makeHeadingContext", "mixinLinkContext", "HEADING_ELEMENT", "makeEmbed", "EMBED_ELEMENT", "makeHorizontalRule", "HORIZONTAL_RULE_ELEMENT", "makeImage", "opts", "IMAGE_ELEMENT", "makeLineBreak", "LINEBREAK_ELEMENT", "makeLink", "LINK_ELEMENT", "makeList", "cb", "context", "content", "mixinListItemContext", "LIST_ELEMENT", "makeListItem", "mixinBlockQuoteContext", "mixinCodeBlockContext", "mixinHeadingContext", "mixinHorizontalRuleContext", "mixinListContext", "mixinParagraphContext", "mixinTableContext", "LIST_ITEM_ELEMENT", "makeParagraph", "mixinTextContext", "mixinLinkContext", "mixinLineBreakContext", "mixinSpoilerContext", "mixinImageContext", "PARAGRAPH_ELEMENT", "makePostLink", "POST_LINK_ELEMENT", "makeRawText", "text", "RAW_TEXT_ELEMENT", "makeSpoilerText", "SPOILER_TEXT_ELEMENT", "makeSubredditLink", "SUBREDDIT_LINK_ELEMENT", "makeTable", "headerContent", "rowContent", "mixinTableContentContext", "TABLE_ELEMENT", "makeTableCell", "tableCellTextContext", "makeTableHeaderCell", "alignment", "COLUMN_ALIGN_LEFT", "COLUMN_ALIGN_RIGHT", "COLUMN_ALIGN_CENTER", "makeTableRow", "mixinTableRowContext", "makeText", "TEXT_ELEMENT", "makeUserLink", "USER_LINK_ELEMENT", "makeUserMention", "USER_MENTION_ELEMENT", "makeVideo", "VIDEO_ELEMENT", "mixinBlockQuoteContext", "ctx", "c", "opts", "cb", "makeBlockQuote", "mixinCodeBlockContext", "makeCodeBlock", "mixinEmbedContext", "makeEmbed", "mixinHeadingContext", "makeHeadingContext", "mixinHorizontalRuleContext", "makeHorizontalRule", "mixinImageContext", "makeImage", "makeAnimatedImage", "mixinLineBreakContext", "makeLineBreak", "mixinLinkContext", "makeLink", "makeCommentLink", "makePostLink", "makeSubredditLink", "makeUserLink", "makeUserMention", "mixinListContext", "makeList", "mixinListItemContext", "makeListItem", "mixinParagraphContext", "makeParagraph", "mixinRawTextContext", "text", "makeRawText", "mixinSpoilerContext", "makeSpoilerText", "mixinTableContentContext", "h", "makeTableHeaderCell", "makeTableRow", "mixinTableContext", "makeTable", "mixinTableRowContext", "makeTableCell", "mixinTextContext", "makeText", "mixinVideoContext", "makeVideo", "__classPrivateFieldSet", "receiver", "state", "value", "kind", "f", "__classPrivateFieldGet", "_RichTextBuilder_content", "RichTextBuilder", "content", "mixinParagraphContext", "mixinHeadingContext", "mixinHorizontalRuleContext", "mixinBlockQuoteContext", "mixinCodeBlockContext", "mixinEmbedContext", "mixinListContext", "mixinTableContext", "mixinImageContext", "mixinVideoContext", "_cb", "_opts", "richtextToString", "richtext", "richtextString", "RichTextBuilder", "DEFAULT_PAGE_SIZE", "DEFAULT_LIMIT", "_before", "_after", "_more", "_started", "_Listing_instances", "next_fn", "Listing", "options", "__privateAdd", "makeGettersEnumerable", "__privateSet", "__privateGet", "currentIndex", "__privateMethod", "more", "count", "limit", "children", "before", "after", "_ModNote_static", "fromProto_fn", "ModNote", "options", "metadata", "client", "Devvit", "Listing", "fetchOptions", "protoRes", "protoModNote", "__privateMethod", "deleted", "res", "assertNonNull", "asT2ID", "asT5ID", "asTID", "__privateAdd", "GraphQL", "q", "metadata", "Devvit", "operationName", "id", "variables", "MODERATOR_PERMISSIONS", "formatPermissions", "permissions", "allPermissions", "permission", "formatModeratorPermissions", "validModPermissions", "FlairType", "_id", "_subredditName", "_text", "_textColor", "_backgroundColor", "_allowableContent", "_modOnly", "_maxEmojis", "_allowUserEdits", "_metadata", "_FlairTemplate_static", "createOrUpdateFlairTemplate_fn", "_FlairTemplate", "data", "subredditName", "metadata", "__privateAdd", "makeGettersEnumerable", "assertNonNull", "__privateSet", "asFlairTextColor", "asFlairBackgroundColor", "asAllowableContent", "__privateGet", "options", "_a", "__privateMethod", "editOptions", "Devvit", "flair", "flairTemplateId", "subreddit", "allowableContent", "backgroundColor", "flairType", "maxEmojis", "modOnly", "text", "textColor", "textEditable", "response", "FlairTemplate", "convertUserFlairProtoToAPI", "userFlair", "_Flair_static", "setFlair_fn", "setUserFlairBatch_fn", "removeFlair_fn", "_Flair", "flairs", "asT3ID", "postId", "username", "csvDelimiter", "flairCsv", "userConfig", "propertyName", "Flair", "color", "Block", "UIResponse", "import_base64_js", "getCustomPostRichTextFallback", "textFallbackOptions", "richTextToTextFallbackString", "textFallback", "RichTextBuilder", "SetCustomPostPreviewRequestBodyType", "_id", "_authorId", "_authorName", "_createdAt", "_subredditId", "_subredditName", "_permalink", "_title", "_body", "_bodyHtml", "_url", "_score", "_numberOfComments", "_numberOfReports", "_thumbnail", "_approved", "_approvedAtUtc", "_bannedAtUtc", "_spam", "_stickied", "_removed", "_removedBy", "_removedByCategory", "_archived", "_edited", "_locked", "_nsfw", "_quarantined", "_spoiler", "_hidden", "_ignoringReports", "_distinguishedBy", "_flair", "_secureMedia", "_modReportReasons", "_userReportReasons", "_metadata", "_Post", "data", "metadata", "__privateAdd", "makeGettersEnumerable", "assertNonNull", "__privateSet", "asT3ID", "asT2ID", "asT5ID", "createdAt", "reason", "e", "t", "u", "__privateGet", "Comment", "options", "newPost", "suggestedSort", "ui", "isSpam", "position", "distinguishedBy", "User", "ModNote", "getThumbnailV2", "id", "client", "Devvit", "postId", "isT3ID", "response", "postData", "runAs", "runAsType", "RunAs", "previewBlock", "BlocksReconciler", "encodedCached", "Block", "textFallback", "sanitizedOptions", "richtextFallback", "getCustomPostRichTextFallback", "submitRequest", "richtextToString", "subredditName", "rest", "richtextString", "GraphQL", "handler", "BlocksHandler", "block", "UIResponse", "blocksRenderContent", "asAdmin", "post", "Listing", "fetchOptions", "listingProtosToPosts", "Post", "listingProto", "child", "thumbnail", "SocialLinkType", "_id", "_username", "_createdAt", "_linkKarma", "_commentKarma", "_nsfw", "_isAdmin", "_modPermissionsBySubreddit", "_url", "_permalink", "_hasVerifiedEmail", "_metadata", "_User", "data", "metadata", "__privateAdd", "makeGettersEnumerable", "assertNonNull", "__privateSet", "asT2ID", "isT2ID", "createdAt", "subredditName", "permissions", "__privateGet", "validModPermissions", "mods", "options", "Comment", "Post", "subreddit", "userFlairs", "Flair", "convertUserFlairProtoToAPI", "response", "GraphQL", "link", "id", "username", "getUsernameById", "client", "Devvit", "error", "key", "userId", "Listing", "fetchOptions", "listingProtosToUsers", "type", "optionalFields", "formatModeratorPermissions", "listingProtosToPostsOrComments", "User", "listingProto", "child", "userIds", "chunkSize", "userIdChunks", "i", "userDataById", "allUsers", "userData", "getCurrentUsernameFromMetadata", "Header", "getCurrentUserFromMetadata", "_id", "_authorId", "_authorName", "_body", "_createdAt", "_parentId", "_postId", "_subredditId", "_subredditName", "_replies", "_approved", "_approvedAtUtc", "_bannedAtUtc", "_edited", "_locked", "_removed", "_stickied", "_spam", "_distinguishedBy", "_numReports", "_collapsedBecauseCrowdControl", "_score", "_permalink", "_modReportReasons", "_userReportReasons", "_url", "_ignoringReports", "_metadata", "_Comment_static", "getCommentsListing_fn", "buildCommentsTree_fn", "_Comment", "data", "metadata", "__privateAdd", "_a", "makeGettersEnumerable", "assertNonNull", "__privateSet", "asT1ID", "asT2ID", "asT5ID", "isCommentId", "asT3ID", "reason", "createdAt", "__privateMethod", "__privateGet", "options", "newComment", "isSpam", "User", "makeSticky", "distinguishedBy", "stickied", "ModNote", "id", "client", "Devvit", "commentId", "isT1ID", "response", "postId", "rest", "richtextString", "richtextToString", "RunAs", "comment", "runAs", "runAsType", "sticky", "asAdmin", "Listing", "fetchOptions", "child", "depthOffset", "_b", "limit", "listingsClient", "linksAndCommentsClient", "more", "moreIds", "children", "responseChildren", "topLevelComment", "redditObjects", "parentId", "commentsMap", "parentComment", "thisMore", "Comment", "_getModerationLog", "options", "metadata", "client", "Devvit", "Listing", "fetchOptions", "response", "aboutLogResponseToModActions", "child", "id", "mod", "modId36", "createdUtc", "subreddit", "subredditNamePrefixed", "action", "srId36", "description", "details", "targetAuthor", "targetBody", "targetFullname", "targetPermalink", "targetTitle", "assertNonNull", "createdAt", "ModMailConversationState", "R2_TO_MODMAIL_CONVERSATION_STATE", "ModMailActionType", "R2_TO_MOD_ACTION_TYPE", "_metadata", "_ModMailService_instances", "transformConversationData_fn", "getConversationMessages_fn", "getConversationModActions_fn", "ModMailService", "metadata", "__privateAdd", "__privateSet", "subreddits", "state", "client", "Devvit", "conversationIds", "__privateGet", "params", "response", "conversations", "id", "__privateMethod", "createModmailConversation", "asT5ID", "notificationSubject", "conversationId", "protoConversation", "protoMessages", "protoModActions", "messages", "messageIds", "o", "messageId", "protoMessage", "modActions", "modActionIds", "modActionId", "protoModAction", "appUserId", "Header", "GraphQL", "_id", "_from", "_body", "_bodyHtml", "_created", "_metadata", "_PrivateMessage", "data", "metadata", "__privateAdd", "makeGettersEnumerable", "assertNonNull", "__privateSet", "asTID", "asT2ID", "asT5ID", "created", "options", "client", "Devvit", "Listing", "fetchOpts", "listing", "child", "to", "subject", "text", "fromSubredditName", "__privateGet", "PrivateMessage", "AboutLocations", "SubredditDescription", "SubredditWikiSettings", "AuthorFlairSettings", "PostFlairSettings", "_id", "_name", "_createdAt", "_type", "_title", "_description", "_language", "_numberOfSubscribers", "_numberOfActiveUsers", "_nsfw", "_settings", "_permalink", "_metadata", "_Subreddit", "data", "metadata", "__privateAdd", "makeGettersEnumerable", "assertNonNull", "__privateSet", "asT5ID", "createdAt", "asSubredditType", "asAllowedPostType", "asCommentMediaTypes", "__privateGet", "options", "submitPostOptions", "Post", "User", "username", "permissions", "note", "_getModerationLog", "FlairTemplate", "name", "response", "Flair", "convertUserFlairProtoToAPI", "userFlair", "client", "Devvit", "only", "Listing", "fetchOptions", "listing", "parseListing", "ids", "subredditName", "title", "message", "result", "id", "Header", "subredditId", "_getSubredditNameById", "Subreddit", "_getSubredditInfoById", "subredditInfo", "GraphQL", "_getSubredditInfoByName", "_getSubredditLeaderboard", "leaderboard", "_getSubredditStyles", "styles", "type", "postsAndComments", "child", "post", "tryParseAsPost", "comment", "tryParseAsComment", "obj", "Comment", "CommunityData", "WidgetItem", "_id", "_name", "_subredditName", "_metadata", "_Widget", "widgetData", "subredditName", "metadata", "__privateAdd", "makeGettersEnumerable", "__privateSet", "__privateGet", "response", "Devvit", "assertNonNull", "widgetsMap", "widgets", "widgetId", "ImageWidget", "CalendarWidget", "TextAreaWidget", "ButtonWidget", "CommunityListWidget", "PostFlairWidget", "CustomWidget", "rulesRsp", "SubredditRulesWidget", "id", "orderByIds", "Widget", "_images", "_ImageWidget", "data", "options", "WidgetItem", "_googleCalendarId", "_configuration", "_styles", "_CalendarWidget", "_text", "_TextAreaWidget", "_buttons", "_description", "_ButtonWidget", "_communities", "_CommunityListWidget", "communityData", "CommunityData", "_templates", "_display", "_PostFlairWidget", "templateId", "_stylesheetUrl", "_height", "_css", "_CustomWidget", "_rules", "subredditAboutRulesRsp", "rules", "description", "priority", "shortName", "violationReason", "WikiPagePermissionLevel", "_name", "_subredditName", "_content", "_contentHtml", "_revisionId", "_revisionDate", "_revisionReason", "_revisionAuthor", "_metadata", "_WikiPage", "name", "subredditName", "data", "metadata", "__privateAdd", "makeGettersEnumerable", "__privateSet", "User", "__privateGet", "content", "reason", "options", "revisionId", "username", "page", "response", "Devvit", "assertNonNull", "client", "Listing", "fetchOptions", "wikiPageRevisionListingProtoToWikiPageRevision", "WikiPageSettings", "WikiPage", "_id", "_page", "_date", "_author", "_reason", "_hidden", "WikiPageRevision", "_listed", "_permLevel", "_editors", "editor", "listingProto", "child", "getVaultByAddress", "address", "metadata", "getVaultByParams", "getVaultByUserId", "userId", "operationName", "queryHash", "params", "contact", "GraphQL", "asT2ID", "modMail", "metadata", "getMetadata", "ModMailService", "getSubredditById", "id", "Subreddit", "asTID", "getSubredditInfoById", "_getSubredditInfoById", "getSubredditByName", "name", "getSubredditInfoByName", "_getSubredditInfoByName", "addSubredditRemovalReason", "subredditName", "options", "getSubredditRemovalReasons", "getCurrentSubredditName", "nameFromMetadata", "Header", "subredditId", "nameFromId", "_getSubredditNameById", "asT5ID", "getCurrentSubreddit", "currentSubreddit", "getPostById", "Post", "submitPost", "crosspost", "getUserById", "User", "getUserByUsername", "username", "getCurrentUsername", "getCurrentUsernameFromMetadata", "getCurrentUser", "getCurrentUserFromMetadata", "getAppUser", "getSnoovatarUrl", "getCommentById", "Comment", "getComments", "getCommentsByUser", "submitComment", "getControversialPosts", "getTopPosts", "getHotPosts", "getNewPosts", "getRisingPosts", "getPostsByUser", "getCommentsAndPostsByUser", "getModerationLog", "_getModerationLog", "getApprovedUsers", "approveUser", "removeUser", "getWikiContributors", "addWikiContributor", "removeWikiContributor", "getBannedUsers", "banUser", "unbanUser", "getBannedWikiContributors", "banWikiContributor", "unbanWikiContributor", "getModerators", "inviteModerator", "revokeModeratorInvite", "removeModerator", "setModeratorPermissions", "permissions", "getMutedUsers", "muteUser", "unmuteUser", "getModNotes", "ModNote", "deleteModNote", "addModNote", "req", "addRemovalNote", "sendPrivateMessage", "PrivateMessage", "sendPrivateMessageAsSubreddit", "approve", "isT1ID", "isT3ID", "remove", "isSpam", "getPostFlairTemplates", "FlairTemplate", "getUserFlairTemplates", "createPostFlairTemplate", "createUserFlairTemplate", "editFlairTemplate", "deleteFlairTemplate", "flairTemplateId", "setUserFlair", "Flair", "setUserFlairBatch", "flairs", "removeUserFlair", "setPostFlair", "removePostFlair", "postId", "asT3ID", "getWidgets", "Widget", "deleteWidget", "widgetId", "addWidget", "widgetData", "reorderWidgets", "orderByIds", "getWikiPage", "page", "WikiPage", "getWikiPages", "createWikiPage", "updateWikiPage", "getWikiPageRevisions", "revertWikiPage", "revisionId", "getWikiPageSettings", "updateWikiPageSettings", "addEditorToWikiPage", "removeEditorFromWikiPage", "getMessages", "markAllMessagesAsRead", "report", "thing", "Devvit", "getModQueue", "getReports", "getSpam", "getUnmoderated", "getEdited", "getVaultByAddress", "address", "getVaultByUserId", "userId", "getSubredditLeaderboard", "_getSubredditLeaderboard", "getSubredditStyles", "_getSubredditStyles", "subscribeToCurrentSubreddit", "unsubscribeFromCurrentSubreddit", "DISALLOWED_ELEMENTS", "DISALLOWED_STYLES", "DISALLOWED_ATTRIBUTES", "ensureXmlns", "svg", "sanitizeSvg", "disallowedElementsRegex", "disallowedStylesRegex", "disallowedAttributesRegex", "elementMatches", "styleMatches", "attributeMatches", "isInvalid", "x", "svg", "strings", "args", "str", "string", "index", "arg", "sanitizeSvg", "EffectType", "WebViewVisibility", "webViewMessageIsInternalAndClientScope", "message", "_hookId", "_url", "_onMessage", "_onUnmount", "_renderContext", "_emitFullscreenEffect", "WebViewHook", "params", "options", "__privateAdd", "message", "jsonString", "__privateGet", "EffectType", "e", "StringUtil", "url", "show", "__privateSet", "event", "WebViewVisibility", "webViewMessageIsInternalAndClientScope", "useWebView", "hook", "registerHook"]
|
|
7
7
|
}
|