Compare commits
5 commits
master
...
ryuko_mast
Author | SHA1 | Date | |
---|---|---|---|
|
da9ea20a9d | ||
|
d1c536e792 | ||
|
d562e2ab88 | ||
|
8e05468cb7 | ||
|
86eb6a7ad2 |
63 changed files with 1741 additions and 4647 deletions
|
@ -1,8 +1 @@
|
|||
**/__pycache__/
|
||||
.git/
|
||||
.github/
|
||||
.gitignore
|
||||
.dockerignore
|
||||
|
||||
**/config.py
|
||||
**/data/
|
||||
config.py
|
||||
|
|
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
|
@ -1,18 +0,0 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
open-pull-requests-limit: 5
|
||||
reviewers:
|
||||
- marysaka
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /
|
||||
open-pull-requests-limit: 5
|
||||
reviewers:
|
||||
- marysaka
|
||||
schedule:
|
||||
interval: weekly
|
2
.github/reviewers.yml
vendored
2
.github/reviewers.yml
vendored
|
@ -1,2 +0,0 @@
|
|||
default:
|
||||
- TSRBerry
|
59
.github/workflows/formatting.yml
vendored
59
.github/workflows/formatting.yml
vendored
|
@ -1,59 +0,0 @@
|
|||
name: Check formatting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
black:
|
||||
name: Python Black
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install black
|
||||
run: pip install black
|
||||
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.name github-actions[bot]
|
||||
git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
|
||||
- name: Run black
|
||||
run: python -m black .
|
||||
|
||||
- name: Check if files have been modified
|
||||
id: mod_check
|
||||
run: |
|
||||
[[ $(git status -s | wc -l) -le 1 ]] \
|
||||
&& echo "is-dirty=false" >> "$GITHUB_OUTPUT" \
|
||||
|| echo "is-dirty=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.mod_check.outputs.is-dirty == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git commit -m "Apply black formatting"
|
||||
git push
|
||||
|
||||
fork-black:
|
||||
name: Python Black
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: psf/black@stable
|
||||
|
41
.github/workflows/mako.yml
vendored
41
.github/workflows/mako.yml
vendored
|
@ -1,41 +0,0 @@
|
|||
name: Mako
|
||||
on:
|
||||
discussion:
|
||||
types: [created, edited, answered, unanswered, category_changed]
|
||||
discussion_comment:
|
||||
types: [created, edited]
|
||||
gollum:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
issues:
|
||||
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]
|
||||
|
||||
jobs:
|
||||
tasks:
|
||||
name: Run Ryujinx tasks
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
discussions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
if: github.event_name == 'pull_request_target'
|
||||
with:
|
||||
# Ensure we pin the source origin as pull_request_target run under forks.
|
||||
fetch-depth: 0
|
||||
repository: Ryujinx/ryuko-ng
|
||||
ref: master
|
||||
|
||||
- name: Run Mako command
|
||||
uses: Ryujinx/Ryujinx-Mako@master
|
||||
with:
|
||||
command: exec-ryujinx-tasks
|
||||
args: --event-name "${{ github.event_name }}" --event-path "${{ github.event_path }}" -w "${{ github.workspace }}" "${{ github.repository }}" "${{ github.run_id }}"
|
||||
app_id: ${{ secrets.MAKO_APP_ID }}
|
||||
private_key: ${{ secrets.MAKO_PRIVATE_KEY }}
|
||||
installation_id: ${{ secrets.MAKO_INSTALLATION_ID }}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -98,7 +98,7 @@ files/*
|
|||
*.ttf
|
||||
|
||||
priv-*
|
||||
config.py
|
||||
|
||||
# Prevent data files from being committed
|
||||
data/
|
||||
robocop_ng/config.py
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,12 +1,12 @@
|
|||
FROM python:3.10-alpine
|
||||
FROM python:alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
COPY requirements.txt ./
|
||||
RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev cargo && pip install --no-cache-dir -r requirements.txt && apk del gcc musl-dev python3-dev libffi-dev openssl-dev cargo
|
||||
|
||||
RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev cargo && pip install --no-cache-dir poetry && poetry config virtualenvs.create false && poetry install --no-root --no-interaction --no-ansi -vvv && apk del gcc musl-dev python3-dev libffi-dev openssl-dev cargo
|
||||
COPY . .
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
WORKDIR /usr/src/app/robocop_ng
|
||||
|
||||
CMD [ "python", "-m", "robocop_ng", "/state" ]
|
||||
CMD [ "python", "./__init__.py" ]
|
||||
|
|
54
README.md
54
README.md
|
@ -1,43 +1,22 @@
|
|||
# ryuko-ng
|
||||
# robocop-ng
|
||||
|
||||
Discord bot for handling Ryujinx moderation tasks and such, (n)ext-(g)en rewrite of Robocop
|
||||
Next-gen rewrite of Kurisu/Robocop bot used on ReSwitched bot with discord.py rewrite, designed to be relatively clean, consistent and un-bloated.
|
||||
|
||||
Code is based on https://github.com/reswitched/robocop-ng.
|
||||
Code is based on https://gitlab.com/a/dpybotbase and https://github.com/916253/Kurisu-Reswitched.
|
||||
|
||||
---
|
||||
|
||||
## How to migrate from discord.py v1 to v2
|
||||
|
||||
As of 18.08.2022 this repo is based on discord.py v2.
|
||||
|
||||
Only changes needed are updating your cogs and ensuring that all privileged intents are enabled for your bot.
|
||||
|
||||
You can find the privileged intents guide here: https://discordpy.readthedocs.io/en/latest/intents.html?highlight=intents#privileged-intents
|
||||
|
||||
You can see the migration instructions for your cogs here: https://discordpy.readthedocs.io/en/latest/migrating.html
|
||||
|
||||
---
|
||||
|
||||
## How to run
|
||||
|
||||
- Copy `robocop_ng/config_template.py` to `robocop_ng/config.py` and **configure all necessary parts for your server**.
|
||||
- Enable all privileged intents ([guide here](https://discordpy.readthedocs.io/en/latest/intents.html?highlight=intents#privileged-intents)) for the bot. You don't need to give Discord your passport as Ryuko-NG is not designed to run in >1 guild at once, let alone >100.
|
||||
- Add the bot to your guild. There are many resources about this online.
|
||||
- Copy `robocop_ng/config_template.py` to `robocop_ng/config.py`, configure all necessary parts to your server.
|
||||
- Enable the `Server Members` privileged intent ([guide here](https://discordpy.readthedocs.io/en/latest/intents.html?highlight=intents#privileged-intents)) for the bot. You don't need to give Discord your passport as Robocop-NG is not designed to run at >1 guild at once, let alone >100.
|
||||
- (obviously) Add the bot to your guild. Many resources about this online.
|
||||
- If you haven't already done this already, **move the bot's role above the roles it'll need to manage, or else it won't function properly**, this is especially important for verification as it doesn't work otherwise.
|
||||
- If you're moving from Kurisu or Robocop: Follow [Tips for people moving from Kurisu/Robocop](https://github.com/Ryujinx/ryuko-ng#tips-for-people-moving-from-kurisurobocop) below.
|
||||
|
||||
### Running with docker
|
||||
|
||||
- `docker build . -t robocopng`
|
||||
- Assuming your robocop-ng repo is on `~/docker/`: `docker run --restart=always -v ~/docker/robocop-ng:/usr/src/app --name robocop_ng robocopng:latest`
|
||||
|
||||
For updates, run `git pull;docker rm -f robocop_ng` then run the two commands above again.
|
||||
|
||||
### Running manually
|
||||
|
||||
- Install python3.8+.
|
||||
- Install dependencies with [poetry](https://python-poetry.org/) using `poetry install`.
|
||||
- Run `robocop_ng/__main__.py` (`cd robocop_ng;python3 __main__.py`).
|
||||
- Install python3.6+.
|
||||
- Install python dependencies (`pip3 install -Ur requirements.txt`, you might need to put `sudo -H` before that). You can also install with [poetry](https://python-poetry.org/) with just `poetry install`.
|
||||
- If you're moving from Kurisu or Robocop: Follow `Tips for people moving from Kurisu/Robocop` below.
|
||||
- Run `robocop_ng/__init__.py` (`cd robocop_ng;python3 __init__.py`). Alternatively, if you did `poetry install`, run `python3 -m robocop_ng` in the same directory as your config files.
|
||||
|
||||
To keep the bot running, you might want to use pm2 or a systemd service.
|
||||
|
||||
|
@ -47,7 +26,7 @@ To keep the bot running, you might want to use pm2 or a systemd service.
|
|||
|
||||
If you're moving from Kurisu/Robocop, and want to preserve your data, you'll want to do the following steps:
|
||||
|
||||
- Copy your `data` folder over into the `robocop_ng` folder.
|
||||
- Copy your `data` folder over.
|
||||
- Rename your `data/warnsv2.json` file to `data/userlog.json`.
|
||||
- Edit `data/restrictions.json` and replace role names (`"Muted"` etc) with role IDs (`526500080879140874` etc). Make sure to have it as int, not as str (don't wrap role id with `"` or `'`).
|
||||
|
||||
|
@ -55,7 +34,7 @@ If you're moving from Kurisu/Robocop, and want to preserve your data, you'll wan
|
|||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. If you're unsure if your PR would be merged or not, ask in the [Ryujinx discord guild](https://discord.gg/ryujinx) pinging Berry.
|
||||
Contributions are welcome. If you're unsure if your PR would be merged or not, either open an issue, ask on ReSwitched off-topic pinging ave or DM ave.
|
||||
|
||||
You're expected to use [black](https://github.com/psf/black) for code formatting before sending a PR. Simply install it with pip (`pip3 install black`), and run it with `black .`.
|
||||
|
||||
|
@ -63,14 +42,13 @@ You're expected to use [black](https://github.com/psf/black) for code formatting
|
|||
|
||||
## Credits
|
||||
|
||||
Ryuko-NG is a fork of [Robocop-NG](https://github.com/reswitched/robocop-ng) that is mainly maintained by [@TSRBerry](https://github.com/TSRBerry) and [@marysaka](https://github.com/marysaka).
|
||||
Robocop-NG was initially developed by @aveao and @tumGER. It is currently maintained by @aveao. Similarly, the official robocop-ng on reswitched discord guild is hosted by @aveao too.
|
||||
|
||||
[Robocop-NG](https://github.com/reswitched/robocop-ng) was initially developed by [@aveao](https://github.com/aveao) and @tumGER. It is currently maintained by [@aveao](https://github.com/aveao). Similarly, the official robocop-ng on the ReSwitched discord guild is hosted by [@aveao](https://github.com/aveao) too.
|
||||
|
||||
I would like to thank the following, in no particular order:
|
||||
I (ave) would like to thank the following, in no particular order:
|
||||
|
||||
- ReSwitched community, for being amazing
|
||||
- ihaveamac/ihaveahax and f916253 for the original kurisu/robocop
|
||||
- misson20000 for adding in reaction removal feature and putting up with my many BS requests on PR reviews
|
||||
- linuxgemini for helping out with Yubico OTP revocation code (which is based on their work)
|
||||
- Everyone who contributed to robocop-ng/ryuko-ng in any way (reporting a bug, sending a PR, forking and hosting their own at their own guild, etc).
|
||||
- Everyone who contributed to robocop-ng in any way (reporting a bug, sending a PR, forking and hosting their own at their own guild, etc).
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
(import (fetchTarball
|
||||
"https://github.com/edolstra/flake-compat/archive/master.tar.gz") {
|
||||
src = builtins.fetchGit ./.;
|
||||
}).defaultNix
|
175
flake.lock
175
flake.lock
|
@ -1,175 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703863825,
|
||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1720535198,
|
||||
"narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems_3",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724417163,
|
||||
"narHash": "sha256-gD0N0pnKxWJcKtbetlkKOIumS0Zovgxx/nMfOIJIzoI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "7619e43c2b48c29e24b88a415256f09df96ec276",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "systems",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1719749022,
|
||||
"narHash": "sha256-ddPKHcqaKCIFSFc/cvxS14goUhCOAwsM1PbMr0ZtHMg=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "8df5ff62195d4e67e2264df0b7f5e8c9995fd0bd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
133
flake.nix
133
flake.nix
|
@ -1,133 +0,0 @@
|
|||
{
|
||||
description = "Application packaged using poetry2nix";
|
||||
|
||||
inputs = {
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
||||
poetry2nix = {
|
||||
url = "github:nix-community/poetry2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, poetry2nix }@inputs:
|
||||
|
||||
let
|
||||
ryuko_overlay = final: prev:
|
||||
let
|
||||
pkgs = import nixpkgs { system = prev.system; };
|
||||
poetry2nix = inputs.poetry2nix.lib.mkPoetry2Nix { inherit pkgs; };
|
||||
in {
|
||||
ryuko-ng = with final;
|
||||
poetry2nix.mkPoetryApplication rec {
|
||||
projectDir = self;
|
||||
src = projectDir;
|
||||
overrides = [ poetry2nix.defaultPoetryOverrides (self: super: {
|
||||
cryptography = super.cryptography.overridePythonAttrs (old: {
|
||||
cargoDeps = pkgs.rustPlatform.fetchCargoTarball {
|
||||
src = old.src;
|
||||
sourceRoot = "${old.pname}-${old.version}/src/rust";
|
||||
name = "${old.pname}-${old.version}";
|
||||
# cryptography-42.0.7
|
||||
sha256 = "sha256-wAup/0sI8gYVsxr/vtcA+tNkBT8wxmp68FPbOuro1E4=";
|
||||
};
|
||||
});
|
||||
}) ];
|
||||
};
|
||||
};
|
||||
in flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ self.overlays."${system}" ];
|
||||
};
|
||||
in {
|
||||
packages = {
|
||||
default = self.packages.${system}.ryuko-ng;
|
||||
ryuko-ng = pkgs.ryuko-ng;
|
||||
};
|
||||
|
||||
overlays = ryuko_overlay;
|
||||
|
||||
nixosModules.ryuko-ng = { pkgs, lib, config, ... }: {
|
||||
options = let inherit (lib) mkEnableOption mkOption types;
|
||||
in {
|
||||
services.ryuko-ng = {
|
||||
enable = mkEnableOption (lib.mdDoc "ryuko-ng discord bot");
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
inherit (lib) mkIf;
|
||||
cfg = config.services.ryuko-ng;
|
||||
in mkIf cfg.enable {
|
||||
nixpkgs.overlays = [ self.overlays."${system}" ];
|
||||
|
||||
systemd.services.ryuko-ng = {
|
||||
description = "ryuko-ng bot";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = ''
|
||||
${pkgs.ryuko-ng.dependencyEnv}/bin/python3 -m robocop_ng /var/lib/ryuko-ng
|
||||
'';
|
||||
|
||||
serviceConfig = rec {
|
||||
Type = "simple";
|
||||
User = "ryuko-ng";
|
||||
Group = "ryuko-ng";
|
||||
StateDirectory = "ryuko-ng";
|
||||
StateDirectoryMode = "0700";
|
||||
CacheDirectory = "ryuko-ng";
|
||||
CacheDirectoryMode = "0700";
|
||||
UMask = "0077";
|
||||
WorkingDirectory = "/var/lib/ryuko-ng";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users.ryuko-ng = {
|
||||
group = "ryuko-ng";
|
||||
isSystemUser = true;
|
||||
};
|
||||
extraUsers.ryuko-ng.uid = 989;
|
||||
|
||||
groups.ryuko-ng = { };
|
||||
extraGroups.ryuko-ng = {
|
||||
name = "ryuko-ng";
|
||||
gid = 987;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = [ self.packages.${system}.ryuko-ng ];
|
||||
packages = [ pkgs.poetry ];
|
||||
};
|
||||
|
||||
checks = {
|
||||
vmTest = with import (nixpkgs + "/nixos/lib/testing-python.nix") {
|
||||
inherit system;
|
||||
};
|
||||
makeTest {
|
||||
name = "ryuko-ng nixos module testing ${system}";
|
||||
|
||||
nodes = {
|
||||
client = { ... }: {
|
||||
imports = [ self.nixosModules.${system}.ryuko-ng ];
|
||||
|
||||
services.ryuko-ng.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
client.wait_for_unit("ryuko-ng.service")
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
formatter = pkgs.nixfmt;
|
||||
});
|
||||
}
|
882
poetry.lock
generated
882
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "robocop_ng"
|
||||
version = "1.0.1"
|
||||
version = "1.0.0"
|
||||
description = "Discord bot for handling ReSwitched moderation tasks and such, (n)ext-(g)en rewrite of Robocop"
|
||||
authors = ["ReSwitched Team"]
|
||||
license = "MIT"
|
||||
|
@ -8,16 +8,16 @@ readme = "README.md"
|
|||
repository = "https://github.com/reswitched/robocop-ng"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
python = "^3.8"
|
||||
"discord.py" = "^1.7.3"
|
||||
python-dateutil = "^2.8.2"
|
||||
humanize = "^4.8.0"
|
||||
humanize = "^3.10.0"
|
||||
parsedatetime = "^2.6"
|
||||
aiohttp = "^3.9.3"
|
||||
gidgethub = "^5.3.0"
|
||||
"discord.py" = "^2.3.2"
|
||||
aiohttp = "^3.7.4"
|
||||
gidgethub = "^5.0.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
|
216
requirements.txt
Normal file
216
requirements.txt
Normal file
|
@ -0,0 +1,216 @@
|
|||
aiohttp==3.7.4.post0; python_version >= "3.6" \
|
||||
--hash=sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5 \
|
||||
--hash=sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8 \
|
||||
--hash=sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95 \
|
||||
--hash=sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290 \
|
||||
--hash=sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f \
|
||||
--hash=sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809 \
|
||||
--hash=sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe \
|
||||
--hash=sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287 \
|
||||
--hash=sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc \
|
||||
--hash=sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87 \
|
||||
--hash=sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0 \
|
||||
--hash=sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970 \
|
||||
--hash=sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f \
|
||||
--hash=sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde \
|
||||
--hash=sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c \
|
||||
--hash=sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8 \
|
||||
--hash=sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f \
|
||||
--hash=sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5 \
|
||||
--hash=sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf \
|
||||
--hash=sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df \
|
||||
--hash=sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213 \
|
||||
--hash=sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4 \
|
||||
--hash=sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009 \
|
||||
--hash=sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5 \
|
||||
--hash=sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013 \
|
||||
--hash=sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16 \
|
||||
--hash=sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5 \
|
||||
--hash=sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b \
|
||||
--hash=sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd \
|
||||
--hash=sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439 \
|
||||
--hash=sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22 \
|
||||
--hash=sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a \
|
||||
--hash=sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb \
|
||||
--hash=sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb \
|
||||
--hash=sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9 \
|
||||
--hash=sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe \
|
||||
--hash=sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf
|
||||
async-timeout==3.0.1; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \
|
||||
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
|
||||
attrs==21.2.0; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \
|
||||
--hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb
|
||||
cffi==1.14.6; python_version >= "3.6" \
|
||||
--hash=sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c \
|
||||
--hash=sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99 \
|
||||
--hash=sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819 \
|
||||
--hash=sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20 \
|
||||
--hash=sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224 \
|
||||
--hash=sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7 \
|
||||
--hash=sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33 \
|
||||
--hash=sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534 \
|
||||
--hash=sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a \
|
||||
--hash=sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5 \
|
||||
--hash=sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca \
|
||||
--hash=sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218 \
|
||||
--hash=sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f \
|
||||
--hash=sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872 \
|
||||
--hash=sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195 \
|
||||
--hash=sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d \
|
||||
--hash=sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b \
|
||||
--hash=sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb \
|
||||
--hash=sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a \
|
||||
--hash=sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e \
|
||||
--hash=sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5 \
|
||||
--hash=sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf \
|
||||
--hash=sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69 \
|
||||
--hash=sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56 \
|
||||
--hash=sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c \
|
||||
--hash=sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762 \
|
||||
--hash=sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771 \
|
||||
--hash=sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a \
|
||||
--hash=sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0 \
|
||||
--hash=sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e \
|
||||
--hash=sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346 \
|
||||
--hash=sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc \
|
||||
--hash=sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd \
|
||||
--hash=sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc \
|
||||
--hash=sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548 \
|
||||
--hash=sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156 \
|
||||
--hash=sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d \
|
||||
--hash=sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e \
|
||||
--hash=sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c \
|
||||
--hash=sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202 \
|
||||
--hash=sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f \
|
||||
--hash=sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87 \
|
||||
--hash=sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728 \
|
||||
--hash=sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2 \
|
||||
--hash=sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd
|
||||
chardet==4.0.0; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \
|
||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa
|
||||
cryptography==3.4.7; python_version >= "3.6" \
|
||||
--hash=sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1 \
|
||||
--hash=sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250 \
|
||||
--hash=sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2 \
|
||||
--hash=sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6 \
|
||||
--hash=sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959 \
|
||||
--hash=sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d \
|
||||
--hash=sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca \
|
||||
--hash=sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873 \
|
||||
--hash=sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d \
|
||||
--hash=sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177 \
|
||||
--hash=sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9 \
|
||||
--hash=sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713
|
||||
discord.py==1.7.3; python_full_version >= "3.5.3" \
|
||||
--hash=sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c \
|
||||
--hash=sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408
|
||||
gidgethub==5.0.1; python_version >= "3.6" \
|
||||
--hash=sha256:67245e93eb0918b37df038148af675df43b62e832c529d7f859f6b90d9f3e70d \
|
||||
--hash=sha256:3efbd6998600254ec7a2869318bd3ffde38edc3a0d37be0c14bc46b45947b682
|
||||
humanize==3.10.0; python_version >= "3.6" \
|
||||
--hash=sha256:aab7625d62dd5e0a054c8413a47d1fa257f3bdd8e9a2442c2fe36061bdd1d9bf \
|
||||
--hash=sha256:b2413730ce6684f85e0439a5b80b8f402e09f03e16ab8023d1da758c6ff41148
|
||||
idna==3.2; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \
|
||||
--hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3
|
||||
multidict==5.1.0; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f \
|
||||
--hash=sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf \
|
||||
--hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \
|
||||
--hash=sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d \
|
||||
--hash=sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d \
|
||||
--hash=sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da \
|
||||
--hash=sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224 \
|
||||
--hash=sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26 \
|
||||
--hash=sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6 \
|
||||
--hash=sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76 \
|
||||
--hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \
|
||||
--hash=sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f \
|
||||
--hash=sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348 \
|
||||
--hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \
|
||||
--hash=sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9 \
|
||||
--hash=sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37 \
|
||||
--hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \
|
||||
--hash=sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632 \
|
||||
--hash=sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952 \
|
||||
--hash=sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79 \
|
||||
--hash=sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456 \
|
||||
--hash=sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7 \
|
||||
--hash=sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635 \
|
||||
--hash=sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a \
|
||||
--hash=sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea \
|
||||
--hash=sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656 \
|
||||
--hash=sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3 \
|
||||
--hash=sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93 \
|
||||
--hash=sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647 \
|
||||
--hash=sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d \
|
||||
--hash=sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8 \
|
||||
--hash=sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1 \
|
||||
--hash=sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841 \
|
||||
--hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \
|
||||
--hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 \
|
||||
--hash=sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359 \
|
||||
--hash=sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5
|
||||
parsedatetime==2.6 \
|
||||
--hash=sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b \
|
||||
--hash=sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455
|
||||
pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" \
|
||||
--hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 \
|
||||
--hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0
|
||||
pyjwt==2.1.0; python_version >= "3.6" \
|
||||
--hash=sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1 \
|
||||
--hash=sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130
|
||||
python-dateutil==2.8.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") \
|
||||
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
|
||||
--hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
|
||||
six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" \
|
||||
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \
|
||||
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926
|
||||
typing-extensions==3.10.0.0; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
|
||||
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \
|
||||
--hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342
|
||||
uritemplate==3.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \
|
||||
--hash=sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f \
|
||||
--hash=sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae
|
||||
yarl==1.6.3; python_version >= "3.6" and python_full_version >= "3.5.3" \
|
||||
--hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \
|
||||
--hash=sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478 \
|
||||
--hash=sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6 \
|
||||
--hash=sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e \
|
||||
--hash=sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406 \
|
||||
--hash=sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76 \
|
||||
--hash=sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366 \
|
||||
--hash=sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721 \
|
||||
--hash=sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643 \
|
||||
--hash=sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e \
|
||||
--hash=sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3 \
|
||||
--hash=sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8 \
|
||||
--hash=sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a \
|
||||
--hash=sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c \
|
||||
--hash=sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f \
|
||||
--hash=sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970 \
|
||||
--hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \
|
||||
--hash=sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50 \
|
||||
--hash=sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2 \
|
||||
--hash=sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec \
|
||||
--hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 \
|
||||
--hash=sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc \
|
||||
--hash=sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959 \
|
||||
--hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \
|
||||
--hash=sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2 \
|
||||
--hash=sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896 \
|
||||
--hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \
|
||||
--hash=sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e \
|
||||
--hash=sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724 \
|
||||
--hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \
|
||||
--hash=sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25 \
|
||||
--hash=sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96 \
|
||||
--hash=sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0 \
|
||||
--hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4 \
|
||||
--hash=sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424 \
|
||||
--hash=sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6 \
|
||||
--hash=sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10
|
|
@ -1,23 +1,15 @@
|
|||
import asyncio
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import traceback
|
||||
import aiohttp
|
||||
import config
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import CommandError, Context
|
||||
|
||||
from robocop_ng.helpers.notifications import report_critical_error
|
||||
|
||||
if len(sys.argv[1:]) != 1:
|
||||
sys.stderr.write("usage: <state_dir>")
|
||||
sys.exit(1)
|
||||
|
||||
state_dir = os.path.abspath(sys.argv[1])
|
||||
sys.path.append(state_dir)
|
||||
|
||||
import config
|
||||
# TODO: check __name__ for __main__ nerd
|
||||
|
||||
script_name = os.path.basename(__file__).split(".")[0]
|
||||
|
||||
|
@ -54,24 +46,11 @@ wanted_jsons = [
|
|||
"data/robocronptab.json",
|
||||
"data/userlog.json",
|
||||
"data/invites.json",
|
||||
"data/macros.json",
|
||||
"data/persistent_roles.json",
|
||||
"data/disabled_ids.json",
|
||||
]
|
||||
|
||||
if not os.path.exists(os.path.join(state_dir, "data")):
|
||||
os.makedirs(os.path.join(state_dir, "data"))
|
||||
|
||||
for wanted_json_idx in range(len(wanted_jsons)):
|
||||
wanted_jsons[wanted_json_idx] = os.path.join(
|
||||
state_dir, wanted_jsons[wanted_json_idx]
|
||||
)
|
||||
if not os.path.isfile(wanted_jsons[wanted_json_idx]):
|
||||
with open(wanted_jsons[wanted_json_idx], "w") as file:
|
||||
file.write("{}")
|
||||
|
||||
intents = discord.Intents.all()
|
||||
intents = discord.Intents.default()
|
||||
intents.typing = False
|
||||
intents.members = True
|
||||
|
||||
bot = commands.Bot(
|
||||
command_prefix=get_prefix, description=config.bot_description, intents=intents
|
||||
|
@ -81,19 +60,15 @@ bot.help_command = commands.DefaultHelpCommand(dm_help=True)
|
|||
bot.log = log
|
||||
bot.config = config
|
||||
bot.script_name = script_name
|
||||
bot.state_dir = state_dir
|
||||
bot.wanted_jsons = wanted_jsons
|
||||
|
||||
|
||||
async def get_channel_safe(self, channel_id: int):
|
||||
res = self.get_channel(channel_id)
|
||||
if res is None:
|
||||
res = await self.fetch_channel(channel_id)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
commands.Bot.get_channel_safe = get_channel_safe
|
||||
if __name__ == "__main__":
|
||||
for cog in config.initial_cogs:
|
||||
try:
|
||||
bot.load_extension(cog)
|
||||
except:
|
||||
log.error(f"Failed to load cog {cog}.")
|
||||
log.error(traceback.print_exc())
|
||||
|
||||
|
||||
@bot.event
|
||||
|
@ -101,7 +76,7 @@ async def on_ready():
|
|||
aioh = {"User-Agent": f"{script_name}/1.0'"}
|
||||
bot.aiosession = aiohttp.ClientSession(headers=aioh)
|
||||
bot.app_info = await bot.application_info()
|
||||
bot.botlog_channel = await bot.get_channel_safe(config.botlog_channel)
|
||||
bot.botlog_channel = bot.get_channel(config.botlog_channel)
|
||||
|
||||
log.info(
|
||||
f"\nLogged in as: {bot.user.name} - "
|
||||
|
@ -140,31 +115,12 @@ async def on_command(ctx):
|
|||
|
||||
|
||||
@bot.event
|
||||
async def on_error(event: str, *args, **kwargs):
|
||||
log.exception(f"Error on {event}:")
|
||||
|
||||
exception = sys.exc_info()[1]
|
||||
is_report_allowed = any(
|
||||
[
|
||||
not isinstance(exception, x)
|
||||
for x in [
|
||||
discord.RateLimited,
|
||||
discord.GatewayNotFound,
|
||||
discord.InteractionResponded,
|
||||
discord.LoginFailure,
|
||||
]
|
||||
]
|
||||
)
|
||||
if exception is not None and is_report_allowed:
|
||||
await report_critical_error(
|
||||
bot,
|
||||
exception,
|
||||
additional_info={"Event": event, "args": args, "kwargs": kwargs},
|
||||
)
|
||||
async def on_error(event_method, *args, **kwargs):
|
||||
log.error(f"Error on {event_method}: {sys.exc_info()}")
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_command_error(ctx: Context, error: CommandError):
|
||||
async def on_command_error(ctx, error):
|
||||
error_text = str(error)
|
||||
|
||||
err_msg = (
|
||||
|
@ -173,7 +129,7 @@ async def on_command_error(ctx: Context, error: CommandError):
|
|||
f"of type {type(error)}: {error_text}"
|
||||
)
|
||||
|
||||
log.exception(err_msg)
|
||||
log.error(err_msg)
|
||||
|
||||
if not isinstance(error, commands.CommandNotFound):
|
||||
err_msg = bot.escape_message(err_msg)
|
||||
|
@ -231,7 +187,6 @@ async def on_command_error(ctx: Context, error: CommandError):
|
|||
|
||||
# Keep a list of commands that involve mentioning users
|
||||
# and can involve users leaving/getting banned
|
||||
# noinspection NonAsciiCharacters,PyPep8Naming
|
||||
ಠ_ಠ = ["warn", "kick", "ban"]
|
||||
|
||||
if isinstance(error, commands.BadArgument):
|
||||
|
@ -271,26 +226,12 @@ async def on_message(message):
|
|||
await bot.invoke(ctx)
|
||||
|
||||
|
||||
async def main():
|
||||
async with bot:
|
||||
if len(config.guild_whitelist) == 1:
|
||||
invite_url = discord.utils.oauth_url(
|
||||
config.client_id,
|
||||
guild=discord.Object(config.guild_whitelist[0]),
|
||||
disable_guild_select=True,
|
||||
)
|
||||
else:
|
||||
invite_url = discord.utils.oauth_url(config.client_id)
|
||||
if not os.path.exists("data"):
|
||||
os.makedirs("data")
|
||||
|
||||
log.info(f"\nInvite URL: {invite_url}\n")
|
||||
for wanted_json in wanted_jsons:
|
||||
if not os.path.exists(wanted_json):
|
||||
with open(wanted_json, "w") as f:
|
||||
f.write("{}")
|
||||
|
||||
for cog in config.initial_cogs:
|
||||
try:
|
||||
await bot.load_extension(f"robocop_ng.{cog}")
|
||||
except Exception as e:
|
||||
log.exception(f"Failed to load cog {cog}:", e)
|
||||
await bot.start(config.token)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
bot.run(config.token, bot=True, reconnect=True)
|
|
@ -1,12 +1,11 @@
|
|||
import inspect
|
||||
import re
|
||||
import traceback
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_bot_manager
|
||||
import traceback
|
||||
import inspect
|
||||
import re
|
||||
import config
|
||||
from helpers.checks import check_if_bot_manager
|
||||
|
||||
|
||||
class Admin(Cog):
|
||||
|
@ -21,7 +20,7 @@ class Admin(Cog):
|
|||
async def _exit(self, ctx):
|
||||
"""Shuts down the bot, bot manager only."""
|
||||
await ctx.send(":wave: Goodbye!")
|
||||
await self.bot.close()
|
||||
await self.bot.logout()
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_bot_manager)
|
||||
|
@ -93,7 +92,7 @@ class Admin(Cog):
|
|||
|
||||
async def cog_load_actions(self, cog_name):
|
||||
if cog_name == "verification":
|
||||
verif_channel = self.bot.get_channel(self.bot.config.welcome_channel)
|
||||
verif_channel = self.bot.get_channel(config.welcome_channel)
|
||||
await self.bot.do_resetalgo(verif_channel, "cog load")
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -107,13 +106,13 @@ class Admin(Cog):
|
|||
if auto:
|
||||
cogs_to_reload = re.findall(r"cogs/([a-z_]*).py[ ]*\|", git_output)
|
||||
for cog in cogs_to_reload:
|
||||
cog_name = "robocop_ng.cogs." + cog
|
||||
if cog_name not in self.bot.config.initial_cogs:
|
||||
cog_name = "cogs." + cog
|
||||
if cog_name not in config.initial_cogs:
|
||||
continue
|
||||
|
||||
try:
|
||||
await self.bot.unload_extension(cog_name)
|
||||
await self.bot.load_extension(cog_name)
|
||||
self.bot.unload_extension(cog_name)
|
||||
self.bot.load_extension(cog_name)
|
||||
self.bot.log.info(f"Reloaded ext {cog}")
|
||||
await ctx.send(f":white_check_mark: `{cog}` successfully reloaded.")
|
||||
await self.cog_load_actions(cog)
|
||||
|
@ -130,7 +129,7 @@ class Admin(Cog):
|
|||
async def load(self, ctx, ext: str):
|
||||
"""Loads a cog, bot manager only."""
|
||||
try:
|
||||
await self.bot.load_extension("robocop_ng.cogs." + ext)
|
||||
self.bot.load_extension("cogs." + ext)
|
||||
await self.cog_load_actions(ext)
|
||||
except:
|
||||
await ctx.send(
|
||||
|
@ -146,7 +145,7 @@ class Admin(Cog):
|
|||
@commands.command()
|
||||
async def unload(self, ctx, ext: str):
|
||||
"""Unloads a cog, bot manager only."""
|
||||
await self.bot.unload_extension("robocop_ng.cogs." + ext)
|
||||
self.bot.unload_extension("cogs." + ext)
|
||||
self.bot.log.info(f"Unloaded ext {ext}")
|
||||
await ctx.send(f":white_check_mark: `{ext}` successfully unloaded.")
|
||||
|
||||
|
@ -160,8 +159,8 @@ class Admin(Cog):
|
|||
self.lastreload = ext
|
||||
|
||||
try:
|
||||
await self.bot.unload_extension("robocop_ng.cogs." + ext)
|
||||
await self.bot.load_extension("robocop_ng.cogs." + ext)
|
||||
self.bot.unload_extension("cogs." + ext)
|
||||
self.bot.load_extension("cogs." + ext)
|
||||
await self.cog_load_actions(ext)
|
||||
except:
|
||||
await ctx.send(
|
||||
|
@ -173,5 +172,5 @@ class Admin(Cog):
|
|||
await ctx.send(f":white_check_mark: `{ext}` successfully reloaded.")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Admin(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Admin(bot))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import time
|
||||
|
||||
import config
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
@ -24,7 +24,7 @@ class Basic(Cog):
|
|||
@commands.cooldown(1, 10, type=commands.BucketType.user)
|
||||
@commands.command(name="dec")
|
||||
async def _dec(self, ctx, num):
|
||||
"""Converts base 16 to 10"""
|
||||
"""Converts base 10 to 16"""
|
||||
await ctx.send(f"{ctx.author.mention}: {int(num, 16)}")
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -37,12 +37,10 @@ class Basic(Cog):
|
|||
async def robocop(self, ctx):
|
||||
"""Shows a quick embed with bot info."""
|
||||
embed = discord.Embed(
|
||||
title="Robocop-NG",
|
||||
url=self.bot.config.source_url,
|
||||
description=self.bot.config.embed_desc,
|
||||
title="Robocop-NG", url=config.source_url, description=config.embed_desc
|
||||
)
|
||||
|
||||
embed.set_thumbnail(url=str(self.bot.user.display_avatar))
|
||||
embed.set_thumbnail(url=self.bot.user.avatar_url)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
@ -58,10 +56,12 @@ class Basic(Cog):
|
|||
rtt_ms = (after - before) * 1000
|
||||
gw_ms = self.bot.latency * 1000
|
||||
|
||||
message_text = f":ping_pong:\nrtt: `{rtt_ms:.1f}ms`\ngw: `{gw_ms:.1f}ms`"
|
||||
message_text = (
|
||||
f":ping_pong:\nrtt: `{rtt_ms:.1f}ms`\ngw: `{gw_ms:.1f}ms`"
|
||||
)
|
||||
self.bot.log.info(message_text)
|
||||
await tmp.edit(content=message_text)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Basic(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Basic(bot))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import config
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
|
@ -10,7 +11,7 @@ class BasicReswitched(Cog):
|
|||
@commands.command()
|
||||
async def communitycount(self, ctx):
|
||||
"""Prints the community member count of the server."""
|
||||
community = ctx.guild.get_role(self.bot.config.named_roles["community"])
|
||||
community = ctx.guild.get_role(config.named_roles["community"])
|
||||
await ctx.send(
|
||||
f"{ctx.guild.name} has {len(community.members)} community members!"
|
||||
)
|
||||
|
@ -19,11 +20,11 @@ class BasicReswitched(Cog):
|
|||
@commands.command()
|
||||
async def hackercount(self, ctx):
|
||||
"""Prints the hacker member count of the server."""
|
||||
h4x0r = ctx.guild.get_role(self.bot.config.named_roles["hacker"])
|
||||
h4x0r = ctx.guild.get_role(config.named_roles["hacker"])
|
||||
await ctx.send(
|
||||
f"{ctx.guild.name} has {len(h4x0r.members)} people with hacker role!"
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(BasicReswitched(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(BasicReswitched(bot))
|
||||
|
|
|
@ -191,5 +191,5 @@ class Common(Cog):
|
|||
return "No output."
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Common(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Common(bot))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import re
|
||||
|
||||
import discord
|
||||
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.errcodes import *
|
||||
from helpers.errcodes import *
|
||||
|
||||
|
||||
class Err(Cog):
|
||||
|
@ -98,6 +97,7 @@ class Err(Cog):
|
|||
Usage: .serr/.nxerr/.err <Error Code>"""
|
||||
|
||||
if self.switch_re.match(err) or err.startswith("0x"): # Switch
|
||||
|
||||
if err.startswith("0x"):
|
||||
err = err[2:]
|
||||
errcode = int(err, 16)
|
||||
|
@ -142,7 +142,7 @@ class Err(Cog):
|
|||
embed.add_field(name="Description", value=desc, inline=True)
|
||||
|
||||
if "ban" in err_description:
|
||||
embed.set_footer(text="F to you | Console: Switch")
|
||||
embed.set_footer("F to you | Console: Switch")
|
||||
else:
|
||||
embed.set_footer(text="Console: Switch")
|
||||
|
||||
|
@ -194,5 +194,5 @@ class Err(Cog):
|
|||
await ctx.send("This doesn't look like typical hex!")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Err(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Err(bot))
|
||||
|
|
87
robocop_ng/cogs/imagemanip.py
Normal file
87
robocop_ng/cogs/imagemanip.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
from helpers.checks import check_if_staff_or_ot
|
||||
import textwrap
|
||||
import PIL.Image
|
||||
import PIL.ImageFilter
|
||||
import PIL.ImageOps
|
||||
import PIL.ImageFont
|
||||
import PIL.ImageDraw
|
||||
|
||||
|
||||
class ImageManip(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.cooldown(1, 60 * 60 * 3, type=commands.BucketType.user)
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(hidden=True)
|
||||
async def cox(self, ctx, *, headline: str):
|
||||
"""Gives a cox headline"""
|
||||
mention = ctx.author.mention
|
||||
|
||||
headline = await commands.clean_content(fix_channel_mentions=True).convert(
|
||||
ctx, headline
|
||||
)
|
||||
|
||||
in_vice = "assets/motherboardlogo.png"
|
||||
in_byjcox = "assets/byjcox.png"
|
||||
font_path = "assets/neue-haas-grotesk-display-bold-regular.otf"
|
||||
|
||||
# Settings for image generation, don't touch anything
|
||||
horipos = 18
|
||||
vertpos = 75
|
||||
line_spacing = 10
|
||||
font_size = 50
|
||||
image_width = 800
|
||||
font_wrap_count = 30
|
||||
sig_height = 15
|
||||
|
||||
# Wrap into lines
|
||||
lines = textwrap.wrap(headline, width=font_wrap_count)
|
||||
# not great, 4am be like
|
||||
image_height = (len(lines) + 2) * (vertpos + line_spacing)
|
||||
|
||||
# Load font
|
||||
f = PIL.ImageFont.truetype(font_path, font_size)
|
||||
|
||||
# Create image base, paste mobo logo
|
||||
im = PIL.Image.new("RGB", (image_width, image_height), color="#FFFFFF")
|
||||
moboim = PIL.Image.open(in_vice)
|
||||
im.paste(moboim, (horipos, 17))
|
||||
|
||||
# Go through all the wrapped text lines
|
||||
for line in lines:
|
||||
# Get size of the text by font, create a new image of that size
|
||||
size = f.getsize(line)
|
||||
txt = PIL.Image.new("L", size)
|
||||
|
||||
# Draw the text
|
||||
d = PIL.ImageDraw.Draw(txt)
|
||||
d.text((0, 0), line, font=f, fill=255)
|
||||
|
||||
# Paste the text into the base image
|
||||
w = txt.rotate(0, expand=1)
|
||||
im.paste(
|
||||
PIL.ImageOps.colorize(w, (0, 0, 0), (0, 0, 0)), (horipos, vertpos), w
|
||||
)
|
||||
|
||||
# Calculate position on next line
|
||||
vertpos += size[1] + line_spacing
|
||||
|
||||
# Add jcox signature
|
||||
jcoxim = PIL.Image.open(in_byjcox)
|
||||
im.paste(jcoxim, (horipos, vertpos + sig_height))
|
||||
|
||||
# Crop the image to the actual resulting size
|
||||
im = im.crop((0, 0, image_width, vertpos + (sig_height * 3)))
|
||||
|
||||
# Save image
|
||||
out_filename = f"/tmp/{ctx.message.id}-out.png"
|
||||
im.save(out_filename, quality=100, optimize=True)
|
||||
await ctx.send(content=f"{mention}: Enjoy.", file=discord.File(out_filename))
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(ImageManip(bot))
|
|
@ -1,12 +1,8 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_collaborator
|
||||
from robocop_ng.helpers.invites import add_invite
|
||||
from helpers.checks import check_if_collaborator
|
||||
import config
|
||||
import json
|
||||
|
||||
|
||||
class Invites(Cog):
|
||||
|
@ -17,14 +13,25 @@ class Invites(Cog):
|
|||
@commands.guild_only()
|
||||
@commands.check(check_if_collaborator)
|
||||
async def invite(self, ctx):
|
||||
welcome_channel = self.bot.get_channel(self.bot.config.welcome_channel)
|
||||
welcome_channel = self.bot.get_channel(config.welcome_channel)
|
||||
author = ctx.message.author
|
||||
reason = f"Created by {str(author)} ({author.id})"
|
||||
invite = await welcome_channel.create_invite(
|
||||
max_age=0, max_uses=1, temporary=True, unique=True, reason=reason
|
||||
)
|
||||
|
||||
add_invite(self.bot, invite.id, invite.url, 1, invite.code)
|
||||
with open("data/invites.json", "r") as f:
|
||||
invites = json.load(f)
|
||||
|
||||
invites[invite.id] = {
|
||||
"uses": 0,
|
||||
"url": invite.url,
|
||||
"max_uses": 1,
|
||||
"code": invite.code,
|
||||
}
|
||||
|
||||
with open("data/invites.json", "w") as f:
|
||||
f.write(json.dumps(invites))
|
||||
|
||||
await ctx.message.add_reaction("🆗")
|
||||
try:
|
||||
|
@ -36,5 +43,5 @@ class Invites(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Invites(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Invites(bot))
|
||||
|
|
|
@ -33,5 +33,5 @@ class Legacy(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Legacy(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Legacy(bot))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import discord
|
||||
import config
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
|
@ -36,13 +37,13 @@ class Links(Cog):
|
|||
@commands.command(hidden=True, aliases=["guides", "link"])
|
||||
async def guide(self, ctx):
|
||||
"""Link to the guides"""
|
||||
await ctx.send(self.bot.config.links_guide_text)
|
||||
await ctx.send(config.links_guide_text)
|
||||
|
||||
@commands.command()
|
||||
async def source(self, ctx):
|
||||
"""Gives link to source code."""
|
||||
await ctx.send(
|
||||
f"You can find my source at {self.bot.config.source_url}. "
|
||||
f"You can find my source at {config.source_url}. "
|
||||
"Serious PRs and issues welcome!"
|
||||
)
|
||||
|
||||
|
@ -53,7 +54,7 @@ class Links(Cog):
|
|||
targetuser = ctx.author
|
||||
await ctx.send(
|
||||
f"{targetuser.mention}: A link to the rules "
|
||||
f"can be found here: {self.bot.config.rules_url}"
|
||||
f"can be found here: {config.rules_url}"
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
|
@ -75,5 +76,5 @@ class Links(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Links(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Links(bot))
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import io
|
||||
import os.path
|
||||
|
||||
import config
|
||||
import discord
|
||||
import io
|
||||
import urllib.parse
|
||||
import os.path
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
|
@ -17,7 +18,7 @@ class Lists(Cog):
|
|||
# Helpers
|
||||
|
||||
def check_if_target_is_staff(self, target):
|
||||
return any(r.id in self.bot.config.staff_role_ids for r in target.roles)
|
||||
return any(r.id in config.staff_role_ids for r in target.roles)
|
||||
|
||||
def is_edit(self, emoji):
|
||||
return str(emoji)[0] == "✏" or str(emoji)[0] == "📝"
|
||||
|
@ -82,7 +83,7 @@ class Lists(Cog):
|
|||
fields = embeds[0].fields
|
||||
for field in fields:
|
||||
if field.name == "Message ID":
|
||||
files_channel = self.bot.get_channel(self.bot.config.list_files_channel)
|
||||
files_channel = self.bot.get_channel(config.list_files_channel)
|
||||
file_message = await files_channel.fetch_message(int(field.value))
|
||||
await file_message.delete()
|
||||
|
||||
|
@ -131,7 +132,7 @@ class Lists(Cog):
|
|||
await ctx.send(f"Number must be greater than 0.")
|
||||
return
|
||||
|
||||
if channel.id not in self.bot.config.list_channels:
|
||||
if channel.id not in config.list_channels:
|
||||
await ctx.send(f"{channel.mention} is not a list channel.")
|
||||
return
|
||||
|
||||
|
@ -158,7 +159,7 @@ class Lists(Cog):
|
|||
await self.bot.wait_until_ready()
|
||||
|
||||
# We only care about reactions in Rules, and Support FAQ
|
||||
if payload.channel_id not in self.bot.config.list_channels:
|
||||
if payload.channel_id not in config.list_channels:
|
||||
return
|
||||
|
||||
channel = self.bot.get_channel(payload.channel_id)
|
||||
|
@ -199,8 +200,8 @@ class Lists(Cog):
|
|||
await r.remove(user)
|
||||
|
||||
# When editing we want to provide the user a copy of the raw text.
|
||||
if self.is_edit(reaction.emoji) and self.bot.config.list_files_channel != 0:
|
||||
files_channel = self.bot.get_channel(self.bot.config.list_files_channel)
|
||||
if self.is_edit(reaction.emoji) and config.list_files_channel != 0:
|
||||
files_channel = self.bot.get_channel(config.list_files_channel)
|
||||
file = discord.File(
|
||||
io.BytesIO(message.content.encode("utf-8")),
|
||||
filename=f"{message.id}.txt",
|
||||
|
@ -219,7 +220,7 @@ class Lists(Cog):
|
|||
await self.bot.wait_until_ready()
|
||||
|
||||
# We only care about reactions in Rules, and Support FAQ
|
||||
if payload.channel_id not in self.bot.config.list_channels:
|
||||
if payload.channel_id not in config.list_channels:
|
||||
return
|
||||
|
||||
channel = self.bot.get_channel(payload.channel_id)
|
||||
|
@ -230,7 +231,7 @@ class Lists(Cog):
|
|||
return
|
||||
|
||||
# We want to remove the embed we added.
|
||||
if self.is_edit(payload.emoji) and self.bot.config.list_files_channel != 0:
|
||||
if self.is_edit(payload.emoji) and config.list_files_channel != 0:
|
||||
await self.clean_up_raw_text_file_message(message)
|
||||
|
||||
@Cog.listener()
|
||||
|
@ -238,7 +239,7 @@ class Lists(Cog):
|
|||
await self.bot.wait_until_ready()
|
||||
|
||||
# We only care about messages in Rules, and Support FAQ
|
||||
if message.channel.id not in self.bot.config.list_channels:
|
||||
if message.channel.id not in config.list_channels:
|
||||
return
|
||||
|
||||
# We don"t care about messages from bots.
|
||||
|
@ -250,7 +251,7 @@ class Lists(Cog):
|
|||
await message.delete()
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
channel = message.channel
|
||||
content = message.content
|
||||
user = message.author
|
||||
|
@ -298,7 +299,7 @@ class Lists(Cog):
|
|||
targeted_message = targeted_reaction.message
|
||||
|
||||
if self.is_edit(targeted_reaction):
|
||||
if self.bot.config.list_files_channel != 0:
|
||||
if config.list_files_channel != 0:
|
||||
await self.clean_up_raw_text_file_message(targeted_message)
|
||||
await targeted_message.edit(content=content)
|
||||
await targeted_reaction.remove(user)
|
||||
|
@ -388,5 +389,5 @@ class Lists(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Lists(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Lists(bot))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
import config
|
||||
import discord
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class Lockdown(Cog):
|
||||
|
@ -23,7 +23,7 @@ class Lockdown(Cog):
|
|||
pass
|
||||
|
||||
async def unlock_for_staff(self, channel: discord.TextChannel, issuer):
|
||||
for role in self.bot.config.staff_role_ids:
|
||||
for role in config.staff_role_ids:
|
||||
await self.set_sendmessage(channel, role, True, issuer)
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -35,15 +35,15 @@ class Lockdown(Cog):
|
|||
Defaults to current channel."""
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
|
||||
roles = None
|
||||
for key, lockdown_conf in self.bot.config.lockdown_configs.items():
|
||||
for key, lockdown_conf in config.lockdown_configs.items():
|
||||
if channel.id in lockdown_conf["channels"]:
|
||||
roles = lockdown_conf["roles"]
|
||||
|
||||
if roles is None:
|
||||
roles = self.bot.config.lockdown_configs["default"]["roles"]
|
||||
roles = config.lockdown_configs["default"]["roles"]
|
||||
|
||||
for role in roles:
|
||||
await self.set_sendmessage(channel, role, False, ctx.author)
|
||||
|
@ -75,15 +75,15 @@ class Lockdown(Cog):
|
|||
"""Unlocks speaking in current channel, staff only."""
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
|
||||
roles = None
|
||||
for key, lockdown_conf in self.bot.config.lockdown_configs.items():
|
||||
for key, lockdown_conf in config.lockdown_configs.items():
|
||||
if channel.id in lockdown_conf["channels"]:
|
||||
roles = lockdown_conf["roles"]
|
||||
|
||||
if roles is None:
|
||||
roles = self.bot.config.lockdown_configs["default"]["roles"]
|
||||
roles = config.lockdown_configs["default"]["roles"]
|
||||
|
||||
await self.unlock_for_staff(channel, ctx.author)
|
||||
|
||||
|
@ -101,5 +101,5 @@ class Lockdown(Cog):
|
|||
await log_channel.send(msg)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Lockdown(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Lockdown(bot))
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,10 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
import discord
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from robocop_ng.helpers.invites import get_invites, set_invites
|
||||
from robocop_ng.helpers.restrictions import get_user_restrictions
|
||||
from robocop_ng.helpers.userlogs import get_userlog
|
||||
import json
|
||||
import re
|
||||
import config
|
||||
from helpers.restrictions import get_user_restrictions
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class Logs(Cog):
|
||||
|
@ -25,7 +21,7 @@ class Logs(Cog):
|
|||
self.clean_re = re.compile(r"[^a-zA-Z0-9_ ]+", re.UNICODE)
|
||||
# All lower case, no spaces, nothing non-alphanumeric
|
||||
susp_hellgex = "|".join(
|
||||
[r"\W*".join(list(word)) for word in self.bot.config.suspect_words]
|
||||
[r"\W*".join(list(word)) for word in config.suspect_words]
|
||||
)
|
||||
self.susp_hellgex = re.compile(susp_hellgex, re.IGNORECASE)
|
||||
|
||||
|
@ -33,15 +29,16 @@ class Logs(Cog):
|
|||
async def on_member_join(self, member):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if member.guild.id not in self.bot.config.guild_whitelist:
|
||||
if member.guild.id not in config.guild_whitelist:
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
# We use this a lot, might as well get it once
|
||||
escaped_name = self.bot.escape_message(member)
|
||||
|
||||
# Attempt to correlate the user joining with an invite
|
||||
invites = get_invites(self.bot)
|
||||
with open("data/invites.json", "r") as f:
|
||||
invites = json.load(f)
|
||||
|
||||
real_invites = await member.guild.invites()
|
||||
|
||||
|
@ -75,7 +72,8 @@ class Logs(Cog):
|
|||
del invites[id]
|
||||
|
||||
# Save invites data.
|
||||
set_invites(self.bot, invites)
|
||||
with open("data/invites.json", "w") as f:
|
||||
f.write(json.dumps(invites))
|
||||
|
||||
# Prepare the invite correlation message
|
||||
if len(probable_invites_used) == 1:
|
||||
|
@ -88,7 +86,7 @@ class Logs(Cog):
|
|||
|
||||
# Check if user account is older than 15 minutes
|
||||
age = member.joined_at - member.created_at
|
||||
if age < self.bot.config.min_age:
|
||||
if age < config.min_age:
|
||||
try:
|
||||
await member.send(
|
||||
f"Your account is too new to "
|
||||
|
@ -126,12 +124,13 @@ class Logs(Cog):
|
|||
|
||||
# Handles user restrictions
|
||||
# Basically, gives back muted role to users that leave with it.
|
||||
rsts = get_user_restrictions(self.bot, member.id)
|
||||
rsts = get_user_restrictions(member.id)
|
||||
roles = [discord.utils.get(member.guild.roles, id=rst) for rst in rsts]
|
||||
await member.add_roles(*roles)
|
||||
|
||||
# Real hell zone.
|
||||
warns = get_userlog(self.bot)
|
||||
with open("data/userlog.json", "r") as f:
|
||||
warns = json.load(f)
|
||||
try:
|
||||
if len(warns[str(member.id)]["warns"]) == 0:
|
||||
await log_channel.send(msg)
|
||||
|
@ -139,7 +138,7 @@ class Logs(Cog):
|
|||
embed = discord.Embed(
|
||||
color=discord.Color.dark_red(), title=f"Warns for {escaped_name}"
|
||||
)
|
||||
embed.set_thumbnail(url=str(member.display_avatar))
|
||||
embed.set_thumbnail(url=member.avatar_url)
|
||||
for idx, warn in enumerate(warns[str(member.id)]["warns"]):
|
||||
embed.add_field(
|
||||
name=f"{idx + 1}: {warn['timestamp']}",
|
||||
|
@ -169,17 +168,16 @@ class Logs(Cog):
|
|||
msg += f"\n- Has invite: https://{invite[0]}"
|
||||
alert = True
|
||||
|
||||
for susp_word in self.bot.config.suspect_words:
|
||||
for susp_word in config.suspect_words:
|
||||
if susp_word in cleancont and not any(
|
||||
ok_word in cleancont
|
||||
for ok_word in self.bot.config.suspect_ignored_words
|
||||
ok_word in cleancont for ok_word in config.suspect_ignored_words
|
||||
):
|
||||
msg += f"\n- Contains suspicious word: `{susp_word}`"
|
||||
alert = True
|
||||
|
||||
if alert:
|
||||
msg += f"\n\nJump: <{message.jump_url}>"
|
||||
spy_channel = self.bot.get_channel(self.bot.config.spylog_channel)
|
||||
spy_channel = self.bot.get_channel(config.spylog_channel)
|
||||
|
||||
# Bad Code :tm:, blame retr0id
|
||||
message_clean = message.content.replace("*", "").replace("_", "")
|
||||
|
@ -190,8 +188,7 @@ class Logs(Cog):
|
|||
# Show a message embed
|
||||
embed = discord.Embed(description=regd)
|
||||
embed.set_author(
|
||||
name=message.author.display_name,
|
||||
icon_url=str(message.author.display_avatar),
|
||||
name=message.author.display_name, icon_url=message.author.avatar_url
|
||||
)
|
||||
|
||||
await spy_channel.send(msg, embed=embed)
|
||||
|
@ -205,13 +202,13 @@ class Logs(Cog):
|
|||
f"R11 violating name by {message.author.mention} " f"({message.author.id})."
|
||||
)
|
||||
|
||||
spy_channel = self.bot.get_channel(self.bot.config.spylog_channel)
|
||||
spy_channel = self.bot.get_channel(config.spylog_channel)
|
||||
await spy_channel.send(msg)
|
||||
|
||||
@Cog.listener()
|
||||
async def on_message(self, message):
|
||||
await self.bot.wait_until_ready()
|
||||
if message.channel.id not in self.bot.config.spy_channels:
|
||||
if message.channel.id not in config.spy_channels:
|
||||
return
|
||||
|
||||
await self.do_spy(message)
|
||||
|
@ -219,7 +216,7 @@ class Logs(Cog):
|
|||
@Cog.listener()
|
||||
async def on_message_edit(self, before, after):
|
||||
await self.bot.wait_until_ready()
|
||||
if after.channel.id not in self.bot.config.spy_channels or after.author.bot:
|
||||
if after.channel.id not in config.spy_channels or after.author.bot:
|
||||
return
|
||||
|
||||
# If content is the same, just skip over it
|
||||
|
@ -233,7 +230,7 @@ class Logs(Cog):
|
|||
before_content = before.clean_content.replace("`", "`\u200d")
|
||||
after_content = after.clean_content.replace("`", "`\u200d")
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
|
||||
msg = (
|
||||
"📝 **Message edit**: \n"
|
||||
|
@ -252,10 +249,10 @@ class Logs(Cog):
|
|||
@Cog.listener()
|
||||
async def on_message_delete(self, message):
|
||||
await self.bot.wait_until_ready()
|
||||
if message.channel.id not in self.bot.config.spy_channels or message.author.bot:
|
||||
if message.channel.id not in config.spy_channels or message.author.bot:
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
msg = (
|
||||
"🗑️ **Message delete**: \n"
|
||||
f"from {self.bot.escape_message(message.author.name)} "
|
||||
|
@ -274,10 +271,10 @@ class Logs(Cog):
|
|||
async def on_member_remove(self, member):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if member.guild.id not in self.bot.config.guild_whitelist:
|
||||
if member.guild.id not in config.guild_whitelist:
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
msg = (
|
||||
f"⬅️ **Leave**: {member.mention} | "
|
||||
f"{self.bot.escape_message(member)}\n"
|
||||
|
@ -289,10 +286,10 @@ class Logs(Cog):
|
|||
async def on_member_ban(self, guild, member):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if guild.id not in self.bot.config.guild_whitelist:
|
||||
if guild.id not in config.guild_whitelist:
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
msg = (
|
||||
f"⛔ **Ban**: {member.mention} | "
|
||||
f"{self.bot.escape_message(member)}\n"
|
||||
|
@ -304,10 +301,10 @@ class Logs(Cog):
|
|||
async def on_member_unban(self, guild, user):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if guild.id not in self.bot.config.guild_whitelist:
|
||||
if guild.id not in config.guild_whitelist:
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
msg = (
|
||||
f"⚠️ **Unban**: {user.mention} | "
|
||||
f"{self.bot.escape_message(user)}\n"
|
||||
|
@ -328,11 +325,11 @@ class Logs(Cog):
|
|||
async def on_member_update(self, member_before, member_after):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if member_after.guild.id not in self.bot.config.guild_whitelist:
|
||||
if member_after.guild.id not in config.guild_whitelist:
|
||||
return
|
||||
|
||||
msg = ""
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
if member_before.roles != member_after.roles:
|
||||
# role removal
|
||||
role_removal = []
|
||||
|
@ -384,5 +381,5 @@ class Logs(Cog):
|
|||
await log_channel.send(msg)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Logs(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Logs(bot))
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog, Context, BucketType, Greedy
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff, check_if_staff_or_dm
|
||||
from robocop_ng.helpers.macros import (
|
||||
get_macro,
|
||||
add_macro,
|
||||
edit_macro,
|
||||
remove_macro,
|
||||
get_macros_dict,
|
||||
add_aliases,
|
||||
remove_aliases,
|
||||
clear_aliases,
|
||||
)
|
||||
|
||||
|
||||
class Macro(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.cooldown(3, 30, BucketType.user)
|
||||
@commands.command(aliases=["m"])
|
||||
async def macro(self, ctx: Context, key: str, targets: Greedy[discord.User] = None):
|
||||
if ctx.guild:
|
||||
await ctx.message.delete()
|
||||
if len(key) > 0:
|
||||
text = get_macro(self.bot, key)
|
||||
if text is not None:
|
||||
if targets is not None:
|
||||
await ctx.send(
|
||||
f"{', '.join(target.mention for target in targets)}:\n{text}"
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
await ctx.send(
|
||||
text, reference=ctx.message.reference, mention_author=True
|
||||
)
|
||||
else:
|
||||
await ctx.send(text)
|
||||
else:
|
||||
await ctx.send(
|
||||
f"{ctx.author.mention}: The macro '{key}' doesn't exist."
|
||||
)
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(name="macroadd", aliases=["ma", "addmacro", "add_macro"])
|
||||
async def add_macro(self, ctx: Context, key: str, *, text: str):
|
||||
if add_macro(self.bot, key, text):
|
||||
await ctx.send(f"Macro '{key}' added!")
|
||||
else:
|
||||
await ctx.send(f"Error: Macro '{key}' already exists.")
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(name="aliasadd", aliases=["addalias", "add_alias"])
|
||||
async def add_alias_macro(self, ctx: Context, existing_key: str, *new_keys: str):
|
||||
if len(new_keys) == 0:
|
||||
await ctx.send("Error: You need to add at least one alias.")
|
||||
else:
|
||||
if add_aliases(self.bot, existing_key, list(new_keys)):
|
||||
await ctx.send(
|
||||
f"Added {len(new_keys)} aliases to macro '{existing_key}'!"
|
||||
)
|
||||
else:
|
||||
await ctx.send(f"Error: No new and unique aliases found.")
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(name="macroedit", aliases=["me", "editmacro", "edit_macro"])
|
||||
async def edit_macro(self, ctx: Context, key: str, *, text: str):
|
||||
if edit_macro(self.bot, key, text):
|
||||
await ctx.send(f"Macro '{key}' edited!")
|
||||
else:
|
||||
await ctx.send(f"Error: Macro '{key}' not found.")
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(
|
||||
name="aliasremove",
|
||||
aliases=[
|
||||
"aliasdelete",
|
||||
"delalias",
|
||||
"aliasdel",
|
||||
"removealias",
|
||||
"remove_alias",
|
||||
"delete_alias",
|
||||
],
|
||||
)
|
||||
async def remove_alias_macro(
|
||||
self, ctx: Context, existing_key: str, *remove_keys: str
|
||||
):
|
||||
if len(remove_keys) == 0:
|
||||
await ctx.send("Error: You need to remove at least one alias.")
|
||||
else:
|
||||
if remove_aliases(self.bot, existing_key, list(remove_keys)):
|
||||
await ctx.send(
|
||||
f"Removed {len(remove_keys)} aliases from macro '{existing_key}'!"
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
f"Error: None of the specified aliases were found for macro '{existing_key}'."
|
||||
)
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(
|
||||
name="macroremove",
|
||||
aliases=[
|
||||
"mr",
|
||||
"md",
|
||||
"removemacro",
|
||||
"remove_macro",
|
||||
"macrodel",
|
||||
"delmacro",
|
||||
"delete_macro",
|
||||
],
|
||||
)
|
||||
async def remove_macro(self, ctx: Context, key: str):
|
||||
if remove_macro(self.bot, key):
|
||||
await ctx.send(f"Macro '{key}' removed!")
|
||||
else:
|
||||
await ctx.send(f"Error: Macro '{key}' not found.")
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(name="aliasclear", aliases=["clearalias", "clear_alias"])
|
||||
async def clear_alias_macro(self, ctx: Context, existing_key: str):
|
||||
if clear_aliases(self.bot, existing_key):
|
||||
await ctx.send(f"Removed all aliases of macro '{existing_key}'!")
|
||||
else:
|
||||
await ctx.send(f"Error: No aliases found for macro '{existing_key}'.")
|
||||
|
||||
@commands.check(check_if_staff_or_dm)
|
||||
@commands.cooldown(3, 30, BucketType.channel)
|
||||
@commands.command(name="macros", aliases=["ml", "listmacros", "list_macros"])
|
||||
async def list_macros(self, ctx: Context, macros_only=False):
|
||||
macros = get_macros_dict(self.bot)
|
||||
if len(macros["macros"]) > 0:
|
||||
messages = []
|
||||
macros_formatted = []
|
||||
|
||||
for key in sorted(macros["macros"].keys()):
|
||||
message = f"- {key}"
|
||||
if not macros_only and key in macros["aliases"]:
|
||||
for alias in macros["aliases"][key]:
|
||||
message += f", {alias}"
|
||||
macros_formatted.append(message)
|
||||
|
||||
message = f"📝 **Macros**:\n"
|
||||
for macro in macros_formatted:
|
||||
if len(message) >= 1500:
|
||||
messages.append(message)
|
||||
message = f"{macro}\n"
|
||||
else:
|
||||
message += f"{macro}\n"
|
||||
|
||||
if message not in messages:
|
||||
# Add the last message as well
|
||||
messages.append(message)
|
||||
|
||||
for msg in messages:
|
||||
await ctx.send(msg)
|
||||
|
||||
else:
|
||||
await ctx.send("Couldn't find any macros.")
|
||||
|
||||
@commands.check(check_if_staff_or_dm)
|
||||
@commands.cooldown(3, 30, BucketType.channel)
|
||||
@commands.command(name="aliases", aliases=["listaliases", "list_aliases"])
|
||||
async def list_aliases(self, ctx: Context, existing_key: str):
|
||||
macros = get_macros_dict(self.bot)
|
||||
existing_key = existing_key.lower()
|
||||
if existing_key in macros["aliases"].keys():
|
||||
message = f"📝 **Aliases for '{existing_key}'**:\n"
|
||||
for alias in sorted(macros["aliases"][existing_key]):
|
||||
message += f"- {alias}\n"
|
||||
await ctx.send(message)
|
||||
else:
|
||||
await ctx.send(f"Couldn't find any aliases for macro '{existing_key}'.")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Macro(bot))
|
|
@ -1,14 +1,11 @@
|
|||
import datetime
|
||||
import math
|
||||
import platform
|
||||
import random
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff_or_ot
|
||||
import math
|
||||
import platform
|
||||
from helpers.checks import check_if_staff_or_ot
|
||||
import datetime
|
||||
|
||||
|
||||
class Meme(Cog):
|
||||
|
@ -29,24 +26,8 @@ class Meme(Cog):
|
|||
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(hidden=True, name="warm")
|
||||
async def warm_member(self, ctx, user: Optional[discord.Member]):
|
||||
async def warm_member(self, ctx, user: discord.Member):
|
||||
"""Warms a user :3"""
|
||||
if user is None and ctx.message.reference is None:
|
||||
celsius = random.randint(15, 20)
|
||||
fahrenheit = self.c_to_f(celsius)
|
||||
kelvin = self.c_to_k(celsius)
|
||||
await ctx.send(
|
||||
f"{ctx.author.mention} tries to warm themself."
|
||||
f" User is now {celsius}°C "
|
||||
f"({fahrenheit}°F, {kelvin}K).\n"
|
||||
"You might have more success warming someone else :3"
|
||||
)
|
||||
else:
|
||||
if user is None:
|
||||
user = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
|
||||
celsius = random.randint(15, 100)
|
||||
fahrenheit = self.c_to_f(celsius)
|
||||
kelvin = self.c_to_k(celsius)
|
||||
|
@ -56,38 +37,10 @@ class Meme(Cog):
|
|||
f"({fahrenheit}°F, {kelvin}K)."
|
||||
)
|
||||
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(hidden=True)
|
||||
async def lick(self, ctx, user: Optional[discord.Member]):
|
||||
"""licks a user :?"""
|
||||
if user is None and ctx.message.reference is None:
|
||||
await ctx.send(f"{ctx.author.mention} licks their lips! 👅")
|
||||
else:
|
||||
if user is None:
|
||||
user = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
await ctx.send(f"{user.mention} has been licked! 👅")
|
||||
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(hidden=True, name="chill", aliases=["cold"])
|
||||
async def chill_member(self, ctx, user: Optional[discord.Member]):
|
||||
async def chill_member(self, ctx, user: discord.Member):
|
||||
"""Chills a user >:3"""
|
||||
if user is None and ctx.message.reference is None:
|
||||
celsius = random.randint(-75, 10)
|
||||
fahrenheit = self.c_to_f(celsius)
|
||||
kelvin = self.c_to_k(celsius)
|
||||
await ctx.send(
|
||||
f"{ctx.author.mention} chills themself."
|
||||
f" User is now {celsius}°C "
|
||||
f"({fahrenheit}°F, {kelvin}K).\n"
|
||||
"🧊 Don't be so hard on yourself. 😔"
|
||||
)
|
||||
else:
|
||||
if user is None:
|
||||
user = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
celsius = random.randint(-50, 15)
|
||||
fahrenheit = self.c_to_f(celsius)
|
||||
kelvin = self.c_to_k(celsius)
|
||||
|
@ -99,30 +52,16 @@ class Meme(Cog):
|
|||
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(hidden=True, aliases=["thank", "reswitchedgold"])
|
||||
async def gild(self, ctx, user: Optional[discord.Member]):
|
||||
async def gild(self, ctx, user: discord.Member):
|
||||
"""Gives a star to a user"""
|
||||
if user is None and ctx.message.reference is None:
|
||||
await ctx.send(f"No stars for you, {ctx.author.mention}!")
|
||||
else:
|
||||
if user is None:
|
||||
user = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
await ctx.send(f"{user.mention} gets a :star:, yay!")
|
||||
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(
|
||||
hidden=True, aliases=["reswitchedsilver", "silv3r", "reswitchedsilv3r"]
|
||||
)
|
||||
async def silver(self, ctx, user: Optional[discord.Member]):
|
||||
async def silver(self, ctx, user: discord.Member):
|
||||
"""Gives a user ReSwitched Silver™"""
|
||||
if user is None and ctx.message.reference is None:
|
||||
await ctx.send(f"{ctx.author.mention}, you can't reward yourself.")
|
||||
else:
|
||||
if user is None:
|
||||
user = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
embed = discord.Embed(
|
||||
title="ReSwitched Silver™!",
|
||||
description=f"Here's your ReSwitched Silver™," f"{user.mention}!",
|
||||
|
@ -190,15 +129,8 @@ class Meme(Cog):
|
|||
|
||||
@commands.check(check_if_staff_or_ot)
|
||||
@commands.command(hidden=True, name="bam")
|
||||
async def bam_member(self, ctx, target: Optional[discord.Member]):
|
||||
async def bam_member(self, ctx, target: discord.Member):
|
||||
"""Bams a user owo"""
|
||||
if target is None and ctx.message.reference is None:
|
||||
await ctx.reply("https://tenor.com/view/bonk-gif-26414884")
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
if target == ctx.author:
|
||||
if target.id == 181627658520625152:
|
||||
return await ctx.send(
|
||||
|
@ -242,5 +174,5 @@ class Meme(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Meme(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Meme(bot))
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import io
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog, Context
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff, check_if_bot_manager
|
||||
from robocop_ng.helpers.restrictions import add_restriction, remove_restriction
|
||||
from robocop_ng.helpers.userlogs import userlog
|
||||
from discord.ext.commands import Cog
|
||||
import config
|
||||
from helpers.checks import check_if_staff, check_if_bot_manager
|
||||
from helpers.userlogs import userlog
|
||||
from helpers.restrictions import add_restriction, remove_restriction
|
||||
import io
|
||||
|
||||
|
||||
class Mod(Cog):
|
||||
|
@ -15,7 +13,7 @@ class Mod(Cog):
|
|||
self.bot = bot
|
||||
|
||||
def check_if_target_is_staff(self, target):
|
||||
return any(r.id in self.bot.config.staff_role_ids for r in target.roles)
|
||||
return any(r.id in config.staff_role_ids for r in target.roles)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_bot_manager)
|
||||
|
@ -26,7 +24,7 @@ class Mod(Cog):
|
|||
await ctx.guild.edit(icon=img_bytes, reason=str(ctx.author))
|
||||
await ctx.send(f"Done!")
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
log_msg = (
|
||||
f"✏️ **Guild Icon Update**: {ctx.author} changed the guild icon."
|
||||
f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
@ -38,19 +36,8 @@ class Mod(Cog):
|
|||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def mute(self, ctx, target: Optional[discord.Member], *, reason: str = ""):
|
||||
async def mute(self, ctx, target: discord.Member, *, reason: str = ""):
|
||||
"""Mutes a user, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
reason = str(target) + reason
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
return await ctx.send("You can't do mod actions on yourself.")
|
||||
|
@ -63,7 +50,7 @@ class Mod(Cog):
|
|||
"I can't mute this user as they're a member of staff."
|
||||
)
|
||||
|
||||
userlog(self.bot, target.id, ctx.author, reason, "mutes", target.name)
|
||||
userlog(target.id, ctx.author, reason, "mutes", target.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -80,7 +67,7 @@ class Mod(Cog):
|
|||
# or has DMs disabled
|
||||
pass
|
||||
|
||||
mute_role = ctx.guild.get_role(self.bot.config.mute_role)
|
||||
mute_role = ctx.guild.get_role(config.mute_role)
|
||||
|
||||
await target.add_roles(mute_role, reason=str(ctx.author))
|
||||
|
||||
|
@ -100,10 +87,10 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"{target.mention} can no longer speak.")
|
||||
add_restriction(self.bot, target.id, self.bot.config.mute_role)
|
||||
add_restriction(target.id, config.mute_role)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
|
@ -114,7 +101,7 @@ class Mod(Cog):
|
|||
ctx, str(target)
|
||||
)
|
||||
|
||||
mute_role = ctx.guild.get_role(self.bot.config.mute_role)
|
||||
mute_role = ctx.guild.get_role(config.mute_role)
|
||||
await target.remove_roles(mute_role, reason=str(ctx.author))
|
||||
|
||||
chan_message = (
|
||||
|
@ -125,28 +112,17 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"{target.mention} can now speak again.")
|
||||
remove_restriction(self.bot, target.id, self.bot.config.mute_role)
|
||||
remove_restriction(target.id, config.mute_role)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(kick_members=True)
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def kick(self, ctx, target: Optional[discord.Member], *, reason: str = ""):
|
||||
async def kick(self, ctx, target: discord.Member, *, reason: str = ""):
|
||||
"""Kicks a user, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
reason = str(target) + reason
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
return await ctx.send("You can't do mod actions on yourself.")
|
||||
|
@ -159,7 +135,7 @@ class Mod(Cog):
|
|||
"I can't kick this user as they're a member of staff."
|
||||
)
|
||||
|
||||
userlog(self.bot, target.id, ctx.author, reason, "kicks", target.name)
|
||||
userlog(target.id, ctx.author, reason, "kicks", target.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -198,7 +174,7 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"👢 {safe_name}, 👍.")
|
||||
|
||||
|
@ -206,19 +182,8 @@ class Mod(Cog):
|
|||
@commands.bot_has_permissions(ban_members=True)
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(aliases=["yeet"])
|
||||
async def ban(self, ctx, target: Optional[discord.Member], *, reason: str = ""):
|
||||
async def ban(self, ctx, target: discord.Member, *, reason: str = ""):
|
||||
"""Bans a user, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
reason = str(target) + reason
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
if target.id == 181627658520625152:
|
||||
|
@ -233,7 +198,7 @@ class Mod(Cog):
|
|||
elif self.check_if_target_is_staff(target):
|
||||
return await ctx.send("I can't ban this user as they're a member of staff.")
|
||||
|
||||
userlog(self.bot, target.id, ctx.author, reason, "bans", target.name)
|
||||
userlog(target.id, ctx.author, reason, "bans", target.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -270,29 +235,17 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"{safe_name} is now b&. 👍")
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def bandel(
|
||||
self, ctx, day_count: int, target: Optional[discord.Member], *, reason: str = ""
|
||||
self, ctx, day_count: int, target: discord.Member, *, reason: str = ""
|
||||
):
|
||||
"""Bans a user for a given number of days, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
reason = str(target) + reason
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
if target.id == 181627658520625152:
|
||||
|
@ -312,7 +265,7 @@ class Mod(Cog):
|
|||
"Message delete day count needs to be between 0 and 7 days."
|
||||
)
|
||||
|
||||
userlog(self.bot, target.id, ctx.author, reason, "bans", target.name)
|
||||
userlog(target.id, ctx.author, reason, "bans", target.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -350,7 +303,7 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(
|
||||
f"{safe_name} is now b&, with {day_count} days of messages deleted. 👍"
|
||||
|
@ -374,7 +327,7 @@ class Mod(Cog):
|
|||
elif target_member and self.check_if_target_is_staff(target_member):
|
||||
return await ctx.send("I can't ban this user as they're a member of staff.")
|
||||
|
||||
userlog(self.bot, target, ctx.author, reason, "bans", target_user.name)
|
||||
userlog(target, ctx.author, reason, "bans", target_user.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -399,7 +352,7 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"{safe_name} is now b&. 👍")
|
||||
|
||||
|
@ -428,7 +381,7 @@ class Mod(Cog):
|
|||
)
|
||||
continue
|
||||
|
||||
userlog(self.bot, target, ctx.author, f"massban", "bans", target_user.name)
|
||||
userlog(target, ctx.author, f"massban", "bans", target_user.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -448,7 +401,7 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"All {len(targets_int)} users are now b&. 👍")
|
||||
|
||||
|
@ -481,7 +434,7 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"{safe_name} is now unb&.")
|
||||
|
||||
|
@ -501,7 +454,7 @@ class Mod(Cog):
|
|||
elif self.check_if_target_is_staff(target):
|
||||
return await ctx.send("I can't ban this user as they're a member of staff.")
|
||||
|
||||
userlog(self.bot, target.id, ctx.author, reason, "bans", target.name)
|
||||
userlog(target.id, ctx.author, reason, "bans", target.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -526,34 +479,21 @@ class Mod(Cog):
|
|||
|
||||
chan_message += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
await log_channel.send(chan_message)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def approve(
|
||||
self, ctx, target: Optional[discord.Member], role: str = "community"
|
||||
):
|
||||
async def approve(self, ctx, target: discord.Member, role: str = "community"):
|
||||
"""Add a role to a user (default: community), staff only."""
|
||||
if role not in self.bot.config.named_roles:
|
||||
if role not in config.named_roles:
|
||||
return await ctx.send(
|
||||
"No such role! Available roles: "
|
||||
+ ",".join(self.bot.config.named_roles)
|
||||
"No such role! Available roles: " + ",".join(config.named_roles)
|
||||
)
|
||||
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
target_role = ctx.guild.get_role(self.bot.config.named_roles[role])
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
target_role = ctx.guild.get_role(config.named_roles[role])
|
||||
|
||||
if target_role in target.roles:
|
||||
return await ctx.send("Target already has this role.")
|
||||
|
@ -571,28 +511,15 @@ class Mod(Cog):
|
|||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(aliases=["unapprove"])
|
||||
async def revoke(
|
||||
self, ctx, target: Optional[discord.Member], role: str = "community"
|
||||
):
|
||||
async def revoke(self, ctx, target: discord.Member, role: str = "community"):
|
||||
"""Remove a role from a user (default: community), staff only."""
|
||||
if role not in self.bot.config.named_roles:
|
||||
if role not in config.named_roles:
|
||||
return await ctx.send(
|
||||
"No such role! Available roles: "
|
||||
+ ",".join(self.bot.config.named_roles)
|
||||
"No such role! Available roles: " + ",".join(config.named_roles)
|
||||
)
|
||||
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
target_role = ctx.guild.get_role(self.bot.config.named_roles[role])
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
target_role = ctx.guild.get_role(config.named_roles[role])
|
||||
|
||||
if target_role not in target.roles:
|
||||
return await ctx.send("Target doesn't have this role.")
|
||||
|
@ -612,47 +539,21 @@ class Mod(Cog):
|
|||
@commands.command(aliases=["clear"])
|
||||
async def purge(self, ctx, limit: int, channel: discord.TextChannel = None):
|
||||
"""Clears a given number of messages, staff only."""
|
||||
modlog_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
|
||||
purged_log_jump_url = ""
|
||||
for deleted_message in await channel.purge(limit=limit):
|
||||
msg = (
|
||||
"🗑️ **Message purged**: \n"
|
||||
f"from {self.bot.escape_message(deleted_message.author.name)} "
|
||||
f"({deleted_message.author.id}), in {deleted_message.channel.mention}:\n"
|
||||
f"`{deleted_message.clean_content}`"
|
||||
)
|
||||
if len(purged_log_jump_url) == 0:
|
||||
purged_log_jump_url = (await log_channel.send(msg)).jump_url
|
||||
else:
|
||||
await log_channel.send(msg)
|
||||
|
||||
await channel.purge(limit=limit)
|
||||
msg = (
|
||||
f"🗑 **Purged**: {str(ctx.author)} purged {limit} "
|
||||
f"messages in {channel.mention}."
|
||||
f"\n🔗 __Jump__: <{purged_log_jump_url}>"
|
||||
)
|
||||
await modlog_channel.send(msg)
|
||||
await log_channel.send(msg)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def warn(self, ctx, target: Optional[discord.Member], *, reason: str = ""):
|
||||
async def warn(self, ctx, target: discord.Member, *, reason: str = ""):
|
||||
"""Warns a user, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
reason = str(target) + reason
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
return await ctx.send("You can't do mod actions on yourself.")
|
||||
|
@ -665,10 +566,8 @@ class Mod(Cog):
|
|||
"I can't warn this user as they're a member of staff."
|
||||
)
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
warn_count = userlog(
|
||||
self.bot, target.id, ctx.author, reason, "warns", target.name
|
||||
)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
warn_count = userlog(target.id, ctx.author, reason, "warns", target.name)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -683,7 +582,7 @@ class Mod(Cog):
|
|||
if reason:
|
||||
msg += " The given reason is: " + reason
|
||||
msg += (
|
||||
f"\n\nPlease read the rules in {self.bot.config.rules_url}. "
|
||||
f"\n\nPlease read the rules in {config.rules_url}. "
|
||||
f"This is warn #{warn_count}."
|
||||
)
|
||||
if warn_count == 2:
|
||||
|
@ -726,87 +625,13 @@ class Mod(Cog):
|
|||
|
||||
await log_channel.send(chan_msg)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(aliases=["softwarn"])
|
||||
async def hackwarn(self, ctx, target: int, *, reason: str = ""):
|
||||
"""Warns a user with their ID, doesn't message them, staff only."""
|
||||
target_user = await self.bot.fetch_user(target)
|
||||
target_member = ctx.guild.get_member(target)
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author.id:
|
||||
return await ctx.send("You can't do mod actions on yourself.")
|
||||
elif target == self.bot.user:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
elif target_member and self.check_if_target_is_staff(target_member):
|
||||
return await ctx.send(
|
||||
"I can't warn this user as they're a member of staff."
|
||||
)
|
||||
|
||||
warn_count = userlog(
|
||||
self.bot, target, ctx.author, reason, "warns", target_user.name
|
||||
)
|
||||
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
)
|
||||
|
||||
chan_msg = (
|
||||
f"⚠️ **Hackwarned**: {str(ctx.author)} warned "
|
||||
f"{target_user.mention} (warn #{warn_count}) | {safe_name}\n"
|
||||
f"🏷 __User ID__: {target}\n"
|
||||
)
|
||||
|
||||
if warn_count == 4:
|
||||
userlog(
|
||||
self.bot,
|
||||
target,
|
||||
ctx.author,
|
||||
"exceeded warn limit",
|
||||
"bans",
|
||||
target_user.name,
|
||||
)
|
||||
chan_msg += "**This resulted in an auto-hackban.**\n"
|
||||
await ctx.guild.ban(
|
||||
target_user,
|
||||
reason=f"{ctx.author}, reason: exceeded warn limit",
|
||||
delete_message_days=0,
|
||||
)
|
||||
|
||||
if reason:
|
||||
chan_msg += f'✏️ __Reason__: "{reason}"'
|
||||
else:
|
||||
chan_msg += (
|
||||
"Please add an explanation below. In the future"
|
||||
", it is recommended to use "
|
||||
"`.hackwarn <user> [reason]`."
|
||||
)
|
||||
|
||||
chan_msg += f"\n🔗 __Jump__: <{ctx.message.jump_url}>"
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
await log_channel.send(chan_msg)
|
||||
await ctx.send(f"{safe_name} warned. " f"User has {warn_count} warning(s).")
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(aliases=["setnick", "nick"])
|
||||
async def nickname(self, ctx, target: Optional[discord.Member], *, nick: str = ""):
|
||||
async def nickname(self, ctx, target: discord.Member, *, nick: str = ""):
|
||||
"""Sets a user's nickname, staff only.
|
||||
|
||||
Just send .nickname <user> to wipe the nickname."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
|
||||
try:
|
||||
if nick:
|
||||
|
@ -864,70 +689,6 @@ class Mod(Cog):
|
|||
|
||||
await ctx.send("Successfully set bot nickname.")
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def move(self, ctx, channelTo: discord.TextChannel, *, limit: int):
|
||||
"""Move a user to another channel, staff only.
|
||||
|
||||
!move {channel to move to} {number of messages}"""
|
||||
# get a list of the messages
|
||||
fetchedMessages = []
|
||||
|
||||
async for message in ctx.channel.history(limit=limit + 1):
|
||||
fetchedMessages.append(message)
|
||||
|
||||
# delete all of those messages from the channel
|
||||
for i in fetchedMessages:
|
||||
await i.delete()
|
||||
|
||||
# invert the list and remove the last message (gets rid of the command message)
|
||||
fetchedMessages = fetchedMessages[::-1]
|
||||
fetchedMessages = fetchedMessages[:-1]
|
||||
|
||||
# Loop over the messages fetched
|
||||
for message in fetchedMessages:
|
||||
# if the message is embedded already
|
||||
if message.embeds:
|
||||
# set the embed message to the old embed object
|
||||
embedMessage = message.embeds[0]
|
||||
# else
|
||||
else:
|
||||
# Create embed message object and set content to original
|
||||
embedMessage = discord.Embed(description=message.content)
|
||||
|
||||
avatar_url = None
|
||||
|
||||
if message.author.display_avatar is not None:
|
||||
avatar_url = str(message.author.display_avatar)
|
||||
|
||||
# set the embed message author to original author
|
||||
embedMessage.set_author(name=message.author, icon_url=avatar_url)
|
||||
# if message has attachments add them
|
||||
if message.attachments:
|
||||
for i in message.attachments:
|
||||
embedMessage.set_image(url=i.proxy_url)
|
||||
|
||||
# Send to the desired channel
|
||||
await channelTo.send(embed=embedMessage)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command(aliases=["slow"])
|
||||
async def slowmode(
|
||||
self, ctx: Context, seconds: int, channel: Optional[discord.TextChannel] = None
|
||||
):
|
||||
if channel is None:
|
||||
channel = ctx.channel
|
||||
|
||||
if seconds > 21600 or seconds < 0:
|
||||
return await ctx.send("Seconds can't be above '21600' or less then '0'")
|
||||
|
||||
await channel.edit(
|
||||
slowmode_delay=seconds, reason=f"{str(ctx.author)} set the slowmode"
|
||||
)
|
||||
await ctx.send(f"Set the slowmode delay in this channel to {seconds} seconds!")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Mod(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Mod(bot))
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from robocop_ng.helpers.userlogs import userlog
|
||||
from helpers.checks import check_if_staff
|
||||
from helpers.userlogs import userlog
|
||||
|
||||
|
||||
class ModNote(Cog):
|
||||
|
@ -15,7 +14,7 @@ class ModNote(Cog):
|
|||
@commands.command(aliases=["addnote"])
|
||||
async def note(self, ctx, target: discord.Member, *, note: str = ""):
|
||||
"""Adds a note to a user, staff only."""
|
||||
userlog(self.bot, target.id, ctx.author, note, "notes", target.name)
|
||||
userlog(target.id, ctx.author, note, "notes", target.name)
|
||||
await ctx.send(f"{ctx.author.mention}: noted!")
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -23,9 +22,9 @@ class ModNote(Cog):
|
|||
@commands.command(aliases=["addnoteid"])
|
||||
async def noteid(self, ctx, target: int, *, note: str = ""):
|
||||
"""Adds a note to a user by userid, staff only."""
|
||||
userlog(self.bot, target, ctx.author, note, "notes")
|
||||
userlog(target, ctx.author, note, "notes")
|
||||
await ctx.send(f"{ctx.author.mention}: noted!")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(ModNote(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(ModNote(bot))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import asyncio
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
import config
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class ModReact(Cog):
|
||||
|
@ -23,16 +22,16 @@ class ModReact(Cog):
|
|||
limit: int = 50,
|
||||
):
|
||||
"""Clears reacts from a given user in the given channel, staff only."""
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
count = 0
|
||||
async for msg in channel.history(limit=limit):
|
||||
for react in msg.reactions:
|
||||
async for react_user in react.users():
|
||||
if react_user == user:
|
||||
if await react.users().find(lambda u: u == user):
|
||||
count += 1
|
||||
await react.remove(user)
|
||||
async for u in react.users():
|
||||
await msg.remove_reaction(react, u)
|
||||
msg = (
|
||||
f"✏️ **Cleared reacts**: {ctx.author.mention} cleared "
|
||||
f"{user.mention}'s reacts from the last {limit} messages "
|
||||
|
@ -48,7 +47,7 @@ class ModReact(Cog):
|
|||
self, ctx, *, limit: int = 50, channel: discord.TextChannel = None
|
||||
):
|
||||
"""Clears all reacts in a given channel, staff only. Use with care."""
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
count = 0
|
||||
|
@ -119,5 +118,5 @@ class ModReact(Cog):
|
|||
await msg.edit(content=f"{msg_text} Done!")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(ModReact(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(ModReact(bot))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import config
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class ModReswitched(Cog):
|
||||
|
@ -12,10 +12,10 @@ class ModReswitched(Cog):
|
|||
@commands.command(aliases=["pingmods", "summonmods"])
|
||||
async def pingmod(self, ctx):
|
||||
"""Pings mods, only use when there's an emergency."""
|
||||
can_ping = any(r.id in self.bot.config.pingmods_allow for r in ctx.author.roles)
|
||||
can_ping = any(r.id in config.pingmods_allow for r in ctx.author.roles)
|
||||
if can_ping:
|
||||
await ctx.send(
|
||||
f"<@&{self.bot.config.pingmods_role}>: {ctx.author.mention} needs assistance."
|
||||
f"<@&{config.pingmods_role}>: {ctx.author.mention} needs assistance."
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
|
@ -27,7 +27,7 @@ class ModReswitched(Cog):
|
|||
@commands.command(aliases=["togglemod"])
|
||||
async def modtoggle(self, ctx):
|
||||
"""Toggles your mod role, staff only."""
|
||||
target_role = ctx.guild.get_role(self.bot.config.modtoggle_role)
|
||||
target_role = ctx.guild.get_role(config.modtoggle_role)
|
||||
|
||||
if target_role in ctx.author.roles:
|
||||
await ctx.author.remove_roles(
|
||||
|
@ -41,5 +41,5 @@ class ModReswitched(Cog):
|
|||
await ctx.send(f"{ctx.author.mention}: Gave you mod role.")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(ModReswitched(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(ModReswitched(bot))
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
import config
|
||||
from datetime import datetime
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from robocop_ng.helpers.restrictions import add_restriction
|
||||
from robocop_ng.helpers.robocronp import add_job
|
||||
from robocop_ng.helpers.userlogs import userlog
|
||||
from helpers.checks import check_if_staff
|
||||
from helpers.robocronp import add_job
|
||||
from helpers.userlogs import userlog
|
||||
from helpers.restrictions import add_restriction
|
||||
|
||||
|
||||
class ModTimed(Cog):
|
||||
|
@ -16,27 +14,16 @@ class ModTimed(Cog):
|
|||
self.bot = bot
|
||||
|
||||
def check_if_target_is_staff(self, target):
|
||||
return any(r.id in self.bot.config.staff_role_ids for r in target.roles)
|
||||
return any(r.id in config.staff_role_ids for r in target.roles)
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def timeban(
|
||||
self, ctx, target: Optional[discord.Member], duration: str, *, reason: str = ""
|
||||
self, ctx, target: discord.Member, duration: str, *, reason: str = ""
|
||||
):
|
||||
"""Bans a user for a specified amount of time, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
duration = str(target) + duration
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
return await ctx.send("You can't do mod actions on yourself.")
|
||||
|
@ -50,7 +37,6 @@ class ModTimed(Cog):
|
|||
)
|
||||
|
||||
userlog(
|
||||
self.bot,
|
||||
target.id,
|
||||
ctx.author,
|
||||
f"{reason} (Timed, until " f"{duration_text})",
|
||||
|
@ -91,9 +77,9 @@ class ModTimed(Cog):
|
|||
" as the reason is automatically sent to the user."
|
||||
)
|
||||
|
||||
add_job(self.bot, "unban", target.id, {"guild": ctx.guild.id}, expiry_timestamp)
|
||||
add_job("unban", target.id, {"guild": ctx.guild.id}, expiry_timestamp)
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(f"{safe_name} is now b&. " f"It will expire {duration_text}. 👍")
|
||||
|
||||
|
@ -101,20 +87,9 @@ class ModTimed(Cog):
|
|||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def timemute(
|
||||
self, ctx, target: Optional[discord.Member], duration: str, *, reason: str = ""
|
||||
self, ctx, target: discord.Member, duration: str, *, reason: str = ""
|
||||
):
|
||||
"""Mutes a user for a specified amount of time, staff only."""
|
||||
if target is None and ctx.message.reference is None:
|
||||
return await ctx.send(
|
||||
f"I'm sorry {ctx.author.mention}, I'm afraid I can't do that."
|
||||
)
|
||||
else:
|
||||
if ctx.message.reference is not None:
|
||||
if target is not None:
|
||||
duration = str(target) + duration
|
||||
target = (
|
||||
await ctx.channel.fetch_message(ctx.message.reference.message_id)
|
||||
).author
|
||||
# Hedge-proofing the code
|
||||
if target == ctx.author:
|
||||
return await ctx.send("You can't do mod actions on yourself.")
|
||||
|
@ -130,7 +105,6 @@ class ModTimed(Cog):
|
|||
)
|
||||
|
||||
userlog(
|
||||
self.bot,
|
||||
target.id,
|
||||
ctx.author,
|
||||
f"{reason} (Timed, until " f"{duration_text})",
|
||||
|
@ -154,7 +128,7 @@ class ModTimed(Cog):
|
|||
# or has DMs disabled
|
||||
pass
|
||||
|
||||
mute_role = ctx.guild.get_role(self.bot.config.mute_role)
|
||||
mute_role = ctx.guild.get_role(config.mute_role)
|
||||
|
||||
await target.add_roles(mute_role, reason=str(ctx.author))
|
||||
|
||||
|
@ -172,17 +146,15 @@ class ModTimed(Cog):
|
|||
" as the reason is automatically sent to the user."
|
||||
)
|
||||
|
||||
add_job(
|
||||
self.bot, "unmute", target.id, {"guild": ctx.guild.id}, expiry_timestamp
|
||||
)
|
||||
add_job("unmute", target.id, {"guild": ctx.guild.id}, expiry_timestamp)
|
||||
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
await log_channel.send(chan_message)
|
||||
await ctx.send(
|
||||
f"{target.mention} can no longer speak. " f"It will expire {duration_text}."
|
||||
)
|
||||
add_restriction(self.bot, target.id, self.bot.config.mute_role)
|
||||
add_restriction(target.id, config.mute_role)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(ModTimed(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(ModTimed(bot))
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import json
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from robocop_ng.helpers.userlogs import get_userlog, set_userlog, userlog_event_types
|
||||
import config
|
||||
import json
|
||||
from helpers.checks import check_if_staff
|
||||
from helpers.userlogs import get_userlog, set_userlog, userlog_event_types
|
||||
|
||||
|
||||
class ModUserlog(Cog):
|
||||
|
@ -21,7 +20,7 @@ class ModUserlog(Cog):
|
|||
wanted_events = [event]
|
||||
embed = discord.Embed(color=discord.Color.dark_red())
|
||||
embed.set_author(name=f"Userlog for {name}")
|
||||
userlog = get_userlog(self.bot)
|
||||
userlog = get_userlog()
|
||||
|
||||
if uid not in userlog:
|
||||
embed.description = f"There are none!{own_note} (no entry)"
|
||||
|
@ -54,18 +53,18 @@ class ModUserlog(Cog):
|
|||
return embed
|
||||
|
||||
def clear_event_from_id(self, uid: str, event_type):
|
||||
userlog = get_userlog(self.bot)
|
||||
userlog = get_userlog()
|
||||
if uid not in userlog:
|
||||
return f"<@{uid}> has no {event_type}!"
|
||||
event_count = len(userlog[uid][event_type])
|
||||
if not event_count:
|
||||
return f"<@{uid}> has no {event_type}!"
|
||||
userlog[uid][event_type] = []
|
||||
set_userlog(self.bot, json.dumps(userlog))
|
||||
set_userlog(json.dumps(userlog))
|
||||
return f"<@{uid}> no longer has any {event_type}!"
|
||||
|
||||
def delete_event_from_id(self, uid: str, idx: int, event_type):
|
||||
userlog = get_userlog(self.bot)
|
||||
userlog = get_userlog()
|
||||
if uid not in userlog:
|
||||
return f"<@{uid}> has no {event_type}!"
|
||||
event_count = len(userlog[uid][event_type])
|
||||
|
@ -84,7 +83,7 @@ class ModUserlog(Cog):
|
|||
f"Reason: {event['reason']}",
|
||||
)
|
||||
del userlog[uid][event_type][idx - 1]
|
||||
set_userlog(self.bot, json.dumps(userlog))
|
||||
set_userlog(json.dumps(userlog))
|
||||
return embed
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -136,7 +135,7 @@ class ModUserlog(Cog):
|
|||
@commands.command(aliases=["clearwarns"])
|
||||
async def clearevent(self, ctx, target: discord.Member, event="warns"):
|
||||
"""Clears all events of given type for a user, staff only."""
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
msg = self.clear_event_from_id(str(target.id), event)
|
||||
safe_name = await commands.clean_content(escape_markdown=True).convert(
|
||||
ctx, str(target)
|
||||
|
@ -155,7 +154,7 @@ class ModUserlog(Cog):
|
|||
@commands.command(aliases=["clearwarnsid"])
|
||||
async def cleareventid(self, ctx, target: int, event="warns"):
|
||||
"""Clears all events of given type for a userid, staff only."""
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
msg = self.clear_event_from_id(str(target), event)
|
||||
await ctx.send(msg)
|
||||
msg = (
|
||||
|
@ -170,7 +169,7 @@ class ModUserlog(Cog):
|
|||
@commands.command(aliases=["delwarn"])
|
||||
async def delevent(self, ctx, target: discord.Member, idx: int, event="warns"):
|
||||
"""Removes a specific event from a user, staff only."""
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
del_event = self.delete_event_from_id(str(target.id), idx, event)
|
||||
event_name = userlog_event_types[event].lower()
|
||||
# This is hell.
|
||||
|
@ -194,7 +193,7 @@ class ModUserlog(Cog):
|
|||
@commands.command(aliases=["delwarnid"])
|
||||
async def deleventid(self, ctx, target: int, idx: int, event="warns"):
|
||||
"""Removes a specific event from a userid, staff only."""
|
||||
log_channel = self.bot.get_channel(self.bot.config.modlog_channel)
|
||||
log_channel = self.bot.get_channel(config.modlog_channel)
|
||||
del_event = self.delete_event_from_id(str(target), idx, event)
|
||||
event_name = userlog_event_types[event].lower()
|
||||
# This is hell.
|
||||
|
@ -234,7 +233,7 @@ class ModUserlog(Cog):
|
|||
await ctx.send(
|
||||
f"user = {user_name}\n"
|
||||
f"id = {user.id}\n"
|
||||
f"avatar = {user.display_avatar}\n"
|
||||
f"avatar = {user.avatar_url}\n"
|
||||
f"bot = {user.bot}\n"
|
||||
f"created_at = {user.created_at}\n"
|
||||
f"display_name = {display_name}\n"
|
||||
|
@ -245,5 +244,5 @@ class ModUserlog(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(ModUserlog(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(ModUserlog(bot))
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from robocop_ng.helpers.userlogs import setwatch
|
||||
from helpers.checks import check_if_staff
|
||||
from helpers.userlogs import setwatch
|
||||
|
||||
|
||||
class ModWatch(Cog):
|
||||
|
@ -15,7 +14,7 @@ class ModWatch(Cog):
|
|||
@commands.command()
|
||||
async def watch(self, ctx, target: discord.Member, *, note: str = ""):
|
||||
"""Puts a user under watch, staff only."""
|
||||
setwatch(self.bot, target.id, ctx.author, True, target.name)
|
||||
setwatch(target.id, ctx.author, True, target.name)
|
||||
await ctx.send(f"{ctx.author.mention}: user is now on watch.")
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -23,7 +22,7 @@ class ModWatch(Cog):
|
|||
@commands.command()
|
||||
async def watchid(self, ctx, target: int, *, note: str = ""):
|
||||
"""Puts a user under watch by userid, staff only."""
|
||||
setwatch(self.bot, target, ctx.author, True, target.name)
|
||||
setwatch(target, ctx.author, True, target.name)
|
||||
await ctx.send(f"{target.mention}: user is now on watch.")
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -31,7 +30,7 @@ class ModWatch(Cog):
|
|||
@commands.command()
|
||||
async def unwatch(self, ctx, target: discord.Member, *, note: str = ""):
|
||||
"""Removes a user from watch, staff only."""
|
||||
setwatch(self.bot, target.id, ctx.author, False, target.name)
|
||||
setwatch(target.id, ctx.author, False, target.name)
|
||||
await ctx.send(f"{ctx.author.mention}: user is now not on watch.")
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -39,9 +38,9 @@ class ModWatch(Cog):
|
|||
@commands.command()
|
||||
async def unwatchid(self, ctx, target: int, *, note: str = ""):
|
||||
"""Removes a user from watch by userid, staff only."""
|
||||
setwatch(self.bot, target, ctx.author, False, target.name)
|
||||
setwatch(target, ctx.author, False, target.name)
|
||||
await ctx.send(f"{target.mention}: user is now not on watch.")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(ModWatch(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(ModWatch(bot))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import aiohttp
|
||||
import gidgethub.aiohttp
|
||||
from discord import Embed
|
||||
from discord.enums import MessageType
|
||||
import config
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_collaborator
|
||||
from robocop_ng.helpers.checks import check_if_pin_channel
|
||||
from discord.enums import MessageType
|
||||
from discord import Embed
|
||||
import aiohttp
|
||||
import gidgethub.aiohttp
|
||||
from helpers.checks import check_if_collaborator
|
||||
from helpers.checks import check_if_pin_channel
|
||||
|
||||
|
||||
class Pin(Cog):
|
||||
|
@ -59,13 +59,13 @@ class Pin(Cog):
|
|||
return (data["id"], data["files"]["pinboard.md"]["content"])
|
||||
|
||||
async def add_pin_to_pinboard(self, channel, data):
|
||||
if self.bot.config.github_oauth_token == "":
|
||||
if config.github_oauth_token == "":
|
||||
# Don't add to gist pinboard if we don't have an oauth token
|
||||
return
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
gh = gidgethub.aiohttp.GitHubAPI(
|
||||
session, "RoboCop-NG", oauth_token=self.bot.config.github_oauth_token
|
||||
session, "RoboCop-NG", oauth_token=config.github_oauth_token
|
||||
)
|
||||
(id, content) = await self.get_pinboard(gh, channel)
|
||||
content += "- " + data + "\n"
|
||||
|
@ -102,7 +102,7 @@ class Pin(Cog):
|
|||
return
|
||||
|
||||
# Check that reaction pinning is allowd in this channel
|
||||
if payload.channel_id not in self.bot.config.allowed_pin_channels:
|
||||
if payload.channel_id not in config.allowed_pin_channels:
|
||||
return
|
||||
|
||||
target_guild = self.bot.get_guild(payload.guild_id)
|
||||
|
@ -111,7 +111,7 @@ class Pin(Cog):
|
|||
|
||||
# Check that the user is allowed to reaction-pin
|
||||
target_user = target_guild.get_member(payload.user_id)
|
||||
for role in self.bot.config.staff_role_ids + self.bot.config.allowed_pin_roles:
|
||||
for role in config.staff_role_ids + config.allowed_pin_roles:
|
||||
if role in [role.id for role in target_user.roles]:
|
||||
target_chan = self.bot.get_channel(payload.channel_id)
|
||||
target_msg = await target_chan.get_message(payload.message_id)
|
||||
|
@ -157,5 +157,5 @@ def check(msg):
|
|||
return msg.type is MessageType.pins_add
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Pin(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Pin(bot))
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import discord
|
||||
import asyncio
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.robocronp import add_job, get_crontab
|
||||
from helpers.robocronp import add_job, get_crontab
|
||||
|
||||
|
||||
class Remind(Cog):
|
||||
|
@ -17,7 +15,7 @@ class Remind(Cog):
|
|||
@commands.command()
|
||||
async def remindlist(self, ctx):
|
||||
"""Lists your reminders."""
|
||||
ctab = get_crontab(self.bot)
|
||||
ctab = get_crontab()
|
||||
uid = str(ctx.author.id)
|
||||
embed = discord.Embed(title=f"Active robocronp jobs")
|
||||
for jobtimestamp in ctab["remind"]:
|
||||
|
@ -39,15 +37,8 @@ class Remind(Cog):
|
|||
@commands.command(aliases=["remindme"])
|
||||
async def remind(self, ctx, when: str, *, text: str = "something"):
|
||||
"""Reminds you about something."""
|
||||
ref_message = None
|
||||
if ctx.message.reference is not None:
|
||||
ref_message = await ctx.channel.fetch_message(
|
||||
ctx.message.reference.message_id
|
||||
)
|
||||
|
||||
if ctx.guild:
|
||||
await ctx.message.delete()
|
||||
|
||||
current_timestamp = time.time()
|
||||
expiry_timestamp = self.bot.parse_time(when)
|
||||
|
||||
|
@ -65,24 +56,22 @@ class Remind(Cog):
|
|||
)
|
||||
|
||||
safe_text = await commands.clean_content().convert(ctx, str(text))
|
||||
if ref_message is not None:
|
||||
safe_text += f"\nMessage reference: {ref_message.jump_url}"
|
||||
|
||||
added_on = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S (UTC)")
|
||||
|
||||
add_job(
|
||||
self.bot,
|
||||
"remind",
|
||||
ctx.author.id,
|
||||
{"text": safe_text, "added": added_on},
|
||||
expiry_timestamp,
|
||||
)
|
||||
|
||||
await ctx.send(
|
||||
msg = await ctx.send(
|
||||
f"{ctx.author.mention}: I'll remind you in "
|
||||
f"DMs about `{safe_text}` in {duration_text}."
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
await msg.delete()
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Remind(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Remind(bot))
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
import asyncio
|
||||
import config
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import discord
|
||||
from discord.ext import commands, tasks
|
||||
import traceback
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
from robocop_ng.helpers.restrictions import remove_restriction
|
||||
from robocop_ng.helpers.robocronp import get_crontab, delete_job
|
||||
from helpers.robocronp import get_crontab, delete_job
|
||||
from helpers.restrictions import remove_restriction
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class Robocronp(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.minutely.start()
|
||||
self.hourly.start()
|
||||
self.daily.start()
|
||||
|
||||
def cog_unload(self):
|
||||
self.minutely.cancel()
|
||||
self.hourly.cancel()
|
||||
self.daily.cancel()
|
||||
bot.loop.create_task(self.minutely())
|
||||
bot.loop.create_task(self.hourly())
|
||||
bot.loop.create_task(self.daily())
|
||||
|
||||
async def send_data(self):
|
||||
await self.bot.wait_until_ready()
|
||||
data_files = [discord.File(fpath) for fpath in self.bot.wanted_jsons]
|
||||
log_channel = await self.bot.get_channel_safe(self.bot.config.botlog_channel)
|
||||
log_channel = self.bot.get_channel(config.botlog_channel)
|
||||
await log_channel.send("Hourly data backups:", files=data_files)
|
||||
|
||||
@commands.guild_only()
|
||||
|
@ -33,7 +27,7 @@ class Robocronp(Cog):
|
|||
@commands.command()
|
||||
async def listjobs(self, ctx):
|
||||
"""Lists timed robocronp jobs, staff only."""
|
||||
ctab = get_crontab(self.bot)
|
||||
ctab = get_crontab()
|
||||
embed = discord.Embed(title=f"Active robocronp jobs")
|
||||
for jobtype in ctab:
|
||||
for jobtimestamp in ctab[jobtype]:
|
||||
|
@ -58,31 +52,30 @@ class Robocronp(Cog):
|
|||
- job name (userid, like 420332322307571713)
|
||||
|
||||
You can get all 3 from listjobs command."""
|
||||
delete_job(self.bot, timestamp, job_type, job_name)
|
||||
delete_job(timestamp, job_type, job_name)
|
||||
await ctx.send(f"{ctx.author.mention}: Deleted!")
|
||||
|
||||
async def do_jobs(self, ctab, jobtype, timestamp):
|
||||
await self.bot.wait_until_ready()
|
||||
log_channel = await self.bot.get_channel_safe(self.bot.config.botlog_channel)
|
||||
log_channel = self.bot.get_channel(config.botlog_channel)
|
||||
for job_name in ctab[jobtype][timestamp]:
|
||||
try:
|
||||
job_details = ctab[jobtype][timestamp][job_name]
|
||||
if jobtype == "unban":
|
||||
target_user = await self.bot.fetch_user(job_name)
|
||||
target_guild = self.bot.get_guild(job_details["guild"])
|
||||
delete_job(self.bot, timestamp, jobtype, job_name)
|
||||
delete_job(timestamp, jobtype, job_name)
|
||||
await target_guild.unban(
|
||||
target_user, reason="Robocronp: Timed ban expired."
|
||||
)
|
||||
elif jobtype == "unmute":
|
||||
remove_restriction(self.bot, job_name, self.bot.config.mute_role)
|
||||
remove_restriction(job_name, config.mute_role)
|
||||
target_guild = self.bot.get_guild(job_details["guild"])
|
||||
target_member = target_guild.get_member(int(job_name))
|
||||
target_role = target_guild.get_role(self.bot.config.mute_role)
|
||||
target_role = target_guild.get_role(config.mute_role)
|
||||
await target_member.remove_roles(
|
||||
target_role, reason="Robocronp: Timed mute expired."
|
||||
)
|
||||
delete_job(self.bot, timestamp, jobtype, job_name)
|
||||
delete_job(timestamp, jobtype, job_name)
|
||||
elif jobtype == "remind":
|
||||
text = job_details["text"]
|
||||
added_on = job_details["added"]
|
||||
|
@ -91,19 +84,18 @@ class Robocronp(Cog):
|
|||
await target.send(
|
||||
f"You asked to be reminded about `{text}` on {added_on}."
|
||||
)
|
||||
delete_job(self.bot, timestamp, jobtype, job_name)
|
||||
delete_job(timestamp, jobtype, job_name)
|
||||
except:
|
||||
# Don't kill cronjobs if something goes wrong.
|
||||
delete_job(self.bot, timestamp, jobtype, job_name)
|
||||
delete_job(timestamp, jobtype, job_name)
|
||||
await log_channel.send(
|
||||
"Crondo has errored, job deleted: ```"
|
||||
f"{traceback.format_exc()}```"
|
||||
)
|
||||
|
||||
async def clean_channel(self, channel_id):
|
||||
await self.bot.wait_until_ready()
|
||||
log_channel = await self.bot.get_channel_safe(self.bot.config.botlog_channel)
|
||||
channel = await self.bot.get_channel_safe(channel_id)
|
||||
log_channel = self.bot.get_channel(config.botlog_channel)
|
||||
channel = self.bot.get_channel(channel_id)
|
||||
try:
|
||||
done_cleaning = False
|
||||
count = 0
|
||||
|
@ -121,12 +113,12 @@ class Robocronp(Cog):
|
|||
f"Cronclean has errored: ```{traceback.format_exc()}```"
|
||||
)
|
||||
|
||||
@tasks.loop(minutes=1)
|
||||
async def minutely(self):
|
||||
await self.bot.wait_until_ready()
|
||||
log_channel = await self.bot.get_channel_safe(self.bot.config.botlog_channel)
|
||||
log_channel = self.bot.get_channel(config.botlog_channel)
|
||||
while not self.bot.is_closed():
|
||||
try:
|
||||
ctab = get_crontab(self.bot)
|
||||
ctab = get_crontab()
|
||||
timestamp = time.time()
|
||||
for jobtype in ctab:
|
||||
for jobtimestamp in ctab[jobtype]:
|
||||
|
@ -134,46 +126,56 @@ class Robocronp(Cog):
|
|||
await self.do_jobs(ctab, jobtype, jobtimestamp)
|
||||
|
||||
# Handle clean channels
|
||||
for clean_channel in self.bot.config.minutely_clean_channels:
|
||||
for clean_channel in config.minutely_clean_channels:
|
||||
await self.clean_channel(clean_channel)
|
||||
except:
|
||||
# Don't kill cronjobs if something goes wrong.
|
||||
await log_channel.send(
|
||||
f"Cron-minutely has errored: ```{traceback.format_exc()}```"
|
||||
)
|
||||
await asyncio.sleep(60)
|
||||
|
||||
@tasks.loop(hours=1)
|
||||
async def hourly(self):
|
||||
await self.bot.wait_until_ready()
|
||||
log_channel = await self.bot.get_channel_safe(self.bot.config.botlog_channel)
|
||||
log_channel = self.bot.get_channel(config.botlog_channel)
|
||||
while not self.bot.is_closed():
|
||||
# Your stuff that should run at boot
|
||||
# and after that every hour goes here
|
||||
await asyncio.sleep(3600)
|
||||
try:
|
||||
await self.send_data()
|
||||
|
||||
# Handle clean channels
|
||||
for clean_channel in self.bot.config.hourly_clean_channels:
|
||||
for clean_channel in config.hourly_clean_channels:
|
||||
await self.clean_channel(clean_channel)
|
||||
except:
|
||||
# Don't kill cronjobs if something goes wrong.
|
||||
await log_channel.send(
|
||||
f"Cron-hourly has errored: ```{traceback.format_exc()}```"
|
||||
)
|
||||
# Your stuff that should run an hour after boot
|
||||
# and after that every hour goes here
|
||||
|
||||
@tasks.loop(hours=24)
|
||||
async def daily(self):
|
||||
await self.bot.wait_until_ready()
|
||||
log_channel = await self.bot.get_channel_safe(self.bot.config.botlog_channel)
|
||||
log_channel = self.bot.get_channel(config.botlog_channel)
|
||||
while not self.bot.is_closed():
|
||||
# Your stuff that should run at boot
|
||||
# and after that every day goes here
|
||||
try:
|
||||
# Reset verification and algorithm
|
||||
if "cogs.verification" in self.bot.config.initial_cogs:
|
||||
verif_channel = await self.bot.get_channel_safe(
|
||||
self.bot.config.welcome_channel
|
||||
)
|
||||
if "cogs.verification" in config.initial_cogs:
|
||||
verif_channel = self.bot.get_channel(config.welcome_channel)
|
||||
await self.bot.do_resetalgo(verif_channel, "daily robocronp")
|
||||
except:
|
||||
# Don't kill cronjobs if something goes wrong.
|
||||
await log_channel.send(
|
||||
f"Cron-daily has errored: ```{traceback.format_exc()}```"
|
||||
)
|
||||
await asyncio.sleep(86400)
|
||||
# Your stuff that should run a day after boot
|
||||
# and after that every day goes here
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Robocronp(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Robocronp(bot))
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
from discord import RawMemberRemoveEvent, Member
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.roles import add_user_roles, get_user_roles
|
||||
|
||||
|
||||
class RolePersistence(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@Cog.listener()
|
||||
async def on_raw_member_remove(self, payload: RawMemberRemoveEvent):
|
||||
save_roles = []
|
||||
for role in payload.user.roles:
|
||||
if (
|
||||
role.is_assignable()
|
||||
and not role.is_default()
|
||||
and not role.is_premium_subscriber()
|
||||
and not role.is_bot_managed()
|
||||
and not role.is_integration()
|
||||
):
|
||||
save_roles.append(role.id)
|
||||
|
||||
if len(save_roles) > 0:
|
||||
add_user_roles(self.bot, payload.user.id, save_roles)
|
||||
|
||||
@Cog.listener()
|
||||
async def on_member_join(self, member: Member):
|
||||
user_roles = get_user_roles(self.bot, member.id)
|
||||
if len(user_roles) > 0:
|
||||
user_roles = [
|
||||
member.guild.get_role(int(role))
|
||||
for role in user_roles
|
||||
if member.guild.get_role(int(role)) is not None
|
||||
]
|
||||
await member.add_roles(
|
||||
*user_roles, reason="Restoring old roles from `role_persistence`."
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(RolePersistence(bot))
|
|
@ -1,266 +0,0 @@
|
|||
import collections
|
||||
import json
|
||||
import os
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class RyujinxReactionRoles(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.channel_id = (
|
||||
self.bot.config.reaction_roles_channel_id
|
||||
) # The channel to send the reaction role message. (self-roles channel)
|
||||
|
||||
self.file = os.path.join(
|
||||
self.bot.state_dir, "data/reactionroles.json"
|
||||
) # the file to store the required reaction role data. (message id of the RR message.)
|
||||
|
||||
self.msg_id = None
|
||||
self.m = None # the msg object
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def register_reaction_role(self, ctx, target_role_id: int, emoji_name: str):
|
||||
"""Register a reaction role, staff only."""
|
||||
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if emoji_name[0] == "<":
|
||||
emoji_name = emoji_name[1:-1]
|
||||
|
||||
if target_role_id in self.bot.config.staff_role_ids:
|
||||
return await ctx.send("Error: Dangerous role found!")
|
||||
|
||||
target_role = ctx.guild.get_role(target_role_id)
|
||||
|
||||
if target_role is None:
|
||||
return await ctx.send("Error: Role not found!")
|
||||
|
||||
target_role_name = target_role.name
|
||||
|
||||
for key in self.reaction_config["reaction_roles_emoji_map"]:
|
||||
value = self.reaction_config["reaction_roles_emoji_map"][key]
|
||||
if type(value) is str and target_role_name == value:
|
||||
return await ctx.send(f"Error: {target_role_name}: already registered.")
|
||||
|
||||
self.reaction_config["reaction_roles_emoji_map"][emoji_name] = target_role_name
|
||||
self.save_reaction_config(self.reaction_config)
|
||||
await self.reload_reaction_message(False)
|
||||
|
||||
await ctx.send(f"{target_role_name}: registered.")
|
||||
|
||||
def get_emoji_full_name(self, emoji):
|
||||
emoji_name = emoji.name
|
||||
if emoji_name is not None and emoji.id is not None:
|
||||
emoji_name = f":{emoji_name}:{emoji.id}"
|
||||
|
||||
return emoji_name
|
||||
|
||||
def get_role(self, key):
|
||||
return discord.utils.get(
|
||||
self.bot.guilds[0].roles,
|
||||
name=self.get_role_from_emoji(key),
|
||||
)
|
||||
|
||||
def get_role_from_emoji(self, key):
|
||||
value = self.emoji_map.get(key)
|
||||
|
||||
if value is not None and type(value) is not str:
|
||||
return value.get("role")
|
||||
|
||||
return value
|
||||
|
||||
async def generate_embed(self):
|
||||
last_descrption = []
|
||||
description = [
|
||||
"React to this message with the emojis given below to get your 'Looking for LDN game' roles. \n"
|
||||
]
|
||||
|
||||
for x in self.emoji_map:
|
||||
value = self.emoji_map[x]
|
||||
|
||||
emoji = x
|
||||
if len(emoji.split(":")) == 3:
|
||||
emoji = f"<{emoji}>"
|
||||
|
||||
if type(value) is str:
|
||||
description.append(
|
||||
f"{emoji} for __{self.emoji_map.get(x).split('(')[1].split(')')[0]}__"
|
||||
)
|
||||
else:
|
||||
role_name = value["role"]
|
||||
line_fmt = value["fmt"]
|
||||
if value.get("should_be_last", False):
|
||||
last_descrption.append(line_fmt.format(emoji, role_name))
|
||||
else:
|
||||
description.append(line_fmt.format(emoji, role_name))
|
||||
|
||||
embed = discord.Embed(
|
||||
title="**Select your roles**",
|
||||
description="\n".join(description) + "\n" + "\n".join(last_descrption),
|
||||
color=420420,
|
||||
)
|
||||
embed.set_footer(
|
||||
text="To remove a role, simply remove the corresponding reaction."
|
||||
)
|
||||
|
||||
return embed
|
||||
|
||||
async def handle_offline_reaction_add(self):
|
||||
await self.bot.wait_until_ready()
|
||||
for reaction in self.m.reactions:
|
||||
reactions_users = []
|
||||
async for user in reaction.users():
|
||||
reactions_users.append(user)
|
||||
|
||||
for user in reactions_users:
|
||||
emoji_name = str(reaction.emoji)
|
||||
if emoji_name[0] == "<":
|
||||
emoji_name = emoji_name[1:-1]
|
||||
|
||||
if self.get_role_from_emoji(emoji_name) is not None:
|
||||
role = self.get_role(emoji_name)
|
||||
if (
|
||||
not user in role.members
|
||||
and not user.bot
|
||||
and type(user) is discord.Member
|
||||
):
|
||||
await user.add_roles(role)
|
||||
else:
|
||||
await self.m.clear_reaction(reaction.emoji)
|
||||
|
||||
async def handle_offline_reaction_remove(self):
|
||||
await self.bot.wait_until_ready()
|
||||
for emoji in self.emoji_map:
|
||||
for reaction in self.m.reactions:
|
||||
emoji_name = str(reaction.emoji)
|
||||
if emoji_name[0] == "<":
|
||||
emoji_name = emoji_name[1:-1]
|
||||
|
||||
reactions_users = []
|
||||
async for user in reaction.users():
|
||||
reactions_users.append(user)
|
||||
|
||||
role = self.get_role(emoji_name)
|
||||
for user in role.members:
|
||||
if user not in reactions_users:
|
||||
member = self.m.guild.get_member(user.id)
|
||||
if member is not None:
|
||||
await member.remove_roles(role)
|
||||
|
||||
def load_reaction_config(self):
|
||||
if not os.path.exists(self.file):
|
||||
with open(self.file, "w") as f:
|
||||
json.dump({}, f)
|
||||
|
||||
with open(self.file, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_reaction_config(self, value):
|
||||
with open(self.file, "w") as f:
|
||||
json.dump(value, f)
|
||||
|
||||
async def reload_reaction_message(self, should_handle_offline=True):
|
||||
await self.bot.wait_until_ready()
|
||||
self.emoji_map = collections.OrderedDict(
|
||||
sorted(
|
||||
self.reaction_config["reaction_roles_emoji_map"].items(),
|
||||
key=lambda x: str(x[1]),
|
||||
)
|
||||
)
|
||||
|
||||
guild = self.bot.guilds[0] # The ryu guild in which the bot is.
|
||||
channel = guild.get_channel(self.channel_id)
|
||||
|
||||
if channel is None:
|
||||
channel = await guild.fetch_channel(self.channel_id)
|
||||
|
||||
history = []
|
||||
async for msg in channel.history():
|
||||
history.append(msg)
|
||||
|
||||
m = discord.utils.get(history, id=self.reaction_config["id"])
|
||||
if m is None:
|
||||
self.reaction_config["id"] = None
|
||||
|
||||
embed = await self.generate_embed()
|
||||
self.m = await channel.send(embed=embed)
|
||||
self.msg_id = self.m.id
|
||||
|
||||
for x in self.emoji_map:
|
||||
await self.m.add_reaction(x)
|
||||
|
||||
self.reaction_config["id"] = self.m.id
|
||||
self.save_reaction_config(self.reaction_config)
|
||||
|
||||
await self.handle_offline_reaction_remove()
|
||||
|
||||
else:
|
||||
self.m = m
|
||||
self.msg_id = self.m.id
|
||||
|
||||
await self.m.edit(embed=await self.generate_embed())
|
||||
for x in self.emoji_map:
|
||||
if not x in self.m.reactions:
|
||||
await self.m.add_reaction(x)
|
||||
|
||||
if should_handle_offline:
|
||||
await self.handle_offline_reaction_add()
|
||||
await self.handle_offline_reaction_remove()
|
||||
|
||||
@Cog.listener()
|
||||
async def on_ready(self):
|
||||
await self.bot.wait_until_ready()
|
||||
self.reaction_config = self.load_reaction_config()
|
||||
|
||||
await self.reload_reaction_message()
|
||||
|
||||
@Cog.listener()
|
||||
async def on_raw_reaction_add(self, payload):
|
||||
await self.bot.wait_until_ready()
|
||||
if payload.member.bot:
|
||||
pass
|
||||
else:
|
||||
if payload.message_id == self.msg_id:
|
||||
emoji_name = self.get_emoji_full_name(payload.emoji)
|
||||
|
||||
if self.get_role_from_emoji(emoji_name) is not None:
|
||||
target_role = self.get_role(emoji_name)
|
||||
|
||||
if target_role is not None:
|
||||
await payload.member.add_roles(target_role)
|
||||
else:
|
||||
self.bot.log.error(
|
||||
f"Role {self.emoji_map[emoji_name]} not found."
|
||||
)
|
||||
await self.m.clear_reaction(payload.emoji)
|
||||
else:
|
||||
await self.m.clear_reaction(payload.emoji)
|
||||
|
||||
@Cog.listener()
|
||||
async def on_raw_reaction_remove(self, payload):
|
||||
await self.bot.wait_until_ready()
|
||||
if payload.message_id == self.msg_id:
|
||||
emoji_name = self.get_emoji_full_name(payload.emoji)
|
||||
|
||||
if self.get_role_from_emoji(emoji_name) is not None:
|
||||
guild = discord.utils.find(
|
||||
lambda guild: guild.id == payload.guild_id, self.bot.guilds
|
||||
)
|
||||
|
||||
target_role = self.get_role(emoji_name)
|
||||
|
||||
if target_role is not None:
|
||||
await guild.get_member(payload.user_id).remove_roles(
|
||||
self.get_role(emoji_name)
|
||||
) # payload.member.remove_roles will throw error
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(RyujinxReactionRoles(bot))
|
|
@ -1,8 +1,9 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
import config
|
||||
import random
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class RyujinxVerification(Cog):
|
||||
|
@ -17,28 +18,24 @@ class RyujinxVerification(Cog):
|
|||
async def on_member_join(self, member):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if member.guild.id not in self.bot.config.guild_whitelist:
|
||||
if (member.guild.id not in config.guild_whitelist):
|
||||
return
|
||||
|
||||
join_channel = self.bot.get_channel(self.bot.config.welcome_channel)
|
||||
join_channel = self.bot.get_channel(config.welcome_channel)
|
||||
|
||||
if join_channel is not None:
|
||||
await join_channel.send(
|
||||
"Hello {0.mention}! Welcome to Ryujinx! Please read the <#411271165429022730>, and then type the verifying command here to gain access to the rest of the channels.\n\nIf you need help with basic common questions, visit the <#585288848704143371> channel after joining.\n\nIf you need help with Animal Crossing visit the <#692104087889641472> channel for common issues and solutions. If you need help that is not Animal Crossing related, please visit the <#410208610455519243> channel after verifying.".format(
|
||||
member
|
||||
)
|
||||
)
|
||||
await join_channel.send('Hello {0.mention}! Welcome to Ryujinx! Please read the <#411271165429022730>, and then type the verifying command here to gain access to the rest of the channels.\n\nIf you need help with basic common questions, visit the <#585288848704143371> channel after joining.\n\nIf you need help with Animal Crossing visit the <#692104087889641472> channel for common issues and solutions. If you need help that is not Animal Crossing related, please visit the <#410208610455519243> channel after verifying.'.format(member))
|
||||
|
||||
async def process_message(self, message):
|
||||
"""Process the verification process"""
|
||||
if message.channel.id == self.bot.config.welcome_channel:
|
||||
if message.channel.id == config.welcome_channel:
|
||||
# Assign common stuff into variables to make stuff less of a mess
|
||||
mcl = message.content.lower()
|
||||
|
||||
# Get the role we will give in case of success
|
||||
success_role = message.guild.get_role(self.bot.config.participant_role)
|
||||
success_role = message.guild.get_role(config.participant_role)
|
||||
|
||||
if self.bot.config.verification_string == mcl:
|
||||
if config.verification_string == mcl:
|
||||
await message.author.add_roles(success_role)
|
||||
await message.delete()
|
||||
|
||||
|
@ -68,23 +65,21 @@ class RyujinxVerification(Cog):
|
|||
async def on_member_join(self, member):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
if member.guild.id not in self.bot.config.guild_whitelist:
|
||||
if (member.guild.id not in config.guild_whitelist):
|
||||
return
|
||||
|
||||
join_channel = self.bot.get_channel(self.bot.config.welcome_channel)
|
||||
join_channel = self.bot.get_channel(config.welcome_channel)
|
||||
|
||||
if join_channel is not None:
|
||||
await join_channel.send(self.bot.config.join_message.format(member))
|
||||
await join_channel.send(config.join_message.format(member))
|
||||
|
||||
@commands.check(check_if_staff)
|
||||
@commands.command()
|
||||
async def reset(self, ctx, limit: int = 100, force: bool = False):
|
||||
"""Wipes messages and pastes the welcome message again. Staff only."""
|
||||
if ctx.message.channel.id != self.bot.config.welcome_channel and not force:
|
||||
await ctx.send(
|
||||
f"This command is limited to"
|
||||
f" <#{self.bot.config.welcome_channel}>, unless forced."
|
||||
)
|
||||
if ctx.message.channel.id != config.welcome_channel and not force:
|
||||
await ctx.send(f"This command is limited to"
|
||||
f" <#{config.welcome_channel}>, unless forced.")
|
||||
return
|
||||
await self.do_reset(ctx.channel, ctx.author.mention, limit)
|
||||
|
||||
|
@ -95,6 +90,5 @@ class RyujinxVerification(Cog):
|
|||
# We only auto clear the channel daily
|
||||
await self.do_reset(channel, author)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(RyujinxVerification(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(RyujinxVerification(bot))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import config
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff_or_ot
|
||||
from helpers.checks import check_if_staff_or_ot
|
||||
|
||||
|
||||
class SAR(Cog):
|
||||
|
@ -15,8 +15,8 @@ class SAR(Cog):
|
|||
"""Lists self assignable roles."""
|
||||
return await ctx.send(
|
||||
"Self assignable roles in this guild: "
|
||||
+ ",".join(self.bot.config.self_assignable_roles)
|
||||
+ f"\n\nRun `{self.bot.config.prefixes[0]}iam role_name_goes_here` to get or remove one."
|
||||
+ ",".join(config.self_assignable_roles)
|
||||
+ f"\n\nRun `{config.prefixes[0]}iam role_name_goes_here` to get or remove one."
|
||||
)
|
||||
|
||||
@commands.cooldown(1, 30, type=commands.BucketType.user)
|
||||
|
@ -25,12 +25,12 @@ class SAR(Cog):
|
|||
@commands.check(check_if_staff_or_ot)
|
||||
async def iam(self, ctx, role: str):
|
||||
"""Gets you a self assignable role."""
|
||||
if role not in self.bot.config.self_assignable_roles:
|
||||
if role not in config.self_assignable_roles:
|
||||
return await ctx.send(
|
||||
"There's no self assignable role with that name. Run .sar to see what you can self assign."
|
||||
)
|
||||
|
||||
target_role = ctx.guild.get_role(self.bot.config.self_assignable_roles[role])
|
||||
target_role = ctx.guild.get_role(config.self_assignable_roles[role])
|
||||
|
||||
if target_role in ctx.author.roles:
|
||||
await ctx.author.remove_roles(target_role, reason=str(ctx.author))
|
||||
|
@ -44,5 +44,5 @@ class SAR(Cog):
|
|||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(SAR(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(SAR(bot))
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
from discord import Guild
|
||||
from discord.errors import Forbidden
|
||||
from discord.ext import tasks
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
|
||||
class VanityUrl(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.vanity_codes: dict[int, str] = self.bot.config.vanity_codes
|
||||
self.check_changed_vanity_codes.start()
|
||||
|
||||
def cog_unload(self):
|
||||
self.check_changed_vanity_codes.cancel()
|
||||
|
||||
async def update_vanity_code(self, guild: Guild, code: str):
|
||||
if "VANITY_URL" in guild.features and guild.vanity_url_code != code:
|
||||
try:
|
||||
await guild.edit(
|
||||
reason="Configured vanity code was different", vanity_code=code
|
||||
)
|
||||
except Forbidden:
|
||||
self.bot.log.exception(f"Not allowed to edit vanity url for: {guild}")
|
||||
self.cog_unload()
|
||||
await self.bot.unload_extension("robocop_ng.cogs.vanity_url")
|
||||
|
||||
@Cog.listener()
|
||||
async def on_guild_update(self, before: Guild, after: Guild):
|
||||
if after.id in self.vanity_codes:
|
||||
await self.update_vanity_code(after, self.vanity_codes[after.id])
|
||||
|
||||
@tasks.loop(hours=12)
|
||||
async def check_changed_vanity_codes(self):
|
||||
await self.bot.wait_until_ready()
|
||||
for guild, vanity_code in self.vanity_codes.items():
|
||||
await self.update_vanity_code(self.bot.get_guild(guild), vanity_code)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(VanityUrl(bot))
|
|
@ -1,20 +1,19 @@
|
|||
import asyncio
|
||||
import hashlib
|
||||
import itertools
|
||||
import random
|
||||
from inspect import cleandoc
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from robocop_ng.helpers.checks import check_if_staff
|
||||
import asyncio
|
||||
import config
|
||||
import random
|
||||
from inspect import cleandoc
|
||||
import hashlib
|
||||
import itertools
|
||||
from helpers.checks import check_if_staff
|
||||
|
||||
|
||||
class Verification(Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.hash_choice = random.choice(self.bot.config.welcome_hashes)
|
||||
self.hash_choice = random.choice(config.welcome_hashes)
|
||||
|
||||
# Export reset channel functions
|
||||
self.bot.do_reset = self.do_reset
|
||||
|
@ -23,10 +22,10 @@ class Verification(Cog):
|
|||
async def do_reset(self, channel, author, limit: int = 100):
|
||||
await channel.purge(limit=limit)
|
||||
|
||||
await channel.send(self.bot.config.welcome_header)
|
||||
await channel.send(config.welcome_header)
|
||||
rules = [
|
||||
"**{}**. {}".format(i, cleandoc(r))
|
||||
for i, r in enumerate(self.bot.config.welcome_rules, 1)
|
||||
for i, r in enumerate(config.welcome_rules, 1)
|
||||
]
|
||||
rule_choice = random.randint(2, len(rules))
|
||||
hash_choice_str = self.hash_choice.upper()
|
||||
|
@ -34,14 +33,12 @@ class Verification(Cog):
|
|||
hash_choice_str += "-512"
|
||||
elif hash_choice_str == "BLAKE2S":
|
||||
hash_choice_str += "-256"
|
||||
rules[rule_choice - 1] += "\n" + self.bot.config.hidden_term_line.format(
|
||||
hash_choice_str
|
||||
)
|
||||
rules[rule_choice - 1] += "\n" + config.hidden_term_line.format(hash_choice_str)
|
||||
msg = (
|
||||
f"🗑 **Reset**: {author} cleared {limit} messages " f" in {channel.mention}"
|
||||
)
|
||||
msg += f"\n💬 __Current challenge location__: under rule {rule_choice}"
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
await log_channel.send(msg)
|
||||
|
||||
# find rule that puts us over 2,000 characters, if any
|
||||
|
@ -63,19 +60,19 @@ class Verification(Cog):
|
|||
await channel.send(item)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
for x in self.bot.config.welcome_footer:
|
||||
for x in config.welcome_footer:
|
||||
await channel.send(cleandoc(x))
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def do_resetalgo(self, channel, author, limit: int = 100):
|
||||
# randomize hash_choice on reset
|
||||
self.hash_choice = random.choice(tuple(self.bot.config.welcome_hashes))
|
||||
self.hash_choice = random.choice(tuple(config.welcome_hashes))
|
||||
|
||||
msg = (
|
||||
f"📘 **Reset Algorithm**: {author} reset " f"algorithm in {channel.mention}"
|
||||
)
|
||||
msg += f"\n💬 __Current algorithm__: {self.hash_choice.upper()}"
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
await log_channel.send(msg)
|
||||
|
||||
await self.do_reset(channel, author)
|
||||
|
@ -84,10 +81,10 @@ class Verification(Cog):
|
|||
@commands.command()
|
||||
async def reset(self, ctx, limit: int = 100, force: bool = False):
|
||||
"""Wipes messages and pastes the welcome message again. Staff only."""
|
||||
if ctx.message.channel.id != self.bot.config.welcome_channel and not force:
|
||||
if ctx.message.channel.id != config.welcome_channel and not force:
|
||||
await ctx.send(
|
||||
f"This command is limited to"
|
||||
f" <#{self.bot.config.welcome_channel}>, unless forced."
|
||||
f" <#{config.welcome_channel}>, unless forced."
|
||||
)
|
||||
return
|
||||
await self.do_reset(ctx.channel, ctx.author.mention, limit)
|
||||
|
@ -96,10 +93,10 @@ class Verification(Cog):
|
|||
@commands.command()
|
||||
async def resetalgo(self, ctx, limit: int = 100, force: bool = False):
|
||||
"""Resets the verification algorithm and does what reset does. Staff only."""
|
||||
if ctx.message.channel.id != self.bot.config.welcome_channel and not force:
|
||||
if ctx.message.channel.id != config.welcome_channel and not force:
|
||||
await ctx.send(
|
||||
f"This command is limited to"
|
||||
f" <#{self.bot.config.welcome_channel}>, unless forced."
|
||||
f" <#{config.welcome_channel}>, unless forced."
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -110,7 +107,7 @@ class Verification(Cog):
|
|||
Not really a rewrite but more of a port
|
||||
|
||||
Git blame tells me that I should blame/credit Robin Lambertz"""
|
||||
if message.channel.id == self.bot.config.welcome_channel:
|
||||
if message.channel.id == config.welcome_channel:
|
||||
# Assign common stuff into variables to make stuff less of a mess
|
||||
member = message.author
|
||||
full_name = str(member)
|
||||
|
@ -137,7 +134,7 @@ class Verification(Cog):
|
|||
return await chan.send(snark)
|
||||
|
||||
# Get the role we will give in case of success
|
||||
success_role = guild.get_role(self.bot.config.named_roles["participant"])
|
||||
success_role = guild.get_role(config.named_roles["participant"])
|
||||
|
||||
# Get a list of stuff we'll allow and will consider close
|
||||
allowed_names = [f"@{full_name}", full_name, str(member.id)]
|
||||
|
@ -170,13 +167,11 @@ class Verification(Cog):
|
|||
)
|
||||
|
||||
# Detect if the user uses the wrong hash algorithm
|
||||
wrong_hash_algos = list(
|
||||
set(self.bot.config.welcome_hashes) - {self.hash_choice}
|
||||
)
|
||||
wrong_hash_algos = list(set(config.welcome_hashes) - {self.hash_choice})
|
||||
for algo in wrong_hash_algos:
|
||||
for name in itertools.chain(allowed_names, close_names):
|
||||
if hashlib.new(algo, name.encode("utf-8")).hexdigest() in mcl:
|
||||
log_channel = self.bot.get_channel(self.bot.config.log_channel)
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
await log_channel.send(
|
||||
f"User {message.author.mention} tried verification with algo {algo} instead of {self.hash_choice}."
|
||||
)
|
||||
|
@ -223,5 +218,5 @@ class Verification(Cog):
|
|||
await chan.send("💢 I don't have permission to do this.")
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Verification(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(Verification(bot))
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from discord.ext.commands import Cog
|
||||
import re
|
||||
import config
|
||||
import secrets
|
||||
import asyncio
|
||||
import base64
|
||||
import hmac
|
||||
import re
|
||||
import secrets
|
||||
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
|
||||
class YubicoOTP(Cog):
|
||||
|
@ -56,7 +56,7 @@ class YubicoOTP(Cog):
|
|||
return int("".join(hexconv), 16)
|
||||
|
||||
def calc_signature(self, text):
|
||||
key = base64.b64decode(self.bot.config.yubico_otp_secret)
|
||||
key = base64.b64decode(config.yubico_otp_secret)
|
||||
signature_bytes = hmac.digest(key, text.encode(), "SHA1")
|
||||
return base64.b64encode(signature_bytes).decode()
|
||||
|
||||
|
@ -72,10 +72,10 @@ class YubicoOTP(Cog):
|
|||
|
||||
async def validate_yubico_otp(self, otp):
|
||||
nonce = secrets.token_hex(15) # Random number in the valid range
|
||||
params = f"id={self.bot.config.yubico_otp_client_id}&nonce={nonce}&otp={otp}"
|
||||
params = f"id={config.yubico_otp_client_id}&nonce={nonce}&otp={otp}"
|
||||
|
||||
# If secret is supplied, sign our request
|
||||
if self.bot.config.yubico_otp_secret:
|
||||
if config.yubico_otp_secret:
|
||||
params += "&h=" + self.calc_signature(params)
|
||||
|
||||
for api_server in self.api_servers:
|
||||
|
@ -84,22 +84,21 @@ class YubicoOTP(Cog):
|
|||
resp = await self.bot.aiosession.get(url)
|
||||
assert resp.status == 200
|
||||
except Exception as ex:
|
||||
self.bot.log.warning(f"Got {repr(ex)} on {api_server} with otp {otp}.")
|
||||
self.bot.log.warning(
|
||||
f"Got {repr(ex)} on {api_server} with otp {otp}."
|
||||
)
|
||||
continue
|
||||
resptext = await resp.text()
|
||||
|
||||
# Turn the fields to a python dict for easier parsing
|
||||
datafields = resptext.strip().split("\r\n")
|
||||
datafields = {
|
||||
line[: line.index("=")]: line[line.index("=") + 1 :]
|
||||
for line in datafields
|
||||
}
|
||||
datafields = {line[:line.index("=")]: line[line.index("=") + 1:] for line in datafields}
|
||||
|
||||
# Verify nonce
|
||||
assert datafields["nonce"] == nonce
|
||||
|
||||
# Verify signature if secret is present
|
||||
if self.bot.config.yubico_otp_secret:
|
||||
if config.yubico_otp_secret:
|
||||
assert self.validate_response_signature(datafields)
|
||||
|
||||
# If we got a success, then return True
|
||||
|
@ -149,5 +148,5 @@ class YubicoOTP(Cog):
|
|||
await msg.delete()
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(YubicoOTP(bot))
|
||||
def setup(bot):
|
||||
bot.add_cog(YubicoOTP(bot))
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
# Basic bot config, insert your token here, update description if you want
|
||||
prefixes = [".", "!"]
|
||||
client_id = 0
|
||||
token = "token-goes-here"
|
||||
bot_description = "Robocop-NG, the moderation bot of ReSwitched."
|
||||
|
||||
|
@ -40,7 +39,7 @@ initial_cogs = [
|
|||
"cogs.robocronp",
|
||||
"cogs.meme",
|
||||
"cogs.invites",
|
||||
"cogs.yubicootp",
|
||||
"cogs.yubicootp"
|
||||
]
|
||||
|
||||
# The following cogs are also available but aren't loaded by default:
|
||||
|
@ -64,16 +63,12 @@ min_age = datetime.timedelta(minutes=15)
|
|||
# The bot will only work in these guilds
|
||||
guild_whitelist = [269333940928512010] # ReSwitched discord
|
||||
|
||||
# Custom invite URL codes
|
||||
vanity_codes = {269333940928512010: "reswitched"}
|
||||
|
||||
# Named roles to be used with .approve and .revoke
|
||||
# Example: .approve User hacker
|
||||
named_roles = {
|
||||
"community": 420010997877833731,
|
||||
"hacker": 364508795038072833,
|
||||
"participant": 434353085926866946,
|
||||
"pirate": 0,
|
||||
}
|
||||
|
||||
# The bot manager and staff roles
|
||||
|
|
|
@ -22,12 +22,6 @@ def check_if_staff_or_ot(ctx):
|
|||
return is_ot or is_staff or is_bot_cmds
|
||||
|
||||
|
||||
def check_if_staff_or_dm(ctx):
|
||||
if not ctx.guild:
|
||||
return True
|
||||
return any(r.id in config.staff_role_ids for r in ctx.author.roles)
|
||||
|
||||
|
||||
def check_if_collaborator(ctx):
|
||||
if not ctx.guild:
|
||||
return False
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from robocop_ng.helpers.notifications import report_critical_error
|
||||
|
||||
|
||||
def read_json(bot, filepath: str) -> dict:
|
||||
if os.path.isfile(filepath) and os.path.getsize(filepath) > 0:
|
||||
with open(filepath, "r") as f:
|
||||
try:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
content = f.read()
|
||||
report_critical_error(
|
||||
bot,
|
||||
e,
|
||||
additional_info={
|
||||
"file": {"length": len(content), "content": content}
|
||||
},
|
||||
)
|
||||
return {}
|
|
@ -1,189 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_disabled_ids_path(bot) -> str:
|
||||
return os.path.join(bot.state_dir, "data/disabled_ids.json")
|
||||
|
||||
|
||||
def is_app_id_valid(app_id: str) -> bool:
|
||||
return len(app_id) == 16 and app_id.isalnum()
|
||||
|
||||
|
||||
def is_build_id_valid(build_id: str) -> bool:
|
||||
return 32 <= len(build_id) <= 64 and build_id.isalnum()
|
||||
|
||||
|
||||
def is_ro_section_valid(ro_section: dict[str, str]) -> bool:
|
||||
return "module" in ro_section.keys() and "sdk_libraries" in ro_section.keys()
|
||||
|
||||
|
||||
def get_disabled_ids(bot) -> dict[str, dict[str, Union[str, dict[str, str]]]]:
|
||||
disabled_ids = read_json(bot, get_disabled_ids_path(bot))
|
||||
if len(disabled_ids) > 0:
|
||||
# Migration code
|
||||
if "app_id" in disabled_ids.keys():
|
||||
old_disabled_ids = disabled_ids.copy()
|
||||
disabled_ids = {}
|
||||
for key in old_disabled_ids["app_id"].values():
|
||||
disabled_ids[key.lower()] = {
|
||||
"app_id": "",
|
||||
"build_id": "",
|
||||
"ro_section": {},
|
||||
}
|
||||
for id_type in ["app_id", "build_id"]:
|
||||
for value, key in old_disabled_ids[id_type].items():
|
||||
disabled_ids[key.lower()][id_type] = value
|
||||
for key, value in old_disabled_ids["ro_section"].items():
|
||||
disabled_ids[key.lower()]["ro_section"] = value
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
|
||||
return disabled_ids
|
||||
|
||||
|
||||
def set_disabled_ids(bot, contents: dict[str, dict[str, Union[str, dict[str, str]]]]):
|
||||
with open(get_disabled_ids_path(bot), "w") as f:
|
||||
json.dump(contents, f)
|
||||
|
||||
|
||||
def add_disable_id_if_necessary(
|
||||
disable_id: str, disabled_ids: dict[str, dict[str, Union[str, dict[str, str]]]]
|
||||
):
|
||||
if disable_id not in disabled_ids.keys():
|
||||
disabled_ids[disable_id] = {"app_id": "", "build_id": "", "ro_section": {}}
|
||||
|
||||
|
||||
def is_app_id_disabled(bot, app_id: str) -> bool:
|
||||
disabled_app_ids = [
|
||||
entry["app_id"]
|
||||
for entry in get_disabled_ids(bot).values()
|
||||
if len(entry["app_id"]) > 0
|
||||
]
|
||||
app_id = app_id.lower()
|
||||
return app_id in disabled_app_ids
|
||||
|
||||
|
||||
def is_build_id_disabled(bot, build_id: str) -> bool:
|
||||
disabled_build_ids = [
|
||||
entry["build_id"]
|
||||
for entry in get_disabled_ids(bot).values()
|
||||
if len(entry["build_id"]) > 0
|
||||
]
|
||||
build_id = build_id.lower()
|
||||
if len(build_id) < 64:
|
||||
build_id += "0" * (64 - len(build_id))
|
||||
return build_id in disabled_build_ids
|
||||
|
||||
|
||||
def is_ro_section_disabled(bot, ro_section: dict[str, Union[str, list[str]]]) -> bool:
|
||||
disabled_ro_sections = [
|
||||
entry["ro_section"]
|
||||
for entry in get_disabled_ids(bot).values()
|
||||
if len(entry["ro_section"]) > 0
|
||||
]
|
||||
matches = []
|
||||
for disabled_ro_section in disabled_ro_sections:
|
||||
for key, content in disabled_ro_section.items():
|
||||
if key == "module":
|
||||
matches.append(ro_section[key].lower() == content.lower())
|
||||
else:
|
||||
matches.append(ro_section[key] == content)
|
||||
if all(matches) and len(matches) > 0:
|
||||
return True
|
||||
else:
|
||||
matches = []
|
||||
return False
|
||||
|
||||
|
||||
def remove_disable_id(bot, disable_id: str) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
if disable_id in disabled_ids.keys():
|
||||
del disabled_ids[disable_id]
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_disabled_app_id(bot, disable_id: str, app_id: str) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
disable_id = disable_id.lower()
|
||||
app_id = app_id.lower()
|
||||
if not is_app_id_disabled(bot, app_id):
|
||||
add_disable_id_if_necessary(disable_id, disabled_ids)
|
||||
disabled_ids[disable_id]["app_id"] = app_id
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_disabled_build_id(bot, disable_id: str, build_id: str) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
disable_id = disable_id.lower()
|
||||
build_id = build_id.lower()
|
||||
if len(build_id) < 64:
|
||||
build_id += "0" * (64 - len(build_id))
|
||||
if not is_build_id_disabled(bot, build_id):
|
||||
add_disable_id_if_necessary(disable_id, disabled_ids)
|
||||
disabled_ids[disable_id]["build_id"] = build_id
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def remove_disabled_app_id(bot, disable_id: str) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
disable_id = disable_id.lower()
|
||||
if (
|
||||
disable_id in disabled_ids.keys()
|
||||
and len(disabled_ids[disable_id]["app_id"]) > 0
|
||||
):
|
||||
disabled_ids[disable_id]["app_id"] = ""
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def remove_disabled_build_id(bot, disable_id: str) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
disable_id = disable_id.lower()
|
||||
if (
|
||||
disable_id in disabled_ids.keys()
|
||||
and len(disabled_ids[disable_id]["build_id"]) > 0
|
||||
):
|
||||
disabled_ids[disable_id]["build_id"] = ""
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_disabled_ro_section(
|
||||
bot, disable_id: str, ro_section: dict[str, Union[str, list[str]]]
|
||||
) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
disable_id = disable_id.lower()
|
||||
add_disable_id_if_necessary(disable_id, disabled_ids)
|
||||
if len(ro_section) > len(disabled_ids[disable_id]["ro_section"]):
|
||||
for key, content in ro_section.items():
|
||||
if key == "module":
|
||||
disabled_ids[disable_id]["ro_section"][key] = content.lower()
|
||||
else:
|
||||
disabled_ids[disable_id]["ro_section"][key] = content
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def remove_disabled_ro_section(bot, disable_id: str) -> bool:
|
||||
disabled_ids = get_disabled_ids(bot)
|
||||
disable_id = disable_id.lower()
|
||||
if (
|
||||
disable_id in disabled_ids.keys()
|
||||
and len(disabled_ids[disable_id]["ro_section"]) > 0
|
||||
):
|
||||
disabled_ids[disable_id]["ro_section"] = {}
|
||||
set_disabled_ids(bot, disabled_ids)
|
||||
return True
|
||||
return False
|
|
@ -1,47 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_disabled_paths_path(bot) -> str:
|
||||
return os.path.join(bot.state_dir, "data/disabled_paths.json")
|
||||
|
||||
|
||||
def get_disabled_paths(bot) -> list[str]:
|
||||
disabled_paths = read_json(bot, get_disabled_paths_path(bot))
|
||||
if "paths" not in disabled_paths.keys():
|
||||
return []
|
||||
return disabled_paths["paths"]
|
||||
|
||||
|
||||
def set_disabled_paths(bot, contents: list[str]):
|
||||
with open(get_disabled_paths_path(bot), "w") as f:
|
||||
json.dump({"paths": contents}, f)
|
||||
|
||||
|
||||
def is_path_disabled(bot, path: str) -> bool:
|
||||
for disabled_path in get_disabled_paths(bot):
|
||||
if disabled_path in path.strip().lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_disabled_path(bot, disabled_path: str) -> bool:
|
||||
disabled_path = disabled_path.strip().lower()
|
||||
disabled_paths = get_disabled_paths(bot)
|
||||
if disabled_path not in disabled_paths:
|
||||
disabled_paths.append(disabled_path)
|
||||
set_disabled_paths(bot, disabled_paths)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def remove_disabled_path(bot, disabled_path: str) -> bool:
|
||||
disabled_path = disabled_path.strip().lower()
|
||||
disabled_paths = get_disabled_paths(bot)
|
||||
if disabled_path in disabled_paths:
|
||||
disabled_paths.remove(disabled_path)
|
||||
set_disabled_paths(bot, disabled_paths)
|
||||
return True
|
||||
return False
|
|
@ -11,26 +11,17 @@ switch_modules = {
|
|||
9: "Loader ",
|
||||
10: "CMIF (IPC command interface) ",
|
||||
11: "HIPC (IPC) ",
|
||||
12: "TMA ",
|
||||
15: "PM ",
|
||||
16: "NS ",
|
||||
17: "BSDSockets ",
|
||||
17: "Sockets ",
|
||||
18: "HTC ",
|
||||
19: "TSC ",
|
||||
20: "NCM Content ",
|
||||
21: "SM ",
|
||||
22: "RO userland ",
|
||||
23: "Gc ",
|
||||
24: "SDMMC ",
|
||||
25: "OVLN ",
|
||||
26: "SPL ",
|
||||
27: "Socket ",
|
||||
29: "HTCLOW ",
|
||||
30: "Bus",
|
||||
31: "HTCFS ",
|
||||
32: "Async ",
|
||||
33: "Util ",
|
||||
35: "TIPC ",
|
||||
100: "ETHC ",
|
||||
101: "I2C ",
|
||||
102: "GPIO ",
|
||||
|
@ -40,7 +31,6 @@ switch_modules = {
|
|||
108: "XCD ",
|
||||
110: "NIFM ",
|
||||
111: "Hwopus ",
|
||||
112: "LSM6DS3 ",
|
||||
113: "Bluetooth ",
|
||||
114: "VI ",
|
||||
115: "NFP ",
|
||||
|
@ -58,7 +48,6 @@ switch_modules = {
|
|||
128: "AM ",
|
||||
129: "Play Report ",
|
||||
130: "AHID ",
|
||||
131: "Applet ",
|
||||
132: "Home Menu (Qlaunch) ",
|
||||
133: "PCV ",
|
||||
134: "OMM ",
|
||||
|
@ -79,74 +68,37 @@ switch_modules = {
|
|||
149: "CEC",
|
||||
150: "Profiler ",
|
||||
151: "Error Upload ",
|
||||
152: "LIDBE ",
|
||||
153: "Audio ",
|
||||
154: "NPNS ",
|
||||
155: "NPNS HTTP Stream ",
|
||||
156: "IDLE ",
|
||||
157: "ARP ",
|
||||
158: "Updater ",
|
||||
159: "SWKBD ",
|
||||
160: "Network Diagnostics ",
|
||||
161: "NFC Mifare ",
|
||||
162: "Userland assert ",
|
||||
163: "Fatal ",
|
||||
164: "NIM Shop ",
|
||||
165: "SPSM ",
|
||||
166: "AOC ",
|
||||
167: "BGTC ",
|
||||
168: "Userland crash ",
|
||||
169: "SASBUS ",
|
||||
170: "PL ",
|
||||
173: "LBL ",
|
||||
175: "JIT ",
|
||||
176: "HDCP ",
|
||||
177: "OMM ",
|
||||
178: "PDM",
|
||||
179: "OLSC ",
|
||||
180: "SREPO ",
|
||||
181: "Dauth ",
|
||||
182: "STDFU ",
|
||||
183: "Debug ",
|
||||
187: "SPI ",
|
||||
189: "PWM ",
|
||||
191: "RTC",
|
||||
192: "Regulator",
|
||||
193: "LED ",
|
||||
197: "Clkrst",
|
||||
198: "Powctl ",
|
||||
202: "HID ",
|
||||
203: "LDN ",
|
||||
204: "CS ",
|
||||
205: "Irsensor ",
|
||||
206: "Capture ",
|
||||
208: "Manu ",
|
||||
209: "ATK ",
|
||||
210: "Web ",
|
||||
211: "LCS ",
|
||||
211: " ",
|
||||
212: "GRC ",
|
||||
213: "Repair ",
|
||||
214: "Album ",
|
||||
215: "RID ",
|
||||
216: "Migration ",
|
||||
217: "Migration Idc Server ",
|
||||
218: "HIDBUS ",
|
||||
219: "ENS ",
|
||||
223: "Websocket ",
|
||||
227: "CAPMTP ",
|
||||
228: "PGL ",
|
||||
229: "Notification ",
|
||||
230: "INS ",
|
||||
231: "LP2P ",
|
||||
232: "RCD ",
|
||||
235: "PRC ",
|
||||
238: "ECTX ",
|
||||
239: "MNPP ",
|
||||
244: "DP2HDMI ",
|
||||
246: "Sprofile ",
|
||||
250: "NDRM ",
|
||||
499: "TSPM ",
|
||||
500: "Devmenu ",
|
||||
# Libnx
|
||||
345: "libnx ",
|
||||
346: "Homebrew ABI ",
|
||||
|
@ -154,7 +106,6 @@ switch_modules = {
|
|||
348: "libnx Nvidia",
|
||||
349: "libnx Binder",
|
||||
# Support Errors
|
||||
520: "Nverpt",
|
||||
800: "General web-applet",
|
||||
809: "WifiWebAuthApplet",
|
||||
810: "Whitelisted-applet",
|
||||
|
@ -174,7 +125,6 @@ switch_known_errcodes = {
|
|||
0xCE01: "Resource exhaustion ",
|
||||
0xD001: "Memory exhaustion ",
|
||||
0xD201: "Handle-table exhaustion ",
|
||||
0xD401: "Invalid memory state / Invalid memory permissions. ",
|
||||
0xD801: "Invalid memory permissions. ",
|
||||
0xDC01: "Invalid memory range ",
|
||||
0xE001: "Invalid thread priority. ",
|
||||
|
@ -341,28 +291,6 @@ switch_known_errcodes = {
|
|||
0x1D60A: "Invalid in object count. ",
|
||||
0x1D80A: "Invalid out object count. ",
|
||||
0x25A0A: "Out of domain entries. ",
|
||||
0x1423: "Invalid command ID received by tipc processor. ",
|
||||
0x1E23: "Invalid message format received by tipc processor. ",
|
||||
0xC823: "Tipc request was deferred for future processing. ",
|
||||
0xCA23: "Tipc object was closed. ",
|
||||
0x4AF: "Bad version returned from calling the (nnjitpluginGetVersion) symbol. ",
|
||||
0xCAAF: "Input NRO/NRR are too large for the storage buffer. ",
|
||||
0x4B0AF: "Symbol funcptr used by this cmd is not initialized (Control/GenerateCode). ",
|
||||
0x4B2AF: "DllPlugin Not initialized, or plugin NRO has already been loaded. ",
|
||||
0x4B4AF: "An error was returned from calling the symbol funcptr with the Control cmd. ",
|
||||
0x104E7: "Nullptr passed to the LocalCommunicationId control.nacp validation func. ",
|
||||
0x140E7: "GroupInfo field out of range. ",
|
||||
0x142E7: "SupportedPlatform not appropriate for this operation. ",
|
||||
0x146E7: "Invalid ServiceName. ",
|
||||
0x148E7: "Must provide PresharedBinaryKey. ",
|
||||
0x1C0E7: "Requested Priority value not allowed. ",
|
||||
0x1C2E7: "Matching LocalCommunicationId not found in the user-process control.nacp. ",
|
||||
0x200E7: "Invalid flag. ",
|
||||
0x204E7: "Invalid SupportedPlatform. ",
|
||||
0x208E7: "Invalid StaticAesKeyIndex. ",
|
||||
0x20AE7: "MemberCountMax cannot be greater than 8. ",
|
||||
0x210E7: "GroupInfo+8F must be 0x20. ",
|
||||
0xA83: "Unrecognized applet ID ",
|
||||
0x20B: "Unsupported operation ",
|
||||
0xCC0B: "Out of server session memory ",
|
||||
0x11A0B: "Went past maximum during marshalling. ",
|
||||
|
@ -513,26 +441,6 @@ switch_known_errcodes = {
|
|||
0x9F469: "Null Amiibo ECQV BLS key buffer ",
|
||||
0x9F669: "Null Amiibo ECQV BLS certificate buffer ",
|
||||
0x9F869: "Null Amiibo ECQV BLS root certificate buffer ",
|
||||
# erpt error codes
|
||||
0x293: "Not Initialized ",
|
||||
0x493: "Already Initialized ",
|
||||
0x693: "Out of Array Space ",
|
||||
0x893: "Out of Field Space ",
|
||||
0xA93: "Out of Memory ",
|
||||
0xC93: "Not Supported ",
|
||||
0xE93: "Invalid Argument ",
|
||||
0x1093: "Not Found ",
|
||||
0x1293: "Field Category Mismatch ",
|
||||
0x1493: "Field Type Mismatch ",
|
||||
0x1693: "Already Exists ",
|
||||
0x1893: "Corrupt Journal ",
|
||||
0x1A93: "Category Not Found ",
|
||||
0x1C93: "Required Context Missing ",
|
||||
0x1E93: "Required Field Missing ",
|
||||
0x2093: "Formatter Error ",
|
||||
0x2293: "Invalid Power State ",
|
||||
0x2493: "Array Field Too Large ",
|
||||
0x2693: "Already Owned ",
|
||||
0x272: "Generic error ",
|
||||
0xCC74: "Time not set ",
|
||||
0x287C: "Argument is NULL ",
|
||||
|
@ -630,9 +538,6 @@ switch_known_errcodes = {
|
|||
0x31B002: "Operation not supported in nn::fssystem::ConcatenationFile",
|
||||
0x327202: "Writable file not closed when committing",
|
||||
0x35F202: "Mount name not found in table.",
|
||||
0x28CB: "The [6.0.0+] Authentication challenge failed. ",
|
||||
0xE2CB: "The specified LocalCommunicationVersion is less than the AccessPoint LocalCommunicationVersion. ",
|
||||
0xE4CB: "The specified LocalCommunicationVersion is larger than the AccessPoint LocalCommunicationVersion. ",
|
||||
0x21A: "SMC is not implemented",
|
||||
0x61A: "SMC is currently in progress/secmon is busy",
|
||||
0x81A: "Secmon not currently performing async operation",
|
||||
|
@ -652,14 +557,6 @@ switch_known_errcodes = {
|
|||
0xA27A: "Data verification failed",
|
||||
0xB47A: "Invalid API call",
|
||||
0xC47A: "Invalid operation",
|
||||
0x4DA: "StatusManager entry IsValid flag not set, or controller-update currently in-progress. ",
|
||||
0x6DA: "Controller-update failed via the LibraryApplet. ",
|
||||
0x8DA: "Invalid BusHandle. ",
|
||||
0xADA: "StatusManager entry flag +0x0 not set, or device not connected. ",
|
||||
0xEDA: "PollingReceivedData not available. ",
|
||||
0x10DA: "StatusManager entry DeviceEnabled flag not set, or flag +0x0 not set. ",
|
||||
0x12DA: "ExternalDeviceId mismatch. ",
|
||||
0x14DA: "BusHandle already initialized. ",
|
||||
0x290: "Exited Abnormally ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Abormal)",
|
||||
0x690: "Canceled ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Canceled)",
|
||||
0x890: "Rejected", # me_irl
|
||||
|
@ -669,51 +566,14 @@ switch_known_errcodes = {
|
|||
0x198CD: "IR camera invalid handle value.",
|
||||
# FS Codes
|
||||
0xD401: "Error: Passed buffer is not usable for fs library. ",
|
||||
0x7802: "Error: Specified mount name already exists. ",
|
||||
0x7D202: "Error: Specified partition is not found. ",
|
||||
0x7D402: "Error: Specified target is not found. ",
|
||||
0xFA002: "Error: Failed to access SD card. ",
|
||||
0x136802: "Error: Failed to access game card. ",
|
||||
0x177202: "Error: Specified operation is not implemented. ",
|
||||
0x177A02: "Error: Specified value is out of range. ",
|
||||
0x190002: "Error: Failed to allocate memory. ",
|
||||
0x1B5802: "Error: Failed to access MMC. ",
|
||||
0x1F4202: "Error: ROM is corrupted. ",
|
||||
0x219A02: "Error: Save data is corrupted. ",
|
||||
0x232A02: "Error: NCA is corrupted. ",
|
||||
0x23F202: "Error: Integrity verification failed. ",
|
||||
0x244202: "Error: Partition FS is corrupted. ",
|
||||
0x246A02: "Error: Built-in-storage is corrupted. ",
|
||||
0x249202: "Error: FAT FS is corrupted. ",
|
||||
0x24BA02: "Error: HOST FS is corrupted. ",
|
||||
0x1F4002: "Error: Data is corrupted. ",
|
||||
0x271002: "Error: Unexpected failure occurred. ",
|
||||
0x2F5C02: "Error: Invalid size was specified.",
|
||||
0x2F5E02: "Error: Null pointer argument was specified. ",
|
||||
0x2EE002: "Error: Precondition violation. ",
|
||||
0x2EE202: "Error: Invalid argument was specified. ",
|
||||
0x2EE402: "Error: Invalid path was specified. ",
|
||||
0x2EE602: "Error: Too long path was specified. ",
|
||||
0x2EE802: "Error: Invalid path character was specified. ",
|
||||
0x2EEA02: "Error: Invalid path format was specified. ",
|
||||
0x2F5A02: "Error: Invalid offset was specified. ",
|
||||
0x2F5C02: "Error: Invalid size was specified. ",
|
||||
0x2F5E02: "Error: Null pointer argument was specified. ",
|
||||
0x2F6202: "Error: Invalid mount name was specified. ",
|
||||
0x2F6402: "Error: Extension size exceeds max value set in nmeta file. ",
|
||||
0x2F6602: "Error: Extension size is not a multiple of nn::fs::SaveDataExtensionUnitSize. ",
|
||||
0x307202: "Error: OpenMode_AllowAppend is required for implicit extension of file size by WriteFile(). ",
|
||||
0x307002: "Error: Invalid operation for the open mode. ",
|
||||
0x313802: "Error: Unsupported operation. ",
|
||||
0x320002: "Error: Permission denied. ",
|
||||
0x327202: "Error: Close files opened in write mode before committing. ",
|
||||
0x328202: "Error: Specified user doesn't exist. ",
|
||||
0x346402: "Error: Enough journal space is not left. ",
|
||||
0x346A02: "Error: The open count of files and directories reached the limitation. ",
|
||||
0x353602: "Error: Save data extension count reached the limitation. ",
|
||||
0x35F202: "Error: Specified mount name is not found. ",
|
||||
# Fatal
|
||||
0x2A2: "An internal assert occured within the application, application aborted.",
|
||||
0x4A2: "Can be triggered by running svcBreak. The svcBreak params have no affect on the value of the thrown error-code.",
|
||||
0xA8: "Userland ARM undefined instruction exception",
|
||||
0x2A8: "Userland ARM prefetch-abort due to PC set to non-executable region",
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_invites_path(bot):
|
||||
return os.path.join(bot.state_dir, "data/invites.json")
|
||||
|
||||
|
||||
def get_invites(bot) -> dict[str, dict[str, Union[str, int]]]:
|
||||
return read_json(bot, get_invites_path(bot))
|
||||
|
||||
|
||||
def add_invite(bot, invite_id: str, url: str, max_uses: int, code: str):
|
||||
invites = get_invites(bot)
|
||||
invites[invite_id] = {
|
||||
"uses": 0,
|
||||
"url": url,
|
||||
"max_uses": max_uses,
|
||||
code: code,
|
||||
}
|
||||
set_invites(bot, invites)
|
||||
|
||||
|
||||
def set_invites(bot, contents: dict[str, dict[str, Union[str, int]]]):
|
||||
with open(get_invites_path(bot), "w") as f:
|
||||
json.dump(contents, f)
|
|
@ -1,142 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Optional, Union
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_macros_path(bot):
|
||||
return os.path.join(bot.state_dir, "data/macros.json")
|
||||
|
||||
|
||||
def get_macros_dict(bot) -> dict[str, dict[str, Union[list[str], str]]]:
|
||||
macros = read_json(bot, get_macros_path(bot))
|
||||
if len(macros) > 0:
|
||||
# Migration code
|
||||
if "aliases" not in macros.keys():
|
||||
new_macros = {"macros": macros, "aliases": {}}
|
||||
unique_macros = set(new_macros["macros"].values())
|
||||
for macro_text in unique_macros:
|
||||
first_macro_key = ""
|
||||
duplicate_num = 0
|
||||
for key, macro in new_macros["macros"].copy().items():
|
||||
if macro == macro_text and duplicate_num == 0:
|
||||
first_macro_key = key
|
||||
duplicate_num += 1
|
||||
continue
|
||||
elif macro == macro_text:
|
||||
if first_macro_key not in new_macros["aliases"].keys():
|
||||
new_macros["aliases"][first_macro_key] = []
|
||||
new_macros["aliases"][first_macro_key].append(key)
|
||||
del new_macros["macros"][key]
|
||||
duplicate_num += 1
|
||||
|
||||
set_macros(bot, new_macros)
|
||||
return new_macros
|
||||
|
||||
return macros
|
||||
return {"macros": {}, "aliases": {}}
|
||||
|
||||
|
||||
def is_macro_key_available(
|
||||
bot, key: str, macros: dict[str, dict[str, Union[list[str], str]]] = None
|
||||
) -> bool:
|
||||
if macros is None:
|
||||
macros = get_macros_dict(bot)
|
||||
if key in macros["macros"].keys():
|
||||
return False
|
||||
for aliases in macros["aliases"].values():
|
||||
if key in aliases:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def set_macros(bot, contents: dict[str, dict[str, Union[list[str], str]]]):
|
||||
with open(get_macros_path(bot), "w") as f:
|
||||
json.dump(contents, f)
|
||||
|
||||
|
||||
def get_macro(bot, key: str) -> Optional[str]:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
if key in macros["macros"].keys():
|
||||
return macros["macros"][key]
|
||||
for main_key, aliases in macros["aliases"].items():
|
||||
if key in aliases:
|
||||
return macros["macros"][main_key]
|
||||
return None
|
||||
|
||||
|
||||
def add_macro(bot, key: str, message: str) -> bool:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
if is_macro_key_available(bot, key, macros):
|
||||
macros["macros"][key] = message
|
||||
set_macros(bot, macros)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_aliases(bot, key: str, aliases: list[str]) -> bool:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
success = False
|
||||
if key in macros["macros"].keys():
|
||||
for alias in aliases:
|
||||
alias = alias.lower()
|
||||
if is_macro_key_available(bot, alias, macros):
|
||||
if key not in macros["aliases"].keys():
|
||||
macros["aliases"][key] = []
|
||||
macros["aliases"][key].append(alias)
|
||||
success = True
|
||||
if success:
|
||||
set_macros(bot, macros)
|
||||
return success
|
||||
|
||||
|
||||
def edit_macro(bot, key: str, message: str) -> bool:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
if key in macros["macros"].keys():
|
||||
macros["macros"][key] = message
|
||||
set_macros(bot, macros)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def remove_aliases(bot, key: str, aliases: list[str]) -> bool:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
success = False
|
||||
if key not in macros["aliases"].keys():
|
||||
return False
|
||||
for alias in aliases:
|
||||
alias = alias.lower()
|
||||
if alias in macros["aliases"][key]:
|
||||
macros["aliases"][key].remove(alias)
|
||||
if len(macros["aliases"][key]) == 0:
|
||||
del macros["aliases"][key]
|
||||
success = True
|
||||
if success:
|
||||
set_macros(bot, macros)
|
||||
return success
|
||||
|
||||
|
||||
def remove_macro(bot, key: str) -> bool:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
if key in macros["macros"].keys():
|
||||
del macros["macros"][key]
|
||||
set_macros(bot, macros)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def clear_aliases(bot, key: str) -> bool:
|
||||
macros = get_macros_dict(bot)
|
||||
key = key.lower()
|
||||
if key in macros["macros"].keys() and key in macros["aliases"].keys():
|
||||
del macros["aliases"][key]
|
||||
set_macros(bot, macros)
|
||||
return True
|
||||
return False
|
|
@ -1,53 +0,0 @@
|
|||
import json
|
||||
from typing import Optional, Union
|
||||
|
||||
from discord import Message, MessageReference, PartialMessage
|
||||
|
||||
|
||||
MessageReferenceTypes = Union[Message, MessageReference, PartialMessage]
|
||||
|
||||
|
||||
async def notify_management(
|
||||
bot, message: str, reference_message: Optional[MessageReferenceTypes] = None
|
||||
):
|
||||
log_channel = await bot.get_channel_safe(bot.config.botlog_channel)
|
||||
bot_manager_role = log_channel.guild.get_role(bot.config.bot_manager_role_id)
|
||||
|
||||
notification_message = f"{bot_manager_role.mention}:\n"
|
||||
|
||||
if reference_message is not None and reference_message.channel != log_channel:
|
||||
notification_message += f"Message reference: {reference_message.jump_url}\n"
|
||||
notification_message += message
|
||||
|
||||
return await log_channel.send(notification_message)
|
||||
else:
|
||||
notification_message += message
|
||||
|
||||
return await log_channel.send(
|
||||
notification_message,
|
||||
reference=reference_message,
|
||||
mention_author=False,
|
||||
)
|
||||
|
||||
|
||||
async def report_critical_error(
|
||||
bot,
|
||||
error: BaseException,
|
||||
reference_message: Optional[MessageReferenceTypes] = None,
|
||||
additional_info: Optional[dict] = None,
|
||||
):
|
||||
message = "⛔ A critical error occurred!"
|
||||
|
||||
if additional_info is not None:
|
||||
message += f"""
|
||||
```json
|
||||
{json.dumps(additional_info)}
|
||||
```"""
|
||||
|
||||
message += f"""
|
||||
Exception:
|
||||
```
|
||||
{error}
|
||||
```"""
|
||||
|
||||
return await notify_management(bot, message, reference_message)
|
|
@ -1,47 +1,42 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_restrictions_path(bot):
|
||||
return os.path.join(bot.state_dir, "data/restrictions.json")
|
||||
def get_restrictions():
|
||||
with open("data/restrictions.json", "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_restrictions(bot):
|
||||
return read_json(bot, get_restrictions_path(bot))
|
||||
|
||||
|
||||
def set_restrictions(bot, contents):
|
||||
with open(get_restrictions_path(bot), "w") as f:
|
||||
def set_restrictions(contents):
|
||||
with open("data/restrictions.json", "w") as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def get_user_restrictions(bot, uid):
|
||||
def get_user_restrictions(uid):
|
||||
uid = str(uid)
|
||||
rsts = get_restrictions(bot)
|
||||
with open("data/restrictions.json", "r") as f:
|
||||
rsts = json.load(f)
|
||||
if uid in rsts:
|
||||
return rsts[uid]
|
||||
return []
|
||||
|
||||
|
||||
def add_restriction(bot, uid, rst):
|
||||
def add_restriction(uid, rst):
|
||||
# mostly from kurisu source, credits go to ihaveamac
|
||||
uid = str(uid)
|
||||
rsts = get_restrictions(bot)
|
||||
rsts = get_restrictions()
|
||||
if uid not in rsts:
|
||||
rsts[uid] = []
|
||||
if rst not in rsts[uid]:
|
||||
rsts[uid].append(rst)
|
||||
set_restrictions(bot, json.dumps(rsts))
|
||||
set_restrictions(json.dumps(rsts))
|
||||
|
||||
|
||||
def remove_restriction(bot, uid, rst):
|
||||
def remove_restriction(uid, rst):
|
||||
# mostly from kurisu source, credits go to ihaveamac
|
||||
uid = str(uid)
|
||||
rsts = get_restrictions(bot)
|
||||
rsts = get_restrictions()
|
||||
if uid not in rsts:
|
||||
rsts[uid] = []
|
||||
if rst in rsts[uid]:
|
||||
rsts[uid].remove(rst)
|
||||
set_restrictions(bot, json.dumps(rsts))
|
||||
set_restrictions(json.dumps(rsts))
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_crontab_path(bot):
|
||||
return os.path.join(bot.state_dir, "data/robocronptab.json")
|
||||
def get_crontab():
|
||||
with open("data/robocronptab.json", "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_crontab(bot):
|
||||
return read_json(bot, get_crontab_path(bot))
|
||||
|
||||
|
||||
def set_crontab(bot, contents):
|
||||
with open(get_crontab_path(bot), "w") as f:
|
||||
def set_crontab(contents):
|
||||
with open("data/robocronptab.json", "w") as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def add_job(bot, job_type, job_name, job_details, timestamp):
|
||||
def add_job(job_type, job_name, job_details, timestamp):
|
||||
timestamp = str(math.floor(timestamp))
|
||||
job_name = str(job_name)
|
||||
ctab = get_crontab(bot)
|
||||
ctab = get_crontab()
|
||||
|
||||
if job_type not in ctab:
|
||||
ctab[job_type] = {}
|
||||
|
@ -30,14 +24,14 @@ def add_job(bot, job_type, job_name, job_details, timestamp):
|
|||
ctab[job_type][timestamp] = {}
|
||||
|
||||
ctab[job_type][timestamp][job_name] = job_details
|
||||
set_crontab(bot, json.dumps(ctab))
|
||||
set_crontab(json.dumps(ctab))
|
||||
|
||||
|
||||
def delete_job(bot, timestamp, job_type, job_name):
|
||||
def delete_job(timestamp, job_type, job_name):
|
||||
timestamp = str(timestamp)
|
||||
job_name = str(job_name)
|
||||
ctab = get_crontab(bot)
|
||||
ctab = get_crontab()
|
||||
|
||||
del ctab[job_type][timestamp][job_name]
|
||||
|
||||
set_crontab(bot, json.dumps(ctab))
|
||||
set_crontab(json.dumps(ctab))
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import json
|
||||
import os.path
|
||||
import os
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
|
||||
def get_persistent_roles_path(bot):
|
||||
return os.path.join(bot.state_dir, "data/persistent_roles.json")
|
||||
|
||||
|
||||
def get_persistent_roles(bot) -> dict[str, list[str]]:
|
||||
return read_json(bot, get_persistent_roles_path(bot))
|
||||
|
||||
|
||||
def set_persistent_roles(bot, contents: dict[str, list[str]]):
|
||||
with open(get_persistent_roles_path(bot), "w") as f:
|
||||
json.dump(contents, f)
|
||||
|
||||
|
||||
def add_user_roles(bot, uid: int, roles: list[int]):
|
||||
uid = str(uid)
|
||||
roles = [str(x) for x in roles]
|
||||
|
||||
persistent_roles = get_persistent_roles(bot)
|
||||
persistent_roles[uid] = roles
|
||||
set_persistent_roles(bot, persistent_roles)
|
||||
|
||||
|
||||
def get_user_roles(bot, uid: int) -> list[str]:
|
||||
uid = str(uid)
|
||||
persistent_roles = get_persistent_roles(bot)
|
||||
return persistent_roles[uid] if uid in persistent_roles else []
|
|
@ -1,745 +0,0 @@
|
|||
import re
|
||||
from enum import IntEnum, auto
|
||||
from typing import Optional, Union
|
||||
|
||||
from robocop_ng.helpers.disabled_ids import is_build_id_valid
|
||||
from robocop_ng.helpers.size import Size
|
||||
|
||||
|
||||
class CommonError(IntEnum):
|
||||
SHADER_CACHE_COLLISION = auto()
|
||||
DUMP_HASH = auto()
|
||||
SHADER_CACHE_CORRUPTION = auto()
|
||||
UPDATE_KEYS = auto()
|
||||
FILE_PERMISSIONS = auto()
|
||||
FILE_NOT_FOUND = auto()
|
||||
MISSING_SERVICES = auto()
|
||||
VULKAN_OUT_OF_MEMORY = auto()
|
||||
|
||||
|
||||
class RyujinxVersion(IntEnum):
|
||||
MASTER = auto()
|
||||
OLD_MASTER = auto()
|
||||
LDN = auto()
|
||||
MAC = auto()
|
||||
PR = auto()
|
||||
CUSTOM = auto()
|
||||
|
||||
|
||||
class LogAnalyser:
|
||||
_log_text: str
|
||||
_log_errors: list[list[str]]
|
||||
_hardware_info: dict[str, Optional[str]]
|
||||
_emu_info: dict[str, Optional[str]]
|
||||
_game_info: dict[str, Optional[str]]
|
||||
_settings: dict[str, Optional[str]]
|
||||
_notes: Union[set[str], list[str]]
|
||||
|
||||
@staticmethod
|
||||
def is_homebrew(log_file: str) -> bool:
|
||||
return (
|
||||
re.search("Load.*Application: Loading as [Hh]omebrew", log_file) is not None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_filepaths(log_file: str) -> set[str]:
|
||||
return set(
|
||||
x.rstrip("\u0000")
|
||||
for x in re.findall(r"(?:[A-Za-z]:)?(?:[\\/]+[^\\/:\"\r\n]+)+", log_file)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_main_ro_section(log_file: str) -> Optional[dict[str, str]]:
|
||||
ro_section_matches = re.findall(
|
||||
r"PrintRoSectionInfo: main:[\r\n]((?:\s+.*[\r\n])*)", log_file
|
||||
)
|
||||
if ro_section_matches and len(ro_section_matches) > 0:
|
||||
ro_section_match: str = ro_section_matches[-1]
|
||||
ro_section = {"module": "", "sdk_libraries": []}
|
||||
if ro_section_match is None or len(ro_section_match) == 0:
|
||||
return None
|
||||
for line in ro_section_match.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("Module:"):
|
||||
ro_section["module"] = line[8:]
|
||||
elif line.startswith("SDK Libraries:"):
|
||||
ro_section["sdk_libraries"].append(line[19:])
|
||||
elif line.startswith("SDK "):
|
||||
ro_section["sdk_libraries"].append(line[4:])
|
||||
else:
|
||||
break
|
||||
return ro_section
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_app_info(
|
||||
log_file: str,
|
||||
) -> Optional[tuple[str, str, str, list[str], dict[str, str]]]:
|
||||
game_name_match = re.findall(
|
||||
r"Loader [A-Za-z]*: Application Loaded:\s([^;\n\r]*)",
|
||||
log_file,
|
||||
re.MULTILINE,
|
||||
)
|
||||
if game_name_match:
|
||||
game_name = game_name_match[-1].rstrip()
|
||||
app_id_match = re.match(r".* \[([a-zA-Z0-9]*)\]", game_name)
|
||||
if app_id_match:
|
||||
app_id = app_id_match.group(1).strip().upper()
|
||||
else:
|
||||
app_id = ""
|
||||
bids_match_all = re.findall(
|
||||
r"Build ids found for (?:title|application) ([a-zA-Z0-9]*):[\n\r]*((?:\s+.*[\n\r]+)+)",
|
||||
log_file,
|
||||
)
|
||||
if bids_match_all and len(bids_match_all) > 0:
|
||||
bids_match: tuple[str] = bids_match_all[-1]
|
||||
app_id_from_bids = None
|
||||
build_ids = None
|
||||
if bids_match[0] is not None:
|
||||
app_id_from_bids = bids_match[0].strip().upper()
|
||||
if bids_match[1] is not None:
|
||||
build_ids = [
|
||||
bid.strip().upper()
|
||||
for bid in bids_match[1].splitlines()
|
||||
if is_build_id_valid(bid.strip())
|
||||
]
|
||||
|
||||
return (
|
||||
game_name,
|
||||
app_id,
|
||||
app_id_from_bids,
|
||||
build_ids,
|
||||
LogAnalyser.get_main_ro_section(log_file),
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def contains_errors(search_terms, errors):
|
||||
for term in search_terms:
|
||||
for error_lines in errors:
|
||||
line = "\n".join(error_lines)
|
||||
if term in line:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init__(self, log_text: Union[str, list[str]]):
|
||||
self.__init_members()
|
||||
|
||||
if isinstance(log_text, str):
|
||||
self._log_text = log_text.replace("\r\n", "\n")
|
||||
elif isinstance(log_text, list):
|
||||
self._log_text = "\n".join(log_text)
|
||||
else:
|
||||
raise TypeError(log_text)
|
||||
|
||||
# Large files show a header value when not downloaded completely
|
||||
# this regex makes sure that the log text to read starts from the first timestamp, ignoring headers
|
||||
log_file_header_regex = re.compile(r"\d{2}:\d{2}:\d{2}\.\d{3}.*", re.DOTALL)
|
||||
log_file_match = re.search(log_file_header_regex, self._log_text)
|
||||
if log_file_match and log_file_match.group(0) is not None:
|
||||
self._log_text = log_file_match.group(0)
|
||||
else:
|
||||
raise ValueError("No log entries found.")
|
||||
|
||||
self.__get_errors()
|
||||
self.__get_hardware_info()
|
||||
self.__get_settings_info()
|
||||
self.__get_ryujinx_info()
|
||||
self.__get_app_name()
|
||||
self.__get_mods()
|
||||
self.__get_cheats()
|
||||
self.__get_notes()
|
||||
|
||||
def __init_members(self):
|
||||
self._hardware_info = {
|
||||
"cpu": "Unknown",
|
||||
"gpu": "Unknown",
|
||||
"ram": "Unknown",
|
||||
"os": "Unknown",
|
||||
}
|
||||
self._emu_info = {
|
||||
"ryu_version": "Unknown",
|
||||
"ryu_firmware": "Unknown",
|
||||
"logs_enabled": None,
|
||||
}
|
||||
self._game_info = {
|
||||
"game_name": "Unknown",
|
||||
"errors": "No errors found in log",
|
||||
"mods": "No mods found",
|
||||
"cheats": "No cheats found",
|
||||
}
|
||||
self._settings = {
|
||||
"audio_backend": "Unknown",
|
||||
"backend_threading": "Unknown",
|
||||
"docked": "Unknown",
|
||||
"expand_ram": "Unknown",
|
||||
"fs_integrity": "Unknown",
|
||||
"graphics_backend": "Unknown",
|
||||
"ignore_missing_services": "Unknown",
|
||||
"memory_manager": "Unknown",
|
||||
"pptc": "Unknown",
|
||||
"shader_cache": "Unknown",
|
||||
"vsync": "Unknown",
|
||||
"hypervisor": "Unknown",
|
||||
"resolution_scale": "Unknown",
|
||||
"anisotropic_filtering": "Unknown",
|
||||
"aspect_ratio": "Unknown",
|
||||
"texture_recompression": "Unknown",
|
||||
}
|
||||
self._notes = set()
|
||||
self._log_errors = []
|
||||
|
||||
def __get_errors(self):
|
||||
errors = []
|
||||
curr_error_lines = []
|
||||
error_line = False
|
||||
for line in self._log_text.splitlines():
|
||||
if len(line.strip()) == 0:
|
||||
continue
|
||||
if "|E|" in line:
|
||||
curr_error_lines = [line]
|
||||
errors.append(curr_error_lines)
|
||||
error_line = True
|
||||
elif error_line and line[0] == " ":
|
||||
curr_error_lines.append(line)
|
||||
if len(curr_error_lines) > 0:
|
||||
errors.append(curr_error_lines)
|
||||
|
||||
self._log_errors = errors
|
||||
|
||||
def __get_hardware_info(self):
|
||||
for setting in self._hardware_info.keys():
|
||||
match setting:
|
||||
case "cpu":
|
||||
cpu_match = re.search(
|
||||
r"CPU:\s([^;\n\r]*)", self._log_text, re.MULTILINE
|
||||
)
|
||||
if cpu_match is not None and cpu_match.group(1) is not None:
|
||||
self._hardware_info[setting] = cpu_match.group(1).rstrip()
|
||||
|
||||
case "ram":
|
||||
sizes = "|".join(Size.names())
|
||||
ram_match = re.search(
|
||||
rf"RAM: Total ([\d.]+) ({sizes}) ; Available ([\d.]+) ({sizes})",
|
||||
self._log_text,
|
||||
re.MULTILINE,
|
||||
)
|
||||
if ram_match is not None:
|
||||
try:
|
||||
dest_unit = Size.MiB
|
||||
|
||||
ram_available = float(ram_match.group(3))
|
||||
ram_available = Size.from_name(ram_match.group(4)).convert(
|
||||
ram_available, dest_unit
|
||||
)
|
||||
|
||||
ram_total = float(ram_match.group(1))
|
||||
ram_total = Size.from_name(ram_match.group(2)).convert(
|
||||
ram_total, dest_unit
|
||||
)
|
||||
|
||||
self._hardware_info[setting] = (
|
||||
f"{ram_available:.0f}/{ram_total:.0f} {dest_unit.name}"
|
||||
)
|
||||
except ValueError:
|
||||
# ram_match.group(1) or ram_match.group(3) couldn't be parsed as a float.
|
||||
self._hardware_info[setting] = "Error"
|
||||
|
||||
case "os":
|
||||
os_match = re.search(
|
||||
r"Operating System:\s([^;\n\r]*)",
|
||||
self._log_text,
|
||||
re.MULTILINE,
|
||||
)
|
||||
if os_match is not None and os_match.group(1) is not None:
|
||||
self._hardware_info[setting] = os_match.group(1).rstrip()
|
||||
|
||||
case "gpu":
|
||||
gpu_match = re.search(
|
||||
r"PrintGpuInformation:\s([^;\n\r]*)",
|
||||
self._log_text,
|
||||
re.MULTILINE,
|
||||
)
|
||||
if gpu_match is not None and gpu_match.group(1) is not None:
|
||||
self._hardware_info[setting] = gpu_match.group(1).rstrip()
|
||||
|
||||
case _:
|
||||
raise NotImplementedError(setting)
|
||||
|
||||
def __get_ryujinx_info(self):
|
||||
for setting in self._emu_info.keys():
|
||||
match setting:
|
||||
case "ryu_version":
|
||||
for line in self._log_text.splitlines():
|
||||
if "Ryujinx Version:" in line:
|
||||
self._emu_info[setting] = line.split()[-1].strip()
|
||||
break
|
||||
|
||||
case "logs_enabled":
|
||||
logs_match = re.search(
|
||||
r"Logs Enabled:\s([^;\n\r]*)", self._log_text, re.MULTILINE
|
||||
)
|
||||
if logs_match is not None and logs_match.group(1) is not None:
|
||||
self._emu_info[setting] = logs_match.group(1).rstrip()
|
||||
|
||||
case "ryu_firmware":
|
||||
for line in self._log_text.splitlines():
|
||||
if "Firmware Version:" in line:
|
||||
self._emu_info[setting] = line.split()[-1].strip()
|
||||
break
|
||||
|
||||
case _:
|
||||
raise NotImplementedError(setting)
|
||||
|
||||
def __get_setting_value(self, name, key):
|
||||
values = [
|
||||
line.split()[-1]
|
||||
for line in self._log_text.splitlines()
|
||||
if re.search(rf"LogValueChange: ({key})\s", line)
|
||||
]
|
||||
if len(values) > 0:
|
||||
value = values[-1]
|
||||
else:
|
||||
return None
|
||||
|
||||
match name:
|
||||
case "docked":
|
||||
return "Docked" if value == "True" else "Handheld"
|
||||
|
||||
case "resolution_scale":
|
||||
resolution_map = {
|
||||
"-1": "Custom",
|
||||
"1": "Native (720p/1080p)",
|
||||
"2": "2x (1440p/2160p)",
|
||||
"3": "3x (2160p/3240p)",
|
||||
"4": "4x (2880p/4320p)",
|
||||
}
|
||||
if value in resolution_map.keys():
|
||||
return resolution_map[value]
|
||||
else:
|
||||
return "Custom"
|
||||
|
||||
case "anisotropic_filtering":
|
||||
anisotropic_map = {
|
||||
"-1": "Auto",
|
||||
"2": "2x",
|
||||
"4": "4x",
|
||||
"8": "8x",
|
||||
"16": "16x",
|
||||
}
|
||||
if value in anisotropic_map.keys():
|
||||
return anisotropic_map[value]
|
||||
else:
|
||||
return "Auto"
|
||||
|
||||
case "aspect_ratio":
|
||||
aspect_map = {
|
||||
"Fixed4x3": "4:3",
|
||||
"Fixed16x9": "16:9",
|
||||
"Fixed16x10": "16:10",
|
||||
"Fixed21x9": "21:9",
|
||||
"Fixed32x9": "32:9",
|
||||
"Stretched": "Stretch to Fit Window",
|
||||
}
|
||||
if value in aspect_map.keys():
|
||||
return aspect_map[value]
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
case "pptc" | "shader_cache" | "texture_recompression" | "vsync":
|
||||
return "Enabled" if value == "True" else "Disabled"
|
||||
|
||||
case "hypervisor":
|
||||
if "mac" in self._hardware_info["os"]:
|
||||
return "Enabled" if value == "True" else "Disabled"
|
||||
else:
|
||||
return "N/A"
|
||||
case _:
|
||||
return value
|
||||
|
||||
def __get_settings_info(self):
|
||||
settings_map = {
|
||||
"anisotropic_filtering": "MaxAnisotropy",
|
||||
"aspect_ratio": "AspectRatio",
|
||||
"audio_backend": "AudioBackend",
|
||||
"backend_threading": "BackendThreading",
|
||||
"docked": "EnableDockedMode",
|
||||
"expand_ram": "ExpandRam",
|
||||
"fs_integrity": "EnableFsIntegrityChecks",
|
||||
"graphics_backend": "GraphicsBackend",
|
||||
"ignore_missing_services": "IgnoreMissingServices",
|
||||
"memory_manager": "MemoryManagerMode",
|
||||
"pptc": "EnablePtc",
|
||||
"resolution_scale": "ResScale",
|
||||
"shader_cache": "EnableShaderCache",
|
||||
"texture_recompression": "EnableTextureRecompression",
|
||||
"vsync": "EnableVsync",
|
||||
"hypervisor": "UseHypervisor",
|
||||
}
|
||||
|
||||
for key in self._settings.keys():
|
||||
if key in settings_map:
|
||||
self._settings[key] = self.__get_setting_value(key, settings_map[key])
|
||||
else:
|
||||
raise NotImplementedError(key)
|
||||
|
||||
def __get_mods(self):
|
||||
mods_regex = re.compile(
|
||||
r"Found\s(enabled|disabled)?\s?mod\s\'(.+?)\'\s(\[.+?\])"
|
||||
)
|
||||
matches = re.findall(mods_regex, self._log_text)
|
||||
if matches:
|
||||
mods = [
|
||||
{"mod": match[1], "status": match[0], "type": match[2]}
|
||||
for match in matches
|
||||
]
|
||||
mods_status = [
|
||||
f"ℹ️ {i['mod']} ({'ExeFS' if i['type'] == '[E]' else 'RomFS'})"
|
||||
for i in mods
|
||||
if i["status"] == "" or i["status"] == "enabled"
|
||||
]
|
||||
# Remove duplicated mods from output
|
||||
mods_status = list(dict.fromkeys(mods_status))
|
||||
|
||||
self._game_info["mods"] = "\n".join(mods_status)
|
||||
|
||||
def __get_cheats(self):
|
||||
# Make sure to skip cheats which fail to compile
|
||||
cheat_regex = re.compile(
|
||||
r"Installing cheat\s'(.+)'(?!\s\d{2}:\d{2}:\d{2}\.\d{3}\s\|E\|\sTamperMachine\sCompile)"
|
||||
)
|
||||
matches = re.findall(cheat_regex, self._log_text)
|
||||
if matches:
|
||||
cheats = [f"ℹ️ {match}" for match in matches]
|
||||
|
||||
self._game_info["cheats"] = "\n".join(cheats)
|
||||
|
||||
def __get_app_name(self):
|
||||
app_match = re.findall(
|
||||
r"Loader [A-Za-z]*: Application Loaded:\s([^;\n\r]*)",
|
||||
self._log_text,
|
||||
re.MULTILINE,
|
||||
)
|
||||
if app_match:
|
||||
self._game_info["game_name"] = app_match[-1].rstrip()
|
||||
|
||||
def __get_controller_notes(self):
|
||||
controllers_regex = re.compile(r"Hid Configure: ([^\r\n]+)")
|
||||
controllers = re.findall(controllers_regex, self._log_text)
|
||||
if controllers:
|
||||
input_status = [f"ℹ {match}" for match in controllers]
|
||||
# Hid Configure lines can appear multiple times, so converting to dict keys removes duplicate entries,
|
||||
# also maintains the list order
|
||||
input_status = list(dict.fromkeys(input_status))
|
||||
self._notes.add("\n".join(input_status))
|
||||
# If emulator crashes on startup without game load, there is no need to show controller notification at all
|
||||
elif self._game_info["game_name"] != "Unknown":
|
||||
self._notes.add("⚠️ No controller information found")
|
||||
|
||||
def __get_os_notes(self):
|
||||
if (
|
||||
"Windows" in self._hardware_info["os"]
|
||||
and self._settings["graphics_backend"] != "Vulkan"
|
||||
):
|
||||
if "Intel" in self._hardware_info["gpu"]:
|
||||
self._notes.add(
|
||||
"**⚠️ Intel iGPU users should consider using Vulkan graphics backend**"
|
||||
)
|
||||
if "AMD" in self._hardware_info["gpu"]:
|
||||
self._notes.add(
|
||||
"**⚠️ AMD GPU users should consider using Vulkan graphics backend**"
|
||||
)
|
||||
|
||||
def __get_cpu_notes(self):
|
||||
if "VirtualApple" in self._hardware_info["cpu"]:
|
||||
self._notes.add("🔴 **Rosetta should be disabled**")
|
||||
|
||||
def __get_log_notes(self):
|
||||
default_logs = ["Info", "Warning", "Error", "Guest"]
|
||||
user_logs = []
|
||||
if self._emu_info["logs_enabled"] is not None:
|
||||
user_logs = (
|
||||
self._emu_info["logs_enabled"].rstrip().replace(" ", "").split(",")
|
||||
)
|
||||
|
||||
if "Debug" in user_logs:
|
||||
self._notes.add(
|
||||
"⚠️ **Debug logs enabled will have a negative impact on performance**"
|
||||
)
|
||||
|
||||
disabled_logs = set(default_logs).difference(set(user_logs))
|
||||
if disabled_logs:
|
||||
logs_status = [f"⚠️ {log} log is not enabled" for log in disabled_logs]
|
||||
log_string = "\n".join(logs_status)
|
||||
else:
|
||||
log_string = "✅ Default logs enabled"
|
||||
|
||||
self._notes.add(log_string)
|
||||
|
||||
def __get_settings_notes(self):
|
||||
if self._settings["audio_backend"] == "Dummy":
|
||||
self._notes.add(
|
||||
"⚠️ Dummy audio backend, consider changing to SDL2 or OpenAL"
|
||||
)
|
||||
|
||||
if self._settings["pptc"] == "Disabled":
|
||||
self._notes.add("🔴 **PPTC cache should be enabled**")
|
||||
|
||||
if self._settings["shader_cache"] == "Disabled":
|
||||
self._notes.add("🔴 **Shader cache should be enabled**")
|
||||
|
||||
if self._settings["expand_ram"] == "True":
|
||||
self._notes.add(
|
||||
"⚠️ `Use alternative memory layout` should only be enabled for 4K mods"
|
||||
)
|
||||
|
||||
if self._settings["memory_manager"] == "SoftwarePageTable":
|
||||
self._notes.add(
|
||||
"🔴 **`Software` setting in Memory Manager Mode will give slower performance than the default setting of `Host unchecked`**"
|
||||
)
|
||||
|
||||
if self._settings["ignore_missing_services"] == "True":
|
||||
self._notes.add(
|
||||
"⚠️ `Ignore Missing Services` being enabled can cause instability"
|
||||
)
|
||||
|
||||
if self._settings["vsync"] == "Disabled":
|
||||
self._notes.add(
|
||||
"⚠️ V-Sync disabled can cause instability like games running faster than intended or longer load times"
|
||||
)
|
||||
|
||||
if self._settings["fs_integrity"] == "Disabled":
|
||||
self._notes.add(
|
||||
"⚠️ Disabling file integrity checks may cause corrupted dumps to not be detected"
|
||||
)
|
||||
|
||||
if self._settings["backend_threading"] == "Off":
|
||||
self._notes.add(
|
||||
"🔴 **Graphics Backend Multithreading should be set to `Auto`**"
|
||||
)
|
||||
|
||||
def __sort_notes(self):
|
||||
def severity(log_note_string):
|
||||
symbols = ["❌", "🔴", "⚠️", "ℹ", "✅"]
|
||||
return next(
|
||||
i for i, symbol in enumerate(symbols) if symbol in log_note_string
|
||||
)
|
||||
|
||||
game_notes = [note for note in self._notes]
|
||||
# Warnings split on the string after the warning symbol for alphabetical ordering
|
||||
# Severity key then orders alphabetically sorted warnings to show most severe first
|
||||
return sorted(sorted(game_notes, key=lambda x: x.split()[1]), key=severity)
|
||||
|
||||
def __get_notes(self):
|
||||
for common_error in self.get_common_errors():
|
||||
match common_error:
|
||||
case CommonError.SHADER_CACHE_COLLISION:
|
||||
self._notes.add(
|
||||
"⚠️ Cache collision detected. Investigate possible shader cache issues"
|
||||
)
|
||||
case CommonError.SHADER_CACHE_CORRUPTION:
|
||||
self._notes.add(
|
||||
"⚠️ Cache corruption detected. Investigate possible shader cache issues"
|
||||
)
|
||||
case CommonError.DUMP_HASH:
|
||||
self._notes.add(
|
||||
"⚠️ Dump error detected. Investigate possible bad game/firmware dump issues"
|
||||
)
|
||||
case CommonError.UPDATE_KEYS:
|
||||
self._notes.add(
|
||||
"⚠️ Keys or firmware out of date, consider updating them"
|
||||
)
|
||||
case CommonError.FILE_PERMISSIONS:
|
||||
self._notes.add(
|
||||
"⚠️ File permission error. Consider deleting save directory and allowing Ryujinx to make a new one"
|
||||
)
|
||||
case CommonError.FILE_NOT_FOUND:
|
||||
self._notes.add(
|
||||
"⚠️ Save not found error. Consider starting game without a save file or using a new save file"
|
||||
)
|
||||
case CommonError.MISSING_SERVICES:
|
||||
if self._settings["ignore_missing_services"] == "False":
|
||||
self._notes.add(
|
||||
"⚠️ Consider enabling `Ignore Missing Services` in Ryujinx settings"
|
||||
)
|
||||
case CommonError.VULKAN_OUT_OF_MEMORY:
|
||||
if self._settings["texture_recompression"] == "Disabled":
|
||||
self._notes.add(
|
||||
"⚠️ Consider enabling `Texture Recompression` in Ryujinx settings"
|
||||
)
|
||||
case _:
|
||||
raise NotImplementedError(common_error)
|
||||
|
||||
timestamp_regex = re.compile(r"(\d{2}:\d{2}:\d{2}\.\d{3})\s+?\|")
|
||||
latest_timestamp = re.findall(timestamp_regex, self._log_text)[-1]
|
||||
if latest_timestamp:
|
||||
timestamp_message = f"ℹ️ Time elapsed: `{latest_timestamp}`"
|
||||
self._notes.add(timestamp_message)
|
||||
|
||||
if self.is_default_user_profile():
|
||||
self._notes.add(
|
||||
"⚠️ Default user profile in use, consider creating a custom one."
|
||||
)
|
||||
|
||||
self.__get_controller_notes()
|
||||
self.__get_os_notes()
|
||||
self.__get_cpu_notes()
|
||||
|
||||
if (
|
||||
self._emu_info["ryu_firmware"] == "Unknown"
|
||||
and self._game_info["game_name"] != "Unknown"
|
||||
):
|
||||
firmware_warning = f"**❌ Nintendo Switch firmware not found**"
|
||||
self._notes.add(firmware_warning)
|
||||
|
||||
self.__get_settings_notes()
|
||||
if self.get_ryujinx_version() == RyujinxVersion.CUSTOM:
|
||||
self._notes.add("**⚠️ Custom builds are not officially supported**")
|
||||
|
||||
def get_ryujinx_version(self):
|
||||
mainline_version = re.compile(r"^\d\.\d\.\d+$")
|
||||
old_mainline_version = re.compile(r"^\d\.\d\.(\d){4}$")
|
||||
pr_version = re.compile(r"^\d\.\d\.\d\+([a-f]|\d){7}$")
|
||||
ldn_version = re.compile(r"^\d\.\d\.\d-ldn\d+\.\d+(?:\.\d+|$)")
|
||||
mac_version = re.compile(r"^\d\.\d\.\d-macos\d+(?:\.\d+(?:\.\d+|$)|$)")
|
||||
|
||||
if re.match(mainline_version, self._emu_info["ryu_version"]):
|
||||
return RyujinxVersion.MASTER
|
||||
elif re.match(old_mainline_version, self._emu_info["ryu_version"]):
|
||||
return RyujinxVersion.OLD_MASTER
|
||||
elif re.match(mac_version, self._emu_info["ryu_version"]):
|
||||
return RyujinxVersion.MAC
|
||||
elif re.match(ldn_version, self._emu_info["ryu_version"]):
|
||||
return RyujinxVersion.LDN
|
||||
elif re.match(pr_version, self._emu_info["ryu_version"]):
|
||||
return RyujinxVersion.PR
|
||||
else:
|
||||
return RyujinxVersion.CUSTOM
|
||||
|
||||
def is_default_user_profile(self) -> bool:
|
||||
return (
|
||||
re.search(r"UserId: 00000000000000010000000000000000", self._log_text)
|
||||
is not None
|
||||
)
|
||||
|
||||
def get_last_error(self) -> Optional[list[str]]:
|
||||
return self._log_errors[-1] if len(self._log_errors) > 0 else None
|
||||
|
||||
def get_common_errors(self) -> list[CommonError]:
|
||||
errors = []
|
||||
|
||||
if self.contains_errors(["Cache collision found"], self._log_errors):
|
||||
errors.append(CommonError.SHADER_CACHE_COLLISION)
|
||||
if self.contains_errors(
|
||||
[
|
||||
"ResultFsInvalidIvfcHash",
|
||||
"ResultFsNonRealDataVerificationFailed",
|
||||
],
|
||||
self._log_errors,
|
||||
):
|
||||
errors.append(CommonError.DUMP_HASH)
|
||||
if self.contains_errors(
|
||||
[
|
||||
"Ryujinx.Graphics.Gpu.Shader.ShaderCache.Initialize()",
|
||||
"System.IO.InvalidDataException: End of Central Directory record could not be found",
|
||||
"ICSharpCode.SharpZipLib.Zip.ZipException: Cannot find central directory",
|
||||
],
|
||||
self._log_errors,
|
||||
):
|
||||
errors.append(CommonError.SHADER_CACHE_CORRUPTION)
|
||||
if self.contains_errors(["MissingKeyException"], self._log_errors):
|
||||
errors.append(CommonError.UPDATE_KEYS)
|
||||
if self.contains_errors(["ResultFsPermissionDenied"], self._log_errors):
|
||||
errors.append(CommonError.FILE_PERMISSIONS)
|
||||
if self.contains_errors(["ResultFsTargetNotFound"], self._log_errors):
|
||||
errors.append(CommonError.FILE_NOT_FOUND)
|
||||
if self.contains_errors(["ServiceNotImplementedException"], self._log_errors):
|
||||
errors.append(CommonError.MISSING_SERVICES)
|
||||
if self.contains_errors(["ErrorOutOfDeviceMemory"], self._log_errors):
|
||||
errors.append(CommonError.VULKAN_OUT_OF_MEMORY)
|
||||
|
||||
return errors
|
||||
|
||||
def analyse_discord(
|
||||
self, is_channel_allowed: bool, pr_channel: int
|
||||
) -> dict[str, dict[str, str]]:
|
||||
last_error = self.get_last_error()
|
||||
if last_error is not None:
|
||||
last_error = "\n".join(last_error[:2])
|
||||
self._game_info["errors"] = f"```\n{last_error}\n```"
|
||||
else:
|
||||
self._game_info["errors"] = "No errors found in log"
|
||||
|
||||
# Limit mods and cheats to 5 entries
|
||||
mods = self._game_info["mods"].splitlines()
|
||||
cheats = self._game_info["cheats"].splitlines()
|
||||
if len(mods) > 5:
|
||||
limit_mods = mods[:5]
|
||||
limit_mods.append(f"✂️ {len(mods) - 5} other mods")
|
||||
self._game_info["mods"] = "\n".join(limit_mods)
|
||||
if len(cheats) > 5:
|
||||
limit_cheats = cheats[:5]
|
||||
limit_cheats.append(f"✂️ {len(cheats) - 5} other cheats")
|
||||
self._game_info["cheats"] = "\n".join(limit_cheats)
|
||||
|
||||
if is_channel_allowed and self.get_ryujinx_version() == RyujinxVersion.PR:
|
||||
self._notes.add(
|
||||
f"**⚠️ PR build logs should be posted in <#{pr_channel}> if reporting bugs or tests**"
|
||||
)
|
||||
|
||||
self._notes = self.__sort_notes()
|
||||
full_game_info = self._game_info
|
||||
full_game_info["notes"] = (
|
||||
"\n".join(self._notes) if len(self._notes) > 0 else "Nothing to note"
|
||||
)
|
||||
|
||||
return {
|
||||
"hardware_info": self._hardware_info,
|
||||
"emu_info": self._emu_info,
|
||||
"game_info": full_game_info,
|
||||
"settings": self._settings,
|
||||
}
|
||||
|
||||
def analyse(self) -> dict[str, Union[dict[str, str], list[str], list[list[str]]]]:
|
||||
self._notes = list(self.__sort_notes())
|
||||
|
||||
last_error = self.get_last_error()
|
||||
if last_error is not None:
|
||||
last_error = "\n".join(last_error[:2])
|
||||
self._game_info["errors"] = f"```\n{last_error}\n```"
|
||||
else:
|
||||
self._game_info["errors"] = "No errors found in log"
|
||||
|
||||
return {
|
||||
"hardware_info": self._hardware_info,
|
||||
"emu_info": self._emu_info,
|
||||
"game_info": self._game_info,
|
||||
"notes": self._notes,
|
||||
"errors": self._log_errors,
|
||||
"settings": self._settings,
|
||||
"app_info": self.get_app_info(self._log_text),
|
||||
"paths": list(self.get_filepaths(self._log_text)),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("log_file", type=str)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isfile(args.log_file):
|
||||
print(f"Couldn't find log file: {args.log_file}")
|
||||
exit(1)
|
||||
|
||||
with open(args.log_file, "r") as file:
|
||||
text = file.read()
|
||||
|
||||
analyser = LogAnalyser(text)
|
||||
result = analyser.analyse()
|
||||
|
||||
print(json.dumps(result, indent=2))
|
|
@ -1,53 +0,0 @@
|
|||
from enum import IntEnum, auto
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Size(IntEnum):
|
||||
KB = auto()
|
||||
KiB = auto()
|
||||
MB = auto()
|
||||
MiB = auto()
|
||||
GB = auto()
|
||||
GiB = auto()
|
||||
|
||||
@classmethod
|
||||
def names(cls) -> list[str]:
|
||||
return [size.name for size in cls]
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name: str) -> Self:
|
||||
for size in cls:
|
||||
if size.name.lower() == name.lower():
|
||||
return size
|
||||
raise ValueError(f"No matching member found for: {name}")
|
||||
|
||||
@property
|
||||
def _is_si_unit(self) -> bool:
|
||||
return self.value % 2 != 0
|
||||
|
||||
@property
|
||||
def _unit_value(self) -> int:
|
||||
return self.value // 2 + (1 if self._is_si_unit else 0)
|
||||
|
||||
@property
|
||||
def _base_factor(self) -> int:
|
||||
return 10**3 if self._is_si_unit else 2**10
|
||||
|
||||
@property
|
||||
def _byte_factor(self) -> int:
|
||||
return (
|
||||
10 ** (3 * self._unit_value)
|
||||
if self._is_si_unit
|
||||
else 2 ** (10 * self._unit_value)
|
||||
)
|
||||
|
||||
def convert(self, value: float, fmt: Self) -> float:
|
||||
if self == fmt:
|
||||
return value
|
||||
if self._is_si_unit == fmt._is_si_unit:
|
||||
if self < fmt:
|
||||
return value / self._base_factor ** (fmt._unit_value - self._unit_value)
|
||||
else:
|
||||
return value * self._base_factor ** (self._unit_value - fmt._unit_value)
|
||||
else:
|
||||
return value * (self._byte_factor / fmt._byte_factor)
|
|
@ -1,9 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from robocop_ng.helpers.data_loader import read_json
|
||||
|
||||
userlog_event_types = {
|
||||
"warns": "Warn",
|
||||
"bans": "Ban",
|
||||
|
@ -13,21 +10,18 @@ userlog_event_types = {
|
|||
}
|
||||
|
||||
|
||||
def get_userlog_path(bot):
|
||||
return os.path.join(bot.state_dir, "data/userlog.json")
|
||||
def get_userlog():
|
||||
with open("data/userlog.json", "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_userlog(bot):
|
||||
return read_json(bot, get_userlog_path(bot))
|
||||
|
||||
|
||||
def set_userlog(bot, contents):
|
||||
with open(get_userlog_path(bot), "w") as f:
|
||||
def set_userlog(contents):
|
||||
with open("data/userlog.json", "w") as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def fill_userlog(bot, userid, uname):
|
||||
userlogs = get_userlog(bot)
|
||||
def fill_userlog(userid, uname):
|
||||
userlogs = get_userlog()
|
||||
uid = str(userid)
|
||||
if uid not in userlogs:
|
||||
userlogs[uid] = {
|
||||
|
@ -45,8 +39,8 @@ def fill_userlog(bot, userid, uname):
|
|||
return userlogs, uid
|
||||
|
||||
|
||||
def userlog(bot, uid, issuer, reason, event_type, uname: str = ""):
|
||||
userlogs, uid = fill_userlog(bot, uid, uname)
|
||||
def userlog(uid, issuer, reason, event_type, uname: str = ""):
|
||||
userlogs, uid = fill_userlog(uid, uname)
|
||||
|
||||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
log_data = {
|
||||
|
@ -58,13 +52,13 @@ def userlog(bot, uid, issuer, reason, event_type, uname: str = ""):
|
|||
if event_type not in userlogs[uid]:
|
||||
userlogs[uid][event_type] = []
|
||||
userlogs[uid][event_type].append(log_data)
|
||||
set_userlog(bot, json.dumps(userlogs))
|
||||
set_userlog(json.dumps(userlogs))
|
||||
return len(userlogs[uid][event_type])
|
||||
|
||||
|
||||
def setwatch(bot, uid, issuer, watch_state, uname: str = ""):
|
||||
userlogs, uid = fill_userlog(bot, uid, uname)
|
||||
def setwatch(uid, issuer, watch_state, uname: str = ""):
|
||||
userlogs, uid = fill_userlog(uid, uname)
|
||||
|
||||
userlogs[uid]["watch"] = watch_state
|
||||
set_userlog(bot, json.dumps(userlogs))
|
||||
set_userlog(json.dumps(userlogs))
|
||||
return
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
(import (fetchTarball
|
||||
"https://github.com/edolstra/flake-compat/archive/master.tar.gz") {
|
||||
src = builtins.fetchGit ./.;
|
||||
}).shellNix
|
Loading…
Reference in a new issue