mirror of https://gitlab.com/litecord/litecord.git
Implement channel categories and fix channel/role ordering
This commit is contained in:
parent
409d3fcc8f
commit
7e36e6f2c6
|
|
@ -109,3 +109,5 @@ attachments/*
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
|
|
||||||
|
|
@ -208,10 +208,10 @@
|
||||||
},
|
},
|
||||||
"hypercorn": {
|
"hypercorn": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9ab2d6b686e5a9a455433144a269d3004ad0aa079d15ed291554858a9b8dbd38",
|
"sha256:0562810a44f3e5aa0b28d42adacbbf7185f2975bb305733e6de71d6a927c8d7b",
|
||||||
"sha256:9afb874f8332645a00b77311d1975ac6690fe37e3c268d9d65f5250a3134ee29"
|
"sha256:3e9ee14deb34215907364adb62694b36abc471b8eaaf22d812eb85757e5479ad"
|
||||||
],
|
],
|
||||||
"version": "==0.6.0"
|
"version": "==0.7.0"
|
||||||
},
|
},
|
||||||
"hyperframe": {
|
"hyperframe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -318,35 +318,52 @@
|
||||||
},
|
},
|
||||||
"pillow": {
|
"pillow": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55",
|
"sha256:0804f77cb1e9b6dbd37601cee11283bba39a8d44b9ddb053400c58e0c0d7d9de",
|
||||||
"sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479",
|
"sha256:0ab7c5b5d04691bcbd570658667dd1e21ca311c62dcfd315ad2255b1cd37f64f",
|
||||||
"sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a",
|
"sha256:0b3e6cf3ea1f8cecd625f1420b931c83ce74f00c29a0ff1ce4385f99900ac7c4",
|
||||||
"sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d",
|
"sha256:0c6ce6ae03a50b0306a683696234b8bc88c5b292d4181ae365b89bd90250ab08",
|
||||||
"sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb",
|
"sha256:1454ee7297a81c8308ad61d74c849486efa1badc543453c4b90db0bf99decc1c",
|
||||||
"sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb",
|
"sha256:23efd7f83f2ad6036e2b9ef27a46df7e333de1ad9087d341d87e12225d0142b2",
|
||||||
"sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8",
|
"sha256:365c06a45712cd723ec16fa4ceb32ce46ad201eb7bbf6d3c16b063c72b61a3ed",
|
||||||
"sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72",
|
"sha256:38301fbc0af865baa4752ddae1bb3cbb24b3d8f221bf2850aad96b243306fa03",
|
||||||
"sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754",
|
"sha256:3aef1af1a91798536bbab35d70d35750bd2884f0832c88aeb2499aa2d1ed4992",
|
||||||
"sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f",
|
"sha256:3c86051d41d1c8b28b9dde08ac93e73aa842991995b12771b0af28da49086bbf",
|
||||||
"sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce",
|
"sha256:3fe0ab49537d9330c9bba7f16a5f8b02da615b5c809cdf7124f356a0f182eccd",
|
||||||
"sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601",
|
"sha256:406c856e0f6fc330322a319457d9ff6162834050cda2cf1eaaaea4b771d01914",
|
||||||
"sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5",
|
"sha256:45a619d5c1915957449264c81c008934452e3fd3604e36809212300b2a4dab68",
|
||||||
"sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734",
|
"sha256:49f90f147883a0c3778fd29d3eb169d56416f25758d0f66775db9184debc8010",
|
||||||
"sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b",
|
"sha256:504f5334bfd974490a86fef3e3b494cd3c332a8a680d2f258ca03388b40ae230",
|
||||||
"sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b",
|
"sha256:51fe9cfcd32c849c6f36ca293648f279fc5097ca8dd6e518b10df3a6a9a13431",
|
||||||
"sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1",
|
"sha256:571b5a758baf1cb6a04233fb23d6cf1ca60b31f9f641b1700bfaab1194020555",
|
||||||
"sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91",
|
"sha256:5ac381e8b1259925287ccc5a87d9cf6322a2dc88ae28a97fe3e196385288413f",
|
||||||
"sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8",
|
"sha256:6052a9e9af4a9a2cc01da4bbee81d42d33feca2bde247c4916d8274b12bb31a4",
|
||||||
"sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239",
|
"sha256:6153db744a743c0c8c91b8e3b9d40e0b13a5d31dbf8a12748c6d9bfd3ddc01ad",
|
||||||
"sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af",
|
"sha256:6fd63afd14a16f5d6b408f623cc2142917a1f92855f0df997e09a49f0341be8a",
|
||||||
"sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8",
|
"sha256:70acbcaba2a638923c2d337e0edea210505708d7859b87c2bd81e8f9902ae826",
|
||||||
"sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232",
|
"sha256:70b1594d56ed32d56ed21a7fbb2a5c6fd7446cdb7b21e749c9791eac3a64d9e4",
|
||||||
"sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a",
|
"sha256:76638865c83b1bb33bcac2a61ce4d13c17dba2204969dedb9ab60ef62bede686",
|
||||||
"sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3",
|
"sha256:7b2ec162c87fc496aa568258ac88631a2ce0acfe681a9af40842fc55deaedc99",
|
||||||
"sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062"
|
"sha256:7b403ea842b70c4fa0a4969a5d8d86e932c941095b7cda077ea68f7b98ead30b",
|
||||||
|
"sha256:7be698a28175eae5354da94f5f3dc787d5efae6aca7ad1f286a781afde6a27dd",
|
||||||
|
"sha256:7cee2cef07c8d76894ebefc54e4bb707dfc7f258ad155bd61d87f6cd487a70ff",
|
||||||
|
"sha256:7d16d4498f8b374fc625c4037742fbdd7f9ac383fd50b06f4df00c81ef60e829",
|
||||||
|
"sha256:82840783842b27933cc6388800cb547f31caf436f7e23384d456bdf5fc8dfe49",
|
||||||
|
"sha256:8755e600b33f4e8c76a590b42acc35d24f4dc801a5868519ce569b9462d77598",
|
||||||
|
"sha256:9159285ab4030c6f85e001468cb5886de05e6bd9304e9e7d46b983f7d2fad0cc",
|
||||||
|
"sha256:b50bc1780681b127e28f0075dfb81d6135c3a293e0c1d0211133c75e2179b6c0",
|
||||||
|
"sha256:b5aa19f1da16b4f5e47b6930053f08cba77ceccaed68748061b0ec24860e510c",
|
||||||
|
"sha256:bd0582f831ad5bcad6ca001deba4568573a4675437db17c4031939156ff339fa",
|
||||||
|
"sha256:cdd53acd3afb9878a2289a1b55807871f9877c81174ae0d3763e52f907131d25",
|
||||||
|
"sha256:cfd40d8a4b59f7567620410f966bb1f32dc555b2b19f82a91b147fac296f645c",
|
||||||
|
"sha256:e150c5aed6e67321edc6893faa6701581ca2d393472f39142a00e551bcd249a5",
|
||||||
|
"sha256:e3ae410089de680e8f84c68b755b42bc42c0ceb8c03dbea88a5099747091d38e",
|
||||||
|
"sha256:e403b37c6a253ebca5d0f2e5624643997aaae529dc96299162418ef54e29eb70",
|
||||||
|
"sha256:e9046e559c299b395b39ac7dbf16005308821c2f24a63cae2ab173bd6aa11616",
|
||||||
|
"sha256:ef6be704ae2bc8ad0ebc5cb850ee9139493b0fc4e81abcc240fb392a63ebc808",
|
||||||
|
"sha256:f8dc19d92896558f9c4317ee365729ead9d7bbcf2052a9a19a3ef17abbb8ac5b"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==6.0.0"
|
"version": "==6.1.0"
|
||||||
},
|
},
|
||||||
"pycparser": {
|
"pycparser": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -354,12 +371,6 @@
|
||||||
],
|
],
|
||||||
"version": "==2.19"
|
"version": "==2.19"
|
||||||
},
|
},
|
||||||
"pytoml": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
|
|
||||||
],
|
|
||||||
"version": "==0.1.20"
|
|
||||||
},
|
|
||||||
"quart": {
|
"quart": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06ad6c71f12e6f64625bebd4e591903fa653740df4b0e50de2e8722a6370011e",
|
"sha256:06ad6c71f12e6f64625bebd4e591903fa653740df4b0e50de2e8722a6370011e",
|
||||||
|
|
@ -382,13 +393,21 @@
|
||||||
],
|
],
|
||||||
"version": "==2.1.0"
|
"version": "==2.1.0"
|
||||||
},
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||||
|
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e",
|
||||||
|
"sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"
|
||||||
|
],
|
||||||
|
"version": "==0.10.0"
|
||||||
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64",
|
"sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95",
|
||||||
"sha256:f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c",
|
"sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87",
|
||||||
"sha256:fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71"
|
"sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"
|
||||||
],
|
],
|
||||||
"version": "==3.7.2"
|
"version": "==3.7.4"
|
||||||
},
|
},
|
||||||
"websockets": {
|
"websockets": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -491,35 +510,35 @@
|
||||||
},
|
},
|
||||||
"importlib-metadata": {
|
"importlib-metadata": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a9f185022cfa69e9ca5f7eabfd5a58b689894cb78a11e3c8c89398a8ccbb8e7f",
|
"sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7",
|
||||||
"sha256:df1403cd3aebeb2b1dcd3515ca062eecb5bd3ea7611f18cba81130c68707e879"
|
"sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"
|
||||||
],
|
],
|
||||||
"version": "==0.17"
|
"version": "==0.18"
|
||||||
},
|
},
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
|
"sha256:3ad685ff8512bf6dc5a8b82ebf73543999b657eded8c11803d9ba6b648986f4d",
|
||||||
"sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
|
"sha256:8bb43d1f51ecef60d81854af61a3a880555a14643691cc4b64a6ee269c78f09a"
|
||||||
],
|
],
|
||||||
"markers": "python_version > '2.7'",
|
"markers": "python_version > '2.7'",
|
||||||
"version": "==7.0.0"
|
"version": "==7.1.0"
|
||||||
},
|
},
|
||||||
"mypy": {
|
"mypy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2afe51527b1f6cdc4a5f34fc90473109b22bf7f21086ba3e9451857cf11489e6",
|
"sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a",
|
||||||
"sha256:56a16df3e0abb145d8accd5dbb70eba6c4bd26e2f89042b491faa78c9635d1e2",
|
"sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4",
|
||||||
"sha256:5764f10d27b2e93c84f70af5778941b8f4aa1379b2430f85c827e0f5464e8714",
|
"sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0",
|
||||||
"sha256:5bbc86374f04a3aa817622f98e40375ccb28c4836f36b66706cf3c6ccce86eda",
|
"sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae",
|
||||||
"sha256:6a9343089f6377e71e20ca734cd8e7ac25d36478a9df580efabfe9059819bf82",
|
"sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339",
|
||||||
"sha256:6c9851bc4a23dc1d854d3f5dfd5f20a016f8da86bcdbb42687879bb5f86434b0",
|
"sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76",
|
||||||
"sha256:b8e85956af3fcf043d6f87c91cbe8705073fc67029ba6e22d3468bfee42c4823",
|
"sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498",
|
||||||
"sha256:b9a0af8fae490306bc112229000aa0c2ccc837b49d29a5c42e088c132a2334dd",
|
"sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4",
|
||||||
"sha256:bbf643528e2a55df2c1587008d6e3bda5c0445f1240dfa85129af22ae16d7a9a",
|
"sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd",
|
||||||
"sha256:c46ab3438bd21511db0f2c612d89d8344154c0c9494afc7fbc932de514cf8d15",
|
"sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba",
|
||||||
"sha256:f7a83d6bd805855ef83ec605eb01ab4fa42bcef254b13631e451cbb44914a9b0"
|
"sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.701"
|
"version": "==0.720"
|
||||||
},
|
},
|
||||||
"mypy-extensions": {
|
"mypy-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -575,34 +594,38 @@
|
||||||
},
|
},
|
||||||
"typed-ast": {
|
"typed-ast": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b",
|
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e",
|
||||||
"sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d",
|
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e",
|
||||||
"sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a",
|
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0",
|
||||||
"sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462",
|
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c",
|
||||||
"sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee",
|
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631",
|
||||||
"sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a",
|
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4",
|
||||||
"sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4",
|
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34",
|
||||||
"sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649",
|
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b",
|
||||||
"sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a",
|
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a",
|
||||||
"sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f",
|
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233",
|
||||||
"sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7",
|
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1",
|
||||||
"sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760",
|
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36",
|
||||||
"sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18",
|
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d",
|
||||||
"sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616",
|
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a",
|
||||||
"sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd",
|
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
||||||
"sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21",
|
|
||||||
"sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93",
|
|
||||||
"sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb",
|
|
||||||
"sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7"
|
|
||||||
],
|
],
|
||||||
"version": "==1.3.5"
|
"version": "==1.4.0"
|
||||||
|
},
|
||||||
|
"typing-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95",
|
||||||
|
"sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87",
|
||||||
|
"sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"
|
||||||
|
],
|
||||||
|
"version": "==3.7.4"
|
||||||
},
|
},
|
||||||
"zipp": {
|
"zipp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d",
|
"sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a",
|
||||||
"sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"
|
"sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"
|
||||||
],
|
],
|
||||||
"version": "==0.5.1"
|
"version": "==0.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,8 +182,7 @@ async def close_channel(channel_id):
|
||||||
main_tbl = {
|
main_tbl = {
|
||||||
ChannelType.GUILD_TEXT: 'guild_text_channels',
|
ChannelType.GUILD_TEXT: 'guild_text_channels',
|
||||||
ChannelType.GUILD_VOICE: 'guild_voice_channels',
|
ChannelType.GUILD_VOICE: 'guild_voice_channels',
|
||||||
|
ChannelType.GUILD_CATEGORY: None,
|
||||||
# TODO: categories?
|
|
||||||
}[ctype]
|
}[ctype]
|
||||||
|
|
||||||
await _update_func(guild_id, channel_id)
|
await _update_func(guild_id, channel_id)
|
||||||
|
|
@ -195,11 +194,18 @@ async def close_channel(channel_id):
|
||||||
await delete_messages(channel_id)
|
await delete_messages(channel_id)
|
||||||
await guild_cleanup(channel_id)
|
await guild_cleanup(channel_id)
|
||||||
|
|
||||||
|
if main_tbl is not None:
|
||||||
await app.db.execute(f"""
|
await app.db.execute(f"""
|
||||||
DELETE FROM {main_tbl}
|
DELETE FROM {main_tbl}
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
""", channel_id)
|
""", channel_id)
|
||||||
|
|
||||||
|
if ctype == ChannelType.GUILD_CATEGORY:
|
||||||
|
await app.db.execute("""
|
||||||
|
UPDATE guild_channels SET parent_id = NULL
|
||||||
|
WHERE parent_id = $1
|
||||||
|
""", channel_id)
|
||||||
|
|
||||||
await app.db.execute("""
|
await app.db.execute("""
|
||||||
DELETE FROM guild_channels
|
DELETE FROM guild_channels
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
from quart import Blueprint, request, current_app as app, jsonify
|
from quart import Blueprint, request, current_app as app, jsonify
|
||||||
|
|
||||||
from litecord.blueprints.auth import token_check
|
from litecord.blueprints.auth import token_check
|
||||||
from litecord.snowflake import get_snowflake
|
|
||||||
from litecord.errors import BadRequest
|
|
||||||
from litecord.enums import ChannelType
|
|
||||||
from litecord.blueprints.guild.roles import gen_pairs
|
|
||||||
|
|
||||||
from litecord.schemas import (
|
|
||||||
validate, ROLE_UPDATE_POSITION, CHAN_CREATE
|
|
||||||
)
|
|
||||||
from litecord.blueprints.checks import (
|
from litecord.blueprints.checks import (
|
||||||
guild_check, guild_owner_check, guild_perm_check
|
guild_check, guild_owner_check, guild_perm_check
|
||||||
)
|
)
|
||||||
|
from litecord.blueprints.guild.roles import gen_pairs
|
||||||
|
from litecord.enums import ChannelType
|
||||||
|
from litecord.errors import BadRequest
|
||||||
|
from litecord.schemas import (
|
||||||
|
validate, CHANNEL_UPDATE_POSITION, CHAN_CREATE
|
||||||
|
)
|
||||||
|
from litecord.snowflake import get_snowflake
|
||||||
|
|
||||||
bp = Blueprint('guild_channels', __name__)
|
bp = Blueprint('guild_channels', __name__)
|
||||||
|
|
||||||
|
|
@ -52,6 +50,8 @@ async def _specific_chan_create(channel_id, ctype, **kwargs):
|
||||||
kwargs.get('bitrate', 64),
|
kwargs.get('bitrate', 64),
|
||||||
kwargs.get('user_limit', 0)
|
kwargs.get('user_limit', 0)
|
||||||
)
|
)
|
||||||
|
elif ctype == ChannelType.GUILD_CATEGORY and 'parent_id' in kwargs and kwargs['parent_id'] is not None:
|
||||||
|
raise BadRequest('Guild category cannot have a parent!')
|
||||||
|
|
||||||
|
|
||||||
async def create_guild_channel(guild_id: int, channel_id: int,
|
async def create_guild_channel(guild_id: int, channel_id: int,
|
||||||
|
|
@ -72,11 +72,13 @@ async def create_guild_channel(guild_id: int, channel_id: int,
|
||||||
# account for the first channel in a guild too
|
# account for the first channel in a guild too
|
||||||
max_pos = max_pos or 0
|
max_pos = max_pos or 0
|
||||||
|
|
||||||
|
parent_id = kwargs['parent_id'] if 'parent_id' in kwargs else None
|
||||||
|
|
||||||
# all channels go to guild_channels
|
# all channels go to guild_channels
|
||||||
await app.db.execute("""
|
await app.db.execute("""
|
||||||
INSERT INTO guild_channels (id, guild_id, name, position)
|
INSERT INTO guild_channels (id, guild_id, parent_id, name, position)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
""", channel_id, guild_id, kwargs['name'], max_pos + 1)
|
""", channel_id, guild_id, parent_id, kwargs['name'], max_pos + 1)
|
||||||
|
|
||||||
# the rest of sql magic is dependant on the channel
|
# the rest of sql magic is dependant on the channel
|
||||||
# we're creating (a text or voice or category),
|
# we're creating (a text or voice or category),
|
||||||
|
|
@ -107,7 +109,8 @@ async def create_channel(guild_id):
|
||||||
channel_type = ChannelType(channel_type)
|
channel_type = ChannelType(channel_type)
|
||||||
|
|
||||||
if channel_type not in (ChannelType.GUILD_TEXT,
|
if channel_type not in (ChannelType.GUILD_TEXT,
|
||||||
ChannelType.GUILD_VOICE):
|
ChannelType.GUILD_VOICE,
|
||||||
|
ChannelType.GUILD_CATEGORY):
|
||||||
raise BadRequest('Invalid channel type')
|
raise BadRequest('Invalid channel type')
|
||||||
|
|
||||||
new_channel_id = get_snowflake()
|
new_channel_id = get_snowflake()
|
||||||
|
|
@ -138,39 +141,37 @@ async def _chan_update_dispatch(guild_id: int, channel_id: int):
|
||||||
await app.dispatcher.dispatch_guild(guild_id, 'CHANNEL_UPDATE', chan)
|
await app.dispatcher.dispatch_guild(guild_id, 'CHANNEL_UPDATE', chan)
|
||||||
|
|
||||||
|
|
||||||
async def _do_single_swap(guild_id: int, pair: tuple):
|
async def _do_channel_updates(guild_id: int, updates: list):
|
||||||
"""Do a single channel swap, dispatching
|
"""Update channel positions, given the list of pairs to do.
|
||||||
the CHANNEL_UPDATE events for after the swap"""
|
|
||||||
pair1, pair2 = pair
|
|
||||||
channel_1, new_pos_1 = pair1
|
|
||||||
channel_2, new_pos_2 = pair2
|
|
||||||
|
|
||||||
# do the swap in a transaction.
|
|
||||||
conn = await app.db.acquire()
|
|
||||||
|
|
||||||
async with conn.transaction():
|
|
||||||
await conn.executemany("""
|
|
||||||
UPDATE guild_channels
|
|
||||||
SET position = $1
|
|
||||||
WHERE id = $2 AND guild_id = $3
|
|
||||||
""", [
|
|
||||||
(new_pos_1, channel_1, guild_id),
|
|
||||||
(new_pos_2, channel_2, guild_id)])
|
|
||||||
|
|
||||||
await app.db.release(conn)
|
|
||||||
|
|
||||||
await _chan_update_dispatch(guild_id, channel_1)
|
|
||||||
await _chan_update_dispatch(guild_id, channel_2)
|
|
||||||
|
|
||||||
|
|
||||||
async def _do_channel_swaps(guild_id: int, swap_pairs: list):
|
|
||||||
"""Swap channel pairs' positions, given the list
|
|
||||||
of pairs to do.
|
|
||||||
|
|
||||||
Dispatches CHANNEL_UPDATEs to the guild.
|
Dispatches CHANNEL_UPDATEs to the guild.
|
||||||
"""
|
"""
|
||||||
for pair in swap_pairs:
|
updated = []
|
||||||
await _do_single_swap(guild_id, pair)
|
|
||||||
|
conn = await app.db.acquire()
|
||||||
|
for pair in updates:
|
||||||
|
_id, pos = pair
|
||||||
|
|
||||||
|
async with conn.transaction():
|
||||||
|
await conn.execute("""
|
||||||
|
UPDATE guild_channels
|
||||||
|
SET position = $1
|
||||||
|
WHERE id = $2 AND guild_id = $3
|
||||||
|
""", pos, _id, guild_id)
|
||||||
|
updated.append(_id)
|
||||||
|
|
||||||
|
await app.db.release(conn)
|
||||||
|
|
||||||
|
for _id in updated:
|
||||||
|
await _chan_update_dispatch(guild_id, _id)
|
||||||
|
|
||||||
|
|
||||||
|
def _group_channel(chan):
|
||||||
|
if ChannelType(chan['type']) == ChannelType.GUILD_CATEGORY:
|
||||||
|
return 'c'
|
||||||
|
elif chan['parent_id'] is None:
|
||||||
|
return 'n'
|
||||||
|
return chan['parent_id']
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<int:guild_id>/channels', methods=['PATCH'])
|
@bp.route('/<int:guild_id>/channels', methods=['PATCH'])
|
||||||
|
|
@ -181,22 +182,42 @@ async def modify_channel_pos(guild_id):
|
||||||
await guild_owner_check(user_id, guild_id)
|
await guild_owner_check(user_id, guild_id)
|
||||||
await guild_perm_check(user_id, guild_id, 'manage_channels')
|
await guild_perm_check(user_id, guild_id, 'manage_channels')
|
||||||
|
|
||||||
# same thing as guild.roles, so we use
|
|
||||||
# the same schema and all.
|
|
||||||
raw_j = await request.get_json()
|
raw_j = await request.get_json()
|
||||||
j = validate({'roles': raw_j}, ROLE_UPDATE_POSITION)
|
j = validate({'channels': raw_j}, CHANNEL_UPDATE_POSITION)
|
||||||
j = j['roles']
|
j = j['channels']
|
||||||
|
|
||||||
channels = await app.storage.get_channel_data(guild_id)
|
channels = {int(chan['id']): chan for chan in await app.storage.get_channel_data(guild_id)}
|
||||||
|
channel_tree = {}
|
||||||
|
|
||||||
channel_positions = {chan['position']: int(chan['id'])
|
for chan in j:
|
||||||
for chan in channels}
|
conn = await app.db.acquire()
|
||||||
|
_id = int(chan['id'])
|
||||||
|
if _id in channels and 'parent_id' in chan and (chan['parent_id'] is None or chan['parent_id'] in channels):
|
||||||
|
channels[_id]['parent_id'] = chan['parent_id']
|
||||||
|
await conn.execute("""
|
||||||
|
UPDATE guild_channels
|
||||||
|
SET parent_id = $1
|
||||||
|
WHERE id = $2 AND guild_id = $3
|
||||||
|
""", chan['parent_id'], chan['id'], guild_id)
|
||||||
|
|
||||||
swap_pairs = gen_pairs(
|
await _chan_update_dispatch(guild_id, chan['id'])
|
||||||
j,
|
await app.db.release(conn)
|
||||||
channel_positions
|
|
||||||
|
for chan in channels.values():
|
||||||
|
channel_tree.setdefault(_group_channel(chan), []).append(chan)
|
||||||
|
|
||||||
|
for _key in channel_tree:
|
||||||
|
_channels = channel_tree[_key]
|
||||||
|
_channel_ids = list(map(lambda chan: int(chan['id']), _channels))
|
||||||
|
print(_key, _channel_ids)
|
||||||
|
_channel_positions = {chan['position']: int(chan['id'])
|
||||||
|
for chan in _channels}
|
||||||
|
_change_list = list(filter(lambda chan: 'position' in chan and int(chan['id']) in _channel_ids, j))
|
||||||
|
_swap_pairs = gen_pairs(
|
||||||
|
_change_list,
|
||||||
|
_channel_positions
|
||||||
)
|
)
|
||||||
|
|
||||||
await _do_channel_swaps(guild_id, swap_pairs)
|
await _do_channel_updates(guild_id, _swap_pairs)
|
||||||
|
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from typing import List, Dict, Tuple
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
from quart import Blueprint, request, current_app as app, jsonify
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
from quart import Blueprint, request, current_app as app, jsonify
|
||||||
|
|
||||||
from litecord.auth import token_check
|
from litecord.auth import token_check
|
||||||
|
|
||||||
from litecord.blueprints.checks import (
|
from litecord.blueprints.checks import (
|
||||||
guild_check, guild_perm_check
|
guild_check, guild_perm_check
|
||||||
)
|
)
|
||||||
|
from litecord.permissions import get_role_perms
|
||||||
from litecord.schemas import (
|
from litecord.schemas import (
|
||||||
validate, ROLE_CREATE, ROLE_UPDATE, ROLE_UPDATE_POSITION
|
validate, ROLE_CREATE, ROLE_UPDATE, ROLE_UPDATE_POSITION
|
||||||
)
|
)
|
||||||
|
|
||||||
from litecord.snowflake import get_snowflake
|
from litecord.snowflake import get_snowflake
|
||||||
from litecord.utils import dict_get
|
from litecord.utils import dict_get
|
||||||
from litecord.permissions import get_role_perms
|
|
||||||
|
|
||||||
DEFAULT_EVERYONE_PERMS = 104324161
|
DEFAULT_EVERYONE_PERMS = 104324161
|
||||||
log = Logger(__name__)
|
log = Logger(__name__)
|
||||||
|
|
@ -152,39 +150,33 @@ async def _role_update_dispatch(role_id: int, guild_id: int):
|
||||||
|
|
||||||
|
|
||||||
async def _role_pairs_update(guild_id: int, pairs: list):
|
async def _role_pairs_update(guild_id: int, pairs: list):
|
||||||
"""Update the roles' positions.
|
"""Update the role positions.
|
||||||
|
|
||||||
Dispatches GUILD_ROLE_UPDATE for all roles being updated.
|
Dispatches GUILD_ROLE_UPDATE for all roles being updated.
|
||||||
"""
|
"""
|
||||||
for pair in pairs:
|
updated = []
|
||||||
pair_1, pair_2 = pair
|
|
||||||
|
|
||||||
role_1, new_pos_1 = pair_1
|
|
||||||
role_2, new_pos_2 = pair_2
|
|
||||||
|
|
||||||
conn = await app.db.acquire()
|
conn = await app.db.acquire()
|
||||||
|
|
||||||
async with conn.transaction():
|
async with conn.transaction():
|
||||||
|
for pair in pairs:
|
||||||
|
_id, pos = pair
|
||||||
|
|
||||||
# update happens in a transaction
|
# update happens in a transaction
|
||||||
# so we don't fuck it up
|
# so we don't fuck it up
|
||||||
await conn.execute("""
|
await conn.execute("""
|
||||||
UPDATE roles
|
UPDATE roles
|
||||||
SET position = $1
|
SET position = $1
|
||||||
WHERE roles.id = $2
|
WHERE roles.id = $2
|
||||||
""", new_pos_1, role_1)
|
""", pos, _id)
|
||||||
|
updated.append(_id)
|
||||||
await conn.execute("""
|
|
||||||
UPDATE roles
|
|
||||||
SET position = $1
|
|
||||||
WHERE roles.id = $2
|
|
||||||
""", new_pos_2, role_2)
|
|
||||||
|
|
||||||
await app.db.release(conn)
|
await app.db.release(conn)
|
||||||
|
|
||||||
# the route fires multiple Guild Role Update.
|
for _id in updated:
|
||||||
await _role_update_dispatch(role_1, guild_id)
|
await _role_update_dispatch(_id, guild_id)
|
||||||
await _role_update_dispatch(role_2, guild_id)
|
|
||||||
|
PairList = List[Tuple[int, int]]
|
||||||
|
|
||||||
PairList = List[Tuple[Tuple[int, int], Tuple[int, int]]]
|
|
||||||
|
|
||||||
def gen_pairs(list_of_changes: List[Dict[str, int]],
|
def gen_pairs(list_of_changes: List[Dict[str, int]],
|
||||||
current_state: Dict[int, int],
|
current_state: Dict[int, int],
|
||||||
|
|
@ -192,23 +184,6 @@ def gen_pairs(list_of_changes: List[Dict[str, int]],
|
||||||
"""Generate a list of pairs that, when applied to the database,
|
"""Generate a list of pairs that, when applied to the database,
|
||||||
will generate the desired state given in list_of_changes.
|
will generate the desired state given in list_of_changes.
|
||||||
|
|
||||||
We must check if the given list_of_changes isn't overwriting an
|
|
||||||
element's (such as a role or a channel) position to an existing one,
|
|
||||||
without there having an already existing change for the other one.
|
|
||||||
|
|
||||||
Here's a pratical explanation with roles:
|
|
||||||
|
|
||||||
R1 (in position RP1) wants to be in the same position
|
|
||||||
as R2 (currently in position RP2).
|
|
||||||
|
|
||||||
So, if we did the simpler approach, list_of_changes
|
|
||||||
would just contain the preferred change: (R1, RP2).
|
|
||||||
|
|
||||||
With gen_pairs, there MUST be a (R2, RP1) in list_of_changes,
|
|
||||||
if there is, the given result in gen_pairs will be a pair
|
|
||||||
((R1, RP2), (R2, RP1)) which is then used to actually
|
|
||||||
update the roles' positions in a transaction.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
list_of_changes:
|
list_of_changes:
|
||||||
|
|
@ -224,52 +199,37 @@ def gen_pairs(list_of_changes: List[Dict[str, int]],
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list
|
list
|
||||||
List of swaps to do to achieve the preferred
|
List of changes to do to achieve the preferred
|
||||||
state given by ``list_of_changes``.
|
state given by ``list_of_changes``.
|
||||||
"""
|
"""
|
||||||
pairs = []
|
pairs = []
|
||||||
blacklist = blacklist or []
|
blacklist = blacklist or []
|
||||||
|
|
||||||
preferred_state = {element['id']: element['position']
|
preferred_state = []
|
||||||
for element in list_of_changes}
|
for chan in current_state:
|
||||||
|
preferred_state.insert(chan, current_state[chan])
|
||||||
|
|
||||||
for blacklisted_id in blacklist:
|
for blacklisted_id in blacklist:
|
||||||
preferred_state.pop(blacklisted_id)
|
if blacklisted_id in preferred_state:
|
||||||
|
preferred_state.remove(blacklisted_id)
|
||||||
|
|
||||||
|
current_state = preferred_state.copy()
|
||||||
|
|
||||||
# for each change, we must find a matching change
|
# for each change, we must find a matching change
|
||||||
# in the same list, so we can make a swap pair
|
# in the same list, so we can make a swap pair
|
||||||
for change in list_of_changes:
|
for change in list_of_changes:
|
||||||
element_1, new_pos_1 = change['id'], change['position']
|
_id, pos = change['id'], change['position']
|
||||||
|
if _id not in preferred_state:
|
||||||
# check current pairs
|
|
||||||
# so we don't repeat an element
|
|
||||||
flag = False
|
|
||||||
|
|
||||||
for pair in pairs:
|
|
||||||
if (element_1, new_pos_1) in pair:
|
|
||||||
flag = True
|
|
||||||
|
|
||||||
# skip if found
|
|
||||||
if flag:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# search if there is a role/channel in the
|
preferred_state.remove(_id)
|
||||||
# position we want to change to
|
preferred_state.insert(pos, _id)
|
||||||
element_2 = current_state.get(new_pos_1)
|
|
||||||
|
|
||||||
if element_2 is None:
|
assert len(current_state) == len(preferred_state)
|
||||||
continue
|
|
||||||
|
|
||||||
# if there is, is that existing channel being
|
for i in range(len(current_state)):
|
||||||
# swapped to another position?
|
if current_state[i] != preferred_state[i]:
|
||||||
new_pos_2 = preferred_state.get(element_2)
|
pairs.append((preferred_state[i], i))
|
||||||
|
|
||||||
# if its being swapped to leave space, add it
|
|
||||||
# to the pairs list
|
|
||||||
if new_pos_2 is not None:
|
|
||||||
pairs.append(
|
|
||||||
((element_1, new_pos_1), (element_2, new_pos_2))
|
|
||||||
)
|
|
||||||
|
|
||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ def _in_enum(enum, value) -> bool:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class LitecordValidator(Validator):
|
class LitecordValidator(Validator):
|
||||||
"""Main validator class for Litecord, containing custom types."""
|
"""Main validator class for Litecord, containing custom types."""
|
||||||
def _validate_type_username(self, value: str) -> bool:
|
def _validate_type_username(self, value: str) -> bool:
|
||||||
|
|
@ -413,6 +412,22 @@ ROLE_UPDATE_POSITION = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CHANNEL_UPDATE_POSITION = {
|
||||||
|
'channels': {
|
||||||
|
'type': 'list',
|
||||||
|
'schema': {
|
||||||
|
'type': 'dict',
|
||||||
|
'schema': {
|
||||||
|
'id': {'coerce': int},
|
||||||
|
'position': {'coerce': int},
|
||||||
|
'parent_id': {'coerce': int, 'required': False, 'nullable': True},
|
||||||
|
'lock_permissions': {'type': 'boolean', 'required': False},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MEMBER_UPDATE = {
|
MEMBER_UPDATE = {
|
||||||
'nick': {
|
'nick': {
|
||||||
'type': 'nickname', 'required': False},
|
'type': 'nickname', 'required': False},
|
||||||
|
|
|
||||||
|
|
@ -381,6 +381,9 @@ class Storage:
|
||||||
|
|
||||||
return {**row, **dict(vrow)}
|
return {**row, **dict(vrow)}
|
||||||
|
|
||||||
|
if chan_type == ChannelType.GUILD_CATEGORY:
|
||||||
|
return row
|
||||||
|
|
||||||
log.warning('unknown channel type: {}', chan_type)
|
log.warning('unknown channel type: {}', chan_type)
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
@ -478,6 +481,8 @@ class Storage:
|
||||||
res['permission_overwrites'] = await self.chan_overwrites(
|
res['permission_overwrites'] = await self.chan_overwrites(
|
||||||
channel_id)
|
channel_id)
|
||||||
|
|
||||||
|
if (res['parent_id']) is not None:
|
||||||
|
res['parent_id'] = str(res['parent_id'])
|
||||||
res['id'] = str(res['id'])
|
res['id'] = str(res['id'])
|
||||||
return res
|
return res
|
||||||
elif ctype == ChannelType.DM:
|
elif ctype == ChannelType.DM:
|
||||||
|
|
@ -561,6 +566,8 @@ class Storage:
|
||||||
|
|
||||||
# Making sure.
|
# Making sure.
|
||||||
res['id'] = str(res['id'])
|
res['id'] = str(res['id'])
|
||||||
|
if (res['parent_id']) is not None:
|
||||||
|
res['parent_id'] = str(res['parent_id'])
|
||||||
channels.append(res)
|
channels.append(res)
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue