Bluesky app fork with some witchin' additions 馃挮
1import {type $Typed, ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
2
3import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
4import {logger} from '#/logger'
5import * as bsky from '#/types/bsky'
6
7export function canBeMessaged(profile: bsky.profile.AnyProfileView) {
8 switch (profile.associated?.chat?.allowIncoming) {
9 case 'none':
10 return false
11 case 'all':
12 return true
13 // if unset, treat as following
14 case 'following':
15 case undefined:
16 return Boolean(profile.viewer?.followedBy)
17 // any other values are invalid according to the lexicon, so
18 // let's treat as false to be safe
19 default:
20 return false
21 }
22}
23
24export function localDateString(date: Date) {
25 // can't use toISOString because it should be in local time
26 const mm = date.getMonth()
27 const dd = date.getDate()
28 const yyyy = date.getFullYear()
29 // not padding with 0s because it's not necessary, it's just used for comparison
30 return `${yyyy}-${mm}-${dd}`
31}
32
33export function hasAlreadyReacted(
34 message: ChatBskyConvoDefs.MessageView,
35 myDid: string | undefined,
36 emoji: string,
37): boolean {
38 if (!message.reactions) {
39 return false
40 }
41 return !!message.reactions.find(
42 reaction => reaction.value === emoji && reaction.sender.did === myDid,
43 )
44}
45
46export function hasReachedReactionLimit(
47 message: ChatBskyConvoDefs.MessageView,
48 myDid: string | undefined,
49): boolean {
50 if (!message.reactions) {
51 return false
52 }
53 const myReactions = message.reactions.filter(
54 reaction => reaction.sender.did === myDid,
55 )
56 return myReactions.length >= EMOJI_REACTION_LIMIT
57}
58
59export type GroupConvoMember = ChatBskyActorDefs.ProfileViewBasic & {
60 // can be missing if account deleted
61 kind?: $Typed<ChatBskyActorDefs.GroupConvoMember>
62}
63
64export type DirectConvoMember = ChatBskyActorDefs.ProfileViewBasic & {
65 kind: $Typed<ChatBskyActorDefs.DirectConvoMember>
66}
67
68export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & (
69 | {
70 kind: 'group'
71 details: ChatBskyConvoDefs.GroupConvo
72 primaryMember: GroupConvoMember // the owner
73 members: Array<GroupConvoMember>
74 }
75 | {
76 kind: 'direct'
77 details: ChatBskyConvoDefs.DirectConvo
78 primaryMember: DirectConvoMember // the other user
79 members: Array<DirectConvoMember>
80 }
81)
82
83/**
84 * Converts a raw convoView into something easier to use (i.e. extracts chat owner)
85 * and enforces the correct type for convo members.
86 */
87export function parseConvoView(
88 convoView: ChatBskyConvoDefs.ConvoView,
89 ownDid: string | undefined,
90): ConvoWithDetails | null {
91 if (
92 bsky.dangerousIsType<ChatBskyConvoDefs.GroupConvo>(
93 convoView.kind,
94 ChatBskyConvoDefs.isGroupConvo,
95 )
96 ) {
97 let owner: GroupConvoMember | undefined = undefined
98
99 for (const member of convoView.members) {
100 if (
101 bsky.dangerousIsType<ChatBskyActorDefs.GroupConvoMember>(
102 member.kind,
103 ChatBskyActorDefs.isGroupConvoMember,
104 )
105 ) {
106 if (member.kind.role === 'owner') {
107 // have to do a type assertion here
108 // this works: {...member, kind: member.kind}
109 // however that's creating a new object for no good reason
110 owner = member as GroupConvoMember
111 }
112 } else {
113 logger.warn(
114 'Expected a GroupConvoMember, got an unknown kind of member',
115 )
116 return null
117 }
118 }
119
120 if (!owner) {
121 logger.warn('No owner found in group convo')
122 return null
123 }
124
125 return {
126 view: convoView,
127 kind: 'group',
128 details: convoView.kind,
129 primaryMember: owner,
130 members: convoView.members as Array<GroupConvoMember>,
131 }
132 } else if (
133 bsky.dangerousIsType<ChatBskyConvoDefs.DirectConvo>(
134 convoView.kind,
135 ChatBskyConvoDefs.isDirectConvo,
136 )
137 ) {
138 const otherUser = convoView.members.find(m => m.did !== ownDid)
139
140 if (!otherUser) {
141 logger.warn('No other user found in direct convo')
142 return null
143 }
144
145 return {
146 view: convoView,
147 kind: 'direct',
148 details: convoView.kind,
149 primaryMember: otherUser as DirectConvoMember,
150 members: convoView.members as Array<DirectConvoMember>,
151 }
152 } else {
153 logger.warn('Unknown convo kind: ' + JSON.stringify(convoView.kind))
154 return null
155 }
156}