Compare commits

..
49 Commits
Author SHA1 Message Date
Dusty.P c1a19ce2c8 Create LICENSE 2018-05-10 12:47:21 -08:00
Dustin Pianalto 8065d4d7d6 Merge remote-tracking branch 'origin/master' 2018-04-18 14:42:31 -08:00
Dustin Pianalto 7b94487ea0 Fixed layout on admin request list 2018-04-18 14:42:04 -08:00
Dustin Pianalto 6e5aaa0266 Fixed layout on admin request list 2018-04-18 14:36:09 -08:00
Dusty.P e5f8d513f1 Cleanup role command 2018-04-15 00:36:14 -08:00
Dusty.P 55b395b271 Cleanup role command 2018-04-15 00:21:13 -08:00
Dusty.P 927275d5b3 Move Google keys to bot_secrets.json 2018-04-15 00:08:56 -08:00
Dusty.P 5bc5eaefd1 Move Google keys to bot_secrets.json 2018-04-15 00:02:55 -08:00
Dusty.P 65b3bb39e3 Fix syntax errors 2018-04-14 23:49:13 -08:00
Dusty.P 2da878f3d6 Refactor to conform to PEP 8 styling 2018-04-14 23:42:40 -08:00
Dusty.P 55209e711e Fixed on_guild_join 2018-04-14 11:27:52 -08:00
Dusty.P e53694155b Fixed syntax error 2018-04-14 11:05:54 -08:00
Dusty.P 083640a173 Fixed syntax error 2018-04-14 11:04:15 -08:00
Dusty.P 17efb1fcea Fixed syntax error 2018-04-14 11:02:20 -08:00
Dusty.P 72275a1baf Added git to auto load list 2018-04-14 11:01:27 -08:00
Dusty.P 7209e38cc8 Fixed syntax error 2018-04-14 11:00:47 -08:00
Dusty.P 33df91ab8f Updated all database queries to not use string formatting. 2018-04-14 10:57:23 -08:00
Dusty.P 69fab0e7a3 Updated Email Checker 2018-04-14 03:22:58 -08:00
Dusty.P bb39f1f1b4 Changed Email checker 2018-04-14 03:18:49 -08:00
Dusty.P a3e59c75a3 Fixed error in run_command 2018-04-14 03:12:51 -08:00
Dusty.P c6c4b1eb91 Changed run_command to use create_subprocess_shell 2018-04-14 03:11:13 -08:00
Dusty.P a87f10d985 Removed Email from stat message 2018-04-14 03:00:29 -08:00
Dusty.P 98f09f73a4 Made pull message more concise. 2018-04-14 02:57:37 -08:00
Dusty.P 3f1a55d1fe Fixed errors in pull command 2018-04-14 02:54:15 -08:00
Dusty.P f369f3c54e Update pull command to be more robust 2018-04-14 02:52:08 -08:00
Dusty.P 95006e2bad remove __pycache__ dirs 2018-04-14 02:28:11 -08:00
Dusty.P 54a83838ed Ignore __pycache__/ 2018-04-14 02:21:27 -08:00
Dusty.P eaf7ce2dce Ignore */__pycache__/* 2018-04-14 02:17:52 -08:00
Dusty.P 160b46d890 Ignore *__pycache__*, logs/* 2018-04-14 02:16:45 -08:00
Dusty.P 4531cccb04 Ignore __*__ 2018-04-14 02:15:03 -08:00
Dusty.P 6ee4651f37 Changed run_command to pass multiple strings 2018-04-14 02:12:44 -08:00
Dusty.P 7fab42ab1b Change run_command to str 2018-04-14 02:11:13 -08:00
Dusty.P cfdf047aeb Import asyncio 2018-04-14 02:09:48 -08:00
Dusty.P 4dd76a12c1 Import logging 2018-04-14 02:08:44 -08:00
Dusty.P ae50ff6fcb Fixed error in imports 2018-04-14 02:07:42 -08:00
Dusty.P 722f27a678 Added git pull and git status (beta) 2018-04-14 02:06:28 -08:00
Dusty.P 32fe245f24 Added maxlen to paginate 2018-04-14 02:02:05 -08:00
Dusty.P 6d5698840a Changed get_prefix to get_custom_prefix 2018-04-14 01:41:45 -08:00
Dusty.P 5f212e3a3c Fixed json typo 2018-04-14 01:37:39 -08:00
Dusty.P 6a30e7b1e7 Move Google Search API Key to bot_secrets.json 2018-04-14 01:24:32 -08:00
Dusty.P 2799fa7e28 Delete sftp-config.json 2018-04-14 01:23:10 -08:00
Dusty.P 63434f7849 Added Poop emoji to emojis dict 2018-04-14 01:12:21 -08:00
Dusty.P de9119662f Changed command_prefix function to get_prefix 2018-04-14 00:45:57 -08:00
Dusty.P 9beee7497d Merge branch 'master' of github.com:dustinpianalto/Geeksbot 2018-04-14 00:43:03 -08:00
Dusty.P 077cb36382 Changed command_prefix to use sql parameterisation 2018-04-13 23:42:34 -08:00
dustinpianalto 98fe3216ad Delete google_client_secret.json 2018-04-13 23:15:35 -08:00
Dusty.P 84caaee9d5 Ignore Google Secrets file 2018-04-13 23:14:46 -08:00
Dusty.P 465e22163f Initial Upload of Geeksbot Files 2018-04-13 23:10:36 -08:00
Dusty.P eb4110e1e6 First Commit 2018-04-13 22:09:26 -08:00
56 changed files with 3285 additions and 1593 deletions
Binary file not shown.
-60
View File
@@ -1,60 +0,0 @@
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: Setup Golang 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Install go-bindata and build migrations
run: |
go get -u github.com/go-bindata/go-bindata/...
go-bindata -pkg migrations -o $GITHUB_WORKSPACE/internal/database/migrations/bindata.go $GITHUB_WORKSPACE/internal/database/migrations/
- name: Build container image
env:
IMAGE_TAG: ${{ steps.get_version.outputs.version-without-v }}
run: docker build -t registry.digitalocean.com/djpianalto/geeksbot:$IMAGE_TAG .
- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
- name: Login to DigitalOcean Container Registry with short-lived credentials
run: doctl registry login --expiry-seconds 600
- name: Push image to DigitalOcean Container Registry
run: docker push registry.digitalocean.com/djpianalto/geeksbot
- name: Update deployment file
run: TAG=${{ steps.get_version.outputs.version-without-v }} && sed -i 's|<IMAGE>|registry.digitalocean.com/djpianalto/geeksbot:'${TAG}'|' $GITHUB_WORKSPACE/deployment.yml
- name: Save DigitalOcean kubeconfig with short-lived credentials
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 discord-bots
- name: Deploy to DigitalOcean Kubernetes
run: kubectl apply -f $GITHUB_WORKSPACE/deployment.yml
- name: Verify deployment
run: kubectl rollout status deployment/geeksbot
+4 -2
View File
@@ -1,2 +1,4 @@
.env
.idea
bot_secrets.json
google_client_secret.json
__pycache__/
logs/*
+14
View File
@@ -0,0 +1,14 @@
<?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" />
<orderEntry type="library" name="R User Library" level="project" />
<orderEntry type="library" name="R Skeletons" level="application" />
</component>
<component name="TestRunnerService">
<option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="Dustin.Pianalto">
<words>
<w>rcon</w>
</words>
</dictionary>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?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" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?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>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
-20
View File
@@ -1,20 +0,0 @@
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" ]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Dusty.P
Copyright (c) 2018 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
+1 -1
View File
@@ -1 +1 @@
# Geeksbot
# Geeksbot
-17
View File
@@ -1,17 +0,0 @@
package geeksbot
type Channel struct {
ID string
Guild Guild
Admin bool
Default bool
NewPatron bool
}
type ChannelService interface {
Channel(id string) (*Channel, error)
CreateChannel(c *Channel) (*Channel, error)
DeleteChannel(c *Channel) error
GuildChannels(g *Guild) ([]*Channel, error)
UpdateChannel(c *Channel) (*Channel, error)
}
-102
View File
@@ -1,102 +0,0 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/bwmarrin/discordgo"
"github.com/dustinpianalto/disgoman"
"github.com/dustinpianalto/geeksbot/internal/database"
"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),
}
database.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)
}
}
+1
View File
@@ -0,0 +1 @@
351794468870946827
+12
View File
@@ -0,0 +1,12 @@
{
"load_list": [
"admin",
"events",
"rcon",
"repl",
"patreon",
"fun",
"utils",
"git"
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"rcon_enabled" : false,
"channel_lockdown" : false,
"raid_status" : 0,
"pg_filter" : true,
"patreon_enabled" : false,
"referral_enabled" : false
}
+29
View File
@@ -0,0 +1,29 @@
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
View File
@@ -0,0 +1 @@
0
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"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"}
+673
View File
@@ -0,0 +1,673 @@
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
BIN
View File
Binary file not shown.
-36
View File
@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: geeksbot
namespace: default
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>
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: geeksbot
key: database_url
- name: DISCORD_TOKEN
valueFrom:
secretKeyRef:
name: geeksbot
key: discord_token
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+295
View File
@@ -0,0 +1,295 @@
import discord
from discord.ext import commands
import json
import logging
import inspect
import os
from .imports import checks
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_\-?=&/]+'
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}')
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 self.bot.con.all('select id from geeksbot_emojis where id = %(id)s', {'id': emoji.id}):
self.bot.con.run("update geeksbot_emojis set id = %(id)s, name = %(name)s, code = %(emoji_code)s "
"where name = %(name)s",
{'name': emoji.name, 'id': emoji.id, 'emoji_code': emoji_code})
else:
self.bot.con.run("insert into geeksbot_emojis(id,name,code) values (%(id)s,%(name)s,%(emoji_code)s)",
{'name': emoji.name, 'id': emoji.id, 'emoji_code': 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 = self.bot.con.one('select * from guild_config where guild_id = %(id)s', {'id': 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):
"""Run help set for more info"""
pass
@commands.group(case_insensitive=True)
async def add(self, ctx):
"""Run help set for more info"""
pass
@commands.group(case_insensitive=True)
async def remove(self, ctx):
"""Run help set for more info"""
pass
@set.command(name='admin_chan', aliases=['ac', 'admin_chat', 'admin chat'])
async def _admin_channel(self, ctx, channel: discord.TextChannel=None):
if ctx.guild:
if checks.is_admin(self.bot, ctx):
if channel is not None:
self.bot.con.run('update guild_config set admin_chat = %(chan)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'chan': 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'):
if ctx.guild:
if checks.is_admin(self.bot, ctx):
if str(config).lower() == 'true':
if self.bot.con.one('select allowed_channels from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}) is []:
await ctx.send('Please set at least one allowed channel before running this command.')
else:
self.bot.con.run('update guild_config set channel_lockdown = True where guild_id = %(id)s',
{'id': ctx.guild.id})
await ctx.send('Channel Lockdown is now active.')
elif str(config).lower() == 'false':
if self.bot.con.one('select channel_lockdown from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
self.bot.con.run('update guild_config set channel_lockdown = False where guild_id = %(id)s',
{'id': 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):
if ctx.guild:
if checks.is_admin(self.bot, ctx):
channels = channels.lower().replace(' ', '').split(',')
added = ''
for channel in channels:
chnl = discord.utils.get(ctx.guild.channels, name=channel)
if chnl is None:
await ctx.send(f'{channel} is not a valid text channel in this guild.')
else:
admin_log.info('Chan found')
if self.bot.con.one('select allowed_channels from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
if chnl.id in json.loads(self.bot.con.one('select allowed_channels from guild_config '
'where guild_id = %(id)s',
{'id': ctx.guild.id})):
admin_log.info('Chan found in config')
await ctx.send(f'{channel} is already in the list of allowed channels. Skipping...')
else:
admin_log.info('Chan not found in config')
allowed_channels = json.loads(self.bot.con.one('select allowed_channels from '
'guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})).append(chnl.id)
self.bot.con.run('update guild_config set allowed_channels = %(channels)s '
'where guild_id = %(id)s',
{'id': ctx.guild.id, 'channels': allowed_channels})
added = f'{added}\n{channel}'
else:
admin_log.info('Chan not found in config')
allowed_channels = [chnl.id]
self.bot.con.run('update guild_config set allowed_channels = %(channels)s '
'where guild_id = %(id)s',
{'id': ctx.guild.id, 'channels': allowed_channels})
added = f'{added}\n{channel}'
if added != '':
await ctx.send(f'The following channels have been added to the allowed channel list: {added}')
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.')
@commands.command()
@commands.is_owner()
async def view_code(self, ctx, code_name):
await ctx.send(f"```py\n{inspect.getsource(self.bot.get_command(code_name).callback)}\n```")
@add.command(aliases=['prefix', 'p'])
@commands.cooldown(1, 5, type=commands.BucketType.guild)
async def add_prefix(self, ctx, *, prefix=None):
if ctx.guild:
if checks.is_admin(self.bot, ctx):
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s',
{'id': 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]
self.bot.con.run('update guild_config set prefix = %(prefixes)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'prefixes': 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):
if ctx.guild:
if checks.is_admin(self.bot, ctx):
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s',
{'id': 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:
self.bot.con.run('update guild_config set prefix = %(prefixes)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'prefixes': 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):
role = discord.utils.get(ctx.guild.roles, name=role)
if role is not None:
roles = json.loads(self.bot.con.one('select admin_roles from guild_config where guild_id = %(id)s',
{'id': 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
self.bot.con.run('update guild_config set admin_roles = %(roles)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'roles': 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):
role = discord.utils.get(ctx.guild.roles, name=role)
if role is not None:
roles = json.loads(self.bot.con.one('select admin_roles from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}))
if role.name in roles:
del roles[role.name]
self.bot.con.run('update guild_config set admin_roles = %(roles)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'roles': 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))
+279
View File
@@ -0,0 +1,279 @@
import discord
import logging
from datetime import datetime
import json
import re
from .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
async def on_raw_message_delete(self, msg_id, chan_id):
self.bot.con.run('update messages set deleted_at = %(time)s where id = %(id)s',
{'time': datetime.utcnow(), 'id': msg_id})
async def on_raw_bulk_message_delete(self, msg_ids, chan_id):
sql = ''
for msg_id in msg_ids:
sql += f';update messages set deleted_at = %(time)s where id = {msg_id}'
self.bot.con.run(sql, {'time': datetime.utcnow()})
async def on_message(self, ctx):
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 (%(id)s, %(tts)s, %(type)s, %(content)s, %(embeds)s, %(channel)s, %(mention_everyone)s, %(mentions)s,\
%(channel_mentions)s, %(role_mentions)s, %(webhook)s, %(attachments)s, %(pinned)s, %(reactions)s, %(guild)s,\
%(created_at)s, %(system_content)s, %(author)s)'
msg_data = dict()
msg_data['id'] = ctx.id
msg_data['tts'] = ctx.tts
msg_data['type'] = str(ctx.type)
msg_data['content'] = ctx.content
msg_data['embeds'] = [json.dumps(e.to_dict()) for e in ctx.embeds]
msg_data['channel'] = ctx.channel.id
msg_data['mention_everyone'] = ctx.mention_everyone
msg_data['mentions'] = [user.id for user in ctx.mentions]
msg_data['channel_mentions'] = [channel.id for channel in ctx.channel_mentions]
msg_data['role_mentions'] = [role.id for role in ctx.role_mentions]
msg_data['webhook'] = ctx.webhook_id
msg_data['attachments'] = [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]
msg_data['pinned'] = ctx.pinned
msg_data['guild'] = ctx.guild.id
msg_data['created_at'] = ctx.created_at
msg_data['system_content'] = ctx.system_content
msg_data['author'] = ctx.author.id
msg_data['reactions'] = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions]
self.bot.con.run(sql, msg_data)
if ctx.guild:
if ctx.author != ctx.guild.me:
if self.bot.con.one(f"select pg_filter from guild_config where guild_id = {ctx.guild.id}"):
profane = 0
for word in self.bot.con.one('select profane_words from guild_config where guild_id = %(id)s',
{'id': 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]
self.bot.con.run('update messages set reactions = %(reacts)s where id = %(id)s',
{'id': react.message.id, 'reacts': reacts})
async def on_message_edit(self, before, ctx):
previous_content = self.bot.con.one('select previous_content from messages where id = %(id)s', {'id': ctx.id})
if previous_content:
previous_content.append(before.content)
else:
previous_content = [before.content]
previous_embeds = self.bot.con.one('select previous_embeds from messages where id = %(id)s', {'id': 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) \
= (%(edited_at)s, %(previous_content)s, %(previous_embeds)s, %(tts)s, %(type)s, %(content)s,\
%(embeds)s, %(channel)s, %(mention_everyone)s, %(mentions)s, %(channel_mentions)s, %(role_mentions)s,\
%(webhook)s, %(attachments)s, %(pinned)s, %(reactions)s, %(guild)s, %(created_at)s, %(system_content)s,\
%(author)s) where id = %(id)s'
msg_data = dict()
msg_data['id'] = ctx.id
msg_data['tts'] = ctx.tts
msg_data['type'] = str(ctx.type)
msg_data['content'] = ctx.content
msg_data['embeds'] = [json.dumps(e.to_dict()) for e in ctx.embeds]
msg_data['channel'] = ctx.channel.id
msg_data['mention_everyone'] = ctx.mention_everyone
msg_data['mentions'] = [user.id for user in ctx.mentions]
msg_data['channel_mentions'] = [channel.id for channel in ctx.channel_mentions]
msg_data['role_mentions'] = [role.id for role in ctx.role_mentions]
msg_data['webhook'] = ctx.webhook_id
msg_data['attachments'] = ctx.attachments
msg_data['pinned'] = ctx.pinned
msg_data['guild'] = ctx.guild.id
msg_data['created_at'] = ctx.created_at
msg_data['system_content'] = ctx.system_content
msg_data['author'] = ctx.author.id
msg_data['reactions'] = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions]
msg_data['previous_content'] = previous_content
msg_data['previous_embeds'] = previous_embeds
msg_data['edited_at'] = datetime.utcnow()
self.bot.con.run(sql, msg_data)
@staticmethod
async def on_command_error(ctx, error):
if ctx.channel.id == 418452585683484680 and type(error) == discord.ext.commands.errors.CommandNotFound:
return
for page in utils.paginate(error):
await ctx.send(page)
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)
self.bot.con.run("insert into guild_config(guild_id, guild_name, admin_roles, rcon_enabled, channel_lockdown,\
raid_status, pg_filter, patreon_enabled, referral_enabled)\
values (%(guild_id)s, %(name)s, %(admin_roles)s, %(rcon_enabled)s, %(channel_lockdown)s,\
%(raid_status)s, %(pg_filter)s, %(patreon_enabled)s, %(referral_enabled)s)",
{'guild_id': default_config['guild_id'],
'name': default_config['name'],
'admin_roles': json.dumps(default_config['admin_roles']),
'rcon_enabled': default_config['rcon_enabled'],
'channel_lockdown': default_config['channel_lockdown'],
'raid_status': default_config['raid_status'],
'pg_filter': default_config['pg_filter'],
'patreon_enabled': default_config['patreon_enabled'],
'referral_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):
self.bot.con.run(f'delete from guild_config where guild_id = %(id)s', {'id': 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 = self.bot.con.one('select join_leave_chat from guild_config where guild_id = %(id)s',
{'id': 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 = {'id': member.id,
'name': member.name,
'discriminator': member.discriminator,
'bot': member.bot
}
mem = self.bot.con.one('select guilds,nicks from user_data where id = %(id)s', {'id': member.id})
if mem:
mem[1].append(json.dumps({member.guild.id: member.display_name}))
mem[0].append(member.guild.id)
mem_data['nicks'] = mem[1]
mem_data['guilds'] = mem[0]
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) =\
(%(name)s, %(discriminator)s, %(bot)s, %(nicks)s, %(guilds)s) where\
id = %(id)s', mem_data)
else:
mem_data['nicks'] = [json.dumps({member.guild.id: member.display_name})]
mem_data['guilds'] = [member.guild.id]
self.bot.con.run('insert into user_data (id, name, discriminator, bot, nicks, guilds) values\
(%(id)s, %(name)s, %(discriminator)s, %(bot)s, %(nicks)s, %(guilds)s)', 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 = self.bot.con.one('select join_leave_chat from guild_config where guild_id = %(id)s',
{'id': 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)
def setup(bot):
bot.add_cog(BotEvents(bot))
+151
View File
@@ -0,0 +1,151 @@
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):
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):
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()
@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):
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:408543365085397013>')
else:
await ctx.send(f'{ctx.author.display_name} slaps '
f'{member.mention} around a bit with a large trout <:trout:408543365085397013>')
@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):
# if number < 20001 and number > 0:
# 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.')
@commands.command(hidden=True)
@commands.is_owner()
async def volume(self, ctx, volume: float):
self.bot.player.volume = volume
def setup(bot):
bot.add_cog(Fun(bot))
+54
View File
@@ -0,0 +1,54 @@
import discord
from discord.ext import commands
import logging
from .imports.utils import paginate, run_command
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)
async def git(self, ctx):
"""Run help git for more info"""
pass
@git.command()
@commands.is_owner()
async def pull(self, ctx):
em = discord.Embed(style='rich',
title=f'Git Pull',
color=embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git fetch --all')), 120) + '\n'
result += await asyncio.wait_for(self.bot.loop.create_task(run_command('git reset --hard '
'origin/master')), 120) + '\n\n'
result += await asyncio.wait_for(self.bot.loop.create_task(run_command('git show --stat | '
'sed "s/.*@.*[.].*/ /g"')), 10)
results = paginate(result, maxlen=1014)
for page in results[:5]:
em.add_field(name='', value=f'{page}')
await ctx.send(embed=em)
@git.command()
@commands.is_owner()
async def status(self, ctx):
em = discord.Embed(style='rich',
title=f'Git Pull',
color=embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git status')), 10)
results = paginate(result, maxlen=1014)
for page in results[:5]:
em.add_field(name='', value=f'{page}')
await ctx.send(embed=em)
def setup(bot):
bot.add_cog(Git(bot))
View File
+75
View File
@@ -0,0 +1,75 @@
import discord
import json
from . import utils
owner_id = 351794468870946827
def check_admin_role(bot, ctx, member):
admin_roles = json.loads(bot.con.one(f"select admin_roles from guild_config where guild_id = %(id)s",
{'id': 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
def check_rcon_role(bot, ctx, member):
rcon_admin_roles = json.loads(bot.con.one("select rcon_admin_roles from guild_config where guild_id = %(id)s",
{'id': 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
def is_admin(bot, ctx):
admin_roles = json.loads(bot.con.one("select admin_roles from guild_config where guild_id = %(id)s",
{'id': 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
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
def is_rcon_admin(bot, ctx):
rcon_admin_roles = json.loads(bot.con.one("select rcon_admin_roles from guild_config where guild_id = %(id)s",
{'id': 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
+93
View File
@@ -0,0 +1,93 @@
from io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator
from . import checks
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.con.one(f'select muted_role from guild_config where guild_id = {ctx.guild.id}')
if mute_role:
if admin or 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):
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 paginate(text, maxlen=1990):
paginator = Paginator(prefix='```py', max_size=maxlen+10)
if type(text) == list:
data = to_list_of_str(text)
elif type(text) == dict:
data = to_list_of_str(text)
else:
data = str(text).split('\n')
for line in data:
if len(line) > maxlen:
n = maxlen
for l in [line[i:i+n] for i in range(0, len(line), n)]:
paginator.add_line(l)
else:
paginator.add_line(line)
return paginator.pages
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()
+126
View File
@@ -0,0 +1,126 @@
import discord
from discord.ext import commands
import json
from .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 self.bot.con.one('select patreon_enabled from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}):
patreon_info = self.bot.con.one('select patreon_message,patreon_links\
from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
message = patreon_info[0].replace('\\n', '\n')
patreon_links = json.loads(patreon_info[1])
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 checks.is_admin(self.bot, ctx):
patreon_message = self.bot.con.one('select patreon_message from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if message == patreon_message:
await ctx.send('That is already the current message for this guild.')
else:
self.bot.con.run('update guild_config set patreon_message = %(message)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'message': 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 checks.is_admin(self.bot, ctx):
patreon_info = self.bot.con.one('select patreon_links from guild_config where guild_id = %(id)s',
{'id': 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
self.bot.con.run('update guild_config set patreon_links = %(links)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'links': 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 checks.is_admin(self.bot, ctx):
patreon_info = self.bot.con.one('select patreon_links from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if patreon_info:
patreon_links = json.loads(patreon_info)
if name in patreon_links:
del patreon_links[name]
self.bot.con.run('update guild_config set patreon_links = %(links)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'links': 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 checks.is_admin(self.bot, ctx):
patreon_status = self.bot.con.one('select patreon_enabled from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if patreon_status and state:
await ctx.send('Patreon is already enabled for this guild.')
elif patreon_status and not state:
self.bot.con.run('update guild_config set patreon_enabled = %(state)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'state': state})
await ctx.send('Patreon has been disabled for this guild.')
elif not patreon_status and state:
self.bot.con.run('update guild_config set patreon_enabled = %(state)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'state': 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 self.bot.con.one('select referral_enabled from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}):
referral_info = self.bot.con.one('select referral_message,referral_links from guild_config\
where guild_id = %(id)s', {'id': 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))
+530
View File
@@ -0,0 +1,530 @@
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 .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))
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 checks.is_rcon_admin(self.bot, ctx):
if server is not None:
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
server = server.replace('_', ' ').title()
if server in rcon_connections:
rcon_connections[server]["monitoring_chat"] = 1
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': 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)
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(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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 checks.is_rcon_admin(self.bot, ctx):
if server is not None:
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
server = server.replace('_', ' ').title()
if server in rcon_connections:
rcon_connections[server]["monitoring_chat"] = 0
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': 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 checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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:
try:
connection_info = rcon_connections[server]
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
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 checks.is_rcon_admin(self.bot, ctx):
server = server.title()
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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
}
self.bot.con.run('update guild_config set rcon_connections = %(connections)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'connections': 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 checks.is_rcon_admin(self.bot, ctx):
server = server.title()
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if server in rcon_connections:
del rcon_connections[server]
self.bot.con.run('update guild_config set rcon_connections = %(connections)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'connections': 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 checks.is_rcon_admin(self.bot, ctx):
if steam_ids is not None:
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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 checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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.strip())
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 checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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.strip())
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 checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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 checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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:
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': 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(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': 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))
+176
View File
@@ -0,0 +1,176 @@
from discord.ext import commands
import asyncio
import traceback
import discord
import inspect
import textwrap
from contextlib import redirect_stdout
import io
from .imports.utils import paginate, run_command
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
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:
value = stdout.getvalue()
await ctx.send('```py\n{}{}\n```'.format(value, traceback.format_exc()))
else:
value = stdout.getvalue()
# noinspection PyBroadException
try:
await ctx.message.add_reaction('')
except Exception:
pass
if ret is None:
if value:
for page in paginate(value):
await ctx.send(page)
else:
self._last_result = ret
if value:
for page in paginate(value):
await ctx.send(page)
for page in paginate(ret):
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:
if len(fmt) > 1990:
for page in paginate(fmt):
await response.channel.send(page)
await ctx.send(response.channel)
else:
await response.channel.send(f'```py\n{fmt}\n```')
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).split(' ')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 10)
value = result
for page in paginate(value):
await ctx.send(page)
await ctx.message.add_reaction('')
except asyncio.TimeoutError:
value = f"Command did not complete in the time allowed."
for page in paginate(value):
await ctx.send(page)
await ctx.message.add_reaction('')
def setup(bot):
bot.add_cog(Repl(bot))
+533
View File
@@ -0,0 +1,533 @@
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 .imports import checks
import pytz
import gspread
from oauth2client.service_account import ServiceAccountCredentials
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 = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚']
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()
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()
@commands.is_owner()
async def sysinfo(self, ctx):
await ctx.send(f'''
```ml
CPU Percentages: {psutil.cpu_percent(percpu=True)}
Memory Usage: {psutil.virtual_memory().percent}%
Disc Usage: {psutil.disk_usage("/").percent}%
```''')
@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"""
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**
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')}" \
f"Response Received: {datetime.strftime(now, '%H:%M:%S.%f')}" \
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):
"""Run help admin for more info"""
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:
self.bot.con.run('insert into admin_requests (issuing_member_id, guild_orig, request_text,'
'request_time) values (%(member_id)s, %(guild_id)s, %(text)s, %(time)s)',
{'member_id': ctx.author.id, 'guild_id': ctx.guild.id, 'text': request_msg,
'time': ctx.message.created_at})
channel = self.bot.con.one(f'select admin_chat from guild_config where guild_id = {ctx.guild.id}')
if channel:
chan = discord.utils.get(ctx.guild.channels, id=channel)
msg = ''
admin_roles = []
roles = self.bot.con.one(f'select admin_roles,rcon_admin_roles from guild_config where '
f'guild_id = %(id)s', {'id': ctx.guild.id})
request_id = self.bot.con.one(f'select id from admin_requests where '
f'issuing_member_id = %(member_id)s and request_time = %(time)s',
{'member_id': ctx.author.id, 'time': ctx.message.created_at})
for item in roles:
i = json.loads(item)
for j in i:
if i[j] not in admin_roles:
admin_roles.append(i[j])
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):
"""Returns a list of all active Admin help requests for this guild
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 checks.is_admin(self.bot, ctx) or checks.is_rcon_admin(self.bot, ctx):
if assigned_to is None:
requests = self.bot.con.all(f'select * from admin_requests where guild_orig = %(guild_id)s '
f'and completed_time is null', {'guild_id': 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 checks.check_admin_role(self.bot, ctx, assigned_to)\
or checks.check_rcon_role(self.bot, ctx, assigned_to):
requests = self.bot.con.all('select * from admin_requests where assigned_to = %(admin_id)s '
'and guild_orig = %(guild_id)s and completed_time is null',
{'admin_id': assigned_to.id, 'guild_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 {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 = self.bot.con.all('select * from admin_requests where issuing_member_id = %(member_id)s '
'and guild_orig = %(guild_id)s and completed_time is null',
{'member_id': ctx.author.id, 'guild_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 checks.is_admin(self.bot, ctx) or 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 = self.bot.con.one(f'select * from admin_requests where id = %(request_id)s',
{'request_id': request_id})
if request:
if request[3] == ctx.guild.id:
if request[6] is None:
self.bot.con.run('update admin_requests set completed_time = %(time_now)s where '
'id = %(request_id)s',
{'time_now': ctx.message.created_at, 'request_id': 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 provided,
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'):
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)
@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):
def is_me(message):
if message.author == self.bot.user:
return True
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': ctx.guild.id})
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 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'):
if 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):
res = self.bot.gcs_service.cse().list(q=search, cx=self.bot.bot_secrets['cx']).execute()
results = res['items'][:4]
em = discord.Embed()
em.title = f'Google Search'
em.description = f'Top 4 results for "{search}"'
em.colour = embed_color
for result in results:
em.add_field(name=f'{result["title"]}', value=f'{result["snippet"]}\n{result["link"]}')
await ctx.send(embed=em)
@commands.command(hidden=True, name='sheets')
async def google_sheets(self, ctx, member: discord.Member):
if 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)
def setup(bot):
bot.add_cog(Utils(bot))
+158
View File
@@ -0,0 +1,158 @@
from typing import Dict
from discord.ext import commands
import logging
from datetime import datetime
import json
import aiohttp
from postgres import Postgres
from collections import deque
from googleapiclient.discovery import build
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 = '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': '💩',
}
description = 'I am Geeksbot v0.1! Fear me!'
class Geeksbot(commands.Bot):
def __init__(self, **kwargs):
kwargs["command_prefix"] = self.get_custom_prefix
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)
# with open(f'{config_dir}{profane_words_file}') as file:
# self.profane_words = file.readlines()
self.guild_config = {}
self.infected = {}
self.TOKEN = self.bot_secrets['token']
del self.bot_secrets['token']
self.con = Postgres(f" host={self.bot_secrets['db_con']['host']}\
port={self.bot_secrets['db_con']['port']}\
dbname={self.bot_secrets['db_con']['db_name']}\
connect_timeout=10 user={self.bot_secrets['db_con']['user']}\
password={self.bot_secrets['db_con']['password']}")
del self.bot_secrets['db_con']
self.default_prefix = 'g$'
self.voice_chans = {}
self.spam_list = {}
self.gcs_service = build('customsearch', 'v1', developerKey=self.bot_secrets['google_search_key'])
async def get_custom_prefix(self, bot_inst, message):
return self.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': message.guild.id})\
or self.default_prefix
async def load_ext(self, ctx, mod=None):
self.load_extension('{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('{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(description=description, 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(bot.con.one(f"select channel_lockdown from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id})):
if ctx.channel.id in json.loads(bot.con.one(f"select allowed_channels from guild_config "
f"where guild_id = %(id)s",
{'id': ctx.guild.id})):
await bot.process_commands(ctx)
elif ctx.channel.id == 418452585683484680:
prefix = bot.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': 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.recent_msgs = {}
for guild in bot.guilds:
bot.recent_msgs[guild.id] = deque(maxlen=50)
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))
logging.info('Done loading, Geeksbot is active.')
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')
bot.run(bot.TOKEN)
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
until python /home/dusty/bin/geeksbot/geeksbot.py; do
echo "Geeksbot shutdown with error: $?. Restarting..." >&2
sleep 1
done
-12
View File
@@ -1,12 +0,0 @@
module github.com/dustinpianalto/geeksbot
go 1.14
require (
github.com/bwmarrin/discordgo v0.22.1
github.com/dustinpianalto/disgoman v0.0.15
github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect
github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
github.com/golang-migrate/migrate/v4 v4.14.1
github.com/hashicorp/errwrap v1.1.0 // indirect
)
-588
View File
@@ -1,588 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg=
cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/spanner v1.9.0/go.mod h1:xvlEn0NZ5v1iJPYsBnUVRDNvccDxsBTEi16pJRKQVws=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0=
github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
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/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
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/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
github.com/go-bindata/go-bindata v1.0.0 h1:DZ34txDXWn1DyWa+vQf7V9ANc2ILTtrEjtlsdJRF26M=
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-migrate/migrate v1.3.2 h1:QAlFV1QF9zdkzy/jujlBVkVu+L/+k18cg8tuY1/4JDY=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE=
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
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/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0=
github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-25
View File
@@ -1,25 +0,0 @@
package geeksbot
type Guild struct {
ID string
NewPatronMessage string
Prefixes []string
}
type Role struct {
ID string
RoleType int
Guild Guild
}
type GuildService interface {
Guild(id string) (*Guild, error)
CreateGuild(g *Guild) (*Guild, error)
DeleteGuild(g *Guild) error
UpdateGuild(g *Guild) (*Guild, error)
GuildRoles(g *Guild) ([]*Role, error)
CreateRole(r *Role) (*Role, error)
Role(id string) (*Role, error)
UpdateRole(r *Role) (*Role, error)
DeleteRole(r *Role) error
}
-44
View File
@@ -1,44 +0,0 @@
package database
import (
"database/sql"
"fmt"
"log"
"github.com/dustinpianalto/geeksbot/internal/database/migrations"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
bindata "github.com/golang-migrate/migrate/v4/source/go_bindata"
)
var (
db *sql.DB
)
func ConnectDatabase(dbConnString string) {
s := bindata.Resource(migrations.AssetNames(),
func(name string) ([]byte, error) {
return migrations.Asset(name)
})
db, err := sql.Open("postgres", dbConnString)
if err != nil {
log.Fatal(fmt.Errorf("Can't connect to the database: %w", err))
}
log.Println("Database Connected.")
db.SetMaxOpenConns(75)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(300)
d, err := bindata.WithInstance(s)
if err != nil {
log.Fatal(fmt.Errorf("Cannot load migrations: %w", err))
}
instance, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
log.Fatal(fmt.Errorf("Cannot create db driver: %w", err))
}
m, err := migrate.NewWithInstance("go-bindata", d, "postgres", instance)
if err != nil {
log.Fatal(fmt.Errorf("Cannot create migration instance: %w", err))
}
m.Up()
}
@@ -1,13 +0,0 @@
BEGIN;
DROP TABLE IF EXISTS servers;
DROP TABLE IF EXISTS comments;
DROP TABLE IF EXISTS requests;
DROP TABLE IF EXISTS patreon_tier;
DROP TABLE IF EXISTS patreon_creator;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS channels;
DROP TABLE IF EXISTS roles;
DROP TYPE IF EXISTS role_types;
DROP TABLE IF EXISTS guilds;
COMMIT;
@@ -1,174 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS guilds (
id varchar(30),
new_patron_message varchar(1000),
prefixes varchar(10)[],
PRIMARY KEY(id)
);
CREATE TYPE IF NOT EXISTS role_type as ENUM (
'normal',
'moderator',
'admin',
'patreon'
);
CREATE TABLE IF NOT EXISTS roles (
id varchar(30),
role_type role_type,
guild_id varchar(30),
PRIMARY KEY(id),
CONSTRAINT fk_guild
FOREIGN KEY(guild_id)
REFERENCES guilds(id)
ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS channels (
id varchar(30),
guild_id varchar(30),
admin boolean,
default boolean,
new_patron boolean,
PRIMARY KEY(id),
CONSTRAINT fk_guild
FOREIGN KEY(guild_id)
REFERENCES guilds(id)
ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS users (
id varchar(30),
steam_id varchar(30),
active boolean,
staff boolean,
admin boolean,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS messages (
id varchar(30),
created_at timestamp,
modified_at timestamp,
content varchar(2000)
previous_content varchar(2000)[],
channel_id varchar(30),
author_id varchar(30),
embed json,
previous_embeds json[],
PRIMARY KEY(id),
CONSTRAINT fk_channel
FOREIGN KEY(channel_id)
REFERENCES channels(id)
ON DELETE CASCADE,
CONSTRAINT fk_user
FOREIGN KEY(author_id)
REFERENCES users(id)
ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS patreon_creator (
id integer GENERATED ALWAYS AS IDENTITY,
creator varchar(100),
link varchar(200),
guild_id varchar(30),
PRIMARY KEY(id),
CONSTRAINT fk_guild
FOREIGN KEY(guild_id)
REFERENCES guilds(id)
ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS patreon_tier (
id integer GENERATED ALWAYS AS IDENTITY,
name varchar(100),
description varchar(1000),
creator integer,
role varchar(30),
next_tier integer,
PRIMARY KEY(id),
CONSTRAINT fk_creator
FOREIGN KEY(creator)
REFERENCES patreon_creator(id)
ON DELETE CASCADE,
CONSTRAINT fk_role
FOREIGN KEY(role)
REFERENCES roles(id)
ON DELETE SET NULL,
CONSTRAINT fk_tier
FOREIGN_KEY(next_tier)
REFERENCES patreon_tier(id)
ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS requests (
id integer GENERATED ALWAYS AS IDENTITY,
author_id varchar(30),
channel_id varchar(30),
content varchar(1000),
requested_at timestamp,
completed_at timestamp,
completed boolean,
completed_by varchar(30),
message_id varchar(30),
completed_message varchar(1000),
PRIMARY KEY(id),
CONSTRAINT fk_user
FOREIGN KEY(author_id)
REFERENCES users(id)
ON DELETE CASCADE,
CONSTRAINT fk_channel
FOREIGN KEY(channel_id)
REFERENCES channels(id)
ON DELETE CASCADE,
CONSTRAINT fk_completed_by
FOREIGN KEY(completed_by)
REFERENCES users(id)
ON DELETE SET NULL,
CONSTRAINT fk_message
FOREIGN KEY(message_id)
REFERENCES messages(id)
ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS comments (
id integer GENERATED ALWAYS AS IDENTITY,
author_id varchar(30),
request_id integer,
comment_at timestamp,
content varchar(1000),
PRIMARY KEY(id),
CONSTRAINT fk_user
FOREIGN KEY(author_id)
REFERENCES users(id)
ON DELETE CASCADE,
CONSTRAINT fk_request
FOREIGN KEY(request_id)
REFERENCES requests(id)
ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS servers (
id integer GENERATED ALWAYS AS IDENTITY,
name varchar(100),
ip_address varchar(15),
port integer,
password varchar(200),
alerts_channel_id varchar(30),
guild_id varchar(30),
info_channel_id varchar(30),
info_message_id varchar(30),
settings_message_id varchar(30),
PRIMARY KEY(id),
CONSTRAINT fk_alert_channel
FOREIGN KEY(alerts_channel_id)
REFERENCES channels(id)
ON DELETE SET NULL,
CONSTRAINT fk_guild
FOREIGN KEY(guild_id)
REFERENCES guilds(id)
ON DELETE CASCADE,
CONSTRAINT fk_info_channel
FOREIGN KEY(info_channel_id)
REFERENCES channels(id)
ON DELETE SET NULL,
CONSTRAINT fk_info_message
FOREIGN KEY(info_message_id)
REFERENCES messages(id)
ON DELETE SET NULL,
CONSTRAINT fk_settings_message
FOREIGN KEY(settings_message_id)
REFERENCES messages(id)
ON DELETE SET NULL
);
COMMIT;
-64
View File
@@ -1,64 +0,0 @@
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)
}
-32
View File
@@ -1,32 +0,0 @@
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),
}
}
-22
View File
@@ -1,22 +0,0 @@
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)
}
-248
View File
@@ -1,248 +0,0 @@
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,
}
}
}
-23
View File
@@ -1,23 +0,0 @@
package geeksbot
import "github.com/bwmarrin/discordgo"
type Message struct {
ID string
CreatedAt int64
ModifiedAt int64
Content string
PreviousContent []string
Channel *Channel
Author *User
Embed *discordgo.MessageEmbed
PreviousEmbeds []*discordgo.MessageEmbed
}
type MessageService interface {
Message(id string) (*Message, error)
CreateMessage(m *Message) (*Message, error)
DeleteMessage(m *Message) error
ChannelMessages(c *Channel) ([]*Message, error)
UpdateMessage(m *Message) (*Message, error)
}
+35
View File
@@ -0,0 +1,35 @@
@checks.no_bots()
@commands.cooldown(1,5,commands.BucketType.user)
@commands.command()
async def captcha(self, ctx, type, *, text):
type = type.lower()
if type not in "checked unchecked loading".split():
raise commands.BadArgument(f"Invalid type {type!r}. Available "
"types: `unchecked`, `loading`, `checked`")
font = ImageFont.truetype("Roboto-Regular.ttf", 14)
async with ctx.typing():
img = Image.open(f"blank-captcha-{type}.png")
img.load()
d = ImageDraw.Draw(img)
fnc = functools.partial(d.text, (53,30), text, fill=(0,0,0,255),
font=font)
await self.bot.loop.run_in_executor(None, fnc)
img.save("captcha.png")
await ctx.send(file=discord.File("captcha.png"))
os.system("rm captcha.png")
img.close()
import functools, youtube_dl
#bot.voice_chan = await ctx.author.voice.channel.connect()
bot.voice_chan.stop()
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)
url = 'https://www.youtube.com/watch?v=hjbPszSt5Pc'
func = functools.partial(ydl.extract_info, url, download=False)
info = func()
#bot.voice_chan.play(discord.FFmpegPCMAudio('dead_puppies.mp3'))
bot.voice_chan.play(discord.FFmpegPCMAudio(info['url']))
#async while bot.voice_chan.is_playing():
# pass
#await bot.voice_chan.disconnect()
-31
View File
@@ -1,31 +0,0 @@
package geeksbot
type PatreonCreator struct {
ID int
Creator string
Link string
Guild *Guild
}
type PatreonTier struct {
ID int
Name string
Description string
Creator *PatreonCreator
Role *Role
NextTier *PatreonTier
}
type PatreonService interface {
PatreonCreatorByID(id int) (*PatreonCreator, error)
PatreonCreatorByName(name string) (*PatreonCreator, error)
CreatePatreonCreator(c *PatreonCreator) (*PatreonCreator, error)
UpdatePatreonCreator(c *PatreonCreator) (*PatreonCreator, error)
DeletePatreonCreator(c *PatreonCreator) error
PatreonTierByID(id int) (*PatreonTier, error)
PatreonTierByName(name string) (*PatreonTier, error)
CreatePatreonTier(t *PatreonTier) (*PatreonTier, error)
UpdatePatreonTier(t *PatreonTier) (*PatreonTier, error)
DeletePatreonTier(t *PatreonTier) error
GuildPatreonCreators(g *Guild) ([]*PatreonCreator, error)
}
-37
View File
@@ -1,37 +0,0 @@
package geeksbot
import "time"
type Request struct {
ID int64
Author *User
Channel *Channel
Content string
RequestedAt time.Time
CompletedAt time.Time
CompletedBy *User
Message *Message
CompletedMessage string
}
type Comment struct {
ID int64
Author *User
Request *Request
CommentAt time.Time
Content string
}
type RequestService interface {
Request(id int64) (*Request, error)
UserRequests(u *User, completed bool) ([]*Request, error)
GuildRequests(g *Guild, completed bool) ([]*Request, error)
CreateRequest(r *Request) (*Request, error)
UpdateRequest(r *Request) (*Request, error)
DeleteRequest(r *Request) error
Comment(id int64) (*Comment, error)
RequestComments(r *Request) ([]*Comment, error)
RequestCommentCount(r *Request) (int, error)
CreateComment(c *Comment) (*Comment, error)
DeleteComment(c *Comment) error
}
-23
View File
@@ -1,23 +0,0 @@
package geeksbot
type Server struct {
ID int
Name string
IPAddr string
Port int
Password string
AlertsChannel *Channel
Guild *Guild
InfoChannel *Channel
InfoMessage *Message
SettingsMessage *Message
}
type ServerService interface {
ServerByID(id int) (*Server, error)
ServerByName(name string) (*Server, error)
CreateServer(s *Server) (*Server, error)
DeleteServer(s *Server) error
UpdateServer(s *Server) (*Server, error)
GuildServers(g *Guild) ([]*Server, error)
}
-18
View File
@@ -1,18 +0,0 @@
package geeksbot
import "database/sql"
type User struct {
ID string
SteamID sql.NullString
IsActive bool
IsStaff bool
IsAdmin bool
}
type UserService interface {
User(id string) (*User, error)
CreateUser(u *User) (*User, error)
DeleteUser(u *User) error
UpdateUser(u *User) (*User, error)
}