Browse Source

Big module restructure, added async

pull/6/head
Screamo Bmo 2 years ago
parent
commit
cc122a004d
  1. 0
      .gitignore
  2. 0
      LICENSE
  3. 3
      Pipfile
  4. 193
      Pipfile.lock
  5. 0
      README.md
  6. 2
      api_v1/__init__.py
  7. 15
      api_v1/admin/__init__.py
  8. 7
      api_v1/categories/__init__.py
  9. 21
      api_v1/client.py
  10. 79
      api_v1/comment/__init__.py
  11. 79
      api_v1/community/__init__.py
  12. 23
      api_v1/community/reports.py
  13. 15
      api_v1/community/settings.py
  14. 99
      api_v1/helpers.py
  15. 7
      api_v1/modlog/__init__.py
  16. 111
      api_v1/post/__init__.py
  17. 39
      api_v1/private_message/__init__.py
  18. 3
      api_v1/routes.py
  19. 7
      api_v1/search/__init__.py
  20. 31
      api_v1/site/__init__.py
  21. 15
      api_v1/site/config.py
  22. 7
      api_v1/site/mods.py
  23. 19
      api_v1/sitemod/__init__.py
  24. 19
      api_v1/types/__init__.py
  25. 5
      api_v1/types/category.py
  26. 58
      api_v1/types/comment.py
  27. 81
      api_v1/types/comment_view.py
  28. 100
      api_v1/types/community.py
  29. 32
      api_v1/types/community_settings.py
  30. 77
      api_v1/types/community_view.py
  31. 38
      api_v1/types/lib.py
  32. 107
      api_v1/types/moderator_views.py
  33. 81
      api_v1/types/post.py
  34. 13
      api_v1/types/post_hexbear.py
  35. 53
      api_v1/types/post_view.py
  36. 24
      api_v1/types/private_message_view.py
  37. 63
      api_v1/types/report.py
  38. 34
      api_v1/types/report_views.py
  39. 119
      api_v1/types/site.py
  40. 23
      api_v1/types/site_view.py
  41. 0
      api_v1/types/user.py
  42. 39
      api_v1/types/user_mention_view.py
  43. 26
      api_v1/types/user_view.py
  44. 449
      api_v1/user/__init__.py
  45. 1
      hexbear/__init__.py
  46. 4
      hexbear/v1/__init__.py
  47. 19
      hexbear/v1/admin.py
  48. 20
      hexbear/v1/categories.py
  49. 123
      hexbear/v1/comment.py
  50. 218
      hexbear/v1/community.py
  51. 154
      hexbear/v1/lib.py
  52. 34
      hexbear/v1/modlog.py
  53. 187
      hexbear/v1/post.py
  54. 75
      hexbear/v1/private_message.py
  55. 119
      hexbear/v1/reports.py
  56. 27
      hexbear/v1/search.py
  57. 121
      hexbear/v1/site.py
  58. 19
      hexbear/v1/sitemod.py
  59. 351
      hexbear/v1/user.py
  60. 463
      hexbear/v1/views.py
  61. 22
      main.py
  62. 2
      scripts/rust_to_python.py
  63. 20
      tests/test_noauth.py

0
.gitignore

0
LICENSE

3
Pipfile

@ -8,6 +8,9 @@ pylint = "*"
[packages]
typing-inspect = "*"
requests = "*"
result = "*"
aiohttp = "*"
[requires]
python_version = "3.9"

