diff --git a/.geeksbot_launcher.sh.swp b/.geeksbot_launcher.sh.swp new file mode 100644 index 0000000..751b69a Binary files /dev/null and b/.geeksbot_launcher.sh.swp differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..786e996 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bot_secrets.json diff --git a/config/admin_ids b/config/admin_ids new file mode 100644 index 0000000..eb074af --- /dev/null +++ b/config/admin_ids @@ -0,0 +1 @@ +351794468870946827 \ No newline at end of file diff --git a/config/bot_config.json b/config/bot_config.json new file mode 100644 index 0000000..6cd9162 --- /dev/null +++ b/config/bot_config.json @@ -0,0 +1,11 @@ +{ + "load_list": [ + "admin", + "events", + "rcon", + "repl", + "patreon", + "fun", + "utils" + ] +} diff --git a/config/default_guild_config.json b/config/default_guild_config.json new file mode 100644 index 0000000..d1df79c --- /dev/null +++ b/config/default_guild_config.json @@ -0,0 +1,8 @@ +{ + "rcon_enabled" : false, + "channel_lockdown" : false, + "raid_status" : 0, + "pg_filter" : true, + "patreon_enabled" : false, + "referral_enabled" : false +} \ No newline at end of file diff --git a/config/google_client_secret.json b/config/google_client_secret.json new file mode 100644 index 0000000..9d8bbac --- /dev/null +++ b/config/google_client_secret.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "geeksbot-196221", + "private_key_id": "0ccc9c634c498b2dd49cd56f5436433f18b88c94", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEOPnlmVQowNFo\nXkiEv2f7l/USSUgPcTt0nj8s3zd5D4kVgkhs0E15bVg1NdeJn+ElA7coRvLqGgF1\n+SwC7WE7t24WwYrRnC9QEMwt0fcpKJkPhdre+HQYOPOILeyhTxN3rh9x9AJAOjA1\nlokYhcnLMVpTnh7qQhvZLCJWPesInqg9WQ18yZCXDBdGzalVSDBMlt7fONtL/P9i\nffT955GQBKdRh3WW/i9oY0IuMRMMeP8EP0T2w9JgnlyhpjIlb6kGYPZfyXQ+hNfe\njpP4pdC+VxkbAfk0f9NiKDODeSCjG2z2Y8tUSVbhN6aeaFd26ZMzAG9cro3vL0dO\nafJWjX3bAgMBAAECggEAEOewY97+A1V6qmlhCbVsfHOphKNKnDCkWiuIrtF7on2Q\n+7OR5h9bZV2NxBEjmh2WPzhJjySZqKCH8AWBDK7YedVs8cnyhJTWS1QUeygZM/MQ\nSL2T+dy23SAJnyRcZsNSHoqiS+YlTCpB3Vle7f1swV7ln3RCc+qGeAMs4Xg2fQij\niqJjyl5EVUxpadyd+Nmcnfwq5Vdh+olRV+139TqNbwymip/3XNkugotQ/EqcvP3o\n4EHj//k1nCA4HvCJjcm5EJs38iT99z7OFu5VebhuUclKTHTygQ8glFbpUxXbl3xX\n81racTiytdOEzUHR8Ox2IfYQ5b2tCnMPfFuVs1DMSQKBgQDrEtfJ8Ms6MovunbO2\nobc89EnjWy/W8mpucb0MWh3gSBtKttQPHiHDZzH+FPR+fK+ZSh3ULYWY13SHPUaH\nPP1asOLUjASJiFcw5ogHx5VQc6EPLASsuyADuwt5yOllPtK4B11YTZLhgHAVsQTJ\n3A7VmYOD8TGjwgvPQdNpT8SUswKBgQDVsL8DckSJwLopkfBD1vksu+p+QyH+HoaY\nG7UDFkeabqa1A2oQBhRdBkVZuonggJVYTHhYvTflXl6m0Aj8A9PBx1KBMSp3jRZq\nRwb9euAe+zWITSpmMH+iRqiElv+pfzQ++K0u3laU4Ysybsj+Vy49endPtOnezDf5\nLNB6T3sWOQKBgQDlo7S2C8scgUB9zAVBxl0Q6Lw9pFjprEsYtXeu12IUNZyjslMa\nqZ7mGquVwLbP0dJg9yyImCfIlcG6U7vQZV5C+EW+yUGtcUlr9eixYOGWhD60aZXv\nf6XYvyKIyCJoy6RiLp+bobx5GlVke1doMtczBxKZFEgf53JN98olOM2bTQKBgA0n\nPfq2U+WuyUa5xvJGDzxjrMFs3HDJ7Dr8qZ2xB2NIIFbQCP1HgoVfV3F4e/gnsgmn\nW1kK/J/PuT/HWmY4zhYFcNym7BhDxPdxu8pqf9UoXVkwdsWngpO4ibLvoHkMbWja\n4b4azXWIlIrcKt8M+rmqCiIL3sFqDJ/31DVTIx5xAoGBANfkldD7z9nveEYwb4L3\nA6W2QK1KrSBLcQSNbw4tcKxiyirY0y5JlPy7NdjzGvfqEujGmaZ0f4yXYuN8R04D\nLM0vdlqpaHPs/dP3K+ukMpvwvqq8Keiw8ERJ7z3L+WJyT/wvx8t5Z1zT3JMbm9b/\n2Yfylx+UKh0DQezFbMzJi4Yc\n-----END PRIVATE KEY-----\n", + "client_email": "geeksbot-gsheets@geeksbot-196221.iam.gserviceaccount.com", + "client_id": "103726668019205391198", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/geeksbot-gsheets%40geeksbot-196221.iam.gserviceaccount.com" +} \ No newline at end of file diff --git a/config/profane_words b/config/profane_words new file mode 100644 index 0000000..866fa5f --- /dev/null +++ b/config/profane_words @@ -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 \ No newline at end of file diff --git a/config/reboot b/config/reboot new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/config/reboot @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/data_files/4_digit_primes b/data_files/4_digit_primes new file mode 100644 index 0000000..e4d23e8 --- /dev/null +++ b/data_files/4_digit_primes @@ -0,0 +1 @@ +["0002","0003","0005","0007","0011","0013","0017","0019","0023","0029","0031","0037","0041","0043","0047","0053","0059","0061","0067","0071","0073","0079","0083","0089","0097","0101","0103","0107","0109","0113","0127","0131","0137","0139","0149","0151","0157","0163","0167","0173","0179","0181","0191","0193","0197","0199","0211","0223","0227","0229","0233","0239","0241","0251","0257","0263","0269","0271","0277","0281","0283","0293","0307","0311","0313","0317","0331","0337","0347","0349","0353","0359","0367","0373","0379","0383","0389","0397","0401","0409","0419","0421","0431","0433","0439","0443","0449","0457","0461","0463","0467","0479","0487","0491","0499","0503","0509","0521","0523","0541","0547","0557","0563","0569","0571","0577","0587","0593","0599","0601","0607","0613","0617","0619","0631","0641","0643","0647","0653","0659","0661","0673","0677","0683","0691","0701","0709","0719","0727","0733","0739","0743","0751","0757","0761","0769","0773","0787","0797","0809","0811","0821","0823","0827","0829","0839","0853","0857","0859","0863","0877","0881","0883","0887","0907","0911","0919","0929","0937","0941","0947","0953","0967","0971","0977","0983","0991","0997","1009","1013","1019","1021","1031","1033","1039","1049","1051","1061","1063","1069","1087","1091","1093","1097","1103","1109","1117","1123","1129","1151","1153","1163","1171","1181","1187","1193","1201","1213","1217","1223","1229","1231","1237","1249","1259","1277","1279","1283","1289","1291","1297","1301","1303","1307","1319","1321","1327","1361","1367","1373","1381","1399","1409","1423","1427","1429","1433","1439","1447","1451","1453","1459","1471","1481","1483","1487","1489","1493","1499","1511","1523","1531","1543","1549","1553","1559","1567","1571","1579","1583","1597","1601","1607","1609","1613","1619","1621","1627","1637","1657","1663","1667","1669","1693","1697","1699","1709","1721","1723","1733","1741","1747","1753","1759","1777","1783","1787","1789","1801","1811","1823","1831","1847","1861","1867","1871","1873","1877","1879","1889","1901","1907","1913","1931","1933","1949","1951","1973","1979","1987","1993","1997","1999","2003","2011","2017","2027","2029","2039","2053","2063","2069","2081","2083","2087","2089","2099","2111","2113","2129","2131","2137","2141","2143","2153","2161","2179","2203","2207","2213","2221","2237","2239","2243","2251","2267","2269","2273","2281","2287","2293","2297","2309","2311","2333","2339","2341","2347","2351","2357","2371","2377","2381","2383","2389","2393","2399","2411","2417","2423","2437","2441","2447","2459","2467","2473","2477","2503","2521","2531","2539","2543","2549","2551","2557","2579","2591","2593","2609","2617","2621","2633","2647","2657","2659","2663","2671","2677","2683","2687","2689","2693","2699","2707","2711","2713","2719","2729","2731","2741","2749","2753","2767","2777","2789","2791","2797","2801","2803","2819","2833","2837","2843","2851","2857","2861","2879","2887","2897","2903","2909","2917","2927","2939","2953","2957","2963","2969","2971","2999","3001","3011","3019","3023","3037","3041","3049","3061","3067","3079","3083","3089","3109","3119","3121","3137","3163","3167","3169","3181","3187","3191","3203","3209","3217","3221","3229","3251","3253","3257","3259","3271","3299","3301","3307","3313","3319","3323","3329","3331","3343","3347","3359","3361","3371","3373","3389","3391","3407","3413","3433","3449","3457","3461","3463","3467","3469","3491","3499","3511","3517","3527","3529","3533","3539","3541","3547","3557","3559","3571","3581","3583","3593","3607","3613","3617","3623","3631","3637","3643","3659","3671","3673","3677","3691","3697","3701","3709","3719","3727","3733","3739","3761","3767","3769","3779","3793","3797","3803","3821","3823","3833","3847","3851","3853","3863","3877","3881","3889","3907","3911","3917","3919","3923","3929","3931","3943","3947","3967","3989","4001","4003","4007","4013","4019","4021","4027","4049","4051","4057","4073","4079","4091","4093","4099","4111","4127","4129","4133","4139","4153","4157","4159","4177","4201","4211","4217","4219","4229","4231","4241","4243","4253","4259","4261","4271","4273","4283","4289","4297","4327","4337","4339","4349","4357","4363","4373","4391","4397","4409","4421","4423","4441","4447","4451","4457","4463","4481","4483","4493","4507","4513","4517","4519","4523","4547","4549","4561","4567","4583","4591","4597","4603","4621","4637","4639","4643","4649","4651","4657","4663","4673","4679","4691","4703","4721","4723","4729","4733","4751","4759","4783","4787","4789","4793","4799","4801","4813","4817","4831","4861","4871","4877","4889","4903","4909","4919","4931","4933","4937","4943","4951","4957","4967","4969","4973","4987","4993","4999","5003","5009","5011","5021","5023","5039","5051","5059","5077","5081","5087","5099","5101","5107","5113","5119","5147","5153","5167","5171","5179","5189","5197","5209","5227","5231","5233","5237","5261","5273","5279","5281","5297","5303","5309","5323","5333","5347","5351","5381","5387","5393","5399","5407","5413","5417","5419","5431","5437","5441","5443","5449","5471","5477","5479","5483","5501","5503","5507","5519","5521","5527","5531","5557","5563","5569","5573","5581","5591","5623","5639","5641","5647","5651","5653","5657","5659","5669","5683","5689","5693","5701","5711","5717","5737","5741","5743","5749","5779","5783","5791","5801","5807","5813","5821","5827","5839","5843","5849","5851","5857","5861","5867","5869","5879","5881","5897","5903","5923","5927","5939","5953","5981","5987","6007","6011","6029","6037","6043","6047","6053","6067","6073","6079","6089","6091","6101","6113","6121","6131","6133","6143","6151","6163","6173","6197","6199","6203","6211","6217","6221","6229","6247","6257","6263","6269","6271","6277","6287","6299","6301","6311","6317","6323","6329","6337","6343","6353","6359","6361","6367","6373","6379","6389","6397","6421","6427","6449","6451","6469","6473","6481","6491","6521","6529","6547","6551","6553","6563","6569","6571","6577","6581","6599","6607","6619","6637","6653","6659","6661","6673","6679","6689","6691","6701","6703","6709","6719","6733","6737","6761","6763","6779","6781","6791","6793","6803","6823","6827","6829","6833","6841","6857","6863","6869","6871","6883","6899","6907","6911","6917","6947","6949","6959","6961","6967","6971","6977","6983","6991","6997","7001","7013","7019","7027","7039","7043","7057","7069","7079","7103","7109","7121","7127","7129","7151","7159","7177","7187","7193","7207","7211","7213","7219","7229","7237","7243","7247","7253","7283","7297","7307","7309","7321","7331","7333","7349","7351","7369","7393","7411","7417","7433","7451","7457","7459","7477","7481","7487","7489","7499","7507","7517","7523","7529","7537","7541","7547","7549","7559","7561","7573","7577","7583","7589","7591","7603","7607","7621","7639","7643","7649","7669","7673","7681","7687","7691","7699","7703","7717","7723","7727","7741","7753","7757","7759","7789","7793","7817","7823","7829","7841","7853","7867","7873","7877","7879","7883","7901","7907","7919","7927","7933","7937","7949","7951","7963","7993","8009","8011","8017","8039","8053","8059","8069","8081","8087","8089","8093","8101","8111","8117","8123","8147","8161","8167","8171","8179","8191","8209","8219","8221","8231","8233","8237","8243","8263","8269","8273","8287","8291","8293","8297","8311","8317","8329","8353","8363","8369","8377","8387","8389","8419","8423","8429","8431","8443","8447","8461","8467","8501","8513","8521","8527","8537","8539","8543","8563","8573","8581","8597","8599","8609","8623","8627","8629","8641","8647","8663","8669","8677","8681","8689","8693","8699","8707","8713","8719","8731","8737","8741","8747","8753","8761","8779","8783","8803","8807","8819","8821","8831","8837","8839","8849","8861","8863","8867","8887","8893","8923","8929","8933","8941","8951","8963","8969","8971","8999","9001","9007","9011","9013","9029","9041","9043","9049","9059","9067","9091","9103","9109","9127","9133","9137","9151","9157","9161","9173","9181","9187","9199","9203","9209","9221","9227","9239","9241","9257","9277","9281","9283","9293","9311","9319","9323","9337","9341","9343","9349","9371","9377","9391","9397","9403","9413","9419","9421","9431","9433","9437","9439","9461","9463","9467","9473","9479","9491","9497","9511","9521","9533","9539","9547","9551","9587","9601","9613","9619","9623","9629","9631","9643","9649","9661","9677","9679","9689","9697","9719","9721","9733","9739","9743","9749","9767","9769","9781","9787","9791","9803","9811","9817","9829","9833","9839","9851","9857","9859","9871","9883","9887","9901","9907","9923","9929","9931","9941","9949","9967","9973"] \ No newline at end of file diff --git a/data_files/blobs.json b/data_files/blobs.json new file mode 100644 index 0000000..d482346 --- /dev/null +++ b/data_files/blobs.json @@ -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"} diff --git a/data_files/time_zones b/data_files/time_zones new file mode 100644 index 0000000..cf2ed23 --- /dev/null +++ b/data_files/time_zones @@ -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 \ No newline at end of file diff --git a/dead_puppies.mp3 b/dead_puppies.mp3 new file mode 100644 index 0000000..da1c8f2 Binary files /dev/null and b/dead_puppies.mp3 differ diff --git a/exts/__init__.py b/exts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/exts/__pycache__/__init__.cpython-36.pyc b/exts/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..20a8c4a Binary files /dev/null and b/exts/__pycache__/__init__.cpython-36.pyc differ diff --git a/exts/__pycache__/admin.cpython-36.pyc b/exts/__pycache__/admin.cpython-36.pyc new file mode 100644 index 0000000..dc97ffa Binary files /dev/null and b/exts/__pycache__/admin.cpython-36.pyc differ diff --git a/exts/__pycache__/admin_tools.cpython-36.pyc b/exts/__pycache__/admin_tools.cpython-36.pyc new file mode 100644 index 0000000..c7d4bab Binary files /dev/null and b/exts/__pycache__/admin_tools.cpython-36.pyc differ diff --git a/exts/__pycache__/events.cpython-36.pyc b/exts/__pycache__/events.cpython-36.pyc new file mode 100644 index 0000000..8d2293d Binary files /dev/null and b/exts/__pycache__/events.cpython-36.pyc differ diff --git a/exts/__pycache__/fun.cpython-36.pyc b/exts/__pycache__/fun.cpython-36.pyc new file mode 100644 index 0000000..00a0a5e Binary files /dev/null and b/exts/__pycache__/fun.cpython-36.pyc differ diff --git a/exts/__pycache__/patreon.cpython-36.pyc b/exts/__pycache__/patreon.cpython-36.pyc new file mode 100644 index 0000000..cd7ac5f Binary files /dev/null and b/exts/__pycache__/patreon.cpython-36.pyc differ diff --git a/exts/__pycache__/profanity.cpython-36.pyc b/exts/__pycache__/profanity.cpython-36.pyc new file mode 100644 index 0000000..c96b822 Binary files /dev/null and b/exts/__pycache__/profanity.cpython-36.pyc differ diff --git a/exts/__pycache__/rcon.cpython-36.pyc b/exts/__pycache__/rcon.cpython-36.pyc new file mode 100644 index 0000000..71af97f Binary files /dev/null and b/exts/__pycache__/rcon.cpython-36.pyc differ diff --git a/exts/__pycache__/repl.cpython-36.pyc b/exts/__pycache__/repl.cpython-36.pyc new file mode 100644 index 0000000..00bdd42 Binary files /dev/null and b/exts/__pycache__/repl.cpython-36.pyc differ diff --git a/exts/__pycache__/srcds.cpython-36.pyc b/exts/__pycache__/srcds.cpython-36.pyc new file mode 100644 index 0000000..2a87454 Binary files /dev/null and b/exts/__pycache__/srcds.cpython-36.pyc differ diff --git a/exts/__pycache__/utils.cpython-36.pyc b/exts/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000..44eac60 Binary files /dev/null and b/exts/__pycache__/utils.cpython-36.pyc differ diff --git a/exts/admin.py b/exts/admin.py new file mode 100644 index 0000000..23caa6b --- /dev/null +++ b/exts/admin.py @@ -0,0 +1,269 @@ +import discord +from discord.ext import commands +import json +from srcds import rcon as rcon_con +import time, logging, math +from datetime import datetime, timedelta +import asyncio, inspect +import aiohttp, async_timeout +from bs4 import BeautifulSoup as bs +import traceback +import os, sys +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 + + def _get_config_string(self, 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'' + else: + emoji_code = f'<:{emoji.name}:{emoji.id}>' + if self.bot.con.all(f'select id from geeksbot_emojis where id = %(id)s', {'id':emoji.id}): + self.bot.con.run(f"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(f"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(f'select * from guild_config where guild_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 != None: + self.bot.con.run(f'update guild_config set admin_chat = %(chan)s where guild_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(f'select allowed_channels from guild_config where guild_id = {ctx.guild.id}') == []: + await ctx.send('Please set at least one allowed channel before running this command.') + else: + self.bot.con.run(f'update guild_config set channel_lockdown = True where guild_id = {ctx.guild.id}') + await ctx.send('Channel Lockdown is now active.') + elif str(config).lower() == 'false': + if self.bot.con.one(f'select channel_lockdown from guild_config where guild_id = {ctx.guild.id}'): + self.bot.con.run(f'update guild_config set channel_lockdown = False where guild_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 == 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(f'select allowed_channels from guild_config where guild_id = {ctx.guild.id}'): + if chnl.id in json.loads(self.bot.con.one(f'select allowed_channels from guild_config where guild_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(f'select allowed_channels from guild_config where guild_id = {ctx.guild.id}')).append(chnl.id) + self.bot.con.run(f"update guild_config set allowed_channels = %(channels)s where guild_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(f"update guild_config set allowed_channels = %(channels)s where guild_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(f'select prefix from guild_config where guild_id = {ctx.guild.id}') + if prefix == None: + await ctx.send(prefixes) + return + elif prefixes == 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(f"update guild_config set prefix = %(prefixes)s where guild_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)} {'prefix' if len(prefixes) == 1 else 'prefixes'} 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 = [] + prefixes = self.bot.con.one(f'select prefix from guild_config where guild_id = {ctx.guild.id}') + found = 0 + if prefix == None: + await ctx.send(prefixes) + return + elif prefixes == 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(f"update guild_config set prefix = %(prefixes)s where guild_id = {ctx.guild.id}", {'prefixes':prefixes}) + await ctx.guild.me.edit(nick=f'[{prefixes[0] if len(prefixes) != 0 else self.bot.default_prefix}] Geeksbot') + await ctx.send(f"Updated. You currently have {len(prefixes)} {'prefix' if len(prefixes) == 1 else 'prefixes'} 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 != None: + roles = json.loads(self.bot.con.one(f'select admin_roles from guild_config where guild_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(f"update guild_config set admin_roles = %(roles)s where guild_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 != None: + roles = json.loads(self.bot.con.one(f'select admin_roles from guild_config where guild_id = {ctx.guild.id}')) + if role.name in roles: + del roles[role.name] + self.bot.con.run(f"update guild_config set admin_roles = %(roles)s where guild_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)) diff --git a/exts/events.py b/exts/events.py new file mode 100644 index 0000000..c570bc5 --- /dev/null +++ b/exts/events.py @@ -0,0 +1,245 @@ +import discord +from discord.ext import commands +import logging +from datetime import datetime, timedelta +import json, asyncio, traceback +import os, re +from .imports import checks, 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 bot_events(): + def __init__(self, bot): + self.bot = bot + + def _get_config_string(self, 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: + 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 = {} + 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(f'select profane_words from guild_config where guild_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(f'update messages set reactions = %(reacts)s where id = {react.message.id}', {'reacts': reacts}) + + async def on_message_edit(self, before, ctx): + previous_content = self.bot.con.one(f'select previous_content from messages where id = {ctx.id}') + if previous_content: + previous_content.append(before.content) + else: + previous_content = [before.content] + previous_embeds = self.bot.con.one(f'select previous_embeds from messages where 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 = {} + 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) + + async def on_command_error(self,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(f"insert into guild_config(guild_id,guild_name,admin_roles,rcon_enabled,channel_lockdown,raid_status,pg_filter,patreon_enabled,referral_enabled)\ + values ({default_config['guild_id']},E'{default_config['name']}','{json.dumps(default_config['admin_roles'])}',{default_config['rcon_enabled']},{default_config['channel_lockdown']},{default_config['raid_status']},{default_config['pg_filter']},{default_config['patreon_enabled']},{default_config['referral_enabled']})") + events_log.info(f'Entry Created for {guild.name}') + config_str = self._get_config_string(default_config) + self.bot.recent_msgs[guild.id] = deque(maxlen=50) + await guild.me.edit(nick='[g$] Geeksbot') + # await guild.owner.send(f'Geeksbot has joined your guild {guild.name}!\nYour current configuration is:\n```{config_str}```\nEnjoy!') + + async def on_guild_remove(self, guild): + self.bot.con.run(f'delete from guild_config where guild_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(f'select join_leave_chat from guild_config where guild_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(f'select guilds,nicks from user_data where 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(f'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(f'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(f'select join_leave_chat from guild_config where guild_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) + time_str = f"{str(int(days)) + ' days, ' if days != 0 else ''}{str(int(hours)) + ' hours, ' if hours != 0 else ''}{str(int(minutes)) + ' minutes and ' if minutes != 0 else ''}{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(bot_events(bot)) diff --git a/exts/fun.py b/exts/fun.py new file mode 100644 index 0000000..fa4380b --- /dev/null +++ b/exts/fun.py @@ -0,0 +1,143 @@ +import discord +from discord.ext import commands +import logging +from datetime import datetime +import json, asyncio +import os, re, aiohttp, async_timeout + +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. 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 '') + else: + await ctx.send(f'{ctx.author.display_name} slaps {member.mention} around a bit with a large trout <:trout:408543365085397013>') + + def get_factorial(self,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: + import youtube_dl + 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)) diff --git a/exts/imports/__init__.py b/exts/imports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/exts/imports/__pycache__/__init__.cpython-36.pyc b/exts/imports/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..9632d5e Binary files /dev/null and b/exts/imports/__pycache__/__init__.cpython-36.pyc differ diff --git a/exts/imports/__pycache__/checks.cpython-36.pyc b/exts/imports/__pycache__/checks.cpython-36.pyc new file mode 100644 index 0000000..128451e Binary files /dev/null and b/exts/imports/__pycache__/checks.cpython-36.pyc differ diff --git a/exts/imports/__pycache__/utils.cpython-36.pyc b/exts/imports/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000..5f4baeb Binary files /dev/null and b/exts/imports/__pycache__/utils.cpython-36.pyc differ diff --git a/exts/imports/checks.py b/exts/imports/checks.py new file mode 100644 index 0000000..b6044b0 --- /dev/null +++ b/exts/imports/checks.py @@ -0,0 +1,64 @@ +import discord, json, asyncio +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 = {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(f"select rcon_admin_roles from guild_config where guild_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(f"select admin_roles from guild_config where guild_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(f"select rcon_admin_roles from guild_config where guild_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 == 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 \ No newline at end of file diff --git a/exts/imports/utils.py b/exts/imports/utils.py new file mode 100644 index 0000000..8431a1d --- /dev/null +++ b/exts/imports/utils.py @@ -0,0 +1,87 @@ +from io import StringIO +import sys, asyncio +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 = self.bot.con.one(f'select muted_role from guild_config where guild_id = {ctx.guild.id}') + if muted_role: + if admin or checks.is_admin(bot, ctx): + if ctx.guild.me.guild_permissions.manage_roles: + if member: + 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=[], 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 = [] + 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 = [] + out.append('{') + for key in items: + rec_loop(items[key], key, out, level) + if not recurse: + out.append('}') + + return out + +def paginate(text): + data = [] + paginator = Paginator(prefix='```py') + 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) > 1900: + n = 1900 + 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_exec( + *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() \ No newline at end of file diff --git a/exts/patreon.py b/exts/patreon.py new file mode 100644 index 0000000..a388b1c --- /dev/null +++ b/exts/patreon.py @@ -0,0 +1,112 @@ +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(f'select patreon_enabled from guild_config where guild_id = {ctx.guild.id}'): + patreon_info = self.bot.con.one(f'select patreon_message,patreon_links from guild_config where guild_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 == 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(f'select patreon_message from guild_config where guild_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(f'update guild_config set patreon_message = %(message)s where guild_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(f'select patreon_links from guild_config where guild_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(f'update guild_config set patreon_links = %(links)s where guild_id = {ctx.guild.id}', {'links': json.dumps(patreon_links)}) + await ctx.send(f"The Patreon link for {name} has been {'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(f'select patreon_links from guild_config where guild_id = {ctx.guild.id}') + patreon_links = {} + if patreon_info: + patreon_links = json.loads(patreon_info) + if name in patreon_links: + del patreon_links[name] + self.bot.con.run(f'update guild_config set patreon_links = %(links)s where guild_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(f'select patreon_enabled from guild_config where guild_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(f'update guild_config set patreon_enabled = %(state)s where guild_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(f'update guild_config set patreon_enabled = %(state)s where guild_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(f'select referral_enabled from guild_config where guild_id = {ctx.guild.id}'): + referral_info = self.bot.con.one(f'select referral_message,referral_links from guild_config where guild_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 == 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)) diff --git a/exts/rcon.py b/exts/rcon.py new file mode 100644 index 0000000..6c0a2d3 --- /dev/null +++ b/exts/rcon.py @@ -0,0 +1,489 @@ +import discord +from discord.ext import commands +import json +from srcds import rcon as rcon_con +import time, 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 + + def _listplayers(self, 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() + + def _saveworld(self, 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() + + def _whitelist(self, 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 + + def _broadcast(self, 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 + + def _get_current_chat(self, 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 as e: + 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 + + + + def admin(self, 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 2 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 != None: + rcon_connections = json.loads(self.bot.con.one(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + server = server.replace('_',' ').title() + if server in rcon_connections: + rcon_connections[server]["monitoring_chat"] = 1 + self.bot.con.run(f"update guild_config set rcon_connections = '{json.dumps(rcon_connections)}' where guild_id = {ctx.guild.id}") + 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(f"select rcon_connections from guild_config where guild_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 != None: + rcon_connections = json.loads(self.bot.con.one(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + server = server.replace('_',' ').title() + if server in rcon_connections: + rcon_connections[server]["monitoring_chat"] = 0 + self.bot.con.run(f"update guild_config set rcon_connections = '{json.dumps(rcon_connections)}' where guild_id = {ctx.guild.id}") + 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 2 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(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + if server != 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 (, , ) 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(f"select rcon_connections from guild_config where guild_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(f"update guild_config set rcon_connections = %(connections)s where guild_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 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(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + if server in rcon_connections: + del rcon_connections[server] + self.bot.con.run(f"update guild_config set rcon_connections = %(connections)s where guild_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 != None: + rcon_connections = json.loads(self.bot.con.one(f"select rcon_connections from guild_config where guild_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.strip()) + 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 sucessfully.''' + if checks.is_rcon_admin(self.bot, ctx): + rcon_connections = json.loads(self.bot.con.one(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + success_msg = 'Running saveworld' + if server == None: + success_msg += ' on all the servers:' + # server = server.replace('_',' ').title() + if server in rcon_connections: + connection_info = rcon_connections[server] + 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.title() in rcon_connections: + success_msg = '{0} {1}:'.format(success_msg, server.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(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + if message != 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 has more than one word in it's name it will either need to be sorrounded by double quotes or the words seperated by _''' + if checks.is_rcon_admin(self.bot, ctx): + rcon_connections = json.loads(self.bot.con.one(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + if server != None: + server = server.replace('_',' ').title() + if message != 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(f"select rcon_connections from guild_config where guild_id = {ctx.guild.id}")) + edited = 0 + category = discord.utils.get(ctx.guild.categories, name='Server Chats') + if category == 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(f"update guild_config set rcon_connections = '{json.dumps(rcon_connections)}' where guild_id = {ctx.guild.id}") + 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(f"select rcon_connections from guild_config where guild_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']}\n឵ **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)) diff --git a/exts/repl.py b/exts/repl.py new file mode 100644 index 0000000..3aae7e3 --- /dev/null +++ b/exts/repl.py @@ -0,0 +1,175 @@ + +from discord.ext import commands +import time +import datetime +import math +import asyncio +import traceback +import discord +import inspect +import textwrap +from contextlib import redirect_stdout +import io +import os, sys +import subprocess +import async_timeout +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() + + def cleanup_code(self, 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') + + def get_syntax_error(self, 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'] + try: + with redirect_stdout(stdout): + ret = await func() + except Exception as e: + value = stdout.getvalue() + await ctx.send('```py\n{}{}\n```'.format(value, traceback.format_exc())) + else: + value = stdout.getvalue() + try: + await ctx.message.add_reaction('✅') + except: + 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, '', 'eval') + except SyntaxError: + pass + else: + executor = eval + if executor is exec: + try: + code = compile(cleaned, '', 'exec') + except SyntaxError as e: + await response.channel.send(self.get_syntax_error(e)) + continue + variables['message'] = response + fmt = None + stdout = io.StringIO() + try: + with redirect_stdout(stdout): + result = executor(code, variables) + if inspect.isawaitable(result): + result = await result + except Exception as e: + 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)) diff --git a/exts/utils.py b/exts/utils.py new file mode 100644 index 0000000..37a8024 --- /dev/null +++ b/exts/utils.py @@ -0,0 +1,445 @@ +import discord +from discord.ext import commands +import json +from srcds import rcon as rcon_con +import time, logging, math, psutil +from datetime import datetime, timedelta +import asyncio, inspect +import aiohttp, async_timeout +from bs4 import BeautifulSoup as bs +import traceback +from .imports import checks +from .imports.utils import paginate +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\nCPU Percentages: {psutil.cpu_percent(percpu=True)}\nMemory Usage: {psutil.virtual_memory().percent}%\nDisc Usage: {psutil.disk_usage("/").percent}%```') + + @commands.command(hidden=True) + async def role(self, ctx, role): + if ctx.guild.id == 396156980974059531 and role != 'Admin' and role != 'Admin Geeks': + try: + role = discord.utils.get(ctx.guild.roles, name=role) + except: + await ctx.send('Unknown Role') + else: + if role != 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}**') + + def create_date_string(self, 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'} {diff.seconds // 3600} {'hour' if diff.seconds // 3600 == 1 else 'hours'} {diff.seconds % 3600 // 60} {'minute' if diff.seconds % 3600 // 60 == 1 else 'minutes'} {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} {"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. + 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} {"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**\nDiscord Latency: **{math.ceil(self.bot.latency*1000)}ms**' + await msg.edit(embed=em) + + if mode == 'comp': + try: + count = int(count) + except: + 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: {datetime.strftime(self.bot.ping_times[i]['snd'].created_at, '%H:%M:%S.%f')}\nResponse Received: {datetime.strftime(now, '%H:%M:%S.%f')}\nTotal 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 != 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 guild_id = {ctx.guild.id}') + request_id = self.bot.con.one(f'select id from admin_requests where 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}\n{ctx.author.mention} has requested assistance:\n```{request_msg}```\nRequested 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 == None: + requests = self.bot.con.all(f'select * from admin_requests where guild_orig = %(guild_id)s 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{request[0]:^12}{member.display_name if member else 'None':^20}{admin.display_name if admin else 'None':^20}" + em.add_field(name='￲', value=f"```{title}\n\n{request[4]}\n\nRequested 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 asigned 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: {member.display_name if member else 'None'}", value=f"{request[4]}\nRequested 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} 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]}{' Assigned to: ' + admin.display_name if admin else ''}", value=f"{request[4]}\nRequested 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 `', 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: + 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] == 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 {ctx.guild.get_member(request[1]).display_name} 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(f'select prefix from guild_config where guild_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='012214819999548252842:tarnolfyw44').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('128GnQPOx0u7oPGvu5nAJks_Qv2R0Ru9ILAniICoxhJ8') + 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)) diff --git a/geeksbot.py b/geeksbot.py new file mode 100644 index 0000000..e3dad97 --- /dev/null +++ b/geeksbot.py @@ -0,0 +1,146 @@ +import discord +from discord.ext import commands +import logging +from datetime import datetime +import json, asyncio +from srcds import rcon as rcon_con +import re, aiohttp, async_timeout +from bs4 import BeautifulSoup as bs +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 = { + 'x': '❌', + 'y': '✅', +} + + + +description = 'I am Geeksbot! Fear me!' + +class Geeksbot(commands.Bot): + def __init__(self, **kwargs): + kwargs["command_prefix"] = self.command_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 = son.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='AIzaSyAfGHj5alDWMsnVMeGUD53dI0RQij94PU4') + + async def command_prefix(self, bot, message): + return self.con.one(f'select prefix from guild_config where guild_id = {message.guild.id}') or self.default_prefix + + async def load_ext(self, ctx, mod): + bot.load_extension('{0}.{1}'.format(extension_dir,mod)) + if ctx != None: + await ctx.send('{0} loaded.'.format(mod)) + + async def unload_ext(self, ctx, mod): + bot.unload_extension('{0}.{1}'.format(extension_dir,mod)) + if ctx != 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): + '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 = {ctx.guild.id}")): + if ctx.channel.id in json.loads(bot.con.one(f"select allowed_channels from guild_config where guild_id = {ctx.guild.id}")): + await bot.process_commands(ctx) + elif ctx.channel.id == 418452585683484680: + prefix = bot.con.one(f'select prefix from guild_config where guild_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) diff --git a/geeksbot_launcher.sh b/geeksbot_launcher.sh new file mode 100644 index 0000000..7b28f8d --- /dev/null +++ b/geeksbot_launcher.sh @@ -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 diff --git a/misc.py b/misc.py new file mode 100644 index 0000000..bf16706 --- /dev/null +++ b/misc.py @@ -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() \ No newline at end of file diff --git a/sftp-config.json b/sftp-config.json new file mode 100644 index 0000000..4edbc9d --- /dev/null +++ b/sftp-config.json @@ -0,0 +1,45 @@ +{ + // The tab key will cycle through the settings when first created + // Visit http://wbond.net/sublime_packages/sftp/settings for help + + // sftp, ftp or ftps + "type": "sftp", + + "save_before_upload": true, + "upload_on_save": true, + "sync_down_on_open": true, + "sync_skip_deletes": false, + "sync_same_age": true, + "confirm_downloads": false, + "confirm_sync": false, + "confirm_overwrite_newer": false, + + "host": "10.10.0.70", + "user": "dusty", + "password": "SunFeb07", + "port": "22", + + "remote_path": "/home/dusty/bin/geeksbot/", + "ignore_regexes": [ + "\\.sublime-(project|workspace)", "sftp-config(-alt\\d?)?\\.json", + "sftp-settings\\.json", "/venv/", "\\.svn/", "\\.hg/", "\\.git/", + "\\.bzr", "_darcs", "CVS", "\\.DS_Store", "Thumbs\\.db", "desktop\\.ini", "\\.log" + ], + //"file_permissions": "664", + //"dir_permissions": "775", + + //"extra_list_connections": 0, + + "connect_timeout": 30, + //"keepalive": 120, + //"ftp_passive_mode": true, + //"ftp_obey_passive_host": false, + //"ssh_key_file": "~/.ssh/id_rsa", + //"sftp_flags": ["-F", "/path/to/ssh_config"], + + //"preserve_modification_times": false, + //"remote_time_offset_in_hours": 0, + //"remote_encoding": "utf-8", + //"remote_locale": "C", + //"allow_config_upload": false, +}