@meith/import 0.16.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/src/map.ts ADDED
@@ -0,0 +1,297 @@
1
+ import type {
2
+ MybbAttachment,
3
+ MybbAvatarRow,
4
+ MybbBan,
5
+ MybbForum,
6
+ MybbForumSubscription,
7
+ MybbPoll,
8
+ MybbPollVote,
9
+ MybbPost,
10
+ MybbPrivateMessage,
11
+ MybbRelationList,
12
+ MybbReputation,
13
+ MybbThread,
14
+ MybbThreadSubscription,
15
+ MybbUser,
16
+ MybbWarning,
17
+ } from './mybb-rows'
18
+ import type {
19
+ ImportedAttachment,
20
+ ImportedAvatar,
21
+ ImportedBan,
22
+ ImportedForum,
23
+ ImportedPoll,
24
+ ImportedPollVote,
25
+ ImportedPost,
26
+ ImportedPrivateMessage,
27
+ ImportedReputation,
28
+ ImportedSubscription,
29
+ ImportedThread,
30
+ ImportedUser,
31
+ ImportedUserRelation,
32
+ ImportedWarning,
33
+ } from './types'
34
+ import { fromUnixSeconds, visibilityOf } from './types'
35
+
36
+ export function mapUser(row: MybbUser): ImportedUser {
37
+ return {
38
+ legacyId: row.uid,
39
+ username: row.username,
40
+ email: row.email.trim().toLowerCase(),
41
+ legacyPasswordHash: `mybb$${row.salt}$${row.password}`,
42
+ registeredAt: fromUnixSeconds(row.regdate) ?? new Date(0),
43
+ lastVisitAt: fromUnixSeconds(row.lastvisit),
44
+ postCount: Math.max(0, row.postnum),
45
+ legacyGroupId: row.usergroup,
46
+ }
47
+ }
48
+
49
+ const FORUM_TYPES: Readonly<Record<string, ImportedForum['type']>> = {
50
+ f: 'forum',
51
+ c: 'category',
52
+ l: 'link',
53
+ }
54
+
55
+ export function mapForum(row: MybbForum): ImportedForum {
56
+ return {
57
+ legacyId: row.fid,
58
+ type: FORUM_TYPES[row.type] ?? 'forum',
59
+ title: row.name,
60
+ description: row.description.trim() === '' ? null : row.description,
61
+ legacyParentId: row.pid === 0 ? null : row.pid,
62
+ displayOrder: row.disporder,
63
+ linkUrl: row.type === 'l' && row.linkto.trim() !== '' ? row.linkto : null,
64
+ }
65
+ }
66
+
67
+ export function mapThread(row: MybbThread): ImportedThread {
68
+ const createdAt = fromUnixSeconds(row.dateline) ?? new Date(0)
69
+
70
+ return {
71
+ legacyId: row.tid,
72
+ legacyForumId: row.fid,
73
+ title: row.subject,
74
+ legacyAuthorId: row.uid,
75
+ authorUsername: row.username,
76
+ createdAt,
77
+ lastPostAt: fromUnixSeconds(row.lastpost) ?? createdAt,
78
+ replyCount: Math.max(0, row.replies),
79
+ viewCount: Math.max(0, row.views),
80
+ isSticky: row.sticky === 1,
81
+ isLocked: row.closed === '1',
82
+ visibility: visibilityOf(row.visible),
83
+ }
84
+ }
85
+
86
+ export function mapPost(row: MybbPost): ImportedPost {
87
+ return {
88
+ legacyId: row.pid,
89
+ legacyThreadId: row.tid,
90
+ legacyForumId: row.fid,
91
+ legacyAuthorId: row.uid,
92
+ authorUsername: row.username,
93
+ body: row.message,
94
+ createdAt: fromUnixSeconds(row.dateline) ?? new Date(0),
95
+ editedAt: row.edituid === 0 ? null : fromUnixSeconds(row.edittime),
96
+ visibility: visibilityOf(row.visible),
97
+ }
98
+ }
99
+
100
+ const AVATAR_SOURCES: Readonly<Record<string, ImportedAvatar['source']>> = {
101
+ upload: 'upload',
102
+ gallery: 'gallery',
103
+ remote: 'remote',
104
+ }
105
+
106
+ export function mapAvatar(row: MybbAvatarRow): ImportedAvatar | null {
107
+ const source = AVATAR_SOURCES[row.avatartype]
108
+ if (source === undefined) return null
109
+
110
+ const path = row.avatar.split('?')[0]?.replace(/^\.\//, '') ?? ''
111
+ if (path === '') return null
112
+
113
+ const [width, height] = row.avatardimensions.split('|').map((part) => Number(part))
114
+
115
+ return {
116
+ legacyUserId: row.uid,
117
+ source,
118
+ path,
119
+ width: Number.isFinite(width) && width! > 0 ? width! : null,
120
+ height: Number.isFinite(height) && height! > 0 ? height! : null,
121
+ }
122
+ }
123
+
124
+ export function mapAttachment(row: MybbAttachment): ImportedAttachment {
125
+ const path = row.attachname.replace(/^\.\//, '')
126
+ const thumbnail = row.thumbnail.trim()
127
+
128
+ return {
129
+ legacyId: row.aid,
130
+ legacyPostId: row.pid,
131
+ legacyUserId: row.uid,
132
+ filename: row.filename,
133
+ contentType: row.filetype.trim() === '' ? null : row.filetype.trim(),
134
+ sizeBytes: Math.max(0, row.filesize),
135
+ path,
136
+ thumbnailPath:
137
+ thumbnail === '' || thumbnail === 'SMALL' ? null : thumbnail.replace(/^\.\//, ''),
138
+ downloadCount: Math.max(0, row.downloads),
139
+ createdAt: fromUnixSeconds(row.dateuploaded) ?? new Date(0),
140
+ }
141
+ }
142
+
143
+ const OPTION_SEPARATOR = '||~|~||'
144
+
145
+ export function mapPoll(row: MybbPoll): ImportedPoll | null {
146
+ const labels = row.options.split(OPTION_SEPARATOR)
147
+ const counts = row.votes.split(OPTION_SEPARATOR)
148
+ const createdAt = fromUnixSeconds(row.dateline) ?? new Date(0)
149
+
150
+ const options = labels
151
+ .map((label, index) => ({
152
+ order: index + 1,
153
+ label: label.trim(),
154
+ voteCount: Math.max(0, Number(counts[index]) || 0),
155
+ }))
156
+ .filter((option) => option.label !== '')
157
+
158
+ if (options.length === 0) return null
159
+
160
+ return {
161
+ legacyId: row.pid,
162
+ legacyThreadId: row.tid,
163
+ question: row.question,
164
+ options,
165
+ closesAt:
166
+ row.timeout > 0 ? new Date(createdAt.getTime() + row.timeout * 24 * 60 * 60 * 1000) : null,
167
+ createdAt,
168
+ maxOptions: row.multiple > 0 ? 0 : 1,
169
+ allowRevote: false,
170
+ publicVotes: row.public > 0,
171
+ }
172
+ }
173
+
174
+ export function mapPollVote(row: MybbPollVote): ImportedPollVote | null {
175
+ if (row.voteoption <= 0) return null
176
+ return {
177
+ legacyPollId: row.pid,
178
+ legacyUserId: row.uid,
179
+ optionOrder: row.voteoption,
180
+ votedAt: fromUnixSeconds(row.dateline),
181
+ }
182
+ }
183
+
184
+ const PM_FOLDERS: Readonly<Record<number, 'inbox' | 'sent' | 'trash'>> = {
185
+ 1: 'inbox',
186
+ 2: 'sent',
187
+ 4: 'trash',
188
+ }
189
+
190
+ export function mapPrivateMessage(row: MybbPrivateMessage): ImportedPrivateMessage | null {
191
+ if (row.folder === 3) return null
192
+
193
+ return {
194
+ legacyId: row.pmid,
195
+ legacyAuthorId: row.fromid,
196
+ subject: row.subject,
197
+ body: row.message,
198
+ sentAt: fromUnixSeconds(row.dateline) ?? new Date(0),
199
+ copies: [
200
+ {
201
+ legacyOwnerId: row.uid,
202
+ folder: PM_FOLDERS[row.folder] ?? 'inbox',
203
+ readAt: row.status === 0 ? null : fromUnixSeconds(row.readtime),
204
+ },
205
+ ],
206
+ }
207
+ }
208
+
209
+ export function mapThreadSubscription(row: MybbThreadSubscription): ImportedSubscription {
210
+ return {
211
+ legacyId: row.sid,
212
+ legacyUserId: row.uid,
213
+ legacyTargetId: row.tid,
214
+ mode: row.notification === 0 ? 'none' : 'instant',
215
+ }
216
+ }
217
+
218
+ export function mapForumSubscription(row: MybbForumSubscription): ImportedSubscription {
219
+ return {
220
+ legacyId: row.uid,
221
+ legacyUserId: row.uid,
222
+ legacyTargetId: row.fid,
223
+ mode: 'instant',
224
+ }
225
+ }
226
+
227
+ export function mapReputation(row: MybbReputation): ImportedReputation {
228
+ return {
229
+ legacyId: row.rid,
230
+ legacyUserId: row.uid,
231
+ legacyGivenByUserId: row.adduid,
232
+ legacyPostId: row.pid === 0 ? null : row.pid,
233
+ points: row.reputation,
234
+ comment: row.comments,
235
+ createdAt: fromUnixSeconds(row.dateline) ?? new Date(0),
236
+ }
237
+ }
238
+
239
+ export function mapWarning(row: MybbWarning): ImportedWarning {
240
+ return {
241
+ legacyId: row.wid,
242
+ legacyUserId: row.uid,
243
+ legacyIssuedByUserId: row.issuedby,
244
+ legacyPostId: row.pid === 0 ? null : row.pid,
245
+ title: row.title,
246
+ points: Math.max(0, row.points),
247
+ notes: row.notes,
248
+ issuedAt: fromUnixSeconds(row.dateline) ?? new Date(0),
249
+ expiresAt: fromUnixSeconds(row.expires),
250
+ revokedAt: fromUnixSeconds(row.daterevoked),
251
+ revokeReason: row.revokereason.trim() === '' ? null : row.revokereason,
252
+ }
253
+ }
254
+
255
+ export function mapBan(row: MybbBan): ImportedBan {
256
+ return {
257
+ legacyId: row.uid,
258
+ legacyUserId: row.uid,
259
+ legacyBannedByUserId: row.admin,
260
+ reason: row.reason,
261
+ publicReason: null,
262
+ createdAt: fromUnixSeconds(row.dateline) ?? new Date(0),
263
+ expiresAt: fromUnixSeconds(row.lifted),
264
+ }
265
+ }
266
+
267
+ function idList(list: string): number[] {
268
+ return [
269
+ ...new Set(
270
+ list
271
+ .split(',')
272
+ .map((part) => Number(part.trim()))
273
+ .filter((id) => Number.isSafeInteger(id) && id > 0),
274
+ ),
275
+ ]
276
+ }
277
+
278
+ export function mapRelationList(row: MybbRelationList): ImportedUserRelation[] {
279
+ const buddies = idList(row.buddylist)
280
+ const ignoredSet = new Set(idList(row.ignorelist))
281
+ const buddySet = new Set(buddies)
282
+
283
+ return [
284
+ ...buddies.map((other) => ({
285
+ legacyUserId: row.uid,
286
+ legacyOtherUserId: other,
287
+ kind: 'buddy' as const,
288
+ })),
289
+ ...[...ignoredSet]
290
+ .filter((other) => !buddySet.has(other))
291
+ .map((other) => ({
292
+ legacyUserId: row.uid,
293
+ legacyOtherUserId: other,
294
+ kind: 'ignore' as const,
295
+ })),
296
+ ].filter((relation) => relation.legacyOtherUserId !== row.uid)
297
+ }
@@ -0,0 +1,64 @@
1
+ import type { ImportSink, WriteResult } from './run'
2
+
3
+ const byLegacyId = (row: { legacyId: number }): string => String(row.legacyId)
4
+ const byPair = (row: { legacyUserId: number; legacyTargetId: number }): string =>
5
+ `${row.legacyUserId}:${row.legacyTargetId}`
6
+
7
+ export class MemorySink implements ImportSink {
8
+ readonly stored = new Map<string, Map<string, unknown>>()
9
+ readonly calls: string[] = []
10
+
11
+ putUsers = (rows: readonly { legacyId: number }[]) => this.#put('users', rows, byLegacyId)
12
+ putForums = (rows: readonly { legacyId: number }[]) => this.#put('forums', rows, byLegacyId)
13
+ putThreads = (rows: readonly { legacyId: number }[]) => this.#put('threads', rows, byLegacyId)
14
+ putPosts = (rows: readonly { legacyId: number }[]) => this.#put('posts', rows, byLegacyId)
15
+ putAvatars = (rows: readonly { legacyUserId: number }[]) =>
16
+ this.#put('avatars', rows, (row) => String(row.legacyUserId))
17
+ putAttachments = (rows: readonly { legacyId: number }[]) =>
18
+ this.#put('attachments', rows, byLegacyId)
19
+ putPolls = (rows: readonly { legacyId: number }[]) => this.#put('polls', rows, byLegacyId)
20
+ putPollVotes = (
21
+ rows: readonly { legacyPollId: number; legacyUserId: number; optionOrder: number }[],
22
+ ) =>
23
+ this.#put(
24
+ 'pollVotes',
25
+ rows,
26
+ (row) => `${row.legacyPollId}:${row.legacyUserId}:${row.optionOrder}`,
27
+ )
28
+ putPrivateMessages = (rows: readonly { legacyId: number }[]) =>
29
+ this.#put('privateMessages', rows, byLegacyId)
30
+ putThreadSubscriptions = (rows: readonly { legacyUserId: number; legacyTargetId: number }[]) =>
31
+ this.#put('threadSubscriptions', rows, byPair)
32
+ putForumSubscriptions = (rows: readonly { legacyUserId: number; legacyTargetId: number }[]) =>
33
+ this.#put('forumSubscriptions', rows, byPair)
34
+ putReputation = (rows: readonly { legacyId: number }[]) =>
35
+ this.#put('reputation', rows, byLegacyId)
36
+ putWarnings = (rows: readonly { legacyId: number }[]) => this.#put('warnings', rows, byLegacyId)
37
+ putBans = (rows: readonly { legacyId: number }[]) => this.#put('bans', rows, byLegacyId)
38
+ putUserRelations = (rows: readonly { legacyUserId: number; legacyOtherUserId: number }[]) =>
39
+ this.#put('userRelations', rows, (row) => `${row.legacyUserId}:${row.legacyOtherUserId}`)
40
+
41
+ count(kind: string): number {
42
+ return this.stored.get(kind)?.size ?? 0
43
+ }
44
+
45
+ rows<T>(kind: string): readonly T[] {
46
+ return [...(this.stored.get(kind)?.values() ?? [])] as T[]
47
+ }
48
+
49
+ async #put<T>(kind: string, rows: readonly T[], keyOf: (row: T) => string): Promise<WriteResult> {
50
+ this.calls.push(`${kind}:${rows.length}`)
51
+ const table = this.stored.get(kind) ?? new Map<string, unknown>()
52
+ this.stored.set(kind, table)
53
+
54
+ let inserted = 0
55
+ let updated = 0
56
+ for (const row of rows) {
57
+ const key = keyOf(row)
58
+ if (table.has(key)) updated += 1
59
+ else inserted += 1
60
+ table.set(key, row)
61
+ }
62
+ return { inserted, updated, skipped: [] }
63
+ }
64
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ mapAttachment,
3
+ mapAvatar,
4
+ mapBan,
5
+ mapForum,
6
+ mapForumSubscription,
7
+ mapPoll,
8
+ mapPollVote,
9
+ mapPost,
10
+ mapPrivateMessage,
11
+ mapRelationList,
12
+ mapReputation,
13
+ mapThread,
14
+ mapThreadSubscription,
15
+ mapUser,
16
+ mapWarning,
17
+ } from './map'
18
+ import type { MybbTables } from './mybb-rows'
19
+ import { groupRows, type ImportSource, type Page, pageByGroup, pageById } from './source'
20
+ import type {
21
+ ImportedAttachment,
22
+ ImportedAvatar,
23
+ ImportedBan,
24
+ ImportedForum,
25
+ ImportedPoll,
26
+ ImportedPollVote,
27
+ ImportedPost,
28
+ ImportedPrivateMessage,
29
+ ImportedReputation,
30
+ ImportedSubscription,
31
+ ImportedThread,
32
+ ImportedUser,
33
+ ImportedUserRelation,
34
+ ImportedWarning,
35
+ } from './types'
36
+
37
+ function mapPage<Raw, Mapped>(page: Page<Raw>, map: (row: Raw) => Mapped | null): Page<Mapped> {
38
+ return {
39
+ rows: page.rows.map(map).filter((row): row is NonNullable<Mapped> => row !== null),
40
+ nextCursor: page.nextCursor,
41
+ }
42
+ }
43
+
44
+ export class FixtureMybbSource implements ImportSource {
45
+ readonly name = 'mybb'
46
+
47
+ constructor(private readonly data: MybbTables) {}
48
+
49
+ users(afterId: number, limit: number): Promise<Page<ImportedUser>> {
50
+ return Promise.resolve(
51
+ mapPage(
52
+ pageById(this.data.users ?? [], (row) => row.uid, afterId, limit),
53
+ mapUser,
54
+ ),
55
+ )
56
+ }
57
+
58
+ forums(afterId: number, limit: number): Promise<Page<ImportedForum>> {
59
+ return Promise.resolve(
60
+ mapPage(
61
+ pageById(this.data.forums ?? [], (row) => row.fid, afterId, limit),
62
+ mapForum,
63
+ ),
64
+ )
65
+ }
66
+
67
+ threads(afterId: number, limit: number): Promise<Page<ImportedThread>> {
68
+ return Promise.resolve(
69
+ mapPage(
70
+ pageById(this.data.threads ?? [], (row) => row.tid, afterId, limit),
71
+ mapThread,
72
+ ),
73
+ )
74
+ }
75
+
76
+ posts(afterId: number, limit: number): Promise<Page<ImportedPost>> {
77
+ return Promise.resolve(
78
+ mapPage(
79
+ pageById(this.data.posts ?? [], (row) => row.pid, afterId, limit),
80
+ mapPost,
81
+ ),
82
+ )
83
+ }
84
+
85
+ avatars(afterId: number, limit: number): Promise<Page<ImportedAvatar>> {
86
+ return Promise.resolve(
87
+ mapPage(
88
+ pageById(this.data.avatars ?? [], (row) => row.uid, afterId, limit),
89
+ mapAvatar,
90
+ ),
91
+ )
92
+ }
93
+
94
+ attachments(afterId: number, limit: number): Promise<Page<ImportedAttachment>> {
95
+ return Promise.resolve(
96
+ mapPage(
97
+ pageById(this.data.attachments ?? [], (row) => row.aid, afterId, limit),
98
+ mapAttachment,
99
+ ),
100
+ )
101
+ }
102
+
103
+ polls(afterId: number, limit: number): Promise<Page<ImportedPoll>> {
104
+ return Promise.resolve(
105
+ mapPage(
106
+ pageById(this.data.polls ?? [], (row) => row.pid, afterId, limit),
107
+ mapPoll,
108
+ ),
109
+ )
110
+ }
111
+
112
+ pollVotes(afterId: number, limit: number): Promise<Page<ImportedPollVote>> {
113
+ return Promise.resolve(
114
+ mapPage(
115
+ pageById(this.data.pollVotes ?? [], (row) => row.vid, afterId, limit),
116
+ mapPollVote,
117
+ ),
118
+ )
119
+ }
120
+
121
+ privateMessages(afterId: number, limit: number): Promise<Page<ImportedPrivateMessage>> {
122
+ return Promise.resolve(
123
+ mapPage(
124
+ pageById(this.data.privateMessages ?? [], (row) => row.pmid, afterId, limit),
125
+ mapPrivateMessage,
126
+ ),
127
+ )
128
+ }
129
+
130
+ threadSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
131
+ return Promise.resolve(
132
+ mapPage(
133
+ pageById(this.data.threadSubscriptions ?? [], (row) => row.sid, afterId, limit),
134
+ mapThreadSubscription,
135
+ ),
136
+ )
137
+ }
138
+
139
+ async forumSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
140
+ const { fetch, fetchGroup } = groupRows(this.data.forumSubscriptions ?? [], (row) => row.uid)
141
+ const page = await pageByGroup({
142
+ afterGroup: afterId,
143
+ limit,
144
+ groupOf: (row) => row.uid,
145
+ fetch,
146
+ fetchGroup,
147
+ })
148
+ return mapPage(page, mapForumSubscription)
149
+ }
150
+
151
+ reputation(afterId: number, limit: number): Promise<Page<ImportedReputation>> {
152
+ return Promise.resolve(
153
+ mapPage(
154
+ pageById(this.data.reputation ?? [], (row) => row.rid, afterId, limit),
155
+ mapReputation,
156
+ ),
157
+ )
158
+ }
159
+
160
+ warnings(afterId: number, limit: number): Promise<Page<ImportedWarning>> {
161
+ return Promise.resolve(
162
+ mapPage(
163
+ pageById(this.data.warnings ?? [], (row) => row.wid, afterId, limit),
164
+ mapWarning,
165
+ ),
166
+ )
167
+ }
168
+
169
+ bans(afterId: number, limit: number): Promise<Page<ImportedBan>> {
170
+ return Promise.resolve(
171
+ mapPage(
172
+ pageById(this.data.bans ?? [], (row) => row.uid, afterId, limit),
173
+ mapBan,
174
+ ),
175
+ )
176
+ }
177
+
178
+ userRelations(afterId: number, limit: number): Promise<Page<ImportedUserRelation>> {
179
+ const page = pageById(this.data.relationLists ?? [], (row) => row.uid, afterId, limit)
180
+ return Promise.resolve({
181
+ rows: page.rows.flatMap(mapRelationList),
182
+ nextCursor: page.nextCursor,
183
+ })
184
+ }
185
+ }
@@ -0,0 +1,172 @@
1
+ export interface MybbUser {
2
+ readonly uid: number
3
+ readonly username: string
4
+ readonly email: string
5
+ readonly password: string
6
+ readonly salt: string
7
+ readonly usergroup: number
8
+ readonly regdate: number
9
+ readonly lastvisit: number
10
+ readonly postnum: number
11
+ }
12
+
13
+ export interface MybbForum {
14
+ readonly fid: number
15
+ readonly name: string
16
+ readonly description: string
17
+ readonly type: string
18
+ readonly pid: number
19
+ readonly disporder: number
20
+ readonly linkto: string
21
+ readonly threads: number
22
+ readonly posts: number
23
+ }
24
+
25
+ export interface MybbThread {
26
+ readonly tid: number
27
+ readonly fid: number
28
+ readonly subject: string
29
+ readonly uid: number
30
+ readonly username: string
31
+ readonly dateline: number
32
+ readonly lastpost: number
33
+ readonly replies: number
34
+ readonly views: number
35
+ readonly sticky: number
36
+ readonly closed: string
37
+ readonly visible: number
38
+ }
39
+
40
+ export interface MybbPost {
41
+ readonly pid: number
42
+ readonly tid: number
43
+ readonly fid: number
44
+ readonly uid: number
45
+ readonly username: string
46
+ readonly subject: string
47
+ readonly message: string
48
+ readonly dateline: number
49
+ readonly edituid: number
50
+ readonly edittime: number
51
+ readonly visible: number
52
+ }
53
+
54
+ export interface MybbAvatarRow {
55
+ readonly uid: number
56
+ readonly avatar: string
57
+ readonly avatartype: string
58
+ readonly avatardimensions: string
59
+ }
60
+
61
+ export interface MybbAttachment {
62
+ readonly aid: number
63
+ readonly pid: number
64
+ readonly uid: number
65
+ readonly filename: string
66
+ readonly filetype: string
67
+ readonly filesize: number
68
+ readonly attachname: string
69
+ readonly thumbnail: string
70
+ readonly downloads: number
71
+ readonly dateuploaded: number
72
+ }
73
+
74
+ export interface MybbPoll {
75
+ readonly pid: number
76
+ readonly tid: number
77
+ readonly question: string
78
+ readonly dateline: number
79
+ readonly options: string
80
+ readonly votes: string
81
+ readonly timeout: number
82
+ readonly multiple: number
83
+ readonly public: number
84
+ }
85
+
86
+ export interface MybbPollVote {
87
+ readonly vid: number
88
+ readonly pid: number
89
+ readonly uid: number
90
+ readonly voteoption: number
91
+ readonly dateline: number
92
+ }
93
+
94
+ export interface MybbPrivateMessage {
95
+ readonly pmid: number
96
+ readonly uid: number
97
+ readonly fromid: number
98
+ readonly subject: string
99
+ readonly message: string
100
+ readonly dateline: number
101
+ readonly folder: number
102
+ readonly status: number
103
+ readonly readtime: number
104
+ }
105
+
106
+ export interface MybbThreadSubscription {
107
+ readonly sid: number
108
+ readonly uid: number
109
+ readonly tid: number
110
+ readonly notification: number
111
+ }
112
+
113
+ export interface MybbForumSubscription {
114
+ readonly fid: number
115
+ readonly uid: number
116
+ }
117
+
118
+ export interface MybbReputation {
119
+ readonly rid: number
120
+ readonly uid: number
121
+ readonly adduid: number
122
+ readonly pid: number
123
+ readonly reputation: number
124
+ readonly dateline: number
125
+ readonly comments: string
126
+ }
127
+
128
+ export interface MybbWarning {
129
+ readonly wid: number
130
+ readonly uid: number
131
+ readonly pid: number
132
+ readonly title: string
133
+ readonly points: number
134
+ readonly dateline: number
135
+ readonly issuedby: number
136
+ readonly expires: number
137
+ readonly daterevoked: number
138
+ readonly revokereason: string
139
+ readonly notes: string
140
+ }
141
+
142
+ export interface MybbBan {
143
+ readonly uid: number
144
+ readonly admin: number
145
+ readonly dateline: number
146
+ readonly lifted: number
147
+ readonly reason: string
148
+ }
149
+
150
+ export interface MybbRelationList {
151
+ readonly uid: number
152
+ readonly buddylist: string
153
+ readonly ignorelist: string
154
+ }
155
+
156
+ export interface MybbTables {
157
+ readonly users?: readonly MybbUser[]
158
+ readonly forums?: readonly MybbForum[]
159
+ readonly threads?: readonly MybbThread[]
160
+ readonly posts?: readonly MybbPost[]
161
+ readonly avatars?: readonly MybbAvatarRow[]
162
+ readonly attachments?: readonly MybbAttachment[]
163
+ readonly polls?: readonly MybbPoll[]
164
+ readonly pollVotes?: readonly MybbPollVote[]
165
+ readonly privateMessages?: readonly MybbPrivateMessage[]
166
+ readonly threadSubscriptions?: readonly MybbThreadSubscription[]
167
+ readonly forumSubscriptions?: readonly MybbForumSubscription[]
168
+ readonly reputation?: readonly MybbReputation[]
169
+ readonly warnings?: readonly MybbWarning[]
170
+ readonly bans?: readonly MybbBan[]
171
+ readonly relationLists?: readonly MybbRelationList[]
172
+ }