tiebameow 0.2.8__py3-none-any.whl

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.
@@ -0,0 +1,572 @@
1
+ """数据模型定义模块。
2
+
3
+ 该模块定义了所有与贴吧数据相关的SQLAlchemy ORM模型和Pydantic验证模型,
4
+ 包括论坛、用户、主题贴、回复、楼中楼等实体,以及各种内容片段的数据模型。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime # noqa: TC003
10
+ from typing import TYPE_CHECKING, Any, cast
11
+
12
+ from pydantic import TypeAdapter, ValidationError
13
+ from sqlalchemy import BIGINT, JSON, Boolean, DateTime, Enum, Index, Integer, String, Text, UniqueConstraint
14
+ from sqlalchemy.dialects.postgresql import JSONB
15
+ from sqlalchemy.ext.mutable import MutableList
16
+ from sqlalchemy.orm import DeclarativeBase, Mapped, foreign, mapped_column, relationship
17
+ from sqlalchemy.types import TypeDecorator, TypeEngine
18
+
19
+ from ..schemas.fragments import FRAG_MAP, Fragment, FragUnknownModel
20
+ from ..schemas.rules import Actions, ReviewRule, RuleNode, TargetType
21
+ from ..utils.time_utils import now_with_tz
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Callable
25
+
26
+ import aiotieba.typing as aiotieba
27
+ from sqlalchemy.engine.interfaces import Dialect
28
+
29
+ from .dto import BaseUserDTO, CommentDTO, PostDTO, ThreadDTO
30
+
31
+ type AiotiebaType = aiotieba.Thread | aiotieba.Post | aiotieba.Comment
32
+
33
+
34
+ __all__ = [
35
+ "Base",
36
+ "Forum",
37
+ "User",
38
+ "Thread",
39
+ "Post",
40
+ "Comment",
41
+ "Fragment",
42
+ "RuleBase",
43
+ "ReviewRules",
44
+ ]
45
+
46
+
47
+ class Base(DeclarativeBase):
48
+ pass
49
+
50
+
51
+ class FragmentListType(TypeDecorator[list[Fragment]]):
52
+ """自动处理Fragment模型列表的JSON序列化与反序列化。
53
+
54
+ 自动适配不同数据库的JSON类型。
55
+ """
56
+
57
+ impl = JSON
58
+ cache_ok = True
59
+
60
+ def __init__(self, fallback: Callable[[], Fragment] | None = None, *args: object, **kwargs: object):
61
+ super().__init__(*args, **kwargs)
62
+ self.adapter: TypeAdapter[Fragment] = TypeAdapter(Fragment)
63
+ self.fallback = fallback
64
+
65
+ def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
66
+ if dialect.name == "postgresql":
67
+ return dialect.type_descriptor(JSONB())
68
+ return dialect.type_descriptor(JSON())
69
+
70
+ def process_bind_param(self, value: list[Fragment] | None, dialect: Dialect) -> list[dict[str, Any]] | None:
71
+ if value is None:
72
+ return None
73
+ return [self.adapter.dump_python(item, mode="json") for item in value]
74
+
75
+ def process_result_value(self, value: list[dict[str, Any]] | None, dialect: Dialect) -> list[Fragment] | None:
76
+ if value is None:
77
+ return None
78
+ return [self._validate(item) for item in value]
79
+
80
+ def _validate(self, item: dict[str, Any]) -> Fragment:
81
+ if "type" in item:
82
+ if model_cls := FRAG_MAP.get(item["type"]):
83
+ return model_cls.model_construct(**item)
84
+
85
+ try:
86
+ return self.adapter.validate_python(item)
87
+ except ValidationError:
88
+ if self.fallback:
89
+ return self.fallback()
90
+ raise
91
+
92
+
93
+ class RuleNodeType(TypeDecorator[RuleNode]):
94
+ """自动处理RuleNode模型的JSON序列化与反序列化。"""
95
+
96
+ impl = JSON
97
+ cache_ok = True
98
+
99
+ def __init__(self, *args: object, **kwargs: object):
100
+ super().__init__(*args, **kwargs)
101
+ self.adapter: TypeAdapter[RuleNode] = TypeAdapter(RuleNode)
102
+
103
+ def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
104
+ if dialect.name == "postgresql":
105
+ return dialect.type_descriptor(JSONB())
106
+ return dialect.type_descriptor(JSON())
107
+
108
+ def process_bind_param(self, value: RuleNode | None, dialect: Dialect) -> dict[str, Any] | None:
109
+ if value is None:
110
+ return None
111
+ return cast("dict[str, Any]", self.adapter.dump_python(value, mode="json"))
112
+
113
+ def process_result_value(self, value: dict[str, Any] | None, dialect: Dialect) -> RuleNode | None:
114
+ if value is None:
115
+ return None
116
+ return self.adapter.validate_python(value)
117
+
118
+
119
+ class ActionsType(TypeDecorator[Actions]):
120
+ """自动处理Actions模型的JSON序列化与反序列化。"""
121
+
122
+ impl = JSON
123
+ cache_ok = True
124
+
125
+ def __init__(self, *args: object, **kwargs: object):
126
+ super().__init__(*args, **kwargs)
127
+ self.adapter: TypeAdapter[Actions] = TypeAdapter(Actions)
128
+
129
+ def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
130
+ if dialect.name == "postgresql":
131
+ return dialect.type_descriptor(JSONB())
132
+ return dialect.type_descriptor(JSON())
133
+
134
+ def process_bind_param(self, value: Actions | None, dialect: Dialect) -> dict[str, Any] | None:
135
+ if value is None:
136
+ return None
137
+ return cast("dict[str, Any]", self.adapter.dump_python(value, mode="json"))
138
+
139
+ def process_result_value(self, value: dict[str, Any] | None, dialect: Dialect) -> Actions | None:
140
+ if value is None:
141
+ return None
142
+ return self.adapter.validate_python(value)
143
+
144
+
145
+ class MixinBase(Base):
146
+ """为SQLAlchemy模型提供通用方法的混入类。"""
147
+
148
+ __abstract__ = True
149
+
150
+ def to_dict(self) -> dict[str, Any]:
151
+ """将模型实例的列数据转换为字典。
152
+
153
+ 此方法包含直接映射到数据库表的列,用于批量插入操作。
154
+
155
+ Returns:
156
+ dict: 包含模型列名和对应值的字典。
157
+ """
158
+ result = {}
159
+ for c in self.__table__.columns:
160
+ value = getattr(self, c.name)
161
+ result[c.name] = value
162
+ return result
163
+
164
+
165
+ class Forum(MixinBase):
166
+ """贴吧信息数据模型。
167
+
168
+ Attributes:
169
+ fid: 论坛ID,主键。
170
+ fname: 论坛名称,建立索引用于快速查询。
171
+ threads: 该论坛下的所有帖子,与Thread模型的反向关系。
172
+ """
173
+
174
+ __tablename__ = "forum"
175
+
176
+ fid: Mapped[int] = mapped_column(BIGINT, primary_key=True)
177
+ fname: Mapped[str] = mapped_column(String(255), index=True)
178
+
179
+ threads: Mapped[list[Thread]] = relationship(
180
+ "Thread",
181
+ back_populates="forum",
182
+ primaryjoin=lambda: Forum.fid == foreign(Thread.fid),
183
+ )
184
+
185
+
186
+ class User(MixinBase):
187
+ """用户数据模型。
188
+
189
+ Attributes:
190
+ user_id: 用户user_id,主键。
191
+ portrait: 用户portrait。
192
+ user_name: 用户名。
193
+ nick_name: 用户昵称。
194
+ threads: 该用户发布的所有帖子,与Thread模型的反向关系。
195
+ posts: 该用户发布的所有回复,与Post模型的反向关系。
196
+ comments: 该用户发布的所有评论,与Comment模型的反向关系。
197
+ """
198
+
199
+ __tablename__ = "user"
200
+
201
+ user_id: Mapped[int] = mapped_column(BIGINT, primary_key=True)
202
+ portrait: Mapped[str] = mapped_column(String(255), nullable=True, index=True)
203
+ user_name: Mapped[str] = mapped_column(String(255), nullable=True, index=True)
204
+ nick_name: Mapped[str] = mapped_column(String(255), nullable=True, index=True)
205
+
206
+ threads: Mapped[list[Thread]] = relationship(
207
+ "Thread",
208
+ back_populates="author",
209
+ primaryjoin=lambda: User.user_id == foreign(Thread.author_id),
210
+ )
211
+ posts: Mapped[list[Post]] = relationship(
212
+ "Post",
213
+ back_populates="author",
214
+ primaryjoin=lambda: User.user_id == foreign(Post.author_id),
215
+ )
216
+ comments: Mapped[list[Comment]] = relationship(
217
+ "Comment",
218
+ back_populates="author",
219
+ primaryjoin=lambda: User.user_id == foreign(Comment.author_id),
220
+ )
221
+
222
+ @classmethod
223
+ def from_dto(cls, dto: BaseUserDTO) -> User:
224
+ """从UserDTO对象创建User模型实例。
225
+
226
+ Args:
227
+ dto: UserDTO对象。
228
+
229
+ Returns:
230
+ User: 转换后的User模型实例。
231
+ """
232
+ return cls(
233
+ user_id=dto.user_id,
234
+ portrait=dto.portrait,
235
+ user_name=dto.user_name,
236
+ nick_name=dto.nick_name,
237
+ )
238
+
239
+
240
+ class Thread(MixinBase):
241
+ """主题贴数据模型。
242
+
243
+ Attributes:
244
+ tid: 主题贴tid,与create_time组成复合主键。
245
+ create_time: 主题贴创建时间,带时区信息,与tid组成复合主键。
246
+ title: 主题贴标题内容。
247
+ text: 主题贴的纯文本内容。
248
+ contents: 正文内容碎片列表,以JSONB格式存储。
249
+ last_time: 最后回复时间,带时区信息。
250
+ reply_num: 回复数。
251
+ author_level: 作者在主题贴所在吧的等级。
252
+ scrape_time: 数据抓取时间。
253
+ fid: 所属贴吧fid,外键关联到Forum表。
254
+ author_id: 作者user_id,外键关联到User表。
255
+ forum: 所属贴吧对象,与Forum模型的关系。
256
+ author: 作者用户对象,与User模型的关系。
257
+ posts: 该贴子下的所有回复,与Post模型的反向关系。
258
+ """
259
+
260
+ __tablename__ = "thread"
261
+ __table_args__ = (
262
+ Index("idx_thread_forum_ctime", "fid", "create_time"),
263
+ Index("idx_thread_forum_ltime", "fid", "last_time"),
264
+ Index("idx_thread_author_time", "author_id", "create_time"),
265
+ Index("idx_thread_author_forum_time", "author_id", "fid", "create_time"),
266
+ )
267
+
268
+ tid: Mapped[int] = mapped_column(BIGINT, primary_key=True)
269
+ create_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), primary_key=True)
270
+ title: Mapped[str] = mapped_column(String(255))
271
+ text: Mapped[str] = mapped_column(Text)
272
+ contents: Mapped[list[Fragment] | None] = mapped_column(
273
+ MutableList.as_mutable(FragmentListType(fallback=FragUnknownModel)), nullable=True
274
+ )
275
+ last_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
276
+ reply_num: Mapped[int] = mapped_column(Integer)
277
+ author_level: Mapped[int] = mapped_column(Integer)
278
+ scrape_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now_with_tz)
279
+
280
+ fid: Mapped[int] = mapped_column(BIGINT, index=True)
281
+ author_id: Mapped[int] = mapped_column(BIGINT, index=True)
282
+
283
+ forum: Mapped[Forum] = relationship(
284
+ "Forum",
285
+ back_populates="threads",
286
+ primaryjoin=lambda: foreign(Thread.fid) == Forum.fid,
287
+ )
288
+ author: Mapped[User] = relationship(
289
+ "User",
290
+ back_populates="threads",
291
+ primaryjoin=lambda: foreign(Thread.author_id) == User.user_id,
292
+ )
293
+ posts: Mapped[list[Post]] = relationship(
294
+ "Post",
295
+ back_populates="thread",
296
+ primaryjoin=lambda: Thread.tid == foreign(Post.tid),
297
+ )
298
+
299
+ @classmethod
300
+ def from_dto(cls, dto: ThreadDTO) -> Thread:
301
+ """从ThreadDTO对象创建Thread模型实例。
302
+
303
+ Args:
304
+ dto: ThreadDTO对象。
305
+
306
+ Returns:
307
+ Thread: 转换后的Thread模型实例。
308
+ """
309
+ return cls(
310
+ tid=dto.tid,
311
+ create_time=dto.create_time,
312
+ title=dto.title,
313
+ text=dto.text,
314
+ contents=dto.contents,
315
+ last_time=dto.last_time,
316
+ reply_num=dto.reply_num,
317
+ author_level=dto.author.level,
318
+ scrape_time=now_with_tz(),
319
+ fid=dto.fid,
320
+ author_id=dto.author_id,
321
+ )
322
+
323
+
324
+ class Post(MixinBase):
325
+ """回复数据模型。
326
+
327
+ Attributes:
328
+ pid: 回复pid,与create_time组成复合主键。
329
+ create_time: 回复创建时间,带时区信息,与pid组成复合主键。
330
+ text: 回复的纯文本内容。
331
+ contents: 回复的正文内容碎片列表,以JSONB格式存储。
332
+ floor: 楼层号。
333
+ reply_num: 该回复下的楼中楼数量。
334
+ author_level: 作者在主题贴所在吧的等级。
335
+ scrape_time: 数据抓取时间。
336
+ tid: 所属贴子tid,外键关联到Thread表。
337
+ author_id: 作者user_id,外键关联到User表。
338
+ thread: 所属主题贴对象,与Thread模型的关系。
339
+ author: 作者用户对象,与User模型的关系。
340
+ comments: 该回复下的所有楼中楼,与Comment模型的反向关系。
341
+ """
342
+
343
+ __tablename__ = "post"
344
+ __table_args__ = (
345
+ Index("idx_post_thread_time", "tid", "create_time"),
346
+ Index("idx_post_author_time", "author_id", "create_time"),
347
+ )
348
+
349
+ pid: Mapped[int] = mapped_column(BIGINT, primary_key=True)
350
+ create_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), primary_key=True)
351
+ text: Mapped[str] = mapped_column(Text)
352
+ contents: Mapped[list[Fragment] | None] = mapped_column(
353
+ MutableList.as_mutable(FragmentListType(fallback=FragUnknownModel)), nullable=True
354
+ )
355
+ floor: Mapped[int] = mapped_column(Integer)
356
+ reply_num: Mapped[int] = mapped_column(Integer)
357
+ author_level: Mapped[int] = mapped_column(Integer)
358
+ scrape_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now_with_tz)
359
+
360
+ tid: Mapped[int] = mapped_column(BIGINT, index=True)
361
+ fid: Mapped[int] = mapped_column(BIGINT, index=True)
362
+ author_id: Mapped[int] = mapped_column(BIGINT, index=True)
363
+
364
+ thread: Mapped[Thread] = relationship(
365
+ "Thread",
366
+ back_populates="posts",
367
+ primaryjoin=lambda: foreign(Post.tid) == Thread.tid,
368
+ )
369
+ author: Mapped[User] = relationship(
370
+ "User",
371
+ back_populates="posts",
372
+ primaryjoin=lambda: foreign(Post.author_id) == User.user_id,
373
+ )
374
+ comments: Mapped[list[Comment]] = relationship(
375
+ "Comment",
376
+ back_populates="post",
377
+ primaryjoin=lambda: Post.pid == foreign(Comment.pid),
378
+ )
379
+
380
+ @classmethod
381
+ def from_dto(cls, dto: PostDTO) -> Post:
382
+ """从PostDTO对象创建Post模型实例。
383
+
384
+ Args:
385
+ dto: PostDTO对象。
386
+
387
+ Returns:
388
+ Post: 转换后的Post模型实例。
389
+ """
390
+ return cls(
391
+ pid=dto.pid,
392
+ create_time=dto.create_time,
393
+ text=dto.text,
394
+ contents=dto.contents,
395
+ floor=dto.floor,
396
+ reply_num=dto.reply_num,
397
+ author_level=dto.author.level,
398
+ scrape_time=now_with_tz(),
399
+ tid=dto.tid,
400
+ fid=dto.fid,
401
+ author_id=dto.author_id,
402
+ )
403
+
404
+
405
+ class Comment(MixinBase):
406
+ """楼中楼数据模型。
407
+
408
+ Attributes:
409
+ cid: 楼中楼pid,存储为cid以区分,与create_time组成复合主键。
410
+ create_time: 楼中楼创建时间,带时区信息,与cid组成复合主键。
411
+ text: 楼中楼的纯文本内容。
412
+ contents: 楼中楼的正文内容碎片列表,以JSONB格式存储。
413
+ author_level: 作者在主题贴所在吧的等级。
414
+ reply_to_id: 被回复者的user_id,可为空。
415
+ scrape_time: 数据抓取时间。
416
+ pid: 所属回复ID,外键关联到Post表。
417
+ author_id: 作者user_id,外键关联到User表。
418
+ post: 所属回复对象,与Post模型的关系。
419
+ author: 作者用户对象,与User模型的关系。
420
+ """
421
+
422
+ __tablename__ = "comment"
423
+ __table_args__ = (
424
+ Index("idx_comment_post_time", "pid", "create_time"),
425
+ Index("idx_comment_author_time", "author_id", "create_time"),
426
+ )
427
+
428
+ cid: Mapped[int] = mapped_column(BIGINT, primary_key=True)
429
+ create_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), primary_key=True)
430
+ text: Mapped[str] = mapped_column(Text)
431
+ contents: Mapped[list[Fragment] | None] = mapped_column(
432
+ MutableList.as_mutable(FragmentListType(fallback=FragUnknownModel)), nullable=True
433
+ )
434
+ author_level: Mapped[int] = mapped_column(Integer)
435
+ reply_to_id: Mapped[int | None] = mapped_column(BIGINT, nullable=True)
436
+ scrape_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now_with_tz)
437
+
438
+ pid: Mapped[int] = mapped_column(BIGINT, index=True)
439
+ tid: Mapped[int] = mapped_column(BIGINT, index=True)
440
+ fid: Mapped[int] = mapped_column(BIGINT, index=True)
441
+ author_id: Mapped[int] = mapped_column(BIGINT, index=True)
442
+
443
+ post: Mapped[Post] = relationship(
444
+ "Post",
445
+ back_populates="comments",
446
+ primaryjoin=lambda: foreign(Comment.pid) == Post.pid,
447
+ )
448
+ author: Mapped[User] = relationship(
449
+ "User",
450
+ back_populates="comments",
451
+ primaryjoin=lambda: foreign(Comment.author_id) == User.user_id,
452
+ )
453
+
454
+ @classmethod
455
+ def from_dto(cls, dto: CommentDTO) -> Comment:
456
+ """从CommentDTO对象创建Comment模型实例。
457
+
458
+ Args:
459
+ dto: CommentDTO对象。
460
+
461
+ Returns:
462
+ Comment: 转换后的Comment模型实例。
463
+ """
464
+ return cls(
465
+ cid=dto.cid,
466
+ create_time=dto.create_time,
467
+ text=dto.text,
468
+ contents=dto.contents,
469
+ author_level=dto.author.level,
470
+ reply_to_id=dto.reply_to_id,
471
+ scrape_time=now_with_tz(),
472
+ pid=dto.pid,
473
+ tid=dto.tid,
474
+ fid=dto.fid,
475
+ author_id=dto.author_id,
476
+ )
477
+
478
+
479
+ class RuleBase(DeclarativeBase):
480
+ pass
481
+
482
+
483
+ class ReviewRules(RuleBase):
484
+ """审查规则的数据库模型。
485
+
486
+ 对应数据库中的 review_rules 表。
487
+
488
+ Attributes:
489
+ id: 主键 ID。
490
+ fid: 贴吧 fid。
491
+ forum_rule_id: 贴吧规则 ID。
492
+ target_type: 规则作用目标类型。
493
+ name: 规则名称。
494
+ enabled: 是否启用。
495
+ priority: 优先级。
496
+ trigger: 触发条件 JSON。
497
+ actions: 动作列表 JSON。
498
+ created_at: 创建时间。
499
+ updated_at: 更新时间。
500
+ """
501
+
502
+ __tablename__ = "review_rules"
503
+ __table_args__ = (
504
+ UniqueConstraint("fid", "forum_rule_id", name="uq_review_rules_fid_forum_rule_id"),
505
+ Index("idx_review_rules_fid_forum_rule_id", "fid", "forum_rule_id"),
506
+ Index("idx_review_rules_fid_enabled_priority", "fid", "enabled", "priority"),
507
+ )
508
+
509
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
510
+ fid: Mapped[int] = mapped_column(BIGINT, nullable=False)
511
+ forum_rule_id: Mapped[int] = mapped_column(Integer, nullable=False)
512
+ uploader_id: Mapped[int] = mapped_column(BIGINT, default=0, nullable=False)
513
+ target_type: Mapped[TargetType] = mapped_column(
514
+ Enum(TargetType, name="target_type_enum"),
515
+ index=True,
516
+ default=TargetType.ALL,
517
+ nullable=False,
518
+ )
519
+ name: Mapped[str] = mapped_column(String, nullable=False)
520
+ enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
521
+ priority: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
522
+ trigger: Mapped[RuleNode] = mapped_column(RuleNodeType, index=True, nullable=False)
523
+ actions: Mapped[Actions] = mapped_column(ActionsType, nullable=False)
524
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now_with_tz, nullable=False)
525
+ updated_at: Mapped[datetime] = mapped_column(
526
+ DateTime(timezone=True), index=True, default=now_with_tz, onupdate=now_with_tz, nullable=False
527
+ )
528
+
529
+ @classmethod
530
+ def from_rule_data(cls, review_rule: ReviewRule) -> ReviewRules:
531
+ """
532
+ 从ReviewRule对象创建ReviewRules模型实例。
533
+
534
+ id 字段将被忽略,由数据库自动生成。
535
+
536
+ Args:
537
+ review_rule: ReviewRule对象。
538
+
539
+ Returns:
540
+ ReviewRules: 转换后的ReviewRules模型实例。
541
+ """
542
+ return cls(
543
+ fid=review_rule.fid,
544
+ forum_rule_id=review_rule.forum_rule_id,
545
+ uploader_id=review_rule.uploader_id,
546
+ target_type=review_rule.target_type,
547
+ name=review_rule.name,
548
+ enabled=review_rule.enabled,
549
+ priority=review_rule.priority,
550
+ trigger=review_rule.trigger,
551
+ actions=review_rule.actions,
552
+ )
553
+
554
+ def to_rule_data(self) -> ReviewRule:
555
+ """
556
+ 将ReviewRules模型实例转换为ReviewRule对象。
557
+
558
+ Returns:
559
+ ReviewRule: 转换后的ReviewRule对象。
560
+ """
561
+ return ReviewRule(
562
+ id=self.id,
563
+ fid=self.fid,
564
+ forum_rule_id=self.forum_rule_id,
565
+ uploader_id=self.uploader_id,
566
+ target_type=self.target_type,
567
+ name=self.name,
568
+ enabled=self.enabled,
569
+ priority=self.priority,
570
+ trigger=self.trigger,
571
+ actions=self.actions,
572
+ )
@@ -0,0 +1,45 @@
1
+ from .parser import (
2
+ convert_aiotieba_comment,
3
+ convert_aiotieba_comments,
4
+ convert_aiotieba_commentsp,
5
+ convert_aiotieba_commentuser,
6
+ convert_aiotieba_content_list,
7
+ convert_aiotieba_forum,
8
+ convert_aiotieba_fragment,
9
+ convert_aiotieba_pageinfo,
10
+ convert_aiotieba_post,
11
+ convert_aiotieba_posts,
12
+ convert_aiotieba_postuser,
13
+ convert_aiotieba_share_thread,
14
+ convert_aiotieba_thread,
15
+ convert_aiotieba_threadp,
16
+ convert_aiotieba_threads,
17
+ convert_aiotieba_threaduser,
18
+ convert_aiotieba_tiebauiduser,
19
+ convert_aiotieba_user,
20
+ convert_aiotieba_userinfo,
21
+ )
22
+ from .rule_parser import RuleEngineParser
23
+
24
+ __all__ = [
25
+ "convert_aiotieba_comment",
26
+ "convert_aiotieba_comments",
27
+ "convert_aiotieba_commentsp",
28
+ "convert_aiotieba_commentuser",
29
+ "convert_aiotieba_content_list",
30
+ "convert_aiotieba_forum",
31
+ "convert_aiotieba_fragment",
32
+ "convert_aiotieba_pageinfo",
33
+ "convert_aiotieba_post",
34
+ "convert_aiotieba_posts",
35
+ "convert_aiotieba_postuser",
36
+ "convert_aiotieba_share_thread",
37
+ "convert_aiotieba_thread",
38
+ "convert_aiotieba_threadp",
39
+ "convert_aiotieba_threads",
40
+ "convert_aiotieba_threaduser",
41
+ "convert_aiotieba_tiebauiduser",
42
+ "convert_aiotieba_user",
43
+ "convert_aiotieba_userinfo",
44
+ "RuleEngineParser",
45
+ ]