Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14390c5c21 | ||
|
|
9436a4385f | ||
|
|
3e77b98be4 | ||
|
|
2c5d4a62aa | ||
|
|
5abaf88102 | ||
|
|
f1980e9bcb | ||
|
|
e0de5433df | ||
|
|
6d67134fb7 | ||
|
|
8b67892946 | ||
|
|
9c7b24b9d7 | ||
|
|
97bba4ac55 |
@@ -0,0 +1,64 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
# Controls when the action will run. Triggers the workflow on push to master or development
|
||||||
|
# with a tag like v1.0.0 or v1.0.0-dev
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]+.[0-9]+.[0-9]+
|
||||||
|
- v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z]+
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2.0.0
|
||||||
|
|
||||||
|
- name: install buildx
|
||||||
|
id: buildx
|
||||||
|
uses: crazy-max/ghaction-docker-buildx@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Docker Login
|
||||||
|
# You may pin to the exact commit or the version.
|
||||||
|
# uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||||
|
uses: docker/login-action@v1.10.0
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.DR_URL }}
|
||||||
|
# Username used to log against the Docker registry
|
||||||
|
username: ${{ secrets.DH_USERNAME }}
|
||||||
|
# Password or personal access token used to log against the Docker registry
|
||||||
|
password: ${{ secrets.DH_PASSWORD }}
|
||||||
|
# Log out from the Docker registry at the end of a job
|
||||||
|
logout: true
|
||||||
|
|
||||||
|
- name: Docker Build & Push
|
||||||
|
env:
|
||||||
|
IMAGE_TAG: ${{ steps.get_version.outputs.version-without-v }}
|
||||||
|
run: |
|
||||||
|
docker buildx build --push \
|
||||||
|
--tag ${{ secrets.DR_URL }}/geeksbot:$IMAGE_TAG \
|
||||||
|
--platform linux/amd64,linux/arm/v7,linux/arm64 .
|
||||||
|
|
||||||
|
- name: Update deployment file
|
||||||
|
run: TAG=${{ steps.get_version.outputs.version-without-v }} && sed -i 's|<IMAGE>|${{ secrets.DR_URL }}/geeksbot:'${TAG}'|' $GITHUB_WORKSPACE/deployment.yml
|
||||||
|
|
||||||
|
- uses: azure/k8s-set-context@v1
|
||||||
|
with:
|
||||||
|
method: kubeconfig
|
||||||
|
kubeconfig: ${{ secrets.KUBE_CONFIG }}
|
||||||
|
id: setcontext
|
||||||
|
|
||||||
|
- name: Deploy to Kubernetes
|
||||||
|
run: kubectl apply -f $GITHUB_WORKSPACE/deployment.yml
|
||||||
|
|
||||||
|
- name: Verify deployment
|
||||||
|
run: kubectl rollout status deployment/geeksbot
|
||||||
+1
-92
@@ -1,93 +1,2 @@
|
|||||||
bot_secrets.json
|
|
||||||
google_client_secret.json
|
|
||||||
logs/*
|
|
||||||
*.sh.swp
|
|
||||||
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
.python-version
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
.env
|
||||||
.venv
|
.idea
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-debug/
|
|
||||||
cmake-build-release/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|||||||
Generated
-11
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="TestRunnerService">
|
|
||||||
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
Generated
-5
@@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
||||||
Generated
-7
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="JavaScriptSettings">
|
|
||||||
<option name="languageLevel" value="ES6" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (Geeksbot)" project-jdk-type="Python SDK" />
|
|
||||||
</project>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/Geeksbot.iml" filepath="$PROJECT_DIR$/.idea/Geeksbot.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
FROM golang:1.14-alpine as dev
|
||||||
|
|
||||||
|
WORKDIR /go/src/Geeksbot
|
||||||
|
COPY ./go.mod .
|
||||||
|
COPY ./go.sum .
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN go install github.com/dustinpianalto/geeksbot/...
|
||||||
|
|
||||||
|
CMD [ "go", "run", "cmd/geeksbot/main.go"]
|
||||||
|
|
||||||
|
from alpine
|
||||||
|
|
||||||
|
WORKDIR /bin
|
||||||
|
|
||||||
|
COPY --from=dev /go/bin/geeksbot ./geeksbot
|
||||||
|
|
||||||
|
CMD [ "geeksbot" ]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Dusty.P
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
# Geeksbot
|
# Geeksbot
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/dustinpianalto/disgoman"
|
||||||
|
"github.com/dustinpianalto/geeksbot/internal/exts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
Token := os.Getenv("DISCORD_TOKEN")
|
||||||
|
dg, err := discordgo.New("Bot " + Token)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("There was an error when creating the Discord Session, ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dg.State.MaxMessageCount = 100
|
||||||
|
dg.StateEnabled = true
|
||||||
|
|
||||||
|
dg.Identify = discordgo.Identify{
|
||||||
|
Intents: discordgo.MakeIntent(discordgo.IntentsAll),
|
||||||
|
}
|
||||||
|
|
||||||
|
//postgres.ConnectDatabase(os.Getenv("DATABASE_URL"))
|
||||||
|
//postgres.InitializeDatabase()
|
||||||
|
//utils.LoadTestData()
|
||||||
|
|
||||||
|
//us := &postgres.UserService{DB: postgres.DB}
|
||||||
|
//gs := &postgres.GuildService{DB: postgres.DB}
|
||||||
|
|
||||||
|
owners := []string{
|
||||||
|
"351794468870946827",
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := disgoman.CommandManager{
|
||||||
|
Prefixes: getPrefixes,
|
||||||
|
Owners: owners,
|
||||||
|
StatusManager: disgoman.GetDefaultStatusManager(),
|
||||||
|
ErrorChannel: make(chan disgoman.CommandError, 10),
|
||||||
|
Commands: make(map[string]*disgoman.Command),
|
||||||
|
IgnoreBots: true,
|
||||||
|
CheckPermissions: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Command Handlers
|
||||||
|
exts.AddCommandHandlers(&manager)
|
||||||
|
//services.InitalizeServices(us, gs)
|
||||||
|
|
||||||
|
//if _, ok := handler.Commands["help"]; !ok {
|
||||||
|
// handler.AddDefaultHelpCommand()
|
||||||
|
//}
|
||||||
|
|
||||||
|
dg.AddHandler(manager.OnMessage)
|
||||||
|
dg.AddHandler(manager.StatusManager.OnReady)
|
||||||
|
//dg.AddHandler(guild_management.OnMessageUpdate)
|
||||||
|
//dg.AddHandler(guild_management.OnMessageDelete)
|
||||||
|
//dg.AddHandler(user_management.OnGuildMemberAddLogging)
|
||||||
|
//dg.AddHandler(user_management.OnGuildMemberRemoveLogging)
|
||||||
|
|
||||||
|
err = dg.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("There was an error opening the connection, ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the Error handler in a goroutine
|
||||||
|
go ErrorHandler(manager.ErrorChannel)
|
||||||
|
|
||||||
|
// Start the Logging handler in a goroutine
|
||||||
|
//go logging.LoggingHandler(logging.LoggingChannel)
|
||||||
|
|
||||||
|
log.Println("The Bot is now running.")
|
||||||
|
sc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
||||||
|
<-sc
|
||||||
|
|
||||||
|
log.Println("Shutting Down...")
|
||||||
|
err = dg.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrefixes(guildID string) []string {
|
||||||
|
return []string{"G.", "g."}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorHandler(ErrorChan chan disgoman.CommandError) {
|
||||||
|
for ce := range ErrorChan {
|
||||||
|
msg := ce.Message
|
||||||
|
if msg == "" {
|
||||||
|
msg = ce.Error.Error()
|
||||||
|
}
|
||||||
|
_, _ = ce.Context.Send(msg)
|
||||||
|
log.Println(ce.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: geeksbot
|
||||||
|
namespace: discord-bots
|
||||||
|
labels:
|
||||||
|
app: geeksbot
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: geeksbot
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 1
|
||||||
|
minReadySeconds: 120
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: geeksbot
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: geeksbot
|
||||||
|
image: <IMAGE>
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "1"
|
||||||
|
limits:
|
||||||
|
memory: "1Gi"
|
||||||
|
cpu: "2"
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: geeksbot
|
||||||
|
key: database_url
|
||||||
|
- name: DISCORD_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: geeksbot
|
||||||
|
key: discord_token
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: registry-2
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
until python -m src; do
|
|
||||||
echo "Geeksbot shutdown with error: $?. Restarting..." >&2
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module github.com/dustinpianalto/geeksbot
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bwmarrin/discordgo v0.22.1
|
||||||
|
github.com/dustinpianalto/disgoman v0.0.15
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
|
||||||
|
github.com/bwmarrin/discordgo v0.22.1 h1:254fNYyfqJWKbPzO5g8j/nUvRgj4dNlI19EB8rnkpt8=
|
||||||
|
github.com/bwmarrin/discordgo v0.22.1/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||||
|
github.com/dustinpianalto/disgoman v0.0.15 h1:kdIw6jhC82WBut7+4BarqxBw06dozU+Hu47LQzkkoGM=
|
||||||
|
github.com/dustinpianalto/disgoman v0.0.15/go.mod h1:v3FM6n+4dH9XlvO+IDx6MN3DUnGq6YVDBvy1A1k202g=
|
||||||
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package discord_utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseDateString(inTime time.Time) string {
|
||||||
|
d := time.Now().Sub(inTime)
|
||||||
|
s := int64(d.Seconds())
|
||||||
|
days := s / 86400
|
||||||
|
s = s - (days * 86400)
|
||||||
|
hours := s / 3600
|
||||||
|
s = s - (hours * 3600)
|
||||||
|
minutes := s / 60
|
||||||
|
seconds := s - (minutes * 60)
|
||||||
|
dateString := ""
|
||||||
|
if days != 0 {
|
||||||
|
dateString += fmt.Sprintf("%v days ", days)
|
||||||
|
}
|
||||||
|
if hours != 0 {
|
||||||
|
dateString += fmt.Sprintf("%v hours ", hours)
|
||||||
|
}
|
||||||
|
if minutes != 0 {
|
||||||
|
dateString += fmt.Sprintf("%v minutes ", minutes)
|
||||||
|
}
|
||||||
|
if seconds != 0 {
|
||||||
|
dateString += fmt.Sprintf("%v seconds ", seconds)
|
||||||
|
}
|
||||||
|
if dateString != "" {
|
||||||
|
dateString += " ago."
|
||||||
|
} else {
|
||||||
|
dateString = "Now"
|
||||||
|
}
|
||||||
|
stamp := inTime.Format("2006-01-02 15:04:05")
|
||||||
|
return fmt.Sprintf("%v\n%v", dateString, stamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseDurationString(inDur time.Duration) string {
|
||||||
|
s := int64(inDur.Seconds())
|
||||||
|
days := s / 86400
|
||||||
|
s = s - (days * 86400)
|
||||||
|
hours := s / 3600
|
||||||
|
s = s - (hours * 3600)
|
||||||
|
minutes := s / 60
|
||||||
|
seconds := s - (minutes * 60)
|
||||||
|
durString := ""
|
||||||
|
if days != 0 {
|
||||||
|
durString += fmt.Sprintf("%v days ", days)
|
||||||
|
}
|
||||||
|
if hours != 0 {
|
||||||
|
durString += fmt.Sprintf("%v hours ", hours)
|
||||||
|
}
|
||||||
|
if minutes != 0 {
|
||||||
|
durString += fmt.Sprintf("%v minutes ", minutes)
|
||||||
|
}
|
||||||
|
if seconds != 0 {
|
||||||
|
durString += fmt.Sprintf("%v seconds ", seconds)
|
||||||
|
}
|
||||||
|
if durString == "" {
|
||||||
|
durString = "0 seconds"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", durString)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package discord_utils
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Snowflake struct {
|
||||||
|
CreationTime time.Time
|
||||||
|
WorkerID int8
|
||||||
|
ProcessID int8
|
||||||
|
Increment int16
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSnowflake(s int64) Snowflake {
|
||||||
|
const (
|
||||||
|
DISCORD_EPOCH = 1420070400000
|
||||||
|
TIME_BITS_LOC = 22
|
||||||
|
WORKER_ID_LOC = 17
|
||||||
|
WORKER_ID_MASK = 0x3E0000
|
||||||
|
PROCESS_ID_LOC = 12
|
||||||
|
PROCESS_ID_MASK = 0x1F000
|
||||||
|
INCREMENT_MASK = 0xFFF
|
||||||
|
)
|
||||||
|
creationTime := time.Unix(((s>>TIME_BITS_LOC)+DISCORD_EPOCH)/1000.0, 0)
|
||||||
|
workerID := (s & WORKER_ID_MASK) >> WORKER_ID_LOC
|
||||||
|
processID := (s & PROCESS_ID_MASK) >> PROCESS_ID_LOC
|
||||||
|
increment := s & INCREMENT_MASK
|
||||||
|
return Snowflake{
|
||||||
|
CreationTime: creationTime,
|
||||||
|
WorkerID: int8(workerID),
|
||||||
|
ProcessID: int8(processID),
|
||||||
|
Increment: int16(increment),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package exts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dustinpianalto/disgoman"
|
||||||
|
"github.com/dustinpianalto/geeksbot/internal/exts/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddCommandHandlers(h *disgoman.CommandManager) {
|
||||||
|
// Arguments:
|
||||||
|
// name - command name - string
|
||||||
|
// desc - command description - string
|
||||||
|
// owneronly - only allow owners to run - bool
|
||||||
|
// hidden - hide command from non-owners - bool
|
||||||
|
// perms - permissisions required - anpan.Permission (int)
|
||||||
|
// type - command type, sets where the command is available
|
||||||
|
// run - function to run - func(anpan.Context, []string) / CommandRunFunc
|
||||||
|
_ = h.AddCommand(utils.UserCommand)
|
||||||
|
_ = h.AddCommand(utils.SayCommand)
|
||||||
|
_ = h.AddCommand(utils.GitCommand)
|
||||||
|
_ = h.AddCommand(utils.InviteCommand)
|
||||||
|
_ = h.AddCommand(utils.PingCommand)
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/dustinpianalto/disgoman"
|
||||||
|
"github.com/dustinpianalto/geeksbot/internal/discord_utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PingCommand = &disgoman.Command{
|
||||||
|
Name: "ping",
|
||||||
|
Aliases: []string{" "},
|
||||||
|
Description: "Check the bot's ping",
|
||||||
|
OwnerOnly: false,
|
||||||
|
Hidden: false,
|
||||||
|
RequiredPermissions: 0,
|
||||||
|
Invoke: pingCommandFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func pingCommandFunc(ctx disgoman.Context, _ []string) {
|
||||||
|
timeBefore := time.Now()
|
||||||
|
msg, _ := ctx.Send("Pong!")
|
||||||
|
took := time.Now().Sub(timeBefore)
|
||||||
|
_, err := ctx.Session.ChannelMessageEdit(ctx.Message.ChannelID, msg.ID, fmt.Sprintf("Pong!\nPing Took **%s**", took.String()))
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Ping Failed",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var InviteCommand = &disgoman.Command{
|
||||||
|
Name: "invite",
|
||||||
|
Aliases: nil,
|
||||||
|
Description: "Get the invite link for this bot or others",
|
||||||
|
OwnerOnly: false,
|
||||||
|
Hidden: false,
|
||||||
|
RequiredPermissions: 0,
|
||||||
|
Invoke: inviteCommandFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func inviteCommandFunc(ctx disgoman.Context, args []string) {
|
||||||
|
var ids []string
|
||||||
|
if len(args) == 0 && len(ctx.Message.Mentions) == 0 {
|
||||||
|
ids = []string{ctx.Session.State.User.ID}
|
||||||
|
} else {
|
||||||
|
if len(ctx.Message.Mentions) > 0 {
|
||||||
|
for _, user := range ctx.Message.Mentions {
|
||||||
|
member, err := ctx.Session.GuildMember(ctx.Guild.ID, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Could not find member " + user.Username,
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, member.User.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
for _, id := range args {
|
||||||
|
member, err := ctx.Session.GuildMember(ctx.Guild.ID, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Could not find member " + id,
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, member.User.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, id := range ids {
|
||||||
|
url := fmt.Sprintf("<https://discordapp.com/oauth2/authorize?client_id=%v&scope=bot>", id)
|
||||||
|
_, err := ctx.Send(url)
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Couldn't send the invite link.",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var GitCommand = &disgoman.Command{
|
||||||
|
Name: "git",
|
||||||
|
Aliases: nil,
|
||||||
|
Description: "Show my github link",
|
||||||
|
OwnerOnly: false,
|
||||||
|
Hidden: false,
|
||||||
|
RequiredPermissions: 0,
|
||||||
|
Invoke: gitCommandFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitCommandFunc(ctx disgoman.Context, _ []string) {
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: "Hi there, My code is on Github",
|
||||||
|
Color: 0,
|
||||||
|
URL: "https://github.com/dustinpianalto/Geeksbot",
|
||||||
|
}
|
||||||
|
_, err := ctx.Session.ChannelMessageSendEmbed(ctx.Channel.ID, embed)
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Git failed",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var SayCommand = &disgoman.Command{
|
||||||
|
Name: "say",
|
||||||
|
Aliases: nil,
|
||||||
|
Description: "Repeat a message",
|
||||||
|
OwnerOnly: false,
|
||||||
|
Hidden: false,
|
||||||
|
RequiredPermissions: 0,
|
||||||
|
SanitizeEveryone: true,
|
||||||
|
Invoke: sayCommandFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func sayCommandFunc(ctx disgoman.Context, args []string) {
|
||||||
|
resp := strings.Join(args, " ")
|
||||||
|
resp = strings.ReplaceAll(resp, "@everyone", "@\ufff0everyone")
|
||||||
|
resp = strings.ReplaceAll(resp, "@here", "@\ufff0here")
|
||||||
|
_, err := ctx.Session.ChannelMessageSend(ctx.Message.ChannelID, resp)
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Say Failed",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var UserCommand = &disgoman.Command{
|
||||||
|
Name: "user",
|
||||||
|
Aliases: nil,
|
||||||
|
Description: "Get user info",
|
||||||
|
OwnerOnly: false,
|
||||||
|
Hidden: false,
|
||||||
|
RequiredPermissions: 0,
|
||||||
|
Invoke: userCommandFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCommandFunc(ctx disgoman.Context, args []string) {
|
||||||
|
var member *discordgo.Member
|
||||||
|
if len(args) == 0 {
|
||||||
|
member, _ = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Author.ID)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
if len(ctx.Message.Mentions) > 0 {
|
||||||
|
member, err = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Mentions[0].ID)
|
||||||
|
} else {
|
||||||
|
member, err = ctx.Session.GuildMember(ctx.Guild.ID, args[0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Couldn't get that member",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumb := &discordgo.MessageEmbedThumbnail{
|
||||||
|
URL: member.User.AvatarURL(""),
|
||||||
|
}
|
||||||
|
|
||||||
|
var botString string
|
||||||
|
if member.User.Bot {
|
||||||
|
botString = "BOT"
|
||||||
|
} else {
|
||||||
|
botString = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles []*discordgo.Role
|
||||||
|
for _, roleID := range member.Roles {
|
||||||
|
role, _ := ctx.Session.State.Role(ctx.Guild.ID, roleID)
|
||||||
|
roles = append(roles, role)
|
||||||
|
}
|
||||||
|
sort.Slice(roles, func(i, j int) bool { return roles[i].Position > roles[j].Position })
|
||||||
|
var roleMentions []string
|
||||||
|
for _, role := range roles {
|
||||||
|
roleMentions = append(roleMentions, role.Mention())
|
||||||
|
}
|
||||||
|
var rolesString string
|
||||||
|
if len(roleMentions) > 0 {
|
||||||
|
rolesString = strings.Join(roleMentions, " ")
|
||||||
|
} else {
|
||||||
|
rolesString = "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
rolesField := &discordgo.MessageEmbedField{
|
||||||
|
Name: "Roles:",
|
||||||
|
Value: rolesString,
|
||||||
|
Inline: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
guildJoinTime, _ := member.JoinedAt.Parse()
|
||||||
|
guildJoinedField := &discordgo.MessageEmbedField{
|
||||||
|
Name: "Joined Guild:",
|
||||||
|
Value: discord_utils.ParseDateString(guildJoinTime),
|
||||||
|
Inline: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
int64ID, _ := strconv.ParseInt(member.User.ID, 10, 64)
|
||||||
|
s := discord_utils.ParseSnowflake(int64ID)
|
||||||
|
discordJoinedField := &discordgo.MessageEmbedField{
|
||||||
|
Name: "Joined Discord:",
|
||||||
|
Value: discord_utils.ParseDateString(s.CreationTime),
|
||||||
|
Inline: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: fmt.Sprintf("%v#%v %v", member.User.Username, member.User.Discriminator, botString),
|
||||||
|
Description: fmt.Sprintf("**%v** (%v)", member.Nick, member.User.ID),
|
||||||
|
Color: ctx.Session.State.UserColor(member.User.ID, ctx.Channel.ID),
|
||||||
|
Thumbnail: thumb,
|
||||||
|
Fields: []*discordgo.MessageEmbedField{
|
||||||
|
guildJoinedField,
|
||||||
|
discordJoinedField,
|
||||||
|
rolesField,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := ctx.Session.ChannelMessageSendEmbed(ctx.Channel.ID, embed)
|
||||||
|
if err != nil {
|
||||||
|
ctx.CommandManager.ErrorChannel <- disgoman.CommandError{
|
||||||
|
Context: ctx,
|
||||||
|
Message: "Couldn't send the user embed",
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
from typing import Dict
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import aiohttp
|
|
||||||
from googleapiclient.discovery import build
|
|
||||||
from concurrent import futures
|
|
||||||
from src.shared_libs import database
|
|
||||||
|
|
||||||
|
|
||||||
log_format = '{asctime}.{msecs:03.0f}|{levelname:<8}|{name}::{message}'
|
|
||||||
date_format = '%Y.%m.%d %H.%M.%S'
|
|
||||||
|
|
||||||
log_dir = 'logs'
|
|
||||||
|
|
||||||
log_file = '{0}/geeksbot_{1}.log'.format(log_dir, datetime.now().strftime('%Y%m%d_%H%M%S%f'))
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG, style='{', filename=log_file, datefmt=date_format, format=log_format)
|
|
||||||
console_handler = logging.StreamHandler()
|
|
||||||
console_handler.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter(log_format, style='{', datefmt=date_format)
|
|
||||||
console_handler.setFormatter(formatter)
|
|
||||||
logging.getLogger('').addHandler(console_handler)
|
|
||||||
|
|
||||||
config_dir = 'src/config/'
|
|
||||||
admin_id_file = 'admin_ids'
|
|
||||||
extension_dir = 'exts'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
bot_config_file = 'bot_config.json'
|
|
||||||
secrets_file = 'bot_secrets.json'
|
|
||||||
profane_words_file = 'profane_words'
|
|
||||||
|
|
||||||
emojis: Dict[str, str] = {
|
|
||||||
'x': '❌',
|
|
||||||
'y': '✅',
|
|
||||||
'poop': '💩',
|
|
||||||
'boom': '💥',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Geeksbot(commands.Bot):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
kwargs["command_prefix"] = self.get_custom_prefix
|
|
||||||
self.description = 'I am Geeksbot! Fear me!'
|
|
||||||
kwargs['description'] = self.description
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.aio_session = aiohttp.ClientSession(loop=self.loop)
|
|
||||||
with open(f'{config_dir}{bot_config_file}') as file:
|
|
||||||
self.bot_config = json.load(file)
|
|
||||||
with open(f'{config_dir}{secrets_file}') as file:
|
|
||||||
self.bot_secrets = json.load(file)
|
|
||||||
self.guild_config = {}
|
|
||||||
self.infected = {}
|
|
||||||
self.TOKEN = self.bot_secrets['token']
|
|
||||||
self.embed_color = discord.Colour.from_rgb(49, 107, 111)
|
|
||||||
self.error_color = discord.Colour.from_rgb(142, 29, 31)
|
|
||||||
del self.bot_secrets['token']
|
|
||||||
self.db_con = database.DatabaseConnection(**self.bot_secrets['db_con'])
|
|
||||||
self.default_prefix = 'g$'
|
|
||||||
self.voice_chans = {}
|
|
||||||
self.spam_list = {}
|
|
||||||
self.owner_id = 351794468870946827
|
|
||||||
self.__version__ = 'v1.0.0'
|
|
||||||
self.gcs_service = build('customsearch', 'v1', developerKey=self.bot_secrets['google_search_key'])
|
|
||||||
self.tpe = futures.ThreadPoolExecutor()
|
|
||||||
self.geo_api = '2d4e419c2be04c8abe91cb5dd1548c72'
|
|
||||||
self.unicode_emojis: Dict[str, str] = {
|
|
||||||
'x': '❌',
|
|
||||||
'y': '✅',
|
|
||||||
'poop': '💩',
|
|
||||||
'boom': '💥',
|
|
||||||
'left_fist': '🤛',
|
|
||||||
'lock': '🔒',
|
|
||||||
}
|
|
||||||
self.book_emojis: Dict[str, str] = {
|
|
||||||
'unlock': '🔓',
|
|
||||||
'start': '⏮',
|
|
||||||
'back': '◀',
|
|
||||||
'hash': '#\N{COMBINING ENCLOSING KEYCAP}',
|
|
||||||
'forward': '▶',
|
|
||||||
'end': '⏭',
|
|
||||||
'close': '🇽',
|
|
||||||
}
|
|
||||||
|
|
||||||
async def logout(self):
|
|
||||||
await self.db_con.close()
|
|
||||||
super().logout()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_custom_prefix(bot_inst, message):
|
|
||||||
return await bot_inst.db_con.fetchval('select prefix from guild_config where guild_id = $1',
|
|
||||||
message.guild.id) or bot_inst.default_prefix
|
|
||||||
|
|
||||||
async def load_ext(self, ctx, mod=None):
|
|
||||||
self.load_extension('src.{0}.{1}'.format(extension_dir, mod))
|
|
||||||
if ctx is not None:
|
|
||||||
await ctx.send('{0} loaded.'.format(mod))
|
|
||||||
|
|
||||||
async def unload_ext(self, ctx, mod=None):
|
|
||||||
self.unload_extension('src.{0}.{1}'.format(extension_dir, mod))
|
|
||||||
if ctx is not None:
|
|
||||||
await ctx.send('{0} unloaded.'.format(mod))
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
await super().close()
|
|
||||||
self.aio_session.close() # aiohttp is drunk and can't decide if it's a coro or not
|
|
||||||
|
|
||||||
|
|
||||||
bot = Geeksbot(case_insensitive=True)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def load(ctx, mod=None):
|
|
||||||
"""Allows the owner to load extensions dynamically"""
|
|
||||||
await bot.load_ext(ctx, mod)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def reload(ctx, mod=None):
|
|
||||||
"""Allows the owner to reload extensions dynamically"""
|
|
||||||
if mod == 'all':
|
|
||||||
load_list = bot.bot_config['load_list']
|
|
||||||
for load_item in load_list:
|
|
||||||
await bot.unload_ext(ctx, f'{load_item}')
|
|
||||||
await bot.load_ext(ctx, f'{load_item}')
|
|
||||||
else:
|
|
||||||
await bot.unload_ext(ctx, mod)
|
|
||||||
await bot.load_ext(ctx, mod)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def unload(ctx, mod):
|
|
||||||
"""Allows the owner to unload extensions dynamically"""
|
|
||||||
await bot.unload_ext(ctx, mod)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.event
|
|
||||||
async def on_message(ctx):
|
|
||||||
if not ctx.author.bot:
|
|
||||||
if ctx.guild:
|
|
||||||
if int(await bot.db_con.fetchval("select channel_lockdown from guild_config where guild_id = $1",
|
|
||||||
ctx.guild.id)):
|
|
||||||
if ctx.channel.id in json.loads(await bot.db_con.fetchval("select allowed_channels from guild_config "
|
|
||||||
"where guild_id = $1",
|
|
||||||
ctx.guild.id)):
|
|
||||||
await bot.process_commands(ctx)
|
|
||||||
elif ctx.channel.id == 418452585683484680:
|
|
||||||
prefix = await bot.db_con.fetchval('select prefix from guild_config where guild_id = $1', ctx.guild.id)
|
|
||||||
prefix = prefix[0] if prefix else bot.default_prefix
|
|
||||||
ctx.content = f'{prefix}{ctx.content}'
|
|
||||||
await bot.process_commands(ctx)
|
|
||||||
else:
|
|
||||||
await bot.process_commands(ctx)
|
|
||||||
else:
|
|
||||||
await bot.process_commands(ctx)
|
|
||||||
|
|
||||||
|
|
||||||
@bot.event
|
|
||||||
async def on_ready():
|
|
||||||
bot.remove_command('help')
|
|
||||||
bot.recent_msgs = {}
|
|
||||||
logging.info('Logged in as {0.name}|{0.id}'.format(bot.user))
|
|
||||||
load_list = bot.bot_config['load_list']
|
|
||||||
for load_item in load_list:
|
|
||||||
await bot.load_ext(None, f'{load_item}')
|
|
||||||
logging.info('Extension Loaded: {0}'.format(load_item))
|
|
||||||
with open(f'{config_dir}reboot', 'r') as f:
|
|
||||||
reboot = f.readlines()
|
|
||||||
if int(reboot[0]) == 1:
|
|
||||||
await bot.get_channel(int(reboot[1])).send('Restart Finished.')
|
|
||||||
with open(f'{config_dir}reboot', 'w') as f:
|
|
||||||
f.write(f'0')
|
|
||||||
logging.info('Done loading, Geeksbot is active.')
|
|
||||||
|
|
||||||
bot.run(bot.TOKEN)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
351794468870946827
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"load_list": [
|
|
||||||
"admin",
|
|
||||||
"events",
|
|
||||||
"rcon",
|
|
||||||
"repl",
|
|
||||||
"patreon",
|
|
||||||
"fun",
|
|
||||||
"utils",
|
|
||||||
"git"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"rcon_enabled" : false,
|
|
||||||
"channel_lockdown" : false,
|
|
||||||
"raid_status" : 0,
|
|
||||||
"pg_filter" : true,
|
|
||||||
"patreon_enabled" : false,
|
|
||||||
"referral_enabled" : false
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
shit
|
|
||||||
piss
|
|
||||||
fuck
|
|
||||||
cunt
|
|
||||||
cock
|
|
||||||
motherfucker
|
|
||||||
tits
|
|
||||||
ballsack
|
|
||||||
bangbros
|
|
||||||
bitch
|
|
||||||
blow job
|
|
||||||
blowjob
|
|
||||||
clit
|
|
||||||
f u c k
|
|
||||||
gangbang
|
|
||||||
gaylord
|
|
||||||
gaysex
|
|
||||||
god damn
|
|
||||||
goddamn
|
|
||||||
homoerotic
|
|
||||||
hotsex
|
|
||||||
jerk-off
|
|
||||||
jerk off
|
|
||||||
masturbat
|
|
||||||
porn
|
|
||||||
pussy
|
|
||||||
pussi
|
|
||||||
skank
|
|
||||||
whore
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
0
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
{"blobsmirk": "https://discordapp.com/api/emojis/414330931059490827.png", "BlobThinking": "https://discordapp.com/api/emojis/408366351602941952.png", "BlobThinkingCool": "https://discordapp.com/api/emojis/414839939117613057.png", "BlobVote": "https://discordapp.com/api/emojis/414840024266309632.png", "BlobDead": "https://discordapp.com/api/emojis/414840075289886722.png", "BlobIdea": "https://discordapp.com/api/emojis/414840112317333544.png", "BlobOK": "https://discordapp.com/api/emojis/414840149059436544.png", "BlobNausea": "https://discordapp.com/api/emojis/414840414672125992.png", "BlobConfused": "https://discordapp.com/api/emojis/414840484377264158.png", "BlobLenny": "https://discordapp.com/api/emojis/414840707010920459.png", "BlobCrying": "https://discordapp.com/api/emojis/414841411117121556.png", "blobsad": "https://discordapp.com/api/emojis/319122469795397632.png", "blobsmile": "https://discordapp.com/api/emojis/319360049887576074.png", "blobthumbsup": "https://discordapp.com/api/emojis/324917738894000130.png", "blobcouncil": "https://discordapp.com/api/emojis/317793691257274378.png", "FeelsBlobMan": "https://discordapp.com/api/emojis/317969827861889026.png", "BlobPoliceAngry": "https://discordapp.com/api/emojis/317969829854314496.png", "OKBlob": "https://discordapp.com/api/emojis/317970202492928002.png", "blobthinking": "https://discordapp.com/api/emojis/318287662068924416.png", "photoblobs": "https://discordapp.com/api/emojis/318013782662053888.png", "bloblul": "https://discordapp.com/api/emojis/356789385875816448.png", "blobthink": "https://discordapp.com/api/emojis/318799923116113921.png", "blobangry": "https://discordapp.com/api/emojis/319359420997828608.png", "blobsob": "https://discordapp.com/api/emojis/393353541122523136.png", "blobsurprised": "https://discordapp.com/api/emojis/319359953263394817.png", "blobwink": "https://discordapp.com/api/emojis/319360115129974786.png", "blobheadache": "https://discordapp.com/api/emojis/319360532291387392.png", "blobcorner": "https://discordapp.com/api/emojis/319360584703541249.png", "blobrofl": "https://discordapp.com/api/emojis/319360614923370496.png", "blobfacepalm": "https://discordapp.com/api/emojis/319360831626412032.png", "blobdead": "https://discordapp.com/api/emojis/319361012908163072.png", "blobshh": "https://discordapp.com/api/emojis/324917371259060224.png", "blobhammer": "https://discordapp.com/api/emojis/324917546098622466.png", "blobhappy": "https://discordapp.com/api/emojis/324917905257136128.png", "blobexpressionless": "https://discordapp.com/api/emojis/324918006331473923.png", "blobokhand": "https://discordapp.com/api/emojis/324918192763961344.png", "blobwhat": "https://discordapp.com/api/emojis/324918397643128832.png", "blobpat": "https://discordapp.com/api/emojis/324918615994531840.png", "blobpopcorn": "https://discordapp.com/api/emojis/324918859540856832.png", "blobblush": "https://discordapp.com/api/emojis/324918940256174089.png", "blobstraightface": "https://discordapp.com/api/emojis/324919073903476766.png", "blobhifive": "https://discordapp.com/api/emojis/324919278845427713.png", "sadblob": "https://discordapp.com/api/emojis/414017193890545685.png", "blob": "https://discordapp.com/api/emojis/401869697588527105.png"}
|
|
||||||
@@ -1,673 +0,0 @@
|
|||||||
A Alpha Time Zone Military
|
|
||||||
UTC +1
|
|
||||||
ACDT Australian Central Daylight Time
|
|
||||||
CDT – Central Daylight Time
|
|
||||||
CDST – Central Daylight Savings Time Australia
|
|
||||||
UTC +10:30
|
|
||||||
ACST Australian Central Standard Time
|
|
||||||
CST – Central Standard Time Australia
|
|
||||||
UTC +9:30
|
|
||||||
ACT Acre Time South America
|
|
||||||
UTC -5
|
|
||||||
ACT Australian Central Time Australia
|
|
||||||
UTC +9:30 / +10:30
|
|
||||||
ACWST Australian Central Western Standard Time Australia
|
|
||||||
UTC +8:45
|
|
||||||
ADT Arabia Daylight Time
|
|
||||||
AST – Arabia Summer Time Asia
|
|
||||||
UTC +3
|
|
||||||
ADT Atlantic Daylight Time
|
|
||||||
ADST – Atlantic Daylight Saving Time
|
|
||||||
AST – Atlantic Summer Time
|
|
||||||
HAA – Heure Avancée de l'Atlantique (French)
|
|
||||||
North America
|
|
||||||
Atlantic
|
|
||||||
UTC -3
|
|
||||||
AEDT Australian Eastern Daylight Time
|
|
||||||
EDT – Eastern Daylight Time
|
|
||||||
EDST – Eastern Daylight Saving Time Australia
|
|
||||||
UTC +11
|
|
||||||
AEST Australian Eastern Standard Time
|
|
||||||
EST – Eastern Standard Time
|
|
||||||
AET – Australian Eastern Time Australia
|
|
||||||
UTC +10
|
|
||||||
AET Australian Eastern Time Australia
|
|
||||||
UTC +10:00 / +11:00
|
|
||||||
AFT Afghanistan Time Asia
|
|
||||||
UTC +4:30
|
|
||||||
AKDT Alaska Daylight Time
|
|
||||||
ADST – Alaska Daylight Saving Time North America
|
|
||||||
UTC -8
|
|
||||||
AKST Alaska Standard Time
|
|
||||||
AT – Alaska Time North America
|
|
||||||
UTC -9
|
|
||||||
ALMT Alma-Ata Time Asia
|
|
||||||
UTC +6
|
|
||||||
AMST Amazon Summer Time South America
|
|
||||||
UTC -3
|
|
||||||
AMST Armenia Summer Time
|
|
||||||
AMDT – Armenia Daylight Time Asia
|
|
||||||
UTC +5
|
|
||||||
AMT Amazon Time South America
|
|
||||||
UTC -4
|
|
||||||
AMT Armenia Time Asia
|
|
||||||
UTC +4
|
|
||||||
ANAST Anadyr Summer Time Asia
|
|
||||||
UTC +12
|
|
||||||
ANAT Anadyr Time Asia
|
|
||||||
UTC +12
|
|
||||||
AQTT Aqtobe Time Asia
|
|
||||||
UTC +5
|
|
||||||
ART Argentina Time Antarctica
|
|
||||||
South America
|
|
||||||
UTC -3
|
|
||||||
AST Arabia Standard Time
|
|
||||||
AST – Arabic Standard Time
|
|
||||||
AST – Al Manamah Standard Time Asia
|
|
||||||
UTC +2
|
|
||||||
AST Atlantic Standard Time
|
|
||||||
AT – Atlantic Time
|
|
||||||
AST – Tiempo Estándar del Atlántico (Spanish)
|
|
||||||
HNA – Heure Normale de l'Atlantique (French)
|
|
||||||
North America
|
|
||||||
Atlantic
|
|
||||||
Caribbean
|
|
||||||
UTC -4
|
|
||||||
AT Atlantic Time North America
|
|
||||||
Atlantic
|
|
||||||
Caribbean
|
|
||||||
UTC -4:00 / -3:00
|
|
||||||
AWDT Australian Western Daylight Time
|
|
||||||
WDT – Western Daylight Time
|
|
||||||
WST – Western Summer Time Australia
|
|
||||||
UTC +9
|
|
||||||
AWST Australian Western Standard Time
|
|
||||||
WST – Western Standard Time
|
|
||||||
WAT – Western Australia Time Australia
|
|
||||||
UTC +8
|
|
||||||
AZOST Azores Summer Time
|
|
||||||
AZODT – Azores Daylight Time Atlantic
|
|
||||||
UTC +0
|
|
||||||
AZOT Azores Time
|
|
||||||
AZOST – Azores Standard Time Atlantic
|
|
||||||
UTC -1
|
|
||||||
AZST Azerbaijan Summer Time Asia
|
|
||||||
UTC +5
|
|
||||||
AZT Azerbaijan Time Asia
|
|
||||||
UTC +4
|
|
||||||
AoE Anywhere on Earth Pacific
|
|
||||||
UTC -12
|
|
||||||
B Bravo Time Zone Military
|
|
||||||
UTC +2
|
|
||||||
BNT Brunei Darussalam Time
|
|
||||||
BDT – Brunei Time Asia
|
|
||||||
UTC +8
|
|
||||||
BOT Bolivia Time South America
|
|
||||||
UTC -4
|
|
||||||
BRST Brasília Summer Time
|
|
||||||
BST – Brazil Summer Time
|
|
||||||
BST – Brazilian Summer Time South America
|
|
||||||
UTC -2
|
|
||||||
BRT Brasília Time
|
|
||||||
BT – Brazil Time
|
|
||||||
BT – Brazilian Time South America
|
|
||||||
UTC -3
|
|
||||||
BST Bangladesh Standard Time Asia
|
|
||||||
UTC +6
|
|
||||||
BST Bougainville Standard Time Pacific
|
|
||||||
UTC +11
|
|
||||||
BST British Summer Time
|
|
||||||
BDT – British Daylight Time
|
|
||||||
BDST – British Daylight Saving Time Europe
|
|
||||||
UTC +1
|
|
||||||
BTT Bhutan Time Asia
|
|
||||||
UTC +6
|
|
||||||
C Charlie Time Zone Military
|
|
||||||
UTC +3
|
|
||||||
CAST Casey Time Antarctica
|
|
||||||
UTC +8
|
|
||||||
CAT Central Africa Time Africa
|
|
||||||
UTC +2
|
|
||||||
CCT Cocos Islands Time Indian Ocean
|
|
||||||
UTC +6:30
|
|
||||||
CDT Central Daylight Time
|
|
||||||
CDST – Central Daylight Saving Time
|
|
||||||
NACDT – North American Central Daylight Time
|
|
||||||
HAC – Heure Avancée du Centre (French)
|
|
||||||
North America
|
|
||||||
UTC -5
|
|
||||||
CDT Cuba Daylight Time Caribbean
|
|
||||||
UTC -4
|
|
||||||
CEST Central European Summer Time
|
|
||||||
CEDT – Central European Daylight Time
|
|
||||||
ECST – European Central Summer Time
|
|
||||||
MESZ – Mitteleuropäische Sommerzeit (German)
|
|
||||||
Europe
|
|
||||||
Antarctica
|
|
||||||
UTC +2
|
|
||||||
CET Central European Time
|
|
||||||
ECT – European Central Time
|
|
||||||
CET – Central Europe Time
|
|
||||||
MEZ – Mitteleuropäische Zeit (German)
|
|
||||||
Europe
|
|
||||||
Africa
|
|
||||||
UTC +1
|
|
||||||
CHADT Chatham Island Daylight Time
|
|
||||||
CDT – Chatham Daylight Time Pacific
|
|
||||||
UTC +13:45
|
|
||||||
CHAST Chatham Island Standard Time Pacific
|
|
||||||
UTC +12:45
|
|
||||||
CHOST Choibalsan Summer Time
|
|
||||||
CHODT – Choibalsan Daylight Time
|
|
||||||
CHODST – Choibalsan Daylight Saving Time Asia
|
|
||||||
UTC +9
|
|
||||||
CHOT Choibalsan Time Asia
|
|
||||||
UTC +8
|
|
||||||
CHUT Chuuk Time Pacific
|
|
||||||
UTC +10
|
|
||||||
CIDST Cayman Islands Daylight Saving Time Caribbean
|
|
||||||
UTC -4
|
|
||||||
CIST Cayman Islands Standard Time
|
|
||||||
CIT – Cayman Islands Time Caribbean
|
|
||||||
UTC -5
|
|
||||||
CKT Cook Island Time Pacific
|
|
||||||
UTC -10
|
|
||||||
CLST Chile Summer Time
|
|
||||||
CLDT – Chile Daylight Time South America
|
|
||||||
Antarctica
|
|
||||||
UTC -3
|
|
||||||
CLT Chile Standard Time
|
|
||||||
CT – Chile Time
|
|
||||||
CLST – Chile Standard Time South America
|
|
||||||
Antarctica
|
|
||||||
UTC -4
|
|
||||||
COT Colombia Time South America
|
|
||||||
UTC -5
|
|
||||||
CST Central Standard Time
|
|
||||||
CT – Central Time
|
|
||||||
NACST – North American Central Standard Time
|
|
||||||
CST – Tiempo Central Estándar (Spanish)
|
|
||||||
HNC – Heure Normale du Centre (French)
|
|
||||||
North America
|
|
||||||
Central America
|
|
||||||
UTC -6
|
|
||||||
CST China Standard Time Asia
|
|
||||||
UTC +8
|
|
||||||
CST Cuba Standard Time Caribbean
|
|
||||||
UTC -5
|
|
||||||
CT Central Time North America
|
|
||||||
Central America
|
|
||||||
UTC -6:00 / -5:00
|
|
||||||
CVT Cape Verde Time Africa
|
|
||||||
UTC -1
|
|
||||||
CXT Christmas Island Time Australia
|
|
||||||
UTC +7
|
|
||||||
ChST Chamorro Standard Time
|
|
||||||
GST – Guam Standard Time Pacific
|
|
||||||
UTC +10
|
|
||||||
D Delta Time Zone Military
|
|
||||||
UTC +4
|
|
||||||
DAVT Davis Time Antarctica
|
|
||||||
UTC +7
|
|
||||||
DDUT Dumont-d'Urville Time Antarctica
|
|
||||||
UTC +10
|
|
||||||
E Echo Time Zone Military
|
|
||||||
UTC +5
|
|
||||||
EASST Easter Island Summer Time
|
|
||||||
EADT – Easter Island Daylight Time Pacific
|
|
||||||
UTC -5
|
|
||||||
EAST Easter Island Standard Time Pacific
|
|
||||||
UTC -6
|
|
||||||
EAT Eastern Africa Time
|
|
||||||
EAT – East Africa Time Africa
|
|
||||||
Indian Ocean
|
|
||||||
UTC +3
|
|
||||||
ECT Ecuador Time South America
|
|
||||||
UTC -5
|
|
||||||
EDT Eastern Daylight Time
|
|
||||||
EDST – Eastern Daylight Savings Time
|
|
||||||
NAEDT – North American Eastern Daylight Time
|
|
||||||
HAE – Heure Avancée de l'Est (French)
|
|
||||||
EDT – Tiempo de verano del Este (Spanish)
|
|
||||||
North America
|
|
||||||
Caribbean
|
|
||||||
UTC -4
|
|
||||||
EEST Eastern European Summer Time
|
|
||||||
EEDT – Eastern European Daylight Time
|
|
||||||
OESZ – Osteuropäische Sommerzeit (German)
|
|
||||||
Europe
|
|
||||||
Asia
|
|
||||||
UTC +3
|
|
||||||
EET Eastern European Time
|
|
||||||
OEZ – Osteuropäische Zeit (German)
|
|
||||||
Europe
|
|
||||||
Asia
|
|
||||||
Africa
|
|
||||||
UTC +2
|
|
||||||
EGST Eastern Greenland Summer Time
|
|
||||||
EGST – East Greenland Summer Time North America
|
|
||||||
UTC +0
|
|
||||||
EGT East Greenland Time
|
|
||||||
EGT – Eastern Greenland Time North America
|
|
||||||
UTC -1
|
|
||||||
EST Eastern Standard Time
|
|
||||||
ET – Eastern Time
|
|
||||||
NAEST – North American Eastern Standard Time
|
|
||||||
ET – Tiempo del Este (Spanish)
|
|
||||||
HNE – Heure Normale de l'Est (French)
|
|
||||||
North America
|
|
||||||
Caribbean
|
|
||||||
Central America
|
|
||||||
UTC -5
|
|
||||||
ET Eastern Time North America
|
|
||||||
Caribbean
|
|
||||||
Central America
|
|
||||||
UTC -5:00 / -4:00
|
|
||||||
F Foxtrot Time Zone Military
|
|
||||||
UTC +6
|
|
||||||
FET Further-Eastern European Time Europe
|
|
||||||
UTC +3
|
|
||||||
FJST Fiji Summer Time
|
|
||||||
FJDT – Fiji Daylight Time Pacific
|
|
||||||
UTC +13
|
|
||||||
FJT Fiji Time Pacific
|
|
||||||
UTC +12
|
|
||||||
FKST Falkland Islands Summer Time
|
|
||||||
FKDT – Falkland Island Daylight Time South America
|
|
||||||
UTC -3
|
|
||||||
FKT Falkland Island Time
|
|
||||||
FKST – Falkland Island Standard Time South America
|
|
||||||
UTC -4
|
|
||||||
FNT Fernando de Noronha Time South America
|
|
||||||
UTC -2
|
|
||||||
G Golf Time Zone Military
|
|
||||||
UTC +7
|
|
||||||
GALT Galapagos Time Pacific
|
|
||||||
UTC -6
|
|
||||||
GAMT Gambier Time
|
|
||||||
GAMT – Gambier Islands Time Pacific
|
|
||||||
UTC -9
|
|
||||||
GET Georgia Standard Time Asia
|
|
||||||
UTC +4
|
|
||||||
GFT French Guiana Time South America
|
|
||||||
UTC -3
|
|
||||||
GILT Gilbert Island Time Pacific
|
|
||||||
UTC +12
|
|
||||||
GMT Greenwich Mean Time
|
|
||||||
UTC – Coordinated Universal Time
|
|
||||||
GT – Greenwich Time Europe
|
|
||||||
Africa
|
|
||||||
North America
|
|
||||||
Antarctica
|
|
||||||
UTC +0
|
|
||||||
GST Gulf Standard Time Asia
|
|
||||||
UTC +4
|
|
||||||
GST South Georgia Time South America
|
|
||||||
UTC -2
|
|
||||||
GYT Guyana Time South America
|
|
||||||
UTC -4
|
|
||||||
H Hotel Time Zone Military
|
|
||||||
UTC +8
|
|
||||||
HADT Hawaii-Aleutian Daylight Time
|
|
||||||
HDT – Hawaii Daylight Time North America
|
|
||||||
UTC -9
|
|
||||||
HAST Hawaii-Aleutian Standard Time
|
|
||||||
HST – Hawaii Standard Time North America
|
|
||||||
Pacific
|
|
||||||
UTC -10
|
|
||||||
HKT Hong Kong Time Asia
|
|
||||||
UTC +8
|
|
||||||
HOVST Hovd Summer Time
|
|
||||||
HOVDT – Hovd Daylight Time
|
|
||||||
HOVDST – Hovd Daylight Saving Time Asia
|
|
||||||
UTC +8
|
|
||||||
HOVT Hovd Time Asia
|
|
||||||
UTC +7
|
|
||||||
I India Time Zone Military
|
|
||||||
UTC +9
|
|
||||||
ICT Indochina Time Asia
|
|
||||||
UTC +7
|
|
||||||
IDT Israel Daylight Time Asia
|
|
||||||
UTC +3
|
|
||||||
IOT Indian Chagos Time Indian Ocean
|
|
||||||
UTC +6
|
|
||||||
IRDT Iran Daylight Time
|
|
||||||
IRST – Iran Summer Time
|
|
||||||
IDT – Iran Daylight Time Asia
|
|
||||||
UTC +4:30
|
|
||||||
IRKST Irkutsk Summer Time Asia
|
|
||||||
UTC +9
|
|
||||||
IRKT Irkutsk Time Asia
|
|
||||||
UTC +8
|
|
||||||
IRST Iran Standard Time
|
|
||||||
IT – Iran Time Asia
|
|
||||||
UTC +3:30
|
|
||||||
IST India Standard Time
|
|
||||||
IT – India Time
|
|
||||||
IST – Indian Standard Time Asia
|
|
||||||
UTC +5:30
|
|
||||||
IST Irish Standard Time
|
|
||||||
IST – Irish Summer Time Europe
|
|
||||||
UTC +1
|
|
||||||
IST Israel Standard Time Asia
|
|
||||||
UTC +2
|
|
||||||
JST Japan Standard Time Asia
|
|
||||||
UTC +9
|
|
||||||
K Kilo Time Zone Military
|
|
||||||
UTC +10
|
|
||||||
KGT Kyrgyzstan Time Asia
|
|
||||||
UTC +6
|
|
||||||
KOST Kosrae Time Pacific
|
|
||||||
UTC +11
|
|
||||||
KRAST Krasnoyarsk Summer Time Asia
|
|
||||||
UTC +8
|
|
||||||
KRAT Krasnoyarsk Time Asia
|
|
||||||
UTC +7
|
|
||||||
KST Korea Standard Time
|
|
||||||
KST – Korean Standard Time
|
|
||||||
KT – Korea Time Asia
|
|
||||||
UTC +9
|
|
||||||
KUYT Kuybyshev Time
|
|
||||||
SAMST – Samara Summer Time Europe
|
|
||||||
UTC +4
|
|
||||||
L Lima Time Zone Military
|
|
||||||
UTC +11
|
|
||||||
LHDT Lord Howe Daylight Time Australia
|
|
||||||
UTC +11
|
|
||||||
LHST Lord Howe Standard Time Australia
|
|
||||||
UTC +10:30
|
|
||||||
LINT Line Islands Time Pacific
|
|
||||||
UTC +14
|
|
||||||
M Mike Time Zone Military
|
|
||||||
UTC +12
|
|
||||||
MAGST Magadan Summer Time
|
|
||||||
MAGST – Magadan Island Summer Time Asia
|
|
||||||
UTC +12
|
|
||||||
MAGT Magadan Time
|
|
||||||
MAGT – Magadan Island Time Asia
|
|
||||||
UTC +11
|
|
||||||
MART Marquesas Time Pacific
|
|
||||||
UTC -9:30
|
|
||||||
MAWT Mawson Time Antarctica
|
|
||||||
UTC +5
|
|
||||||
MDT Mountain Daylight Time
|
|
||||||
MDST – Mountain Daylight Saving Time
|
|
||||||
NAMDT – North American Mountain Daylight Time
|
|
||||||
HAR – Heure Avancée des Rocheuses (French)
|
|
||||||
North America
|
|
||||||
UTC -6
|
|
||||||
MHT Marshall Islands Time Pacific
|
|
||||||
UTC +12
|
|
||||||
MMT Myanmar Time Asia
|
|
||||||
UTC +6:30
|
|
||||||
MSD Moscow Daylight Time
|
|
||||||
Moscow Summer Time Europe
|
|
||||||
UTC +4
|
|
||||||
MSK Moscow Standard Time
|
|
||||||
MCK – Moscow Time Europe
|
|
||||||
Asia
|
|
||||||
UTC +3
|
|
||||||
MST Mountain Standard Time
|
|
||||||
MT – Mountain Time
|
|
||||||
NAMST – North American Mountain Standard Time
|
|
||||||
HNR – Heure Normale des Rocheuses (French)
|
|
||||||
North America
|
|
||||||
UTC -7
|
|
||||||
MT Mountain Time North America
|
|
||||||
UTC -7:00 / -6:00
|
|
||||||
MUT Mauritius Time Africa
|
|
||||||
UTC +4
|
|
||||||
MVT Maldives Time Asia
|
|
||||||
UTC +5
|
|
||||||
MYT Malaysia Time
|
|
||||||
MST – Malaysian Standard Time Asia
|
|
||||||
UTC +8
|
|
||||||
N November Time Zone Military
|
|
||||||
UTC -1
|
|
||||||
NCT New Caledonia Time Pacific
|
|
||||||
UTC +11
|
|
||||||
NDT Newfoundland Daylight Time
|
|
||||||
HAT – Heure Avancée de Terre-Neuve (French)
|
|
||||||
North America
|
|
||||||
UTC -2:30
|
|
||||||
NFT Norfolk Time
|
|
||||||
NFT – Norfolk Island Time Australia
|
|
||||||
UTC +11
|
|
||||||
NOVST Novosibirsk Summer Time
|
|
||||||
OMSST – Omsk Summer Time Asia
|
|
||||||
UTC +7
|
|
||||||
NOVT Novosibirsk Time
|
|
||||||
OMST – Omsk Standard Time Asia
|
|
||||||
UTC +6
|
|
||||||
NPT Nepal Time Asia
|
|
||||||
UTC +5:45
|
|
||||||
NRT Nauru Time Pacific
|
|
||||||
UTC +12
|
|
||||||
NST Newfoundland Standard Time
|
|
||||||
HNT – Heure Normale de Terre-Neuve (French)
|
|
||||||
North America
|
|
||||||
UTC -3:30
|
|
||||||
NUT Niue Time Pacific
|
|
||||||
UTC -11
|
|
||||||
NZDT New Zealand Daylight Time Pacific
|
|
||||||
Antarctica
|
|
||||||
UTC +13
|
|
||||||
NZST New Zealand Standard Time Pacific
|
|
||||||
Antarctica
|
|
||||||
UTC +12
|
|
||||||
O Oscar Time Zone Military
|
|
||||||
UTC -2
|
|
||||||
OMSST Omsk Summer Time
|
|
||||||
NOVST – Novosibirsk Summer Time Asia
|
|
||||||
UTC +7
|
|
||||||
OMST Omsk Standard Time
|
|
||||||
OMST – Omsk Time
|
|
||||||
NOVT – Novosibirsk Time Asia
|
|
||||||
UTC +6
|
|
||||||
ORAT Oral Time Asia
|
|
||||||
UTC +5
|
|
||||||
P Papa Time Zone Military
|
|
||||||
UTC -3
|
|
||||||
PDT Pacific Daylight Time
|
|
||||||
PDST – Pacific Daylight Saving Time
|
|
||||||
NAPDT – North American Pacific Daylight Time
|
|
||||||
HAP – Heure Avancée du Pacifique (French)
|
|
||||||
North America
|
|
||||||
UTC -7
|
|
||||||
PET Peru Time South America
|
|
||||||
UTC -5
|
|
||||||
PETST Kamchatka Summer Time Asia
|
|
||||||
UTC +12
|
|
||||||
PETT Kamchatka Time
|
|
||||||
PETT – Petropavlovsk-Kamchatski Time Asia
|
|
||||||
UTC +12
|
|
||||||
PGT Papua New Guinea Time Pacific
|
|
||||||
UTC +10
|
|
||||||
PHOT Phoenix Island Time Pacific
|
|
||||||
UTC +13
|
|
||||||
PHT Philippine Time
|
|
||||||
PST – Philippine Standard Time Asia
|
|
||||||
UTC +8
|
|
||||||
PKT Pakistan Standard Time
|
|
||||||
PKT – Pakistan Time Asia
|
|
||||||
UTC +5
|
|
||||||
PMDT Pierre & Miquelon Daylight Time North America
|
|
||||||
UTC -2
|
|
||||||
PMST Pierre & Miquelon Standard Time North America
|
|
||||||
UTC -3
|
|
||||||
PONT Pohnpei Standard Time Pacific
|
|
||||||
UTC +11
|
|
||||||
PST Pacific Standard Time
|
|
||||||
PT – Pacific Time
|
|
||||||
NAPST – North American Pacific Standard Time
|
|
||||||
PT – Tiempo del Pacífico (Spanish)
|
|
||||||
HNP – Heure Normale du Pacifique (French)
|
|
||||||
North America
|
|
||||||
UTC -8
|
|
||||||
PST Pitcairn Standard Time Pacific
|
|
||||||
UTC -8
|
|
||||||
PT Pacific Time North America
|
|
||||||
UTC -8:00 / -7:00
|
|
||||||
PWT Palau Time Pacific
|
|
||||||
UTC +9
|
|
||||||
PYST Paraguay Summer Time South America
|
|
||||||
UTC -3
|
|
||||||
PYT Paraguay Time South America
|
|
||||||
UTC -4
|
|
||||||
PYT Pyongyang Time
|
|
||||||
PYST – Pyongyang Standard Time Asia
|
|
||||||
UTC +8:30
|
|
||||||
Q Quebec Time Zone Military
|
|
||||||
UTC -4
|
|
||||||
QYZT Qyzylorda Time Asia
|
|
||||||
UTC +6
|
|
||||||
R Romeo Time Zone Military
|
|
||||||
UTC -5
|
|
||||||
RET Reunion Time Africa
|
|
||||||
UTC +4
|
|
||||||
ROTT Rothera Time Antarctica
|
|
||||||
UTC -3
|
|
||||||
S Sierra Time Zone Military
|
|
||||||
UTC -6
|
|
||||||
SAKT Sakhalin Time Asia
|
|
||||||
UTC +11
|
|
||||||
SAMT Samara Time
|
|
||||||
SAMT – Samara Standard Time Europe
|
|
||||||
UTC +4
|
|
||||||
SAST South Africa Standard Time
|
|
||||||
SAST – South African Standard Time Africa
|
|
||||||
UTC +2
|
|
||||||
SBT Solomon Islands Time
|
|
||||||
SBT – Solomon Island Time Pacific
|
|
||||||
UTC +11
|
|
||||||
SCT Seychelles Time Africa
|
|
||||||
UTC +4
|
|
||||||
SGT Singapore Time
|
|
||||||
SST – Singapore Standard Time Asia
|
|
||||||
UTC +8
|
|
||||||
SRET Srednekolymsk Time Asia
|
|
||||||
UTC +11
|
|
||||||
SRT Suriname Time South America
|
|
||||||
UTC -3
|
|
||||||
SST Samoa Standard Time Pacific
|
|
||||||
UTC -11
|
|
||||||
SYOT Syowa Time Antarctica
|
|
||||||
UTC +3
|
|
||||||
T Tango Time Zone Military
|
|
||||||
UTC -7
|
|
||||||
TAHT Tahiti Time Pacific
|
|
||||||
UTC -10
|
|
||||||
TFT French Southern and Antarctic Time
|
|
||||||
KIT – Kerguelen (Islands) Time Indian Ocean
|
|
||||||
UTC +5
|
|
||||||
TJT Tajikistan Time Asia
|
|
||||||
UTC +5
|
|
||||||
TKT Tokelau Time Pacific
|
|
||||||
UTC +13
|
|
||||||
TLT East Timor Time Asia
|
|
||||||
UTC +9
|
|
||||||
TMT Turkmenistan Time Asia
|
|
||||||
UTC +5
|
|
||||||
TOST Tonga Summer Time Pacific
|
|
||||||
UTC +14
|
|
||||||
TOT Tonga Time Pacific
|
|
||||||
UTC +13
|
|
||||||
TRT Turkey Time Asia
|
|
||||||
Europe
|
|
||||||
UTC +3
|
|
||||||
TVT Tuvalu Time Pacific
|
|
||||||
UTC +12
|
|
||||||
U Uniform Time Zone Military
|
|
||||||
UTC -8
|
|
||||||
ULAST Ulaanbaatar Summer Time
|
|
||||||
ULAST – Ulan Bator Summer Time Asia
|
|
||||||
UTC +9
|
|
||||||
ULAT Ulaanbaatar Time
|
|
||||||
ULAT – Ulan Bator Time Asia
|
|
||||||
UTC +8
|
|
||||||
UTC Coordinated Universal Time Worldwide
|
|
||||||
UTC
|
|
||||||
UYST Uruguay Summer Time South America
|
|
||||||
UTC -2
|
|
||||||
UYT Uruguay Time South America
|
|
||||||
UTC -3
|
|
||||||
UZT Uzbekistan Time Asia
|
|
||||||
UTC +5
|
|
||||||
V Victor Time Zone Military
|
|
||||||
UTC -9
|
|
||||||
VET Venezuelan Standard Time
|
|
||||||
HLV – Hora Legal de Venezuela (Spanish)
|
|
||||||
South America
|
|
||||||
UTC -4
|
|
||||||
VLAST Vladivostok Summer Time Asia
|
|
||||||
UTC +11
|
|
||||||
VLAT Vladivostok Time Asia
|
|
||||||
UTC +10
|
|
||||||
VOST Vostok Time Antarctica
|
|
||||||
UTC +6
|
|
||||||
VUT Vanuatu Time
|
|
||||||
EFATE – Efate Time Pacific
|
|
||||||
UTC +11
|
|
||||||
W Whiskey Time Zone Military
|
|
||||||
UTC -10
|
|
||||||
WAKT Wake Time Pacific
|
|
||||||
UTC +12
|
|
||||||
WARST Western Argentine Summer Time South America
|
|
||||||
UTC -3
|
|
||||||
WAST West Africa Summer Time Africa
|
|
||||||
UTC +2
|
|
||||||
WAT West Africa Time Africa
|
|
||||||
UTC +1
|
|
||||||
WEST Western European Summer Time
|
|
||||||
WEDT – Western European Daylight Time
|
|
||||||
WESZ – Westeuropäische Sommerzeit (German)
|
|
||||||
Europe
|
|
||||||
Africa
|
|
||||||
UTC +1
|
|
||||||
WET Western European Time
|
|
||||||
GMT – Greenwich Mean Time
|
|
||||||
WEZ – Westeuropäische Zeit (German)
|
|
||||||
Europe
|
|
||||||
Africa
|
|
||||||
UTC +0
|
|
||||||
WFT Wallis and Futuna Time Pacific
|
|
||||||
UTC +12
|
|
||||||
WGST Western Greenland Summer Time
|
|
||||||
WGST – West Greenland Summer Time North America
|
|
||||||
UTC -2
|
|
||||||
WGT West Greenland Time
|
|
||||||
WGT – Western Greenland Time North America
|
|
||||||
UTC -3
|
|
||||||
WIB Western Indonesian Time
|
|
||||||
WIB – Waktu Indonesia Barat Asia
|
|
||||||
UTC +7
|
|
||||||
WIT Eastern Indonesian Time
|
|
||||||
WIT – Waktu Indonesia Timur Asia
|
|
||||||
UTC +9
|
|
||||||
WITA Central Indonesian Time
|
|
||||||
WITA – Waktu Indonesia Tengah Asia
|
|
||||||
UTC +8
|
|
||||||
WST West Samoa Time
|
|
||||||
ST – Samoa Time Pacific
|
|
||||||
UTC +14
|
|
||||||
WST Western Sahara Summer Time Africa
|
|
||||||
UTC +1
|
|
||||||
WT Western Sahara Standard Time
|
|
||||||
WT – Western Sahara Time Africa
|
|
||||||
UTC +0
|
|
||||||
X X-ray Time Zone Military
|
|
||||||
UTC -11
|
|
||||||
Y Yankee Time Zone Military
|
|
||||||
UTC -12
|
|
||||||
YAKST Yakutsk Summer Time Asia
|
|
||||||
UTC +10
|
|
||||||
YAKT Yakutsk Time Asia
|
|
||||||
UTC +9
|
|
||||||
YAPT Yap Time Pacific
|
|
||||||
UTC +10
|
|
||||||
YEKST Yekaterinburg Summer Time Asia
|
|
||||||
UTC +6
|
|
||||||
YEKT Yekaterinburg Time Asia
|
|
||||||
UTC +5
|
|
||||||
Z Zulu Time Zone Military
|
|
||||||
UTC +0
|
|
||||||
@@ -1,320 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import inspect
|
|
||||||
import os
|
|
||||||
from src.imports import checks, utils
|
|
||||||
|
|
||||||
config_dir = 'src/config/'
|
|
||||||
admin_id_file = 'admin_ids'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
embed_color = discord.Colour.from_rgb(49, 107, 111)
|
|
||||||
bot_config_file = 'bot_config.json'
|
|
||||||
invite_match = '(https?://)?(www.)?discord(app.com/(invite|oauth2)|.gg|.io)/[\w\d_\-?=&/]+'
|
|
||||||
|
|
||||||
admin_log = logging.getLogger('admin')
|
|
||||||
|
|
||||||
|
|
||||||
class Admin:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_config_string(guild_config):
|
|
||||||
config_str = ''
|
|
||||||
for config in guild_config:
|
|
||||||
if isinstance(guild_config[config], dict):
|
|
||||||
config_str = f'{config_str}\n{" "*4}{config}'
|
|
||||||
for item in guild_config[config]:
|
|
||||||
config_str = f'{config_str}\n{" "*8}{item}: {guild_config[config][item]}'
|
|
||||||
elif isinstance(guild_config[config], list):
|
|
||||||
config_str = f'{config_str}\n{" "*4}{config}'
|
|
||||||
for item in guild_config[config]:
|
|
||||||
config_str = f'{config_str}\n{" "*8}{item}'
|
|
||||||
else:
|
|
||||||
config_str = f'{config_str}\n{" "*4}{config}: {guild_config[config]}'
|
|
||||||
return config_str
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def reload_bot_config(self, ctx):
|
|
||||||
with open(f'{config_dir}{bot_config_file}') as file:
|
|
||||||
self.bot.bot_config = json.load(file)
|
|
||||||
del self.bot.bot_config['token']
|
|
||||||
del self.bot.bot_config['db_con']
|
|
||||||
await ctx.send('Config reloaded.')
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def reboot(self, ctx):
|
|
||||||
await ctx.send('Geeksbot is restarting.')
|
|
||||||
with open(f'{config_dir}reboot', 'w') as f:
|
|
||||||
f.write(f'1\n{ctx.channel.id}')
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
os._exit(1)
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def get_bot_config(self, ctx):
|
|
||||||
n = 2000
|
|
||||||
config = [str(self.bot.bot_config)[i:i+n] for i in range(0, len(str(self.bot.bot_config)), n)]
|
|
||||||
for conf in config:
|
|
||||||
await ctx.message.author.send(conf)
|
|
||||||
await ctx.send(f'{ctx.message.author.mention} check your DMs.')
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def update_emojis(self, ctx):
|
|
||||||
emojis = self.bot.emojis
|
|
||||||
for emoji in emojis:
|
|
||||||
if emoji.animated:
|
|
||||||
emoji_code = f'<a:{emoji.name}:{emoji.id}>'
|
|
||||||
else:
|
|
||||||
emoji_code = f'<:{emoji.name}:{emoji.id}>'
|
|
||||||
if await self.bot.db_con.fetch('select id from geeksbot_emojis where id = $1', emoji.id):
|
|
||||||
await self.bot.db_con.execute("update geeksbot_emojis set id = $2, name = $1, code = $3 "
|
|
||||||
"where name = $1", emoji.name, emoji.id, emoji_code)
|
|
||||||
else:
|
|
||||||
await self.bot.db_con.execute("insert into geeksbot_emojis(id,name,code) values ($2,$1,$3)",
|
|
||||||
emoji.name, emoji.id, emoji_code)
|
|
||||||
await ctx.message.add_reaction('✅')
|
|
||||||
await ctx.send(f'Emojis have been updated in the database.')
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.check(checks.is_guild_owner)
|
|
||||||
async def get_guild_config(self, ctx):
|
|
||||||
config = await self.bot.db_con.fetchrow('select * from guild_config where guild_id = $1', ctx.guild.id)
|
|
||||||
configs = [str(config)[i:i+1990] for i in range(0, len(config), 1990)]
|
|
||||||
await ctx.message.author.send(f'The current config for the {ctx.guild.name} guild is:\n')
|
|
||||||
admin_log.info(configs)
|
|
||||||
for config in configs:
|
|
||||||
await ctx.message.author.send(f'```{config}```')
|
|
||||||
await ctx.send(f'{ctx.message.author.mention} check your DMs.')
|
|
||||||
|
|
||||||
@commands.group(case_insensitive=True)
|
|
||||||
async def set(self, ctx):
|
|
||||||
"""Group for setting configuration options"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@commands.group(case_insensitive=True)
|
|
||||||
async def add(self, ctx):
|
|
||||||
"""Group for adding items to guild config"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@commands.group(case_insensitive=True)
|
|
||||||
async def remove(self, ctx):
|
|
||||||
"""Group for removing items from guild config"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@set.command(name='admin_chan', aliases=['ac', 'admin_chat', 'admin chat'])
|
|
||||||
async def _admin_channel(self, ctx, channel: discord.TextChannel=None):
|
|
||||||
"""Sets the admin notification channel"""
|
|
||||||
if ctx.guild:
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
if channel is not None:
|
|
||||||
await self.bot.db_con.execute('update guild_config set admin_chat = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, channel.id)
|
|
||||||
await ctx.send(f'{channel.name} is now set as the Admin Chat channel for this guild.')
|
|
||||||
|
|
||||||
@set.command(name='channel_lockdown', aliases=['lockdown', 'restrict_access', 'cl'])
|
|
||||||
async def _channel_lockdown(self, ctx, config='true'):
|
|
||||||
"""Toggles the channel lockdown restriction
|
|
||||||
When this is active Geeksbot can only respond in channels defined in allowed_channels
|
|
||||||
If you run this before configuring allowed_channels it will tell you to run that command first."""
|
|
||||||
if ctx.guild:
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
if str(config).lower() == 'true':
|
|
||||||
if await self.bot.db_con.fetchval('select allowed_channels from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id) is []:
|
|
||||||
await ctx.send('Please set at least one allowed channel before running this command.')
|
|
||||||
else:
|
|
||||||
await self.bot.db_con.execute('update guild_config set channel_lockdown = True '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
await ctx.send('Channel Lockdown is now active.')
|
|
||||||
elif str(config).lower() == 'false':
|
|
||||||
if await self.bot.db_con.fetchval('select channel_lockdown from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id):
|
|
||||||
await self.bot.db_con.execute('update guild_config set channel_lockdown = False '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
await ctx.send('Channel Lockdown has been deactivated.')
|
|
||||||
else:
|
|
||||||
await ctx.send('Channel Lockdown is already deactivated.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send('This command must be run from inside a guild.')
|
|
||||||
|
|
||||||
@add.command(name='allowed_channels', aliases=['channel', 'ac'])
|
|
||||||
async def _allowed_channels(self, ctx, *, channels):
|
|
||||||
"""Defines channels Geeksbot can respond in
|
|
||||||
This only takes effect if channel_lockdown is enabled.
|
|
||||||
If one of the channels passed is not found then it is ignored."""
|
|
||||||
if ctx.guild:
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
channels = channels.lower().replace(' ', '').split(',')
|
|
||||||
existing_channels = list()
|
|
||||||
channels_add = list()
|
|
||||||
admin_log.info(channels)
|
|
||||||
allowed_channels = await self.bot.db_con.fetchval('select allowed_channels from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
if allowed_channels == 'null':
|
|
||||||
allowed_channels = None
|
|
||||||
|
|
||||||
channels = [discord.utils.get(ctx.guild.channels, name=channel)
|
|
||||||
for channel in channels if channel is not None]
|
|
||||||
|
|
||||||
if allowed_channels and channels:
|
|
||||||
allowed_channels = [int(channel) for channel in json.loads(allowed_channels)]
|
|
||||||
existing_channels = [channel for channel in channels if channel.id in allowed_channels]
|
|
||||||
channels_add = [channel for channel in channels if channel.id not in allowed_channels]
|
|
||||||
allowed_channels += [channel.id for channel in channels if channel.id not in allowed_channels]
|
|
||||||
await self.bot.db_con.execute('update guild_config set allowed_channels = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(allowed_channels))
|
|
||||||
elif channels:
|
|
||||||
admin_log.info('Config is empty')
|
|
||||||
allowed_channels = [channel.id for channel in channels]
|
|
||||||
await self.bot.db_con.execute('update guild_config set allowed_channels = $2 '
|
|
||||||
'where guild_id = $1', ctx.guild.id,
|
|
||||||
json.dumps(allowed_channels))
|
|
||||||
else:
|
|
||||||
await ctx.send('None of those are valid text channels for this guild.')
|
|
||||||
return
|
|
||||||
|
|
||||||
if existing_channels:
|
|
||||||
channel_str = '\n'.join([str(channel.name) for channel in existing_channels])
|
|
||||||
await ctx.send(f'The following channels were skipped because they are already in the config:\n'
|
|
||||||
f'{channel_str}\n')
|
|
||||||
if channels_add:
|
|
||||||
channel_str = '\n'.join([str(channel.name) for channel in channels_add])
|
|
||||||
await ctx.send('The following channels have been added to the allowed channel list:\n'
|
|
||||||
f'{channel_str}\n')
|
|
||||||
await ctx.message.add_reaction('✅')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send('This command must be run from inside a guild.')
|
|
||||||
|
|
||||||
# TODO Fix view_code
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def view_code(self, ctx, code_name):
|
|
||||||
pag = utils.Paginator(self.bot, prefix='```py', suffix='```')
|
|
||||||
pag.add(inspect.getsource(self.bot.all_commands[code_name].callback))
|
|
||||||
for page in pag.pages():
|
|
||||||
await ctx.send(page)
|
|
||||||
|
|
||||||
@add.command(aliases=['prefix', 'p'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.guild)
|
|
||||||
async def add_prefix(self, ctx, *, prefix=None):
|
|
||||||
"""Adds a custom prefix for the current guild
|
|
||||||
Note: This overwrites the default of g$. If you would
|
|
||||||
like to keep using g$ you will need to add it to the
|
|
||||||
Guild config as well."""
|
|
||||||
if ctx.guild:
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id)
|
|
||||||
if prefix is None:
|
|
||||||
await ctx.send(prefixes)
|
|
||||||
return
|
|
||||||
elif prefixes is None:
|
|
||||||
prefixes = prefix.replace(' ', ',').split(',')
|
|
||||||
else:
|
|
||||||
for p in prefix.replace(' ', ',').split(','):
|
|
||||||
prefixes.append(p)
|
|
||||||
if len(prefixes) > 10:
|
|
||||||
await ctx.send(f'Only 10 prefixes are allowed per guild.\nPlease remove some before adding more.')
|
|
||||||
prefixes = prefixes[:10]
|
|
||||||
await self.bot.db_con.execute('update guild_config set prefix = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, prefixes)
|
|
||||||
await ctx.guild.me.edit(nick=f'[{prefixes[0]}] Geeksbot')
|
|
||||||
await ctx.send(f"Updated. You currently have {len(prefixes)} "
|
|
||||||
f"{'prefix' if len(prefixes) == 1 else 'prefixes'} "
|
|
||||||
f"in your config.\n{', '.join(prefixes)}")
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'This command must be run from inside a guild.')
|
|
||||||
|
|
||||||
@remove.command(aliases=['prefix', 'p'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.guild)
|
|
||||||
async def remove_prefix(self, ctx, *, prefix=None):
|
|
||||||
"""Removes custom prefix from the current guild
|
|
||||||
If the last prefix is removed then Geeksbot will default
|
|
||||||
Back to g$"""
|
|
||||||
if ctx.guild:
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id)
|
|
||||||
found = 0
|
|
||||||
if prefix is None:
|
|
||||||
await ctx.send(prefixes)
|
|
||||||
return
|
|
||||||
elif prefixes is None or prefixes == []:
|
|
||||||
await ctx.send('There are no custom prefixes setup for this guild.')
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
prefix = prefix.replace(' ', ',').split(',')
|
|
||||||
for p in prefix:
|
|
||||||
if p in prefixes:
|
|
||||||
prefixes.remove(p)
|
|
||||||
found = 1
|
|
||||||
else:
|
|
||||||
await ctx.send(f'The prefix {p} is not in the config for this guild.')
|
|
||||||
if found:
|
|
||||||
await self.bot.db_con.execute('update guild_config set prefix = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, prefixes)
|
|
||||||
await ctx.guild.me.edit(nick=f'[{prefixes[0] if len(prefixes) != 0 else self.bot.default_prefix}] '
|
|
||||||
f'Geeksbot')
|
|
||||||
await ctx.send(f"Updated. You currently have {len(prefixes)} "
|
|
||||||
f"{'prefix' if len(prefixes) == 1 else 'prefixes'} "
|
|
||||||
f"in your config.\n{', '.join(prefixes)}")
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'This command must be run from inside a guild.')
|
|
||||||
|
|
||||||
@add.command(name='admin_role', aliases=['admin'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.guild)
|
|
||||||
@commands.check(checks.is_guild_owner)
|
|
||||||
async def _add_admin_role(self, ctx, role=None):
|
|
||||||
"""Adds role to the admin list for current guild
|
|
||||||
Allowing members of that role to run admin commands
|
|
||||||
on the current guild."""
|
|
||||||
role = discord.utils.get(ctx.guild.roles, name=role)
|
|
||||||
if role is not None:
|
|
||||||
roles = json.loads(await self.bot.db_con.fetchval('select admin_roles from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if role.name in roles:
|
|
||||||
await ctx.send(f'{role.name} is already registered as an admin role in this guild.')
|
|
||||||
else:
|
|
||||||
roles[role.name] = role.id
|
|
||||||
await self.bot.db_con.execute('update guild_config set admin_roles = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(roles))
|
|
||||||
await ctx.send(f'{role.name} has been added to the list of admin roles for this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send('You must include a role with this command.')
|
|
||||||
|
|
||||||
@remove.command(name='admin_role', aliases=['admin'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.guild)
|
|
||||||
@commands.check(checks.is_guild_owner)
|
|
||||||
async def _remove_admin_role(self, ctx, role=None):
|
|
||||||
"""Removes role from admin list in current guild"""
|
|
||||||
role = discord.utils.get(ctx.guild.roles, name=role)
|
|
||||||
if role is not None:
|
|
||||||
roles = json.loads(await self.bot.db_con.fetchval('select admin_roles from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if role.name in roles:
|
|
||||||
del roles[role.name]
|
|
||||||
await self.bot.db_con.execute('update guild_config set admin_roles = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(roles))
|
|
||||||
await ctx.send(f'{role.name} has been removed from the list of admin roles for this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{role.name} is not registered as an admin role in this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send('You must include a role with this command.')
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Admin(bot))
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
from src.imports import utils
|
|
||||||
|
|
||||||
config_dir = 'config/'
|
|
||||||
admin_id_file = 'admin_ids'
|
|
||||||
extension_dir = 'extensions'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
guild_config_dir = 'guild_config/'
|
|
||||||
rcon_config_file = 'server_rcon_config'
|
|
||||||
dododex_url = 'http://www.dododex.com'
|
|
||||||
embed_color = discord.Colour.from_rgb(49, 107, 111)
|
|
||||||
red_color = discord.Colour.from_rgb(142, 29, 31)
|
|
||||||
bot_config_file = 'bot_config'
|
|
||||||
default_guild_config_file = 'default_guild_config.json'
|
|
||||||
|
|
||||||
events_log = logging.getLogger('events')
|
|
||||||
|
|
||||||
emojis = {
|
|
||||||
'x': '❌',
|
|
||||||
'y': '✅',
|
|
||||||
'poop': '💩',
|
|
||||||
'crown': '👑',
|
|
||||||
'eggplant': '🍆',
|
|
||||||
'sob': '😭',
|
|
||||||
'trident': '🔱'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class BotEvents:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_config_string(guild_config):
|
|
||||||
config_str = ''
|
|
||||||
for config in guild_config:
|
|
||||||
if isinstance(guild_config[config], dict):
|
|
||||||
config_str = f'{config_str}\n{" "*4}{config}'
|
|
||||||
for item in guild_config[config]:
|
|
||||||
config_str = f'{config_str}\n{" "*8}{item}: {guild_config[config][item]}'
|
|
||||||
elif isinstance(guild_config[config], list):
|
|
||||||
config_str = f'{config_str}\n{" "*4}{config}'
|
|
||||||
for item in guild_config[config]:
|
|
||||||
config_str = f'{config_str}\n{" "*8}{item}'
|
|
||||||
else:
|
|
||||||
config_str = f'{config_str}\n{" "*4}{config}: {guild_config[config]}'
|
|
||||||
return config_str
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
async def on_raw_message_delete(self, msg_id, chan_id):
|
|
||||||
await self.bot.db_con.execute('update messages set deleted_at = $1 where id = $2',
|
|
||||||
datetime.utcnow(), msg_id)
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
async def on_raw_bulk_message_delete(self, msg_ids, chan_id):
|
|
||||||
del_time = datetime.utcnow()
|
|
||||||
for msg_id in msg_ids:
|
|
||||||
await self.bot.db_con.execute('update messages set deleted_at = $1 where id = $2',
|
|
||||||
del_time, msg_id)
|
|
||||||
|
|
||||||
async def on_message(self, ctx):
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
if ctx.author in self.bot.infected:
|
|
||||||
if datetime.now().timestamp() > self.bot.infected[ctx.author][1] + 300:
|
|
||||||
del self.bot.infected[ctx.author]
|
|
||||||
# await ctx.channel.send(f'{ctx.author.mention} You have been healed.')
|
|
||||||
else:
|
|
||||||
await ctx.add_reaction(self.bot.infected[ctx.author][0])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
sql = 'insert into messages (id, tts, type, content, embeds, channel, mention_everyone, mentions,\
|
|
||||||
channel_mentions, role_mentions, webhook, attachments, pinned, reactions, guild, created_at,\
|
|
||||||
system_content, author) \
|
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)'
|
|
||||||
msg_data = [ctx.id, ctx.tts, str(ctx.type), ctx.content, [json.dumps(e.to_dict()) for e in ctx.embeds],
|
|
||||||
ctx.channel.id, ctx.mention_everyone, [user.id for user in ctx.mentions],
|
|
||||||
[channel.id for channel in ctx.channel_mentions], [role.id for role in ctx.role_mentions],
|
|
||||||
ctx.webhook_id, [json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
|
|
||||||
'filename': a.filename, 'url': a.url}) for a in ctx.attachments],
|
|
||||||
ctx.pinned, [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions],
|
|
||||||
ctx.guild.id if ctx.guild else ctx.author.id, ctx.created_at, ctx.system_content, ctx.author.id]
|
|
||||||
await self.bot.db_con.execute(sql, *msg_data)
|
|
||||||
if ctx.guild:
|
|
||||||
if ctx.author != ctx.guild.me:
|
|
||||||
if await self.bot.db_con.fetchval("select pg_filter from guild_config where guild_id = $1",
|
|
||||||
ctx.guild.id):
|
|
||||||
profane = 0
|
|
||||||
for word in await self.bot.db_con.fetchval('select profane_words from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id):
|
|
||||||
word = word.strip()
|
|
||||||
if word in ctx.content.lower():
|
|
||||||
events_log.info(f'Found non PG word {word}')
|
|
||||||
repl_str = '\*' * (len(word) - 1)
|
|
||||||
re_replace = re.compile(re.escape(word), re.IGNORECASE)
|
|
||||||
ctx.content = re_replace.sub(f'{word[:1]}{repl_str}', ctx.content)
|
|
||||||
profane = 1
|
|
||||||
if profane:
|
|
||||||
hook = await ctx.channel.create_webhook(name="PG Filter")
|
|
||||||
await ctx.delete()
|
|
||||||
if len(ctx.author.display_name) < 2:
|
|
||||||
username = f'឵{ctx.author.display_name}'
|
|
||||||
else:
|
|
||||||
username = ctx.author.display_name
|
|
||||||
await hook.send(ctx.content, username=username, avatar_url=ctx.author.avatar_url)
|
|
||||||
await hook.delete()
|
|
||||||
|
|
||||||
async def on_reaction_add(self, react, user):
|
|
||||||
if react.emoji == emojis['poop'] and react.message.author.id == 351794468870946827:
|
|
||||||
await react.message.remove_reaction(emojis['poop'], user)
|
|
||||||
await react.message.channel.send(f"You can't Poop on my Owner {user.mention} :P")
|
|
||||||
if react.emoji == emojis['poop'] and react.message.author.id == 396588996706304010:
|
|
||||||
await react.message.remove_reaction(emojis['poop'], user)
|
|
||||||
await react.message.channel.send(f"You can't Poop on me {user.mention} :P")
|
|
||||||
reactions = react.message.reactions
|
|
||||||
reacts = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in reactions]
|
|
||||||
await self.bot.db_con.execute('update messages set reactions = $2 where id = $1',
|
|
||||||
react.message.id, reacts)
|
|
||||||
|
|
||||||
async def on_message_edit(self, before, ctx):
|
|
||||||
previous_content = await self.bot.db_con.fetchval('select previous_content from messages where id = $1', ctx.id)
|
|
||||||
if previous_content:
|
|
||||||
previous_content.append(before.content)
|
|
||||||
else:
|
|
||||||
previous_content = [before.content]
|
|
||||||
previous_embeds = await self.bot.db_con.fetchval('select previous_embeds from messages where id = $1', ctx.id)
|
|
||||||
if previous_embeds:
|
|
||||||
previous_embeds.append([json.dumps(e.to_dict()) for e in before.embeds])
|
|
||||||
else:
|
|
||||||
previous_embeds = [[json.dumps(e.to_dict()) for e in before.embeds]]
|
|
||||||
sql = 'update messages set (edited_at, previous_content, previous_embeds, tts, type, content, embeds, ' \
|
|
||||||
'channel, mention_everyone, mentions, channel_mentions, role_mentions, webhook, attachments, pinned, ' \
|
|
||||||
'reactions, guild, created_at, system_content, author) = ' \
|
|
||||||
'($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)' \
|
|
||||||
'where id = $21'
|
|
||||||
msg_data = [datetime.utcnow(), previous_content, previous_embeds, ctx.tts, str(ctx.type), ctx.content,
|
|
||||||
[json.dumps(e.to_dict()) for e in ctx.embeds], ctx.channel.id, ctx.mention_everyone,
|
|
||||||
[user.id for user in ctx.mentions], [channel.id for channel in ctx.channel_mentions],
|
|
||||||
[role.id for role in ctx.role_mentions], ctx.webhook_id,
|
|
||||||
[json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
|
|
||||||
'filename': a.filename, 'url': a.url}) for a in ctx.attachments], ctx.pinned,
|
|
||||||
[json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions], ctx.guild.id,
|
|
||||||
ctx.created_at, ctx.system_content, ctx.author.id, ctx.id]
|
|
||||||
await self.bot.db_con.execute(sql, *msg_data)
|
|
||||||
|
|
||||||
# noinspection PyMethodMayBeStatic
|
|
||||||
async def on_command_error(self, ctx, error):
|
|
||||||
pag = utils.Paginator(ctx.bot, embed=True, max_line_length=48)
|
|
||||||
pag.set_embed_meta(title=f'Command Error',
|
|
||||||
color=self.bot.error_color,
|
|
||||||
thumbnail=f'{ctx.guild.me.avatar_url}')
|
|
||||||
pag.add(error)
|
|
||||||
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
|
|
||||||
await book.create_book()
|
|
||||||
|
|
||||||
async def on_guild_join(self, guild):
|
|
||||||
with open(f"{config_dir}{default_guild_config_file}", 'r') as file:
|
|
||||||
default_config = json.loads(file.read())
|
|
||||||
admin_role = guild.role_hierarchy[0]
|
|
||||||
default_config['admin_roles'] = {admin_role.name: admin_role.id}
|
|
||||||
default_config['name'] = guild.name.replace("'", "\\'")
|
|
||||||
default_config['guild_id'] = guild.id
|
|
||||||
events_log.info(default_config)
|
|
||||||
await self.bot.db_con.execute("insert into guild_config(guild_id, guild_name, admin_roles, rcon_enabled, "
|
|
||||||
"channel_lockdown, raid_status, pg_filter, patreon_enabled, referral_enabled) "
|
|
||||||
"values ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
|
||||||
default_config['guild_id'], default_config['name'],
|
|
||||||
json.dumps(default_config['admin_roles']), default_config['rcon_enabled'],
|
|
||||||
default_config['channel_lockdown'], default_config['raid_status'],
|
|
||||||
default_config['pg_filter'], default_config['patreon_enabled'],
|
|
||||||
default_config['referral_enabled'])
|
|
||||||
events_log.info(f'Entry Created for {guild.name}')
|
|
||||||
await guild.me.edit(nick='[g$] Geeksbot')
|
|
||||||
|
|
||||||
async def on_guild_remove(self, guild):
|
|
||||||
await self.bot.db_con.execute(f'delete from guild_config where guild_id = $1', guild.id)
|
|
||||||
events_log.info(f'Left the {guild.name} guild.')
|
|
||||||
|
|
||||||
async def on_member_join(self, member):
|
|
||||||
events_log.info(f'Member joined: {member.name} {member.id} Guild: {member.guild.name} {member.guild.id}')
|
|
||||||
join_chan = await self.bot.db_con.fetchval('select join_leave_chat from guild_config where guild_id = $1',
|
|
||||||
member.guild.id)
|
|
||||||
if join_chan:
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
color=embed_color
|
|
||||||
)
|
|
||||||
em.set_thumbnail(url=member.avatar_url)
|
|
||||||
em.add_field(name=f'Welcome {member.name}#{member.discriminator}', value=member.id, inline=False)
|
|
||||||
em.add_field(name='User created on:', value=member.created_at.strftime('%Y-%m-%d at %H:%M:%S GMT'),
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name='Bot:', value=str(member.bot))
|
|
||||||
em.set_footer(text=f"{member.guild.name} | {member.joined_at.strftime('%Y-%m-%d at %H:%M:%S GMT')}",
|
|
||||||
icon_url=member.guild.icon_url)
|
|
||||||
await discord.utils.get(member.guild.channels, id=join_chan).send(embed=em)
|
|
||||||
mem_data = [member.id,
|
|
||||||
member.name,
|
|
||||||
member.discriminator,
|
|
||||||
member.bot
|
|
||||||
]
|
|
||||||
mem = await self.bot.db_con.fetchval('select guilds,nicks from user_data where id = $1', member.id)
|
|
||||||
if mem:
|
|
||||||
mem[1].append(json.dumps({member.guild.id: member.display_name}))
|
|
||||||
mem[0].append(member.guild.id)
|
|
||||||
mem_data.append(mem[1])
|
|
||||||
mem_data.append(mem[0])
|
|
||||||
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) = '
|
|
||||||
'($2, $3, $4, $5, $6) where id = $1', *mem_data)
|
|
||||||
else:
|
|
||||||
mem_data.append([json.dumps({member.guild.id: member.display_name})])
|
|
||||||
mem_data.append([member.guild.id])
|
|
||||||
self.bot.con.run('insert into user_data (id, name, discriminator, bot, nicks, guilds) '
|
|
||||||
'values ($1, $2, $3, $4, $5, $6)', *mem_data)
|
|
||||||
|
|
||||||
async def on_member_remove(self, member):
|
|
||||||
leave_time = datetime.utcnow()
|
|
||||||
events_log.info(f'Member left: {member.name} {member.id} Guild: {member.guild.name} {member.guild.id}')
|
|
||||||
join_chan = await self.bot.db_con.fetchval('select join_leave_chat from guild_config where guild_id = $1',
|
|
||||||
member.guild.id)
|
|
||||||
if join_chan:
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
color=red_color
|
|
||||||
)
|
|
||||||
em.set_thumbnail(url=member.avatar_url)
|
|
||||||
em.add_field(name=f'RIP {member.name}#{member.discriminator}', value=member.id, inline=False)
|
|
||||||
join_time = member.joined_at
|
|
||||||
em.add_field(name='Joined on:', value=join_time.strftime('%Y-%m-%d at %H:%M:%S GMT'), inline=True)
|
|
||||||
em.add_field(name='Bot:', value=str(member.bot), inline=True)
|
|
||||||
em.add_field(name='Left on:', value=leave_time.strftime('%Y-%m-%d at %H:%M:%S GMT'), inline=False)
|
|
||||||
total_time = leave_time - join_time
|
|
||||||
days, remainder = divmod(total_time.total_seconds(), 86400)
|
|
||||||
hours, remainder = divmod(remainder, 3600)
|
|
||||||
minutes, seconds = divmod(remainder, 60)
|
|
||||||
days_str = str(int(days)) + ' days, ' if days != 0 else ''
|
|
||||||
hours_str = str(int(hours)) + ' hours, ' if hours != 0 else ''
|
|
||||||
minutes_str = str(int(minutes)) + ' minutes and ' if minutes != 0 else ''
|
|
||||||
time_str = f"{days_str}{hours_str}{minutes_str}{int(seconds)} seconds"
|
|
||||||
em.add_field(name='Total time in Guild:', value=time_str, inline=False)
|
|
||||||
em.set_footer(text=f"{member.guild.name} | {datetime.utcnow().strftime('%Y-%m-%d at %H:%M:%S GMT')}",
|
|
||||||
icon_url=member.guild.icon_url)
|
|
||||||
await discord.utils.get(member.guild.channels, id=join_chan).send(embed=em)
|
|
||||||
mem_data = [member.id,
|
|
||||||
member.name,
|
|
||||||
member.discriminator,
|
|
||||||
member.bot
|
|
||||||
]
|
|
||||||
mem = await self.bot.db_con.fetchrow('select guilds,nicks from user_data where id = $1', member.id)
|
|
||||||
if mem:
|
|
||||||
mem[0].remove(member.guild.id)
|
|
||||||
mem[1].remove(json.dumps({member.guild.id: member.display_name}))
|
|
||||||
mem_data.append(mem[1])
|
|
||||||
mem_data.append(mem[0])
|
|
||||||
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) = '
|
|
||||||
'($2, $3, $4, $5, $6) where id = $1', *mem_data)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(BotEvents(bot))
|
|
||||||
-175
@@ -1,175 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import asyncio
|
|
||||||
import youtube_dl
|
|
||||||
|
|
||||||
config_dir = 'config/'
|
|
||||||
admin_id_file = 'admin_ids'
|
|
||||||
extension_dir = 'extensions'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
guild_config_dir = 'guild_config/'
|
|
||||||
rcon_config_file = 'server_rcon_config'
|
|
||||||
dododex_url = 'http://www.dododex.com'
|
|
||||||
embed_color = discord.Colour.from_rgb(49, 107, 111)
|
|
||||||
bot_config_file = 'bot_config'
|
|
||||||
default_guild_config_file = 'default_guild_config.json'
|
|
||||||
emoji_guild = 408524303164899338
|
|
||||||
|
|
||||||
events_log = logging.getLogger('events')
|
|
||||||
|
|
||||||
emojis = {
|
|
||||||
'x': '❌',
|
|
||||||
'y': '✅',
|
|
||||||
'poop': '💩'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Fun:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 30, type=commands.BucketType.user)
|
|
||||||
async def infect(self, ctx, member: discord.Member, emoji):
|
|
||||||
"""Infects a user with the given emoji
|
|
||||||
|
|
||||||
Every time the user sends a message that I am also in I will react to that message with said emoji."""
|
|
||||||
if member.id == self.bot.user.id and ctx.author.id != owner_id:
|
|
||||||
await ctx.send(f'You rolled a Critical Fail...\nInfection bounces off and rebounds on the attacker.')
|
|
||||||
member = ctx.author
|
|
||||||
if member in self.bot.infected:
|
|
||||||
await ctx.send(f'{member.display_name} is already infected. '
|
|
||||||
f'Please wait until they are healed before infecting them again...')
|
|
||||||
else:
|
|
||||||
emoji = self.bot.get_emoji(int(emoji.split(':')[2].strip('>'))) if '<:' in emoji \
|
|
||||||
or '<a:' in emoji else emoji
|
|
||||||
self.bot.infected[member] = [emoji, datetime.now().timestamp()]
|
|
||||||
await ctx.send(f"{member.display_name} has been infected with {emoji}")
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def heal(self, ctx, member: discord.Member):
|
|
||||||
"""Removes infection from user."""
|
|
||||||
if ctx.author == member and ctx.author.id != owner_id:
|
|
||||||
await ctx.send('You can\'t heal yourself silly...')
|
|
||||||
else:
|
|
||||||
if member in self.bot.infected:
|
|
||||||
del self.bot.infected[member]
|
|
||||||
await ctx.send(f'{member.mention} You have been healed by {ctx.author.display_name}.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{member.display_name} is not infected...')
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def print_infections(self, ctx):
|
|
||||||
await ctx.author.send(f'```{self.bot.infected}```')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def slap(self, ctx, member: discord.Member):
|
|
||||||
"""IRC Style Trout Slap"""
|
|
||||||
trout = await self.bot.db_con.fetchval("select code from geeksbot_emojis where id = 449083238766477312")
|
|
||||||
if member.id == self.bot.user.id and ctx.author.id != owner_id:
|
|
||||||
await ctx.send(f'You rolled a Critical Fail...\nThe trout bounces off and rebounds on the attacker.')
|
|
||||||
await ctx.send(f'{ctx.author.mention} '
|
|
||||||
f'You slap yourself in the face with a large trout {trout}')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{ctx.author.display_name} slaps '
|
|
||||||
f'{member.mention} around a bit with a large trout {trout}')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_factorial(number):
|
|
||||||
a = 1
|
|
||||||
for i in range(1, int(number)):
|
|
||||||
a = a * (i + 1)
|
|
||||||
return a
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def fact(self, ctx, number: int):
|
|
||||||
"""Returns the given factorial up to 20,000!"""
|
|
||||||
if 0 < number < 20001:
|
|
||||||
n = 1990
|
|
||||||
with ctx.channel.typing():
|
|
||||||
a = await self.bot.loop.run_in_executor(None, self.get_factorial, number)
|
|
||||||
if len(str(a)) > 6000:
|
|
||||||
for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
|
|
||||||
await ctx.author.send(f'```py\n{b}```')
|
|
||||||
await ctx.send(f"{ctx.author.mention} Check your DMs.")
|
|
||||||
else:
|
|
||||||
for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
|
|
||||||
await ctx.send(f'```py\n{b}```')
|
|
||||||
else:
|
|
||||||
await ctx.send("Invalid number. Please enter a number between 0 and 20,000")
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def play(self, ctx, url=None):
|
|
||||||
if ctx.author.voice.channel.name in self.bot.voice_chans:
|
|
||||||
if self.bot.voice_chans[ctx.author.voice.channel.name].is_playing():
|
|
||||||
await ctx.send('There is currently a song playing. Please wait until it is done...')
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.bot.voice_chans[ctx.author.voice.channel.name] = await ctx.author.voice.channel.connect()
|
|
||||||
asyncio.sleep(5)
|
|
||||||
if url:
|
|
||||||
opts = {"format": 'webm[abr>0]/bestaudio/best',
|
|
||||||
"ignoreerrors": True,
|
|
||||||
"default_search": "auto",
|
|
||||||
"source_address": "0.0.0.0",
|
|
||||||
'quiet': True}
|
|
||||||
ydl = youtube_dl.YoutubeDL(opts)
|
|
||||||
info = ydl.extract_info(url, download=False)
|
|
||||||
self.bot.player = discord.FFmpegPCMAudio(info['url'])
|
|
||||||
else:
|
|
||||||
self.bot.player = discord.FFmpegPCMAudio('dead_puppies.mp3')
|
|
||||||
self.bot.player = discord.PCMVolumeTransformer(self.bot.player, volume=0.3)
|
|
||||||
self.bot.voice_chans[ctx.author.voice.channel.name].play(self.bot.player)
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def stop(self, ctx):
|
|
||||||
if ctx.author.voice.channel.name in self.bot.voice_chans:
|
|
||||||
if self.bot.voice_chans[ctx.author.voice.channel.name].is_playing():
|
|
||||||
self.bot.voice_chans[ctx.author.voice.channel.name].stop()
|
|
||||||
else:
|
|
||||||
await ctx.send('Nothing is playing...')
|
|
||||||
else:
|
|
||||||
await ctx.send('Not connected to that voice channel.')
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def disconnect(self, ctx):
|
|
||||||
if ctx.author.voice.channel.name in self.bot.voice_chans:
|
|
||||||
await self.bot.voice_chans[ctx.author.voice.channel.name].disconnect()
|
|
||||||
del self.bot.voice_chans[ctx.author.voice.channel.name]
|
|
||||||
else:
|
|
||||||
await ctx.send('Not connected to that voice channel.')
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def volume(self, ctx, volume: float):
|
|
||||||
self.bot.player.volume = volume
|
|
||||||
|
|
||||||
@commands.command(name='explode', aliases=['splode'])
|
|
||||||
async def explode_user(self, ctx, member: discord.Member=None):
|
|
||||||
"""Trolls user by punching them to oblivion."""
|
|
||||||
if member is None or member.id == 396588996706304010:
|
|
||||||
member = ctx.author
|
|
||||||
|
|
||||||
trans = await self.bot.db_con.fetchval('select code from geeksbot_emojis where id = 405943174809255956')
|
|
||||||
msg = await ctx.send(f'{member.mention}{trans*20}{self.bot.unicode_emojis["left_fist"]}')
|
|
||||||
for i in range(4):
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await msg.edit(content=f'{member.mention}{trans*(20-(i*5))}{self.bot.unicode_emojis["left_fist"]}')
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
await msg.edit(content=f'{self.bot.unicode_emojis["boom"]}')
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
await msg.edit(content=f'{self.bot.unicode_emojis["boom"]} <---- {member.mention} that was you...')
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Fun(bot))
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import logging
|
|
||||||
from src.imports.utils import Paginator, run_command, Book
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
embed_color = discord.Colour.from_rgb(49, 107, 111)
|
|
||||||
|
|
||||||
git_log = logging.getLogger('git')
|
|
||||||
|
|
||||||
|
|
||||||
class Git:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.group(case_insensitive=True, invoke_without_command=True)
|
|
||||||
async def git(self, ctx):
|
|
||||||
"""Shows my Git link"""
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
title=f'Here is where you can find my code',
|
|
||||||
url='https://github.com/dustinpianalto/Geeksbot/tree/development',
|
|
||||||
description='I am the development branch of Geeksbot. You can find the master branch here:\n'
|
|
||||||
'https://github.com/dustinpianalto/Geeksbot/',
|
|
||||||
color=embed_color)
|
|
||||||
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@git.command()
|
|
||||||
@commands.is_owner()
|
|
||||||
async def pull(self, ctx):
|
|
||||||
"""Pulls updates from GitHub rebasing branch."""
|
|
||||||
pag = Paginator(self.bot, max_line_length=44, embed=True)
|
|
||||||
pag.set_embed_meta(title='Git Pull',
|
|
||||||
color=self.bot.embed_color,
|
|
||||||
thumbnail=f'{ctx.guild.me.avatar_url}')
|
|
||||||
pag.add('\uFFF6' + await asyncio.wait_for(self.bot.loop.create_task(run_command('git fetch --all')), 120))
|
|
||||||
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command('git reset --hard '
|
|
||||||
'origin/$(git '
|
|
||||||
'rev-parse --symbolic-full-name'
|
|
||||||
' --abbrev-ref HEAD)')), 120))
|
|
||||||
pag.add('\uFFF7\n\uFFF8')
|
|
||||||
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command('git show --stat | '
|
|
||||||
'sed "s/.*@.*[.].*/ /g"')), 10))
|
|
||||||
book = Book(pag, (None, ctx.channel, self.bot, ctx.message))
|
|
||||||
await book.create_book()
|
|
||||||
|
|
||||||
@git.command()
|
|
||||||
@commands.is_owner()
|
|
||||||
async def status(self, ctx):
|
|
||||||
"""Gets status of current branch."""
|
|
||||||
pag = Paginator(self.bot, max_line_length=44, max_lines=30, embed=True)
|
|
||||||
pag.set_embed_meta(title='Git Status',
|
|
||||||
color=self.bot.embed_color,
|
|
||||||
thumbnail=f'{ctx.guild.me.avatar_url}')
|
|
||||||
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git status')), 10)
|
|
||||||
pag.add(result)
|
|
||||||
book = Book(pag, (None, ctx.channel, self.bot, ctx.message))
|
|
||||||
await book.create_book()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Git(bot))
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import json
|
|
||||||
from src.imports import checks
|
|
||||||
|
|
||||||
config_dir = 'config'
|
|
||||||
extension_dir = 'extensions'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
|
|
||||||
|
|
||||||
class Patreon:
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@commands.command(aliases=['get_patreon', 'patreon'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def get_patreon_links(self, ctx, target: discord.Member=None):
|
|
||||||
"""Prints Patreon information for creators on the server."""
|
|
||||||
if await self.bot.db_con.fetchval('select patreon_enabled from guild_config where guild_id = $1', ctx.guild.id):
|
|
||||||
patreon_info = await self.bot.db_con.fetchrow('select patreon_message,patreon_links from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
message = patreon_info['patreon_message'].replace('\\n', '\n')
|
|
||||||
patreon_links = json.loads(patreon_info['patreon_links'])
|
|
||||||
for key in patreon_links:
|
|
||||||
message = message + '\n{0}: {1}'.format(key, patreon_links[key])
|
|
||||||
if target is None:
|
|
||||||
await ctx.send(message)
|
|
||||||
else:
|
|
||||||
await ctx.send('{0}\n{1}'.format(target.mention, message))
|
|
||||||
else:
|
|
||||||
await ctx.send('Patreon links are not enabled on this guild.')
|
|
||||||
|
|
||||||
@commands.command(aliases=['patreon_message'])
|
|
||||||
async def set_patreon_message(self, ctx, message):
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
patreon_message = await self.bot.db_con.fetchval('select patreon_message from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
if message == patreon_message:
|
|
||||||
await ctx.send('That is already the current message for this guild.')
|
|
||||||
else:
|
|
||||||
await self.bot.db_con.execute('update guild_config set patreon_message = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, message)
|
|
||||||
await ctx.send(f'The patreon message for this guild has been set to:\n{message}')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command(aliases=['add_patreon', 'set_patreon'])
|
|
||||||
async def add_patreon_info(self, ctx, name, url):
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
patreon_info = await self.bot.db_con.fetchval('select patreon_links from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id)
|
|
||||||
patreon_links = {}
|
|
||||||
update = 0
|
|
||||||
if patreon_info:
|
|
||||||
patreon_links = json.loads(patreon_info)
|
|
||||||
if name in patreon_links:
|
|
||||||
update = 1
|
|
||||||
patreon_links[name] = url
|
|
||||||
await self.bot.db_con.execute('update guild_config set patreon_links = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(patreon_links))
|
|
||||||
await ctx.send(f"The Patreon link for {name} has been "
|
|
||||||
f"{'updated to the new url.' if update else'added to the config for this guild.'}")
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command(aliases=['remove_patreon'])
|
|
||||||
async def remove_patreon_info(self, ctx, name):
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
patreon_info = await self.bot.db_con.fetchval('select patreon_links from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id)
|
|
||||||
if patreon_info:
|
|
||||||
patreon_links = json.loads(patreon_info)
|
|
||||||
if name in patreon_links:
|
|
||||||
del patreon_links[name]
|
|
||||||
await self.bot.db_con.execute('update guild_config set patreon_links = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(patreon_links))
|
|
||||||
await ctx.send(f'The Patreon link for {name} has been removed from the config for this guild.')
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{name} is not in the Patreon config for this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'There is no Patreon config for this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
async def enable_patreon(self, ctx, state: bool=True):
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
patreon_status = await self.bot.db_con.fetchval('select patreon_enabled from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
if patreon_status and state:
|
|
||||||
await ctx.send('Patreon is already enabled for this guild.')
|
|
||||||
elif patreon_status and not state:
|
|
||||||
await self.bot.db_con.execute('update guild_config set patreon_enabled = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, state)
|
|
||||||
await ctx.send('Patreon has been disabled for this guild.')
|
|
||||||
elif not patreon_status and state:
|
|
||||||
await self.bot.db_con.execute('update guild_config set patreon_enabled = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, state)
|
|
||||||
await ctx.send('Patreon has been enabled for this guild.')
|
|
||||||
elif not patreon_status and not state:
|
|
||||||
await ctx.send('Patreon is already disabled for this guild.')
|
|
||||||
|
|
||||||
@commands.command(aliases=['referral', 'ref'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def referral_links(self, ctx, target: discord.Member=None):
|
|
||||||
"""Prints G-Portal Referral Links."""
|
|
||||||
if await self.bot.db_con.fetchval('select referral_enabled from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id):
|
|
||||||
referral_info = await self.bot.db_con.fetchval('select referral_message,referral_links from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
message = referral_info[0]
|
|
||||||
referral_links = json.loads(referral_info[1])
|
|
||||||
for key in referral_links:
|
|
||||||
message = message + '\n{0}: {1}'.format(key, referral_links[key])
|
|
||||||
if target is None:
|
|
||||||
await ctx.send(message)
|
|
||||||
else:
|
|
||||||
await ctx.send('{0}\n{1}'.format(target.mention, message))
|
|
||||||
else:
|
|
||||||
await ctx.send('Referrals are not enabled on this guild.')
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Patreon(bot))
|
|
||||||
@@ -1,538 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import json
|
|
||||||
from srcds import rcon as rcon_con
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import asyncio
|
|
||||||
import traceback
|
|
||||||
from src.imports import checks
|
|
||||||
|
|
||||||
config_dir = 'config'
|
|
||||||
admin_id_file = 'admin_ids'
|
|
||||||
extension_dir = 'extensions'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
guild_config_file = 'guild_config'
|
|
||||||
rcon_config_file = 'server_rcon_config'
|
|
||||||
guild_config_dir = 'guild_config/'
|
|
||||||
|
|
||||||
|
|
||||||
rcon_log = logging.getLogger('rcon')
|
|
||||||
|
|
||||||
game_commands = ['admin', ]
|
|
||||||
game_prefix = '$'
|
|
||||||
|
|
||||||
|
|
||||||
class Rcon:
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _listplayers(con_info):
|
|
||||||
con = rcon_con.RconConnection(con_info['ip'], con_info['port'], con_info['password'])
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('listplayers')
|
|
||||||
rcon_log.info(response)
|
|
||||||
while response == b'Keep Alive\x00\x00':
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('listplayers')
|
|
||||||
rcon_log.info(response)
|
|
||||||
return response.strip(b'\n \x00\x00').decode('ascii').strip()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _saveworld(con_info):
|
|
||||||
con = rcon_con.RconConnection(con_info['ip'], con_info['port'], con_info['password'])
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('saveworld')
|
|
||||||
rcon_log.info(response)
|
|
||||||
while response == b'Keep Alive\x00\x00':
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('saveworld')
|
|
||||||
rcon_log.info(response)
|
|
||||||
return response.strip(b'\n \x00\x00').decode('ascii').strip()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _whitelist(con_info, steam_ids):
|
|
||||||
messages = []
|
|
||||||
con = rcon_con.RconConnection(con_info['ip'], con_info['port'], con_info['password'])
|
|
||||||
asyncio.sleep(5)
|
|
||||||
for steam_id in steam_ids:
|
|
||||||
response = con.exec_command('AllowPlayerToJoinNoCheck {0}'.format(steam_id))
|
|
||||||
rcon_log.info(response)
|
|
||||||
while response == b'Keep Alive\x00\x00':
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('AllowPlayerToJoinNoCheck {0}'.format(steam_id))
|
|
||||||
rcon_log.info(response)
|
|
||||||
messages.append(response.strip(b'\n \x00\x00').decode('ascii').strip())
|
|
||||||
return messages
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _broadcast(con_info, message):
|
|
||||||
messages = []
|
|
||||||
con = rcon_con.RconConnection(con_info['ip'], con_info['port'], con_info['password'])
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('broadcast {0}'.format(message))
|
|
||||||
rcon_log.info(response)
|
|
||||||
while response == b'Keep Alive\x00\x00':
|
|
||||||
asyncio.sleep(5)
|
|
||||||
response = con.exec_command('broadcast {0}'.format(message))
|
|
||||||
rcon_log.info(response)
|
|
||||||
messages.append(response.strip(b'\n \x00\x00').decode('ascii').strip())
|
|
||||||
return messages
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_current_chat(con):
|
|
||||||
response = con.exec_command('getchat')
|
|
||||||
rcon_log.debug(response)
|
|
||||||
return response.strip(b'\n \x00\x00').decode('ascii').strip()
|
|
||||||
|
|
||||||
def server_chat_background_process(self, guild_id, con):
|
|
||||||
return_messages = []
|
|
||||||
try:
|
|
||||||
message = 'Server received, But no response!!'
|
|
||||||
time_now = datetime.now().timestamp()
|
|
||||||
while 'Server received, But no response!!' in message and time_now + 20 > datetime.now().timestamp():
|
|
||||||
message = self._get_current_chat(con)
|
|
||||||
rcon_log.debug(message)
|
|
||||||
time.sleep(1)
|
|
||||||
if 'Server received, But no response!!' not in message:
|
|
||||||
for msg in message.split('\n'):
|
|
||||||
msg = '{0} ||| {1}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg.strip())
|
|
||||||
return_messages.append(msg)
|
|
||||||
except rcon_con.RconError:
|
|
||||||
rcon_log.error('RCON Error {0}\n{1}'.format(guild_id, traceback.format_exc()))
|
|
||||||
return_messages.append('RCON Error')
|
|
||||||
except Exception as e:
|
|
||||||
rcon_log.error('Exception {0}\n{1}'.format(guild_id, traceback.format_exc()))
|
|
||||||
return_messages.append('Exception')
|
|
||||||
return_messages.append(e)
|
|
||||||
rcon_log.debug(return_messages)
|
|
||||||
return return_messages
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def admin(ctx, msg, rcon_server, admin_roles):
|
|
||||||
player = msg.split(' ||| ')[1].split(' (')[0]
|
|
||||||
con = rcon_con.RconConnection(rcon_server['ip'],
|
|
||||||
rcon_server['port'],
|
|
||||||
rcon_server['password'],
|
|
||||||
True)
|
|
||||||
con.exec_command('ServerChatToPlayer "{0}" GeeksBot: Admin Geeks have been notified you need assistance. '
|
|
||||||
'Please be patient.'.format(player))
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
con._sock.close()
|
|
||||||
for role in admin_roles:
|
|
||||||
msg = '{0} {1}'.format(msg, discord.utils.get(ctx.guild.roles, id=admin_roles[role]).mention)
|
|
||||||
return msg
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def monitor_chat(self, ctx, *, server=None):
|
|
||||||
"""Begins monitoring the specified ARK server for chat messages and other events.
|
|
||||||
The specified server must already be in the current guild\'s configuration.
|
|
||||||
To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
|
|
||||||
The server argument is not case sensitive and if the server name has two
|
|
||||||
words it can be in one of the following forms:
|
|
||||||
first last
|
|
||||||
first_last
|
|
||||||
"first last"
|
|
||||||
To view all the valid ARK servers for this guild see list_ark_servers."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
if server is not None:
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
|
|
||||||
'from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id))
|
|
||||||
server = server.replace('_', ' ').title()
|
|
||||||
if server in rcon_connections:
|
|
||||||
rcon_connections[server]["monitoring_chat"] = 1
|
|
||||||
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(rcon_connections))
|
|
||||||
channel = self.bot.get_channel(rcon_connections[server]['game_chat_chan_id'])
|
|
||||||
await channel.send('Started monitoring on the {0} server.'.format(server))
|
|
||||||
await ctx.message.add_reaction('✅')
|
|
||||||
rcon_log.debug('Started monitoring on the {0} server.'.format(server))
|
|
||||||
while rcon_connections[server]["monitoring_chat"] == 1:
|
|
||||||
try:
|
|
||||||
con = rcon_con.RconConnection(rcon_connections[server]['ip'],
|
|
||||||
rcon_connections[server]['port'],
|
|
||||||
rcon_connections[server]['password'],
|
|
||||||
True)
|
|
||||||
messages = await self.bot.loop.run_in_executor(None, self.server_chat_background_process,
|
|
||||||
ctx.guild.id, con)
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
con._sock.close()
|
|
||||||
except TimeoutError:
|
|
||||||
rcon_log.error(traceback.format_exc())
|
|
||||||
await channel.send('TimeoutError')
|
|
||||||
await asyncio.sleep(30)
|
|
||||||
else:
|
|
||||||
rcon_log.debug('Got chat from {0}.'.format(server))
|
|
||||||
for message in messages:
|
|
||||||
rcon_log.info(message)
|
|
||||||
message = '```{0}```'.format(message)
|
|
||||||
for command in game_commands:
|
|
||||||
prefix_command = '{0}{1}'.format(game_prefix, command)
|
|
||||||
if prefix_command in message:
|
|
||||||
try:
|
|
||||||
func = getattr(self, command)
|
|
||||||
except AttributeError:
|
|
||||||
rcon_log.warning('Function not found "{0}"'.format(command))
|
|
||||||
else:
|
|
||||||
rcon_log.info(f'Sending to {command}')
|
|
||||||
message = func(ctx, message, rcon_connections['server'])
|
|
||||||
await channel.send('{0}'.format(message))
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
|
|
||||||
'from guild_config '
|
|
||||||
'where guild_id = $1',
|
|
||||||
ctx.guild.id))
|
|
||||||
await channel.send('Monitoring Stopped')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'Server not found: {server}')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You must include a server in this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def end_monitor_chat(self, ctx, *, server=None):
|
|
||||||
"""Ends chat monitoring on the specified server.
|
|
||||||
Context is the same as monitor_chat"""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
if server is not None:
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
|
|
||||||
'from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id))
|
|
||||||
server = server.replace('_', ' ').title()
|
|
||||||
if server in rcon_connections:
|
|
||||||
rcon_connections[server]["monitoring_chat"] = 0
|
|
||||||
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(rcon_connections))
|
|
||||||
else:
|
|
||||||
await ctx.send(f'Server not found: {server}')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You must include a server in this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def listplayers(self, ctx, *, server=None):
|
|
||||||
"""Lists the players currently connected to the specified ARK server.
|
|
||||||
The specified server must already be in the current guild\'s configuration.
|
|
||||||
To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
|
|
||||||
The server argument is not case sensitive and if the server name has two
|
|
||||||
words it can be in one of the following forms:
|
|
||||||
first last
|
|
||||||
first_last
|
|
||||||
"first last"
|
|
||||||
To view all the valid ARK servers for this guild see list_ark_servers."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if server is not None:
|
|
||||||
server = server.replace('_', ' ').title()
|
|
||||||
if server in rcon_connections:
|
|
||||||
connection_info = rcon_connections[server]
|
|
||||||
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
|
|
||||||
await ctx.channel.trigger_typing()
|
|
||||||
message = self._listplayers(connection_info)
|
|
||||||
await ctx.channel.trigger_typing()
|
|
||||||
await msg.delete()
|
|
||||||
await ctx.send('Players currently on the {0} server:\n{1}'.format(server.title(), message))
|
|
||||||
else:
|
|
||||||
await ctx.send('That server is not in my configuration.\nPlease add it via !add_rcon_server "{0}" '
|
|
||||||
'"ip" port "password" if you would like to get info from it.'.format(server))
|
|
||||||
else:
|
|
||||||
for server in rcon_connections:
|
|
||||||
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
connection_info = rcon_connections[server]
|
|
||||||
async with ctx.channel.typing():
|
|
||||||
message = self._listplayers(connection_info)
|
|
||||||
except Exception as e:
|
|
||||||
await msg.delete()
|
|
||||||
await ctx.send(f'Player listing failed on the {server.title()}\n{e}')
|
|
||||||
else:
|
|
||||||
await msg.delete()
|
|
||||||
await ctx.send('Players currently on the {0} server:\n{1}'.format(server.title(), message))
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def add_rcon_server(self, ctx, server, ip, port, password):
|
|
||||||
"""Adds the specified server to the current guild\'s rcon config.
|
|
||||||
All strings (<server>, <ip>, <password>) must be contained inside double quotes."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
server = server.title()
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if server not in rcon_connections:
|
|
||||||
rcon_connections[server] = {
|
|
||||||
'ip': ip,
|
|
||||||
'port': port,
|
|
||||||
'password': password,
|
|
||||||
'name': server.lower().replace(' ', '_'),
|
|
||||||
'game_chat_chan_id': 0,
|
|
||||||
'msg_chan_id': 0,
|
|
||||||
'monitoring_chat': 0
|
|
||||||
}
|
|
||||||
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(rcon_connections))
|
|
||||||
await ctx.send('{0} server has been added to my configuration.'.format(server))
|
|
||||||
else:
|
|
||||||
await ctx.send('This server name is already in my configuration. Please choose another.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
await ctx.message.delete()
|
|
||||||
await ctx.send('Command deleted to prevent password leak.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def remove_rcon_server(self, ctx, server):
|
|
||||||
"""removes the specified server from the current guild\'s rcon config.
|
|
||||||
All strings <server> must be contained inside double quotes."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
server = server.title()
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if server in rcon_connections:
|
|
||||||
del rcon_connections[server]
|
|
||||||
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(rcon_connections))
|
|
||||||
await ctx.send('{0} has been removed from my configuration.'.format(server))
|
|
||||||
else:
|
|
||||||
await ctx.send('{0} is not in my configuration.'.format(server))
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def add_whitelist(self, ctx, *, steam_ids=None):
|
|
||||||
"""Adds the included Steam 64 IDs to the running whitelist on all the ARK servers in the current guild\'s rcon config.
|
|
||||||
Steam 64 IDs should be a comma seperated list of IDs.
|
|
||||||
Example: 76561198024193239,76561198024193239,76561198024193239"""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
if steam_ids is not None:
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
|
|
||||||
'from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id))
|
|
||||||
error = 0
|
|
||||||
error_msg = ''
|
|
||||||
success_msg = 'Adding to the running whitelist on all servers.'
|
|
||||||
steam_ids = steam_ids.replace(', ', ',').replace(' ', ',').split(',')
|
|
||||||
for (i, steam_id) in enumerate(steam_ids):
|
|
||||||
try:
|
|
||||||
steam_id = int(steam_id)
|
|
||||||
except ValueError:
|
|
||||||
error = 1
|
|
||||||
error_msg = '{0}\n__**ERROR:**__ {1} is not a valid Steam64 ID'.format(error_msg, steam_id)
|
|
||||||
else:
|
|
||||||
steam_ids[i] = steam_id
|
|
||||||
if error == 0:
|
|
||||||
msg = await ctx.send(success_msg)
|
|
||||||
for server in rcon_connections:
|
|
||||||
try:
|
|
||||||
success_msg = '{0}\n\n{1}:'.format(success_msg, server.title())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
messages = await self.bot.loop.run_in_executor(None,
|
|
||||||
self._whitelist,
|
|
||||||
rcon_connections[server],
|
|
||||||
steam_ids)
|
|
||||||
except Exception as e:
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, e)
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
else:
|
|
||||||
for message in messages:
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, message.strip())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
await msg.add_reaction('✅')
|
|
||||||
else:
|
|
||||||
await ctx.send(error_msg)
|
|
||||||
else:
|
|
||||||
await ctx.send('I need a list of steam IDs to add to the whitelist.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def saveworld(self, ctx, *, server=None):
|
|
||||||
"""Runs SaveWorld on the specified ARK server.
|
|
||||||
If a server is not specified it will default to running saveworld on all servers in the guild\'s config.
|
|
||||||
Will print out "World Saved" for each server when the command completes successfully."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
success_msg = 'Running saveworld'
|
|
||||||
if server is None:
|
|
||||||
success_msg += ' on all the servers:'
|
|
||||||
msg = await ctx.send(success_msg)
|
|
||||||
for server in rcon_connections:
|
|
||||||
try:
|
|
||||||
success_msg = '{0}\n\n{1}:'.format(success_msg, server.title())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
message = await self.bot.loop.run_in_executor(None, self._saveworld, rcon_connections[server])
|
|
||||||
except Exception as e:
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, e)
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
else:
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, message.strip())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
await msg.add_reaction('✅')
|
|
||||||
elif server.replace('_', ' ').title() in rcon_connections:
|
|
||||||
success_msg = '{0} {1}:'.format(success_msg, server.replace('_', ' ').title())
|
|
||||||
msg = await ctx.send(success_msg)
|
|
||||||
message = await self.bot.loop.run_in_executor(None, self._saveworld, rcon_connections[server.title()])
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, message.strip())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
await msg.add_reaction('✅')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{server.title()} is not currently in the configuration for this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.group(case_insensitive=True)
|
|
||||||
async def broadcast(self, ctx):
|
|
||||||
"""Run help broadcast for more info"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@broadcast.command(name='all')
|
|
||||||
@commands.guild_only()
|
|
||||||
async def broadcast_all(self, ctx, *, message=None):
|
|
||||||
"""Sends a broadcast message to all servers in the guild config.
|
|
||||||
The message will be prefixed with the Discord name of the person running the command.
|
|
||||||
Will print "Success" for each server once the broadcast is sent."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if message is not None:
|
|
||||||
message = f'{ctx.author.display_name}: {message}'
|
|
||||||
success_msg = f'Broadcasting "{message}" to all servers.'
|
|
||||||
msg = await ctx.send(success_msg)
|
|
||||||
for server in rcon_connections:
|
|
||||||
try:
|
|
||||||
success_msg = '{0}\n\n{1}:'.format(success_msg, server.title())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
messages = await self.bot.loop.run_in_executor(None,
|
|
||||||
self._broadcast,
|
|
||||||
rcon_connections[server],
|
|
||||||
message)
|
|
||||||
except Exception as e:
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, e)
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
else:
|
|
||||||
for mesg in messages:
|
|
||||||
if mesg == 'Server received, But no response!!':
|
|
||||||
mesg = 'Success'
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, mesg.strip())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
await msg.add_reaction('✅')
|
|
||||||
else:
|
|
||||||
await ctx.send('You must include a message with this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@broadcast.command(name='server')
|
|
||||||
@commands.guild_only()
|
|
||||||
async def broadcast_server(self, ctx, server, *, message=None):
|
|
||||||
"""Sends a broadcast message to the specified server that is in the guild's config.
|
|
||||||
The message will be prefixed with the Discord name of the person running the command.
|
|
||||||
If <server> has more than one word in it's name it will either need to be surrounded
|
|
||||||
by double quotes or the words separated by _"""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
if server is not None:
|
|
||||||
server = server.replace('_', ' ').title()
|
|
||||||
if message is not None:
|
|
||||||
message = f'{ctx.author.display_name}: {message}'
|
|
||||||
success_msg = f'Broadcasting "{message}" to {server}.'
|
|
||||||
msg = await ctx.send(success_msg)
|
|
||||||
if server in rcon_connections:
|
|
||||||
messages = await self.bot.loop.run_in_executor(None,
|
|
||||||
self._broadcast,
|
|
||||||
rcon_connections[server],
|
|
||||||
message)
|
|
||||||
for mesg in messages:
|
|
||||||
if mesg != 'Server received, But no response!!':
|
|
||||||
success_msg = '{0}\n{1}'.format(success_msg, mesg.strip())
|
|
||||||
await msg.edit(content=success_msg.strip())
|
|
||||||
await msg.add_reaction('✅')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{server} is not in the config for this guild')
|
|
||||||
else:
|
|
||||||
await ctx.send('You must include a message with this command.')
|
|
||||||
else:
|
|
||||||
await ctx.send('You must include a server with this command')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.guild_only()
|
|
||||||
async def create_server_chat_chans(self, ctx):
|
|
||||||
"""Creates a category and server chat channels in the current guild.
|
|
||||||
The category will be named "Server Chats" and read_messages is disabled for the guild default role (everyone)
|
|
||||||
This can be overridden by modifying the category's permissions.
|
|
||||||
Inside this category a channel will be created for each server in the current guild's rcon config
|
|
||||||
and these channel's permissions will be synced to the category.
|
|
||||||
These channels will be added to the guild's rcon config and are where the
|
|
||||||
server chat messages will be sent when monitor_chat is run."""
|
|
||||||
if await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
edited = 0
|
|
||||||
category = discord.utils.get(ctx.guild.categories, name='Server Chats')
|
|
||||||
if category is None:
|
|
||||||
overrides = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False)}
|
|
||||||
category = await ctx.guild.create_category('Server Chats', overwrites=overrides)
|
|
||||||
channels = ctx.guild.channels
|
|
||||||
cat_chans = []
|
|
||||||
for channel in channels:
|
|
||||||
if channel.category_id == category.id:
|
|
||||||
cat_chans.append(channel)
|
|
||||||
for server in rcon_connections:
|
|
||||||
exists = 0
|
|
||||||
if cat_chans:
|
|
||||||
for channel in cat_chans:
|
|
||||||
if rcon_connections[server]['game_chat_chan_id'] == channel.id:
|
|
||||||
exists = 1
|
|
||||||
if exists == 0:
|
|
||||||
print('Creating {}'.format(server))
|
|
||||||
chan = await ctx.guild.create_text_channel(rcon_connections[server]['name'], category=category)
|
|
||||||
rcon_connections[server]['game_chat_chan_id'] = chan.id
|
|
||||||
edited = 1
|
|
||||||
if edited == 1:
|
|
||||||
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
|
|
||||||
ctx.guild.id, json.dumps(rcon_connections))
|
|
||||||
await ctx.message.add_reaction('✅')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command(aliases=['servers', 'list_servers'])
|
|
||||||
@commands.guild_only()
|
|
||||||
@commands.check(checks.is_restricted_chan)
|
|
||||||
async def list_ark_servers(self, ctx):
|
|
||||||
"""Returns a list of all the ARK servers in the current guild\'s config."""
|
|
||||||
servers = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id))
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
title=f'__**There are currently {len(servers)} ARK servers in my config:**__',
|
|
||||||
color=discord.Colour.green()
|
|
||||||
)
|
|
||||||
if ctx.guild.icon:
|
|
||||||
em.set_thumbnail(url=f'{ctx.guild.icon_url}')
|
|
||||||
for server in servers:
|
|
||||||
description = f"""
|
|
||||||
឵ **IP:** {servers[server]['ip']}:{servers[server]['port']}
|
|
||||||
឵ **Steam Connect:** [steam://connect/{servers[server]['ip']}:{servers[server]['port']}]\
|
|
||||||
(steam://connect/{servers[server]['ip']}:{servers[server]['port']})"""
|
|
||||||
em.add_field(name=f'__***{server}***__', value=description, inline=False)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Rcon(bot))
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
|
|
||||||
from discord.ext import commands
|
|
||||||
import asyncio
|
|
||||||
import traceback
|
|
||||||
import discord
|
|
||||||
import inspect
|
|
||||||
import textwrap
|
|
||||||
from contextlib import redirect_stdout
|
|
||||||
import io
|
|
||||||
from src.imports.utils import run_command, format_output, Paginator
|
|
||||||
|
|
||||||
ownerids = [351794468870946827, 275280442884751360]
|
|
||||||
ownerid = 351794468870946827
|
|
||||||
|
|
||||||
|
|
||||||
class Repl:
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self._last_result = None
|
|
||||||
self.sessions = set()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cleanup_code(content):
|
|
||||||
"""Automatically removes code blocks from the code."""
|
|
||||||
if content.startswith('```') and content.endswith('```'):
|
|
||||||
return '\n'.join(content.split('\n')[1:(- 1)])
|
|
||||||
return content.strip('` \n')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_syntax_error(e):
|
|
||||||
if e.text is None:
|
|
||||||
return '```py\n{0.__class__.__name__}: {0}\n```'.format(e)
|
|
||||||
return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(e, '^', type(e).__name__)
|
|
||||||
|
|
||||||
@commands.command(hidden=True, name='exec')
|
|
||||||
async def _eval(self, ctx, *, body: str):
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
return
|
|
||||||
pag = Paginator(self.bot)
|
|
||||||
env = {
|
|
||||||
'bot': self.bot,
|
|
||||||
'ctx': ctx,
|
|
||||||
'channel': ctx.channel,
|
|
||||||
'author': ctx.author,
|
|
||||||
'server': ctx.guild,
|
|
||||||
'message': ctx.message,
|
|
||||||
'_': self._last_result,
|
|
||||||
}
|
|
||||||
env.update(globals())
|
|
||||||
body = self.cleanup_code(body)
|
|
||||||
stdout = io.StringIO()
|
|
||||||
to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ')
|
|
||||||
try:
|
|
||||||
exec(to_compile, env)
|
|
||||||
except SyntaxError as e:
|
|
||||||
return await ctx.send(self.get_syntax_error(e))
|
|
||||||
func = env['func']
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
with redirect_stdout(stdout):
|
|
||||||
ret = await func()
|
|
||||||
except Exception:
|
|
||||||
pag.add(stdout.getvalue())
|
|
||||||
pag.add(traceback.format_exc())
|
|
||||||
for page in pag.pages():
|
|
||||||
await ctx.send(page)
|
|
||||||
else:
|
|
||||||
value = stdout.getvalue()
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
await ctx.message.add_reaction('✅')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
value = format_output(value)
|
|
||||||
pag.add(value)
|
|
||||||
pag.add(f'\nReturned: {ret}')
|
|
||||||
self._last_result = ret
|
|
||||||
for page in pag.pages():
|
|
||||||
await ctx.send(page)
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
async def repl(self, ctx):
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
return
|
|
||||||
msg = ctx.message
|
|
||||||
variables = {
|
|
||||||
'ctx': ctx,
|
|
||||||
'bot': self.bot,
|
|
||||||
'message': msg,
|
|
||||||
'server': msg.guild,
|
|
||||||
'channel': msg.channel,
|
|
||||||
'author': msg.author,
|
|
||||||
'_': None,
|
|
||||||
}
|
|
||||||
if msg.channel.id in self.sessions:
|
|
||||||
await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.')
|
|
||||||
return
|
|
||||||
self.sessions.add(msg.channel.id)
|
|
||||||
await ctx.send('Enter code to execute or evaluate. `exit()` or `quit` to exit.')
|
|
||||||
while True:
|
|
||||||
response = await self.bot.wait_for('message', check=(lambda m: m.content.startswith('`')))
|
|
||||||
if response.author.id == ownerid:
|
|
||||||
cleaned = self.cleanup_code(response.content)
|
|
||||||
if cleaned in ('quit', 'exit', 'exit()'):
|
|
||||||
await response.channel.send('Exiting.')
|
|
||||||
self.sessions.remove(msg.channel.id)
|
|
||||||
return
|
|
||||||
executor = exec
|
|
||||||
if cleaned.count('\n') == 0:
|
|
||||||
try:
|
|
||||||
code = compile(cleaned, '<repl session>', 'eval')
|
|
||||||
except SyntaxError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
executor = eval
|
|
||||||
if executor is exec:
|
|
||||||
try:
|
|
||||||
code = compile(cleaned, '<repl session>', 'exec')
|
|
||||||
except SyntaxError as e:
|
|
||||||
await response.channel.send(self.get_syntax_error(e))
|
|
||||||
continue
|
|
||||||
variables['message'] = response
|
|
||||||
fmt = None
|
|
||||||
stdout = io.StringIO()
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
with redirect_stdout(stdout):
|
|
||||||
result = executor(code, variables)
|
|
||||||
if inspect.isawaitable(result):
|
|
||||||
result = await result
|
|
||||||
except Exception:
|
|
||||||
value = stdout.getvalue()
|
|
||||||
fmt = '{}{}'.format(value, traceback.format_exc())
|
|
||||||
else:
|
|
||||||
value = stdout.getvalue()
|
|
||||||
if result is not None:
|
|
||||||
fmt = '{}{}'.format(value, result)
|
|
||||||
variables['_'] = result
|
|
||||||
elif value:
|
|
||||||
fmt = '{}'.format(value)
|
|
||||||
try:
|
|
||||||
if fmt is not None:
|
|
||||||
pag = Paginator(self.bot)
|
|
||||||
pag.add(fmt)
|
|
||||||
for page in pag.pages():
|
|
||||||
await response.channel.send(page)
|
|
||||||
await ctx.send(response.channel)
|
|
||||||
except discord.Forbidden:
|
|
||||||
pass
|
|
||||||
except discord.HTTPException as e:
|
|
||||||
await msg.channel.send('Unexpected error: `{}`'.format(e))
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
async def os(self, ctx, *, body: str):
|
|
||||||
if ctx.author.id != ownerid:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
body = self.cleanup_code(body)
|
|
||||||
pag = Paginator(self.bot)
|
|
||||||
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 10))
|
|
||||||
for page in pag.pages():
|
|
||||||
await ctx.send(page)
|
|
||||||
await ctx.message.add_reaction('✅')
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
await ctx.send(f"Command did not complete in the time allowed.")
|
|
||||||
await ctx.message.add_reaction('❌')
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Repl(bot))
|
|
||||||
@@ -1,781 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import math
|
|
||||||
import psutil
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import asyncio
|
|
||||||
import async_timeout
|
|
||||||
from src.imports import checks, utils
|
|
||||||
import pytz
|
|
||||||
import gspread
|
|
||||||
from oauth2client.service_account import ServiceAccountCredentials
|
|
||||||
import matplotlib as mpl
|
|
||||||
mpl.use('Agg')
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
from mpl_toolkits.basemap import Basemap
|
|
||||||
from io import BytesIO
|
|
||||||
from itertools import chain
|
|
||||||
import numpy as np
|
|
||||||
from dateutil.parser import parse
|
|
||||||
from copy import copy
|
|
||||||
|
|
||||||
config_dir = 'config/'
|
|
||||||
admin_id_file = 'admin_ids'
|
|
||||||
extension_dir = 'extensions'
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
embed_color = discord.Colour.from_rgb(49, 107, 111)
|
|
||||||
bot_config_file = 'bot_config.json'
|
|
||||||
invite_match = '(https?://)?(www.)?discord(app.com/(invite|oauth2)|.gg|.io)/[\w\d_\-?=&/]+'
|
|
||||||
|
|
||||||
utils_log = logging.getLogger('utils')
|
|
||||||
clock_emojis = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚']
|
|
||||||
replace_tzs = {'MST': 'US/Mountain', 'HST': 'US/Hawaii', 'EST': 'US/Eastern'}
|
|
||||||
|
|
||||||
|
|
||||||
class Utils:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
async def _4_hour_ping(self, channel, message, wait_time):
|
|
||||||
channel = self.bot.get_channel(channel)
|
|
||||||
time_now = datetime.utcnow()
|
|
||||||
await channel.send(message)
|
|
||||||
while True:
|
|
||||||
if time_now < datetime.utcnow() - timedelta(seconds=wait_time+10):
|
|
||||||
await channel.send(message)
|
|
||||||
time_now = datetime.utcnow()
|
|
||||||
await asyncio.sleep(wait_time/100)
|
|
||||||
|
|
||||||
async def background_ping(self, ctx, i):
|
|
||||||
def check(message):
|
|
||||||
return message.author == ctx.guild.me and 'ping_test' in message.content
|
|
||||||
msg = await self.bot.wait_for('message', timeout=5, check=check)
|
|
||||||
self.bot.ping_times[i]['rec'] = msg
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
async def channel_ping(self, ctx, wait_time: float=10, message: str='=bump', channel: int=265828729970753537):
|
|
||||||
await ctx.send('Starting Background Process.')
|
|
||||||
self.bot.loop.create_task(self._4_hour_ping(channel, message, wait_time))
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
@commands.is_owner()
|
|
||||||
async def sysinfo(self, ctx):
|
|
||||||
"""Gets system status for my server."""
|
|
||||||
await ctx.send(f'```ml\n'
|
|
||||||
f'CPU Percentages: {psutil.cpu_percent(percpu=True)}\n'
|
|
||||||
f'Memory Usage: {psutil.virtual_memory().percent}%\n'
|
|
||||||
f'Disc Usage: {psutil.disk_usage("/").percent}%\n'
|
|
||||||
f'```')
|
|
||||||
|
|
||||||
@commands.command(hidden=True)
|
|
||||||
async def role(self, ctx, role: str):
|
|
||||||
if ctx.guild.id == 396156980974059531 and role != 'Admin' and role != 'Admin Geeks':
|
|
||||||
role = discord.utils.get(ctx.guild.roles, name=role)
|
|
||||||
if role is not None:
|
|
||||||
await ctx.message.author.add_roles(role)
|
|
||||||
await ctx.send("Roles Updated")
|
|
||||||
else:
|
|
||||||
await ctx.send('Unknown Role')
|
|
||||||
else:
|
|
||||||
await ctx.send("You are not authorized to send this command.")
|
|
||||||
|
|
||||||
@commands.command(aliases=['oauth', 'link'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def invite(self, ctx, guy: discord.User=None):
|
|
||||||
"""Shows you the bot's invite link.
|
|
||||||
If you pass in an ID of another bot, it gives you the invite link to that bot.
|
|
||||||
"""
|
|
||||||
guy = guy or self.bot.user
|
|
||||||
url = discord.utils.oauth_url(guy.id)
|
|
||||||
await ctx.send(f'**{url}**')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_date_string(time, time_now):
|
|
||||||
diff = (time_now - time)
|
|
||||||
date_str = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
return f"{diff.days} {'day' if diff.days == 1 else 'days'} " \
|
|
||||||
f"{diff.seconds // 3600} {'hour' if diff.seconds // 3600 == 1 else 'hours'} " \
|
|
||||||
f"{diff.seconds % 3600 // 60} {'minute' if diff.seconds % 3600 // 60 == 1 else 'minutes'} " \
|
|
||||||
f"{diff.seconds % 3600 % 60} {'second' if diff.seconds % 3600 % 60 == 1 else 'seconds'} ago.\n{date_str}"
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def me(self, ctx):
|
|
||||||
"""Prints out your user information."""
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
title=f'{ctx.author.name}#{ctx.author.discriminator} ({ctx.author.display_name})',
|
|
||||||
description=f'({ctx.author.id})',
|
|
||||||
color=embed_color)
|
|
||||||
em.set_thumbnail(url=f'{ctx.author.avatar_url}')
|
|
||||||
em.add_field(name=f'Highest Role:',
|
|
||||||
value=f'{ctx.author.top_role}',
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name=f'Bot:',
|
|
||||||
value=f'{ctx.author.bot}',
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name=f'Joined Guild:',
|
|
||||||
value=f'{self.create_date_string(ctx.author.joined_at, ctx.message.created_at)}',
|
|
||||||
inline=False)
|
|
||||||
em.add_field(name=f'Joined Discord:',
|
|
||||||
value=f'{self.create_date_string(ctx.author.created_at, ctx.message.created_at)}',
|
|
||||||
inline=False)
|
|
||||||
em.add_field(name=f'Current Status:',
|
|
||||||
value=f'{ctx.author.status}',
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name=f"Currently{' '+ctx.author.activity.type.name.title() if ctx.author.activity else ''}:",
|
|
||||||
value=f"{ctx.author.activity.name if ctx.author.activity else 'Not doing anything important.'}",
|
|
||||||
inline=True)
|
|
||||||
count = 0
|
|
||||||
async for message in ctx.channel.history(after=(ctx.message.created_at - timedelta(hours=1))):
|
|
||||||
if message.author == ctx.author:
|
|
||||||
count += 1
|
|
||||||
em.add_field(name=f'Activity:',
|
|
||||||
value=f'You have sent {count} '
|
|
||||||
f'{"message" if count == 1 else "messages"} in the last hour to this channel.',
|
|
||||||
inline=False)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def user(self, ctx, member: discord.Member):
|
|
||||||
"""Prints User information.
|
|
||||||
<member> should be in the form @Dusty.P#0001"""
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
title=f'{member.name}#{member.discriminator} ({member.display_name})',
|
|
||||||
description=f'({member.id})',
|
|
||||||
color=embed_color)
|
|
||||||
em.set_thumbnail(url=f'{member.avatar_url}')
|
|
||||||
em.add_field(name=f'Highest Role:',
|
|
||||||
value=f'{member.top_role}',
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name=f'Bot:',
|
|
||||||
value=f'{member.bot}',
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name=f'Joined Guild:',
|
|
||||||
value=f'{self.create_date_string(member.joined_at,ctx.message.created_at)}',
|
|
||||||
inline=False)
|
|
||||||
em.add_field(name=f'Joined Discord:',
|
|
||||||
value=f'{self.create_date_string(member.created_at,ctx.message.created_at)}',
|
|
||||||
inline=False)
|
|
||||||
em.add_field(name=f'Current Status:',
|
|
||||||
value=f'{member.status}',
|
|
||||||
inline=True)
|
|
||||||
em.add_field(name=f"Currently{' '+member.activity.type.name.title() if member.activity else ''}:",
|
|
||||||
value=f"{member.activity.name if member.activity else 'Not doing anything important.'}",
|
|
||||||
inline=True)
|
|
||||||
count = 0
|
|
||||||
async for message in ctx.channel.history(after=(ctx.message.created_at - timedelta(hours=1))):
|
|
||||||
if message.author == member:
|
|
||||||
count += 1
|
|
||||||
em.add_field(name=f'Activity:',
|
|
||||||
value=f'{member.display_name} has sent {count} '
|
|
||||||
f'{"message" if count == 1 else "messages"} in the last hour to this channel.',
|
|
||||||
inline=False)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@commands.command()
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def ping(self, ctx, mode='normal', count: int=2):
|
|
||||||
"""Check the Bot\'s connection to Discord
|
|
||||||
|
|
||||||
For more detailed information set the <mode> as comp and it will test the ping
|
|
||||||
<count> number of times."""
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
title=f'Pong 🏓',
|
|
||||||
color=discord.Colour.green()
|
|
||||||
)
|
|
||||||
msg = await ctx.send(embed=em)
|
|
||||||
time1 = ctx.message.created_at
|
|
||||||
time = (msg.created_at - time1).total_seconds() * 1000
|
|
||||||
em.description = f'Response Time: **{math.ceil(time)}ms**\n' \
|
|
||||||
f'Discord Latency: **{math.ceil(self.bot.latency*1000)}ms**'
|
|
||||||
await msg.edit(embed=em)
|
|
||||||
|
|
||||||
if mode == 'comp':
|
|
||||||
try:
|
|
||||||
count = int(count)
|
|
||||||
except ValueError:
|
|
||||||
await ctx.send('Not a valid count. Must be a whole number.')
|
|
||||||
else:
|
|
||||||
if count > 24:
|
|
||||||
await ctx.send('24 Pings is the max allowed. Setting count to 24.', delete_after=5)
|
|
||||||
count = 24
|
|
||||||
self.bot.ping_times = []
|
|
||||||
times = []
|
|
||||||
for i in range(count):
|
|
||||||
self.bot.ping_times.append({})
|
|
||||||
self.bot.loop.create_task(self.background_ping(ctx, i))
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
self.bot.ping_times[i]['snd'] = await ctx.send('ping_test')
|
|
||||||
now = datetime.utcnow()
|
|
||||||
while 'rec' not in self.bot.ping_times[i]:
|
|
||||||
now = datetime.utcnow()
|
|
||||||
if now.timestamp() > self.bot.ping_times[i]['snd'].created_at.timestamp()+5:
|
|
||||||
break
|
|
||||||
if 'rec' in self.bot.ping_times[i]:
|
|
||||||
time = now - self.bot.ping_times[i]['snd'].created_at
|
|
||||||
time = time.total_seconds()
|
|
||||||
times.append(time)
|
|
||||||
value = f"Message Sent:" \
|
|
||||||
f"{datetime.strftime(self.bot.ping_times[i]['snd'].created_at, '%H:%M:%S.%f')}\n" \
|
|
||||||
f"Response Received: {datetime.strftime(now, '%H:%M:%S.%f')}\n" \
|
|
||||||
f"Total Time: {math.ceil(time * 1000)}ms"
|
|
||||||
await self.bot.ping_times[i]['rec'].delete()
|
|
||||||
em.add_field(name=f'Ping Test {i}', value=value, inline=True)
|
|
||||||
else:
|
|
||||||
em.add_field(name=f'Ping Test {i}', value='Timeout...', inline=True)
|
|
||||||
total_time = 0
|
|
||||||
print(times)
|
|
||||||
for time in times:
|
|
||||||
total_time += time * 1000
|
|
||||||
em.add_field(value=f'Total Time for Comprehensive test: {math.ceil(total_time)}ms',
|
|
||||||
name=f'Average: **{round(total_time/count,1)}ms**',
|
|
||||||
inline=False)
|
|
||||||
await msg.edit(embed=em)
|
|
||||||
|
|
||||||
@commands.group(case_insensitive=True)
|
|
||||||
async def admin(self, ctx):
|
|
||||||
"""Group for Admin help requests"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@admin.command(name='new', aliases=['nr'])
|
|
||||||
@commands.cooldown(1, 30, type=commands.BucketType.user)
|
|
||||||
async def new_admin_request(self, ctx, *, request_msg=None):
|
|
||||||
"""Submit a new request for admin assistance.
|
|
||||||
The admin will be notified when your request is made and it will be added to the request list for this guild.
|
|
||||||
"""
|
|
||||||
if ctx.guild:
|
|
||||||
if request_msg is not None:
|
|
||||||
if len(request_msg) < 1000:
|
|
||||||
await self.bot.db_con.execute('insert into admin_requests (issuing_member_id, guild_orig, '
|
|
||||||
'request_text, request_time) values ($1, $2, $3, $4)',
|
|
||||||
ctx.author.id, ctx.guild.id, request_msg, ctx.message.created_at)
|
|
||||||
channel = await self.bot.db_con.fetchval(f'select admin_chat from guild_config where guild_id = $1',
|
|
||||||
ctx.guild.id)
|
|
||||||
if channel:
|
|
||||||
chan = discord.utils.get(ctx.guild.channels, id=channel)
|
|
||||||
msg = ''
|
|
||||||
roles = await self.bot.db_con.fetchval(f'select admin_roles,rcon_admin_roles from guild_config '
|
|
||||||
f'where guild_id = $1', ctx.guild.id)
|
|
||||||
request_id = await self.bot.db_con.fetchval(f'select id from admin_requests where '
|
|
||||||
f'issuing_member_id = $1 and request_time = $2',
|
|
||||||
ctx.author.id, ctx.message.created_at)
|
|
||||||
admin_roles = json.loads(roles).values()
|
|
||||||
for role in admin_roles:
|
|
||||||
msg = '{0} {1}'.format(msg, discord.utils.get(ctx.guild.roles, id=role).mention)
|
|
||||||
msg += f"New Request ID: {request_id} " \
|
|
||||||
f"{ctx.author.mention} has requested assistance: \n" \
|
|
||||||
f"```{request_msg}``` \n" \
|
|
||||||
f"Requested on: {datetime.strftime(ctx.message.created_at, '%Y-%m-%d at %H:%M:%S')} GMT"
|
|
||||||
await chan.send(msg)
|
|
||||||
await ctx.send('The Admin have received your request.')
|
|
||||||
else:
|
|
||||||
await ctx.send('Request is too long, please keep your message to less than 1000 characters.')
|
|
||||||
else:
|
|
||||||
await ctx.send('Please include a message containing information about your request.')
|
|
||||||
else:
|
|
||||||
await ctx.send('This command must be run from inside a guild.')
|
|
||||||
|
|
||||||
@admin.command(name='list', aliases=['lr'])
|
|
||||||
@commands.cooldown(1, 5, type=commands.BucketType.user)
|
|
||||||
async def list_admin_requests(self, ctx, assigned_to: discord.Member=None):
|
|
||||||
"""List of all active Admin help requests
|
|
||||||
|
|
||||||
If a user runs this command it will return all the requests that they have submitted and are still open.
|
|
||||||
- The [assigned_to] argument is ignored but will still give an error if an incorrect value is entered.
|
|
||||||
|
|
||||||
If an admin runs this command it will return all the open requests for this guild.
|
|
||||||
- If the [assigned_to] argument is included it will instead return all open requests that
|
|
||||||
are assigned to the specified admin.
|
|
||||||
"""
|
|
||||||
em = discord.Embed(style='rich',
|
|
||||||
title=f'Admin Help Requests',
|
|
||||||
color=discord.Colour.green()
|
|
||||||
)
|
|
||||||
if await checks.is_admin(self.bot, ctx) or await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
if assigned_to is None:
|
|
||||||
requests = await self.bot.db_con.fetch(f'select * from admin_requests where guild_orig = $1 '
|
|
||||||
f'and completed_time is null', ctx.guild.id)
|
|
||||||
em.title = f'Admin help requests for {ctx.guild.name}'
|
|
||||||
if requests:
|
|
||||||
for request in requests:
|
|
||||||
member = discord.utils.get(ctx.guild.members, id=request[1])
|
|
||||||
admin = discord.utils.get(ctx.guild.members, id=request[2])
|
|
||||||
title = f"{'Request ID':^12}{'Requested By':^20}{'Assigned to':^20}\n" + \
|
|
||||||
f"{request[0]:^12}{member.display_name if member else 'None':^20}" + \
|
|
||||||
f"{admin.display_name if admin else 'None':^20}"
|
|
||||||
em.add_field(name='',
|
|
||||||
value=f"```{title} \n\n"
|
|
||||||
f"{request[4]}\n\n"
|
|
||||||
f"Requested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT```",
|
|
||||||
inline=False)
|
|
||||||
else:
|
|
||||||
em.add_field(name='There are no pending requests for this guild.', value='', inline=False)
|
|
||||||
else:
|
|
||||||
if await checks.check_admin_role(self.bot, ctx, assigned_to)\
|
|
||||||
or await checks.check_rcon_role(self.bot, ctx, assigned_to):
|
|
||||||
requests = await self.bot.db_con.fetch('select * from admin_requests where assigned_to = $1 '
|
|
||||||
'and guild_orig = $2 and completed_time is null',
|
|
||||||
assigned_to.id, ctx.guild.id)
|
|
||||||
em.title = f'Admin help requests assigned to {assigned_to.display_name} in {ctx.guild.name}'
|
|
||||||
if requests:
|
|
||||||
for request in requests:
|
|
||||||
member = discord.utils.get(ctx.guild.members, id=request[1])
|
|
||||||
em.add_field(name=f"Request ID: {request[0]} Requested By:"
|
|
||||||
f"{member.display_name if member else 'None'}",
|
|
||||||
value=f"{request[4]} \n"
|
|
||||||
f"Requested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT\n"
|
|
||||||
"",
|
|
||||||
inline=False)
|
|
||||||
else:
|
|
||||||
em.add_field(name=f'There are no pending requests for '
|
|
||||||
f'{assigned_to.display_name} on this guild.',
|
|
||||||
value='',
|
|
||||||
inline=False)
|
|
||||||
else:
|
|
||||||
em.title = f'{assigned_to.display_name} is not an admin in this guild.'
|
|
||||||
else:
|
|
||||||
requests = await self.bot.db_con.fetch('select * from admin_requests where issuing_member_id = $1 '
|
|
||||||
'and guild_orig = $2 and completed_time is null',
|
|
||||||
ctx.author.id, ctx.guild.id)
|
|
||||||
em.title = f'Admin help requests for {ctx.author.display_name}'
|
|
||||||
if requests:
|
|
||||||
for request in requests:
|
|
||||||
admin = discord.utils.get(ctx.guild.members, id=request[2])
|
|
||||||
em.add_field(name=f"Request ID: {request[0]}"
|
|
||||||
f"{' Assigned to: ' + admin.display_name if admin else ''}",
|
|
||||||
value=f"{request[4]}\n"
|
|
||||||
f"Requested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT\n"
|
|
||||||
"",
|
|
||||||
inline=False)
|
|
||||||
else:
|
|
||||||
em.add_field(name='You have no pending Admin Help requests.',
|
|
||||||
value='To submit a request please use `admin new <message>`',
|
|
||||||
inline=False)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@admin.command(name='close')
|
|
||||||
async def close_request(self, ctx, *, request_ids=None):
|
|
||||||
"""Allows Admin to close admin help tickets.
|
|
||||||
[request_id] must be a valid integer pointing to an open Request ID
|
|
||||||
"""
|
|
||||||
if await checks.is_admin(self.bot, ctx) or await checks.is_rcon_admin(self.bot, ctx):
|
|
||||||
if request_ids:
|
|
||||||
request_ids = request_ids.replace(' ', '').split(',')
|
|
||||||
for request_id in request_ids:
|
|
||||||
try:
|
|
||||||
request_id = int(request_id)
|
|
||||||
except ValueError:
|
|
||||||
await ctx.send(f'{request_id} is not a valid request id.')
|
|
||||||
else:
|
|
||||||
request = await self.bot.db_con.fetchrow(f'select * from admin_requests where id = $1',
|
|
||||||
request_id)
|
|
||||||
if request:
|
|
||||||
if request[3] == ctx.guild.id:
|
|
||||||
if request[6] is None:
|
|
||||||
await self.bot.db_con.execute('update admin_requests set completed_time = $1 where '
|
|
||||||
'id = $2', ctx.message.created_at, request_id)
|
|
||||||
await ctx.send(f'Request {request_id} by '
|
|
||||||
f'{ctx.guild.get_member(request[1]).display_name}'
|
|
||||||
f' has been marked complete.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'Request {request_id} is already marked complete.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'Request {request_id} is not registered to this guild.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'{request_id} is not a valid request id.')
|
|
||||||
else:
|
|
||||||
await ctx.send('You must include at least one request id to close.')
|
|
||||||
else:
|
|
||||||
await ctx.send(f'You are not authorized to run this command.')
|
|
||||||
|
|
||||||
@commands.command(name='weather', aliases=['wu'])
|
|
||||||
@commands.cooldown(5, 15, type=commands.BucketType.default)
|
|
||||||
async def get_weather(self, ctx, *, location='palmer ak'):
|
|
||||||
"""Gets the weather data for the location given
|
|
||||||
|
|
||||||
If no location is included then it will get the weather for the Bot's home location.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
url = f'http://autocomplete.wunderground.com/aq?query={location}&format=JSON'
|
|
||||||
with async_timeout.timeout(10):
|
|
||||||
async with self.bot.aio_session.get(url) as response:
|
|
||||||
data = await response.json()
|
|
||||||
link = data['RESULTS'][0]['l']
|
|
||||||
url = f'http://api.wunderground.com/api/88e14343b2dd6d8e/geolookup/conditions/{link}.json'
|
|
||||||
with async_timeout.timeout(10):
|
|
||||||
async with self.bot.aio_session.get(url) as response:
|
|
||||||
data = json.loads(await response.text())
|
|
||||||
utils_log.info(data)
|
|
||||||
em = discord.Embed()
|
|
||||||
em.title = f"Weather for {data['current_observation']['display_location']['full']}"
|
|
||||||
em.url = data['current_observation']['forecast_url']
|
|
||||||
em.description = data['current_observation']['observation_time']
|
|
||||||
em.set_thumbnail(url=data['current_observation']['icon_url'])
|
|
||||||
em.set_footer(text='Data provided by wunderground.com',
|
|
||||||
icon_url=data['current_observation']['image']['url'])
|
|
||||||
value_str = f'''```
|
|
||||||
{'Temp:':<20}{data['current_observation']['temperature_string']:<22}
|
|
||||||
{'Feels Like:':<20}{data['current_observation']['feelslike_string']:<22}
|
|
||||||
{'Relative Humidity:':<20}{data['current_observation']['relative_humidity']:<22}
|
|
||||||
{'Wind:':<5}{data['current_observation']['wind_string']:^44}
|
|
||||||
```'''
|
|
||||||
em.add_field(name=f"Current Conditions: {data['current_observation']['weather']}",
|
|
||||||
value=value_str,
|
|
||||||
inline=False)
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
except IndexError:
|
|
||||||
await ctx.send('Can\'t find that location, please try again.')
|
|
||||||
|
|
||||||
@commands.command(name='localtime', aliases=['time', 'lt'])
|
|
||||||
@commands.cooldown(1, 3, type=commands.BucketType.user)
|
|
||||||
async def get_localtime(self, ctx, timezone: str='Anchorage'):
|
|
||||||
"""Shows the current time in the timezone given
|
|
||||||
|
|
||||||
This defaults to the Bot's local timezone of Anchorage Alaska USA if none are given."""
|
|
||||||
|
|
||||||
em = discord.Embed()
|
|
||||||
|
|
||||||
try:
|
|
||||||
tz = pytz.timezone(timezone)
|
|
||||||
localtime = datetime.now(tz=tz)
|
|
||||||
em.title = f'{clock_emojis[(localtime.hour % 12)]} {tz}'
|
|
||||||
em.description = localtime.strftime('%c')
|
|
||||||
em.colour = embed_color
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
|
||||||
for tz in pytz.all_timezones:
|
|
||||||
if timezone.lower() in tz.lower():
|
|
||||||
localtime = datetime.now(tz=pytz.timezone(tz))
|
|
||||||
em.title = f'{clock_emojis[(localtime.hour % 12)]} {tz}'
|
|
||||||
em.description = localtime.strftime('%c')
|
|
||||||
em.colour = embed_color
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
return
|
|
||||||
em.title = 'Unknown Timezone.'
|
|
||||||
em.colour = discord.Colour.red()
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
# noinspection PyUnboundLocalVariable
|
|
||||||
@commands.command(name='gettimein', aliases=['timein', 'gti'])
|
|
||||||
@commands.cooldown(1, 3, type=commands.BucketType.user)
|
|
||||||
async def get_time_in_timezone(self, ctx, timezone: str='US/Eastern', *, time: str=None):
|
|
||||||
"""Convert the time provided to given timezone
|
|
||||||
|
|
||||||
Attempts to process the given time and timezone and convert into the given timezone.
|
|
||||||
Example: g$gti CET Friday June 15 2018 US/Alaska
|
|
||||||
This will be processed into a datetime with US/Alaska set as the timezone and will
|
|
||||||
convert it into CET timezone and return both times."""
|
|
||||||
|
|
||||||
em = discord.Embed()
|
|
||||||
|
|
||||||
if time is None:
|
|
||||||
em.set_footer(text='Time not given... using current UTC time.')
|
|
||||||
in_time = datetime.utcnow()
|
|
||||||
parsed_tz = pytz.timezone('UTC')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
orig_time = copy(time)
|
|
||||||
split_time = time.split()
|
|
||||||
try:
|
|
||||||
parsed_tz = pytz.timezone(replace_tzs.get(split_time[-1].upper()) or split_time[-1])
|
|
||||||
time = utils.replace_text_ignorecase(time, old=split_time[-1], new='')
|
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
|
||||||
for tz in pytz.all_timezones:
|
|
||||||
if split_time[-1].lower() in tz.lower():
|
|
||||||
time = utils.replace_text_ignorecase(time, old=split_time[-1], new='')
|
|
||||||
if tz in replace_tzs:
|
|
||||||
tz = replace_tzs['tz']
|
|
||||||
parsed_tz = pytz.timezone(tz)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
em.set_footer(text='Valid timezone not found in time string. Using UTC...')
|
|
||||||
parsed_tz = pytz.timezone('UTC')
|
|
||||||
if not time.isspace() and not time == '':
|
|
||||||
in_time = parse(time.upper())
|
|
||||||
in_time = parsed_tz.localize(in_time)
|
|
||||||
else:
|
|
||||||
em.set_footer(text='Time not given. Using current time.')
|
|
||||||
in_time = datetime.now(tz=parsed_tz)
|
|
||||||
except ValueError:
|
|
||||||
raise commands.CommandError(f'For some reason I can\'t parse this time string: \n'
|
|
||||||
f'{orig_time} {time} {parsed_tz}\n'
|
|
||||||
f'Examples of valid time strings are in my help documentation.\n'
|
|
||||||
f'Please try again.')
|
|
||||||
try:
|
|
||||||
out_tz = pytz.timezone(timezone)
|
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
|
||||||
for tz in pytz.all_timezones:
|
|
||||||
if timezone.lower() in tz.lower():
|
|
||||||
out_tz = pytz.timezone(tz)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
out_tz = None
|
|
||||||
em.title = 'Unknown Timezone.'
|
|
||||||
em.colour = discord.Colour.red()
|
|
||||||
finally:
|
|
||||||
if out_tz:
|
|
||||||
out_time = in_time.astimezone(out_tz)
|
|
||||||
em.add_field(name=f'{parsed_tz}',
|
|
||||||
value=f'{clock_emojis[(in_time.hour % 12)]} {in_time.strftime("%c")}', inline=False)
|
|
||||||
em.add_field(name=f'{out_tz}',
|
|
||||||
value=f'{clock_emojis[(out_time.hour % 12)]} {out_time.strftime("%c")}', inline=False)
|
|
||||||
em.colour = self.bot.embed_color
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@commands.command(name='purge', aliases=['clean', 'erase'])
|
|
||||||
@commands.cooldown(1, 3, type=commands.BucketType.user)
|
|
||||||
async def purge_messages(self, ctx, number: int=20, member: discord.Member=None):
|
|
||||||
"""Purge messages from the current channel
|
|
||||||
By default this will only purge messages sent by Geeksbot and any messages that appear to
|
|
||||||
have called Geeksbot (aka start with one of the Geeksbot's prefixes for this Guild)
|
|
||||||
If you want to purge messages from a different user you must provide a number and member
|
|
||||||
|
|
||||||
Note: Geeksbot will not find <number> of messages by the given member, it will instead
|
|
||||||
search the last <number> messages in the channel and delete any by the given member"""
|
|
||||||
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config '
|
|
||||||
'where guild_id = $1', ctx.guild.id)
|
|
||||||
|
|
||||||
def is_me(message):
|
|
||||||
nonlocal prefixes
|
|
||||||
if message.author == self.bot.user:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if prefixes:
|
|
||||||
for prefix in prefixes:
|
|
||||||
if message.content.startswith(prefix):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
return message.content.startswith(self.bot.default_prefix)
|
|
||||||
|
|
||||||
def is_member(message):
|
|
||||||
return message.author == member
|
|
||||||
|
|
||||||
def is_author(message):
|
|
||||||
return message.author == ctx.author
|
|
||||||
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
if member:
|
|
||||||
deleted = await ctx.channel.purge(limit=number, check=is_member)
|
|
||||||
if member != ctx.author:
|
|
||||||
await ctx.message.delete()
|
|
||||||
else:
|
|
||||||
deleted = await ctx.channel.purge(limit=number, check=is_me)
|
|
||||||
else:
|
|
||||||
deleted = await ctx.channel.purge(limit=number, check=is_author)
|
|
||||||
em = discord.Embed(title='❌ Purge', colour=discord.Colour.red())
|
|
||||||
em.description = f'Deleted {len(deleted)} messages.'
|
|
||||||
await ctx.send(embed=em, delete_after=5)
|
|
||||||
|
|
||||||
@commands.command(name='purge_all', aliases=['cls', 'clear'])
|
|
||||||
@commands.cooldown(1, 3, type=commands.BucketType.user)
|
|
||||||
async def purge_all(self, ctx, number: int=20, contents: str='all'):
|
|
||||||
"""Purge all messages from the current channel
|
|
||||||
|
|
||||||
Will delete all of the last <number> of messages from the channel
|
|
||||||
If <contents> is not 'all' then only messages containing <contents>
|
|
||||||
will be deleted."""
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
if contents != 'all':
|
|
||||||
deleted = await ctx.channel.purge(limit=number, check=lambda message: message.content == contents)
|
|
||||||
else:
|
|
||||||
deleted = await ctx.channel.purge(limit=number)
|
|
||||||
em = discord.Embed(title='❌ Purge', colour=discord.Colour.red())
|
|
||||||
em.description = f'Deleted {len(deleted)} messages.'
|
|
||||||
if contents != ctx.message.content and contents != 'all':
|
|
||||||
await ctx.message.delete()
|
|
||||||
await ctx.send(embed=em, delete_after=5)
|
|
||||||
|
|
||||||
@commands.command(name='google', aliases=['g', 'search'])
|
|
||||||
async def google_search(self, ctx, *, search):
|
|
||||||
"""WIP Search Google for the given string"""
|
|
||||||
res = self.bot.gcs_service.cse().list(q=search, cx=self.bot.bot_secrets['cx']).execute()
|
|
||||||
results = res['items']
|
|
||||||
pag = utils.Paginator(self.bot, max_line_length=100, embed=True)
|
|
||||||
pag.set_embed_meta(title='Google Search', description=f'Top results for "{search}"', color=self.bot.embed_color)
|
|
||||||
for result in results:
|
|
||||||
pag.add(f'\uFFF6{result["title"]}\n{result["link"]}', keep_intact=True)
|
|
||||||
pag.add(f'{result["snippet"]}')
|
|
||||||
pag.add('\uFFF7\n\uFFF8')
|
|
||||||
msg = await ctx.send('Starting Book')
|
|
||||||
book = utils.Book(pag, (msg, ctx.channel, self.bot, ctx.message))
|
|
||||||
await book.create_book()
|
|
||||||
|
|
||||||
@commands.command(hidden=True, name='sheets')
|
|
||||||
async def google_sheets(self, ctx, member: discord.Member):
|
|
||||||
"""Access Google Sheets and looks for the member"""
|
|
||||||
|
|
||||||
if await checks.is_admin(self.bot, ctx):
|
|
||||||
scope = ['https://spreadsheets.google.com/feeds',
|
|
||||||
'https://www.googleapis.com/auth/drive']
|
|
||||||
credentials = ServiceAccountCredentials.from_json_keyfile_name('config/google_client_secret.json', scope)
|
|
||||||
gc = gspread.authorize(credentials)
|
|
||||||
sh = gc.open_by_key(self.bot.bot_secrets['sheet'])
|
|
||||||
ws = sh.worksheet('Current Whitelist')
|
|
||||||
names = ws.col_values('3')
|
|
||||||
steam = ws.col_values('6')
|
|
||||||
tier = ws.col_values('5')
|
|
||||||
patron = ws.col_values('4')
|
|
||||||
em = discord.Embed()
|
|
||||||
em.title = f'User Data from Whitelist Sheet'
|
|
||||||
em.colour = embed_color
|
|
||||||
for i, name in enumerate(names):
|
|
||||||
if member.name.lower() in name.lower()\
|
|
||||||
or member.display_name.lower() in name.lower()\
|
|
||||||
or name.lower() in member.name.lower()\
|
|
||||||
or name.lower() in member.display_name.lower():
|
|
||||||
em.add_field(name=name,
|
|
||||||
value=f'Steam ID: {steam[i]}\nPatreon Level: {tier[i]}\nPatron of: {patron[i]}')
|
|
||||||
await ctx.send(embed=em)
|
|
||||||
|
|
||||||
@commands.command(name='iss')
|
|
||||||
async def iss_loc(self, ctx):
|
|
||||||
"""Locate the International Space Station
|
|
||||||
|
|
||||||
Gets the location of the ISS and display on a
|
|
||||||
Blue Marble map."""
|
|
||||||
def gen_image(iss_loc):
|
|
||||||
lat = iss_loc['latitude']
|
|
||||||
lon = iss_loc['longitude']
|
|
||||||
plt.figure(figsize=(5, 5))
|
|
||||||
m = Basemap(projection='ortho', resolution=None, lat_0=lat, lon_0=lon)
|
|
||||||
m.bluemarble(scale=0.5)
|
|
||||||
x, y = m(lon, lat)
|
|
||||||
plt.plot(x, y, 'ok', markersize=10, color='red')
|
|
||||||
plt.text(x, y, ' ISS', fontsize=20, color='red')
|
|
||||||
plt.tight_layout()
|
|
||||||
|
|
||||||
img = BytesIO()
|
|
||||||
plt.savefig(img, format='png', transparent=True)
|
|
||||||
img.seek(0)
|
|
||||||
self.bot.loop.create_task(ctx.send('Current ISS Location', file=discord.File(img, 'output.png')))
|
|
||||||
|
|
||||||
async with ctx.typing():
|
|
||||||
async with self.bot.aio_session.get('https://api.wheretheiss.at/v1/satellites/25544') as response:
|
|
||||||
loc = await response.json()
|
|
||||||
await self.bot.loop.run_in_executor(self.bot.tpe, gen_image, loc)
|
|
||||||
|
|
||||||
@commands.command(name='location', aliases=['loc', 'map'])
|
|
||||||
async def map_location(self, ctx, *, location):
|
|
||||||
"""WIP Displays the given location on a map
|
|
||||||
|
|
||||||
Searches for the location provided and plots the Lat Long on a map.
|
|
||||||
Note: This is SLOW!!! Be prepared to wait up to a minute for the result"""
|
|
||||||
|
|
||||||
def draw_map(m, scale=1):
|
|
||||||
# draw a shaded-relief image
|
|
||||||
m.shadedrelief(scale=scale)
|
|
||||||
m.fillcontinents(color="#FFDDCC", lake_color='#DDEEFF')
|
|
||||||
m.drawmapboundary(fill_color="#DDEEFF")
|
|
||||||
m.drawcoastlines(color='gray')
|
|
||||||
m.drawcountries(color='gray')
|
|
||||||
m.drawstates(color='gray')
|
|
||||||
|
|
||||||
# lats and longs are returned as a dictionary
|
|
||||||
lats = m.drawparallels(np.linspace(-90, 90, 30))
|
|
||||||
lons = m.drawmeridians(np.linspace(-180, 180, 90))
|
|
||||||
|
|
||||||
# keys contain the plt.Line2D instances
|
|
||||||
lat_lines = chain(*(tup[1][0] for tup in lats.items()))
|
|
||||||
lon_lines = chain(*(tup[1][0] for tup in lons.items()))
|
|
||||||
all_lines = chain(lat_lines, lon_lines)
|
|
||||||
|
|
||||||
# cycle through these lines and set the desired style
|
|
||||||
for line in all_lines:
|
|
||||||
line.set(linestyle='-', alpha=0.3, color='gray')
|
|
||||||
|
|
||||||
def gen_image(loc):
|
|
||||||
lat = loc['lat']
|
|
||||||
lon = loc['lng']
|
|
||||||
plt.figure(figsize=(4, 4))
|
|
||||||
m = Basemap(projection='lcc', width=2E6, height=2E6, resolution='i', lat_0=lat, lon_0=lon)
|
|
||||||
draw_map(m)
|
|
||||||
x, y = m(lon, lat)
|
|
||||||
plt.plot(x, y, 'ok', markersize=5, color='red')
|
|
||||||
plt.text(x, y, f' {location.title()}', fontsize=12, color='red')
|
|
||||||
plt.tight_layout()
|
|
||||||
|
|
||||||
img = BytesIO()
|
|
||||||
plt.savefig(img, format='png', transparent=True)
|
|
||||||
img.seek(0)
|
|
||||||
self.bot.loop.create_task(ctx.send(file=discord.File(img, f'{location} map.png')))
|
|
||||||
self.bot.loop.create_task(ctx.trigger_typing())
|
|
||||||
|
|
||||||
msg = await ctx.send(f'Checking on location data for {location.title()}')
|
|
||||||
async with ctx.typing():
|
|
||||||
async with self.bot.aio_session.get(
|
|
||||||
f'https://api.opencagedata.com/geocode/v1/json?q={location}&key={self.bot.geo_api}') as result:
|
|
||||||
data = await result.json()
|
|
||||||
if data['total_results'] != 0:
|
|
||||||
location_data = data['results'][0]['geometry']
|
|
||||||
await msg.edit(content=f'Got Location. Please wait, Generating the image can take up to a minute.')
|
|
||||||
async with ctx.typing():
|
|
||||||
await self.bot.loop.run_in_executor(self.bot.tpe, gen_image, location_data)
|
|
||||||
await msg.delete()
|
|
||||||
else:
|
|
||||||
await msg.edit(content=f'I can\'t find any data for that location.\nPlease try again.')
|
|
||||||
|
|
||||||
@commands.command(name='help', aliases=['h'])
|
|
||||||
@commands.cooldown(1, 5, commands.BucketType.user)
|
|
||||||
async def custom_help(self, ctx, *, command: str=None):
|
|
||||||
"""This help message"""
|
|
||||||
pag = utils.Paginator(self.bot, embed=True, max_line_length=48)
|
|
||||||
prefixes = await self.bot.get_custom_prefix(self.bot, ctx.message)
|
|
||||||
if isinstance(prefixes, list):
|
|
||||||
prefixes = ', '.join(prefixes)
|
|
||||||
owner = await self.bot.get_user_info(self.bot.owner_id)
|
|
||||||
if command is None:
|
|
||||||
pag.set_embed_meta(title='Geeksbot Help',
|
|
||||||
description=f'For more information about a command please run\n'
|
|
||||||
f'{prefixes.split(",")[0]}help [group] <command>',
|
|
||||||
thumbnail=f'{ctx.guild.me.avatar_url}')
|
|
||||||
pag.add(f"\uFFF6Welcome to Geeksbot's help command.\n"
|
|
||||||
f"< {self.bot.description} >\n\n"
|
|
||||||
f"Below you will find some basic information about me.\n\n"
|
|
||||||
f"Version: <{self.bot.__version__}>\n\n"
|
|
||||||
f"Owner: <Dusty.P>\n"
|
|
||||||
f"> Username: {owner.name}#{owner.discriminator}\n"
|
|
||||||
f"> ID: {owner.id}\n\n"
|
|
||||||
f"Prefixes available for this guild:\n"
|
|
||||||
f"> {prefixes}\n\uFFF7\n\uFFF8")
|
|
||||||
for cog in sorted(self.bot.cogs):
|
|
||||||
for command in sorted(self.bot.get_cog_commands(cog), key=lambda x: x.name):
|
|
||||||
if not command.hidden:
|
|
||||||
pag.add(f'\uFFF6{command.name}')
|
|
||||||
pag.add(f'> {command.short_doc}', truncate=True)
|
|
||||||
try:
|
|
||||||
for com in sorted(command.commands, key=lambda x: x.name):
|
|
||||||
if not com.hidden:
|
|
||||||
pag.add(f'# {com.name}')
|
|
||||||
pag.add(f'> {com.short_doc}', truncate=True)
|
|
||||||
except AttributeError as e:
|
|
||||||
pass
|
|
||||||
pag.add('\uFFF7')
|
|
||||||
pag.add('\uFFF8')
|
|
||||||
else:
|
|
||||||
pag.set_embed_meta(title='Geeksbot Help',
|
|
||||||
thumbnail=f'{ctx.guild.me.avatar_url}')
|
|
||||||
command = command.split(maxsplit=1)
|
|
||||||
if command[0] in self.bot.all_commands:
|
|
||||||
if len(command) > 1 and self.bot.all_commands[command[0]].group:
|
|
||||||
command = self.bot.all_commands[command[0]].all_commands.get(command[1], None)
|
|
||||||
else:
|
|
||||||
command = self.bot.all_commands[command[0]]
|
|
||||||
else:
|
|
||||||
command = None
|
|
||||||
|
|
||||||
if command and not command.hidden:
|
|
||||||
pag.add(f'\uFFF6{command.name}')
|
|
||||||
pag.add(f'Usage: {prefixes.split()[0]}{command.signature}\n')
|
|
||||||
pag.add(f'\uFFF0\n{command.help}')
|
|
||||||
else:
|
|
||||||
pag.add('\uFFF6There is no command by that name.\n>')
|
|
||||||
|
|
||||||
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
|
|
||||||
await book.create_book()
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
bot.add_cog(Utils(bot))
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import discord
|
|
||||||
import json
|
|
||||||
from src.imports import utils
|
|
||||||
|
|
||||||
owner_id = 351794468870946827
|
|
||||||
|
|
||||||
|
|
||||||
async def check_admin_role(bot, ctx, member):
|
|
||||||
admin_roles = json.loads(await bot.db_con.fetchval(f"select admin_roles from guild_config where guild_id = $1",
|
|
||||||
ctx.guild.id))
|
|
||||||
for role in admin_roles:
|
|
||||||
if discord.utils.get(ctx.guild.roles, id=admin_roles[role]) in member.roles:
|
|
||||||
return True
|
|
||||||
return member.id == ctx.guild.owner.id or member.id == owner_id
|
|
||||||
|
|
||||||
|
|
||||||
async def check_rcon_role(bot, ctx, member):
|
|
||||||
rcon_admin_roles = json.loads(await bot.db_con.fetchval("select rcon_admin_roles from guild_config "
|
|
||||||
"where guild_id = $1", ctx.guild.id))
|
|
||||||
for role in rcon_admin_roles:
|
|
||||||
if discord.utils.get(ctx.guild.roles, id=rcon_admin_roles[role]) in member.roles:
|
|
||||||
return True
|
|
||||||
return member.id == ctx.guild.owner.id or member.id == owner_id
|
|
||||||
|
|
||||||
|
|
||||||
async def is_admin(bot, ctx):
|
|
||||||
admin_roles = json.loads(await bot.db_con.fetchval("select admin_roles from guild_config where guild_id = $1",
|
|
||||||
ctx.guild.id))
|
|
||||||
for role in admin_roles:
|
|
||||||
if discord.utils.get(ctx.guild.roles, id=admin_roles[role]) in ctx.message.author.roles:
|
|
||||||
return True
|
|
||||||
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
|
|
||||||
|
|
||||||
|
|
||||||
async def is_guild_owner(ctx):
|
|
||||||
if ctx.guild:
|
|
||||||
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def is_rcon_admin(bot, ctx):
|
|
||||||
rcon_admin_roles = json.loads(await bot.db_con.fetchval("select rcon_admin_roles from guild_config "
|
|
||||||
"where guild_id = $1", ctx.guild.id))
|
|
||||||
for role in rcon_admin_roles:
|
|
||||||
if discord.utils.get(ctx.guild.roles, id=rcon_admin_roles[role]) in ctx.message.author.roles:
|
|
||||||
return True
|
|
||||||
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
|
|
||||||
|
|
||||||
|
|
||||||
def is_restricted_chan(ctx):
|
|
||||||
if ctx.guild:
|
|
||||||
if ctx.channel.overwrites_for(ctx.guild.default_role).read_messages is False:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def is_spam(bot, ctx):
|
|
||||||
max_rep = 5
|
|
||||||
rep_time = 20
|
|
||||||
spam_rep = 5
|
|
||||||
spam_time = 3
|
|
||||||
spam_check = 0
|
|
||||||
msg_check = 0
|
|
||||||
for msg in bot.recent_msgs[ctx.guild.id]:
|
|
||||||
if msg['author'] == ctx.author:
|
|
||||||
if msg['time'] > ctx.message.created_at.timestamp() - spam_time:
|
|
||||||
spam_check += 1
|
|
||||||
if msg['content'].lower() == ctx.content.lower() \
|
|
||||||
and msg['time'] > ctx.message.created_at.timestamp() - rep_time:
|
|
||||||
msg_check += 1
|
|
||||||
if spam_check == spam_rep - 1 or msg_check == max_rep - 1:
|
|
||||||
await utils.mute(bot, ctx, admin=1, member=ctx.author.id)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,449 +0,0 @@
|
|||||||
from io import StringIO
|
|
||||||
import sys
|
|
||||||
import asyncio
|
|
||||||
import discord
|
|
||||||
from discord.ext.commands.formatter import Paginator as DannyPag
|
|
||||||
from src.imports import checks
|
|
||||||
import re
|
|
||||||
import typing
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
class Capturing(list):
|
|
||||||
def __enter__(self):
|
|
||||||
self._stdout = sys.stdout
|
|
||||||
sys.stdout = self._stringio = StringIO()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
self.extend(self._stringio.getvalue().splitlines())
|
|
||||||
del self._stringio # free up some memory
|
|
||||||
sys.stdout = self._stdout
|
|
||||||
|
|
||||||
|
|
||||||
async def mute(bot, ctx, admin=0, member_id=None):
|
|
||||||
mute_role = bot.db_con.fetchval(f'select muted_role from guild_config where guild_id = $1', ctx.guild.id)
|
|
||||||
if mute_role:
|
|
||||||
if admin or await checks.is_admin(bot, ctx):
|
|
||||||
if ctx.guild.me.guild_permissions.manage_roles:
|
|
||||||
if member_id:
|
|
||||||
ctx.guild.get_member(member_id).edit(roles=[discord.utils.get(ctx.guild.roles, id=mute_role)])
|
|
||||||
|
|
||||||
|
|
||||||
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
|
|
||||||
# noinspection PyShadowingNames
|
|
||||||
def rec_loop(item, key, out, level):
|
|
||||||
quote = '"'
|
|
||||||
if type(item) == list:
|
|
||||||
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
|
|
||||||
new_level = level + 1
|
|
||||||
out = to_list_of_str(item, out, new_level, 1)
|
|
||||||
out.append(f'{" "*level}]')
|
|
||||||
elif type(item) == dict:
|
|
||||||
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
|
|
||||||
new_level = level + 1
|
|
||||||
out = to_list_of_str(item, out, new_level, 1)
|
|
||||||
out.append(f'{" "*level}}}')
|
|
||||||
else:
|
|
||||||
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
|
|
||||||
|
|
||||||
if type(items) == list:
|
|
||||||
if not recurse:
|
|
||||||
out = list()
|
|
||||||
out.append('[')
|
|
||||||
for item in items:
|
|
||||||
rec_loop(item, None, out, level)
|
|
||||||
if not recurse:
|
|
||||||
out.append(']')
|
|
||||||
elif type(items) == dict:
|
|
||||||
if not recurse:
|
|
||||||
out = list()
|
|
||||||
out.append('{')
|
|
||||||
for key in items:
|
|
||||||
rec_loop(items[key], key, out, level)
|
|
||||||
if not recurse:
|
|
||||||
out.append('}')
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def format_output(text):
|
|
||||||
if type(text) == list:
|
|
||||||
text = to_list_of_str(text)
|
|
||||||
elif type(text) == dict:
|
|
||||||
text = to_list_of_str(text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def replace_text_ignorecase(in_str: str, old: str, new: str='') -> str:
|
|
||||||
re_replace = re.compile(re.escape(old), re.IGNORECASE)
|
|
||||||
return re_replace.sub(f'{new}', in_str)
|
|
||||||
|
|
||||||
|
|
||||||
async def run_command(args):
|
|
||||||
# Create subprocess
|
|
||||||
process = await asyncio.create_subprocess_shell(
|
|
||||||
args,
|
|
||||||
# stdout must a pipe to be accessible as process.stdout
|
|
||||||
stdout=asyncio.subprocess.PIPE)
|
|
||||||
# Wait for the subprocess to finish
|
|
||||||
stdout, stderr = await process.communicate()
|
|
||||||
# Return stdout
|
|
||||||
return stdout.decode().strip()
|
|
||||||
|
|
||||||
|
|
||||||
class Paginator:
|
|
||||||
def __init__(self,
|
|
||||||
bot: discord.ext.commands.Bot,
|
|
||||||
*,
|
|
||||||
max_chars: int=1970,
|
|
||||||
max_lines: int=20,
|
|
||||||
prefix: str='```md',
|
|
||||||
suffix: str='```',
|
|
||||||
page_break: str='\uFFF8',
|
|
||||||
field_break: str='\uFFF7',
|
|
||||||
field_name_char: str='\uFFF6',
|
|
||||||
inline_char: str='\uFFF5',
|
|
||||||
max_line_length: int=100,
|
|
||||||
embed=False):
|
|
||||||
_max_len = 6000 if embed else 1980
|
|
||||||
assert 0 < max_lines <= max_chars
|
|
||||||
assert 0 < max_line_length < 120
|
|
||||||
|
|
||||||
self._parts = list()
|
|
||||||
self._prefix = prefix
|
|
||||||
self._suffix = suffix
|
|
||||||
self._max_chars = max_chars if max_chars + len(prefix) + len(suffix) + 2 <= _max_len \
|
|
||||||
else _max_len - len(prefix) - len(suffix) - 2
|
|
||||||
self._max_lines = max_lines - (prefix + suffix).count('\n') + 1
|
|
||||||
self._page_break = page_break
|
|
||||||
self._max_line_length = max_line_length
|
|
||||||
self._pages = list()
|
|
||||||
self._max_field_chars = 1014
|
|
||||||
self._max_field_name = 256
|
|
||||||
self._max_description = 2048
|
|
||||||
self._embed = embed
|
|
||||||
self._field_break = field_break
|
|
||||||
self._field_name_char = field_name_char
|
|
||||||
self._inline_char = inline_char
|
|
||||||
self._embed_title = ''
|
|
||||||
self._embed_description = ''
|
|
||||||
self._embed_color = None
|
|
||||||
self._embed_thumbnail = None
|
|
||||||
self._embed_url = None
|
|
||||||
self._bot = bot
|
|
||||||
|
|
||||||
def set_embed_meta(self, title: str='\uFFF0',
|
|
||||||
description: str='\uFFF0',
|
|
||||||
color: discord.Colour=None,
|
|
||||||
thumbnail: str=None,
|
|
||||||
url: str=None):
|
|
||||||
if len(title) <= self._max_field_name:
|
|
||||||
self._embed_title = title
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Provided Title is too long')
|
|
||||||
if len(description) <= self._max_description:
|
|
||||||
self._embed_description = description
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Provided Description is too long')
|
|
||||||
self._embed_color = color
|
|
||||||
self._embed_thumbnail = thumbnail
|
|
||||||
self._embed_url = url
|
|
||||||
|
|
||||||
def pages(self) -> typing.List[str]:
|
|
||||||
_pages = list()
|
|
||||||
_fields = list()
|
|
||||||
_page = ''
|
|
||||||
_lines = 0
|
|
||||||
_field_name = ''
|
|
||||||
_field_value = ''
|
|
||||||
_inline = False
|
|
||||||
|
|
||||||
def open_page():
|
|
||||||
nonlocal _page, _lines, _fields
|
|
||||||
if not self._embed:
|
|
||||||
_page = self._prefix
|
|
||||||
_lines = 0
|
|
||||||
else:
|
|
||||||
_fields = list()
|
|
||||||
|
|
||||||
def close_page():
|
|
||||||
nonlocal _page, _lines, _fields
|
|
||||||
if not self._embed:
|
|
||||||
_page += self._suffix
|
|
||||||
_pages.append(_page)
|
|
||||||
else:
|
|
||||||
if _fields:
|
|
||||||
_pages.append(_fields)
|
|
||||||
open_page()
|
|
||||||
|
|
||||||
open_page()
|
|
||||||
|
|
||||||
if not self._embed:
|
|
||||||
for part in [str(p) for p in self._parts]:
|
|
||||||
if part == self._page_break:
|
|
||||||
close_page()
|
|
||||||
|
|
||||||
new_chars = len(_page) + len(part)
|
|
||||||
|
|
||||||
if new_chars > self._max_chars:
|
|
||||||
close_page()
|
|
||||||
elif (_lines + (part.count('\n') + 1 or 1)) > self._max_lines:
|
|
||||||
close_page()
|
|
||||||
|
|
||||||
_lines += (part.count('\n') + 1 or 1)
|
|
||||||
_page += '\n' + part
|
|
||||||
else:
|
|
||||||
def open_field(name: str):
|
|
||||||
nonlocal _field_value, _field_name
|
|
||||||
_field_name = name
|
|
||||||
_field_value = self._prefix
|
|
||||||
|
|
||||||
def close_field(next_name: str=None):
|
|
||||||
nonlocal _field_name, _field_value, _fields
|
|
||||||
_field_value += self._suffix
|
|
||||||
if _field_value != self._prefix + self._suffix:
|
|
||||||
_fields.append({'name': _field_name, 'value': _field_value, 'inline': _inline})
|
|
||||||
if next_name:
|
|
||||||
open_field(next_name)
|
|
||||||
|
|
||||||
open_field('\uFFF0')
|
|
||||||
|
|
||||||
for part in [str(p) for p in self._parts]:
|
|
||||||
if part == self._page_break:
|
|
||||||
close_page()
|
|
||||||
continue
|
|
||||||
elif part == self._field_break:
|
|
||||||
if len(_fields) + 1 < 25:
|
|
||||||
close_field(next_name='\uFFF0')
|
|
||||||
else:
|
|
||||||
close_field()
|
|
||||||
close_page()
|
|
||||||
continue
|
|
||||||
|
|
||||||
if part.startswith(self._field_name_char):
|
|
||||||
part = part.replace(self._field_name_char, '')
|
|
||||||
if part.startswith(self._inline_char):
|
|
||||||
_inline = True
|
|
||||||
part = part.replace(self._inline_char, '')
|
|
||||||
else:
|
|
||||||
_inline = False
|
|
||||||
if _field_value and _field_value != self._prefix:
|
|
||||||
close_field(part)
|
|
||||||
else:
|
|
||||||
_field_name = part
|
|
||||||
continue
|
|
||||||
|
|
||||||
_field_value += '\n' + part
|
|
||||||
|
|
||||||
close_field()
|
|
||||||
|
|
||||||
close_page()
|
|
||||||
self._pages = _pages
|
|
||||||
return _pages
|
|
||||||
|
|
||||||
def process_pages(self) -> typing.List[str]:
|
|
||||||
_pages = self._pages or self.pages()
|
|
||||||
_len_pages = len(_pages)
|
|
||||||
_len_page_str = len(f'{_len_pages}/{_len_pages}')
|
|
||||||
if not self._embed:
|
|
||||||
for i, page in enumerate(_pages):
|
|
||||||
if len(page) + _len_page_str <= 2000:
|
|
||||||
_pages[i] = f'{i + 1}/{_len_pages}\n{page}'
|
|
||||||
else:
|
|
||||||
for i, page in enumerate(_pages):
|
|
||||||
em = discord.Embed(title=self._embed_title,
|
|
||||||
description=self._embed_description,
|
|
||||||
color=self._bot.embed_color,
|
|
||||||
)
|
|
||||||
if self._embed_thumbnail:
|
|
||||||
em.set_thumbnail(url=self._embed_thumbnail)
|
|
||||||
if self._embed_url:
|
|
||||||
em.url = self._embed_url
|
|
||||||
if self._embed_color:
|
|
||||||
em.color = self._embed_color
|
|
||||||
em.set_footer(text=f'{i + 1}/{_len_pages}')
|
|
||||||
for field in page:
|
|
||||||
em.add_field(name=field['name'], value=field['value'], inline=field['inline'])
|
|
||||||
_pages[i] = em
|
|
||||||
return _pages
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return sum(len(p) for p in self._parts)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
# noinspection PyProtectedMember
|
|
||||||
return self.__class__ == other.__class__ and self._parts == other._parts
|
|
||||||
|
|
||||||
def add_page_break(self, *, to_beginning: bool=False) -> None:
|
|
||||||
self.add(self._page_break, to_beginning=to_beginning)
|
|
||||||
|
|
||||||
def add(self, item: typing.Any, *, to_beginning: bool=False, keep_intact: bool=False, truncate=False) -> None:
|
|
||||||
item = str(item)
|
|
||||||
i = 0
|
|
||||||
if not keep_intact and not item == self._page_break:
|
|
||||||
item_parts = item.strip('\n').split('\n')
|
|
||||||
for part in item_parts:
|
|
||||||
if len(part) > self._max_line_length:
|
|
||||||
if not truncate:
|
|
||||||
length = 0
|
|
||||||
out_str = ''
|
|
||||||
|
|
||||||
def close_line(line):
|
|
||||||
nonlocal i, out_str, length
|
|
||||||
self._parts.insert(i, out_str) if to_beginning else self._parts.append(out_str)
|
|
||||||
i += 1
|
|
||||||
out_str = line + ' '
|
|
||||||
length = len(out_str)
|
|
||||||
|
|
||||||
bits = part.split(' ')
|
|
||||||
for bit in bits:
|
|
||||||
next_len = length + len(bit) + 1
|
|
||||||
if next_len <= self._max_line_length:
|
|
||||||
out_str += bit + ' '
|
|
||||||
length = next_len
|
|
||||||
elif len(bit) > self._max_line_length:
|
|
||||||
if out_str:
|
|
||||||
close_line(line='')
|
|
||||||
for out_str in [bit[i:i + self._max_line_length]
|
|
||||||
for i in range(0, len(bit), self._max_line_length)]:
|
|
||||||
close_line('')
|
|
||||||
else:
|
|
||||||
close_line(bit)
|
|
||||||
close_line('')
|
|
||||||
else:
|
|
||||||
line = f'{part:.{self._max_line_length-3}}...'
|
|
||||||
self._parts.insert(i, line) if to_beginning else self._parts.append(line)
|
|
||||||
else:
|
|
||||||
self._parts.insert(i, part) if to_beginning else self._parts.append(part)
|
|
||||||
i += 1
|
|
||||||
elif keep_intact and not item == self._page_break:
|
|
||||||
if len(item) >= self._max_chars or item.count('\n') > self._max_lines:
|
|
||||||
raise RuntimeError('{item} is too long to keep on a single page and is marked to keep intact.')
|
|
||||||
if to_beginning:
|
|
||||||
self._parts.insert(0, item)
|
|
||||||
else:
|
|
||||||
self._parts.append(item)
|
|
||||||
else:
|
|
||||||
if to_beginning:
|
|
||||||
self._parts.insert(0, item)
|
|
||||||
else:
|
|
||||||
self._parts.append(item)
|
|
||||||
|
|
||||||
|
|
||||||
class Book:
|
|
||||||
def __init__(self, pag: Paginator, ctx: typing.Tuple[typing.Optional[discord.Message],
|
|
||||||
discord.TextChannel,
|
|
||||||
discord.ext.commands.Bot,
|
|
||||||
discord.Message]) -> None:
|
|
||||||
self._pages = pag.process_pages()
|
|
||||||
self._len_pages = len(self._pages)
|
|
||||||
self._current_page = 0
|
|
||||||
self._message, self._channel, self._bot, self._calling_message = ctx
|
|
||||||
self._locked = True
|
|
||||||
if pag == Paginator(self._bot):
|
|
||||||
raise RuntimeError('Cannot create a book out of an empty Paginator.')
|
|
||||||
|
|
||||||
def advance_page(self) -> None:
|
|
||||||
self._current_page += 1
|
|
||||||
if self._current_page >= self._len_pages:
|
|
||||||
self._current_page = 0
|
|
||||||
|
|
||||||
def reverse_page(self) -> None:
|
|
||||||
self._current_page += -1
|
|
||||||
if self._current_page < 0:
|
|
||||||
self._current_page = self._len_pages - 1
|
|
||||||
|
|
||||||
async def display_page(self) -> None:
|
|
||||||
if isinstance(self._pages[self._current_page], discord.Embed):
|
|
||||||
if self._message:
|
|
||||||
await self._message.edit(content=None, embed=self._pages[self._current_page])
|
|
||||||
else:
|
|
||||||
self._message = await self._channel.send(embed=self._pages[self._current_page])
|
|
||||||
else:
|
|
||||||
if self._message:
|
|
||||||
await self._message.edit(content=self._pages[self._current_page], embed=None)
|
|
||||||
else:
|
|
||||||
self._message = await self._channel.send(self._pages[self._current_page])
|
|
||||||
|
|
||||||
async def create_book(self) -> None:
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
async def reaction_checker():
|
|
||||||
# noinspection PyShadowingNames
|
|
||||||
def check(reaction, user):
|
|
||||||
if self._locked:
|
|
||||||
return str(reaction.emoji) in self._bot.book_emojis.values() \
|
|
||||||
and user == self._calling_message.author \
|
|
||||||
and reaction.message.id == self._message.id
|
|
||||||
else:
|
|
||||||
return str(reaction.emoji) in self._bot.book_emojis.values() \
|
|
||||||
and reaction.message.id == self._message.id
|
|
||||||
|
|
||||||
await self.display_page()
|
|
||||||
|
|
||||||
if len(self._pages) > 1:
|
|
||||||
for emoji in self._bot.book_emojis.values():
|
|
||||||
try:
|
|
||||||
await self._message.add_reaction(emoji)
|
|
||||||
except (discord.Forbidden, KeyError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
await self._message.add_reaction(self._bot.book_emojis['unlock'])
|
|
||||||
await self._message.add_reaction(self._bot.book_emojis['close'])
|
|
||||||
except (discord.Forbidden, KeyError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
reaction, user = await self._bot.wait_for('reaction_add', timeout=60, check=check)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
try:
|
|
||||||
await self._message.clear_reactions()
|
|
||||||
except discord.Forbidden:
|
|
||||||
pass
|
|
||||||
raise asyncio.CancelledError
|
|
||||||
else:
|
|
||||||
await self._message.remove_reaction(reaction, user)
|
|
||||||
if str(reaction.emoji) == self._bot.book_emojis['close']:
|
|
||||||
await self._calling_message.delete()
|
|
||||||
await self._message.delete()
|
|
||||||
raise asyncio.CancelledError
|
|
||||||
elif str(reaction.emoji) == self._bot.book_emojis['forward']:
|
|
||||||
self.advance_page()
|
|
||||||
elif str(reaction.emoji) == self._bot.book_emojis['back']:
|
|
||||||
self.reverse_page()
|
|
||||||
elif str(reaction.emoji) == self._bot.book_emojis['end']:
|
|
||||||
self._current_page = self._len_pages - 1
|
|
||||||
elif str(reaction.emoji) == self._bot.book_emojis['start']:
|
|
||||||
self._current_page = 0
|
|
||||||
elif str(reaction.emoji) == self._bot.book_emojis['hash']:
|
|
||||||
m = await self._channel.send(f'Please enter a number in range 1 to {self._len_pages}')
|
|
||||||
|
|
||||||
def num_check(message):
|
|
||||||
if self._locked:
|
|
||||||
return message.content.isdigit() \
|
|
||||||
and 0 < int(message.content) <= self._len_pages \
|
|
||||||
and message.author == self._calling_message.author
|
|
||||||
else:
|
|
||||||
return message.content.isdigit() \
|
|
||||||
and 0 < int(message.content) <= self._len_pages
|
|
||||||
|
|
||||||
try:
|
|
||||||
msg = await self._bot.wait_for('message', timeout=30, check=num_check)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
await m.edit(content='Message Timed out.')
|
|
||||||
else:
|
|
||||||
self._current_page = int(msg.content) - 1
|
|
||||||
try:
|
|
||||||
await m.delete()
|
|
||||||
await msg.delete()
|
|
||||||
except discord.Forbidden:
|
|
||||||
pass
|
|
||||||
elif str(reaction.emoji) == self._bot.book_emojis['unlock']:
|
|
||||||
self._locked = False
|
|
||||||
await self._message.remove_reaction(reaction, self._channel.guild.me)
|
|
||||||
continue
|
|
||||||
await self.display_page()
|
|
||||||
|
|
||||||
self._bot.loop.create_task(reaction_checker())
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import asyncpg
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseConnection:
|
|
||||||
def __init__(self, host: str='localhost', port: int=5432, database: str='', user: str='', password: str=''):
|
|
||||||
if user == '' or password == '' or database == '':
|
|
||||||
raise RuntimeError('Username or Password are blank')
|
|
||||||
self.kwargs = {'host': host, 'port': port, 'database': database, 'user': user, 'password': password}
|
|
||||||
self._conn = None
|
|
||||||
asyncio.get_event_loop().run_until_complete(self.acquire())
|
|
||||||
self.fetchval = self._conn.fetchval
|
|
||||||
self.execute = self._conn.execute
|
|
||||||
self.fetch = self._conn.fetch
|
|
||||||
self.fetchrow = self._conn.fetchrow
|
|
||||||
|
|
||||||
async def acquire(self):
|
|
||||||
if not self._conn:
|
|
||||||
self._conn = await asyncpg.create_pool(**self.kwargs)
|
|
||||||
|
|
||||||
async def close(self):
|
|
||||||
await self._conn.close()
|
|
||||||
self._conn = None
|
|
||||||
Reference in New Issue
Block a user