···11+import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api'
22+33+import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types'
44+55+export function threadgateViewToAllowUISetting(
66+ threadgateView: AppBskyFeedDefs.ThreadgateView | undefined,
77+): ThreadgateAllowUISetting[] {
88+ const threadgate =
99+ threadgateView &&
1010+ AppBskyFeedThreadgate.isRecord(threadgateView.record) &&
1111+ AppBskyFeedThreadgate.validateRecord(threadgateView.record).success
1212+ ? threadgateView.record
1313+ : undefined
1414+ return threadgateRecordToAllowUISetting(threadgate)
1515+}
1616+1717+/**
1818+ * Converts a full {@link AppBskyFeedThreadgate.Record} to a list of
1919+ * {@link ThreadgateAllowUISetting}, for use by app UI.
2020+ */
2121+export function threadgateRecordToAllowUISetting(
2222+ threadgate: AppBskyFeedThreadgate.Record | undefined,
2323+): ThreadgateAllowUISetting[] {
2424+ /*
2525+ * If `threadgate` doesn't exist (default), or if `threadgate.allow === undefined`, it means
2626+ * anyone can reply.
2727+ *
2828+ * If `threadgate.allow === []` it means no one can reply, and we translate to UI code
2929+ * here. This was a historical choice, and we have no lexicon representation
3030+ * for 'replies disabled' other than an empty array.
3131+ */
3232+ if (!threadgate || threadgate.allow === undefined) {
3333+ return [{type: 'everybody'}]
3434+ }
3535+ if (threadgate.allow.length === 0) {
3636+ return [{type: 'nobody'}]
3737+ }
3838+3939+ const settings: ThreadgateAllowUISetting[] = threadgate.allow
4040+ .map(allow => {
4141+ let setting: ThreadgateAllowUISetting | undefined
4242+ if (allow.$type === 'app.bsky.feed.threadgate#mentionRule') {
4343+ setting = {type: 'mention'}
4444+ } else if (allow.$type === 'app.bsky.feed.threadgate#followingRule') {
4545+ setting = {type: 'following'}
4646+ } else if (allow.$type === 'app.bsky.feed.threadgate#listRule') {
4747+ setting = {type: 'list', list: allow.list}
4848+ }
4949+ return setting
5050+ })
5151+ .filter(n => !!n)
5252+ return settings
5353+}
5454+5555+/**
5656+ * Converts an array of {@link ThreadgateAllowUISetting} to the `allow` prop on
5757+ * {@link AppBskyFeedThreadgate.Record}.
5858+ *
5959+ * If the `allow` property on the record is undefined, we infer that to mean
6060+ * that everyone can reply. If it's an empty array, we infer that to mean that
6161+ * no one can reply.
6262+ */
6363+export function threadgateAllowUISettingToAllowRecordValue(
6464+ threadgate: ThreadgateAllowUISetting[],
6565+): AppBskyFeedThreadgate.Record['allow'] {
6666+ if (threadgate.find(v => v.type === 'everybody')) {
6767+ return undefined
6868+ }
6969+7070+ let allow: (
7171+ | AppBskyFeedThreadgate.MentionRule
7272+ | AppBskyFeedThreadgate.FollowingRule
7373+ | AppBskyFeedThreadgate.ListRule
7474+ )[] = []
7575+7676+ if (!threadgate.find(v => v.type === 'nobody')) {
7777+ for (const rule of threadgate) {
7878+ if (rule.type === 'mention') {
7979+ allow.push({$type: 'app.bsky.feed.threadgate#mentionRule'})
8080+ } else if (rule.type === 'following') {
8181+ allow.push({$type: 'app.bsky.feed.threadgate#followingRule'})
8282+ } else if (rule.type === 'list') {
8383+ allow.push({
8484+ $type: 'app.bsky.feed.threadgate#listRule',
8585+ list: rule.list,
8686+ })
8787+ }
8888+ }
8989+ }
9090+9191+ return allow
9292+}
9393+9494+/**
9595+ * Merges two {@link AppBskyFeedThreadgate.Record} objects, combining their
9696+ * `allow` and `hiddenReplies` arrays and de-deduplicating them.
9797+ *
9898+ * Note: `allow` can be undefined here, be sure you don't accidentally set it
9999+ * to an empty array. See other comments in this file.
100100+ */
101101+export function mergeThreadgateRecords(
102102+ prev: AppBskyFeedThreadgate.Record,
103103+ next: Partial<AppBskyFeedThreadgate.Record>,
104104+): AppBskyFeedThreadgate.Record {
105105+ // can be undefined if everyone can reply!
106106+ const allow: AppBskyFeedThreadgate.Record['allow'] | undefined =
107107+ prev.allow || next.allow
108108+ ? [...(prev.allow || []), ...(next.allow || [])].filter(
109109+ (v, i, a) => a.findIndex(t => t.$type === v.$type) === i,
110110+ )
111111+ : undefined
112112+ const hiddenReplies = Array.from(
113113+ new Set([...(prev.hiddenReplies || []), ...(next.hiddenReplies || [])]),
114114+ )
115115+116116+ return createThreadgateRecord({
117117+ post: prev.post,
118118+ allow, // can be undefined!
119119+ hiddenReplies,
120120+ })
121121+}
122122+123123+/**
124124+ * Create a new {@link AppBskyFeedThreadgate.Record} object with the given
125125+ * properties.
126126+ */
127127+export function createThreadgateRecord(
128128+ threadgate: Partial<AppBskyFeedThreadgate.Record>,
129129+): AppBskyFeedThreadgate.Record {
130130+ if (!threadgate.post) {
131131+ throw new Error('Cannot create a threadgate record without a post URI')
132132+ }
133133+134134+ return {
135135+ $type: 'app.bsky.feed.threadgate',
136136+ post: threadgate.post,
137137+ createdAt: new Date().toISOString(),
138138+ allow: threadgate.allow, // can be undefined!
139139+ hiddenReplies: threadgate.hiddenReplies || [],
140140+ }
141141+}