Compare commits

..

No commits in common. "mistress" and "rewrite" have entirely different histories.

35 changed files with 475 additions and 787 deletions

169
.gitignore vendored
View file

@ -1,9 +1,162 @@
node_modules # Byte-compiled / optimized / DLL files
dist __pycache__/
package-lock.json *.py[cod]
npm-debug.log *$py.class
.nyc
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm-project.org/#use-with-ide
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env .env
.DS_Store .venv
config.json env/
*.db venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View file

@ -1,14 +1 @@
# PluriPastries # pluralcakepy
PluriPastries is a pluralkit alternative for the revolt chat platform.
It supports creating members and sending messages as one of the members.
## How to host the bot yourself.
### requirements.
SQLite on arch.
```bash
sudo pacman -S sqlite
```
You will need to install the bun runtime from their installation script.
```bash
curl -fsSL https://bun.sh/install | bash
```

BIN
bun.lockb

Binary file not shown.

View file

@ -1,20 +0,0 @@
{
"name": "pluripastries",
"version": "0.5.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "cake",
"license": "GPLv3",
"devDependencies": {
"bun-types": "^1.0.31",
"typescript": "^5.1.3",
"typescript-language-server": "^3.3.2"
},
"dependencies": {
"revolt.js": "^7.0.0-beta.9",
"shlex": "^2.1.2"
}
}

239
pdm.lock Normal file
View file