193
Pipfile.lock

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "be90e3f18ff119a3b261828afd42fa950abe7ae4660ccb13aa45c4c2e603d130"
"sha256": "6a4b78bd1ea4fa568360fcc25ef7d2d20c78640eb988da5e2e0866ada6122dda"
},
"pipfile-spec": 6,
"requires": {
@ -16,6 +16,130 @@
]
},
"default": {
"aiohttp": {
"hashes": [
"sha256:0b795072bb1bf87b8620120a6373a3c61bfcb8da7e5c2377f4bb23ff4f0b62c9",
"sha256:0d438c8ca703b1b714e82ed5b7a4412c82577040dadff479c08405e2a715564f",
"sha256:16a3cb5df5c56f696234ea9e65e227d1ebe9c18aa774d36ff42f532139066a5f",
"sha256:1edfd82a98c5161497bbb111b2b70c0813102ad7e0aa81cbeb34e64c93863005",
"sha256:2406dc1dda01c7f6060ab586e4601f18affb7a6b965c50a8c90ff07569cf782a",
"sha256:2858b2504c8697beb9357be01dc47ef86438cc1cb36ecb6991796d19475faa3e",
"sha256:2a7b7640167ab536c3cb90cfc3977c7094f1c5890d7eeede8b273c175c3910fd",
"sha256:3228b7a51e3ed533f5472f54f70fd0b0a64c48dc1649a0f0e809bec312934d7a",
"sha256:328b552513d4f95b0a2eea4c8573e112866107227661834652a8984766aa7656",
"sha256:39f4b0a6ae22a1c567cb0630c30dd082481f95c13ca528dc501a7766b9c718c0",
"sha256:3b0036c978cbcc4a4512278e98e3e6d9e6b834dc973206162eddf98b586ef1c6",
"sha256:3ea8c252d8df5e9166bcf3d9edced2af132f4ead8ac422eac723c5781063709a",
"sha256:41608c0acbe0899c852281978492f9ce2c6fbfaf60aff0cefc54a7c4516b822c",
"sha256:59d11674964b74a81b149d4ceaff2b674b3b0e4d0f10f0be1533e49c4a28408b",
"sha256:5e479df4b2d0f8f02133b7e4430098699450e1b2a826438af6bec9a400530957",
"sha256:684850fb1e3e55c9220aad007f8386d8e3e477c4ec9211ae54d968ecdca8c6f9",
"sha256:6ccc43d68b81c424e46192a778f97da94ee0630337c9bbe5b2ecc9b0c1c59001",
"sha256:6d42debaf55450643146fabe4b6817bb2a55b23698b0434107e892a43117285e",
"sha256:710376bf67d8ff4500a31d0c207b8941ff4fba5de6890a701d71680474fe2a60",
"sha256:756ae7efddd68d4ea7d89c636b703e14a0c686688d42f588b90778a3c2fc0564",
"sha256:77149002d9386fae303a4a162e6bce75cc2161347ad2ba06c2f0182561875d45",
"sha256:78e2f18a82b88cbc37d22365cf8d2b879a492faedb3f2975adb4ed8dfe994d3a",
"sha256:7d9b42127a6c0bdcc25c3dcf252bb3ddc70454fac593b1b6933ae091396deb13",
"sha256:8389d6044ee4e2037dca83e3f6994738550f6ee8cfb746762283fad9b932868f",
"sha256:9c1a81af067e72261c9cbe33ea792893e83bc6aa987bfbd6fdc1e5e7b22777c4",
"sha256:c1e0920909d916d3375c7a1fdb0b1c78e46170e8bb42792312b6eb6676b2f87f",
"sha256:c68fdf21c6f3573ae19c7ee65f9ff185649a060c9a06535e9c3a0ee0bbac9235",
"sha256:c733ef3bdcfe52a1a75564389bad4064352274036e7e234730526d155f04d914",
"sha256:c9c58b0b84055d8bc27b7df5a9d141df4ee6ff59821f922dd73155861282f6a3",
"sha256:d03abec50df423b026a5aa09656bd9d37f1e6a49271f123f31f9b8aed5dc3ea3",
"sha256:d2cfac21e31e841d60dc28c0ec7d4ec47a35c608cb8906435d47ef83ffb22150",
"sha256:dcc119db14757b0c7bce64042158307b9b1c76471e655751a61b57f5a0e4d78e",
"sha256:df3a7b258cc230a65245167a202dd07320a5af05f3d41da1488ba0fa05bc9347",
"sha256:df48a623c58180874d7407b4d9ec06a19b84ed47f60a3884345b1a5099c1818b",
"sha256:e1b95972a0ae3f248a899cdbac92ba2e01d731225f566569311043ce2226f5e7",
"sha256:f326b3c1bbfda5b9308252ee0dcb30b612ee92b0e105d4abec70335fab5b1245",
"sha256:f411cb22115cb15452d099fec0ee636b06cf81bfb40ed9c02d30c8dc2bc2e3d1"
],
"index": "pypi",
"version": "==3.7.3"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1"
},
"attrs": {
"hashes": [
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.3.0"
},
"certifi": {
"hashes": [
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
],
"version": "==2020.12.5"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"multidict": {
"hashes": [
"sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a",
"sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93",
"sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632",
"sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656",
"sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79",
"sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7",
"sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d",
"sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5",
"sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224",
"sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26",
"sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea",
"sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348",
"sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6",
"sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76",
"sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1",
"sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f",
"sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952",
"sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a",
"sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37",
"sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9",
"sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359",
"sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8",
"sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da",
"sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3",
"sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d",
"sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf",
"sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841",
"sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d",
"sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93",
"sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f",
"sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647",
"sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635",
"sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456",
"sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda",
"sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5",
"sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
"sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
],
"markers": "python_version >= '3.6'",
"version": "==5.1.0"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
@ -23,6 +147,22 @@
],
"version": "==0.4.3"
},
"requests": {
"hashes": [
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
],
"index": "pypi",
"version": "==2.25.0"
},
"result": {
"hashes": [
"sha256:46f039a2d17e47709c13e29af359c3fa91fd5cacddba2a8109fdcb514e6ff471",
"sha256:f4563ff615b1147822d13eb363fbda202511fcbf281b3cf7acf0723ca7cb612b"
],
"index": "pypi",
"version": "==0.5.0"
},
"typing-extensions": {
"hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
@ -39,6 +179,57 @@
],
"index": "pypi",
"version": "==0.6.0"
},
"urllib3": {
"hashes": [
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.2"
},
"yarl": {
"hashes": [
"sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e",
"sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434",
"sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366",
"sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3",
"sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec",
"sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959",
"sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e",
"sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c",
"sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6",
"sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a",
"sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6",
"sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424",
"sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e",
"sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f",
"sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50",
"sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2",
"sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc",
"sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4",
"sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970",
"sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10",
"sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0",
"sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406",
"sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896",
"sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643",
"sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721",
"sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478",
"sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724",
"sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e",
"sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8",
"sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96",
"sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25",
"sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76",
"sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2",
"sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2",
"sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c",
"sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
"sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
],
"markers": "python_version >= '3.6'",
"version": "==1.6.3"
}
},
"develop": {

0
README.md

2
api_v1/__init__.py

@ -1,2 +0,0 @@
from .routes import *
from .types import *

15
api_v1/admin/__init__.py

@ -1,15 +0,0 @@
class AddAdminRequest(NamedTuple):
user_id: int
added: bool
auth: str
class AddAdminResponse(NamedTuple):
admins: List[UserView]
def add(payload: AddAdminRequest):
return request(
'post',
endpoint='/api/v1/admin/add',
payload=payload,
response_type=AddAdminResponse
)

7
api_v1/categories/__init__.py

@ -1,7 +0,0 @@
def get_list(payload: site.ListCategories):
return request(
'get',
endpoint='/api/v1/categories',
payload=payload,
response_type=site.ListCategoriesResponse
)

21
api_v1/client.py

@ -1,21 +0,0 @@
from functools import partial
from .types import Login, LoginResponse
from .routes import login
class Client:
def __init__(self, *, username: str, password: str):
request = Login(username_or_email=username, password=password)
token = login(payload=request)
while token.requires_2fa == True:
auth_code = input("2FA Code (Check email): ")
request = Login(username_or_email=username, password=password, code_2fa=auth_code)
token = login(payload=request)
if token.requies_2fa == True:
print("Incorrect 2FA code")
if token.jwt is None:
raise ValueError("Invalid JWT token received from server")
self.auth = token.jwt
def authorize(self, func):
return partial(func, auth=self.auth)

79
api_v1/comment/__init__.py

@ -1,79 +0,0 @@
def create_comment(payload: comment.CreateComment):
return request(
'post',
endpoint='/api/v1/comment',
payload=payload,
response_type=dict
)
def edit_comment(payload: comment.EditComment):
return request(
'put',
endpoint='/api/v1/comment',
payload=payload,
response_type=dict
)
def create_comment_report(payload: report.CreateCommentReport):
return request(
'post',
endpoint='/api/v1/comment/report',
payload=payload,
response_type=dict
)
def delete_comment(payload: comment.DeleteComment):
return request(
'post',
endpoint='/api/v1/comment/delete',
payload=payload,
response_type=dict
)
def remove_comment(payload: comment.RemoveComment):
return request(
'post',
endpoint='/api/v1/comment/remove',
payload=payload,
response_type=dict
)
def mark_comment_as_read(payload: comment.MarkCommentAsRead):
return request(
'post',
endpoint='/api/v1/comment/mark_as_read',
payload=payload,
response_type=dict
)
def create_comment_like(payload: comment.CreateCommentLike):
return request(
'post',
endpoint='/api/v1/comment/like',
payload=payload,
response_type=dict
)
def save_comment(payload: comment.SaveComment):
return request(
'put',
endpoint='/api/v1/comment/save',
payload=payload,
response_type=dict
)
def resolve_comment_report(payload: report.ResolveCommentReport):
return request(
'post',
endpoint='/api/v1/comment/resolve_report',
payload=payload,
response_type=dict
)
def get_comments(payload: comment.GetComments):
return request(
'get',
endpoint='/api/v1/comment/list',
payload=payload,
response_type=comment.GetCommentsResponse
)

79
api_v1/community/__init__.py

@ -1,79 +0,0 @@
def create(payload: community.CreateCommunity):
return request(
'post',
endpoint='/api/v1/community',
payload=payload,
response_type=dict
)
def get(payload: community.GetCommunity):
return request(
'get',
endpoint='/api/v1/community',
payload=payload,
response_type=community.GetCommunityResponse
)
def edit(payload: community.EditCommunity):
return request(
'put',
endpoint='/api/v1/community',
payload=payload,
response_type=dict
)
def get_list(payload: community.ListCommunities):
return request(
'get',
endpoint='/api/v1/community/list',
payload=payload,
response_type=community.ListCommunitiesResponse
)
def follow(payload: community.FollowCommunity):
return request(
'post',
endpoint='/api/v1/community/follow',
payload=payload,
response_type=dict
)
def delete(payload: community.DeleteCommunity):
return request(
'post',
endpoint='/api/v1/community/delete',
payload=payload,
response_type=dict
)
def remove(payload: community.RemoveCommunity):
return request(
'post',
endpoint='/api/v1/community/remove',
payload=payload,
response_type=dict
)
def transfer(payload: community.TransferCommunity):
return request(
'post',
endpoint='/api/v1/community/transfer',
payload=payload,
response_type=dict
)
def ban_user(payload: community.BanFromCommunity):
return request(
'post',
endpoint='/api/v1/community/ban_user',
payload=payload,
response_type=dict
)
def add_mod(payload: community.AddModToCommunity):
return request(
'post',
endpoint='/api/v1/community/mod',
payload=payload,
response_type=dict
)

23
api_v1/community/reports.py

@ -1,23 +0,0 @@
def comments(payload: report.ListCommentReports):
return request(
'get',
endpoint='/api/v1/community/comment_reports',
payload=payload,
response_type=dict
)
def posts(payload: report.ListPostReports):
return request(
'get',
endpoint='/api/v1/community/post_reports',
payload=payload,
response_type=dict
)
def get_count(payload: report.GetReportCount):
return request(
'get',
endpoint='/api/v1/community/reports',
payload=payload,
response_type=dict
)

15
api_v1/community/settings.py

@ -1,15 +0,0 @@
def get(payload: community_settings.GetCommunitySettings):
return request(
'get',
endpoint='/api/v1/community/settings',
payload=payload,
response_type=dict
)
def edit(payload: community_settings.EditCommunitySettings):
return request(
'put',
endpoint='/api/v1/community/settings',
payload=payload,
response_type=dict
)

99
api_v1/helpers.py

@ -1,99 +0,0 @@
from typing import NamedTuple, Literal, TypeVar, Optional, Union
import requests
from typing_inspect import is_optional_type, is_generic_type, get_origin, get_args
from datetime import datetime
import json
Method = Literal['get', 'post', 'put']
R = TypeVar('R')
class APIError(NamedTuple):
message: str
class ErrorResponse(NamedTuple):
error: APIError
def request(
method: Method,
*,
endpoint: str,
payload: NamedTuple,
response_type: R,
base_url='https://www.chapo.chat'
) -> Union[R, ErrorResponse]:
"""Make API call and parse response"""
payload = {
key: (
str(value).lower() # Why the fuck does requests not automtically make booleans lowercase?
if type(value) == bool
else value
)
for key, value in payload._asdict().items()
}
url = f'{base_url}{endpoint}'
if method == 'get':
response = requests.get(url, params=payload)
elif method == 'put':
response = requests.put(url, data=payload)
elif method == 'post':
resposne = requests.post(url, data=payload)
else:
ValueError(f'Method must be "get", "put", or "post", not "{method}"')
response.raise_for_status()
parsed = response.json()
return parse_to_namedtuple(parsed, parsed_type=response_type)
def parse_to_namedtuple(obj: dict, parsed_type: type):
if 'error' in obj:
parsed_type = ErrorResponse
if parsed_type is None or parsed_type == dict:
return obj
if not is_typed_named_tuple(parsed_type):
raise TypeError(f'parsed_type must be NamedTuple, not {parsed_type}')
field_types = vars(parsed_type)['__annotations__'] # parsed_type._field_types doesn't exist when running tests for some reason
defaults = parsed_type._field_defaults
parsed_obj = {}
for field, type_ in field_types.items():
try:
value = obj[field]
except KeyError:
if field in defaults:
value = defaults[field]
else:
raise ValueError(f'Missing required field encountered while parsing: {field}')
else:
parsed_obj[field] = ensure_type(type_, value)
return parsed_type(**parsed_obj)
def is_typed_named_tuple(t):
return all(
hasattr(t, attr)
for attr in ('_fields', '_field_defaults', '__annotations__') # '_field_types')
)
def ensure_type(type_, value):
if type(value) == type_:
return value
if type_ == datetime:
return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
if is_optional_type(type_):
if value is None:
return None
else:
optional_type = get_args(type_)[0]
return ensure_type(optional_type, value)
if is_generic_type(type_):
origin = get_origin(type_)
if origin == list:
list_type = get_args(type_)[0]
return [ensure_type(list_type, item) for item in value]
else:
raise TypeError(f"Unknown generic type: {type_}")
if is_typed_named_tuple(type_):
return parse_to_namedtuple(value, parsed_type=type_)
raise TypeError(f'Type mismatch: {type_} and {type(value)}')

7
api_v1/modlog/__init__.py

@ -1,7 +0,0 @@
def get(payload: site.GetModlog):
return request(
'get',
endpoint='/api/v1/modlog',
payload=payload,
response_type=site.GetModlogResponse
)

111
api_v1/post/__init__.py

@ -1,111 +0,0 @@
def create(payload: post.CreatePost):
return request(
'post',
endpoint='/api/v1/post',
payload=payload,
response_type=dict
)
def get(payload: post.GetPost):
return request(
'get',
endpoint='/api/v1/post',
payload=payload,
response_type=post.GetPostResponse
)
def edit(payload: post.EditPost):
return request(
'put',
endpoint='/api/v1/post',
payload=payload,
response_type=dict
)
def create_report(payload: report.CreatePostReport):
return request(
'post',
endpoint='/api/v1/post/report',
payload=payload,
response_type=dict
)
def delete(payload: post.DeletePost):
return request(
'post',
endpoint='/api/v1/post/delete',
payload=payload,
response_type=dict
)
def remove(payload: post.RemovePost):
return request(
'post',
endpoint='/api/v1/post/remove',
payload=payload,
response_type=dict
)
def lock(payload: post.LockPost):
return request(
'post',
endpoint='/api/v1/post/lock',
payload=payload,
response_type=dict
)
def sticky(payload: post.StickyPost):
return request(
'post',
endpoint='/api/v1/post/sticky',
payload=payload,
response_type=dict
)
def feed(payload: post.GetPosts):
return request(
'get',
endpoint='/api/v1/post/list',
payload=payload,
response_type=post.GetPostsResponse
)
def create_like(payload: post.CreatePostLike):
return request(
'post',
endpoint='/api/v1/post/like',
payload=payload,
response_type=dict
)
def save(payload: post.SavePost):
return request(
'put',
endpoint='/api/v1/post/save',
payload=payload,
response_type=dict
)
def resolve_report(payload: report.ResolvePostReport):
return request(
'post',
endpoint='/api/v1/post/resolve_report',
payload=payload,
response_type=dict
)
def list_featured(payload: post_hexbear.GetFeaturedPosts):
return request(
'get',
endpoint='/api/v1/post/featured',
payload=payload,
response_type=post_hexbear.GetFeaturedPostsResponse
)
def feature(payload: post_hexbear.FeaturePost):
return request(
'put',
endpoint='/api/v1/post/featured',
payload=payload,
response_type=dict
)

39
api_v1/private_message/__init__.py

@ -1,39 +0,0 @@
def create_private_message(payload: user.CreatePrivateMessage):
return request(
'post',
endpoint='/api/v1/private_message',
payload=payload,
response_type=user.PrivateMessageResponse
)
def edit_private_message(payload: user.EditPrivateMessage):
return request(
'put',
endpoint='/api/v1/private_message',
payload=payload,
response_type=user.PrivateMessageResponse
)
def get_private_messages(payload: user.GetPrivateMessages):
return request(
'get',
endpoint='/api/v1/private_message/list',
payload=payload,
response_type=user.PrivateMessagesResponse
)
def delete_private_message(payload: user.DeletePrivateMessage):
return request(
'post',
endpoint='/api/v1/private_message/delete',
payload=payload,
response_type=user.PrivateMessageResponse
)
def mark_private_message_as_read(payload: user.MarkPrivateMessageAsRead):
return request(
'post',
endpoint='/api/v1/private_message/mark_as_read',
payload=payload,
response_type=user.PrivateMessageResponse
)

3
api_v1/routes.py

@ -1,3 +0,0 @@
from .types import site, community, report, community_settings, post_hexbear, post, comment, user
from .helpers import request, ErrorResponse
from typing import Union

7
api_v1/search/__init__.py

@ -1,7 +0,0 @@
def search(payload: site.Search):
return request(
'get',
endpoint='/api/v1/search',
payload=payload,
response_type=site.SearchResponse
)

31
api_v1/site/__init__.py

@ -1,31 +0,0 @@
def get(payload: site.GetSite):
return request(
'get',
endpoint='/api/v1/site',
payload=payload,
response_type=site.GetSiteResponse
)
def create(payload: site.CreateSite):
return request(
'post',
endpoint='/api/v1/site',
payload=payload,
response_type=dict
)
def edit(payload: site.EditSite):
return request(
'put',
endpoint='/api/v1/site',
payload=payload,
response_type=dict
)
def transfer(payload: site.TransferSite):
return request(
'post',
endpoint='/api/v1/site/transfer',
payload=payload,
response_type=dict
)

15
api_v1/site/config.py

@ -1,15 +0,0 @@
def get(payload: site.GetSiteConfig):
return request(
'get',
endpoint='/api/v1/site/config',
payload=payload,
response_type=dict
)
def save(payload: site.SaveSiteConfig):
return request(
'put',
endpoint='/api/v1/site/config',
payload=payload,
response_type=dict
)

7
api_v1/site/mods.py

@ -1,7 +0,0 @@
def get(payload: site.GetSiteModerators):
return request(
'get',
endpoint='/api/v1/site/mods',
payload=payload,
response_type=site.GetSiteModeratorsResponse
)

19
api_v1/sitemod/__init__.py

@ -1,19 +0,0 @@
from typing import NamedTuple, List
from ..views import UserView
class AddSitemodRequest(NamedTuple):
user_id: int
added: bool
auth: str
class AddSitemodResponse(NamedTuple):
sitemods: List[UserView]
def add(payload: AddSitemodRequest):
return request(
'post',
endpoint='/api/v1/sitemod/add',
payload=payload,
response_type=AddSitemodResponse
)

19
api_v1/types/__init__.py

@ -1,19 +0,0 @@
from .category import *
from .comment import *
from .comment_view import *
from .community import *
from .community_view import *
from .community_settings import *
from .lib import *
from .moderator_views import *
from .post import *
from .post_hexbear import *
from .post_view import *
from .private_message_view import *
from .report import *
from .report_views import *
from .site import *
from .site_view import *
from .user import *
from .user_mention_view import *
from .user_view import *

5
api_v1/types/category.py

@ -1,5 +0,0 @@
from typing import NamedTuple, Optional, List
class Category(NamedTuple):
id: int
name: str

58
api_v1/types/comment.py

@ -1,58 +0,0 @@
from .comment_view import CommentView
from .lib import ListingType, SortType
from typing import NamedTuple, Optional, List
class CreateComment(NamedTuple):
content: str
parent_id: Optional[int]
post_id: int
form_id: Optional[str]
auth: str
class EditComment(NamedTuple):
content: str
edit_id: int
form_id: Optional[str]
auth: str
class DeleteComment(NamedTuple):
edit_id: int
deleted: bool
auth: str
class RemoveComment(NamedTuple):
edit_id: int
removed: bool
reason: Optional[str]
auth: str
class MarkCommentAsRead(NamedTuple):
edit_id: int
read: bool
auth: str
class SaveComment(NamedTuple):
comment_id: int
save: bool
auth: str
class CommentResponse(NamedTuple):
comment: CommentView
recipient_ids: List[int]
form_id: Optional[str]
class CreateCommentLike(NamedTuple):
comment_id: int
score: int
auth: str
class GetComments(NamedTuple):
type_: ListingType = 'All'
sort: SortType = 'New'
page: Optional[int] = None
limit: Optional[int] = None
community_id: Optional[int] = None
auth: Optional[str] = None
class GetCommentsResponse(NamedTuple):
comments: List[CommentView]

81
api_v1/types/comment_view.py

@ -1,81 +0,0 @@
from typing import NamedTuple, Optional, List
from datetime import datetime
class CommentView(NamedTuple):
id: int
creator_id: int
post_id: int
post_name: str
parent_id: Optional[int]
content: str
removed: bool
read: bool
published: datetime
updated: Optional[datetime]
deleted: bool
ap_id: str
local: bool
community_id: int
community_actor_id: str
community_local: bool
community_name: str
community_icon: Optional[str]
banned: bool
banned_from_community: bool
creator_actor_id: str
creator_local: bool
creator_name: str
creator_preferred_username: Optional[str]
creator_published: datetime
creator_avatar: Optional[str]
creator_tags: Optional[dict]
creator_community_tags: Optional[dict]
score: int
upvotes: int
downvotes: int
hot_rank: int
hot_rank_active: int
user_id: Optional[int]
my_vote: Optional[int]
subscribed: Optional[bool]
saved: Optional[bool]
class ReplyView(NamedTuple):
id: int
creator_id: int
post_id: int
post_name: str
parent_id: Optional[int]
content: str
removed: bool
read: bool
published: datetime
updated: Optional[datetime]
deleted: bool
ap_id: str
local: bool
community_id: int
community_actor_id: str
community_local: bool
community_name: str
community_icon: Optional[str]
banned: bool
banned_from_community: bool
creator_actor_id: str
creator_local: bool
creator_name: str
creator_preferred_username: Optional[str]
creator_avatar: Optional[str]
creator_tags: Optional[dict]
creator_community_tags: Optional[dict]
creator_published: datetime
score: int
upvotes: int
downvotes: int
hot_rank: int
hot_rank_active: int
user_id: Optional[int]
my_vote: Optional[int]
subscribed: Optional[bool]
saved: Optional[bool]
recipient_id: int

100
api_v1/types/community.py

@ -1,100 +0,0 @@
from .community_view import CommunityFollowerView, CommunityModeratorView, CommunityView
from .user_view import UserView
from .lib import SortType
from typing import NamedTuple, Optional, List
class GetCommunity(NamedTuple):
id: Optional[int] = None
name: Optional[str] = None
auth: Optional[str] = None
class GetCommunityResponse(NamedTuple):
community: CommunityView
moderators: List[CommunityModeratorView]
online: int
admins: List[UserView] # hexbear
sitemods: List[UserView] # hexbear
class CreateCommunity(NamedTuple):
name: str
title: str
description: Optional[str]
icon: Optional[str]
banner: Optional[str]
category_id: int
nsfw: bool
auth: str
class CommunityResponse(NamedTuple):
community: CommunityView
class ListCommunities(NamedTuple):
sort: SortType
page: Optional[int] = None
limit: Optional[int] = None
auth: Optional[str] = None
class ListCommunitiesResponse(NamedTuple):
communities: List[CommunityView]
class BanFromCommunity(NamedTuple):
community_id: int
user_id: int
ban: bool
remove_data: Optional[bool]
reason: Optional[str]
expires: Optional[int]
auth: str
class BanFromCommunityResponse(NamedTuple):
user: UserView
banned: bool
class AddModToCommunity(NamedTuple):
community_id: int
user_id: int
added: bool
auth: str
class AddModToCommunityResponse(NamedTuple):
moderators: List[CommunityModeratorView]
class EditCommunity(NamedTuple):
edit_id: int
title: str
description: Optional[str]
icon: Optional[str]
banner: Optional[str]
category_id: int
nsfw: bool
auth: str
class DeleteCommunity(NamedTuple):
edit_id: int
deleted: bool
auth: str
class RemoveCommunity(NamedTuple):
edit_id: int
removed: bool
reason: Optional[str]
expires: Optional[int]
auth: str
class FollowCommunity(NamedTuple):
community_id: int
follow: bool
auth: str
class GetFollowedCommunities(NamedTuple):
auth: str
class GetFollowedCommunitiesResponse(NamedTuple):
communities: List[CommunityFollowerView]
class TransferCommunity(NamedTuple):
community_id: int
user_id: int
auth: str

32
api_v1/types/community_settings.py

@ -1,32 +0,0 @@
from datetime import datetime
from typing import NamedTuple, Optional, List
class GetCommunitySettings(NamedTuple):
community_id: int
auth: Optional[str] = None
class GetCommunitySettingsResponse(NamedTuple):
read_only: bool
private: bool
post_links: bool
comment_images: int
published: datetime
allow_as_default: bool
class EditCommunitySettings(NamedTuple):
community_id: int
read_only: bool
private: bool
post_links: bool
comment_images: int
allow_as_default: bool
auth: str
class EditCommunitySettingsResponse(NamedTuple):
read_only: bool
private: bool
post_links: bool
comment_images: int
published: datetime
allow_as_default: bool

77
api_v1/types/community_view.py

@ -1,77 +0,0 @@
from typing import NamedTuple, Optional, List
from datetime import datetime
class CommunityView(NamedTuple):
id: int
name: str
title: str
icon: Optional[str]
banner: Optional[str]
description: Optional[str]
category_id: int
creator_id: int
removed: bool
published: datetime
updated: Optional[datetime]
deleted: bool
nsfw: bool
actor_id: str
local: bool
last_refreshed_at: datetime
creator_actor_id: str
creator_local: bool
creator_name: str
creator_preferred_username: Optional[str]
creator_avatar: Optional[str]
category_name: str
number_of_subscribers: int
number_of_posts: int
number_of_comments: int
hot_rank: int
user_id: Optional[int]
subscribed: Optional[bool]
class CommunityModeratorView(NamedTuple):
id: int
community_id: int
user_id: int
published: datetime
user_actor_id: str
user_local: bool
user_name: str
user_preferred_username: Optional[str]
avatar: Optional[str]
community_actor_id: str
community_local: bool
community_name: str
community_icon: Optional[str]
class CommunityFollowerView(NamedTuple):
id: int
community_id: int
user_id: int
published: datetime
user_actor_id: str
user_local: bool
user_name: str
user_preferred_username: Optional[str]
avatar: Optional[str]
community_actor_id: str
community_local: bool
community_name: str
community_icon: Optional[str]
class CommunityUserBanView(NamedTuple):
id: int
community_id: int
user_id: int
published: datetime
user_actor_id: str
user_local: bool
user_name: str
user_preferred_username: Optional[str]
avatar: Optional[str]
community_actor_id: str
community_local: bool
community_name: str
community_icon: Optional[str]

38
api_v1/types/lib.py

@ -1,38 +0,0 @@
from typing import NamedTuple, Literal
class APIError(NamedTuple):
message: str
SortType = Literal[
'Active',
'Hot',
'New',
'TopDay',
'TopWeek',
'TopMonth',
'TopYear',
'TopAll'
]
ListingType = Literal[
'All',
'Local',
'Subscribed',
'Community'
]
SearchType = Literal[
'All',
'Comments',
'Posts',
'Communities',
'Users',
'Url'
]
ListingType = Literal[
'All',
'Local',
'Subscribed',
'Community',
]

107
api_v1/types/moderator_views.py

@ -1,107 +0,0 @@
from typing import NamedTuple, Optional, List
from datetime import datetime
class ModRemovePostView(NamedTuple):
id: int
mod_user_id: int
post_id: int
reason: Optional[str]
removed: Optional[bool]
when_: datetime
mod_user_name: str
post_name: str
community_id: int
community_name: str
class ModLockPostView(NamedTuple):
id: int
mod_user_id: int
post_id: int
locked: Optional[bool]
when_: datetime
mod_user_name: str
post_name: str
community_id: int
community_name: str
class ModStickyPostView(NamedTuple):
id: int
mod_user_id: int
post_id: int
stickied: Optional[bool]
when_: datetime
mod_user_name: str
post_name: str
community_id: int
community_name: str
class ModRemoveCommentView(NamedTuple):
id: int
mod_user_id: int
comment_id: int
reason: Optional[str]
removed: Optional[bool]
when_: datetime
mod_user_name: str
comment_user_id: int
comment_user_name: str
comment_content: str
post_id: int
post_name: str
community_id: int
community_name: str
class ModRemoveCommunityView(NamedTuple):
id: int
mod_user_id: int
community_id: int
reason: Optional[str]
removed: Optional[bool]
expires: Optional[datetime]
when_: datetime
mod_user_name: str
community_name: str
class ModBanFromCommunityView(NamedTuple):
id: int
mod_user_id: int
other_user_id: int
community_id: int
reason: Optional[str]
banned: Optional[bool]
expires: Optional[datetime]
when_: datetime
mod_user_name: str
other_user_name: str
community_name: str
class ModBanView(NamedTuple):
id: int
mod_user_id: int
other_user_id: int
reason: Optional[str]
banned: Optional[bool]
expires: Optional[datetime]
when_: datetime
mod_user_name: str
other_user_name: str
class ModAddCommunityView(NamedTuple):
id: int
mod_user_id: int
other_user_id: int
community_id: int
removed: Optional[bool]
when_: datetime
mod_user_name: str
other_user_name: str
community_name: str
class ModAddView(NamedTuple):
id: int
mod_user_id: int
other_user_id: int
removed: Optional[bool]
when_: datetime
mod_user_name: str
other_user_name: str

81
api_v1/types/post.py

@ -1,81 +0,0 @@
from .comment_view import CommentView
from .community_view import CommunityModeratorView, CommunityView
from .post_view import PostView
from .user_view import UserView
from typing import NamedTuple, Optional, List
class CreatePost(NamedTuple):
name: str
url: Optional[str]
body: Optional[str]
nsfw: bool
community_id: int
auth: str
class PostResponse(NamedTuple):
post: PostView
class GetPost(NamedTuple):
id: int
auth: Optional[str] = None
class GetPostResponse(NamedTuple):
post: PostView
comments: List[CommentView]
community: CommunityView
moderators: List[CommunityModeratorView]
online: int
admins: List[UserView] # Hexbear
sitemods: List[UserView] # Hexbear
class GetPosts(NamedTuple):
type_: str
sort: str
page: Optional[int]
limit: Optional[int]
community_id: Optional[int]
community_name: Optional[str]
auth: Optional[str]
class GetPostsResponse(NamedTuple):
posts: List[PostView]
class CreatePostLike(NamedTuple):
post_id: int
score: int
auth: str
class EditPost(NamedTuple):
edit_id: int
name: str
url: Optional[str]
body: Optional[str]
nsfw: bool
auth: str
class DeletePost(NamedTuple):
edit_id: int
deleted: bool
auth: str
class RemovePost(NamedTuple):
edit_id: int
removed: bool
reason: Optional[str]
auth: str
class LockPost(NamedTuple):
edit_id: int
locked: bool
auth: str
class StickyPost(NamedTuple):
edit_id: int
stickied: bool
auth: str
class SavePost(NamedTuple):
post_id: int
save: bool
auth: str

13
api_v1/types/post_hexbear.py

@ -1,13 +0,0 @@
from typing import NamedTuple, Optional, List
from .post import PostView
class GetFeaturedPosts(NamedTuple):
auth: Optional[str] = None
class GetFeaturedPostsResponse(NamedTuple):
posts: List[PostView]
class FeaturePost(NamedTuple):
id: int
featured: bool
auth: str

53
api_v1/types/post_view.py

@ -1,53 +0,0 @@
from typing import NamedTuple, Optional, List
from datetime import datetime
class PostView(NamedTuple):
id: int
name: str
url: Optional[str]
body: Optional[str]
creator_id: int
community_id: int
removed: bool
locked: bool
published: datetime
updated: Optional[datetime]
deleted: bool
nsfw: bool
stickied: bool
featured: bool
embed_title: Optional[str]
embed_description: Optional[str]
embed_html: Optional[str]
thumbnail_url: Optional[str]
ap_id: str
local: bool
creator_actor_id: str
creator_local: bool
creator_name: str
creator_preferred_username: Optional[str]
creator_published: datetime
creator_avatar: Optional[str]
creator_tags: Optional[dict]
creator_community_tags: Optional[dict]
banned: bool
banned_from_community: bool
community_actor_id: str
community_local: bool
community_name: str
community_icon: Optional[str]
community_removed: bool
community_deleted: bool
community_nsfw: bool
number_of_comments: int
score: int
upvotes: int
downvotes: int
hot_rank: int
hot_rank_active: int
newest_activity_time: datetime
user_id: Optional[int]
my_vote: Optional[int]
subscribed: Optional[bool]
read: Optional[bool]
saved: Optional[bool]

24
api_v1/types/private_message_view.py

@ -1,24 +0,0 @@
from typing import NamedTuple, Optional, List
from datetime import datetime
class PrivateMessageView(NamedTuple):
id: int
creator_id: int
recipient_id: int
content: str
deleted: bool
read: bool
published: datetime
updated: Optional[datetime]
ap_id: str
local: bool
creator_name: str
creator_preferred_username: Optional[str]
creator_avatar: Optional[str]
creator_actor_id: str
creator_local: bool
recipient_name: str
recipient_preferred_username: Optional[str]
recipient_avatar: Optional[str]
recipient_actor_id: str
recipient_local: bool

63
api_v1/types/report.py

@ -1,63 +0,0 @@
from .report_views import CommentReportView, PostReportView
from typing import NamedTuple, Optional, List
from uuid import UUID
class CreateCommentReport(NamedTuple):
comment: int
reason: Optional[str]
auth: str
class CommentReportResponse(NamedTuple):
success: bool
class CreatePostReport(NamedTuple):
post: int
reason: Optional[str]
auth: str
class PostReportResponse(NamedTuple):
success: bool
class ListCommentReports(NamedTuple):
page: Optional[int]
limit: Optional[int]
community: int
auth: str
class ListCommentReportResponse(NamedTuple):
reports: List[CommentReportView]
class ListPostReports(NamedTuple):