@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/LICENSE.md +165 -0
- package/package.json +24 -0
- package/src/index.ts +130 -0
- package/src/legacy-urls.ts +131 -0
- package/src/map.ts +297 -0
- package/src/memory-sink.fixture.ts +64 -0
- package/src/mybb-fixture.ts +185 -0
- package/src/mybb-rows.ts +172 -0
- package/src/mysql-source.ts +391 -0
- package/src/phpbb-fixture.ts +191 -0
- package/src/phpbb-map.ts +297 -0
- package/src/phpbb-mysql-source.ts +355 -0
- package/src/phpbb-rows.ts +160 -0
- package/src/phpbb-text.ts +44 -0
- package/src/run.ts +261 -0
- package/src/source.ts +98 -0
- package/src/types.ts +176 -0
package/src/phpbb-map.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PhpbbAttachment,
|
|
3
|
+
PhpbbAvatarRow,
|
|
4
|
+
PhpbbBan,
|
|
5
|
+
PhpbbForum,
|
|
6
|
+
PhpbbPollOption,
|
|
7
|
+
PhpbbPollTopic,
|
|
8
|
+
PhpbbPollVote,
|
|
9
|
+
PhpbbPost,
|
|
10
|
+
PhpbbPrivateMessage,
|
|
11
|
+
PhpbbPrivateMessageCopy,
|
|
12
|
+
PhpbbTopic,
|
|
13
|
+
PhpbbUser,
|
|
14
|
+
PhpbbWarning,
|
|
15
|
+
PhpbbWatch,
|
|
16
|
+
PhpbbZebra,
|
|
17
|
+
} from './phpbb-rows'
|
|
18
|
+
import { decodeHtmlEntities, decodePhpbbText } from './phpbb-text'
|
|
19
|
+
import type {
|
|
20
|
+
ImportedAttachment,
|
|
21
|
+
ImportedAvatar,
|
|
22
|
+
ImportedBan,
|
|
23
|
+
ImportedForum,
|
|
24
|
+
ImportedPoll,
|
|
25
|
+
ImportedPollVote,
|
|
26
|
+
ImportedPost,
|
|
27
|
+
ImportedPrivateMessage,
|
|
28
|
+
ImportedReputation,
|
|
29
|
+
ImportedSubscription,
|
|
30
|
+
ImportedThread,
|
|
31
|
+
ImportedUser,
|
|
32
|
+
ImportedUserRelation,
|
|
33
|
+
ImportedWarning,
|
|
34
|
+
Visibility,
|
|
35
|
+
} from './types'
|
|
36
|
+
import { fromUnixSeconds } from './types'
|
|
37
|
+
|
|
38
|
+
const BOT_USER_TYPE = 2
|
|
39
|
+
|
|
40
|
+
export function mapPhpbbUser(row: PhpbbUser): ImportedUser | null {
|
|
41
|
+
if (row.user_type === BOT_USER_TYPE) return null
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
legacyId: row.user_id,
|
|
45
|
+
username: decodeHtmlEntities(row.username),
|
|
46
|
+
email: row.user_email.trim().toLowerCase(),
|
|
47
|
+
legacyPasswordHash: `phpbb$${row.user_password}`,
|
|
48
|
+
registeredAt: fromUnixSeconds(row.user_regdate) ?? new Date(0),
|
|
49
|
+
lastVisitAt: fromUnixSeconds(row.user_lastvisit),
|
|
50
|
+
postCount: Math.max(0, row.user_posts),
|
|
51
|
+
legacyGroupId: row.group_id,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const FORUM_TYPES: Readonly<Record<number, ImportedForum['type']>> = {
|
|
56
|
+
0: 'category',
|
|
57
|
+
1: 'forum',
|
|
58
|
+
2: 'link',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function mapPhpbbForum(row: PhpbbForum): ImportedForum {
|
|
62
|
+
const description = decodeHtmlEntities(row.forum_desc).trim()
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
legacyId: row.forum_id,
|
|
66
|
+
type: FORUM_TYPES[row.forum_type] ?? 'forum',
|
|
67
|
+
title: decodeHtmlEntities(row.forum_name),
|
|
68
|
+
description: description === '' ? null : description,
|
|
69
|
+
legacyParentId: row.parent_id === 0 ? null : row.parent_id,
|
|
70
|
+
displayOrder: row.left_id,
|
|
71
|
+
linkUrl: row.forum_type === 2 && row.forum_link.trim() !== '' ? row.forum_link : null,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function phpbbVisibility(visibility: number): Visibility {
|
|
76
|
+
if (visibility === 1) return 'visible'
|
|
77
|
+
if (visibility === 2) return 'deleted'
|
|
78
|
+
return 'unapproved'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function mapPhpbbTopic(row: PhpbbTopic): ImportedThread | null {
|
|
82
|
+
if (row.topic_moved_id !== 0) return null
|
|
83
|
+
|
|
84
|
+
const createdAt = fromUnixSeconds(row.topic_time) ?? new Date(0)
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
legacyId: row.topic_id,
|
|
88
|
+
legacyForumId: row.forum_id,
|
|
89
|
+
title: decodeHtmlEntities(row.topic_title),
|
|
90
|
+
legacyAuthorId: row.topic_poster,
|
|
91
|
+
authorUsername: decodeHtmlEntities(row.topic_first_poster_name),
|
|
92
|
+
createdAt,
|
|
93
|
+
lastPostAt: fromUnixSeconds(row.topic_last_post_time) ?? createdAt,
|
|
94
|
+
replyCount: Math.max(0, row.topic_posts_approved - 1),
|
|
95
|
+
viewCount: Math.max(0, row.topic_views),
|
|
96
|
+
isSticky: row.topic_type > 0,
|
|
97
|
+
isLocked: row.topic_status === 1,
|
|
98
|
+
visibility: phpbbVisibility(row.topic_visibility),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function mapPhpbbPost(row: PhpbbPost): ImportedPost {
|
|
103
|
+
return {
|
|
104
|
+
legacyId: row.post_id,
|
|
105
|
+
legacyThreadId: row.topic_id,
|
|
106
|
+
legacyForumId: row.forum_id,
|
|
107
|
+
legacyAuthorId: row.poster_id,
|
|
108
|
+
authorUsername: decodeHtmlEntities(row.post_username),
|
|
109
|
+
body: decodePhpbbText(row.post_text, row.bbcode_uid),
|
|
110
|
+
createdAt: fromUnixSeconds(row.post_time) ?? new Date(0),
|
|
111
|
+
editedAt: fromUnixSeconds(row.post_edit_time),
|
|
112
|
+
visibility: phpbbVisibility(row.post_visibility),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const AVATAR_SOURCES: Readonly<Record<string, ImportedAvatar['source']>> = {
|
|
117
|
+
'avatar.driver.upload': 'upload',
|
|
118
|
+
'avatar.driver.remote': 'remote',
|
|
119
|
+
'avatar.driver.gallery': 'gallery',
|
|
120
|
+
'1': 'upload',
|
|
121
|
+
'2': 'remote',
|
|
122
|
+
'3': 'gallery',
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function mapPhpbbAvatar(row: PhpbbAvatarRow): ImportedAvatar | null {
|
|
126
|
+
const source = AVATAR_SOURCES[row.user_avatar_type.trim()]
|
|
127
|
+
if (source === undefined) return null
|
|
128
|
+
|
|
129
|
+
const value = row.user_avatar.split('?')[0]?.trim() ?? ''
|
|
130
|
+
if (value === '') return null
|
|
131
|
+
|
|
132
|
+
const path =
|
|
133
|
+
source === 'upload'
|
|
134
|
+
? `images/avatars/upload/*_${row.user_id}${extensionOf(value)}`
|
|
135
|
+
: source === 'gallery'
|
|
136
|
+
? `images/avatars/gallery/${value}`
|
|
137
|
+
: value
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
legacyUserId: row.user_id,
|
|
141
|
+
source,
|
|
142
|
+
path,
|
|
143
|
+
width: row.user_avatar_width > 0 ? row.user_avatar_width : null,
|
|
144
|
+
height: row.user_avatar_height > 0 ? row.user_avatar_height : null,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function extensionOf(filename: string): string {
|
|
149
|
+
const dot = filename.lastIndexOf('.')
|
|
150
|
+
return dot === -1 ? '' : filename.slice(dot).toLowerCase()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function mapPhpbbAttachment(row: PhpbbAttachment): ImportedAttachment {
|
|
154
|
+
const mimetype = row.mimetype.trim()
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
legacyId: row.attach_id,
|
|
158
|
+
legacyPostId: row.post_msg_id,
|
|
159
|
+
legacyUserId: row.poster_id,
|
|
160
|
+
filename: decodeHtmlEntities(row.real_filename),
|
|
161
|
+
contentType: mimetype === '' ? null : mimetype,
|
|
162
|
+
sizeBytes: Math.max(0, row.filesize),
|
|
163
|
+
path: `files/${row.physical_filename}`,
|
|
164
|
+
thumbnailPath: row.thumbnail === 1 ? `files/thumb_${row.physical_filename}` : null,
|
|
165
|
+
downloadCount: Math.max(0, row.download_count),
|
|
166
|
+
createdAt: fromUnixSeconds(row.filetime) ?? new Date(0),
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function mapPhpbbPoll(
|
|
171
|
+
topic: PhpbbPollTopic,
|
|
172
|
+
options: readonly PhpbbPollOption[],
|
|
173
|
+
): ImportedPoll | null {
|
|
174
|
+
const createdAt = fromUnixSeconds(topic.poll_start) ?? new Date(0)
|
|
175
|
+
|
|
176
|
+
const mapped = options
|
|
177
|
+
.filter((option) => option.topic_id === topic.topic_id)
|
|
178
|
+
.sort((a, b) => a.poll_option_id - b.poll_option_id)
|
|
179
|
+
.map((option) => ({
|
|
180
|
+
order: option.poll_option_id,
|
|
181
|
+
label: decodeHtmlEntities(option.poll_option_text).trim(),
|
|
182
|
+
voteCount: Math.max(0, option.poll_option_total),
|
|
183
|
+
}))
|
|
184
|
+
.filter((option) => option.label !== '')
|
|
185
|
+
|
|
186
|
+
if (mapped.length === 0) return null
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
legacyId: topic.topic_id,
|
|
190
|
+
legacyThreadId: topic.topic_id,
|
|
191
|
+
question: decodeHtmlEntities(topic.poll_title),
|
|
192
|
+
options: mapped,
|
|
193
|
+
closesAt:
|
|
194
|
+
topic.poll_length > 0 ? new Date((topic.poll_start + topic.poll_length) * 1000) : null,
|
|
195
|
+
createdAt,
|
|
196
|
+
maxOptions: Math.min(Math.max(1, topic.poll_max_options), mapped.length),
|
|
197
|
+
allowRevote: topic.poll_vote_change > 0,
|
|
198
|
+
publicVotes: false,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function mapPhpbbPollVote(row: PhpbbPollVote): ImportedPollVote {
|
|
203
|
+
return {
|
|
204
|
+
legacyPollId: row.topic_id,
|
|
205
|
+
legacyUserId: row.vote_user_id,
|
|
206
|
+
optionOrder: row.poll_option_id,
|
|
207
|
+
votedAt: null,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function mapPhpbbPrivateMessage(
|
|
212
|
+
message: PhpbbPrivateMessage,
|
|
213
|
+
copies: readonly PhpbbPrivateMessageCopy[],
|
|
214
|
+
): ImportedPrivateMessage | null {
|
|
215
|
+
const mapped = copies
|
|
216
|
+
.filter((copy) => copy.msg_id === message.msg_id)
|
|
217
|
+
.map((copy) => ({
|
|
218
|
+
legacyOwnerId: copy.user_id,
|
|
219
|
+
folder:
|
|
220
|
+
copy.pm_deleted === 1
|
|
221
|
+
? ('trash' as const)
|
|
222
|
+
: copy.user_id === message.author_id && copy.folder_id < 0
|
|
223
|
+
? ('sent' as const)
|
|
224
|
+
: ('inbox' as const),
|
|
225
|
+
readAt: copy.pm_unread === 1 ? null : (fromUnixSeconds(message.message_time) ?? null),
|
|
226
|
+
}))
|
|
227
|
+
|
|
228
|
+
if (mapped.length === 0) return null
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
legacyId: message.msg_id,
|
|
232
|
+
legacyAuthorId: message.author_id,
|
|
233
|
+
subject: decodeHtmlEntities(message.message_subject),
|
|
234
|
+
body: decodePhpbbText(message.message_text, message.bbcode_uid),
|
|
235
|
+
sentAt: fromUnixSeconds(message.message_time) ?? new Date(0),
|
|
236
|
+
copies: mapped,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function mapPhpbbWatch(row: PhpbbWatch): ImportedSubscription {
|
|
241
|
+
return {
|
|
242
|
+
legacyId: row.user_id,
|
|
243
|
+
legacyUserId: row.user_id,
|
|
244
|
+
legacyTargetId: row.target_id,
|
|
245
|
+
mode: 'instant',
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function mapPhpbbWarning(row: PhpbbWarning): ImportedWarning {
|
|
250
|
+
return {
|
|
251
|
+
legacyId: row.warning_id,
|
|
252
|
+
legacyUserId: row.user_id,
|
|
253
|
+
legacyIssuedByUserId: 0,
|
|
254
|
+
legacyPostId: row.post_id === 0 ? null : row.post_id,
|
|
255
|
+
title: 'Warning',
|
|
256
|
+
points: 1,
|
|
257
|
+
notes: '',
|
|
258
|
+
issuedAt: fromUnixSeconds(row.warning_time) ?? new Date(0),
|
|
259
|
+
expiresAt: null,
|
|
260
|
+
revokedAt: null,
|
|
261
|
+
revokeReason: null,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function mapPhpbbBan(row: PhpbbBan): ImportedBan | null {
|
|
266
|
+
if (row.ban_userid === 0 || row.ban_exclude === 1) return null
|
|
267
|
+
|
|
268
|
+
const publicReason = decodeHtmlEntities(row.ban_give_reason).trim()
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
legacyId: row.ban_id,
|
|
272
|
+
legacyUserId: row.ban_userid,
|
|
273
|
+
legacyBannedByUserId: 0,
|
|
274
|
+
reason: decodeHtmlEntities(row.ban_reason),
|
|
275
|
+
publicReason: publicReason === '' ? null : publicReason,
|
|
276
|
+
createdAt: fromUnixSeconds(row.ban_start) ?? new Date(0),
|
|
277
|
+
expiresAt: fromUnixSeconds(row.ban_end),
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function mapPhpbbZebra(row: PhpbbZebra): ImportedUserRelation | null {
|
|
282
|
+
if (row.user_id === row.zebra_id) return null
|
|
283
|
+
if (row.friend === 1) {
|
|
284
|
+
return { legacyUserId: row.user_id, legacyOtherUserId: row.zebra_id, kind: 'buddy' }
|
|
285
|
+
}
|
|
286
|
+
if (row.foe === 1) {
|
|
287
|
+
return { legacyUserId: row.user_id, legacyOtherUserId: row.zebra_id, kind: 'ignore' }
|
|
288
|
+
}
|
|
289
|
+
return null
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function noPhpbbReputation(): Promise<{
|
|
293
|
+
rows: readonly ImportedReputation[]
|
|
294
|
+
nextCursor: null
|
|
295
|
+
}> {
|
|
296
|
+
return Promise.resolve({ rows: [], nextCursor: null })
|
|
297
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertSafePrefix,
|
|
3
|
+
connectMysql,
|
|
4
|
+
keysetPage,
|
|
5
|
+
type MysqlSourceOptions,
|
|
6
|
+
mapPage,
|
|
7
|
+
type Queryable,
|
|
8
|
+
selectRows,
|
|
9
|
+
} from './mysql-source'
|
|
10
|
+
import {
|
|
11
|
+
mapPhpbbAttachment,
|
|
12
|
+
mapPhpbbAvatar,
|
|
13
|
+
mapPhpbbBan,
|
|
14
|
+
mapPhpbbForum,
|
|
15
|
+
mapPhpbbPoll,
|
|
16
|
+
mapPhpbbPollVote,
|
|
17
|
+
mapPhpbbPost,
|
|
18
|
+
mapPhpbbPrivateMessage,
|
|
19
|
+
mapPhpbbTopic,
|
|
20
|
+
mapPhpbbUser,
|
|
21
|
+
mapPhpbbWarning,
|
|
22
|
+
mapPhpbbWatch,
|
|
23
|
+
mapPhpbbZebra,
|
|
24
|
+
noPhpbbReputation,
|
|
25
|
+
} from './phpbb-map'
|
|
26
|
+
import type {
|
|
27
|
+
PhpbbAttachment,
|
|
28
|
+
PhpbbAvatarRow,
|
|
29
|
+
PhpbbBan,
|
|
30
|
+
PhpbbForum,
|
|
31
|
+
PhpbbPollOption,
|
|
32
|
+
PhpbbPollTopic,
|
|
33
|
+
PhpbbPollVote,
|
|
34
|
+
PhpbbPost,
|
|
35
|
+
PhpbbPrivateMessage,
|
|
36
|
+
PhpbbPrivateMessageCopy,
|
|
37
|
+
PhpbbTopic,
|
|
38
|
+
PhpbbUser,
|
|
39
|
+
PhpbbWarning,
|
|
40
|
+
PhpbbWatch,
|
|
41
|
+
PhpbbZebra,
|
|
42
|
+
} from './phpbb-rows'
|
|
43
|
+
import { type ImportSource, type Page, pageByGroup } from './source'
|
|
44
|
+
import type {
|
|
45
|
+
ImportedAttachment,
|
|
46
|
+
ImportedAvatar,
|
|
47
|
+
ImportedBan,
|
|
48
|
+
ImportedForum,
|
|
49
|
+
ImportedPoll,
|
|
50
|
+
ImportedPollVote,
|
|
51
|
+
ImportedPost,
|
|
52
|
+
ImportedPrivateMessage,
|
|
53
|
+
ImportedReputation,
|
|
54
|
+
ImportedSubscription,
|
|
55
|
+
ImportedThread,
|
|
56
|
+
ImportedUser,
|
|
57
|
+
ImportedUserRelation,
|
|
58
|
+
ImportedWarning,
|
|
59
|
+
} from './types'
|
|
60
|
+
|
|
61
|
+
export class MysqlPhpbbSource implements ImportSource {
|
|
62
|
+
readonly name = 'phpbb'
|
|
63
|
+
|
|
64
|
+
private constructor(
|
|
65
|
+
private readonly connection: Queryable,
|
|
66
|
+
private readonly prefix: string,
|
|
67
|
+
) {}
|
|
68
|
+
|
|
69
|
+
static async connect(options: MysqlSourceOptions): Promise<MysqlPhpbbSource> {
|
|
70
|
+
const prefix = options.tablePrefix ?? 'phpbb_'
|
|
71
|
+
assertSafePrefix(prefix)
|
|
72
|
+
return new MysqlPhpbbSource(await connectMysql(options), prefix)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async close(): Promise<void> {
|
|
76
|
+
await this.connection.end()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async users(afterId: number, limit: number): Promise<Page<ImportedUser>> {
|
|
80
|
+
return mapPage(
|
|
81
|
+
await keysetPage<PhpbbUser>(
|
|
82
|
+
this.connection,
|
|
83
|
+
`select user_id, user_type, username, user_email, user_password,
|
|
84
|
+
user_regdate, user_lastvisit, user_posts, group_id
|
|
85
|
+
from \`${this.prefix}users\``,
|
|
86
|
+
'user_id',
|
|
87
|
+
(row) => row.user_id,
|
|
88
|
+
afterId,
|
|
89
|
+
limit,
|
|
90
|
+
),
|
|
91
|
+
mapPhpbbUser,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async forums(afterId: number, limit: number): Promise<Page<ImportedForum>> {
|
|
96
|
+
return mapPage(
|
|
97
|
+
await keysetPage<PhpbbForum>(
|
|
98
|
+
this.connection,
|
|
99
|
+
`select forum_id, parent_id, forum_name, forum_desc, forum_type, forum_link, left_id
|
|
100
|
+
from \`${this.prefix}forums\``,
|
|
101
|
+
'forum_id',
|
|
102
|
+
(row) => row.forum_id,
|
|
103
|
+
afterId,
|
|
104
|
+
limit,
|
|
105
|
+
),
|
|
106
|
+
mapPhpbbForum,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async threads(afterId: number, limit: number): Promise<Page<ImportedThread>> {
|
|
111
|
+
return mapPage(
|
|
112
|
+
await keysetPage<PhpbbTopic>(
|
|
113
|
+
this.connection,
|
|
114
|
+
`select topic_id, forum_id, topic_title, topic_poster, topic_first_poster_name,
|
|
115
|
+
topic_time, topic_last_post_time, topic_views, topic_posts_approved,
|
|
116
|
+
topic_status, topic_type, topic_visibility, topic_moved_id
|
|
117
|
+
from \`${this.prefix}topics\``,
|
|
118
|
+
'topic_id',
|
|
119
|
+
(row) => row.topic_id,
|
|
120
|
+
afterId,
|
|
121
|
+
limit,
|
|
122
|
+
),
|
|
123
|
+
mapPhpbbTopic,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async posts(afterId: number, limit: number): Promise<Page<ImportedPost>> {
|
|
128
|
+
return mapPage(
|
|
129
|
+
await keysetPage<PhpbbPost>(
|
|
130
|
+
this.connection,
|
|
131
|
+
`select post_id, topic_id, forum_id, poster_id, post_username, post_subject,
|
|
132
|
+
post_text, bbcode_uid, post_time, post_edit_time, post_visibility
|
|
133
|
+
from \`${this.prefix}posts\``,
|
|
134
|
+
'post_id',
|
|
135
|
+
(row) => row.post_id,
|
|
136
|
+
afterId,
|
|
137
|
+
limit,
|
|
138
|
+
),
|
|
139
|
+
mapPhpbbPost,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async avatars(afterId: number, limit: number): Promise<Page<ImportedAvatar>> {
|
|
144
|
+
const rows = await selectRows<PhpbbAvatarRow>(
|
|
145
|
+
this.connection,
|
|
146
|
+
`select user_id, user_avatar, user_avatar_type, user_avatar_width, user_avatar_height
|
|
147
|
+
from \`${this.prefix}users\`
|
|
148
|
+
where user_id > ? and user_avatar <> ''
|
|
149
|
+
order by user_id asc limit ?`,
|
|
150
|
+
[afterId, limit],
|
|
151
|
+
)
|
|
152
|
+
const last = rows.at(-1)
|
|
153
|
+
return mapPage(
|
|
154
|
+
{ rows, nextCursor: rows.length < limit || last === undefined ? null : last.user_id },
|
|
155
|
+
mapPhpbbAvatar,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async attachments(afterId: number, limit: number): Promise<Page<ImportedAttachment>> {
|
|
160
|
+
const rows = await selectRows<PhpbbAttachment>(
|
|
161
|
+
this.connection,
|
|
162
|
+
`select attach_id, post_msg_id, poster_id, real_filename, physical_filename,
|
|
163
|
+
mimetype, filesize, download_count, filetime, thumbnail
|
|
164
|
+
from \`${this.prefix}attachments\`
|
|
165
|
+
where attach_id > ? and in_message = 0
|
|
166
|
+
order by attach_id asc limit ?`,
|
|
167
|
+
[afterId, limit],
|
|
168
|
+
)
|
|
169
|
+
const last = rows.at(-1)
|
|
170
|
+
return mapPage(
|
|
171
|
+
{ rows, nextCursor: rows.length < limit || last === undefined ? null : last.attach_id },
|
|
172
|
+
mapPhpbbAttachment,
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async polls(afterId: number, limit: number): Promise<Page<ImportedPoll>> {
|
|
177
|
+
const topics = await selectRows<PhpbbPollTopic>(
|
|
178
|
+
this.connection,
|
|
179
|
+
`select topic_id, poll_title, poll_start, poll_length, poll_max_options, poll_vote_change
|
|
180
|
+
from \`${this.prefix}topics\`
|
|
181
|
+
where topic_id > ? and poll_title <> '' and topic_moved_id = 0
|
|
182
|
+
order by topic_id asc limit ?`,
|
|
183
|
+
[afterId, limit],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const last = topics.at(-1)
|
|
187
|
+
const options =
|
|
188
|
+
topics.length === 0
|
|
189
|
+
? []
|
|
190
|
+
: await selectRows<PhpbbPollOption>(
|
|
191
|
+
this.connection,
|
|
192
|
+
`select topic_id, poll_option_id, poll_option_text, poll_option_total
|
|
193
|
+
from \`${this.prefix}poll_options\`
|
|
194
|
+
where topic_id in (${topics.map(() => '?').join(', ')})
|
|
195
|
+
order by topic_id asc, poll_option_id asc`,
|
|
196
|
+
topics.map((topic) => topic.topic_id),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
rows: topics
|
|
201
|
+
.map((topic) => mapPhpbbPoll(topic, options))
|
|
202
|
+
.filter((poll): poll is NonNullable<typeof poll> => poll !== null),
|
|
203
|
+
nextCursor: topics.length < limit || last === undefined ? null : last.topic_id,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async pollVotes(afterId: number, limit: number): Promise<Page<ImportedPollVote>> {
|
|
208
|
+
const table = `\`${this.prefix}poll_votes\``
|
|
209
|
+
const page = await pageByGroup<PhpbbPollVote>({
|
|
210
|
+
afterGroup: afterId,
|
|
211
|
+
limit,
|
|
212
|
+
groupOf: (row) => row.topic_id,
|
|
213
|
+
fetch: (afterGroup, take) =>
|
|
214
|
+
selectRows(
|
|
215
|
+
this.connection,
|
|
216
|
+
`select topic_id, poll_option_id, vote_user_id from ${table}
|
|
217
|
+
where topic_id > ? order by topic_id asc, vote_user_id asc limit ?`,
|
|
218
|
+
[afterGroup, take],
|
|
219
|
+
),
|
|
220
|
+
fetchGroup: (group) =>
|
|
221
|
+
selectRows(
|
|
222
|
+
this.connection,
|
|
223
|
+
`select topic_id, poll_option_id, vote_user_id from ${table}
|
|
224
|
+
where topic_id = ? order by vote_user_id asc`,
|
|
225
|
+
[group],
|
|
226
|
+
),
|
|
227
|
+
})
|
|
228
|
+
return mapPage(page, mapPhpbbPollVote)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async privateMessages(afterId: number, limit: number): Promise<Page<ImportedPrivateMessage>> {
|
|
232
|
+
const messages = await selectRows<PhpbbPrivateMessage>(
|
|
233
|
+
this.connection,
|
|
234
|
+
`select msg_id, author_id, message_subject, message_text, bbcode_uid, message_time
|
|
235
|
+
from \`${this.prefix}privmsgs\`
|
|
236
|
+
where msg_id > ? order by msg_id asc limit ?`,
|
|
237
|
+
[afterId, limit],
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
const last = messages.at(-1)
|
|
241
|
+
const copies =
|
|
242
|
+
messages.length === 0
|
|
243
|
+
? []
|
|
244
|
+
: await selectRows<PhpbbPrivateMessageCopy>(
|
|
245
|
+
this.connection,
|
|
246
|
+
`select msg_id, user_id, author_id, folder_id, pm_deleted, pm_unread
|
|
247
|
+
from \`${this.prefix}privmsgs_to\`
|
|
248
|
+
where msg_id in (${messages.map(() => '?').join(', ')})
|
|
249
|
+
order by msg_id asc, user_id asc`,
|
|
250
|
+
messages.map((message) => message.msg_id),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
rows: messages
|
|
255
|
+
.map((message) => mapPhpbbPrivateMessage(message, copies))
|
|
256
|
+
.filter((message): message is NonNullable<typeof message> => message !== null),
|
|
257
|
+
nextCursor: messages.length < limit || last === undefined ? null : last.msg_id,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
threadSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
|
|
262
|
+
return this.#watch(`\`${this.prefix}topics_watch\``, 'topic_id', afterId, limit)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
forumSubscriptions(afterId: number, limit: number): Promise<Page<ImportedSubscription>> {
|
|
266
|
+
return this.#watch(`\`${this.prefix}forums_watch\``, 'forum_id', afterId, limit)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
reputation(): Promise<Page<ImportedReputation>> {
|
|
270
|
+
return noPhpbbReputation()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async warnings(afterId: number, limit: number): Promise<Page<ImportedWarning>> {
|
|
274
|
+
return mapPage(
|
|
275
|
+
await keysetPage<PhpbbWarning>(
|
|
276
|
+
this.connection,
|
|
277
|
+
`select warning_id, user_id, post_id, warning_time
|
|
278
|
+
from \`${this.prefix}warnings\``,
|
|
279
|
+
'warning_id',
|
|
280
|
+
(row) => row.warning_id,
|
|
281
|
+
afterId,
|
|
282
|
+
limit,
|
|
283
|
+
),
|
|
284
|
+
mapPhpbbWarning,
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async bans(afterId: number, limit: number): Promise<Page<ImportedBan>> {
|
|
289
|
+
return mapPage(
|
|
290
|
+
await keysetPage<PhpbbBan>(
|
|
291
|
+
this.connection,
|
|
292
|
+
`select ban_id, ban_userid, ban_start, ban_end, ban_exclude,
|
|
293
|
+
ban_reason, ban_give_reason
|
|
294
|
+
from \`${this.prefix}banlist\``,
|
|
295
|
+
'ban_id',
|
|
296
|
+
(row) => row.ban_id,
|
|
297
|
+
afterId,
|
|
298
|
+
limit,
|
|
299
|
+
),
|
|
300
|
+
mapPhpbbBan,
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async userRelations(afterId: number, limit: number): Promise<Page<ImportedUserRelation>> {
|
|
305
|
+
const table = `\`${this.prefix}zebra\``
|
|
306
|
+
const page = await pageByGroup<PhpbbZebra>({
|
|
307
|
+
afterGroup: afterId,
|
|
308
|
+
limit,
|
|
309
|
+
groupOf: (row) => row.user_id,
|
|
310
|
+
fetch: (afterGroup, take) =>
|
|
311
|
+
selectRows(
|
|
312
|
+
this.connection,
|
|
313
|
+
`select user_id, zebra_id, friend, foe from ${table}
|
|
314
|
+
where user_id > ? order by user_id asc, zebra_id asc limit ?`,
|
|
315
|
+
[afterGroup, take],
|
|
316
|
+
),
|
|
317
|
+
fetchGroup: (group) =>
|
|
318
|
+
selectRows(
|
|
319
|
+
this.connection,
|
|
320
|
+
`select user_id, zebra_id, friend, foe from ${table}
|
|
321
|
+
where user_id = ? order by zebra_id asc`,
|
|
322
|
+
[group],
|
|
323
|
+
),
|
|
324
|
+
})
|
|
325
|
+
return mapPage(page, mapPhpbbZebra)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async #watch(
|
|
329
|
+
table: string,
|
|
330
|
+
targetColumn: 'topic_id' | 'forum_id',
|
|
331
|
+
afterId: number,
|
|
332
|
+
limit: number,
|
|
333
|
+
): Promise<Page<ImportedSubscription>> {
|
|
334
|
+
const page = await pageByGroup<PhpbbWatch>({
|
|
335
|
+
afterGroup: afterId,
|
|
336
|
+
limit,
|
|
337
|
+
groupOf: (row) => row.user_id,
|
|
338
|
+
fetch: (afterGroup, take) =>
|
|
339
|
+
selectRows(
|
|
340
|
+
this.connection,
|
|
341
|
+
`select user_id, ${targetColumn} as target_id from ${table}
|
|
342
|
+
where user_id > ? order by user_id asc, ${targetColumn} asc limit ?`,
|
|
343
|
+
[afterGroup, take],
|
|
344
|
+
),
|
|
345
|
+
fetchGroup: (group) =>
|
|
346
|
+
selectRows(
|
|
347
|
+
this.connection,
|
|
348
|
+
`select user_id, ${targetColumn} as target_id from ${table}
|
|
349
|
+
where user_id = ? order by ${targetColumn} asc`,
|
|
350
|
+
[group],
|
|
351
|
+
),
|
|
352
|
+
})
|
|
353
|
+
return mapPage(page, mapPhpbbWatch)
|
|
354
|
+
}
|
|
355
|
+
}
|