@ -0,0 +1,239 @@
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
groups = ["default"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
content_hash = "sha256:5cd19c2d8cd5b70d4f61f66164d3d27b583373b94f43afd456e68d6f9fe70c63"
[[package]]
name = "aenum"
version = "3.1.15"
summary = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants"
groups = ["default"]
files = [
{file = "aenum-3.1.15-py3-none-any.whl", hash = "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288"},
{file = "aenum-3.1.15.tar.gz", hash = "sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559"},
]
[[package]]
name = "aiohttp"
version = "3.8.6"
requires_python = ">=3.6"
summary = "Async http client/server framework (asyncio)"
groups = ["default"]
dependencies = [
"aiosignal>=1.1.2",
"async-timeout<5.0,>=4.0.0a3",
"attrs>=17.3.0",
"charset-normalizer<4.0,>=2.0",
"frozenlist>=1.1.1",
"multidict<7.0,>=4.5",
"yarl<2.0,>=1.0",
]
files = [
{file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"},
]
[[package]]
name = "aiosignal"
version = "1.3.1"
requires_python = ">=3.7"
summary = "aiosignal: a list of registered asynchronous callbacks"
groups = ["default"]
dependencies = [
"frozenlist>=1.1.0",
]
files = [
{file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
]
[[package]]
name = "async-timeout"
version = "4.0.3"
requires_python = ">=3.7"
summary = "Timeout context manager for asyncio programs"
groups = ["default"]
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
name = "attrs"
version = "23.2.0"
requires_python = ">=3.7"
summary = "Classes Without Boilerplate"
groups = ["default"]
files = [
{file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
requires_python = ">=3.7.0"
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
groups = ["default"]
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
name = "frozenlist"
version = "1.4.1"
requires_python = ">=3.8"
summary = "A list-like structure which implements collections.abc.MutableSequence"
groups = ["default"]
files = [
{file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"},
{file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"},
{file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"},
{file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"},
{file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"},
{file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"},
{file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"},
{file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"},
{file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"},
{file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"},
{file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"},
{file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"},
{file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"},
{file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"},
{file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"},
{file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"},
{file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"},
]
[[package]]
name = "idna"
version = "3.7"
requires_python = ">=3.5"
summary = "Internationalized Domain Names in Applications (IDNA)"
groups = ["default"]
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "multidict"
version = "6.0.5"
requires_python = ">=3.7"
summary = "multidict implementation"
groups = ["default"]
files = [
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"},
{file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"},
{file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"},
{file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"},
{file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"},
{file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"},
{file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"},
{file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"},
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
]
[[package]]
name = "peewee"
version = "3.17.6"
summary = "a little orm"
groups = ["default"]
files = [
{file = "peewee-3.17.6.tar.gz", hash = "sha256:cea5592c6f4da1592b7cff8eaf655be6648a1f5857469e30037bf920c03fb8fb"},
]
[[package]]
name = "revolt-py"
version = "0.2.0"
requires_python = ">=3.9"
summary = "Python wrapper for the revolt.chat API"
groups = ["default"]
dependencies = [
"aenum==3.1.*",
"aiohttp==3.8.*",
"typing-extensions>=4.4.0",
"ulid-py==1.1.*",
]
files = [
{file = "revolt.py-0.2.0-py3-none-any.whl", hash = "sha256:d40876db30ff23904e3bc46cfda62ddfff8abf4147df41d2811b9a565cefc4ae"},
{file = "revolt.py-0.2.0.tar.gz", hash = "sha256:06d54d3796df20d7382502c25060fe3eed061f8d200626f3ed171e927ce8b371"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
groups = ["default"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "ulid-py"
version = "1.1.0"
summary = "Universally Unique Lexicographically Sortable Identifier"
groups = ["default"]
files = [
{file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"},
{file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"},
]
[[package]]
name = "yarl"
version = "1.9.4"
requires_python = ">=3.7"
summary = "Yet another URL library"
groups = ["default"]
dependencies = [
"idna>=2.0",
"multidict>=4.0",
]
files = [
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
]

19
pyproject.toml Normal file
View file

@ -0,0 +1,19 @@
[project]
name = "pluralcakepy"
version = "0.1.0"
description = "Default template for PDM package"
authors = [
{name = "cake", email = "cake@cakey.me"},
]
dependencies = [
"revolt-py>=0.2.0",
"aiohttp>=3.8.6",
"peewee>=3.17.6",
]
requires-python = "==3.11.*"
readme = "README.md"
license = {text = "AGPLv3"}
[tool.pdm]
distribution = false

View file

@ -0,0 +1,19 @@
from revolt import Message
from utils import split_args
def commandHandler(message: Message, debug: bool = False) -> None:
content: str | None = message.content
arguments: list[str] = split_args(content)
prefix = arguments.pop(0)
command: str = arguments.pop(0)
if debug:
print(f"author id: {message.author.id}\
\nauthor name: {message.author.nickname}\
\ncontent: {content}\
\ncommand: {command}\
\nargs: {arguments}"
)
if message.author.bot or not content:
return

View file

@ -1,28 +0,0 @@
import { Database } from "bun:sqlite";
import MemberModel from "../models/MemberModel"
import { MemberRepo } from "../repositories/MemberRepo"
export async function avatarMemberChange(userId: string, args: string[], database: Database){
const memberRepo = new MemberRepo(database);
const userAlters : MemberModel[] = memberRepo.getAltersByUserId(userId)
let userAltersNames : string[] = [];
userAlters.forEach(alter => userAltersNames.push(alter.name));
if (args.length == 2) {
if (userAltersNames.includes(args[0])){
userAlters.forEach( alter => {
if (alter.name == args[0]) {
alter.profile_pic_url = args[1];
memberRepo.editAlter(alter)
}
})
}
return "The profile picture for the Member has been changed"
}
if (args.length != 2) {
return "Error: Insufficent number of arguments. \nexample: !ck avatar < name > < url >"
}
}

View file

@ -1,32 +0,0 @@
import { MemberRepo } from "../repositories/MemberRepo"
import MemberModel from "../models/MemberModel"
import { Database } from "bun:sqlite";
async function colorMemberChange(userId: string, args: string[], database: Database){
const memberRepo = new MemberRepo(database);
const regexHex = /^#[0-9A-F]{6}$/i;
if (args.length == 2 && regexHex.test(args[1])){
const alterName = args[0];
const alterColor = args[1];
const alters: MemberModel[] = memberRepo.getAltersByUserId(userId) // fetching alters from user.
if (alters.length == 0) return "This user has no Members." // checking if returned array is at least not 0, if len 0, then return.
let alterToEdit: MemberModel;
alters.forEach( alter => {
if (alter.name == alterName) {
alterToEdit = alter;
}
})
alterToEdit.color = alterColor;
memberRepo.editAlter(alterToEdit)
return "The Member's colour has been changed."
}
return "Error: Insufficent Arguments"
}
export default colorMemberChange

View file

@ -1,56 +0,0 @@
import { Database } from "bun:sqlite";
import { MemberRepo } from "../repositories/MemberRepo";
import MemberModel from "models/MemberModel"
async function createMember(userId: string, args: string[], database: Database){
const memberRepo = new MemberRepo(database);
const model: MemberModel = {
owner: userId,
name: args[0],
prefix: args[1],
profile_pic_url: "https://tse3.mm.bing.net/th?id=OIP.yte7rRnbCnWi1giriwTOvwHaHa&pid=15.1"
}
const userAlters: MemberModel[] = memberRepo.getAltersByUserId(userId)
let alterTags : string[] = [];
userAlters.forEach( alter => {
alterTags.push(alter.prefix)
})
let alterNames : string[] = [];
userAlters.forEach ( alter => {
alterNames.push(alter.name)
})
if (args.length == 2
&& args[1].includes("text")
&& !alterTags.includes(args[1])
&& args[1] != "text"
&& !alterNames.includes(args[0])) {
memberRepo.addAlterForUser(model);
return {message: "Member < " + model.name + " > has been succefully created", code: 0}
}
else {
if (args.length != 2){
return {message: "Error: Insufficent arguments", code: 1};
}
if (args[1] === "text"){
return {message: "Error: Tag may not be only <text>", code: 2};
}
if (!args[1].includes("text")) {
return {message: "Error: This command requires a tag that contains <text> in it", code: 3};
}
if (alterTags.includes(args[1])) {
return {message: "Error: You already have a Member with that tag", code: 4}
}
if (alterNames.includes(args[0])) {
return {message: "Error: You can only have one Member with that name", code: 5}
}
}
}
export default createMember;

View file

@ -1,24 +0,0 @@
import { MemberRepo } from "../repositories/MemberRepo"
import MemberModel from "../models/MemberModel"
import { Database } from "bun:sqlite";
export async function deleteMember(userId: string, args: string[], database: Database) {
const memberRepo = new MemberRepo(database);
let success: boolean = false;
if (args.length === 1) {
const alters: MemberModel[] = memberRepo.getAltersByUserId(userId)
alters.forEach(alter => {
if (alter.name == args[0]) {
memberRepo.delete(alter.id)
success = true;
}
})
}
if (success) {
return "Member has been deleted"
}
else {
return "Failed to delete Member"
}
}

View file

@ -1,18 +0,0 @@
export function returnHelpText() {
return "# Howdy user, welcome to pluralcake\n" +
"pluralcake is a bot that allows you to send messages as your system's Members, with a custom profile picture, tag and nickname. \n" +
"It's pretty much a work in progress, and doesn't allow you to edit or delete messages, and to send pictures." +
"The currently available commands are : \n" +
"### Members:\n" +
"- list | allows you to see your currently available Members.\n" +
"- create <'name of the Member'> <'your Member's tag'>| Allows you to add a Member\n" +
"- delete <'name of the Member'> | Deletes the choosen Member\n" +
"- name <'name of the Member'> | Changes the choosen Member's name\n" +
"- avatar <'Member's name'> <'picture url'> | Allows you to edit your Member's profile picture\n" +
"- color <'Member's name'> <'color hex'> | Allows you to change the color of your Member ( may be integrated with the future website )\n"+
"### Migrations\n"+
"- tupper < Json file attached > | Migrates your Member from tupper\n"
+"### Auto proxy\n"
+"- setAutoProxy <member name> | Sets the member to automatically send messages as\n"
+"- toggleAutoProxy | Enable or disable the autoproxy"
}

View file

@ -1,18 +0,0 @@
import { MemberRepo } from "../repositories/MemberRepo"
import { tableConstructor } from '../utils/tableConstructor'
import { Database } from "bun:sqlite";
import MemberModel from "../models/MemberModel";
async function listMember(userId: string, database: Database) {
const memberRepo = new MemberRepo(database);
const alters: MemberModel[] = memberRepo.getAltersByUserId(userId)
if (alters.length > 0 ) {
return tableConstructor(alters)
}
else {
return "No Members found for this user"
}
}
export default listMember

View file

@ -1,41 +0,0 @@
import { File } from "revolt.js"
import { Database } from "bun:sqlite"
import createMember from "./createMember"
import { avatarMemberChange } from "./avatarMemberChange"
export default async function migrateAlters(author: string, attachments: File[] | undefined, source: String, database: Database) {
// source is not utilized for now, it is put as an argument in case the bot evolves further
let file = attachments[0]
if (file.contentType === "text/plain") {
const fileResponse = await fetch(file.url)
const fileResponseBlob = await fileResponse.blob()
const textFromBlob = fileResponseBlob.text()
const jsonFromText = JSON.parse(await textFromBlob)
if (jsonFromText.tuppers === "undefined"){
return "wrong json file, be certain that this is a tupperbox migration file"
}
const tuppers = jsonFromText.tuppers
tuppers.forEach(async element => {
let name = element.name
let avatar = element.avatar_url
let brackets = element.brackets[0] + "text" + element.brackets[1]
const createResult = await createMember(author, [name, brackets], database)
if ( createResult.code === 0 ) {
await avatarMemberChange(author, [name, avatar], database)
}
let tupperStruct = {tupper: name, message: createResult}
console.log(tupperStruct)
});
return "Migration finished, check your Members to confirm"
}
else {
return "This is not a valid json file"
}
}

View file

@ -1,30 +0,0 @@
import Database from "bun:sqlite";
import { MemberRepo } from "repositories/MemberRepo";
import UserRepo from "repositories/UserRepo";
import UserModel from "models/UserModel";
function setAutoProxy(author_id: string, args: string[], database: Database) {
if (args.length != 1) {return 0}
const memberRepo = new MemberRepo(database);
const userRepo = new UserRepo(database);
const member = memberRepo.getByName(args[0], author_id);
if (member == null) {return 1}
if (
userRepo.getIdByRevoltId(author_id)
){
const user_model: UserModel = {
"revolt_id": author_id,
"autoProxy": false
}
userRepo.new(user_model)
}
const bot_user_id = userRepo.getIdByRevoltId(author_id)
userRepo.setSelectedAutoProxyId(member.id, bot_user_id)
}
export default setAutoProxy

View file

@ -1,25 +0,0 @@
import Database from "bun:sqlite"
import UserModel from "models/UserModel"
import UserRepo from "repositories/UserRepo"
function toggleAutoProxy(revolt_id: string, database: Database) {
const userRepo = new UserRepo(database)
if (userRepo.getById(
userRepo.getIdByRevoltId(revolt_id)
) == null){
const model: UserModel = {
"revolt_id": revolt_id,
"autoProxy": false,
}
userRepo.new(model)
}
const user_id = userRepo.getIdByRevoltId(revolt_id)
const is_enabled = userRepo.isProxyEnabled(
userRepo.getIdByRevoltId(revolt_id)
)
userRepo.setProxyStatus(user_id, !is_enabled)
}
export default toggleAutoProxy

View file

@ -1,40 +0,0 @@
import { Client, Message } from "revolt.js";
import { commandHandler } from "./utils/commandHandler"
import { nonCommandHandler } from "./utils/nonCommandHandler"
import { Database } from "bun:sqlite";
import config from "../config.json"
import dbInit from "utils/dbInit";
const client : Client = new Client({ eagerFetching: false });
const db = new Database(config.databaseName);
dbInit(db);
client.on("ready", async () => {
console.info(`logged in as ${client.user.username}!`)
client.user.edit(
{
status : {
text: config.prefix + " help for the help menu"
}
}
)
});
client.on("messageCreate", async (message: Message) => {
try{ if (!message.author) { await client.users.fetch(message.authorId) } }
catch(e){console.log(e)}
if (message.content === undefined) {return}
if (message.author?.bot) {return}
// checks if the message's body starts with the prefix
if (message.content.startsWith(config.prefix)) {
commandHandler(message, db, config.prefix);
}
else {
nonCommandHandler(message, db);
}
});
client.loginBot(config.botKey);

View file

@ -1,10 +0,0 @@
interface MemberModel {
id?: number;
owner: string;
prefix: string;
name: string;
profile_pic_url: string;
color?: string;
}
export default MemberModel;

View file

@ -1,8 +0,0 @@
interface UserModel {
id?: number,
revolt_id: string,
autoProxy: boolean,
autoProxyMember?: number
}
export default UserModel;

2
src/models/alter.py Normal file
View file

@ -0,0 +1,2 @@
class Alter:
...

2
src/models/user.py Normal file
View file

@ -0,0 +1,2 @@
class User:
...

View file

View file

@ -0,0 +1,28 @@
import revolt
import asyncio
import os
from commandHandler import commandHandler
class Client(revolt.Client):
async def on_message(self, message: revolt.Message) -> None:
commandHandler(message, debug=os.environ.get("debug"))
async def main():
revolt_token: str | None = os.environ.get("REVOLTTOKEN")
if not revolt_token:
raise Exception("no REVOLTTOKEN environment variable")
async with revolt.utils.client_session() as revolt_session:
revolt_client: Client = Client(
session=revolt_session,
token=revolt_token
)
await revolt_client.start()
if __name__ == "__main__":
asyncio.run(main())

View file

@ -1,79 +0,0 @@
import { Database } from 'bun:sqlite';
import MemberModel from '../models/MemberModel';
export class MemberRepo {
db : Database
constructor(database_instance: Database) {
this.db = database_instance;
}
getById(member_id: number): MemberModel {
const row: any = this.db.query("SELECT * FROM members WHERE members.id=?")
.get(member_id)
const mapped_row: MemberModel = {
"id": row.id,
"name": row.name,
"prefix": row.prefix,
"owner": row.owner,
"profile_pic_url": row.profile_pic_url
}
return mapped_row
}
getByName(name: string, user_id: string) {
if (name == null || user_id == null) {return null}
const query = this.db.query(`
SELECT *
FROM members
WHERE members.name=?
AND members.owner=?
`)
const row: any = query.get(name, user_id)
if (row == null) {return null}
const mapped_row: MemberModel = {
"id": row.id,
"name": row.name,
"prefix": row.prefix,
"owner": row.owner,
"profile_pic_url": row.profile_pic_url
}
return mapped_row
}
getAltersByUserId(userId : string) : MemberModel[] {
const query = this.db.query("SELECT * FROM members WHERE members.owner=?")
let result: MemberModel[] = query.all(userId).map( (row: any) => {
const mapped_row: MemberModel = {
"id": row.id,
"name": row.name,
"prefix": row.prefix,
"owner": row.owner,
"profile_pic_url": row.profile_pic_url
}
return mapped_row
})
return result
}
addAlterForUser(alter: MemberModel){
this.db.query("INSERT INTO members (owner, prefix, name, profile_pic_url) VALUES(?, ?, ?, ?)")
.run(alter.owner, alter.prefix, alter.name, alter.profile_pic_url)
}
editAlter(alter: MemberModel){
this.db.query("UPDATE members SET owner=?, prefix=?, name=?, profile_pic_url=?, color=? WHERE members.id=?")
.run(alter.owner, alter.prefix, alter.name, alter.profile_pic_url, alter.color, alter.id)
}
delete(alterId: number) {
this.db.query("DELETE FROM members WHERE members.id=?").run(alterId)
}
}

View file

@ -1,89 +0,0 @@
import UserModel from "models/UserModel";
import { Database } from "bun:sqlite";
class UserRepo {
db: Database
constructor(database: Database) {
this.db = database
}
getById(id) {
const row: any = this.db.query(`
SELECT *
FROM users
WHERE users.id = ?
`).get(id)
if (row != null){
return row.id
}
return null
}
new(model: UserModel) {
this.db.query(`
INSERT INTO users (revolt_id, auto_proxy) values ( ?, ? )
`)
.run(model.revolt_id, model.autoProxy)
}
delete(id) {
this.db.query(`
DELETE *
FROM users
WHERE users.id=?
`).run(id)
}
isProxyEnabled(id: number) {
if (id == null) { return false }
const row: any = this.db.query(`
SELECT auto_proxy
FROM users
WHERE users.id=?
`).get(id)
let is_enabled: boolean
is_enabled = row.auto_proxy == "TRUE" ? true : false
return is_enabled
}
setProxyStatus(id: number, status: boolean) {
const string_status = status == true ? "TRUE" : "FALSE"
this.db.query("UPDATE users SET auto_proxy=? WHERE users.id=?").run(string_status, id)
}
getIdByRevoltId(revolt_id: string): number | null {
const row: any = this.db.query(`
SELECT id
FROM users
WHERE users.revolt_id=?
`).get(revolt_id)
if (row != null) {
const id: number = row.id
return id
}
return null
}
setSelectedAutoProxyId(id: number, user_id: number){
const query = this.db.query(`
UPDATE users
SET auto_proxy_member=?
WHERE users.id=?
`).run(id, user_id)
}
getSelectedAutoProxyIdByUserId(id: number) {
const row: any = this.db.query(`
SELECT auto_proxy_member
FROM users
WHERE users.id=?
`).get(id)
return row.auto_proxy_member
}
}
export default UserRepo

4
src/utils.py Normal file
View file

@ -0,0 +1,4 @@
from shlex import split
def split_args(message: str) -> list[str]:
return split(message)

View file

@ -1,17 +0,0 @@
import UserRepo from "repositories/UserRepo";
import { Database } from "bun:sqlite";
function checkAutoProxy
(
userId: string,
database: Database
): boolean
{
const repo = new UserRepo(database);
const user_id = repo.getIdByRevoltId(userId);
const is_enabled = repo.isProxyEnabled(user_id);
return is_enabled
}
export default checkAutoProxy;

View file

@ -1,73 +0,0 @@
import { Message } from "revolt.js"
import { Database } from "bun:sqlite"
import { split } from "shlex"
import { returnHelpText } from "../commands/help"
import listMember from "../commands/listMember"
import createMember from "../commands/createMember"
import { deleteMember } from "../commands/deleteMember"
import { avatarMemberChange } from "../commands/avatarMemberChange"
import colorMemberChange from "../commands/colorMemberChange"
import migrationCommand from "../commands/migrateMember"
import toggleAutoProxy from "commands/toggleAutoProxy"
import setAutoProxy from "commands/setAutoProxy"
export async function commandHandler(message : Message, db: Database, _prefix : String) {
let args : string[] = split(message.content);
args.shift()
const command = args[0]
args.shift()
switch(command) {
case "help" : {
await message.reply(returnHelpText());
break;
}
case "list" : {
await message.reply(await listMember(message.author.id, db));
break;
}
case "create" : {
const commandResponse = await createMember(message.author.id, args, db);
await message.reply (commandResponse.message)
break
}
case "delete" : {
await message.reply(await deleteMember(message.author.id, args, db))
break
}
case "avatar" : {
await message.reply(await avatarMemberChange(message.author.id, args, db));
break
}
case "setAutoProxy": {
const status = setAutoProxy(message.author.id, args, db);
switch(status) {
case 0: {
await message.reply("not enough arguments")
break
}
case 1: {
message.reply("no member found with that member name")
break
}
}
break
}
case "toggleAutoProxy": {
toggleAutoProxy(message.author.id, db)
await message.reply("toggled")
break
}
case "color" : {
await message.reply(await colorMemberChange(message.author.id, args, db));
break
}
case "tupper" : {
await message.reply(await migrationCommand(message.author.id, message.attachments, "placeholder", db))
break
}
}
}

View file

@ -1,17 +0,0 @@
import { Database } from "bun:sqlite";
function dbInit(database: Database){
database.run("CREATE TABLE IF NOT EXISTS members (id INTEGER PRIMARY KEY AUTOINCREMENT, owner TEXT, prefix TEXT, name TEXT, profile_pic_url TEXT, color TEXT)");
database.run(`
CREATE TABLE IF NOT EXISTS users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
revolt_id TEXT NOT NULL,
auto_proxy BOOLEAN NOT NULL,
auto_proxy_member INTEGER,
FOREIGN KEY (auto_proxy_member)
REFERENCES members (id))
`);
}
export default dbInit

View file

@ -1,16 +0,0 @@
import { Message } from "revolt.js"
import { Database } from "bun:sqlite";
import sendAsMember from "./sendAsMemberHandler";
import checkAutoProxy from "./checkAutoProxy";
import sendAsAutoProxyMember from "./sendAsAutoProxyMember";
export async function nonCommandHandler(message : Message, database: Database){
const should_auto_proxy = checkAutoProxy(message.author.id, database)
if (should_auto_proxy) {
await sendAsAutoProxyMember(message.author.id, message, database)
}
else {
await sendAsMember(message, database);
}
}

View file

@ -1,46 +0,0 @@
import Database from "bun:sqlite";
import { Message } from "revolt.js"
import { MemberRepo } from "repositories/MemberRepo";
import UserRepo from "repositories/UserRepo";
import MemberModel from "models/MemberModel";
async function sendAsAutoProxyMember(revolt_uid: string, message: Message, database: Database) {
// assumes that every check has been done previously
const userRepo = new UserRepo(database)
const memberRepo = new MemberRepo(database)
const user_id = userRepo.getIdByRevoltId(revolt_uid)
const auto_proxy_id = userRepo.getSelectedAutoProxyIdByUserId(user_id)
const member_to_proxy_as: MemberModel = memberRepo.getById(auto_proxy_id)
const replyIds: string[] | undefined = message.replyIds;
let replies: any[] = [];
if (replyIds !== undefined) {
replyIds.forEach( replyId => {
replies.push({
id: replyId,
mention: false
})
})
}
try{
await message.channel.sendMessage({
content: message.content,
masquerade: {
name: member_to_proxy_as.name,
avatar: member_to_proxy_as.profile_pic_url
},
replies: replies
})
await message.delete();
}
catch(e){
console.log(e)
await message.channel.sendMessage("Error: PluralCake requires at least these permissions: \n- Masquerade permissions. \n- Message editing permissions.")
}
}
export default sendAsAutoProxyMember

View file

@ -1,50 +0,0 @@
import { Message } from "revolt.js"
import { Database } from "bun:sqlite";
import { MemberRepo } from "../repositories/MemberRepo"
import MemberModel from "../models/MemberModel"
export default async function sendAsMember(message: Message, database: Database) {
const memberRepo: MemberRepo = new MemberRepo(database);
let members: MemberModel[] = memberRepo.getAltersByUserId(message.author.id);
members.forEach( async alter => {
const pre_prefix = alter.prefix.split("text");
if (
message.content.startsWith(pre_prefix[0])
&& message.content.endsWith(pre_prefix[1])
&& message.content.length > 1
){
let actualContent: string = message.content;
actualContent = actualContent.slice(pre_prefix[0].length, actualContent.length - pre_prefix[1].length)
const replyIds: string[] | undefined = message.replyIds;
let replies: any[] = [];
if (replyIds !== undefined) {
replyIds.forEach( replyId => {
replies.push({
id: replyId,
mention: false
})
})
}
try{
await message.channel.sendMessage({
content: actualContent,
masquerade: {
name: alter.name ,
avatar: alter.profile_pic_url,
color: alter.color
},
replies: replies
})
await message.delete();
}
catch(e){
console.log(e)
await message.channel.sendMessage("Error: PluralCake requires at least these permissions: \n- Masquerade permissions. \n- Message editing permissions.")
}
}
})
}

View file

@ -1,11 +0,0 @@
import { AlterModel } from '../models/alterModel'
export function tableConstructor(data: AlterModel[]){
const header = "| Name | tag |\n|----|----|\n"
let body : string = "";
data.forEach(alter => {
body = body +
"|" + alter.name + "|" + alter.prefix + "|\n"
})
body = body.slice(0, body.length - 2)
return header + body
}

0
tests/__init__.py Normal file
View file

View file

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule" : true,
"target": "es6",
"types": [
"bun-types"
],
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": "./src"
},
"lib": ["es2015"]
}