mirror of https://github.com/stijndcl/didier
Compare commits
615 Commits
Author | SHA1 | Date |
---|---|---|
Stijn De Clercq | d46a31a7be | |
stijndcl | 165be35f8e | |
Stijn De Clercq | 35d1d871de | |
stijndcl | cc75dd8909 | |
stijndcl | 685e4d953e | |
Stijn De Clercq | f181a7a833 | |
stijndcl | dc4ee08b63 | |
Stijn De Clercq | 17b6ebac89 | |
Stijn De Clercq | 5bfd3a92a9 | |
Stijn De Clercq | 1fc7fc94d8 | |
Stijn De Clercq | ae28f3a286 | |
Stijn De Clercq | a54fb985e7 | |
Stijn De Clercq | 3ca4520e56 | |
Stijn De Clercq | eee0a38afa | |
Stijn De Clercq | d874ec6bbd | |
Stijn De Clercq | cdfcd094a4 | |
Stijn De Clercq | feccb88cfd | |
Stijn De Clercq | d52c80aa8b | |
Stijn De Clercq | e6a615cad5 | |
Stijn De Clercq | c2a2fee50f | |
Stijn De Clercq | 9998236e03 | |
Stijn De Clercq | ee6013da5d | |
Stijn De Clercq | 4b25d5d519 | |
Stijn De Clercq | c3a7ff8e4c | |
Stijn De Clercq | 18689c3de8 | |
Stijn De Clercq | 4de765ef62 | |
Stijn De Clercq | 1af7e241e2 | |
Stijn De Clercq | c0adc55be2 | |
Stijn De Clercq | 264e7b5300 | |
Stijn De Clercq | c570cd2db2 | |
Stijn De Clercq | deba3ababa | |
stijndcl | 4e205a02c7 | |
stijndcl | 4548f0e003 | |
stijndcl | 1831446f65 | |
stijndcl | 5deb312474 | |
Stijn De Clercq | 1ae7790ca2 | |
Stijn De Clercq | 96db71a185 | |
Stijn De Clercq | ce7f2eafd8 | |
Stijn De Clercq | c7b0bb8606 | |
Stijn De Clercq | 73a9512339 | |
stijndcl | 617e69b7a3 | |
stijndcl | 29dcf845de | |
Stijn De Clercq | ad0076537d | |
stijndcl | ff5de8e88b | |
stijndcl | d7daeb02ff | |
stijndcl | a05d8e7138 | |
Stijn De Clercq | 5bfb1cecb2 | |
Stijn De Clercq | 3d429f21cc | |
stijndcl | 6c8ab9a2a0 | |
stijndcl | accda93461 | |
Stijn De Clercq | 3bc9c15af7 | |
Stijn De Clercq | 8f8f7fed4a | |
stijndcl | 9e9be39358 | |
Stijn De Clercq | c1721b951c | |
Stijn De Clercq | 465456f128 | |
Stijn De Clercq | 65671f271b | |
stijndcl | 4c4b7ec0cf | |
stijndcl | 8707f467d5 | |
stijndcl | d374f1e8ab | |
Stijn De Clercq | 87e5b1be9f | |
stijndcl | 5505ce64c8 | |
Stijn De Clercq | 21aa118ffa | |
Stijn De Clercq | 78bb9ee66d | |
stijndcl | 462a8ffd3d | |
Stijn De Clercq | 9a64523baa | |
stijndcl | 405545a9b0 | |
Stijn De Clercq | 969c66e2bf | |
stijndcl | 560830b0d8 | |
Stijn De Clercq | 69aa50ef44 | |
stijndcl | 65785fef8f | |
Stijn De Clercq | 1c249adb46 | |
stijndcl | b5c97459f9 | |
stijndcl | 3509bd81e4 | |
stijndcl | 5c0ebb7eeb | |
stijndcl | 855f60727b | |
stijndcl | deefeb1106 | |
Stijn De Clercq | 625429daff | |
stijndcl | 8ae9b5f77c | |
Stijn De Clercq | 41b5efd12d | |
stijndcl | 4160693067 | |
stijndcl | 6c959e2b86 | |
stijndcl | 09dbe61dfe | |
stijndcl | d190185c12 | |
stijndcl | 27accf61b7 | |
stijndcl | 950b7330d0 | |
stijndcl | 3f8778b581 | |
stijndcl | a0c2cee857 | |
stijndcl | 654dcb228d | |
stijndcl | d5db08e6ac | |
stijndcl | 105cee7e6e | |
stijndcl | 5528ce7c2e | |
stijndcl | 60181aadea | |
stijndcl | fa129efd0c | |
stijndcl | 5f72701714 | |
stijndcl | 89b345c61b | |
stijndcl | ce7ba4285a | |
stijndcl | 3a44a1b679 | |
Stijn De Clercq | ca38d06d98 | |
stijndcl | 1df345838d | |
Stijn De Clercq | 98b18f7ee3 | |
stijndcl | 3167c99645 | |
stijndcl | dd93080199 | |
stijndcl | eea44fcfa5 | |
stijndcl | 7e2b7b97ff | |
stijndcl | c79c478ab4 | |
stijndcl | 225cc8129e | |
stijndcl | 961c125648 | |
stijndcl | 773491e2ff | |
stijndcl | bf32a5ef47 | |
Stijn De Clercq | 8922489a41 | |
stijndcl | 3e495d8291 | |
stijndcl | 0a9f73af8c | |
stijndcl | 185aaadce1 | |
stijndcl | ddd632ffd5 | |
stijndcl | d03ece6f58 | |
stijndcl | 00a146cb2b | |
stijndcl | 6cfe788df5 | |
stijndcl | f049f1c80b | |
stijndcl | f19a832725 | |
stijndcl | d9272f17ab | |
stijndcl | 7f21a1cf69 | |
Stijn De Clercq | dc1f0f6b55 | |
stijndcl | 2d0babbdcb | |
stijndcl | df884f55f1 | |
stijndcl | abbb3026eb | |
stijndcl | 72415aeed0 | |
stijndcl | 87caeec47b | |
stijndcl | 97e815cbff | |
stijndcl | 9e3527ae8a | |
stijndcl | 41c8c9d0ab | |
stijndcl | 23edc51dbf | |
stijndcl | 7517f844d8 | |
stijndcl | 1fe04b3687 | |
stijndcl | 181118aa1d | |
stijndcl | 2638c5a3c4 | |
Stijn De Clercq | a23ee3671a | |
stijndcl | 6ef4007f13 | |
stijndcl | bef8742459 | |
stijndcl | 5511046e35 | |
stijndcl | 5d2d7c49c2 | |
stijndcl | 7035f0773f | |
stijndcl | 5cdb6c3f44 | |
stijndcl | da365e3bc1 | |
stijndcl | 65e1034372 | |
stijndcl | 9c36f59e04 | |
stijndcl | c5317b5d27 | |
Stijn De Clercq | bf28611ddc | |
stijndcl | 06b8c4e084 | |
stijndcl | 308c341b1a | |
stijndcl | c33ee82539 | |
stijndcl | 65201cd705 | |
stijndcl | 0262d68fc9 | |
stijndcl | 13f7d03bbb | |
stijndcl | 14ccb42424 | |
stijndcl | 8fea65e4ad | |
stijndcl | ac24688a73 | |
stijndcl | 88bbb9773f | |
Stijn De Clercq | f41e16796d | |
stijndcl | 149d132e6d | |
stijndcl | 152f84ed1c | |
stijndcl | f70736b4d5 | |
stijndcl | 12d2017cbe | |
Stijn De Clercq | 8308b4ad9a | |
stijndcl | a51da649db | |
stijndcl | 29f83c1343 | |
stijndcl | 000fa93180 | |
stijndcl | c4ef5cd619 | |
stijndcl | 73d44de46e | |
stijndcl | 8a4baf6bb8 | |
stijndcl | 7b2109fb07 | |
Stijn De Clercq | b2bc497c50 | |
stijndcl | d245b2195f | |
stijndcl | 5d400fdcac | |
stijndcl | dc2262b246 | |
stijndcl | 1752d651a9 | |
stijndcl | 994ff01de1 | |
stijndcl | 14e0472954 | |
stijndcl | ea6b204cf0 | |
stijndcl | b85f5a612a | |
stijndcl | 9225f61e47 | |
stijndcl | c31767c25f | |
stijndcl | 2de75fd168 | |
stijndcl | e1af53cf44 | |
stijndcl | b581c3e5dc | |
stijndcl | 0186a0793a | |
stijndcl | 654fbcd46b | |
stijndcl | 6f0ac487cc | |
stijndcl | 8a42e24c34 | |
Stijn De Clercq | 773c7ac4d1 | |
stijndcl | 966eb63165 | |
stijndcl | f9083e84ed | |
stijndcl | a0c1b986cd | |
stijndcl | d1d10ee853 | |
stijndcl | 8fb990cea8 | |
stijndcl | 7d7ab98254 | |
stijndcl | dbb570420b | |
stijndcl | 86dd6cb27b | |
stijndcl | 2f40903579 | |
stijndcl | b26421b875 | |
stijndcl | e2959c27ad | |
stijndcl | 107e4fb580 | |
Stijn De Clercq | a510e2fe4a | |
stijndcl | 28cf094ea3 | |
stijndcl | a614e9a9f1 | |
stijndcl | 94de47082b | |
Stijn De Clercq | 8aedde46de | |
stijndcl | e36322b4a7 | |
stijndcl | 8dbf68cac0 | |
stijndcl | b74f794639 | |
stijndcl | bf41acd9f4 | |
stijndcl | e4e77502e8 | |
stijndcl | a0781a046b | |
stijndcl | c4c9461ca3 | |
stijndcl | d4dae7826d | |
stijndcl | 5f9a57cd83 | |
stijndcl | bdaf8a1dc5 | |
stijndcl | 4a137bcad8 | |
stijndcl | db499f3742 | |
stijndcl | ea4181eac0 | |
stijndcl | cbd3030565 | |
Stijn De Clercq | 2f4c2c347f | |
stijndcl | 9da0bc2c5a | |
stijndcl | e6b4c3fd76 | |
stijndcl | 6bebd109bb | |
stijndcl | 52b452c85a | |
stijndcl | 9abe5dd519 | |
stijndcl | 665d677941 | |
stijndcl | b54aed24e8 | |
stijndcl | 2e3b4823d0 | |
stijndcl | 424399b88a | |
stijndcl | edc6343e12 | |
stijndcl | 0834a4ccbc | |
stijndcl | da0e60ac4f | |
stijndcl | 393cc9c891 | |
stijndcl | 66997b7556 | |
stijndcl | 8bc0f1fa7a | |
stijndcl | adcf94c66e | |
stijndcl | f49f32d2e9 | |
Stijn De Clercq | 016d87bcea | |
stijndcl | bb903fdad5 | |
stijndcl | e371e2cc5c | |
Stijn De Clercq | 3057222607 | |
stijndcl | 8bd4495016 | |
Stijn De Clercq | 6c225bacc1 | |
stijndcl | 9401111bee | |
stijndcl | f4056d8af6 | |
stijndcl | c9dd275860 | |
stijndcl | 0c810d84e9 | |
stijndcl | 1aeaa71ef8 | |
stijndcl | 8227190a8d | |
stijndcl | 3debd18d82 | |
stijndcl | 3d0f771f94 | |
stijndcl | 5b47397f29 | |
stijndcl | 72c3acbcc2 | |
stijndcl | f0a05c8b4d | |
stijndcl | 84bf1d7a26 | |
stijndcl | c8392342a6 | |
stijndcl | b9c5c6ab10 | |
stijndcl | 8d6dbe1c94 | |
stijndcl | 04e2889d93 | |
stijndcl | 81c882315a | |
stijndcl | dd66087193 | |
stijndcl | 61128dda92 | |
stijndcl | 8da0eb0b2a | |
stijndcl | fff35c6c44 | |
stijndcl | ba86d4a6f2 | |
stijndcl | 5b510d1f45 | |
Stijn De Clercq | cb0b4a419e | |
Stijn De Clercq | c7210152c6 | |
stijndcl | f33aed0ceb | |
stijndcl | ef493bb8d2 | |
stijndcl | c294bc8da5 | |
stijndcl | 96916d2abd | |
stijndcl | fd72bb1774 | |
stijndcl | bd63f80a7d | |
stijndcl | bec893bd20 | |
stijndcl | 032b636b02 | |
stijndcl | 4587a49311 | |
stijndcl | 9552c38a70 | |
Stijn De Clercq | 76f1ba3543 | |
stijndcl | c95b7ed58f | |
stijndcl | 27d074d760 | |
stijndcl | 9d04d62b1c | |
stijndcl | 60382b8eab | |
stijndcl | b4a3a87e6e | |
stijndcl | 5c1732d119 | |
stijndcl | 5f2e26f154 | |
stijndcl | ca9dd84ab5 | |
stijndcl | d6a560851b | |
stijndcl | 257eae6fa7 | |
stijndcl | ee03cf7d8c | |
stijndcl | 36909f04bd | |
stijndcl | 85a7750d09 | |
stijndcl | 6b91e792e6 | |
stijndcl | cc8f8b1ee4 | |
stijndcl | 3ce823f209 | |
stijndcl | add9399944 | |
stijndcl | 57e805e31c | |
stijndcl | fc195e40b3 | |
stijndcl | d8192cfa0a | |
stijndcl | efdc966611 | |
stijndcl | fd57b5a79b | |
stijndcl | 53f58eb743 | |
stijndcl | 5c2c62c6c4 | |
stijndcl | 868cd392c3 | |
stijndcl | 5a76cbd2ec | |
stijndcl | 000337107b | |
stijndcl | d75831f848 | |
stijndcl | d7262595c6 | |
stijndcl | 6873cab955 | |
stijndcl | bacd2d77fb | |
stijndcl | eb71470edc | |
stijndcl | b23160b8e2 | |
stijndcl | eb182b71f4 | |
stijndcl | 53a3e0e75a | |
stijndcl | 21aeb80c13 | |
stijndcl | 9193e73af9 | |
stijndcl | 00e805d535 | |
stijndcl | 6d61056dc4 | |
stijndcl | a1449a4c9c | |
stijndcl | 3d1aabf77c | |
stijndcl | de0d543bf8 | |
stijndcl | 304ad850b7 | |
stijndcl | e09ec5c946 | |
stijndcl | 37dd5ba3e8 | |
stijndcl | 9518bbe168 | |
stijndcl | 3a35718d84 | |
stijndcl | 0701fbfddb | |
stijndcl | 00481e42eb | |
Stijn De Clercq | 2ce32ee8a3 | |
Stijn De Clercq | 90dec2f5e4 | |
Stijn De Clercq | d64e08671b | |
Stijn De Clercq | 06af3ada48 | |
Stijn De Clercq | b3ce348193 | |
Stijn De Clercq | 09d282db19 | |
Stijn De Clercq | f56cc40c41 | |
Stijn De Clercq | 002665910a | |
Stijn De Clercq | 0c1b6f335c | |
Stijn De Clercq | eb3a3fd7fa | |
stijndcl | 97d533e04f | |
stijndcl | 8e050f9ab3 | |
stijndcl | 0151913e61 | |
stijndcl | b156f90ea0 | |
Stijn De Clercq | 82671896b6 | |
Stijn De Clercq | 868869beff | |
Stijn De Clercq | 98dddf992e | |
Stijn De Clercq | ffa594de75 | |
stijndcl | a2ab822803 | |
stijndcl | 4b003e150b | |
stijndcl | 9341554040 | |
stijndcl | 0f973efe29 | |
stijndcl | 3f2f9cd8f3 | |
Stijn De Clercq | 635560dcd0 | |
stijndcl | 982a4bb056 | |
stijndcl | c43710a429 | |
stijndcl | d18860cae0 | |
Stijn De Clercq | 2c8c20e9f2 | |
Stijn De Clercq | 59fd59308d | |
Stijn De Clercq | 9fc6c783ab | |
Stijn De Clercq | 51f1e44537 | |
Stijn De Clercq | b52d5a263b | |
Stijn De Clercq | 2a0d7d45aa | |
Stijn De Clercq | d2b0e630ec | |
Stijn De Clercq | e97b4fa388 | |
Stijn De Clercq | e6e811fc23 | |
Stijn De Clercq | 959ca627da | |
Stijn De Clercq | 5cccf81c59 | |
Stijn De Clercq | 226570d5d0 | |
Stijn De Clercq | 7ff133a045 | |
Stijn De Clercq | 543b5199b6 | |
Stijn De Clercq | 585134a3fb | |
Stijn De Clercq | 781956b312 | |
Stijn De Clercq | eafc1a8674 | |
Stijn De Clercq | b1fdd22058 | |
Stijn De Clercq | 0165700d9f | |
Stijn De Clercq | 9819e82638 | |
Stijn De Clercq | c6958d22f3 | |
Stijn De Clercq | 6d7b47fee0 | |
Stijn De Clercq | e78e13e26a | |
Stijn De Clercq | a734191973 | |
Stijn De Clercq | 9a999fb34b | |
Stijn De Clercq | 93ede132a2 | |
Stijn De Clercq | a71232e292 | |
Stijn De Clercq | 81a0d90a12 | |
Stijn De Clercq | ca687956f6 | |
Stijn De Clercq | 062d54722b | |
Stijn De Clercq | 7ad2bf351e | |
Stijn De Clercq | 06dc3d3fb9 | |
Stijn De Clercq | 829729c8db | |
Stijn De Clercq | eaed08168c | |
Stijn De Clercq | 9eb0eb5a61 | |
Stijn De Clercq | bca4fbf616 | |
Stijn De Clercq | 17964a23fb | |
Stijn De Clercq | 853b708ece | |
Stijn De Clercq | 3444414638 | |
Stijn De Clercq | e3a788f6c9 | |
Stijn De Clercq | 8b99835f81 | |
Stijn De Clercq | bf272f17c4 | |
Stijn De Clercq | 3c5221f32e | |
Stijn De Clercq | 16a4ba0e83 | |
Stijn De Clercq | ea18dea411 | |
Stijn De Clercq | e130bf4a25 | |
Stijn De Clercq | f5e62a0fb9 | |
Stijn De Clercq | c154a4bb2a | |
Stijn De Clercq | 20eb37cf6c | |
Stijn De Clercq | 8b8aca1b2c | |
Stijn De Clercq | b1d8747a8d | |
Stijn De Clercq | 018268ee1f | |
Stijn De Clercq | 046b67a36e | |
Stijn De Clercq | 8350b01d85 | |
Stijn De Clercq | dd60840576 | |
Stijn De Clercq | 81bdde9632 | |
Stijn De Clercq | 13df55ac53 | |
Stijn De Clercq | c84cb3f944 | |
Stijn De Clercq | fccf4efa1f | |
Stijn De Clercq | 693fab7833 | |
Stijn De Clercq | 7bac54599a | |
Stijn De Clercq | 9a2313d376 | |
Stijn De Clercq | f052885ae0 | |
Stijn De Clercq | 3c52d681b3 | |
Stijn De Clercq | 9f6b36e4e8 | |
Stijn De Clercq | 570c1e4d1b | |
Stijn De Clercq | 96cf11dcaf | |
Stijn De Clercq | b26eeb3fc8 | |
Stijn De Clercq | 89a5f50a1d | |
Stijn De Clercq | 0f6a52c75d | |
Stijn De Clercq | 49c5110f8f | |
Stijn De Clercq | 5f51acd1ff | |
Stijn De Clercq | 6fb072ef58 | |
Maarten Steevens | b36c6e57fb | |
Stijn De Clercq | 287e2fd233 | |
Stijn De Clercq | 2a8500e870 | |
Stijn De Clercq | 73cc6aa0e9 | |
Stijn De Clercq | aae5616488 | |
Stijn De Clercq | f842045511 | |
Stijn De Clercq | 04d03fa2cb | |
Stijn De Clercq | 1ee9900891 | |
Stijn De Clercq | 7ab72aabfd | |
Stijn De Clercq | 11065637eb | |
Stijn De Clercq | 298a1e13d6 | |
Stijn De Clercq | 90dff3aa83 | |
Stijn De Clercq | 43bce8d8fb | |
Stijn De Clercq | a48f15d464 | |
Stijn De Clercq | 75adf849f0 | |
Stijn De Clercq | 51811dd9f9 | |
Stijn De Clercq | ad1175f6cd | |
Stijn De Clercq | 1c82bf959b | |
Stijn De Clercq | 5e946ed5d3 | |
Stijn De Clercq | 2f5e5147dc | |
Stijn De Clercq | 768f43ade9 | |
Stijn De Clercq | 740ec40ace | |
Stijn De Clercq | 2e51af6f1c | |
Stijn De Clercq | 0ec321a51b | |
Stijn De Clercq | a28bd116f0 | |
Stijn De Clercq | ef547a7090 | |
Stijn De Clercq | 358f8693dd | |
Stijn De Clercq | c47f908e57 | |
Stijn De Clercq | 831459a321 | |
Stijn De Clercq | ed0649c953 | |
Stijn De Clercq | 537111d357 | |
Stijn De Clercq | eb6fc6513c | |
Stijn De Clercq | 968df71f98 | |
Stijn De Clercq | b083cfe0bf | |
Stijn De Clercq | b910018ddc | |
Stijn De Clercq | bf9b9c23b0 | |
Stijn De Clercq | daae31a298 | |
Stijn De Clercq | cbe1cf747f | |
Stijn De Clercq | cdb8a04cde | |
Stijn De Clercq | 3b1426b048 | |
Stijn De Clercq | 9561b98f98 | |
Stijn De Clercq | 1d0782bdc8 | |
Stijn De Clercq | 6ddaa6e488 | |
Stijn De Clercq | 02ea90ec6a | |
Stijn De Clercq | 49aaa76aff | |
Stijn De Clercq | e07a2c28d1 | |
Stijn De Clercq | 500da97b8b | |
Stijn De Clercq | 153aa04490 | |
Stijn De Clercq | 41a1527e72 | |
Stijn De Clercq | 07e48e4046 | |
Stijn De Clercq | aa1e28937c | |
Stijn De Clercq | a97a35a13b | |
Stijn De Clercq | 76df656128 | |
Stijn De Clercq | 445ca84834 | |
Stijn De Clercq | 85f29e7afa | |
Stijn De Clercq | e9ea063876 | |
Stijn De Clercq | a198a83153 | |
Stijn De Clercq | 8c6d3682b4 | |
Stijn De Clercq | 9cebb8280e | |
Stijn De Clercq | e8301ce8a2 | |
Stijn De Clercq | b3854324d4 | |
Stijn De Clercq | 1857bdefe9 | |
Stijn De Clercq | 54d31c943a | |
Stijn De Clercq | ee3ee5284d | |
Stijn De Clercq | 49870d23eb | |
Stijn De Clercq | ec30228929 | |
Stijn De Clercq | cec78f0d5f | |
Stijn De Clercq | bd3c3d0745 | |
Stijn De Clercq | 9ce4b0947c | |
Stijn De Clercq | b18c15a380 | |
Stijn De Clercq | 02bd10adcb | |
Stijn De Clercq | ab6819d963 | |
Stijn De Clercq | 09cad58f43 | |
Stijn De Clercq | ecb7de062b | |
Stijn De Clercq | 01266b1346 | |
Stijn De Clercq | ce1e2e7272 | |
Stijn De Clercq | f2b4abd4d2 | |
Stijn De Clercq | 92a82a7175 | |
Stijn De Clercq | ca17a7333b | |
Stijn De Clercq | 7bbe4db26d | |
Stijn De Clercq | 7847cc1d29 | |
Stijn De Clercq | a310d1696c | |
Stijn De Clercq | 4b6743a51f | |
Stijn De Clercq | 543eb38417 | |
Stijn De Clercq | 26b356b08b | |
Stijn De Clercq | 7e5c4031c0 | |
Stijn De Clercq | 418dc41126 | |
Stijn De Clercq | a08bfca4c7 | |
Stijn De Clercq | cedb284adc | |
Stijn De Clercq | b37755f512 | |
Stijn De Clercq | 34fe8a0feb | |
Stijn De Clercq | e56de111eb | |
Stijn De Clercq | 119d7c8dca | |
Stijn De Clercq | 97f6aa105d | |
Stijn De Clercq | 2e21c25361 | |
Stijn De Clercq | 92a5e6454d | |
Stijn De Clercq | 0439b634d9 | |
Stijn De Clercq | 966b378e09 | |
Stijn De Clercq | 3f5eb6a595 | |
Stijn De Clercq | 10da82d211 | |
Stijn De Clercq | a06fcdefe0 | |
Stijn De Clercq | 92048bcd85 | |
Stijn De Clercq | 3cd2456bbf | |
Stijn De Clercq | 4bcd00826e | |
Stijn De Clercq | 3cfc87b7e1 | |
Stijn De Clercq | f6d06eb489 | |
Stijn De Clercq | dc7e982491 | |
Stijn De Clercq | 515a4d1105 | |
Stijn De Clercq | d39b7f8868 | |
Stijn De Clercq | b473ff6e3d | |
Stijn De Clercq | 1ca508f623 | |
Stijn De Clercq | 245a900c87 | |
Stijn De Clercq | d87af32f6d | |
Stijn De Clercq | 20ad83ba23 | |
Stijn De Clercq | baa3478cec | |
Stijn De Clercq | f76d70c169 | |
Stijn De Clercq | 5703ee6fcf | |
Stijn De Clercq | 8e51364268 | |
Stijn De Clercq | d240c3c50f | |
Stijn De Clercq | bc6a0d300c | |
Stijn De Clercq | 7118a80d5d | |
Stijn De Clercq | e3c0d2b444 | |
Jozef Jankaj | 9bb7c542cf | |
Stijn De Clercq | 05e3b0758a | |
Stijn De Clercq | 56a2880b31 | |
Stijn De Clercq | f655748025 | |
Stijn De Clercq | c1169b4496 | |
Stijn De Clercq | 7f465fddae | |
Stijn De Clercq | 1979703194 | |
Stijn De Clercq | 4477ec6c0b | |
Stijn De Clercq | 2bc949de83 | |
Stijn De Clercq | 6c0f9fd017 | |
Stijn De Clercq | 2c634912ef | |
Stijn De Clercq | 981f7a1457 | |
Stijn De Clercq | db0e7b8f9b | |
Stijn De Clercq | 1372f0a996 | |
Stijn De Clercq | 268bb80bfd | |
Stijn De Clercq | c9bef3b300 | |
Stijn De Clercq | 779a84828b | |
Stijn De Clercq | ade7f8b72e | |
Stijn De Clercq | c374fce3f3 | |
Stijn De Clercq | 6568cfdcce | |
Stijn De Clercq | 66ec275188 | |
Stijn De Clercq | fcfb80e53a | |
Stijn De Clercq | 3f8f34aa9f | |
Stijn De Clercq | 6a671a4768 | |
Stijn De Clercq | 6c4c8079c5 | |
Stijn De Clercq | 84b43ade6a | |
Stijn De Clercq | 77573eccc3 | |
Stijn De Clercq | fe843ad4f0 | |
Stijn De Clercq | 415ac2eeb3 | |
Stijn De Clercq | 01a397add4 | |
Stijn De Clercq | a2b7602680 | |
Stijn De Clercq | b971f661b4 | |
Stijn De Clercq | a9c8d31ceb | |
Stijn De Clercq | 78a015e30d | |
Stijn De Clercq | a63642c21f | |
Stijn De Clercq | 6c08a84bd0 | |
Stijn De Clercq | 09f61237a2 | |
Stijn De Clercq | 9fb4cc11f2 | |
Stijn De Clercq | 028a4e5e90 | |
Stijn De Clercq | f2897dbac7 | |
Stijn De Clercq | 4614145df6 | |
Stijn De Clercq | 64eaee4f5d | |
Stijn De Clercq | 0c83472287 | |
Stijn De Clercq | afca7bb451 | |
Stijn De Clercq | 01b1c5d2b3 | |
Stijn De Clercq | d13a5f77d8 | |
Stijn De Clercq | e611cc1285 | |
Stijn De Clercq | 2593c3ba0d | |
Stijn De Clercq | 706ea1fb35 | |
Stijn De Clercq | 469d591a29 | |
Stijn De Clercq | e847b0e4b3 | |
Stijn De Clercq | 9df8126455 | |
Stijn De Clercq | 2bfd507bce | |
Stijn De Clercq | 9898263a73 | |
Stijn De Clercq | 342db2df38 | |
Stijn De Clercq | 7fe2689db1 | |
Stijn De Clercq | 89fdc70813 | |
Stijn De Clercq | 7c47c6af73 | |
Stijn De Clercq | 77addbc30c | |
Stijn De Clercq | f2b62c3ce7 | |
Stijn De Clercq | 8238bdf6de | |
Stijn De Clercq | 2b96f3ec41 | |
Stijn De Clercq | d9d8c6a842 | |
Stijn De Clercq | 25f4ac5314 |
|
@ -3,4 +3,4 @@ root = true
|
|||
[{*.yml, *.yaml}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
max_line_length=80
|
||||
max_line_length=120
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
[flake8]
|
||||
# Don't lint non-Python files
|
||||
exclude =
|
||||
.git,
|
||||
.github,
|
||||
.mypy_cache,
|
||||
.pytest_cache,
|
||||
__pycache__,
|
||||
alembic,
|
||||
htmlcov,
|
||||
venv
|
||||
# Disable rules that we don't care about (or conflict with others)
|
||||
extend-ignore =
|
||||
# Missing docstring in public module
|
||||
D100, D104,
|
||||
# Missing docstring in magic method
|
||||
D105,
|
||||
# Missing docstring in __init__
|
||||
D107,
|
||||
# First line of docstrings should end with a period
|
||||
D400,
|
||||
# First line of docstrings should be in imperative mood
|
||||
D401,
|
||||
# Whitespace before ":"
|
||||
E203,
|
||||
# Standard pseudo-random generators are not suitable for security/cryptographic purposes.
|
||||
S311,
|
||||
# Don't require docstrings when overriding a method,
|
||||
# the base method should have a docstring but the rest not
|
||||
ignore-decorators=overrides
|
||||
max-line-length = 120
|
||||
# Disable some rules for entire files
|
||||
per-file-ignores =
|
||||
# DALL000: Missing __all__, main isn't supposed to be imported
|
||||
main.py: DALL000, run_db_scripts.py: DALL000,
|
||||
# DALL000: Missing __all__, Cogs aren't modules
|
||||
./didier/cogs/*: DALL000,
|
||||
# DALL000: Missing __all__, tests aren't supposed to be imported
|
||||
# S101: Use of assert, this is the point of tests
|
||||
./tests/*: DALL000 S101,
|
||||
# D103: Missing docstring in public function
|
||||
# All of the colours methods are just oneliners to create a colour,
|
||||
# there's no point adding docstrings (function names are enough)
|
||||
./didier/utils/discord/colours.py: D103,
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
assignees: stijndcl
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to reproduce**
|
||||
Indicate how others can reproduce the issue.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] "
|
||||
labels: enhancement
|
||||
assignees: stijndcl
|
||||
|
||||
---
|
||||
|
||||
**Provide a clear description of the feature you'd like to request.**
|
|
@ -0,0 +1,123 @@
|
|||
name: Python CI
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
dependencies:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9.5'
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements-dev.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install dependencies
|
||||
run: pip3 install -r requirements.txt -r requirements-dev.txt
|
||||
tests:
|
||||
needs: [dependencies]
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5433:5432
|
||||
env:
|
||||
POSTGRES_DB: didier_pytest
|
||||
POSTGRES_USER: pytest
|
||||
POSTGRES_PASSWORD: pytest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9.5'
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements-dev.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install dependencies
|
||||
run: pip3 install -r requirements.txt -r requirements-dev.txt
|
||||
- name: Run Pytest
|
||||
run: |
|
||||
coverage run -m pytest
|
||||
coverage xml
|
||||
- name: Upload coverage report to CodeCov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV }}
|
||||
linting:
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9.5'
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements-dev.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install dependencies
|
||||
run: pip3 install -r requirements.txt -r requirements-dev.txt
|
||||
- name: Linting
|
||||
run: flake8
|
||||
typing:
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9.5'
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements-dev.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install dependencies
|
||||
run: pip3 install -r requirements.txt -r requirements-dev.txt
|
||||
- name: Typing
|
||||
run: mypy
|
||||
formatting:
|
||||
needs: [tests]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9.5'
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/requirements-dev.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install dependencies
|
||||
run: pip3 install -r requirements.txt -r requirements-dev.txt
|
||||
- name: Formatting
|
||||
run: black --check didier database
|
|
@ -1,13 +1,162 @@
|
|||
files/status.txt
|
||||
files/readyMessage.txt
|
||||
files/client.txt
|
||||
files/lastTasks.json
|
||||
files/c4.json
|
||||
files/hangman.json
|
||||
files/stats.json
|
||||
files/lost.json
|
||||
files/locked.json
|
||||
files/database.json
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
__pycache__
|
||||
.env
|
||||
|
||||
# Debugging files
|
||||
debug.py
|
||||
|
||||
# Schedule .ics files
|
||||
/files/schedules/
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
default_language_version:
|
||||
python: python3.9.5
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-json
|
||||
- id: end-of-file-fixer
|
||||
- id: pretty-format-json
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.2.0
|
||||
hooks:
|
||||
- id: autoflake
|
||||
name: autoflake (python)
|
||||
args:
|
||||
- "--remove-all-unused-imports"
|
||||
- "--in-place"
|
||||
- "--ignore-init-module-imports"
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
exclude: ^(alembic|.github)
|
||||
args: [--config, .flake8]
|
||||
additional_dependencies:
|
||||
- "flake8-bandit"
|
||||
- "flake8-bugbear"
|
||||
- "flake8-docstrings"
|
||||
- "flake8-dunder-all"
|
||||
- "flake8-eradicate"
|
||||
- "flake8-isort"
|
||||
- "flake8-simplify"
|
|
@ -1 +1 @@
|
|||
3.6.9
|
||||
3.9.5
|
|
@ -0,0 +1 @@
|
|||
python 3.9.5
|
|
@ -0,0 +1,105 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = alembic
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||
# for all available tokens
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to alembic/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
|
@ -0,0 +1 @@
|
|||
Generic single-database configuration.
|
|
@ -0,0 +1,51 @@
|
|||
import asyncio
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||
|
||||
from alembic import context
|
||||
from database.engine import postgres_engine
|
||||
from database.schemas import Base
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def do_run_migrations(connection):
|
||||
context.configure(connection=connection, target_metadata=target_metadata, render_as_batch=True)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations(connectable: AsyncEngine):
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = context.config.attributes.get("connection", None) or postgres_engine
|
||||
|
||||
if isinstance(connectable, AsyncEngine):
|
||||
asyncio.run(run_async_migrations(connectable))
|
||||
else:
|
||||
do_run_migrations(connectable)
|
||||
|
||||
|
||||
run_migrations_online()
|
|
@ -0,0 +1,24 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
|
@ -0,0 +1,94 @@
|
|||
"""Migrate to 2.x
|
||||
|
||||
Revision ID: 09128b6e34dd
|
||||
Revises: 1e3e7f4192c4
|
||||
Create Date: 2023-07-07 16:23:15.990231
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "09128b6e34dd"
|
||||
down_revision = "1e3e7f4192c4"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("bank", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("birthdays", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("bookmarks", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("command_stats", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("custom_command_aliases", schema=None) as batch_op:
|
||||
batch_op.alter_column("command_id", existing_type=sa.INTEGER(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("deadlines", schema=None) as batch_op:
|
||||
batch_op.alter_column("course_id", existing_type=sa.INTEGER(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("github_links", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("nightly_data", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("reminders", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("ufora_announcements", schema=None) as batch_op:
|
||||
batch_op.alter_column("course_id", existing_type=sa.INTEGER(), nullable=False)
|
||||
batch_op.alter_column("publication_date", existing_type=sa.DATE(), nullable=False)
|
||||
|
||||
with op.batch_alter_table("ufora_course_aliases", schema=None) as batch_op:
|
||||
batch_op.alter_column("course_id", existing_type=sa.INTEGER(), nullable=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("ufora_course_aliases", schema=None) as batch_op:
|
||||
batch_op.alter_column("course_id", existing_type=sa.INTEGER(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("ufora_announcements", schema=None) as batch_op:
|
||||
batch_op.alter_column("publication_date", existing_type=sa.DATE(), nullable=True)
|
||||
batch_op.alter_column("course_id", existing_type=sa.INTEGER(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("reminders", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("nightly_data", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("github_links", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("deadlines", schema=None) as batch_op:
|
||||
batch_op.alter_column("course_id", existing_type=sa.INTEGER(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("custom_command_aliases", schema=None) as batch_op:
|
||||
batch_op.alter_column("command_id", existing_type=sa.INTEGER(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("command_stats", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("bookmarks", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("birthdays", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
with op.batch_alter_table("bank", schema=None) as batch_op:
|
||||
batch_op.alter_column("user_id", existing_type=sa.BIGINT(), nullable=True)
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,32 @@
|
|||
"""Add second role to ufora courses
|
||||
|
||||
Revision ID: 11388e39bb90
|
||||
Revises: a64876b41af2
|
||||
Create Date: 2022-09-25 00:09:06.625622
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "11388e39bb90"
|
||||
down_revision = "a64876b41af2"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("ufora_courses", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("alternative_overarching_role_id", sa.BigInteger(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("ufora_courses", schema=None) as batch_op:
|
||||
batch_op.drop_column("alternative_overarching_role_id")
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,57 @@
|
|||
"""Remove wordle
|
||||
|
||||
Revision ID: 1e3e7f4192c4
|
||||
Revises: 954ad804f057
|
||||
Create Date: 2023-07-07 14:52:20.993687
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "1e3e7f4192c4"
|
||||
down_revision = "954ad804f057"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("wordle_word")
|
||||
op.drop_table("wordle_stats")
|
||||
op.drop_table("wordle_guesses")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"wordle_guesses",
|
||||
sa.Column("wordle_guess_id", sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column("user_id", sa.BIGINT(), autoincrement=False, nullable=True),
|
||||
sa.Column("guess", sa.TEXT(), autoincrement=False, nullable=False),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.user_id"], name="wordle_guesses_user_id_fkey"),
|
||||
sa.PrimaryKeyConstraint("wordle_guess_id", name="wordle_guesses_pkey"),
|
||||
)
|
||||
op.create_table(
|
||||
"wordle_stats",
|
||||
sa.Column("wordle_stats_id", sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column("user_id", sa.BIGINT(), autoincrement=False, nullable=True),
|
||||
sa.Column("last_win", sa.DATE(), autoincrement=False, nullable=True),
|
||||
sa.Column("games", sa.INTEGER(), server_default=sa.text("0"), autoincrement=False, nullable=False),
|
||||
sa.Column("wins", sa.INTEGER(), server_default=sa.text("0"), autoincrement=False, nullable=False),
|
||||
sa.Column("current_streak", sa.INTEGER(), server_default=sa.text("0"), autoincrement=False, nullable=False),
|
||||
sa.Column("highest_streak", sa.INTEGER(), server_default=sa.text("0"), autoincrement=False, nullable=False),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.user_id"], name="wordle_stats_user_id_fkey"),
|
||||
sa.PrimaryKeyConstraint("wordle_stats_id", name="wordle_stats_pkey"),
|
||||
)
|
||||
op.create_table(
|
||||
"wordle_word",
|
||||
sa.Column("word_id", sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column("word", sa.TEXT(), autoincrement=False, nullable=False),
|
||||
sa.Column("day", sa.DATE(), autoincrement=False, nullable=False),
|
||||
sa.PrimaryKeyConstraint("word_id", name="wordle_word_pkey"),
|
||||
sa.UniqueConstraint("day", name="wordle_word_day_key"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,246 @@
|
|||
"""Initial migration
|
||||
|
||||
Revision ID: 515dc3f52c6d
|
||||
Revises:
|
||||
Create Date: 2022-09-18 00:30:56.348634
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "515dc3f52c6d"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"custom_commands",
|
||||
sa.Column("command_id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("indexed_name", sa.Text(), nullable=False),
|
||||
sa.Column("response", sa.Text(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("command_id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
with op.batch_alter_table("custom_commands", schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f("ix_custom_commands_indexed_name"), ["indexed_name"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"dad_jokes",
|
||||
sa.Column("dad_joke_id", sa.Integer(), nullable=False),
|
||||
sa.Column("joke", sa.Text(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("dad_joke_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"links",
|
||||
sa.Column("link_id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("url", sa.Text(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("link_id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
op.create_table(
|
||||
"meme",
|
||||
sa.Column("meme_id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("template_id", sa.Integer(), nullable=False),
|
||||
sa.Column("field_count", sa.Integer(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("meme_id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
sa.UniqueConstraint("template_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"tasks",
|
||||
sa.Column("task_id", sa.Integer(), nullable=False),
|
||||
sa.Column("task", sa.Enum("BIRTHDAYS", "SCHEDULES", "UFORA_ANNOUNCEMENTS", name="tasktype"), nullable=False),
|
||||
sa.Column("previous_run", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint("task_id"),
|
||||
sa.UniqueConstraint("task"),
|
||||
)
|
||||
op.create_table(
|
||||
"ufora_courses",
|
||||
sa.Column("course_id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("code", sa.Text(), nullable=False),
|
||||
sa.Column("year", sa.Integer(), nullable=False),
|
||||
sa.Column("compulsory", sa.Boolean(), server_default="1", nullable=False),
|
||||
sa.Column("role_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("overarching_role_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("log_announcements", sa.Boolean(), server_default="0", nullable=False),
|
||||
sa.PrimaryKeyConstraint("course_id"),
|
||||
sa.UniqueConstraint("code"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
op.create_table("users", sa.Column("user_id", sa.BigInteger(), nullable=False), sa.PrimaryKeyConstraint("user_id"))
|
||||
op.create_table(
|
||||
"wordle_word",
|
||||
sa.Column("word_id", sa.Integer(), nullable=False),
|
||||
sa.Column("word", sa.Text(), nullable=False),
|
||||
sa.Column("day", sa.Date(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("word_id"),
|
||||
sa.UniqueConstraint("day"),
|
||||
)
|
||||
op.create_table(
|
||||
"bank",
|
||||
sa.Column("bank_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("dinks", sa.BigInteger(), server_default="0", nullable=False),
|
||||
sa.Column("invested", sa.BigInteger(), server_default="0", nullable=False),
|
||||
sa.Column("interest_level", sa.Integer(), server_default="1", nullable=False),
|
||||
sa.Column("capacity_level", sa.Integer(), server_default="1", nullable=False),
|
||||
sa.Column("rob_level", sa.Integer(), server_default="1", nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("bank_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"birthdays",
|
||||
sa.Column("birthday_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("birthday", sa.Date(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("birthday_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"bookmarks",
|
||||
sa.Column("bookmark_id", sa.Integer(), nullable=False),
|
||||
sa.Column("label", sa.Text(), nullable=False),
|
||||
sa.Column("jump_url", sa.Text(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("bookmark_id"),
|
||||
sa.UniqueConstraint("user_id", "label"),
|
||||
)
|
||||
op.create_table(
|
||||
"custom_command_aliases",
|
||||
sa.Column("alias_id", sa.Integer(), nullable=False),
|
||||
sa.Column("alias", sa.Text(), nullable=False),
|
||||
sa.Column("indexed_alias", sa.Text(), nullable=False),
|
||||
sa.Column("command_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["command_id"],
|
||||
["custom_commands.command_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("alias_id"),
|
||||
sa.UniqueConstraint("alias"),
|
||||
)
|
||||
with op.batch_alter_table("custom_command_aliases", schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f("ix_custom_command_aliases_indexed_alias"), ["indexed_alias"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"deadlines",
|
||||
sa.Column("deadline_id", sa.Integer(), nullable=False),
|
||||
sa.Column("course_id", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("deadline", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["course_id"],
|
||||
["ufora_courses.course_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("deadline_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"nightly_data",
|
||||
sa.Column("nightly_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("last_nightly", sa.Date(), nullable=True),
|
||||
sa.Column("count", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("nightly_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"ufora_announcements",
|
||||
sa.Column("announcement_id", sa.Integer(), nullable=False),
|
||||
sa.Column("course_id", sa.Integer(), nullable=True),
|
||||
sa.Column("publication_date", sa.Date(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["course_id"],
|
||||
["ufora_courses.course_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("announcement_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"ufora_course_aliases",
|
||||
sa.Column("alias_id", sa.Integer(), nullable=False),
|
||||
sa.Column("alias", sa.Text(), nullable=False),
|
||||
sa.Column("course_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["course_id"],
|
||||
["ufora_courses.course_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("alias_id"),
|
||||
sa.UniqueConstraint("alias"),
|
||||
)
|
||||
op.create_table(
|
||||
"wordle_guesses",
|
||||
sa.Column("wordle_guess_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("guess", sa.Text(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("wordle_guess_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"wordle_stats",
|
||||
sa.Column("wordle_stats_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("last_win", sa.Date(), nullable=True),
|
||||
sa.Column("games", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("wins", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("current_streak", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("highest_streak", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("wordle_stats_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("wordle_stats")
|
||||
op.drop_table("wordle_guesses")
|
||||
op.drop_table("ufora_course_aliases")
|
||||
op.drop_table("ufora_announcements")
|
||||
op.drop_table("nightly_data")
|
||||
op.drop_table("deadlines")
|
||||
with op.batch_alter_table("custom_command_aliases", schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f("ix_custom_command_aliases_indexed_alias"))
|
||||
|
||||
op.drop_table("custom_command_aliases")
|
||||
op.drop_table("bookmarks")
|
||||
op.drop_table("birthdays")
|
||||
op.drop_table("bank")
|
||||
op.drop_table("wordle_word")
|
||||
op.drop_table("users")
|
||||
op.drop_table("ufora_courses")
|
||||
op.drop_table("tasks")
|
||||
op.drop_table("meme")
|
||||
op.drop_table("links")
|
||||
op.drop_table("dad_jokes")
|
||||
with op.batch_alter_table("custom_commands", schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f("ix_custom_commands_indexed_name"))
|
||||
|
||||
op.drop_table("custom_commands")
|
||||
sa.Enum("BIRTHDAYS", "SCHEDULES", "UFORA_ANNOUNCEMENTS", name="tasktype").drop(op.get_bind())
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,36 @@
|
|||
"""Add events table
|
||||
|
||||
Revision ID: 954ad804f057
|
||||
Revises: 9fb84b4d9f0b
|
||||
Create Date: 2023-02-02 22:20:23.107931
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "954ad804f057"
|
||||
down_revision = "9fb84b4d9f0b"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"events",
|
||||
sa.Column("event_id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("notification_channel", sa.BigInteger(), nullable=False),
|
||||
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.PrimaryKeyConstraint("event_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("events")
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,30 @@
|
|||
"""Add free games
|
||||
|
||||
Revision ID: 9fb84b4d9f0b
|
||||
Revises: 11388e39bb90
|
||||
Create Date: 2022-10-13 19:17:58.032182
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "9fb84b4d9f0b"
|
||||
down_revision = "11388e39bb90"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"free_games", sa.Column("free_game_id", sa.Integer(), nullable=False), sa.PrimaryKeyConstraint("free_game_id")
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("free_games")
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,39 @@
|
|||
"""Add reminders
|
||||
|
||||
Revision ID: a64876b41af2
|
||||
Revises: c1f9ee875616
|
||||
Create Date: 2022-09-23 13:37:10.331840
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a64876b41af2"
|
||||
down_revision = "c1f9ee875616"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"reminders",
|
||||
sa.Column("reminder_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("category", sa.Enum("LES", name="remindercategory"), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("reminder_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("reminders")
|
||||
sa.Enum("LES", name="remindercategory").drop(op.get_bind())
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,36 @@
|
|||
"""Easter eggs
|
||||
|
||||
Revision ID: b84bb10fb8de
|
||||
Revises: 515dc3f52c6d
|
||||
Create Date: 2022-09-20 00:23:53.160168
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "b84bb10fb8de"
|
||||
down_revision = "515dc3f52c6d"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"easter_eggs",
|
||||
sa.Column("easter_egg_id", sa.Integer(), nullable=False),
|
||||
sa.Column("match", sa.Text(), nullable=False),
|
||||
sa.Column("response", sa.Text(), nullable=False),
|
||||
sa.Column("exact", sa.Boolean(), server_default="1", nullable=False),
|
||||
sa.Column("startswith", sa.Boolean(), server_default="1", nullable=False),
|
||||
sa.PrimaryKeyConstraint("easter_egg_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("easter_eggs")
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,54 @@
|
|||
"""Command stats & GitHub links
|
||||
|
||||
Revision ID: c1f9ee875616
|
||||
Revises: b84bb10fb8de
|
||||
Create Date: 2022-09-20 17:18:02.289593
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c1f9ee875616"
|
||||
down_revision = "b84bb10fb8de"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"command_stats",
|
||||
sa.Column("command_stats_id", sa.Integer(), nullable=False),
|
||||
sa.Column("command", sa.Text(), nullable=False),
|
||||
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.Column("slash", sa.Boolean(), nullable=False),
|
||||
sa.Column("context_menu", sa.Boolean(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("command_stats_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"github_links",
|
||||
sa.Column("github_link_id", sa.Integer(), nullable=False),
|
||||
sa.Column("url", sa.Text(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user_id"],
|
||||
["users.user_id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("github_link_id"),
|
||||
sa.UniqueConstraint("url"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("github_links")
|
||||
op.drop_table("command_stats")
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,14 @@
|
|||
comment:
|
||||
layout: "reach, diff, flags, files"
|
||||
behavior: default
|
||||
require_changes: false # if true: only post the comment if coverage changes
|
||||
require_base: no # [yes :: must have a base report to post]
|
||||
require_head: yes # [yes :: must have a head report to post]
|
||||
|
||||
coverage:
|
||||
round: down
|
||||
precision: 5
|
||||
|
||||
ignore:
|
||||
- "./didier/cogs/*" # Cogs can't really be tested properly
|
||||
- "./tests/*"
|
|
@ -1,226 +0,0 @@
|
|||
from data import constants
|
||||
import datetime
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import timeFormatters, stringFormatters
|
||||
from functions.database import birthdays
|
||||
|
||||
|
||||
class Birthdays(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Birthday", aliases=["Bd", "Birthdays"], case_insensitive=True, invoke_without_command=True)
|
||||
@help.Category(Category.Other)
|
||||
async def birthday(self, ctx, member: discord.Member = None):
|
||||
"""
|
||||
Command to check the birthday of yourself/another person.
|
||||
:param ctx: Discord Context
|
||||
:param member: The member to check
|
||||
"""
|
||||
if member is not None:
|
||||
# A member was tagged
|
||||
nameStr = "**{}**'s".format(member.display_name)
|
||||
res = birthdays.get_user(member.id)
|
||||
else:
|
||||
# No member passed -> check the user's birthday
|
||||
nameStr = "Jouw"
|
||||
res = birthdays.get_user(ctx.author.id)
|
||||
|
||||
if not res:
|
||||
# Nothing found in the db for this member
|
||||
return await ctx.send("{} verjaardag zit nog niet in de database.".format(nameStr))
|
||||
|
||||
# Create a datetime object of the upcoming birthday,
|
||||
# and a formatted string displaying the date
|
||||
dayDatetime, timeString = self.dmToDatetime(res[0][0], res[0][1])
|
||||
|
||||
# Find the weekday related to this day
|
||||
weekday = timeFormatters.intToWeekday(dayDatetime.weekday()).lower()
|
||||
|
||||
return await ctx.send("{} verjaardag staat ingesteld op **{} {}**.".format(
|
||||
nameStr, weekday, timeString
|
||||
))
|
||||
|
||||
@birthday.command(name="Today", aliases=["Now"])
|
||||
async def today(self, ctx):
|
||||
"""
|
||||
Command that lists all birthdays of the day.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
# Create a datetime object for today
|
||||
dt = timeFormatters.dateTimeNow()
|
||||
await ctx.send(self.getBirthdayOnDate(dt))
|
||||
|
||||
@birthday.command(name="Tomorrow", aliases=["Tm", "Tmw"])
|
||||
async def tomorrow(self, ctx):
|
||||
"""
|
||||
Command that lists all birthdays of tomorrow.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
# Create a datetime object for tomorrow
|
||||
dt = timeFormatters.dateTimeNow() + datetime.timedelta(days=1)
|
||||
await ctx.send(self.getBirthdayOnDate(dt).replace("Vandaag", "Morgen").replace("vandaag", "morgen"))
|
||||
|
||||
@birthday.command(name="Week")
|
||||
async def week(self, ctx):
|
||||
"""
|
||||
Command that lists all birthdays for the coming week.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
# Dict of all birthdays this week
|
||||
this_week = {}
|
||||
|
||||
# Create a datetime object starting yesterday so the first line
|
||||
# of the loop can add a day every time,
|
||||
# as premature returning would prevent this from happening
|
||||
# & get the day stuck
|
||||
dt = timeFormatters.dateTimeNow() - datetime.timedelta(days=1)
|
||||
|
||||
# Create an embed
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Verjaardagen deze week")
|
||||
|
||||
# Add all people of the coming week
|
||||
for dayCounter in range(7):
|
||||
dt += datetime.timedelta(days=1)
|
||||
res = birthdays.get_users_on_date(dt.day, dt.month)
|
||||
|
||||
# No birthdays on this day
|
||||
if not res:
|
||||
continue
|
||||
|
||||
# Add everyone from this day into the dict
|
||||
this_week[str(dayCounter)] = {"day": dt.day, "month": dt.month, "users": []}
|
||||
|
||||
for user in res:
|
||||
this_week[str(dayCounter)]["users"].append(user[0])
|
||||
|
||||
# No one found
|
||||
if not this_week:
|
||||
embed.description = "Deze week is er niemand jarig."
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
|
||||
# For every day, add the list of users into the embed
|
||||
for day, value in this_week.items():
|
||||
|
||||
dayDatetime, timeString = self.dmToDatetime(int(value["day"]), int(value["month"]))
|
||||
weekday = timeFormatters.intToWeekday(dayDatetime.weekday())
|
||||
|
||||
embed.add_field(name="{} {}".format(weekday, timeString),
|
||||
value=", ".join(COC.get_member(user).mention for user in value["users"]),
|
||||
inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
def getBirthdayOnDate(self, dt):
|
||||
"""
|
||||
Function to get all birthdays on a certain date.
|
||||
Returns a string right away to avoid more code duplication.
|
||||
:param dt: the date (Python datetime instance)
|
||||
:return: A formatted string containing all birthdays on [dt]
|
||||
"""
|
||||
res = birthdays.get_users_on_date(dt.day, dt.month)
|
||||
|
||||
# Nobody's birthday
|
||||
if not res:
|
||||
return "Vandaag is er niemand jarig."
|
||||
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
|
||||
# Create a list of member objects of the people that have a birthday on this date
|
||||
people = [COC.get_member(int(user[0])) for user in res]
|
||||
|
||||
if len(people) == 1:
|
||||
return "Vandaag is **{}** jarig.".format(people[0].display_name)
|
||||
return "Vandaag zijn {} en {} jarig.".format(
|
||||
", ".join("**" + user.display_name + "**" for user in people[:-1]),
|
||||
people[-1].display_name
|
||||
)
|
||||
|
||||
def dmToDatetime(self, day, month):
|
||||
"""
|
||||
Converts a day + month to a datetime instance.
|
||||
:param day: the day in the date
|
||||
:param month: the month in the date
|
||||
:return: a datetime instance representing the next time this date occurs,
|
||||
and a formatted string for this date
|
||||
"""
|
||||
now = timeFormatters.dateTimeNow()
|
||||
year = now.year
|
||||
|
||||
# Add an extra year to the date in case it has already passed
|
||||
if month < now.month or (month == now.month and day < now.day):
|
||||
year += 1
|
||||
|
||||
# Create a datetime object for this birthday
|
||||
timeString = "{}/{}/{}".format(
|
||||
stringFormatters.leadingZero(str(day)),
|
||||
stringFormatters.leadingZero(str(month)),
|
||||
year
|
||||
)
|
||||
|
||||
dayDatetime = datetime.datetime.strptime(timeString, "%d/%m/%Y")
|
||||
return dayDatetime, timeString
|
||||
|
||||
@birthday.command(name="Set", usage="[DD/MM/YYYY]")
|
||||
async def set(self, ctx, date=None, member: discord.Member = None):
|
||||
"""
|
||||
Command to add your birthday into the database.
|
||||
:param ctx: Discord Context
|
||||
:param date: the date of your birthday
|
||||
:param member: another member whose birthday has to be added/changed
|
||||
"""
|
||||
# No date passed
|
||||
if date is None:
|
||||
return await ctx.send("Geef een datum op.")
|
||||
|
||||
# Invalid format used
|
||||
if date.count("/") != 2:
|
||||
return await ctx.send("Ongeldig formaat (gebruik DD/MM/YYYY).")
|
||||
|
||||
# Check if anything is wrong with the date
|
||||
try:
|
||||
day = int(date.split("/")[0])
|
||||
month = int(date.split("/")[1])
|
||||
year = int(date.split("/")[2])
|
||||
|
||||
# This is not used, but creating an invalid datetime object throws a ValueError
|
||||
# so it prevents invalid dates like 69/420/360
|
||||
dt = datetime.datetime(year=year, month=month, day=day)
|
||||
|
||||
# Assume no one in the Discord is more than 5 years younger, or 10 years older
|
||||
# (which are also virtually impossible, but just to be sure)
|
||||
if year >= timeFormatters.dateTimeNow().year - 15 or year < 1990:
|
||||
raise ValueError
|
||||
|
||||
except ValueError:
|
||||
return await ctx.send("Dit is geen geldige datum.")
|
||||
|
||||
# A member was tagged, check if I did it
|
||||
if member is not None:
|
||||
if str(ctx.author.id) != str(constants.myId):
|
||||
return await ctx.send("Je kan andere mensen hun verjaardag niet instellen, {}.".format(ctx.author.display_name))
|
||||
else:
|
||||
birthdays.add_user(member.id, day, month, year)
|
||||
return await ctx.message.add_reaction("✅")
|
||||
|
||||
# Birthday is already added
|
||||
if birthdays.get_user(ctx.author.id) and str(ctx.author.id) != constants.myId:
|
||||
return await ctx.send("Je verjaardag zit al in de database.")
|
||||
|
||||
# Add into the db
|
||||
birthdays.add_user(ctx.author.id, day, month, year)
|
||||
return await ctx.send("Je verjaardag is toegevoegd aan de database.")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Birthdays(client))
|
149
cogs/bitcoin.py
149
cogs/bitcoin.py
|
@ -1,149 +0,0 @@
|
|||
from converters.numbers import Abbreviated
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, timeFormatters
|
||||
from functions.database import currency
|
||||
import requests
|
||||
|
||||
|
||||
class Bitcoin(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Bitcoin", aliases=["Bc"], case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def bc(self, ctx):
|
||||
"""
|
||||
Command that shows your Bitcoin bank.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
price = self.getPrice()
|
||||
bc = float(currency.getOrAddUser(ctx.author.id)[8])
|
||||
|
||||
currentTime = timeFormatters.dateTimeNow()
|
||||
currentTimeFormatted = currentTime.strftime('%m/%d/%Y om %H:%M:%S')
|
||||
|
||||
# Create the embed
|
||||
embed = discord.Embed(colour=discord.Colour.gold())
|
||||
embed.set_author(name="Bitcoin Bank van {}".format(ctx.author.display_name))
|
||||
embed.add_field(name="Aantal Bitcoins:", value="{:,}".format(round(bc, 8)), inline=False)
|
||||
embed.add_field(name="Huidige waarde:", value="{:,} Didier Dink{}"
|
||||
.format(round(bc * price, 8), checks.pluralS(bc * price)), inline=False)
|
||||
embed.set_footer(text="Huidige Bitcoin prijs: €{:,} ({})".format(price, str(currentTimeFormatted)))
|
||||
|
||||
# Add the Bitcoin icon to the embed
|
||||
file = discord.File("files/images/bitcoin.png", filename="icon.png")
|
||||
embed.set_thumbnail(url="attachment://icon.png")
|
||||
|
||||
await ctx.send(embed=embed, file=file)
|
||||
|
||||
@bc.command(name="Price")
|
||||
async def price(self, ctx):
|
||||
"""
|
||||
Command that shows the current Bitcoin price.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
price = self.getPrice()
|
||||
currentTime = timeFormatters.dateTimeNow()
|
||||
currentTimeFormatted = currentTime.strftime('%m/%d/%Y om %H:%M:%S')
|
||||
await ctx.send(
|
||||
"Huidige Bitcoin prijs: **€{:,}** ({}).".format(price, str(currentTimeFormatted)))
|
||||
|
||||
@bc.command(name="Buy", usage="[Aantal]")
|
||||
async def buy(self, ctx, amount: Abbreviated):
|
||||
"""
|
||||
Command to buy Bitcoins.
|
||||
:param ctx: Discord Context
|
||||
:param amount: the amount of Bitcoins the user wants to buy
|
||||
"""
|
||||
|
||||
resp = checks.isValidAmount(ctx, amount)
|
||||
|
||||
# Not a valid amount: send the appropriate error message
|
||||
if not resp[0]:
|
||||
return await ctx.send(resp[1])
|
||||
|
||||
if amount == "all":
|
||||
amount = resp[1]
|
||||
|
||||
# Calculate the amount of Bitcoins the user can buy with [amount] of Didier Dinks
|
||||
price = self.getPrice()
|
||||
purchased = round(float(amount) / price, 8)
|
||||
|
||||
# Update the db
|
||||
currency.update(ctx.author.id, "dinks", float(currency.dinks(ctx.author.id)) - float(amount))
|
||||
currency.update(ctx.author.id, "bitcoins",
|
||||
float(currency.getOrAddUser(ctx.author.id)[8]) + float(purchased))
|
||||
|
||||
await ctx.send("**{}** heeft **{:,}** Bitcoin{} gekocht voor **{:,}** Didier Dink{}!"
|
||||
.format(ctx.author.display_name, purchased, checks.pluralS(purchased),
|
||||
round(float(amount)), checks.pluralS(amount)))
|
||||
|
||||
@bc.command(name="Sell", usage="[Aantal]")
|
||||
async def sell(self, ctx, amount: Abbreviated):
|
||||
"""
|
||||
Command to sell Bitcoins.
|
||||
:param ctx: Discord Context
|
||||
:param amount: the amount of Bitcoins the user wants to sell
|
||||
"""
|
||||
if amount == "all":
|
||||
amount = float(currency.getOrAddUser(ctx.author.id)[8])
|
||||
|
||||
try:
|
||||
amount = float(amount)
|
||||
if amount <= 0:
|
||||
raise ValueError
|
||||
|
||||
bc = float(currency.getOrAddUser(ctx.author.id)[8])
|
||||
|
||||
if bc == 0.0:
|
||||
# User has no Bitcoins
|
||||
await ctx.send("Je hebt geen Bitcoins, **{}**".format(ctx.author.display_name))
|
||||
elif amount > bc:
|
||||
# User is trying to sell more Bitcoins that he has
|
||||
await ctx.send("Je hebt niet genoeg Bitcoins om dit te doen, **{}**"
|
||||
.format(ctx.author.display_name))
|
||||
else:
|
||||
price = self.getPrice()
|
||||
dinks = float(currency.dinks(ctx.author.id))
|
||||
|
||||
currency.update(ctx.author.id, "bitcoins", bc - amount)
|
||||
currency.update(ctx.author.id, "dinks", dinks + (price * amount))
|
||||
|
||||
await ctx.send("**{}** heeft **{:,}** Bitcoin{} verkocht voor **{:,}** Didier Dink{}!"
|
||||
.format(ctx.author.display_name, round(amount, 8), checks.pluralS(amount),
|
||||
round((price * amount), 8), checks.pluralS(price * amount)))
|
||||
except ValueError:
|
||||
# Can't be parsed to float -> random string OR smaller than 0
|
||||
await ctx.send("Geef een geldig bedrag op.")
|
||||
|
||||
@bc.command(aliases=["Lb", "Leaderboards"], hidden=True)
|
||||
@help.Category(category=Category.Other)
|
||||
async def leaderboard(self, ctx):
|
||||
"""
|
||||
Command that shows the Bitcoin Leaderboard.
|
||||
Alias for Lb Bc.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
# Call the appropriate leaderboard function
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("bitcoin", ctx)
|
||||
|
||||
def getPrice(self):
|
||||
"""
|
||||
Function to get the current Bitcoin price.
|
||||
:return: the current Bitcoin price (float)
|
||||
"""
|
||||
result = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json").json()
|
||||
currentPrice = result["bpi"]["EUR"]["rate_float"]
|
||||
return float(currentPrice)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Bitcoin(client))
|
397
cogs/corona.py
397
cogs/corona.py
|
@ -1,397 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, timeFormatters
|
||||
import requests
|
||||
|
||||
|
||||
class Corona(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
# Gets the information & calls other functions if necessary
|
||||
@commands.group(name="Corona", usage="[Land]*", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def corona(self, ctx, country: str = "Belgium"):
|
||||
"""
|
||||
Command that shows the corona stats for a certain country.
|
||||
:param ctx: Discord Context
|
||||
:param country: the country to show the stats for
|
||||
"""
|
||||
dic = await self.getCountryStats(country)
|
||||
if dic is None:
|
||||
# Country was not found
|
||||
await self.sendError(ctx)
|
||||
return
|
||||
|
||||
# Vaccination stats
|
||||
vaccine = self.getVaccineData(country, dic["today"]["population"], dic["yesterday"]["population"])
|
||||
|
||||
await self.sendEmbed(ctx, dic, vaccine)
|
||||
|
||||
@corona.command(aliases=["lb", "leaderboards"], hidden=True)
|
||||
async def leaderboard(self, ctx):
|
||||
"""
|
||||
Command that shows the Corona Leaderboard.
|
||||
Alias for Lb Corona.
|
||||
:param ctx: Discord Context
|
||||
:return: y
|
||||
"""
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("corona", ctx)
|
||||
|
||||
async def sendEmbed(self, ctx, dic, vaccines):
|
||||
"""
|
||||
Function that sends a Corona embed from a dictionary.
|
||||
:param ctx: Discord Context
|
||||
:param dic: the dictionary corresponding to this country
|
||||
"""
|
||||
embed = discord.Embed(colour=discord.Colour.red(), title="Coronatracker {}".format(dic["today"]["country"]))
|
||||
embed.set_thumbnail(url="https://i.imgur.com/aWnDuBt.png")
|
||||
|
||||
# Total
|
||||
embed.add_field(name="Totale Gevallen (Vandaag):",
|
||||
value=self.createEmbedString(
|
||||
dic["today"]["cases"],
|
||||
dic["today"]["todayCases"],
|
||||
self.trendIndicator(dic, "todayCases")
|
||||
),
|
||||
inline=False)
|
||||
|
||||
# Active
|
||||
embed.add_field(name="Actieve Gevallen (Vandaag):",
|
||||
value=self.createEmbedString(
|
||||
dic["today"]["activeCases"],
|
||||
dic["today"]["activeCases"] - dic["yesterday"]["activeCases"],
|
||||
self.activeTrendIndicator(dic)
|
||||
),
|
||||
inline=False)
|
||||
|
||||
# Deaths
|
||||
embed.add_field(name="Sterfgevallen (Vandaag):",
|
||||
value=self.createEmbedString(
|
||||
dic["today"]["deaths"],
|
||||
dic["today"]["todayDeaths"],
|
||||
self.trendIndicator(dic, "todayDeaths")
|
||||
),
|
||||
inline=False)
|
||||
|
||||
# Recovered
|
||||
embed.add_field(name="Hersteld (Vandaag):",
|
||||
value=self.createEmbedString(
|
||||
dic["today"]["recovered"],
|
||||
dic["today"]["todayRecovered"],
|
||||
self.trendIndicator(dic, "todayRecovered")
|
||||
),
|
||||
inline=False)
|
||||
|
||||
# Test Cases
|
||||
embed.add_field(name="Aantal uitgevoerde tests:",
|
||||
value=self.createEmbedString(
|
||||
dic["today"]["tests"],
|
||||
dic["today"]["todayTests"]
|
||||
),
|
||||
inline=False)
|
||||
|
||||
# Vaccines
|
||||
if vaccines is not None:
|
||||
# embed.add_field(name="Aantal toegediende vaccins:",
|
||||
# value=self.createEmbedString(
|
||||
# vaccines["today"]["vaccines"],
|
||||
# vaccines["today"]["todayVaccines"],
|
||||
# self.trendIndicator(vaccines, "todayVaccines")
|
||||
# ))
|
||||
embed.add_field(name="Aantal toegediende vaccins:",
|
||||
value="{:,}".format(vaccines["today"]["vaccines"]),
|
||||
inline=False)
|
||||
|
||||
# Timestamp of last update
|
||||
timeFormatted = timeFormatters.epochToDate(int(dic["today"]["updated"]) / 1000)
|
||||
embed.set_footer(text="Laatst geüpdatet op {} ({} geleden)".format(
|
||||
timeFormatted["date"], timeFormatted["timeAgo"]))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
def createEmbedString(self, total, today, indicator="", isPercentage=False):
|
||||
"""
|
||||
Function that formats a string to add covid data into the embed,
|
||||
separate function to make code more readable
|
||||
"""
|
||||
# + or - symbol | minus is included in the number so no need
|
||||
symbol = "+" if today >= 0 else ""
|
||||
perc = "%" if isPercentage else ""
|
||||
|
||||
return "{:,}{} **({}{:,}{})** {}".format(
|
||||
total, perc, symbol, today, perc, indicator
|
||||
)
|
||||
|
||||
@commands.command(name="Trends", aliases=["Ct"], usage="[Land]*")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def trends(self, ctx, country: str = "Belgium"):
|
||||
"""
|
||||
Command that gives more precise stats & changes.
|
||||
:param ctx: Discord Context
|
||||
:param country: the country to get the stats for
|
||||
"""
|
||||
dic = await self.getCountryStats(country)
|
||||
if dic is None:
|
||||
await self.sendError(ctx)
|
||||
return
|
||||
|
||||
# Get the distribution for this country
|
||||
distribution = self.distribution(dic)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.red(), title="Coronatrends {}".format(dic["today"]["country"]))
|
||||
embed.set_thumbnail(url="https://i.imgur.com/aWnDuBt.png")
|
||||
|
||||
# Calculate the trends & add them into the fields
|
||||
embed.add_field(name="Totale Gevallen\n({:,})".format(dic["today"]["cases"]),
|
||||
value=self.trend(dic, "cases"),
|
||||
inline=True)
|
||||
|
||||
embed.add_field(name="Sterfgevallen\n({:,})".format(dic["today"]["deaths"]),
|
||||
value=self.trend(dic, "deaths"),
|
||||
inline=True)
|
||||
|
||||
embed.add_field(name="Hersteld\n({:,})".format(dic["today"]["recovered"]),
|
||||
value=self.trend(dic, "recovered"))
|
||||
|
||||
embed.add_field(name="Totale Gevallen\nVandaag ({:,})".format(dic["today"]["todayCases"]),
|
||||
value=self.trend(dic, "todayCases"),
|
||||
inline=True)
|
||||
|
||||
embed.add_field(name="Sterfgevallen\nVandaag ({:,})".format(dic["today"]["todayDeaths"]),
|
||||
value=self.trend(dic, "todayDeaths"),
|
||||
inline=True)
|
||||
|
||||
embed.add_field(name="Hersteld\nVandaag ({:,})".format(dic["today"]["todayRecovered"]),
|
||||
value=self.trend(dic, "todayRecovered"))
|
||||
|
||||
embed.add_field(name="Verdeling", value="Actief: {} | Overleden: {} | Hersteld: {}".format(
|
||||
distribution[0], distribution[1], distribution[2]), inline=False)
|
||||
|
||||
# Timestamp of last update
|
||||
timeFormatted = timeFormatters.epochToDate(int(dic["today"]["updated"]) / 1000)
|
||||
embed.set_footer(text="Laatst geüpdatet op {} ({} geleden)".format(
|
||||
timeFormatted["date"], timeFormatted["timeAgo"]))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def getCountryStats(self, country):
|
||||
"""
|
||||
Function that gets the stats for a specific country.
|
||||
:param country: the country to get the stats for
|
||||
:return: a dictionary containing the info for today & yesterday
|
||||
"""
|
||||
# Check if Global or a country was passed
|
||||
if country.lower() == "global":
|
||||
country = "all?"
|
||||
else:
|
||||
country = "countries/{}?strict=false&".format(country)
|
||||
|
||||
today = requests.get("https://disease.sh/v3/covid-19/{}yesterday=false&allowNull=false".format(country)).json()
|
||||
|
||||
# Send error message
|
||||
if "message" in today:
|
||||
return None
|
||||
|
||||
yesterday = requests.get("https://disease.sh/v3/covid-19/{}yesterday=true&allowNull=false".format(country)) \
|
||||
.json()
|
||||
|
||||
# Divide into today & yesterday to be able to calculate the changes
|
||||
dic = {
|
||||
"today": {
|
||||
"country": today["country"] if country != "all?" else "Global",
|
||||
"cases": today["cases"],
|
||||
"activeCases": today["active"],
|
||||
"todayCases": today["todayCases"],
|
||||
"deaths": today["deaths"],
|
||||
"todayDeaths": today["todayDeaths"],
|
||||
"recovered": today["recovered"],
|
||||
"todayRecovered": today["todayRecovered"],
|
||||
"tests": today["tests"],
|
||||
"todayTests": today["tests"] - yesterday["tests"],
|
||||
"updated": today["updated"],
|
||||
"population": today["population"]
|
||||
},
|
||||
"yesterday": {
|
||||
"cases": yesterday["cases"],
|
||||
"activeCases": yesterday["active"],
|
||||
"todayCases": yesterday["todayCases"],
|
||||
"deaths": yesterday["deaths"],
|
||||
"todayDeaths": yesterday["todayDeaths"],
|
||||
"recovered": yesterday["recovered"],
|
||||
"todayRecovered": yesterday["todayRecovered"],
|
||||
"tests": yesterday["tests"],
|
||||
"updated": yesterday["updated"],
|
||||
"population": yesterday["population"]
|
||||
}
|
||||
}
|
||||
|
||||
return dic
|
||||
|
||||
def getVaccineData(self, country: str, todayPopulation: int, yesterdayPopulation: int):
|
||||
"""
|
||||
Function that gets vaccination stats for a specicic country.
|
||||
This information is later added to the embed.
|
||||
"""
|
||||
if todayPopulation == 0:
|
||||
todayPopulation = 1
|
||||
|
||||
if yesterdayPopulation == 0:
|
||||
yesterdayPopulation = 1
|
||||
|
||||
# "all" has a different endpoint
|
||||
if country == "global":
|
||||
vaccine: dict = requests.get("https://disease.sh/v3/covid-19/vaccine/coverage?lastdays=3").json()
|
||||
else:
|
||||
vaccine: dict = requests.get("https://disease.sh/v3/covid-19/vaccine/coverage/countries/{}?lastdays=3"
|
||||
.format(country)).json()
|
||||
|
||||
# Country-specific is structured differently
|
||||
if "timeline" in vaccine:
|
||||
vaccine = vaccine["timeline"]
|
||||
|
||||
# Error message returned or not enough data yet
|
||||
if "message" in vaccine or len(vaccine.keys()) != 3:
|
||||
return None
|
||||
|
||||
timeFormat = "%m/%d/%y"
|
||||
|
||||
def getDt(dt):
|
||||
"""
|
||||
Function that calls fromString with an argument
|
||||
so it can be used in map
|
||||
"""
|
||||
return timeFormatters.fromString(dt, timeFormat)
|
||||
|
||||
def toString(dt):
|
||||
"""
|
||||
Function to cast datetime back to string
|
||||
"""
|
||||
st: str = dt.strftime(timeFormat)
|
||||
|
||||
# Api doesn't add leading zeroes so the keys don't match anymore ---'
|
||||
if st.startswith("0"):
|
||||
st = st[1:]
|
||||
|
||||
return st
|
||||
|
||||
# Order dates
|
||||
ordered = sorted(map(getDt, vaccine.keys()))
|
||||
# Datetime objects are only required for sorting, turn back into strings
|
||||
ordered = list(map(toString, ordered))
|
||||
|
||||
info = {"today": {}, "yesterday": {}}
|
||||
|
||||
# Total vaccines
|
||||
info["today"]["vaccines"] = vaccine[ordered[2]]
|
||||
# New vaccines today
|
||||
info["today"]["todayVaccines"] = vaccine[ordered[2]] - vaccine[ordered[1]]
|
||||
# % of total population
|
||||
info["today"]["perc"] = round(100 * vaccine[ordered[2]] / todayPopulation, 2)
|
||||
info["yesterday"]["vaccines"] = vaccine[ordered[1]]
|
||||
info["yesterday"]["todayVaccines"] = vaccine[ordered[1]] - vaccine[ordered[0]]
|
||||
|
||||
return info
|
||||
|
||||
def distribution(self, dic):
|
||||
"""
|
||||
Calculates the percentage distribution for every key & shows an indicator.
|
||||
:param dic: the today/yesterday dictionary for this country
|
||||
:return: a list containing the distribution + indicator for active, recovered & deaths
|
||||
"""
|
||||
totalToday = dic["today"]["cases"] if dic["today"]["cases"] != 0 else 1
|
||||
totalYesterday = dic["yesterday"]["cases"] if dic["yesterday"]["cases"] != 0 else 1
|
||||
|
||||
tap = round(100 * dic["today"]["activeCases"] / totalToday, 2) # Today Active Percentage
|
||||
trp = round(100 * dic["today"]["recovered"] / totalToday, 2) # Today Recovered Percentage
|
||||
tdp = round(100 * dic["today"]["deaths"] / totalToday, 2) # Today Deaths Percentage
|
||||
yap = round(100 * dic["yesterday"]["activeCases"] / totalYesterday, 2) # Yesterday Active Percentage
|
||||
yrp = round(100 * dic["yesterday"]["recovered"] / totalYesterday, 2) # Yesterday Recovered Percentage
|
||||
ydp = round(100 * dic["yesterday"]["deaths"] / totalYesterday, 2) # Yesterday Deaths Percentage
|
||||
|
||||
return ["{}% {}".format(tap, self.indicator(tap, yap)),
|
||||
"{}% {}".format(tdp, self.indicator(tdp, ydp)),
|
||||
"{}% {}".format(trp, self.indicator(trp, yrp))]
|
||||
|
||||
async def sendError(self, ctx):
|
||||
"""
|
||||
Function that sends an error embed when an invalid country was passed.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.add_field(name="Error", value="Dit land staat niet in de database.", inline=False)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
# Returns a number and a percentage of rise/decline
|
||||
def trend(self, dic, key):
|
||||
"""
|
||||
Function that creates a string representing a number & percentage of
|
||||
rise & decline for a certain key of the dict.
|
||||
:param dic: the today/yesterday dictionary for this country
|
||||
:param key: the key to compare
|
||||
:return: a string showing the increase in numbers & percentages
|
||||
"""
|
||||
# Difference vs yesterday
|
||||
change = dic["today"][key] - dic["yesterday"][key]
|
||||
|
||||
# Don't divide by 0
|
||||
yesterday = dic["yesterday"][key] if dic["yesterday"][key] != 0 else 1
|
||||
|
||||
# Percentage
|
||||
perc = round(100 * change / yesterday, 2)
|
||||
|
||||
# Sign to add to the number
|
||||
sign = "+" if change >= 0 else ""
|
||||
|
||||
return "{}{:,} ({}{:,}%)".format(sign, change, sign, perc)
|
||||
|
||||
# Requires a bit of math so this is a separate function
|
||||
def activeTrendIndicator(self, dic):
|
||||
"""
|
||||
Function that returns a rise/decline indicator for the active cases of the day.
|
||||
This is a separate function as it requires some math to get right.
|
||||
New cases have to take into account the deaths & recovered cases being
|
||||
subtracted as well.
|
||||
:param dic: the today/yesterday dictionary for this country
|
||||
:return: a triangle emoji or empty string
|
||||
"""
|
||||
todayNew = dic["today"]["todayCases"] - dic["today"]["todayDeaths"] - dic["today"]["todayRecovered"]
|
||||
yesterdayNew = dic["yesterday"]["todayCases"] - dic["yesterday"]["todayDeaths"] - dic["yesterday"][
|
||||
"todayRecovered"]
|
||||
|
||||
return ":small_red_triangle:" if todayNew > yesterdayNew else \
|
||||
(":small_red_triangle_down:" if todayNew < yesterdayNew else "")
|
||||
|
||||
# Returns an arrow indicating rise or decline
|
||||
def trendIndicator(self, dic, key):
|
||||
"""
|
||||
Function that returns a rise/decline indicator for the target key.
|
||||
:param dic: the today/yesterday dictionary for this country
|
||||
:param key: the key to get the indicator for
|
||||
:return: a triangle emoji or empty string
|
||||
"""
|
||||
return ":small_red_triangle:" if dic["today"][key] > dic["yesterday"][key] else \
|
||||
(":small_red_triangle_down:" if dic["today"][key] < dic["yesterday"][key] else "")
|
||||
|
||||
# Also returns an indicator, but compares instead of pulling it out of the dic (for custom numbers)
|
||||
def indicator(self, today, yesterday):
|
||||
"""
|
||||
Function that also returns an indicator but for two numbers
|
||||
instead of comparing values out of the dictionary.
|
||||
:param today: the number representing today
|
||||
:param yesterday: the number representing yesterday
|
||||
:return: a triangle emoji or empty string
|
||||
"""
|
||||
return ":small_red_triangle:" if today > yesterday else \
|
||||
(":small_red_triangle_down:" if yesterday > today else "")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Corona(client))
|
113
cogs/define.py
113
cogs/define.py
|
@ -1,113 +0,0 @@
|
|||
import os
|
||||
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
import requests
|
||||
|
||||
|
||||
class Define(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Define", aliases=["UrbanDictionary", "Ud"], usage="[Woord]")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def define(self, ctx, *words):
|
||||
"""
|
||||
Command that looks up the definition of a word in the Urban Dictionary.
|
||||
:param ctx: Discord Context
|
||||
:param words: Word(s) to look up
|
||||
"""
|
||||
words = list(words)
|
||||
if len(words) == 0:
|
||||
return await ctx.send("Controleer je argumenten.")
|
||||
|
||||
query = " ".join(words)
|
||||
answer = self.lookup(query)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.from_rgb(220, 255, 0))
|
||||
embed.set_author(name="Urban Dictionary")
|
||||
|
||||
embed.add_field(name="Woord", value=answer["word"], inline=True)
|
||||
embed.add_field(name="Auteur", value=answer["author"], inline=True)
|
||||
embed.add_field(name="Definitie", value=self.cleanString(answer["definition"]), inline=False)
|
||||
embed.add_field(name="Voorbeeld", value=self.cleanString(answer["example"]), inline=False)
|
||||
embed.add_field(name="Rating", value=str(round(self.ratio(answer), 2)) + "%")
|
||||
embed.add_field(name="Link naar de volledige definitie",
|
||||
value="[Urban Dictionary]({})".format(str(answer["link"])))
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
def lookup(self, word):
|
||||
"""
|
||||
Function that sends the API request to get the definition.
|
||||
:param word: the woord to look up
|
||||
:return: a dictionary representing the info of this word
|
||||
"""
|
||||
url = "https://mashape-community-urban-dictionary.p.rapidapi.com/define"
|
||||
|
||||
querystring = {"term": word}
|
||||
|
||||
headers = {
|
||||
'x-rapidapi-host': "mashape-community-urban-dictionary.p.rapidapi.com",
|
||||
'x-rapidapi-key': os.getenv("URBANDICTIONARY")
|
||||
}
|
||||
|
||||
try:
|
||||
if word.lower() == "didier":
|
||||
raise Exception
|
||||
|
||||
response = requests.request("GET", url, headers=headers, params=querystring).json()["list"]
|
||||
|
||||
if len(response) > 0:
|
||||
return {"word": response[0]["word"], "definition": response[0]["definition"],
|
||||
"example": response[0]["example"], "thumbs_up": response[0]["thumbs_up"],
|
||||
"thumbs_down": response[0]["thumbs_down"], "link": response[0]["permalink"],
|
||||
"author": response[0]["author"]}
|
||||
|
||||
# No valid response
|
||||
return self.defineDidier()
|
||||
except Exception:
|
||||
return self.defineDidier()
|
||||
|
||||
def cleanString(self, text: str):
|
||||
"""
|
||||
Function that cuts off definitions that are too long & strips out UD markdown
|
||||
from an input string.
|
||||
:param text: the input string to clean up
|
||||
:return: the edited version of the string
|
||||
"""
|
||||
text = text.replace("[", "")
|
||||
text = text.replace("]", "")
|
||||
return text if len(text) < 1024 else text[:1021] + "..."
|
||||
|
||||
def ratio(self, dic):
|
||||
"""
|
||||
Function that alculates the upvote/downvote ratio of the definition.
|
||||
:param dic: the dictionary representing the definition
|
||||
:return: the upvote/downvote ratio (float)
|
||||
"""
|
||||
return (100 * int(dic["thumbs_up"])) / (int(dic["thumbs_up"]) + int(dic["thumbs_down"])) \
|
||||
if int(dic["thumbs_down"]) != 0 else 100.0
|
||||
|
||||
def defineDidier(self):
|
||||
"""
|
||||
Function that returns a stock dictionary to define Didier
|
||||
in case people call it, or no definition was found.
|
||||
:return: a dictionary that defines Didier
|
||||
"""
|
||||
return {"word": "Didier", "definition": "Didier", "example": "1: Didier\n2: Hmm?", "thumbs_up": 69420,
|
||||
"thumbs_down": 0, "author": "Didier",
|
||||
"link": "https://upload.wikimedia.org/wikipedia/commons/a/a5"
|
||||
"/Didier_Reynders_in_Iranian_Parliament_02.jpg"}
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Define(client))
|
567
cogs/dinks.py
567
cogs/dinks.py
|
@ -1,567 +0,0 @@
|
|||
from converters.numbers import Abbreviated, abbreviated
|
||||
from data import constants
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from enums.numbers import Numbers
|
||||
from functions import checks
|
||||
from functions.database import currency, prison, stats
|
||||
from functions.numbers import getRep
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
|
||||
|
||||
def calcCapacity(level):
|
||||
"""
|
||||
Function that calculates the rob capacity for a given level.
|
||||
:param level: the level of the user
|
||||
:return: the capacity the user can rob (float)
|
||||
"""
|
||||
cap = 200
|
||||
for x in range(level):
|
||||
cap *= (math.pow(1.03, x))
|
||||
return round(cap)
|
||||
|
||||
|
||||
class Dinks(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Award", aliases=["Reward"], usage="[@Persoon] [Aantal]", hidden=True)
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def award(self, ctx, user: discord.User, amount: Abbreviated):
|
||||
"""
|
||||
Command that awards a user a certain amount of Didier Dinks.
|
||||
:param ctx: Discord Context
|
||||
:param user: the user to give the Didier Dinks to
|
||||
:param amount: the amount of Didier Dinks to award [user]
|
||||
"""
|
||||
# No amount was passed
|
||||
if amount is None:
|
||||
return
|
||||
|
||||
# Update the db
|
||||
currency.update(user.id, "dinks", float(currency.dinks(user.id)) + float(amount))
|
||||
|
||||
# Gets the abbreviated representation of the amount
|
||||
rep = getRep(amount, Numbers.t.value)
|
||||
|
||||
await ctx.send("**{}** heeft **{}** zowaar **{}** Didier Dink{} beloond!"
|
||||
.format(ctx.author.display_name, self.utilsCog.getDisplayName(ctx, user.id), rep, checks.pluralS(amount)))
|
||||
|
||||
@commands.group(name="Dinks", aliases=["Cash"], case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def dinks(self, ctx):
|
||||
"""
|
||||
Command that shows the user's Didier Dinks & Platinum Dinks
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
dinks = currency.dinksAll(ctx.author.id)
|
||||
|
||||
answer = "**{}** heeft **{:,}** Didier Dink{}"\
|
||||
.format(ctx.author.display_name, math.floor(dinks["dinks"]), checks.pluralS(dinks["dinks"]))
|
||||
|
||||
if dinks["platinum"] > 0:
|
||||
answer += " en **{}** Platinum Dink{}".format(dinks["platinum"], checks.pluralS(dinks["platinum"]))
|
||||
|
||||
await ctx.send(answer + "!")
|
||||
|
||||
@dinks.command(aliases=["Lb", "Leaderboards"], hidden=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
async def leaderboard(self, ctx):
|
||||
"""
|
||||
Command that shows the Didier Dinks Leaderboard.
|
||||
Alias for Lb Dinks.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("dinks", ctx)
|
||||
|
||||
@commands.command(name="Nightly")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def nightly(self, ctx):
|
||||
"""
|
||||
Command to claim daily Didier Dinks.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
response = currency.nightly(int(ctx.author.id))
|
||||
if response[0]:
|
||||
# Claim successful
|
||||
await ctx.send("Je hebt je dagelijkse **{:,}** Didier Dinks geclaimt. :fire:**{}**".format(
|
||||
response[1], response[2]))
|
||||
else:
|
||||
# Already claimed today, react PIPO
|
||||
await ctx.send("Je kan dit niet meerdere keren per dag doen.")
|
||||
reactCog = self.client.get_cog("ReactWord")
|
||||
await reactCog.react(ctx, "pipo")
|
||||
|
||||
@commands.command(name="Give", aliases=["Gift"], usage="[@Persoon] [Aantal]")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def give(self, ctx, person: discord.Member, amount: Abbreviated):
|
||||
"""
|
||||
Command that gives your Didier Dinks to another user.
|
||||
:param ctx: Discord Context
|
||||
:param person: user to give the Didier Dinks to
|
||||
:param amount: the amount of Didier Dinks to give
|
||||
"""
|
||||
# Invalid amount
|
||||
if amount is None:
|
||||
return
|
||||
|
||||
# Disable DM abuse
|
||||
if ctx.guild is None:
|
||||
return await ctx.send("Muttn")
|
||||
|
||||
valid = checks.isValidAmount(ctx, amount)
|
||||
if not valid[0]:
|
||||
return await ctx.send(valid[1])
|
||||
|
||||
amount = float(valid[1])
|
||||
|
||||
currency.update(ctx.author.id, "dinks", float(currency.dinks(ctx.author.id)) - amount)
|
||||
currency.update(person.id, "dinks", float(currency.dinks(person.id)) + amount)
|
||||
|
||||
rep = getRep(math.floor(amount), Numbers.t.value)
|
||||
|
||||
await ctx.send("**{}** heeft **{}** zowaar **{}** Didier Dink{} geschonken!"
|
||||
.format(ctx.author.display_name, person.display_name,
|
||||
rep, checks.pluralS(amount)))
|
||||
|
||||
@commands.group(name="Bank", aliases=["B"], case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def bank(self, ctx):
|
||||
"""
|
||||
Command that shows the user's Didier Bank.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
# 0 1 2 3 4 5 6 7 8 9 10
|
||||
# ID dinks level investedamount investeddays profit defense offense bc nightly streak
|
||||
response = currency.getOrAddUser(ctx.author.id)
|
||||
|
||||
# Calculate the cost to level your bank
|
||||
interestLevelPrice = round(math.pow(1.28, int(response[2])) * 300)
|
||||
ratio = round(float(1 * (1 + (int(response[2]) * 0.01))), 4)
|
||||
|
||||
# Calculate the amount of levels the user can purchase
|
||||
counter = 0
|
||||
sumPrice = float(math.pow(1.28, int(response[2])) * 300)
|
||||
while float(response[1]) + float(response[3]) + float(response[5]) > sumPrice:
|
||||
counter += 1
|
||||
sumPrice += round(float(math.pow(1.28, int(response[2]) + counter) * 300), 4)
|
||||
maxLevels = "" if counter == 0 else " (+{})".format(str(counter))
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Bank van {}".format(ctx.author.display_name))
|
||||
embed.set_thumbnail(url=str(ctx.author.avatar_url))
|
||||
embed.add_field(name="Level:", value=str(response[2]) + maxLevels, inline=True)
|
||||
embed.add_field(name="Ratio:", value=str(ratio), inline=True)
|
||||
embed.add_field(name="Prijs voor volgend level:", value="{:,}".format(interestLevelPrice), inline=False)
|
||||
embed.add_field(name="Momenteel geïnvesteerd:", value="{:,}".format(math.floor(float(response[3]))), inline=False)
|
||||
embed.add_field(name="Aantal dagen geïnvesteerd:", value=str(response[4]), inline=True)
|
||||
embed.add_field(name="Huidige winst na claim:", value="{:,}".format(math.floor(response[5])), inline=False)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@bank.command(name="Stats")
|
||||
async def stats(self, ctx):
|
||||
"""
|
||||
Command that shows the user's bank stats.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
response = currency.getOrAddUser(ctx.author.id)
|
||||
|
||||
# Calculate the prices to level stats up
|
||||
defense = int(response[6])
|
||||
defenseLevelPrice = math.floor(math.pow(1.4, defense) * 365) if defense < 38 else 5 * calcCapacity(defense - 6)
|
||||
offense = int(response[7])
|
||||
capacity = calcCapacity(offense)
|
||||
offenseLevelPrice = math.floor(math.pow(1.5, offense) * 369) if offense < 32 else 5 * capacity
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Bank van {}".format(ctx.author.display_name))
|
||||
embed.add_field(name="Offense:", value=str(offense), inline=True)
|
||||
embed.add_field(name="Prijs voor volgend level:", value="{:,}".format(int(offenseLevelPrice)), inline=True)
|
||||
embed.add_field(name="Capaciteit:", value="{:,}".format(int(capacity)), inline=True)
|
||||
embed.add_field(name="Security:", value=str(defense), inline=True)
|
||||
embed.add_field(name="Prijs voor volgend level:", value="{:,}".format(int(defenseLevelPrice)), inline=True)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@bank.group(name="Upgrade", aliases=["U"], case_insensitive=True, usage="[Categorie]", invoke_without_command=True)
|
||||
async def upgrade(self, ctx):
|
||||
"""
|
||||
Command group to upgrade bank stats,
|
||||
calling the group itself does nothing.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
pass
|
||||
|
||||
@upgrade.command(name="Level", aliases=["L"], hidden=True)
|
||||
async def level(self, ctx):
|
||||
"""
|
||||
Command that upgrades the user's bank level,
|
||||
increasing interest.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
response = currency.getOrAddUser(ctx.author.id)
|
||||
interestLevelPrice = float(math.pow(1.28, int(response[2])) * 300)
|
||||
|
||||
# Check if user has enough Didier Dinks to do this
|
||||
if float(response[1]) >= interestLevelPrice:
|
||||
currency.update(ctx.author.id, "dinks", float(response[1]) - interestLevelPrice)
|
||||
currency.update(ctx.author.id, "banklevel", int(response[2]) + 1)
|
||||
await ctx.send("**{}** heeft zijn bank geüpgradet naar level **{}**!"
|
||||
.format(ctx.author.display_name, str(int(response[2]) + 1)))
|
||||
else:
|
||||
await ctx.send("Je hebt niet genoeg Didier Dinks om dit te doen, **{}**."
|
||||
.format(ctx.author.display_name))
|
||||
|
||||
@upgrade.command(aliases=["Cap", "Capacity", "O", "Offence"], hidden=True)
|
||||
async def offense(self, ctx):
|
||||
"""
|
||||
Command that upgrades the user's bank offense,
|
||||
increasing capacity & rob chances.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
response = currency.getOrAddUser(ctx.author.id)
|
||||
|
||||
offense = int(response[7])
|
||||
capacity = calcCapacity(offense)
|
||||
offenseLevelPrice = math.floor(math.pow(1.5, offense) * 369) if offense < 32 else 5 * capacity
|
||||
|
||||
# Check if user has enough Didier Dinks to do this
|
||||
if float(response[1]) >= offenseLevelPrice:
|
||||
currency.update(ctx.author.id, "dinks", float(response[1]) - offenseLevelPrice)
|
||||
currency.update(ctx.author.id, "offense", int(response[7]) + 1)
|
||||
await ctx.send("**{}** heeft de offense van zijn bank geüpgradet naar level **{}**!"
|
||||
.format(ctx.author.display_name, int(response[7]) + 1))
|
||||
else:
|
||||
await ctx.send("Je hebt niet genoeg Didier Dinks om dit te doen, **{}**."
|
||||
.format(ctx.author.display_name))
|
||||
|
||||
@upgrade.command(aliases=["D", "Defence", "Def", "Security"], hidden=True)
|
||||
async def defense(self, ctx):
|
||||
"""
|
||||
Command that upgrades the user's bank defense,
|
||||
increasing chance of failed robs by others.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
response = currency.getOrAddUser(ctx.author.id)
|
||||
defense = int(response[6])
|
||||
defenseLevelPrice = math.floor(math.pow(1.4, defense) * 365) if defense < 38 else 5 * calcCapacity(defense - 6)
|
||||
|
||||
# Check if user has enough Didier Dinks to do this
|
||||
if float(response[1]) >= defenseLevelPrice:
|
||||
currency.update(ctx.author.id, "dinks", float(response[1]) - defenseLevelPrice)
|
||||
currency.update(ctx.author.id, "defense", int(response[6]) + 1)
|
||||
await ctx.send("**{}** heeft de security van zijn bank geüpgradet naar level **{}**!"
|
||||
.format(ctx.author.display_name, int(response[6]) + 1))
|
||||
else:
|
||||
await ctx.send("Je hebt niet genoeg Didier Dinks om dit te doen, **{}**."
|
||||
.format(ctx.author.display_name))
|
||||
|
||||
@commands.command(name="Invest", aliases=["Deposit"], usage="[Aantal]")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def invest(self, ctx, *amount: Abbreviated):
|
||||
"""
|
||||
Command that invests Didier Dinks into the user's bank.
|
||||
:param ctx: Discord Context
|
||||
:param amount: the amount of Didier Dinks to invest
|
||||
"""
|
||||
# Tuples don't support assignment
|
||||
amount = list(amount)
|
||||
|
||||
if len(amount) != 1:
|
||||
await ctx.send("Geef een geldig bedrag op.")
|
||||
elif not checks.isValidAmount(ctx, amount[0])[0]:
|
||||
await ctx.send(checks.isValidAmount(ctx, amount[0])[1])
|
||||
else:
|
||||
user = currency.getOrAddUser(ctx.author.id)
|
||||
if amount[0] == "all":
|
||||
amount[0] = user[1]
|
||||
|
||||
amount[0] = float(amount[0])
|
||||
currency.update(ctx.author.id, "investedamount", float(user[3]) + amount[0])
|
||||
currency.update(ctx.author.id, "dinks", float(user[1]) - amount[0])
|
||||
await ctx.send("**{}** heeft **{:,}** Didier Dink{} geïnvesteerd!"
|
||||
.format(ctx.author.display_name, math.floor(amount[0]), checks.pluralS(amount[0])))
|
||||
|
||||
@commands.command(name="Claim", usage="[Aantal]*")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def claim(self, ctx, *args):
|
||||
"""
|
||||
Command that claims profit out of the user's Didier Bank.
|
||||
:param ctx:
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
user = currency.getOrAddUser(ctx.author.id)
|
||||
args = list(args)
|
||||
claimAll = False
|
||||
|
||||
if len(args) == 0:
|
||||
args.append("all")
|
||||
if args[0] == "all":
|
||||
args[0] = float(user[5])
|
||||
claimAll = True
|
||||
|
||||
if not claimAll:
|
||||
args[0] = abbreviated(str(args[0]))
|
||||
if args[0] is None:
|
||||
return await ctx.send("Dit is geen geldig bedrag.")
|
||||
|
||||
try:
|
||||
# Checks if it can be parsed to int
|
||||
_ = int(args[0])
|
||||
args[0] = float(args[0])
|
||||
# Can't claim more than you have (or negative amounts)
|
||||
if args[0] < 0 or args[0] > float(user[5]):
|
||||
raise ValueError
|
||||
|
||||
currency.update(ctx.author.id, "profit", float(user[5]) - args[0])
|
||||
currency.update(ctx.author.id, "dinks", float(user[1]) + args[0])
|
||||
s = stats.getOrAddUser(ctx.author.id)
|
||||
stats.update(ctx.author.id, "profit", float(s[7]) + args[0])
|
||||
|
||||
# If you claim everything, you get your invest back as well & your days reset
|
||||
if claimAll:
|
||||
currency.update(ctx.author.id, "dinks", float(user[1]) + float(user[3]) + float(user[5]))
|
||||
currency.update(ctx.author.id, "investedamount", 0.0)
|
||||
currency.update(ctx.author.id, "investeddays", 0)
|
||||
await ctx.send("**{}** heeft **{:,}** Didier Dink{} geclaimt!"
|
||||
.format(ctx.author.display_name, math.floor(args[0] + float(user[3])),
|
||||
checks.pluralS(math.floor(args[0] + float(user[3])))))
|
||||
else:
|
||||
await ctx.send("**{}** heeft **{:,}** Didier Dink{} geclaimt!".format(
|
||||
ctx.author.display_name, math.floor(args[0]), checks.pluralS(math.floor(args[0]))))
|
||||
|
||||
except ValueError:
|
||||
await ctx.send("Geef een geldig bedrag op.")
|
||||
|
||||
@commands.group(name="Rob", usage="[@Persoon]", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def rob(self, ctx, target: discord.User):
|
||||
"""
|
||||
Command to rob another user.
|
||||
:param ctx: Discord Context
|
||||
:param target: the target victim to be robbed
|
||||
:return:
|
||||
"""
|
||||
canRob, caller, target = await self.canRob(ctx, target)
|
||||
|
||||
if not canRob:
|
||||
return
|
||||
|
||||
threshold = 50 + round(int(target[6]) * 0.7)
|
||||
rg = random.randint(0 + int(caller[7]), 100)
|
||||
stat = stats.getOrAddUser(ctx.author.id)
|
||||
|
||||
# Rob succeeded
|
||||
if rg > threshold:
|
||||
capacity = float(calcCapacity(caller[7]))
|
||||
remaining = capacity
|
||||
|
||||
# Try robbing out of invest first, then Dinks pouch
|
||||
amount = capacity if float(target[3]) >= capacity else float(target[3])
|
||||
remaining -= amount
|
||||
currency.update(target[0], "investedamount", float(target[3]) - amount)
|
||||
|
||||
# Rob out of Dinks pouch
|
||||
if amount != capacity and not float(target[1]) < 1:
|
||||
if float(target[1]) >= remaining:
|
||||
amount += remaining
|
||||
currency.update(target[0], "dinks", float(target[1]) - remaining)
|
||||
else:
|
||||
amount += float(target[1])
|
||||
currency.update(target[0], "dinks", 0.0)
|
||||
|
||||
# Update db
|
||||
currency.update(caller[0], "dinks", float(caller[1]) + amount)
|
||||
await ctx.send("**{}** heeft **{:,}** Didier Dink{} gestolen van **{}**!".format(
|
||||
ctx.author.display_name, math.floor(amount), checks.pluralS(math.floor(amount)),
|
||||
self.utilsCog.getDisplayName(ctx, target[0])
|
||||
))
|
||||
|
||||
stats.update(ctx.author.id, "robs_success", int(stat[2]) + 1)
|
||||
stats.update(ctx.author.id, "robs_total", float(stat[4]) + amount)
|
||||
else:
|
||||
# Rob failed
|
||||
|
||||
# Calculate what happens
|
||||
fate = random.randint(1, 10)
|
||||
|
||||
# Leave Dinks behind instead of robbing
|
||||
if fate < 8:
|
||||
punishment = float(calcCapacity(caller[7]))/2
|
||||
prisoned = round(float(caller[1])) < round(punishment)
|
||||
|
||||
# Doesn't have enough Dinks -> prison
|
||||
if prisoned:
|
||||
diff = round(punishment - float(caller[1]))
|
||||
punishment = round(float(caller[1]))
|
||||
days = 1 + round(int(caller[7]) // 10)
|
||||
prison.imprison(caller[0], diff, days,
|
||||
round(round(diff)//days))
|
||||
|
||||
# Update db
|
||||
currency.update(target[0], "dinks", float(target[1]) + punishment)
|
||||
currency.update(caller[0], "dinks", float(caller[1]) - punishment)
|
||||
await ctx.send("**{}** was zo vriendelijk om **{}** zowaar **{:,}** Didier Dink{} te geven!"
|
||||
.format(ctx.author.display_name,
|
||||
self.utilsCog.getDisplayName(ctx, target[0]),
|
||||
math.floor(punishment), checks.pluralS(math.floor(punishment))))
|
||||
|
||||
# Can't put this in the previous if- because the value of Punishment changes
|
||||
if prisoned:
|
||||
await ctx.send("Je bent naar de gevangenis verplaatst omdat je niet genoeg Didier Dinks had.")
|
||||
elif fate == 9:
|
||||
# Prison
|
||||
totalSum = round(calcCapacity(caller[7]))
|
||||
days = 1 + (int(caller[7])//10)
|
||||
|
||||
prison.imprison(caller[0], totalSum, days, totalSum/days)
|
||||
await ctx.send("**{}** niet stelen, **{}** niet stelen!\nJe bent naar de gevangenis verplaatst.".format(
|
||||
ctx.author.display_name, ctx.author.display_name
|
||||
))
|
||||
else:
|
||||
# Escape
|
||||
await ctx.send("Je poging is mislukt, maar je kon nog net op tijd vluchten, **{}**."
|
||||
"\nAllez, 't is goed voor ene keer e, deugeniet.".format(ctx.author.display_name))
|
||||
|
||||
stats.update(ctx.author.id, "robs_failed", int(stat[3]) + 1)
|
||||
|
||||
@rob.command(name="Leaderboard", aliases=["Lb", "Leaderboards"], hidden=True)
|
||||
async def rob_leaderboard(self, ctx):
|
||||
"""
|
||||
Command that shows the Rob Leaderboard.
|
||||
Alias for Lb Rob.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("rob", ctx)
|
||||
|
||||
@rob.command(name="Stats", hidden=True)
|
||||
async def rob_stats(self, ctx):
|
||||
"""
|
||||
Command that shows the user's rob stats.
|
||||
Alias for Stats Rob.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
await self.client.get_cog("Stats").callStats("rob", ctx)
|
||||
|
||||
@commands.command(name="Prison", aliases=["Jail"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Currency)
|
||||
async def prison(self, ctx):
|
||||
"""
|
||||
Command that shows how long you have to sit in prison for.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
user = prison.getUser(ctx.author.id)
|
||||
|
||||
if len(user) == 0:
|
||||
await ctx.send("Je zit niet in de gevangenis, **{}**.".format(ctx.author.display_name))
|
||||
return
|
||||
|
||||
user = user[0]
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="De Gevangenis")
|
||||
embed.add_field(name="Borgsom:", value="{:,}".format(math.floor(user[1])), inline=False)
|
||||
embed.add_field(name="Resterende dagen:", value="{}".format((user[2])), inline=False)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="Bail")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Currency)
|
||||
async def bail(self, ctx):
|
||||
"""
|
||||
Command to bail yourself out of prison.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
user = prison.getUser(ctx.author.id)
|
||||
if len(user) == 0:
|
||||
return await ctx.send("Je zit niet in de gevangenis, **{}**.".format(ctx.author.display_name))
|
||||
|
||||
user = user[0]
|
||||
|
||||
# Check if user can afford this
|
||||
valid = checks.isValidAmount(ctx, math.floor(user[1]))
|
||||
if not valid[0]:
|
||||
return await ctx.send(valid[1])
|
||||
|
||||
dinks = currency.dinks(ctx.author.id)
|
||||
prison.remove(ctx.author.id)
|
||||
currency.update(ctx.author.id, "dinks", float(dinks) - float(user[1]))
|
||||
await ctx.send("**{}** heeft zichzelf vrijgekocht!".format(ctx.author.display_name))
|
||||
|
||||
# Update the user's stats
|
||||
s = stats.getOrAddUser(ctx.author.id)
|
||||
stats.update(ctx.author.id, "bails", int(s[10]) + 1)
|
||||
|
||||
# Increase the bail in the stats file
|
||||
with open("files/stats.json", "r") as fp:
|
||||
s = json.load(fp)
|
||||
|
||||
s["rob"]["bail_paid"] += float(user[1])
|
||||
|
||||
with open("files/stats.json", "w") as fp:
|
||||
json.dump(s, fp)
|
||||
|
||||
async def canRob(self, ctx, target):
|
||||
"""
|
||||
Function that performs checks to see if a user can rob another user.
|
||||
In case the rob is not possible, it already sends an error message to show this.
|
||||
Returns the database dictionaries corresponding to these two users as they are
|
||||
needed in this function anyways, so it prevents an unnecessary database call
|
||||
in the rob command.
|
||||
:param ctx: Discord Context
|
||||
:param target: the target victim to be robbed
|
||||
:return: success: boolean, user1 ("Caller"): tuple, user2 ("Target"): tuple
|
||||
"""
|
||||
# Can't rob in DM's
|
||||
if str(ctx.channel.type) == "private":
|
||||
await ctx.send("Dat doe je niet, {}.".format(ctx.author.display_name))
|
||||
return False, None, None
|
||||
|
||||
# Can't rob bots
|
||||
if str(ctx.author.id) in constants.botIDs:
|
||||
await ctx.send("Nee.")
|
||||
|
||||
# Can't rob in prison
|
||||
if len(prison.getUser(ctx.author.id)) != 0:
|
||||
await ctx.send("Je kan niemand bestelen als je in de gevangenis zit.")
|
||||
return False, None, None
|
||||
|
||||
# Check the database for these users
|
||||
user1 = currency.getOrAddUser(ctx.author.id)
|
||||
user2 = currency.getOrAddUser(target.id)
|
||||
|
||||
# Can't rob without Didier Dinks
|
||||
if float(user1[1]) < 1.0:
|
||||
await ctx.send("Mensen zonder Didier Dinks kunnen niet stelen.")
|
||||
return False, None, None
|
||||
|
||||
# Target has no Didier Dinks to rob
|
||||
if float(user2[1]) < 1.0 and float(user2[3]) < 1.0:
|
||||
await ctx.send("Deze persoon heeft geen Didier Dinks om te stelen.")
|
||||
return False, None, None
|
||||
|
||||
# Passed all tests
|
||||
return True, user1, user2
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Dinks(client))
|
345
cogs/events.py
345
cogs/events.py
|
@ -1,345 +0,0 @@
|
|||
from data import constants
|
||||
import datetime
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from functions import checks, easterEggResponses
|
||||
from functions.database import stats, muttn
|
||||
import pytz
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
class Events(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
self.failedChecksCog = self.client.get_cog("FailedChecks")
|
||||
self.lastFeatureRequest = 0
|
||||
self.lastBugReport = 0
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_connect(self):
|
||||
"""
|
||||
Function called when the bot connects to Discord.
|
||||
"""
|
||||
print("Connected")
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_ready(self):
|
||||
"""
|
||||
Function called when the bot is ready & done leading.
|
||||
"""
|
||||
# Change status
|
||||
with open("files/status.txt", "r") as statusFile:
|
||||
status = statusFile.readline()
|
||||
|
||||
await self.client.change_presence(status=discord.Status.online, activity=discord.Game(str(status)))
|
||||
|
||||
# Print a message in the terminal to show that he's ready
|
||||
with open("files/readyMessage.txt", "r") as readyFile:
|
||||
readyMessage = readyFile.readline()
|
||||
|
||||
print(readyMessage)
|
||||
|
||||
# Add constants to the client as a botvar
|
||||
self.client.constants = constants.Live if "zandbak" not in readyMessage else constants.Zandbak
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message):
|
||||
"""
|
||||
Function called when someone sends a message the bot can see.
|
||||
:param message: the discord.Message instance of the message
|
||||
"""
|
||||
# Check if the server is locked, if so only allow me (to unlock) & Didier (to send the message) to talk
|
||||
if self.client.locked \
|
||||
and message.guild is not None \
|
||||
and str(message.author.id) != constants.myId \
|
||||
and str(message.author.id) != constants.didierId:
|
||||
# Auto unlock when someone sends a message past the current time
|
||||
if time.time() > self.client.lockedUntil:
|
||||
return await self.unlock(message.channel)
|
||||
|
||||
return await self.utilsCog.removeMessage(message)
|
||||
|
||||
# If FreeGamesCheck failed, remove the message & send the user a DM
|
||||
if not checks.freeGamesCheck(message):
|
||||
await self.failedChecksCog.freeGames(message)
|
||||
|
||||
# Boos React to people that call him Dider
|
||||
if "dider" in message.content.lower() and str(message.author.id) not in [constants.myId, constants.didierId, constants.coolerDidierId]:
|
||||
await message.add_reaction("<:boos:629603785840263179>")
|
||||
|
||||
# Check for other easter eggs
|
||||
eER = easterEggResponses.control(self.client, message)
|
||||
if eER:
|
||||
await message.channel.send(eER)
|
||||
|
||||
# Earn XP & Message count
|
||||
stats.sentMessage(message)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command(self, ctx):
|
||||
"""
|
||||
Function called whenever someone invokes a command.
|
||||
Logs commands in your terminal.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
DM = ctx.guild is None
|
||||
print("{} in {}: {}".format(ctx.author.display_name,
|
||||
"DM" if DM else "{} ({})".format(ctx.channel.name, ctx.guild.name),
|
||||
ctx.message.content))
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx, err):
|
||||
"""
|
||||
Function called when a command throws an error.
|
||||
:param ctx: Discord Context
|
||||
:param err: the error thrown
|
||||
"""
|
||||
# Zandbak Didier shouldn't spam the error logs
|
||||
if self.client.user.id == int(constants.coolerDidierId):
|
||||
raise err
|
||||
|
||||
# Don't handle commands that have their own custom error handler
|
||||
if hasattr(ctx.command, 'on_error'):
|
||||
return
|
||||
# Someone just mentioned Didier without calling a real command,
|
||||
# don't care about this error
|
||||
if isinstance(err, (commands.CommandNotFound, commands.CheckFailure, commands.TooManyArguments, commands.ExpectedClosingQuoteError), ):
|
||||
pass
|
||||
# Someone used a command that was on cooldown
|
||||
elif isinstance(err, commands.CommandOnCooldown):
|
||||
await ctx.send("Je kan dit commando niet (meer) spammen.", delete_after=10)
|
||||
# Someone forgot an argument or passed an invalid argument
|
||||
elif isinstance(err, (commands.BadArgument, commands.MissingRequiredArgument)):
|
||||
await ctx.send("Controleer je argumenten.")
|
||||
else:
|
||||
# Remove the InvokeCommandError because it's useless information
|
||||
x = traceback.format_exception(type(err), err, err.__traceback__)
|
||||
errorString = ""
|
||||
for line in x:
|
||||
if "direct cause of the following" in line:
|
||||
break
|
||||
errorString += line.replace("*", "") + "\n" if line.strip() != "" else ""
|
||||
await self.sendErrorEmbed(ctx, err, errorString)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_add(self, react):
|
||||
"""
|
||||
Function called when someone adds a reaction to a message.
|
||||
:param react: the RawReactionEvent associated with the reaction
|
||||
"""
|
||||
# Ignore RPS adding reacts
|
||||
if self.client.get_user(react.user_id).bot:
|
||||
return
|
||||
# Feature request
|
||||
if str(react.emoji) == "➕":
|
||||
await self.sendReactEmbed(react, "Feature Request")
|
||||
# Bug report
|
||||
elif str(react.emoji) == "🐛":
|
||||
await self.sendReactEmbed(react, "Bug Report")
|
||||
# Muttn react
|
||||
elif str(react.emoji) == "<:Muttn:761551956346798111>":
|
||||
await self.addMuttn(react)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_raw_reaction_remove(self, react):
|
||||
"""
|
||||
Function called when someone removes a reaction from a message.
|
||||
:param react: the RawReactionEvent associated with the reaction
|
||||
"""
|
||||
# Decrease Muttn counter
|
||||
if str(react.emoji) == "<:Muttn:761551956346798111>":
|
||||
await self.removeMuttn(react)
|
||||
|
||||
async def removeMuttn(self, react):
|
||||
"""
|
||||
Function that decreases the Muttn counter for someone.
|
||||
:param react: the RawReactionEvent associated with the reaction
|
||||
"""
|
||||
# Get the Message instance of the message
|
||||
channel = self.client.get_channel(react.channel_id)
|
||||
message = await channel.fetch_message(react.message_id)
|
||||
muttn.removeMuttn(message)
|
||||
|
||||
async def addMuttn(self, react):
|
||||
"""
|
||||
Function that checks the Muttn counter for a message.
|
||||
:param react: the RawReactionEvent associated with the reaction
|
||||
"""
|
||||
count = -1
|
||||
# Get the Message instance of the message
|
||||
channel = self.client.get_channel(react.channel_id)
|
||||
message = await channel.fetch_message(react.message_id)
|
||||
|
||||
# Get the amount of reacts on this message
|
||||
for reaction in message.reactions:
|
||||
if str(reaction.emoji) == "<:Muttn:761551956346798111>":
|
||||
count = reaction.count
|
||||
for user in await reaction.users().flatten():
|
||||
# Remove bot reacts
|
||||
if user.bot:
|
||||
count -= 1
|
||||
break
|
||||
|
||||
# React was removed in the milliseconds the fetch_message needs to get the info
|
||||
if count <= 0:
|
||||
return
|
||||
|
||||
# Update the db
|
||||
muttn.muttn(message.author.id, count, message.id)
|
||||
|
||||
def reactCheck(self, react, msg):
|
||||
"""
|
||||
Function that checks if feature requests/bug reports have been sent already.
|
||||
:param react: the RawReactionEvent associated with the reaction
|
||||
:param msg: the message this react was placed on
|
||||
"""
|
||||
# # Blacklist NinjaJay after spamming
|
||||
# if react.user_id in [153162010576551946]:
|
||||
# return False
|
||||
|
||||
# Don't spam DM's when something has already been reported
|
||||
# Check if the react's count is 1
|
||||
for reaction in msg.reactions:
|
||||
if reaction.emoji == react.emoji.name:
|
||||
return reaction.count == 1
|
||||
|
||||
async def sendReactEmbed(self, react, messageType):
|
||||
"""
|
||||
Function that sends a message in Zandbak with what's going on.
|
||||
:param react: the RawReactionEvent associated with the reaction
|
||||
:param messageType: the type of message to send
|
||||
"""
|
||||
channel = self.client.get_channel(react.channel_id)
|
||||
msg = await channel.fetch_message(react.message_id)
|
||||
|
||||
# Didn't pass the check, ignore it
|
||||
if not self.reactCheck(react, msg):
|
||||
return
|
||||
|
||||
typeChannels = {"Feature Request": int(constants.FeatureRequests), "Bug Report": int(constants.BugReports)}
|
||||
|
||||
# Add a 10 second cooldown to requests/reports to avoid spam
|
||||
# even tho apparently the people don't care
|
||||
if round(time.time()) - (
|
||||
self.lastFeatureRequest if messageType == "Feature Request" else self.lastBugReport) < 10:
|
||||
await channel.send("Je moet even wachten vooraleer je nog een {} maakt.".format(messageType.lower()))
|
||||
await msg.add_reaction("🕐")
|
||||
return
|
||||
# Report on an empty message
|
||||
elif msg.content == "":
|
||||
await channel.send("Dit bericht bevat geen tekst.")
|
||||
await msg.add_reaction("❌")
|
||||
return
|
||||
|
||||
# Update the variables
|
||||
if messageType == "Feature Request":
|
||||
self.lastFeatureRequest = round(time.time())
|
||||
else:
|
||||
self.lastBugReport = round(time.time())
|
||||
|
||||
# Ignore people reacting on Didier's messages
|
||||
if str(msg.author.id) != constants.didierId:
|
||||
# Get the user's User instance & the channel to send the message to
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
user = COC.get_member(react.user_id)
|
||||
targetChannel = self.client.get_channel(typeChannels[messageType])
|
||||
|
||||
await targetChannel.send("{} door **{}** in **#{}** ({}):\n``{}``\n{}".format(
|
||||
messageType,
|
||||
user.display_name,
|
||||
channel.name if str(channel.type) != "private" else "DM",
|
||||
channel.guild.name if str(channel.type) != "private" else "DM",
|
||||
msg.content, msg.jump_url
|
||||
))
|
||||
await msg.add_reaction("✅")
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message_edit(self, before, after):
|
||||
"""
|
||||
Function called when a message is edited,
|
||||
so people can't edit messages in FreeGames to cheat the system.
|
||||
:param before: the message before it was edited
|
||||
:param after: the message after it was edited
|
||||
"""
|
||||
# Run the message through the checks again
|
||||
if not checks.freeGamesCheck(after):
|
||||
await self.failedChecksCog.freeGames(after)
|
||||
|
||||
async def sendErrorEmbed(self, ctx, error: Exception, trace):
|
||||
"""
|
||||
Function that sends an error embed in #ErrorLogs.
|
||||
:param ctx: Discord Context
|
||||
:param error: the error thrown
|
||||
:param trace: the stacktrace of the error
|
||||
"""
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.set_author(name="Error")
|
||||
embed.add_field(name="Command:", value="{} in {}: {}".format(ctx.author.display_name,
|
||||
ctx.channel.name if str(
|
||||
ctx.channel.type) != "private" else "DM",
|
||||
ctx.message.content), inline=False)
|
||||
embed.add_field(name="Error:", value=str(error)[:1024], inline=False)
|
||||
embed.add_field(name="Message:", value=str(trace)[:1024], inline=False)
|
||||
|
||||
# Add remaining parts in extra fields
|
||||
# (embed field limits)
|
||||
if len(str(trace)) < 5500:
|
||||
trace_split = [str(trace)[i:i + 1024] for i in range(1024, len(str(trace)), 1024)]
|
||||
for spl in trace_split:
|
||||
embed.add_field(name="\u200b", value=spl, inline=False)
|
||||
|
||||
errorChannel = self.client.get_channel(762668505455132722)
|
||||
await errorChannel.send(embed=embed)
|
||||
|
||||
@commands.command(hidden=True)
|
||||
@commands.check(checks.isMe)
|
||||
async def lock(self, ctx, until=None):
|
||||
"""
|
||||
Command that locks the server during online exams.
|
||||
:param ctx: Discord Context
|
||||
:param until: the timestamp until which to lock (HH:MM)
|
||||
"""
|
||||
# No timestamp passed
|
||||
if until is None:
|
||||
return
|
||||
|
||||
until = until.split(":")
|
||||
|
||||
# Create timestamps
|
||||
now = datetime.datetime.now()
|
||||
untilTimestamp = time.time()
|
||||
|
||||
# Gets the current amount of minutes into the day
|
||||
nowMinuteCount = (now.hour * 60) + now.minute
|
||||
|
||||
# Gets the target amount of minutes into the day
|
||||
untilMinuteCount = (int(until[0]) * 60) + int(until[1])
|
||||
|
||||
# Adds the remaining seconds onto the current time to calculate the end of the lock
|
||||
untilTimestamp += (60 * (untilMinuteCount - nowMinuteCount)) - now.second
|
||||
|
||||
self.client.locked = True
|
||||
self.client.lockedUntil = round(untilTimestamp)
|
||||
|
||||
await ctx.send("De server wordt gelocked tot **{}**.".format(
|
||||
datetime.datetime.fromtimestamp(untilTimestamp,
|
||||
pytz.timezone("Europe/Brussels")
|
||||
).strftime('%H:%M:%S')))
|
||||
|
||||
@commands.command(hidden=True)
|
||||
@commands.check(checks.isMe)
|
||||
async def unlock(self, ctx):
|
||||
"""
|
||||
Command to unlock the server manually before the timer is over.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
self.client.locked = False
|
||||
self.client.lockedUntil = -1
|
||||
await ctx.send("De server is niet langer gelocked.")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Events(client))
|
|
@ -1,28 +0,0 @@
|
|||
from data import constants
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
# Cog that handles failure of checks
|
||||
# Has to be a Cog to have access to the Client
|
||||
class FailedChecks(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# User posted in #FreeGames without being allowed to do so
|
||||
async def freeGames(self, ctx):
|
||||
content = ctx.content
|
||||
errorChannel = self.client.get_channel(int(constants.ErrorLogs))
|
||||
|
||||
await self.utilsCog.removeMessage(ctx)
|
||||
await self.utilsCog.sendDm(ctx.author.id,
|
||||
"Je bericht \n`{}`\n werd verwijderd uit #FreeGames omdat het geen link "
|
||||
"bevatte.\nPost AUB enkel links in dit kanaal.\n*Als je bericht onterecht "
|
||||
"verwijderd werd, stuur dan een DM naar DJ STIJN.*".format(content))
|
||||
await errorChannel.send("`{}`\nDoor **{}** werd verwijderd uit #FreeGames.".format(content,
|
||||
ctx.author.display_name))
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(FailedChecks(client))
|
178
cogs/faq.py
178
cogs/faq.py
|
@ -1,178 +0,0 @@
|
|||
from data import constants
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import stringFormatters, checks
|
||||
from functions.database import faq
|
||||
|
||||
|
||||
class Faq(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="FAQ", usage="[Categorie]* [@Personen]*", case_insensitive=True, invoke_without_command=True)
|
||||
@help.Category(category=Category.Other)
|
||||
async def faq(self, ctx, *args):
|
||||
"""
|
||||
Command group that controls the FAQ commands.
|
||||
When this command is invoked, it sends a list of valid categories.
|
||||
|
||||
After invoking in a subject's channel (without passing a category),
|
||||
it sends the FAQ for that subject instead.
|
||||
:param ctx: Discord Context
|
||||
:param args: args passed
|
||||
"""
|
||||
# A category was requested
|
||||
# This is not the cleanest but 80 subcommands is a bit much
|
||||
if len(args) != 0 and any("@" not in arg for arg in args):
|
||||
return await self.faqCategory(ctx, args)
|
||||
|
||||
# Check if the command was used in a subject's channel
|
||||
if ctx.channel.id in constants.faq_channels:
|
||||
return await self.faqCategory(ctx, (constants.faq_channels[ctx.channel.id],))
|
||||
|
||||
# List of all categories with the first letter capitalized
|
||||
resp = [stringFormatters.titleCase(cat[0]) for cat in faq.getCategories()]
|
||||
|
||||
# Sort alphabetically
|
||||
resp.sort()
|
||||
|
||||
# Create an embed with all the categories
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="FAQ Categorieën")
|
||||
embed.description = "\n".join(resp)
|
||||
|
||||
# Check if the embed has to be sent to the user
|
||||
# or if the user tagged anyone
|
||||
if len(ctx.message.mentions) == 0:
|
||||
await ctx.author.send(embed=embed)
|
||||
else:
|
||||
embed.set_footer(text="Doorgestuurd door {}".format(ctx.author.display_name))
|
||||
# Send it to everyone that was mentioned
|
||||
for person in ctx.message.mentions:
|
||||
if not person.bot:
|
||||
await person.send(embed=embed)
|
||||
|
||||
@faq.command(hidden=True, name="Add", usage="[Category] [Question]* [Answer]*")
|
||||
@commands.check(checks.isMe)
|
||||
async def add(self, ctx, category, question=None, answer=None, answer_markdown=None):
|
||||
"""
|
||||
Command to add a FAQ to the database
|
||||
:param ctx: Discord Context
|
||||
:param category: the category to add the FAQ to
|
||||
:param question: the question
|
||||
:param answer: the answer
|
||||
:param answer_markdown: a version of the answer with markdown applied
|
||||
"""
|
||||
# Add a new category
|
||||
if question is None or answer is None:
|
||||
faq.addCategory(category)
|
||||
await ctx.send("**{}** is toegevoegd.".format(category))
|
||||
else:
|
||||
# Add a new question/answer couple to a category
|
||||
faq.addQuestion(category, question, answer, answer_markdown)
|
||||
await ctx.send("``{}\n{}`` is toegevoegd in {}.".format(question, answer, category))
|
||||
|
||||
# Quotes a specific line of the fac instead of DM'ing the entire thing
|
||||
@faq.command(name="Quote", aliases=["Q"], usage="[Categorie] [Index]")
|
||||
@help.Category(category=Category.Other)
|
||||
async def quote(self, ctx, category, index):
|
||||
"""
|
||||
Command that quotes 1 line of the FAQ into the current channel.
|
||||
:param ctx: Discord Context
|
||||
:param category: the category of the FAQ
|
||||
:param index: the index in the list to quote
|
||||
:return:y
|
||||
"""
|
||||
# Check if a (valid) number was passed
|
||||
try:
|
||||
index = int(index)
|
||||
if index < 1:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
await ctx.send("Dit is geen geldig getal.")
|
||||
|
||||
# Create a list of categories
|
||||
categories = [t[0] for t in faq.getCategories()]
|
||||
|
||||
# Check if a valid category was passed
|
||||
if category.lower() not in categories:
|
||||
return await ctx.send("Dit is geen geldige categorie.")
|
||||
|
||||
resp = faq.getCategory(category.lower())
|
||||
|
||||
# Check if this index exists in this category
|
||||
if len(resp) < index:
|
||||
return await ctx.send("Dit is geen geldig getal.")
|
||||
|
||||
# Sort by entry Id
|
||||
resp.sort(key=lambda x: int(x[0]))
|
||||
|
||||
await ctx.send("**{}**\n{}".format(resp[index - 1][2], resp[index - 1][3]))
|
||||
|
||||
async def faqCategory(self, ctx, args):
|
||||
"""
|
||||
Function that sends everything from a category.
|
||||
:param ctx: Discord Context
|
||||
:param args: the args passed
|
||||
"""
|
||||
|
||||
# Create a list of categories
|
||||
categories = [t[0] for t in faq.getCategories()]
|
||||
|
||||
# Random word was passed as a category
|
||||
if not any(arg.lower() in categories for arg in args):
|
||||
return await self.sendErrorEmbed(ctx, "Dit is geen geldige categorie.")
|
||||
elif len(args) - len(ctx.message.mentions) != 1:
|
||||
# Multiple categories were requested, which is not allowed
|
||||
return await self.sendErrorEmbed(ctx, "Controleer je argumenten.")
|
||||
|
||||
category = ""
|
||||
|
||||
# Find the category the user requested
|
||||
for word in args:
|
||||
if word.lower() in categories:
|
||||
category = word
|
||||
break
|
||||
|
||||
resp = faq.getCategory(category.lower())
|
||||
|
||||
# Sort by entry Id
|
||||
resp.sort(key=lambda x: int(x[0]))
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="FAQ {}".format(stringFormatters.titleCase(category)))
|
||||
|
||||
# Add everything into the embed
|
||||
for i, pair in enumerate(resp):
|
||||
# Add custom markdown if it exists
|
||||
embed.add_field(name="#{}: {}".format(str(i + 1), pair[2]), value=pair[3] if pair[4] is None else pair[4], inline=False)
|
||||
|
||||
# Check if anyone was tagged to send the embed to
|
||||
if len(ctx.message.mentions) == 0:
|
||||
await ctx.author.send(embed=embed)
|
||||
else:
|
||||
embed.set_footer(text="Doorgestuurd door {}".format(ctx.author.display_name))
|
||||
# Author tagged some people to send it to
|
||||
for person in ctx.message.mentions:
|
||||
await person.send(embed=embed)
|
||||
|
||||
async def sendErrorEmbed(self, ctx, message: str):
|
||||
"""
|
||||
Function that sends an error embed.
|
||||
:param ctx: Discord Context
|
||||
:param message: the message to put in the embed
|
||||
"""
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.set_author(name="Error")
|
||||
embed.description = message
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Faq(client))
|
|
@ -1,89 +0,0 @@
|
|||
from bs4 import BeautifulSoup
|
||||
import datetime
|
||||
from decorators import help
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, config
|
||||
import requests
|
||||
import tabulate
|
||||
|
||||
|
||||
class Football(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return checks.isMe(ctx) and not self.client.locked
|
||||
|
||||
@commands.group(name="Jpl", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Sports)
|
||||
async def jpl(self, ctx, *args):
|
||||
pass
|
||||
|
||||
@jpl.command(name="Matches", aliases=["M"], usage="[Week]*")
|
||||
async def matches(self, ctx, *args):
|
||||
args = list(args)
|
||||
if not args:
|
||||
args = [str(config.get("jpl_day"))]
|
||||
if all(letter.isdigit() for letter in args[0]):
|
||||
current_day = requests.get("https://api.sporza.be/web/soccer/matchdays/161733/{}".format(args[0])).json()
|
||||
current_day = current_day["groupedMatches"][0]["matches"]
|
||||
|
||||
# Create dictionaries for every match
|
||||
matches_formatted = {}
|
||||
for i, match in enumerate(current_day):
|
||||
matchDic = {"home": match["homeTeam"]["name"], "away": match["awayTeam"]["name"]}
|
||||
|
||||
# Add date
|
||||
matchDate = datetime.datetime.strptime(match["startDateTime"].split("+")[0], "%Y-%m-%dT%H:%M:%S.%f")
|
||||
matchDic["date"] = matchDate.strftime("%d/%m")
|
||||
matchDic["day"] = self.get_weekday(matchDate.weekday())
|
||||
|
||||
# TODO check back when there's active games (to find the key in the dict) & add the current time if not over
|
||||
# Add scores
|
||||
if match["status"] == "END": # Status != [not_yet_started] whatever it is
|
||||
matchDic["score"] = "{} - {}".format(match["homeScore"], match["awayScore"])
|
||||
else:
|
||||
# If there's no score, show when the match starts
|
||||
matchDic["score"] = "{}:{}".format(
|
||||
("" if len(str(matchDate.hour)) == 2 else "0") + str(matchDate.hour), # Leading Zero
|
||||
("" if len(str(matchDate.minute)) == 2 else "0") + str(matchDate.minute)) # Leading Zero
|
||||
|
||||
matches_formatted[i] = matchDic
|
||||
|
||||
# Put every formatted version of the matches in a list
|
||||
matchList = list([self.format_match(matches_formatted[match]) for match in matches_formatted])
|
||||
await ctx.send("```Jupiler Pro League - Speeldag {}\n\n{}```".format(args[0], tabulate.tabulate(matchList, headers=["Dag", "Datum", "Thuis", "Stand", "Uit", "Tijd"])))
|
||||
else:
|
||||
return await ctx.send("Dit is geen geldige speeldag.")
|
||||
|
||||
# TODO check back when there's active games & add the timestamp instead of EINDE
|
||||
def format_match(self, match):
|
||||
return [match["day"], match["date"], match["home"], match["score"], match["away"], "Einde"]
|
||||
|
||||
def get_weekday(self, day: int):
|
||||
days = ["Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"]
|
||||
return days[day]
|
||||
|
||||
@jpl.command(name="Table", aliases=["Ranking", "Rankings", "Ranks", "T"])
|
||||
async def table(self, ctx, *args):
|
||||
page_html = requests.get("https://sporza.be/nl/categorie/voetbal/jupiler-pro-league/").text
|
||||
bs_parsed = BeautifulSoup(page_html, "html.parser")
|
||||
rows = bs_parsed.find(summary="algemeen klassement").find_all("tr")[1:]
|
||||
rowsFormatted = []
|
||||
for row in rows:
|
||||
rowsFormatted.append(self.createRowList(row))
|
||||
await ctx.send("```Jupiler Pro League Klassement\n\n{}```".format(tabulate.tabulate(rowsFormatted, headers=["#", "Ploeg", "Punten", "M", "M+", "M-", "M="])))
|
||||
|
||||
# Formats the row into an list that can be passed to Tabulate
|
||||
def createRowList(self, row):
|
||||
scoresArray = list([td.renderContents().decode("utf-8") for td in row.find_all("td")])[:6]
|
||||
# Insert the team name into the list
|
||||
scoresArray.insert(1, row.find_all("a")[0].renderContents().decode("utf-8").split("<!--")[0])
|
||||
return scoresArray
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Football(client))
|
182
cogs/fun.py
182
cogs/fun.py
|
@ -1,182 +0,0 @@
|
|||
from data import paginatedLeaderboard
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, mock, stringFormatters
|
||||
from functions.database import memes, trump, dadjoke
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import requests
|
||||
|
||||
|
||||
class Fun(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Dadjoke", aliases=["Dj", "Dad"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Fun)
|
||||
async def dadjoke(self, ctx):
|
||||
"""
|
||||
Command that sends a random dad joke.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
await ctx.send(dadjoke.getRandomJoke())
|
||||
|
||||
@commands.command(name="Stalin", aliases=["Ss", "StalinMotivation", "Motivate"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Quotes)
|
||||
async def stalin(self, ctx):
|
||||
"""
|
||||
Command that sends a random Stalin quote.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
with open("files/StalinMotivation.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
await ctx.send(file[random.randint(1, len(file))])
|
||||
|
||||
@commands.command(name="Satan", aliases=["S8n", "SatanQuote"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Quotes)
|
||||
async def satan(self, ctx):
|
||||
"""
|
||||
Command that sends a random Satan quote.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
with open("files/SatanQuotes.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
await ctx.send(file[random.randint(1, len(file))])
|
||||
|
||||
@commands.command(name="Trump")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Quotes)
|
||||
async def trump(self, ctx):
|
||||
"""
|
||||
Command that sends a random Trump quote.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
quote = trump.getRandomQuote()
|
||||
await ctx.send("**\"{}**\"\n{} - {}".format(quote[1], quote[2], quote[3]))
|
||||
|
||||
@commands.command(name="8-Ball", aliases=["8b", "8Ball"], ignore_extra=True)
|
||||
@help.Category(category=Category.Quotes)
|
||||
async def eightball(self, ctx):
|
||||
"""
|
||||
Command that sends a random 8-ball response.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
with open("files/eightball.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
await ctx.send(file[random.randint(0, len(file) - 1)])
|
||||
|
||||
@commands.command(name="Memegen", usage="[Meme] [Velden]")
|
||||
@commands.cooldown(1, 5, commands.BucketType.guild)
|
||||
@help.Category(category=Category.Fun)
|
||||
async def memegen(self, ctx, name="", *fields):
|
||||
"""
|
||||
Command that generates memes.
|
||||
:param ctx: Discord Context
|
||||
:param name: the name of the meme
|
||||
:param fields: the fields to add to the meme
|
||||
"""
|
||||
if len(fields) == 0:
|
||||
return await ctx.send("Controleer je argumenten.")
|
||||
|
||||
# Get the meme info that corresponds to this name
|
||||
result = memes.getMeme(name)
|
||||
|
||||
# No meme found
|
||||
if not result[0]:
|
||||
return await ctx.send(result[1])
|
||||
|
||||
# Convert to list to support item assignment
|
||||
fields = list(fields)
|
||||
|
||||
# If there's only one field, the user isn't required to use quotes
|
||||
if result[1][2] == 1:
|
||||
fields = [" ".join(fields)]
|
||||
|
||||
# Apply mock to mocking spongebob memes
|
||||
if result[1][1] == "mocking spongebob":
|
||||
fields = list(map(mock.mock, fields))
|
||||
|
||||
# X, X everywhere only takes X as an argument
|
||||
if result[1][1] == "x, x everywhere":
|
||||
fields[0] = " ".join(fields)
|
||||
fields.append(fields[0] + " everywhere")
|
||||
|
||||
# List of fields to send to the API
|
||||
boxes = [{"text": ""}, {"text": ""}, {"text": ""}, {"text": ""}]
|
||||
|
||||
# Add all fields required & ignore the excess ones
|
||||
for i in range(len(fields)):
|
||||
if i > 3:
|
||||
break
|
||||
boxes[i]["text"] = fields[i]
|
||||
|
||||
# Check server status
|
||||
req = requests.get('https://api.imgflip.com/get_memes').json()
|
||||
|
||||
if req["success"]:
|
||||
caption = {
|
||||
"template_id": result[1][0],
|
||||
"username": os.getenv("IMGFLIPNAME"),
|
||||
"password": os.getenv("IMGFLIPPASSWORD"),
|
||||
"boxes[0][text]": boxes[0]["text"],
|
||||
"boxes[1][text]": boxes[1]["text"],
|
||||
"boxes[2][text]": boxes[2]["text"],
|
||||
"boxes[3][text]": boxes[3]["text"]
|
||||
}
|
||||
|
||||
# Send the POST to the API
|
||||
memeReply = requests.post('https://api.imgflip.com/caption_image', caption).json()
|
||||
|
||||
if memeReply['success']:
|
||||
await ctx.send(str(memeReply['data']['url']))
|
||||
await self.utilsCog.removeMessage(ctx.message)
|
||||
else:
|
||||
await ctx.send(
|
||||
"Error! Controleer of je de juiste syntax hebt gebruikt. Gebruik het commando "
|
||||
"\"memes\" voor een lijst aan geaccepteerde meme-namen.")
|
||||
else:
|
||||
await ctx.send("Er is een fout opgetreden.")
|
||||
|
||||
@commands.command(name="Memes")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Fun)
|
||||
async def memes(self, ctx):
|
||||
"""
|
||||
Command that shows a list of memes in the database.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
memeList = memes.getAllMemes()
|
||||
|
||||
# Turn the list into a list of [Name: fields]
|
||||
memeList = [": ".join([stringFormatters.titleCase(meme[1]),
|
||||
str(meme[2])]) for meme in sorted(memeList, key=lambda x: x[1])]
|
||||
|
||||
pages = paginatedLeaderboard.Pages(source=paginatedLeaderboard.Source(memeList, "Memes", discord.Colour.blue()),
|
||||
clear_reactions_after=True)
|
||||
await pages.start(ctx)
|
||||
|
||||
@commands.command(name="Pjoke")
|
||||
@help.Category(category=Category.Fun)
|
||||
async def pjoke(self, ctx):
|
||||
"""
|
||||
Command that sends a random programming joke.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
r = requests.get("https://official-joke-api.appspot.com/jokes/programming/random").json()
|
||||
await ctx.send(r[0]["setup"] + "\n" + r[0]["punchline"])
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Fun(client))
|
257
cogs/games.py
257
cogs/games.py
|
@ -1,257 +0,0 @@
|
|||
from converters.numbers import Abbreviated, abbreviated
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, dinks
|
||||
from functions.database import currency, stats
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
|
||||
|
||||
class Games(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Coinflip", aliases=["Cf"], usage="[Inzet]* [Aantal]*", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Gamble)
|
||||
async def coinflip(self, ctx, *args):
|
||||
"""
|
||||
Command to flip a coin, optionally for Didier Dinks.
|
||||
:param ctx: Discord Context
|
||||
:param args: bet & wager
|
||||
"""
|
||||
args = list(args)
|
||||
choices = ["Kop", "Munt"]
|
||||
result = random.choice(choices)
|
||||
|
||||
# No choice made & no wager
|
||||
if len(args) == 0:
|
||||
await ctx.send("**{}**!".format(result))
|
||||
self.updateStats("cf", "h" if result == "Kop" else "t")
|
||||
return
|
||||
|
||||
# Check for invalid args
|
||||
if len(args) == 1 or args[0][0].lower() not in "kmht":
|
||||
return await ctx.send("Controleer je argumenten.")
|
||||
|
||||
args[1] = abbreviated(args[1])
|
||||
valid = checks.isValidAmount(ctx, args[1])
|
||||
|
||||
# Invalid amount
|
||||
if not valid[0]:
|
||||
return await ctx.send(valid[1])
|
||||
|
||||
# Allow full words, abbreviations, and English alternatives
|
||||
args[0] = "k" if args[0][0].lower() == "k" or args[0][0].lower() == "h" else "m"
|
||||
won = await self.gamble(ctx, args[0], result, valid[1], 2)
|
||||
|
||||
if won:
|
||||
s = stats.getOrAddUser(ctx.author.id)
|
||||
stats.update(ctx.author.id, "cf_wins", int(s[8]) + 1)
|
||||
stats.update(ctx.author.id, "cf_profit", float(s[9]) + float(valid[1]))
|
||||
|
||||
self.updateStats("cf", "h" if result == "Kop" else "t")
|
||||
|
||||
@coinflip.command(name="Stats", hidden=True)
|
||||
async def cf_stats(self, ctx):
|
||||
return await self.client.get_cog("Stats").callStats("cf", ctx)
|
||||
|
||||
@commands.group(name="Dice", aliases=["Roll"], usage="[Inzet]* [Aantal]*", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Gamble)
|
||||
async def dice(self, ctx, *args):
|
||||
"""
|
||||
Command to roll a dice, optionally for Didier Dinks.
|
||||
:param ctx: Discord Context
|
||||
:param args: bet & wager
|
||||
"""
|
||||
args = list(args)
|
||||
result = random.randint(1, 6)
|
||||
|
||||
# No choice made & no wager
|
||||
if len(args) == 0:
|
||||
self.updateStats("dice", result)
|
||||
return await ctx.send(":game_die: **{}**!".format(result))
|
||||
|
||||
# Check for invalid args
|
||||
if len(args) == 1 or not args[0].isdigit() or not 0 < int(args[0]) < 7:
|
||||
return await ctx.send("Controleer je argumenten.")
|
||||
|
||||
args[1] = abbreviated(args[1])
|
||||
valid = checks.isValidAmount(ctx, args[1])
|
||||
|
||||
# Invalid amount
|
||||
if not valid[0]:
|
||||
return await ctx.send(valid[1])
|
||||
|
||||
await self.gamble(ctx, args[0], str(result), valid[1], 6, ":game_die: ")
|
||||
self.updateStats("dice", result)
|
||||
|
||||
@dice.command(name="Stats", hidden=True)
|
||||
async def dice_stats(self, ctx):
|
||||
await self.client.get_cog("Stats").callStats("dice", ctx)
|
||||
|
||||
async def gamble(self, ctx, bet, result, wager, factor, pre="", post=""):
|
||||
"""
|
||||
Function for gambling because it's the same thing every time.
|
||||
:param ctx: Discord Context
|
||||
:param bet: the option the user bet on
|
||||
:param result: randomly generated result
|
||||
:param wager: size of the bet of the user
|
||||
:param factor: the factor by which the person's wager is amplified
|
||||
:param pre: any string that might have to be pre-pended to the output string
|
||||
:param post: any string that might have to be appended to the output string
|
||||
:return: a boolean indicating whether or not the user has won
|
||||
"""
|
||||
# Code no longer first removes your bet to then add profit,
|
||||
# resulting in triple coinflip profit (@Clement).
|
||||
# Subtract one off of the factor to compensate for the initial wager
|
||||
factor -= 1
|
||||
answer = "**{}**! ".format(result)
|
||||
won = False
|
||||
|
||||
# Check if won
|
||||
if result[0].lower() == bet[0].lower():
|
||||
won = True
|
||||
answer += "Je wint **{:,}** Didier Dink{}"
|
||||
currency.update(ctx.author.id, "dinks", float(currency.dinks(ctx.author.id)) + (float(wager) * factor))
|
||||
else:
|
||||
answer += "Je hebt je inzet (**{:,}** Didier Dink{}) verloren"
|
||||
currency.update(ctx.author.id, "dinks", float(currency.dinks(ctx.author.id)) - float(wager))
|
||||
self.loseDinks(round(float(wager)))
|
||||
|
||||
# If won -> multiple dinkS, if lost, it's possible that the user only bet on 1 dinK
|
||||
await ctx.send(pre + answer.format(round(float(wager) * factor if won else float(wager)),
|
||||
checks.pluralS(float(wager) * factor if won else float(wager))) +
|
||||
", **{}**!".format(ctx.author.display_name))
|
||||
return won
|
||||
|
||||
@commands.group(name="Slots", usage="[Aantal]", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Gamble)
|
||||
async def slots(self, ctx, wager: Abbreviated = None):
|
||||
"""
|
||||
Command to play slot machines.
|
||||
:param ctx: Discord Context
|
||||
:param wager: the amount of Didier Dinks to bet with
|
||||
"""
|
||||
valid = checks.isValidAmount(ctx, wager)
|
||||
# Invalid amount
|
||||
if not valid[0]:
|
||||
return await ctx.send(valid[1])
|
||||
|
||||
ratios = dinks.getRatios()
|
||||
|
||||
def randomKey():
|
||||
return random.choice(list(ratios.keys()))
|
||||
|
||||
def generateResults():
|
||||
return [randomKey(), randomKey(), randomKey()]
|
||||
|
||||
# Generate the result
|
||||
result = generateResults()
|
||||
|
||||
textFormatted = "{}\n{}\n:yellow_square:{}:yellow_square:\n:arrow_right:{}:arrow_left::part_alternation_mark:\n" \
|
||||
":yellow_square:{}:yellow_square: :red_circle:\n{}\n{}".format(
|
||||
dinks.slotsHeader, dinks.slotsEmptyRow,
|
||||
"".join(generateResults()), "".join(result), "".join(generateResults()),
|
||||
dinks.slotsEmptyRow, dinks.slotsFooter)
|
||||
|
||||
await ctx.send(textFormatted)
|
||||
|
||||
# Everything different -> no profit
|
||||
if len(set(result)) == 3:
|
||||
await ctx.send("Je hebt je inzet (**{:,}** Didier Dinks) verloren, **{}**.".format(
|
||||
math.floor(float(valid[1])), ctx.author.display_name
|
||||
))
|
||||
currency.update(ctx.author.id, "dinks", float(currency.dinks(ctx.author.id)) - math.floor(float(valid[1])))
|
||||
return
|
||||
|
||||
# Calculate the profit multiplier
|
||||
multiplier = 1.0
|
||||
for symbol in set(result):
|
||||
multiplier *= ratios[symbol][result.count(symbol) - 1]
|
||||
|
||||
await ctx.send(":moneybag: Je wint **{:,}** Didier Dinks, **{}**! :moneybag:".format(
|
||||
round(float(valid[1]) * multiplier, 2), ctx.author.display_name
|
||||
))
|
||||
currency.update(ctx.author.id, "dinks",
|
||||
float(currency.dinks(ctx.author.id)) + (float(valid[1]) * multiplier) - math.floor(
|
||||
float(valid[1])))
|
||||
# Current Dinks - wager + profit
|
||||
|
||||
# Returns list of profits
|
||||
@slots.command(name="Chart", aliases=["Symbols", "Profit"])
|
||||
async def chart(self, ctx):
|
||||
"""
|
||||
Command to show the profit distributions for the Didier Slotmachines.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Slots Profit Chart")
|
||||
ratios = dinks.getRatios()
|
||||
|
||||
# Add every symbol into the embed
|
||||
for ratio in ratios:
|
||||
embed.add_field(name=ratio, value="1: x{}\n2: x{}\n3: x{}".format(
|
||||
str(ratios[ratio][0]), str(ratios[ratio][1]), str(ratios[ratio][2])
|
||||
))
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.group(name="Lost", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Gamble)
|
||||
async def lost(self, ctx):
|
||||
"""
|
||||
Command that shows the amount of Didier Dinks that have been lost due to gambling.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
await ctx.send("Er zijn al **{:,}** Didier Dinks verloren sinds 13/03/2020."
|
||||
.format(dinks.lost()))
|
||||
|
||||
@lost.command(name="Today")
|
||||
async def today(self, ctx):
|
||||
"""
|
||||
Command that shows the amount of Didier Dinks lost today.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
await ctx.send("Er zijn vandaag al **{:,}** Didier Dinks verloren."
|
||||
.format(dinks.lostToday()))
|
||||
|
||||
def updateStats(self, game, key):
|
||||
"""
|
||||
Function to update the stats file for a game.
|
||||
:param game: the game to change the stats for
|
||||
:param key: the key in the game's dict to update
|
||||
"""
|
||||
with open("files/stats.json", "r") as fp:
|
||||
s = json.load(fp)
|
||||
|
||||
s[game][str(key)] += 1
|
||||
|
||||
with open("files/stats.json", "w") as fp:
|
||||
json.dump(s, fp)
|
||||
|
||||
def loseDinks(self, amount):
|
||||
"""
|
||||
Function that adds Didier Dinks to the lost file.
|
||||
:param amount: the amount of Didier Dinks lost
|
||||
"""
|
||||
with open("files/lost.json", "r") as fp:
|
||||
fc = json.load(fp)
|
||||
fc["lost"] = fc["lost"] + round(amount)
|
||||
fc["today"] = fc["today"] + round(amount)
|
||||
with open("files/lost.json", "w") as fp:
|
||||
json.dump(fc, fp)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Games(client))
|
|
@ -1,53 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from decorators import help
|
||||
from enums.help_categories import Category
|
||||
from functions.scraping import google_search
|
||||
|
||||
|
||||
class Google(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Google", aliases=["Gtfm", "Search"], usage="[Query]", case_insensitive=True)
|
||||
@help.Category(Category.Other)
|
||||
async def google(self, ctx, *query):
|
||||
if not query:
|
||||
return await ctx.reply("Je hebt geen query opgegeven.", mention_author=True)
|
||||
|
||||
results, status = google_search(" ".join(query))
|
||||
|
||||
if results is None:
|
||||
return await ctx.send("Er ging iets fout (Response {})".format(status))
|
||||
|
||||
# Filter out all Nones
|
||||
elements = list(filter(lambda x: x is not None, results))
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Google Search")
|
||||
|
||||
# Empty list of results
|
||||
if len(elements) == 0:
|
||||
embed.description = "Geen resultaten gevonden."
|
||||
return await ctx.reply(embed=embed, mention_author=False)
|
||||
|
||||
# Cut excess results out
|
||||
if len(elements) > 10:
|
||||
elements = elements[:10]
|
||||
|
||||
links = []
|
||||
|
||||
for index, (link, title) in enumerate(elements):
|
||||
links.append("{}: [{}]({})".format(index + 1, title, link))
|
||||
|
||||
embed.description = "\n".join(links)
|
||||
|
||||
await ctx.reply(embed=embed, mention_author=False)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Google(client))
|
170
cogs/hangman.py
170
cogs/hangman.py
|
@ -1,170 +0,0 @@
|
|||
from decorators import help
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
|
||||
|
||||
def randomWord():
|
||||
lineb = random.randrange(os.stat("files/words-dutch.txt").st_size)
|
||||
with open("files/words-dutch.txt", encoding="latin-1") as file:
|
||||
file.seek(lineb)
|
||||
file.readline()
|
||||
return file.readline().rstrip().upper()
|
||||
|
||||
|
||||
class Hangman(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Hangman", aliases=["Hm"], usage="[Letter]*", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Games)
|
||||
async def hangman(self, ctx, letter=None):
|
||||
if letter and letter.strip():
|
||||
greek = "αβΓγΔδεζηΘθικΛλμνΞξΠπρΣσςτυΦφχΨψΩω"
|
||||
if len(letter) == 1 and (letter.isalpha() or letter.isdigit()) and letter not in greek:
|
||||
await self.guessLetter(ctx, letter)
|
||||
else:
|
||||
await ctx.send("Dit is geen geldige letter.")
|
||||
return
|
||||
await self.gameStatus(ctx)
|
||||
|
||||
async def guessLetter(self, ctx, letter):
|
||||
with open("files/hangman.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
|
||||
if letter.upper() in file["guessed"]:
|
||||
await ctx.send("Deze letter is al eens geprobeerd.")
|
||||
return
|
||||
|
||||
file["guessed"] += letter.upper()
|
||||
|
||||
if letter.upper() not in file["word"]:
|
||||
file["guesses"] += 1
|
||||
|
||||
if int(file["guesses"] >= 9):
|
||||
await ctx.send(self.endGame(file["word"]))
|
||||
return
|
||||
|
||||
with open("files/hangman.json", "w") as fp:
|
||||
json.dump(file, fp)
|
||||
|
||||
await self.gameStatus(ctx)
|
||||
|
||||
@hangman.command(name="Start", usage="[Woord]*")
|
||||
async def start(self, ctx, *, word=None):
|
||||
with open("files/hangman.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
|
||||
# Can't play two games at once
|
||||
if file["word"]:
|
||||
await ctx.send("Er is al een spel bezig.")
|
||||
return
|
||||
|
||||
if word:
|
||||
if not all(letter.isalpha() or letter.isdigit() or letter in [" "] for letter in word):
|
||||
await ctx.send("Dit is geen geldig woord.")
|
||||
return
|
||||
|
||||
# Can only supply your own words in DM
|
||||
if str(ctx.channel.type) != "private":
|
||||
await ctx.message.delete()
|
||||
await ctx.author.send("Het is niet slim om hangman games aan te maken waar iedereen het kan zien."
|
||||
"\nGebruik dit commando hier zodat niemand het woord op voorhand weet.")
|
||||
return
|
||||
|
||||
# Choose a random word when none were passed
|
||||
if not word:
|
||||
word = randomWord()
|
||||
|
||||
with open("files/hangman.json", "w") as fp:
|
||||
json.dump({"guessed": [], "guesses": 0, "word": word}, fp)
|
||||
|
||||
await self.gameStatus(ctx)
|
||||
|
||||
async def gameStatus(self, ctx):
|
||||
with open("files/hangman.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
|
||||
if not file["word"]:
|
||||
await ctx.send("Er is geen spel bezig.")
|
||||
return
|
||||
|
||||
guessed = " ".join(letter for letter in file["guessed"] if letter not in file["word"])
|
||||
filled = self.fillWord(file)
|
||||
|
||||
if filled.replace(" ", "") == file["word"]:
|
||||
self.clearGame()
|
||||
await ctx.send("**Het woord is geraden.**")
|
||||
await ctx.message.add_reaction("✅")
|
||||
return
|
||||
|
||||
await ctx.send("{}\n{}\nFoute letters: {}".format(filled, self.hangManString(file["guesses"]), guessed))
|
||||
|
||||
@hangman.command(name="Guess", usage="[Woord]")
|
||||
async def guess(self, ctx, *, word=None):
|
||||
if not word:
|
||||
await ctx.send("Geef een woord op.")
|
||||
return
|
||||
|
||||
with open("files/hangman.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
|
||||
if not file["word"]:
|
||||
await ctx.send("Er is geen spel bezig.")
|
||||
return
|
||||
|
||||
if word.upper() == file["word"].upper():
|
||||
self.clearGame()
|
||||
await ctx.send("**Het woord is geraden**")
|
||||
await ctx.message.add_reaction("✅")
|
||||
else:
|
||||
file["guesses"] += 1
|
||||
await ctx.send("**{}** is een foute gok.".format(word))
|
||||
await ctx.message.add_reaction("❌")
|
||||
if file["guesses"] >= 9:
|
||||
await ctx.send(self.endGame(file["word"]))
|
||||
else:
|
||||
with open("files/hangman.json", "w") as fp:
|
||||
json.dump(file, fp)
|
||||
await self.gameStatus(ctx)
|
||||
|
||||
# Create a representation of the word by filling in letters that have been guessed, and dots otherwise
|
||||
def fillWord(self, file):
|
||||
return "**" + " ".join(
|
||||
letter if letter in file["guessed"] else "." if letter.isalpha() or letter.isdigit() else letter for letter
|
||||
in file["word"]) + "**"
|
||||
|
||||
def endGame(self, word):
|
||||
self.clearGame()
|
||||
return self.hangManString(9) + "\nHet woord was **{}**.".format(word)
|
||||
|
||||
def clearGame(self):
|
||||
with open("files/hangman.json", "w") as fp:
|
||||
json.dump({"guessed": [], "guesses": 0, "word": ""}, fp)
|
||||
|
||||
def hangManString(self, number):
|
||||
dic = {
|
||||
0: "\n \n \n \n",
|
||||
1: "\n \n \n \n ===",
|
||||
2: "\n |\n |\n |\n ===",
|
||||
3: "--+---+\n |\n |\n |\n ===",
|
||||
4: "--+---+\n | O\n |\n |\n ===",
|
||||
5: "--+---+\n | O\n | |\n |\n ===",
|
||||
6: "--+---+\n | O\n | /|\n |\n ===",
|
||||
7: "--+---+\n | O\n | /|\\\n |\n ===",
|
||||
8: "--+---+\n | O\n | /|\\\n | /\n ===",
|
||||
9: "--+---+\n | O\n | /|\\\n | / \\\n ===",
|
||||
}
|
||||
return dic[number]
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Hangman(client))
|
197
cogs/help.py
197
cogs/help.py
|
@ -1,197 +0,0 @@
|
|||
from data import constants
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import categories, getCategory, Category
|
||||
import json
|
||||
|
||||
|
||||
class HelpCommand(commands.MinimalHelpCommand):
|
||||
def __init__(self, **options):
|
||||
super().__init__(**options)
|
||||
self.ctx = None
|
||||
self.target = None
|
||||
self.is_mod = False
|
||||
|
||||
# Slightly modifying the original callback in order to
|
||||
# allow sending help to DM's & checking for Enums
|
||||
# while removing cog help & checking for mentions
|
||||
async def command_callback(self, ctx, *, command=None):
|
||||
self.ctx = ctx
|
||||
self.is_mod = str(ctx.author.id) == constants.myId
|
||||
bot = ctx.bot
|
||||
if ctx.bot.locked:
|
||||
return
|
||||
|
||||
if len(ctx.message.mentions) > 5:
|
||||
return await ctx.send("Je kan Help maar naar maximaal 5 mensen doorsturen.")
|
||||
|
||||
# Send help categories
|
||||
if command is None:
|
||||
return await self.send_bot_help(self.get_bot_mapping())
|
||||
|
||||
# Check if command is a category
|
||||
if command.lower() == "mod" and not self.is_mod:
|
||||
return await self.send_error_message("Je hebt geen toegang tot deze commando's.")
|
||||
category = getCategory(command, self.is_mod)
|
||||
if category:
|
||||
return await self.send_category_help(category)
|
||||
|
||||
# Cut the mentions out & split based on subcommand
|
||||
spl = command.split(" ")
|
||||
spl = spl[:len(spl) - len(self.ctx.message.mentions)]
|
||||
|
||||
# Turn dic to lowercase to allow proper name searching
|
||||
all_commands = dict((k.lower(), v) for k, v in bot.all_commands.items())
|
||||
|
||||
if spl[0].lower() not in all_commands:
|
||||
return await self.send_error_message(await self.command_not_found(spl[0]))
|
||||
|
||||
cmd = all_commands[spl[0].lower()]
|
||||
|
||||
# Check if the entered command path exists
|
||||
for key in spl[1:]:
|
||||
try:
|
||||
all_commands = dict((k.lower(), v) for k, v in cmd.all_commands.items())
|
||||
if key.lower() not in all_commands:
|
||||
raise AttributeError
|
||||
found = all_commands[key.lower()]
|
||||
except AttributeError:
|
||||
return await self.send_error_message(await self.subcommand_not_found(cmd, key))
|
||||
cmd = found
|
||||
|
||||
# Subcommands should have the parent command's category
|
||||
temp = cmd
|
||||
while temp.parent is not None:
|
||||
temp = temp.parent
|
||||
|
||||
# Don't allow non-mods to see mod commands
|
||||
try:
|
||||
if temp.callback.category == Category.Mod and not self.is_mod:
|
||||
return await self.send_error_message("Je hebt geen toegang tot dit commando.")
|
||||
except AttributeError:
|
||||
return await self.send_error_message("Dit is geen (openbaar) commando.")
|
||||
|
||||
if isinstance(cmd, commands.Group):
|
||||
return await self.send_group_help(cmd)
|
||||
else:
|
||||
return await self.send_command_help(cmd)
|
||||
|
||||
def get_bot_mapping(self):
|
||||
return categories(self.is_mod)
|
||||
|
||||
# Sends list of commands in a category
|
||||
async def send_category_help(self, category):
|
||||
# Get a list of all commands in this category
|
||||
category_commands = [command.name if not command.callback.unpack else command
|
||||
for command in self.ctx.bot.commands
|
||||
if hasattr(command.callback, "category") and command.callback.category == category]
|
||||
|
||||
# Unpack any groups that have to be unpacked
|
||||
for command in list(category_commands):
|
||||
if not isinstance(command, str):
|
||||
category_commands.remove(command)
|
||||
category_commands.extend([self.get_name(c) for c in self.unpack_group(command)])
|
||||
|
||||
embed = self.create_help_embed(category.value)
|
||||
embed.add_field(name="Commands", value="\n".join(sorted(category_commands)))
|
||||
for person in await self.get_destination():
|
||||
await person.send(embed=embed)
|
||||
|
||||
async def send_bot_help(self, mapping):
|
||||
embed = self.create_help_embed("Help")
|
||||
embed.add_field(name="Categorieën", value="\n".join(sorted(mapping)))
|
||||
await self.ctx.send(embed=embed)
|
||||
|
||||
async def send_command_help(self, command):
|
||||
with open("files/help.json", "r") as fp:
|
||||
helpFile = json.load(fp)
|
||||
|
||||
try:
|
||||
helpDescription = helpFile[self.get_name(command).lower()]
|
||||
except KeyError:
|
||||
helpDescription = "Indien je dit leest is DJ STIJN vergeten om dit commando in de help page te zetten. Stuur hem een DM om hem eraan te herinneren."
|
||||
|
||||
embed = self.create_help_embed("Help")
|
||||
embed.add_field(name=await self.get_command_signature(command),
|
||||
value=await self.add_aliases_formatting(sorted(command.aliases)) + helpDescription)
|
||||
for person in await self.get_destination():
|
||||
await person.send(embed=embed)
|
||||
|
||||
async def send_group_help(self, group):
|
||||
with open("files/help.json", "r") as fp:
|
||||
helpFile = json.load(fp)
|
||||
|
||||
embed = self.create_help_embed(group.name + " Commando's")
|
||||
|
||||
try:
|
||||
helpDescription = helpFile[self.get_name(group).lower()]
|
||||
except KeyError:
|
||||
helpDescription = "Indien je dit leest is DJ STIJN vergeten om dit commando in de help page te zetten. Stuur hem een DM om hem eraan te herinneren."
|
||||
|
||||
embed.add_field(name=await self.get_command_signature(group),
|
||||
value=await self.add_aliases_formatting(sorted(group.aliases)) + helpDescription,
|
||||
inline=False)
|
||||
|
||||
# Signature: Aliases - Usage
|
||||
for subcommand in self.unpack_group(group):
|
||||
embed.add_field(name=await self.get_command_signature(subcommand),
|
||||
value=await self.add_aliases_formatting(sorted(subcommand.aliases)) +
|
||||
helpFile[self.get_name(subcommand).lower()], inline=False)
|
||||
|
||||
for person in await self.get_destination():
|
||||
await person.send(embed=embed)
|
||||
|
||||
# Allow mentioning people to send it to them instead
|
||||
async def get_destination(self):
|
||||
if self.ctx.message.mentions:
|
||||
return set(mention for mention in self.ctx.message.mentions if not mention.bot)
|
||||
return [self.ctx.author]
|
||||
|
||||
async def command_not_found(self, string):
|
||||
return "Er bestaat geen commando met de naam **{}**".format(string)
|
||||
|
||||
async def subcommand_not_found(self, command, string):
|
||||
return "**{}** heeft geen subcommando met de naam **{}**.".format(command.name, string)
|
||||
|
||||
async def get_command_signature(self, command):
|
||||
return "{} {}".format(self.get_name(command), command.usage if command.usage is not None else "")
|
||||
|
||||
async def add_aliases_formatting(self, aliases):
|
||||
return "*Alias: {}*\n".format(", ".join(aliases)) if aliases else ""
|
||||
|
||||
async def send_error_message(self, error):
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.set_author(name="Help")
|
||||
embed.add_field(name="Error", value=error)
|
||||
await self.ctx.author.send(embed=embed)
|
||||
|
||||
def unpack_group(self, group):
|
||||
# Create a list of all command objects in this group, in case they aren't hidden, sorted by name
|
||||
subcommands = [group.all_commands.get(command) for command in group.all_commands]
|
||||
subcommands.sort(key=lambda x: x.name)
|
||||
subcommands = filter(lambda x: not x.hidden, subcommands)
|
||||
return list(set(subcommands))
|
||||
|
||||
def get_name(self, command):
|
||||
return command.qualified_name if command.parents else command.name
|
||||
|
||||
def create_help_embed(self, title):
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name=title)
|
||||
embed.set_footer(text="Syntax: Didier Help [Categorie] of Didier Help [Commando]")
|
||||
return embed
|
||||
|
||||
|
||||
class Help(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self._original_help_command = client.help_command
|
||||
client.help_command = HelpCommand(command_attrs={"aliases": ["rtfm"]})
|
||||
client.help_command.cog = self
|
||||
|
||||
def cog_unload(self):
|
||||
self.client.help_command = self._original_help_command
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Help(client))
|
|
@ -1,54 +0,0 @@
|
|||
from datetime import datetime
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
import pytz
|
||||
import requests
|
||||
|
||||
|
||||
class Launch(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Launch", aliases=["SpaceX"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def launch(self, ctx, *args):
|
||||
resp = self.getNextLaunch()
|
||||
resp: dict = resp[list(resp.keys())[0]]
|
||||
embed = discord.Embed(
|
||||
colour=discord.Colour.blue()
|
||||
)
|
||||
embed.set_author(name="🚀 Volgende SpaceX lancering 🚀")
|
||||
embed.add_field(name="Naam:", value=resp["name"], inline=False)
|
||||
embed.add_field(name="Tijdstip:", value=resp["time"])
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
def getNextLaunch(self):
|
||||
resp = requests.get("https://launchlibrary.net/1.3/launch?next=1&name=falcon").json()
|
||||
if "status" in resp and (resp["status"] == "fail" or resp["status"] == "error"):
|
||||
return {"error": "fail" if resp["status"].lower() == "fail" else "none found"}
|
||||
resp = resp["launches"]
|
||||
ret = {}
|
||||
for launch in resp:
|
||||
ret[launch["id"]] = {
|
||||
"name": launch["name"],
|
||||
"time": self.parseDate(launch["net"][:-4]) if launch["tbdtime"] == 0 else "TBA",
|
||||
"TBA": launch["tbdtime"] == "0"
|
||||
}
|
||||
return ret
|
||||
|
||||
def parseDate(self, timestr):
|
||||
d = datetime.strptime(timestr, "%B %d, %Y %H:%M:%S").timestamp()
|
||||
return str(
|
||||
datetime.fromtimestamp(int(d) + 7200, pytz.timezone("Europe/Brussels")).strftime('%B %d %Y om %H:%M:%S'))
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Launch(client))
|
|
@ -1,208 +0,0 @@
|
|||
from data import paginatedLeaderboard
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from enums.numbers import Numbers
|
||||
from functions import checks, xp
|
||||
from functions.database import currency, stats, poke, muttn
|
||||
import math
|
||||
import requests
|
||||
|
||||
|
||||
# TODO some sort of general leaderboard because all of them are the same
|
||||
class Leaderboards(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Leaderboard", aliases=["Lb", "Leaderboards"], case_insensitive=True, usage="[Categorie]*",
|
||||
invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def leaderboard(self, ctx):
|
||||
categories = ["Bitcoin", "Corona", "Dinks", "Messages", "Poke", "Rob", "XP"]
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Leaderboard Categorieën")
|
||||
embed.description = "\n".join(categories)
|
||||
await ctx.channel.send(embed=embed)
|
||||
|
||||
@leaderboard.command(name="Dinks", aliases=["Cash"], hidden=True)
|
||||
async def dinks(self, ctx):
|
||||
entries = currency.getAllRows()
|
||||
platDinks = currency.getAllPlatDinks()
|
||||
|
||||
# Take platinum dinks into account
|
||||
for i, user in enumerate(entries):
|
||||
if str(user[0]) in platDinks:
|
||||
# Tuples don't support assignment, cast to list
|
||||
user = list(user)
|
||||
user[1] += platDinks[str(user[0])] * Numbers.q.value
|
||||
entries[i] = user
|
||||
|
||||
boardTop = []
|
||||
for i, user in enumerate(sorted(entries, key=lambda x: (float(x[1]) + float(x[3])), reverse=True)):
|
||||
if i == 0 and float(user[1]) + float(user[3]) == 0.0:
|
||||
return await self.emptyLeaderboard(ctx, "Dinks Leaderboard", "Er zijn nog geen personen met Didier Dinks.")
|
||||
elif float(user[1]) + float(user[3]) > 0.0:
|
||||
|
||||
# Get the username in this guild
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} ({:,})**".format(name, math.floor(float(user[1]) + float(user[3]))))
|
||||
else:
|
||||
boardTop.append("{} ({:,})".format(name, math.floor(float(user[1]) + float(user[3]))))
|
||||
|
||||
await self.startPaginated(ctx, boardTop, "Dinks Leaderboard")
|
||||
|
||||
@leaderboard.command(name="Corona", hidden=True)
|
||||
async def corona(self, ctx):
|
||||
result = requests.get("http://corona.lmao.ninja/v2/countries").json()
|
||||
result.sort(key=lambda x: int(x["cases"]), reverse=True)
|
||||
board = []
|
||||
for land in result:
|
||||
|
||||
if land["country"] == "Belgium":
|
||||
board.append("**{} ({:,})**".format(land["country"], land["cases"]))
|
||||
else:
|
||||
board.append("{} ({:,})".format(land["country"], land["cases"]))
|
||||
|
||||
await self.startPaginated(ctx, board, "Corona Leaderboard", discord.Colour.red())
|
||||
|
||||
@leaderboard.command(name="Bitcoin", aliases=["Bc"], hidden=True)
|
||||
async def bitcoin(self, ctx):
|
||||
users = currency.getAllRows()
|
||||
boardTop = []
|
||||
for i, user in enumerate(sorted(users, key=lambda x: x[8], reverse=True)):
|
||||
# Don't create an empty leaderboard
|
||||
if i == 0 and float(user[8]) == 0.0:
|
||||
return await self.emptyLeaderboard(ctx, "Bitcoin Leaderboard", "Er zijn nog geen personen met Bitcoins.")
|
||||
elif float(user[8]) > 0.0:
|
||||
# Only add people with more than 0
|
||||
# Get the username in this guild
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} ({:,})**".format(name, round(user[8], 8)))
|
||||
else:
|
||||
boardTop.append("{} ({:,})".format(name, round(user[8], 8)))
|
||||
|
||||
await self.startPaginated(ctx, boardTop, "Bitcoin Leaderboard")
|
||||
|
||||
@leaderboard.command(name="Rob", hidden=True)
|
||||
async def rob(self, ctx):
|
||||
users = list(stats.getAllRows())
|
||||
boardTop = []
|
||||
for i, user in enumerate(sorted(users, key=lambda x: x[4], reverse=True)):
|
||||
# Don't create an empty leaderboard
|
||||
if i == 0 and float(user[4]) == 0.0:
|
||||
return await self.emptyLeaderboard(ctx, "Rob Leaderboard", "Er heeft nog niemand Didier Dinks gestolen.")
|
||||
elif float(user[4]) > 0.0:
|
||||
# Only add people with more than 0
|
||||
# Get the username in this guild
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} ({:,})**".format(name, math.floor(float(user[4]))))
|
||||
else:
|
||||
boardTop.append("{} ({:,})".format(name, math.floor(float(user[4]))))
|
||||
await self.startPaginated(ctx, boardTop, "Rob Leaderboard")
|
||||
|
||||
@leaderboard.command(name="Poke", hidden=True)
|
||||
async def poke(self, ctx):
|
||||
s = stats.getAllRows()
|
||||
blacklist = poke.getAllBlacklistedUsers()
|
||||
boardTop = []
|
||||
for i, user in enumerate(sorted(s, key=lambda x: x[1], reverse=True)):
|
||||
if i == 0 and int(user[1]) == 0:
|
||||
return await self.emptyLeaderboard(ctx, "Poke Leaderboard", "Er is nog niemand getikt.")
|
||||
|
||||
elif int(user[1]) == 0:
|
||||
break
|
||||
# Don't include blacklisted users
|
||||
elif str(user[0]) not in blacklist:
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} ({:,})**".format(name, round(int(user[1]))))
|
||||
else:
|
||||
boardTop.append("{} ({:,})".format(name, round(int(user[1]))))
|
||||
await self.startPaginated(ctx, boardTop, "Poke Leaderboard")
|
||||
|
||||
@leaderboard.command(name="Xp", aliases=["Level"], hidden=True)
|
||||
async def xp(self, ctx):
|
||||
s = stats.getAllRows()
|
||||
boardTop = []
|
||||
for i, user in enumerate(sorted(s, key=lambda x: x[12], reverse=True)):
|
||||
if int(user[12]) == 0:
|
||||
break
|
||||
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} (Level {:,} | {:,} XP)**".format(name,
|
||||
xp.calculate_level(round(int(user[12]))),
|
||||
round(int(user[12]))))
|
||||
else:
|
||||
boardTop.append("{} (Level {:,} | {:,} XP)".format(name,
|
||||
xp.calculate_level(round(int(user[12]))),
|
||||
round(int(user[12]))))
|
||||
await self.startPaginated(ctx, boardTop, "XP Leaderboard")
|
||||
|
||||
@leaderboard.command(name="Messages", aliases=["Mc", "Mess"], hidden=True)
|
||||
async def messages(self, ctx):
|
||||
s = stats.getAllRows()
|
||||
boardTop = []
|
||||
|
||||
message_count = stats.getTotalMessageCount()
|
||||
|
||||
for i, user in enumerate(sorted(s, key=lambda x: x[11], reverse=True)):
|
||||
if int(user[11]) == 0:
|
||||
break
|
||||
|
||||
perc = round(int(user[11]) * 100 / message_count, 2)
|
||||
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} ({:,} | {}%)**".format(name, round(int(user[11])), perc))
|
||||
else:
|
||||
boardTop.append("{} ({:,} | {}%)".format(name, round(int(user[11])), perc))
|
||||
await self.startPaginated(ctx, boardTop, "Messages Leaderboard")
|
||||
|
||||
@leaderboard.command(name="Muttn", aliases=["M", "Mutn", "Mutten"], hidden=True)
|
||||
async def muttn(self, ctx):
|
||||
users = muttn.getAllRows()
|
||||
boardTop = []
|
||||
for i, user in enumerate(sorted(users, key=lambda x: x[1], reverse=True)):
|
||||
if i == 0 and int(user[1]) == 0:
|
||||
return await self.emptyLeaderboard(ctx, "Muttn Leaderboard", "Der zittn nog geen muttns in de server.")
|
||||
|
||||
if float(user[1]) == 0:
|
||||
break
|
||||
|
||||
name = self.utilsCog.getDisplayName(ctx, user[0])
|
||||
if int(user[0]) == int(ctx.author.id):
|
||||
boardTop.append("**{} ({})%**".format(name, round(float(user[1]), 2)))
|
||||
else:
|
||||
boardTop.append("{} ({}%)".format(name, round(float(user[1]), 2)))
|
||||
await self.startPaginated(ctx, boardTop, "Muttn Leaderboard")
|
||||
|
||||
async def callLeaderboard(self, name, ctx):
|
||||
await [command for command in self.leaderboard.commands if command.name.lower() == name.lower()][0](ctx)
|
||||
|
||||
async def startPaginated(self, ctx, source, name, colour=discord.Colour.blue()):
|
||||
pages = paginatedLeaderboard.Pages(source=paginatedLeaderboard.Source(source, name, colour),
|
||||
clear_reactions_after=True)
|
||||
await pages.start(ctx)
|
||||
|
||||
async def emptyLeaderboard(self, ctx, name, message, colour=discord.Colour.blue()):
|
||||
embed = discord.Embed(colour=colour)
|
||||
embed.set_author(name=name)
|
||||
embed.description = message
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Leaderboards(client))
|
|
@ -1,156 +0,0 @@
|
|||
from decorators import help
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
import itertools
|
||||
import random
|
||||
|
||||
|
||||
class Minesweeper(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Minesweeper", aliases=["Ms"], usage="[Niveau]*")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Games)
|
||||
async def minesweeper(self, ctx, difficulty="m"):
|
||||
if difficulty[0].lower() not in "emh":
|
||||
await ctx.send("Geef een geldige moeilijkheidsgraad op.")
|
||||
return
|
||||
|
||||
await ctx.send(self.createGame(difficulty[0].lower()))
|
||||
|
||||
def createGame(self, difficutly):
|
||||
|
||||
# [X, Y, BombCount]
|
||||
dimensions = {
|
||||
"e": [5, 5, 4],
|
||||
"m": [10, 10, 20],
|
||||
"h": [13, 13, 35]
|
||||
}
|
||||
|
||||
numbers = {
|
||||
0: "||:zero:||",
|
||||
1: "||:one:||",
|
||||
2: "||:two:||",
|
||||
3: "||:three:||",
|
||||
4: "||:four:||",
|
||||
5: "||:five:||",
|
||||
6: "||:six:||",
|
||||
7: "||:seven:||",
|
||||
8: "||:eight:||",
|
||||
}
|
||||
|
||||
# Initialize an empty grid
|
||||
grid = [[" " for _ in range(dimensions[difficutly][0])] for _ in range(dimensions[difficutly][1])]
|
||||
|
||||
# Generate every possible position on the grid
|
||||
positions = set(itertools.product([x for x in range(len(grid))], repeat=2))
|
||||
|
||||
# Place the bombs in the grid randomly
|
||||
for i in range(dimensions[difficutly][2]):
|
||||
bombPosition = random.choice(list(positions))
|
||||
positions.remove(bombPosition)
|
||||
grid[bombPosition[0]][bombPosition[1]] = "||:boom:||"
|
||||
|
||||
# Add in the numbers representing the amount of bombs nearby
|
||||
for i, row in enumerate(grid):
|
||||
for j, cell in enumerate(row):
|
||||
if cell == " ":
|
||||
grid[i][j] = numbers[self.countBombs(grid, [i, j])]
|
||||
|
||||
# Reveal the biggest empty space to the player
|
||||
self.revealSpaces(grid, self.findBiggestEmptySpace(grid))
|
||||
|
||||
# Join the grid into a string
|
||||
li = [" ".join(row) for row in grid]
|
||||
return "\n".join(li)
|
||||
|
||||
# Counts the amount of bombs near a given cell
|
||||
def countBombs(self, grid, cell):
|
||||
positions = [
|
||||
[1, -1], [1, 0], [1, 1],
|
||||
[0, -1], [0, 1],
|
||||
[-1, -1], [-1, 0], [-1, 1]
|
||||
]
|
||||
|
||||
count = 0
|
||||
for position in positions:
|
||||
if 0 <= cell[0] + position[0] < len(grid) and 0 <= cell[1] + position[1] < len(grid[0]):
|
||||
if "boom" in grid[cell[0] + position[0]][cell[1] + position[1]]:
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
# Finds the biggest spot of 0's on the grid to reveal at the start
|
||||
def findBiggestEmptySpace(self, grid):
|
||||
spaces = []
|
||||
biggest = []
|
||||
|
||||
# Floodfill
|
||||
for i, row in enumerate(grid):
|
||||
for j, cell in enumerate(row):
|
||||
# Only check cells that aren't part of a space yet
|
||||
if not any(cell in space for space in spaces) and "zero" in cell:
|
||||
li = [[i, j]]
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for added in li:
|
||||
neighb = self.neighbours(grid, added)
|
||||
# Add all neighbours that have not yet been added to this space
|
||||
for neighbour in neighb:
|
||||
if neighbour not in li:
|
||||
li.append(neighbour)
|
||||
changed = True
|
||||
spaces.append(li)
|
||||
|
||||
# If it's bigger than the current biggest, make it the new biggest
|
||||
if len(li) > len(biggest):
|
||||
biggest = li
|
||||
return biggest
|
||||
|
||||
# Returns all neighbouring cells containing a 0
|
||||
def neighbours(self, grid, cell):
|
||||
positions = [
|
||||
[1, 0],
|
||||
[0, -1], [0, 1],
|
||||
[-1, 0]
|
||||
]
|
||||
|
||||
neighb = []
|
||||
|
||||
for position in positions:
|
||||
if 0 <= cell[0] + position[0] < len(grid) and 0 <= cell[1] + position[1] < len(grid[0]):
|
||||
if "zero" in grid[cell[0] + position[0]][cell[1] + position[1]]:
|
||||
neighb.append([cell[0] + position[0], cell[1] + position[1]])
|
||||
return neighb
|
||||
|
||||
# Take away the spoiler marks from the biggest empty space to help the player start
|
||||
def revealSpaces(self, grid, emptySpaces):
|
||||
positions = [
|
||||
[1, -1], [1, 0], [1, 1],
|
||||
[0, -1], [0, 1],
|
||||
[-1, -1], [-1, 0], [-1, 1]
|
||||
]
|
||||
|
||||
for space in emptySpaces:
|
||||
grid[space[0]][space[1]] = ":zero:"
|
||||
# Reveal all spaces around this one
|
||||
for position in positions:
|
||||
# Check if the space is not zero & is contained inside the grid & the space hasn't been cut before
|
||||
if 0 <= space[0] + position[0] < len(grid) and \
|
||||
0 <= space[1] + position[1] < len(grid[0]) and \
|
||||
"zero" not in grid[space[0] + position[0]][space[1] + position[1]] and \
|
||||
"||" in grid[space[0] + position[0]][space[1] + position[1]]:
|
||||
# Cut the spoiler from this cell
|
||||
grid[space[0] + position[0]][space[1] + position[1]] = grid[space[0] + position[0]][
|
||||
space[1] + position[1]][2:-2]
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Minesweeper(client))
|
|
@ -1,199 +0,0 @@
|
|||
from data import constants
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, config, timeFormatters
|
||||
from functions.database import memes, githubs, twitch, dadjoke
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class ModCommands(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog('Utils')
|
||||
|
||||
@commands.command(name="Remove", aliases=["Rm"], hidden=True)
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def remove(self, ctx, message: str):
|
||||
spl = message.split("/")
|
||||
channel = self.client.get_channel(int(spl[-2]))
|
||||
message = await channel.fetch_message(int(spl[-1]))
|
||||
await message.delete()
|
||||
|
||||
# Load a cog
|
||||
@commands.group(name="Load", usage="[Cog]", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def load(self, ctx, extension: str):
|
||||
try:
|
||||
self.client.load_extension("cogs.{}".format(extension))
|
||||
await self.sendDm(constants.myId, "Loaded **{}**".format(extension))
|
||||
except discord.ext.commands.errors.ExtensionAlreadyLoaded:
|
||||
await self.sendDm(constants.myId, "**{}** has already been loaded".format(extension))
|
||||
|
||||
@commands.command(name="Config", aliases=["Setup", "Set"], case_insensitive=True, usage="[Categorie] [Value]",
|
||||
invoke_without_commands=True)
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(Category.Mod)
|
||||
async def set(self, ctx, category, value):
|
||||
if config.config(category, value):
|
||||
await ctx.message.add_reaction("✅")
|
||||
|
||||
# Load all cogs except for modCommands
|
||||
@load.command(name="All")
|
||||
async def loadAll(self, ctx):
|
||||
for file in os.listdir("./cogs"):
|
||||
if file.endswith(".py") and not file == "modCommands.py":
|
||||
await self.load(ctx, file[:-3])
|
||||
|
||||
# Unload a cog
|
||||
@commands.group(name="Unload", usage="[Cog]", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def unload(self, ctx, extension: str):
|
||||
try:
|
||||
self.client.unload_extension("cogs.{}".format(extension))
|
||||
await self.sendDm(constants.myId, "Unloaded **{}**".format(extension))
|
||||
except discord.ext.commands.errors.ExtensionNotLoaded:
|
||||
await self.sendDm(constants.myId, "**{}** has already been unloaded".format(extension))
|
||||
|
||||
# Unload all cogs except for modCommands
|
||||
@unload.command(name="All")
|
||||
async def unloadAll(self, ctx):
|
||||
for file in os.listdir("./cogs"):
|
||||
if file.endswith(".py") and not file == "modCommands.py":
|
||||
await self.unload(ctx, file[:-3])
|
||||
|
||||
# Reloads a cog
|
||||
@commands.command(name="Reload", aliases=["Update"], usage="[Cog]")
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def reload(self, ctx, cog):
|
||||
await self.unload(ctx, cog)
|
||||
await self.load(ctx, cog)
|
||||
await ctx.message.add_reaction("✅")
|
||||
|
||||
# Repeat what was said
|
||||
@commands.command(name="Repeat", usage="[Text]")
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def repeat(self, ctx, *text):
|
||||
await self.utilsCog.removeMessage(ctx.message)
|
||||
await ctx.send(" ".join(text))
|
||||
|
||||
# Add a reaction to a message
|
||||
@commands.command(name="Reac", aliases=["Reacc"], usage="[Emoji] [Id]")
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def reac(self, ctx, emoji, messageId):
|
||||
channel = ctx.channel
|
||||
|
||||
# Check if the URL or the Id was passed
|
||||
if messageId.count("/") > 3:
|
||||
spl = messageId.split("/")
|
||||
channel = self.client.get_channel(int(spl[-2]))
|
||||
if channel is None:
|
||||
return await ctx.send("Ik kan geen kanaal zien met dit Id.")
|
||||
messageId = int(spl[-1])
|
||||
|
||||
await self.utilsCog.removeMessage(ctx.message)
|
||||
message = await channel.fetch_message(messageId)
|
||||
if message is None:
|
||||
return await ctx.send("Ik kan geen bericht zien met dit Id.")
|
||||
await message.add_reaction(emoji)
|
||||
|
||||
# Adds stuff into their databases
|
||||
@commands.group(name="Add", usage="[Category] [Args]", case_insensitive=True, invoke_without_command=False)
|
||||
@commands.check(checks.isMe)
|
||||
@help.Category(category=Category.Mod)
|
||||
async def add(self, ctx):
|
||||
pass
|
||||
|
||||
@add.command(name="Dadjoke", aliases=["Dj", "Dad"], usage="[Joke]")
|
||||
async def dadjoke(self, ctx, *, joke):
|
||||
dadjoke.addJoke(joke)
|
||||
await ctx.send("Added ``{}``.".format(joke))
|
||||
await ctx.message.add_reaction("✅")
|
||||
|
||||
@add.command(name="8-Ball", aliases=["8b", "Eightball", "8Ball"], usage="[Response]")
|
||||
async def eightball(self, ctx, message):
|
||||
with open("files/eightball.json", "r") as fp:
|
||||
file = json.load(fp)
|
||||
file.append(message)
|
||||
with open("files/eightball.json", "w") as fp:
|
||||
json.dump(file, fp)
|
||||
|
||||
# Adds a meme into the database
|
||||
@add.command(name="Meme", aliases=["Mem"], usage="[Id] [Name] [Aantal Velden]")
|
||||
async def meme(self, ctx, memeid, meme, fields):
|
||||
await ctx.send(memes.insert(memeid, meme, fields)[1])
|
||||
|
||||
# Adds a person's GitHub into the database
|
||||
@add.command(name="GitHub", aliases=["Gh", "Git"], usage="[Id] [Link]")
|
||||
async def github(self, ctx, userid, link):
|
||||
# Allow tagging to work as well
|
||||
if len(ctx.message.mentions) == 1:
|
||||
userid = ctx.message.mentions[0].id
|
||||
githubs.add(userid, link)
|
||||
await ctx.send("{}'s GitHub is toegevoegd aan de database.".format(self.utilsCog.getDisplayName(ctx, userid)))
|
||||
|
||||
# Adds a person's Twitch into the database
|
||||
@add.command(name="Twitch", aliases=["Stream", "Streamer", "Tw"])
|
||||
async def twitch(self, ctx, userid, link):
|
||||
# Allow tagging to work as well
|
||||
if len(ctx.message.mentions) == 1:
|
||||
userid = ctx.message.mentions[0].id
|
||||
twitch.add(userid, link)
|
||||
await ctx.send("{}'s Twitch is toegevoegd aan de database.".format(self.utilsCog.getDisplayName(ctx, userid)))
|
||||
|
||||
@commands.command(name="WhoIs", aliases=["Info", "Whodis"], usage="[@User]")
|
||||
@help.Category(Category.Mod)
|
||||
async def whois(self, ctx, user):
|
||||
if ctx.message.mentions:
|
||||
user = ctx.message.mentions[0].id
|
||||
|
||||
user = await self.client.fetch_user(int(user))
|
||||
|
||||
if user is None:
|
||||
return
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
|
||||
embed.set_author(name=user.display_name, icon_url=user.avatar_url)
|
||||
embed.add_field(name="Discriminator", value="#{}".format(user.discriminator))
|
||||
embed.add_field(name="Discord id", value=user.id)
|
||||
embed.add_field(name="Bot", value="Nee" if not user.bot else "Ja")
|
||||
|
||||
created_local = timeFormatters.epochToDate(user.created_at.timestamp())
|
||||
|
||||
embed.add_field(name="Account aangemaakt", value="{}\n({} geleden)".format(
|
||||
created_local["date"], timeFormatters.diffYearBasisString(round(created_local["dateDT"].timestamp()))
|
||||
), inline=False)
|
||||
|
||||
# Check if the user is in the current guild
|
||||
if ctx.guild is not None:
|
||||
member_instance = ctx.guild.get_member(user.id)
|
||||
|
||||
if member_instance is not None:
|
||||
joined_local = timeFormatters.epochToDate(member_instance.joined_at.timestamp())
|
||||
|
||||
embed.add_field(name="Lid geworden van {} op".format(ctx.guild.name), value="{}\n({} Geleden)".format(
|
||||
joined_local["date"], timeFormatters.diffYearBasisString(round(joined_local["dateDT"].timestamp()))
|
||||
))
|
||||
|
||||
embed.add_field(name="Mention String", value=member_instance.mention, inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
# Send a DM to a user -- Can't re-use Utils cog in (un)load because the cog might not be loaded
|
||||
async def sendDm(self, userid, message: str):
|
||||
user = self.client.get_user(int(userid))
|
||||
await user.send(message)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(ModCommands(client))
|
|
@ -1,37 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from decorators import help
|
||||
from enums.help_categories import Category
|
||||
from functions.database import muttn
|
||||
|
||||
|
||||
class Muttn(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Muttn", aliases=["HowMuttn", "M", "Mutn", "Mutten"], usage="[@Persoon]", case_insensitive=True, invoke_without_command=True)
|
||||
@help.Category(Category.Fun)
|
||||
async def muttn(self, ctx, member: discord.Member = None):
|
||||
if member is None:
|
||||
member = ctx.author
|
||||
|
||||
user = muttn.getOrAddUser(member.id)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue(), title=member.display_name)
|
||||
embed.set_author(name="Muttn-O'-Meter")
|
||||
embed.add_field(name="Percentage", value="{}%".format(round(float(user[1]), 2)))
|
||||
|
||||
embed.add_field(name="Aantal {}'s".format("<:Muttn:761551956346798111>"), value=str(user[2]))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@muttn.command(name="Leaderboard", aliases=["Lb"], hidden=True)
|
||||
async def lb(self, ctx):
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("muttn", ctx)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Muttn(client))
|
|
@ -1,153 +0,0 @@
|
|||
from data import constants
|
||||
import datetime
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, clap, mock, sunrise, timeFormatters
|
||||
import pytz
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
|
||||
# Random things that are usually oneliners & don't belong in any other categories
|
||||
class Oneliners(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog('Utils')
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Age", usage="[Formaat]*")
|
||||
@help.Category(category=Category.Didier)
|
||||
async def age(self, ctx, specification=None):
|
||||
allowedSpecifications = ["d", "days", "m", "months", "w", "weeks", "y", "years"]
|
||||
|
||||
if specification is not None and specification.lower() not in allowedSpecifications:
|
||||
await ctx.send("**{}** is geen geldig formaat.".format(specification))
|
||||
return
|
||||
|
||||
if specification is None:
|
||||
timeString = timeFormatters.diffYearBasisString(constants.creationDate)
|
||||
else:
|
||||
ageSeconds = round(time.time()) - constants.creationDate
|
||||
timeFormat = timeFormatters.getFormat(specification)
|
||||
timeString = str(timeFormatters.timeIn(ageSeconds, timeFormat)[0])
|
||||
timeString += " " + timeFormatters.getPlural(int(timeString), timeFormat)
|
||||
await ctx.send("Didier is **{}** oud.".format(timeString))
|
||||
|
||||
@commands.command(name="Clap", usage="[Tekst]")
|
||||
@help.Category(category=Category.Other)
|
||||
async def clap(self, ctx, *args):
|
||||
await ctx.send(clap.clap("".join(args)))
|
||||
await self.utilsCog.removeMessage(ctx.message)
|
||||
|
||||
@commands.command(name="Reverse", aliases=["Rev"], usage="[Tekst]")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def reverse(self, ctx, *args):
|
||||
await ctx.send(" ".join(args)[::-1])
|
||||
|
||||
@commands.command(name="Government", aliases=["Gov", "Regering"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def government(self, ctx):
|
||||
now = timeFormatters.dateTimeNow()
|
||||
newGov = datetime.datetime.fromtimestamp(1601539200, tz=pytz.timezone("Europe/Brussels"))
|
||||
delta = now - newGov
|
||||
zin = "Na **494** dagen is er weer een regering, **47** dagen te vroeg om het record te breken. Very sad times.\nMAAR hoelang denk je dat de nieuwe regering het gaat volhouden? Place your bets! Momenteel zitten we aan **{}** dag{}.".format(
|
||||
delta.days, "en" if delta.days != 1 else ""
|
||||
)
|
||||
# now = int(time.time())
|
||||
# valVorige = 1545350400
|
||||
# verkiezingen = 1558828800
|
||||
# valDiff = now - valVorige
|
||||
# verkiezingenDiff = now - verkiezingen
|
||||
# zin = (
|
||||
# "We zitten al **%d** dagen zonder regering, en proberen al **%d** dagen een nieuwe te vormen.\nHet "
|
||||
# "huidige wereldrecord is "
|
||||
# "**541** dagen, dus nog **%d** dagen tot we het gebroken hebben." %
|
||||
# (valDiff // 86400, verkiezingenDiff // 86400, 541 - int(verkiezingenDiff // 86400)))
|
||||
await ctx.send(zin)
|
||||
|
||||
@commands.command()
|
||||
async def inject(self, ctx):
|
||||
await ctx.send("**{}** heeft wat code geïnjecteerd.".format(ctx.author.display_name))
|
||||
|
||||
@commands.command(name="Mock", usage="[Tekst]")
|
||||
@help.Category(category=Category.Other)
|
||||
async def mock(self, ctx, *text):
|
||||
await ctx.channel.send("{} - **{}**".format(mock.mock(" ".join(text)), ctx.author.display_name))
|
||||
await self.utilsCog.removeMessage(ctx.message)
|
||||
|
||||
@commands.command(name="Molest", usage="[@Persoon]")
|
||||
async def molest(self, ctx):
|
||||
if constants.didierId in ctx.message.content:
|
||||
await ctx.send("Nee.")
|
||||
elif str(ctx.author.id) in ctx.message.content or ctx.message.content == "molest me":
|
||||
await ctx.send("I didn't know you swing that way, " + ctx.author.display_name)
|
||||
elif "171671190631481345" in ctx.message.content:
|
||||
await ctx.send("Nee")
|
||||
else:
|
||||
await ctx.send("https://imgur.com/a/bwA6Exn")
|
||||
|
||||
@commands.command(name="Changelog", aliases=["Cl", "Change", "Changes"])
|
||||
@help.Category(category=Category.Didier)
|
||||
async def changelog(self, ctx, *args):
|
||||
await ctx.send("V2.0: <https://docs.google.com/document/d/1oa-9oc9yFnZ0X5sLJTWfdahtaL0vF8acLl-xMXA3a40/edit#>\n"
|
||||
"V2.1: https://docs.google.com/document/d/1ezdJBTnKWoog4q9yJrgwfF4iGOn-PZMoBZgSNVYPtqg/edit#")
|
||||
|
||||
@commands.command(name="Todo", aliases=["List", "Td"])
|
||||
@help.Category(category=Category.Didier)
|
||||
async def todo(self, ctx, *args):
|
||||
await ctx.send("https://trello.com/b/PdtsAJea/didier-to-do-list")
|
||||
|
||||
@commands.command(name="LMGTFY", aliases=["Dsfr"], usage="[Query]")
|
||||
@help.Category(category=Category.Other)
|
||||
async def lmgtfy(self, ctx, *, query=None):
|
||||
if query:
|
||||
await ctx.send("https://lmgtfy.com/?q={}&iie=1".format(urllib.parse.quote(query)))
|
||||
|
||||
@commands.command(name="Neck", aliases=["Necc"], usage="[Lengte]*")
|
||||
@help.Category(category=Category.Fun)
|
||||
async def neck(self, ctx, size=None):
|
||||
if not size:
|
||||
size = 1
|
||||
try:
|
||||
size = int(size)
|
||||
if not 0 < size < 16:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
return await ctx.send("Geef een geldig getal op.")
|
||||
|
||||
await ctx.send("<:WhatDidYou:744476950654877756>" + ("<:DoTo:744476965951504414>" * size) + "<:MyDrink:744476979939508275>")
|
||||
|
||||
@commands.command()
|
||||
async def open(self, ctx):
|
||||
# await ctx.send(file=discord.File("files/images/open_source_bad.jpg"))
|
||||
await ctx.send("Shut, it already is.")
|
||||
|
||||
@commands.command()
|
||||
async def sc(self, ctx, *args):
|
||||
await ctx.send("http://take-a-screenshot.org/")
|
||||
|
||||
@commands.command(aliases=["os", "sauce", "src"])
|
||||
async def source(self, ctx):
|
||||
# await ctx.send("<https://bit.ly/31z3BuH>")
|
||||
await ctx.send("https://github.com/stijndcl/didier")
|
||||
|
||||
@commands.command(aliases=["sunrise", "sunshine"])
|
||||
async def sun(self, ctx):
|
||||
s = sunrise.Sun()
|
||||
await ctx.send(":sunny:: **{}**\n:crescent_moon:: **{}**".format(s.sunrise(), s.sunset()))
|
||||
|
||||
@commands.command(name="Tias", aliases=["TryIt"])
|
||||
async def tias(self, ctx, *args):
|
||||
await ctx.send("***Try it and see***")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Oneliners(client))
|
113
cogs/poke.py
113
cogs/poke.py
|
@ -1,113 +0,0 @@
|
|||
from data import constants
|
||||
import datetime
|
||||
from decorators import help
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, timeFormatters
|
||||
from functions.database import poke, stats
|
||||
|
||||
|
||||
class Poke(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Poke", usage="[@Persoon]", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Games)
|
||||
async def poke(self, ctx, member=None):
|
||||
if not await self.pokeChecks(ctx):
|
||||
return
|
||||
|
||||
member = ctx.message.mentions[0]
|
||||
await ctx.send("**{}** heeft **{}** getikt. **{}** is hem!".format(
|
||||
ctx.author.display_name, member.display_name, member.display_name))
|
||||
|
||||
# Add into the database
|
||||
poke.update(ctx.author.id, member.id)
|
||||
stats.update(member.id, "poked", int(stats.getOrAddUser(member.id)[1]) + 1)
|
||||
|
||||
@poke.command(name="Blacklist", aliases=["Bl"])
|
||||
async def blacklist(self, ctx):
|
||||
if poke.blacklisted(ctx.author.id):
|
||||
await ctx.send("Je hebt jezelf al geblacklisted, {}.".format(ctx.author.display_name))
|
||||
return
|
||||
if str(poke.get()[0]) == str(ctx.author.id):
|
||||
await ctx.send("Je kan jezelf niet blacklisten als je aan de beurt bent, {}.".format(
|
||||
ctx.author.display_name))
|
||||
return
|
||||
poke.blacklist(ctx.author.id)
|
||||
await ctx.send("**{}** heeft zichzelf geblacklisted en kan niet meer getikt worden.".format(
|
||||
ctx.author.display_name))
|
||||
|
||||
@poke.command(aliases=["wl"], hidden=True)
|
||||
async def whitelist(self, ctx, *user):
|
||||
user = ctx.message.mentions[0].id
|
||||
if not poke.blacklisted(user):
|
||||
await ctx.send("Deze persoon is niet geblacklisted.")
|
||||
return
|
||||
|
||||
poke.blacklist(user, False)
|
||||
await ctx.send("**{}** heeft {} gewhitelisted.".format(
|
||||
ctx.author.display_name, self.utilsCog.getDisplayName(ctx, user)))
|
||||
|
||||
@poke.command(name="Current")
|
||||
async def current(self, ctx):
|
||||
p = poke.get()
|
||||
pokedTimeStamp = timeFormatters.epochToDate(int(p[1]))["dateDT"]
|
||||
timeString = timeFormatters.diffDayBasisString(pokedTimeStamp)
|
||||
|
||||
await ctx.send("Het is al **{}** aan **{}**.".format(timeString, self.utilsCog.getDisplayName(ctx, p[0])))
|
||||
|
||||
@poke.command(hidden=True)
|
||||
async def me(self, ctx):
|
||||
await ctx.send("Liever niet.")
|
||||
|
||||
@poke.command(hidden=True)
|
||||
@commands.check(checks.isMe)
|
||||
async def reset(self, ctx):
|
||||
new = poke.reset()
|
||||
await ctx.send("Poke is gereset. <@!{}> is hem!".format(str(new)))
|
||||
|
||||
@poke.command(aliases=["Lb", "Leaderboards"], hidden=True)
|
||||
async def leaderboard(self, ctx):
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("poke", ctx)
|
||||
|
||||
async def pokeChecks(self, ctx):
|
||||
if len(ctx.message.mentions) == 0:
|
||||
await ctx.send("Dit is geen geldige persoon.")
|
||||
return False
|
||||
if len(ctx.message.mentions) > 1:
|
||||
await ctx.send("Je kan maar 1 persoon tegelijk tikken.")
|
||||
return False
|
||||
if ctx.message.mentions[0].id == ctx.author.id:
|
||||
await ctx.send("Je kan jezelf niet tikken, {}.".format(ctx.author.display_name))
|
||||
return False
|
||||
if str(ctx.message.mentions[0].id) == constants.didierId:
|
||||
await ctx.send("Je kan me niet tikken, {}.".format(ctx.author.display_name))
|
||||
return False
|
||||
if str(ctx.message.mentions[0].id) in constants.botIDs:
|
||||
await ctx.send("Je kan geen bots tikken, {}.".format(ctx.author.display_name))
|
||||
return False
|
||||
|
||||
# Check database things
|
||||
p = poke.get()
|
||||
if str(p[0]) != str(ctx.author.id):
|
||||
await ctx.send("Het is niet jouw beurt, {}.".format(ctx.author.display_name))
|
||||
return False
|
||||
if str(ctx.message.mentions[0].id) == str(p[2]):
|
||||
await ctx.send("Je mag niet terugtikken, {}.".format(ctx.author.display_name))
|
||||
return False
|
||||
if poke.blacklisted(ctx.message.mentions[0].id):
|
||||
await ctx.send("Deze persoon heeft zichzelf geblacklisted en kan niet meer getikt worden.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Poke(client))
|
44
cogs/qr.py
44
cogs/qr.py
|
@ -1,44 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
import requests
|
||||
import urllib.parse
|
||||
|
||||
|
||||
class QR(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="QR", usage="[Tekst]")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def QR(self, ctx, *link):
|
||||
if len(link) != 1:
|
||||
await ctx.send(file=discord.File("files/images/ngguuqr.png"))
|
||||
await self.client.get_cog("Utils").removeMessage(ctx.message)
|
||||
else:
|
||||
self.generate("".join(link))
|
||||
await ctx.send(file=discord.File("files/images/qrcode.png"))
|
||||
self.remove()
|
||||
await self.client.get_cog("Utils").removeMessage(ctx.message)
|
||||
|
||||
def generate(self, link):
|
||||
fileContent = requests.get(
|
||||
"https://image-charts.com/chart?chs=999x999&cht=qr&chl={}&choe=UTF-8&chof=.png".format(
|
||||
urllib.parse.quote(link))).content
|
||||
with open("files/images/qrcode.png", "wb+") as fp:
|
||||
fp.write(fileContent)
|
||||
|
||||
def remove(self):
|
||||
import os
|
||||
os.remove("files/images/qrcode.png")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(QR(client))
|
135
cogs/random.py
135
cogs/random.py
|
@ -1,135 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import colours
|
||||
import random
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
class Random(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
# Creates an alias
|
||||
@commands.command(name="Choice", aliases=["Choose"], usage="[Argumenten]")
|
||||
async def choose(self, ctx, *options):
|
||||
await self.choice(ctx, options)
|
||||
|
||||
@commands.command(name="Shuffle", usage="[Argumenten]")
|
||||
async def _shuffle(self, ctx, *options):
|
||||
await self.shuffle(ctx, options)
|
||||
|
||||
@commands.group(name="Random", aliases=["R", "Rand", "RNG"], case_insensitive=True, invoke_without_command=True)
|
||||
@help.Category(category=Category.Random, unpack=True)
|
||||
async def random(self, ctx):
|
||||
pass
|
||||
|
||||
@random.command(name="Choice", usage="[Argumenten]")
|
||||
async def choice(self, ctx, *options):
|
||||
if not options or not options[0]:
|
||||
await ctx.send("Geef een geldige reeks op.")
|
||||
return
|
||||
|
||||
await ctx.send(random.choice(options))
|
||||
|
||||
@random.command(name="Number", aliases=["Int"], usage="[Van]* [Tot]*")
|
||||
async def number(self, ctx, to=100, start=1):
|
||||
# This allows number(to) to work, as well as number(start, to)
|
||||
if start > to:
|
||||
start, to = to, start
|
||||
|
||||
await ctx.send(random.randint(start, to))
|
||||
|
||||
@number.error
|
||||
async def on_number_error(self, ctx, error):
|
||||
if isinstance(error, discord.ext.commands.BadArgument):
|
||||
await ctx.send("Dit is geen geldig getal.")
|
||||
else:
|
||||
raise error
|
||||
|
||||
@random.command(name="Name")
|
||||
async def name(self, ctx):
|
||||
try:
|
||||
name = requests.get("https://randomuser.me/api/").json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
await ctx.send("Er ging iets mis. Probeer het opnieuw.")
|
||||
return
|
||||
|
||||
name = name["results"][0]["name"]
|
||||
await ctx.send("{} {} {}".format(name["title"], name["first"], name["last"]))
|
||||
|
||||
@random.command(name="Identity", aliases=["Id"])
|
||||
async def identity(self, ctx):
|
||||
try:
|
||||
identity = requests.get("https://randomuser.me/api/").json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
await ctx.send("Er ging iets mis. Probeer het opnieuw.")
|
||||
return
|
||||
|
||||
identity = identity["results"][0]
|
||||
name = identity["name"]
|
||||
name = "{} {} {}".format(name["title"], name["first"], name["last"])
|
||||
|
||||
gender = identity["gender"]
|
||||
street = "{} {}".format(identity["location"]["street"]["number"], identity["location"]["street"]["name"])
|
||||
location = "{}, {}, {}, {}".format(street, identity["location"]["city"],
|
||||
identity["location"]["state"], identity["location"]["country"])
|
||||
age = identity["dob"]["age"]
|
||||
|
||||
await ctx.send("{}\n{}, {}\n{}".format(name, age, gender, location))
|
||||
|
||||
@random.command(name="Shuffle", aliases=["Order"], usage="[Argumenten]")
|
||||
async def shuffle(self, ctx, *args):
|
||||
if not args:
|
||||
await ctx.send("Geef een geldige reeks op.")
|
||||
return
|
||||
|
||||
# Allows shuffle alias to pass in it's args too
|
||||
if isinstance(args[0], tuple):
|
||||
args = args[0]
|
||||
|
||||
args = list(args)
|
||||
|
||||
random.shuffle(args)
|
||||
|
||||
await ctx.send(" - ".join(args))
|
||||
|
||||
@random.command(name="Colour", aliases=["Color"])
|
||||
async def colour(self, ctx):
|
||||
r, g, b = colours.randomRGB()
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.from_rgb(r, g, b))
|
||||
embed.set_author(name="Random Colour")
|
||||
embed.add_field(name="RGB", value="{}, {}, {}".format(r, g, b), inline=False)
|
||||
embed.add_field(name="HEX", value=colours.RGBToHEX(r, g, b), inline=False)
|
||||
embed.add_field(name="HSL", value="{}°, {}%, {}%".format(*colours.RGBToHSL(r, g, b)), inline=False)
|
||||
embed.add_field(name="HSV", value="{}°, {}%, {}%".format(*colours.RGBToHSV(r, g, b)), inline=False)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@random.command(name="Timestamp", aliases=["Time", "Ts"])
|
||||
async def timestamp(self, ctx):
|
||||
hour = str(random.randint(0, 23))
|
||||
hour = ("0" if len(hour) == 1 else "") + hour
|
||||
minutes = str(random.randint(0, 23))
|
||||
minutes = ("0" if len(minutes) == 1 else "") + minutes
|
||||
await ctx.send("{}:{}".format(hour, minutes))
|
||||
|
||||
@random.command(name="Fact", aliases=["Knowledge"])
|
||||
async def fact(self, ctx):
|
||||
randomFact = requests.get("https://uselessfacts.jsph.pl/random.json?language=en").json()
|
||||
await ctx.send(randomFact["text"])
|
||||
|
||||
@commands.command(name="Yes/No", aliases=["Yn"])
|
||||
@help.Category(Category.Random)
|
||||
async def yesno(self, ctx):
|
||||
await ctx.send(random.choice(["Ja.", "Nee."]))
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Random(client))
|
|
@ -1,75 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import reactWord
|
||||
|
||||
|
||||
class ReactWord(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="React", usage="[Tekst] [Message id/url]*")
|
||||
@help.Category(category=Category.Other)
|
||||
async def react(self, ctx, *words):
|
||||
words = list(words)
|
||||
target = False
|
||||
channel = ctx.channel
|
||||
|
||||
# Check if the URL or the Id was passed
|
||||
if str(words[-1]).count("/") > 3:
|
||||
spl = str(words[-1]).split("/")
|
||||
channel = self.client.get_channel(int(spl[-2]))
|
||||
if channel is None:
|
||||
return await ctx.send("Ik kan geen kanaal zien met dit id.")
|
||||
words[-1] = spl[-1]
|
||||
|
||||
# Get the message object if an Id was passed, otherwise react to the message itself
|
||||
try:
|
||||
message = await channel.fetch_message(words[-1])
|
||||
if message is None:
|
||||
return await ctx.send("Ik kan geen bericht zien met dit id.")
|
||||
target = True
|
||||
except discord.HTTPException:
|
||||
message = ctx.message
|
||||
|
||||
# Reactions that were added before this command was executed
|
||||
previousReactions = ([x.emoji for x in message.reactions]) if len(message.reactions) != 0 else []
|
||||
eligible, arr = reactWord.check(list(words), previousReactions)
|
||||
|
||||
if not eligible:
|
||||
await ctx.send(arr[0])
|
||||
else:
|
||||
if target:
|
||||
await self.utilsCog.removeMessage(ctx.message)
|
||||
for reac in arr:
|
||||
await message.add_reaction(reac)
|
||||
|
||||
@commands.command(name="Character", aliases=["Char"], usage="[Karakter]")
|
||||
@help.Category(category=Category.Other)
|
||||
async def char(self, ctx, char: str = None):
|
||||
# Nothing passed
|
||||
if char is None:
|
||||
return await ctx.send("Controleer je argumenten")
|
||||
|
||||
char = char.lower()
|
||||
|
||||
# Not 1 char passed
|
||||
if len(char) != 1 or char not in reactWord.allowedCharacters():
|
||||
return await ctx.send("Dit is geen geldig karakter.")
|
||||
|
||||
var = reactWord.getAllVariants(char)
|
||||
|
||||
return await ctx.send("**Karakter**: {}\nOpties (**{}**): {}".format(
|
||||
char, len(var), " ".join(var)
|
||||
))
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(ReactWord(client))
|
138
cogs/release.py
138
cogs/release.py
|
@ -1,138 +0,0 @@
|
|||
import datetime
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
import requests
|
||||
import time
|
||||
|
||||
|
||||
class Release(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
# Gets upcoming game releases
|
||||
@commands.group(name="Releases", usage="[Pagina]*", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Games)
|
||||
async def releases(self, ctx, page="1"):
|
||||
for char in page:
|
||||
if not char.isdigit():
|
||||
await ctx.send("Geef een geldige pagina op.")
|
||||
return
|
||||
|
||||
dates = self.getDates()
|
||||
resp = requests.get("https://api.rawg.io/api/games?dates={},{}&page_size=25&page={}&ordering=released".format(
|
||||
dates[0], dates[1], page
|
||||
)).json()
|
||||
|
||||
try:
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Volgende Game Releases | Pagina {}".format(page))
|
||||
|
||||
embed.description = "\n".join(
|
||||
["{} (#{}): {}".format(result["name"], result["id"], self.rewriteDate(result["released"]))
|
||||
for result in resp["results"]])
|
||||
embed.set_footer(text="Voor gedetailleerde info: Didier Game Info [id]")
|
||||
except KeyError:
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.set_author(name="Game Releases")
|
||||
embed.add_field(name="Error", value="Er ging iets fout.")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
def getDates(self):
|
||||
today = datetime.datetime.fromtimestamp(time.time())
|
||||
nextMonth = datetime.datetime.fromtimestamp(time.time() + 2629743)
|
||||
return ["-".join([str(today.year), self.leadingZero(str(today.month)), self.leadingZero(str(today.day))]),
|
||||
"-".join([str(nextMonth.year),
|
||||
self.leadingZero(str(nextMonth.month)), self.leadingZero(str(nextMonth.day))])]
|
||||
|
||||
def leadingZero(self, num):
|
||||
return num if len(num) == 2 else "0" + num
|
||||
|
||||
# Shows more detailed information for a game
|
||||
@releases.command(name="Info", aliases=["Details"], usage="[Game Id]")
|
||||
async def info(self, ctx, *, game_id):
|
||||
game_id = self.create_slug(game_id)
|
||||
resp = requests.get("https://api.rawg.io/api/games/{}".format(str(game_id))).json()
|
||||
if "redirect" in resp:
|
||||
resp = requests.get("https://api.rawg.io/api/games/{}".format(resp["slug"])).json()
|
||||
if "Not found." in resp.values():
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.set_author(name="Game Info")
|
||||
embed.description = "Er is geen game gevonden met deze id of naam."
|
||||
else:
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Game Info")
|
||||
embed.add_field(name="Naam", value=resp["name"])
|
||||
embed.add_field(name="Id", value=resp["id"])
|
||||
embed.add_field(name="Datum", value="TBA" if resp["tba"] else self.rewriteDate(resp["released"]))
|
||||
embed.add_field(name="Platforms", value=", ".join(self.getPlatforms(resp)))
|
||||
embed.add_field(name="Stores", value=", ".join(self.getStores(resp)))
|
||||
embed.add_field(name="Genres", value=", ".join(self.getGenres(resp)))
|
||||
embed.add_field(name="Tags", value=self.getTags(resp), inline=False)
|
||||
embed.add_field(name="Description", value=self.writeDescription(resp["description_raw"]), inline=False)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
# Turns name into a slug
|
||||
def create_slug(self, game_id):
|
||||
try:
|
||||
# Check if it's a number
|
||||
game_id = int(game_id)
|
||||
return str(game_id)
|
||||
except ValueError:
|
||||
game_id = game_id.lower().replace(" ", "-").replace(":", "").replace("'", "")
|
||||
return game_id
|
||||
|
||||
def rewriteDate(self, date):
|
||||
date = date.split("-")
|
||||
return "-".join(reversed(date))
|
||||
|
||||
def getGenres(self, release):
|
||||
return sorted([genre["name"] for genre in release["genres"]])
|
||||
|
||||
# Returns a list of all platforms this game is available on
|
||||
def getPlatforms(self, release):
|
||||
return sorted([platform["platform"]["name"] for platform in release["platforms"]])
|
||||
|
||||
# Returns a list of all stores this game is available on
|
||||
def getStores(self, release):
|
||||
return sorted(store["store"]["name"] for store in release["stores"])
|
||||
|
||||
# Returns a list of all tags associated with this game
|
||||
def getTags(self, release):
|
||||
if len(release["tags"]) == 0:
|
||||
return "N/A"
|
||||
li = sorted([tag["name"] for tag in release["tags"]])
|
||||
st = li[0]
|
||||
for tag in li[1:]:
|
||||
if len(st) + 2 + len(tag) > 1024:
|
||||
break
|
||||
st += ", " + tag
|
||||
return st if st else "N/A"
|
||||
|
||||
# Truncates the description if necessary
|
||||
def writeDescription(self, description):
|
||||
if len(description) > 700:
|
||||
return description[:697] + "..."
|
||||
return description if description else "N/A"
|
||||
|
||||
@info.error
|
||||
async def info_on_error(self, ctx, error):
|
||||
if isinstance(error, commands.BadArgument):
|
||||
await ctx.send("Geef een geldig getal op.")
|
||||
elif isinstance(error, commands.MissingRequiredArgument):
|
||||
await ctx.send("Controleer je argumenten.")
|
||||
else:
|
||||
raise error
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Release(client))
|
|
@ -1,56 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from decorators import help
|
||||
from enums.help_categories import Category
|
||||
from functions.database import remind
|
||||
|
||||
|
||||
class Remind(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Remind", aliases=["Remindme"], usage="[Categorie]", case_insensitive=True, invoke_without_command=True)
|
||||
@help.Category(Category.Other)
|
||||
async def remind(self, ctx):
|
||||
"""
|
||||
Command group to remind the user of a certain thing every day.
|
||||
:param ctx: Discord Context
|
||||
"""
|
||||
rows = remind.getOrAddUser(ctx.author.id)
|
||||
|
||||
# TODO use a loop for this when not lazy
|
||||
categories = [remind.getIcon(rows[1]) + " Nightly", remind.getIcon(rows[2]) + " Les"]
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Remind Categorieën")
|
||||
embed.description = "\n".join(sorted(categories))
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@remind.command(name="Nightly")
|
||||
async def nightly(self, ctx):
|
||||
"""
|
||||
Command to get a daily Nightly reminder
|
||||
"""
|
||||
if remind.switchReminder(ctx.author.id, "nightly"):
|
||||
await ctx.send("Vanaf nu word je er dagelijks aan herinnerd om Didier Nightly te doen.")
|
||||
else:
|
||||
await ctx.send("Je zal er niet langer aan herinnerd worden om Didier Nightly te doen.")
|
||||
|
||||
@remind.command(name="Les", aliases=["Class", "Classes", "Sched", "Schedule"])
|
||||
async def les(self, ctx):
|
||||
"""
|
||||
Command to get a daily reminder with an embed of your schedule
|
||||
"""
|
||||
if remind.switchReminder(ctx.author.id, "les"):
|
||||
await ctx.send("Vanaf nu krijg je dagelijks je lessenrooster toegestuurd.")
|
||||
else:
|
||||
await ctx.send("Je zal je lessenrooster niet langer toegestuurd krijgen.")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Remind(client))
|
108
cogs/school.py
108
cogs/school.py
|
@ -1,108 +0,0 @@
|
|||
from data import constants
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.courses import years
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, eten, les
|
||||
import json
|
||||
|
||||
|
||||
class School(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Eten", aliases=["Food", "Menu"], usage="[Dag]*")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.School)
|
||||
async def eten(self, ctx, *day):
|
||||
day = les.getWeekDay(None if len(day) == 0 else day)[1]
|
||||
|
||||
# Create embed
|
||||
menu = eten.etenScript(day)
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Menu voor {}".format(day))
|
||||
if "gesloten" in menu[0].lower():
|
||||
embed.description = "Restaurant gesloten"
|
||||
else:
|
||||
embed.add_field(name="Soep:", value=menu[0], inline=False)
|
||||
embed.add_field(name="Hoofdgerechten:", value=menu[1], inline=False)
|
||||
|
||||
if menu[2]:
|
||||
embed.add_field(name="Groenten:", value=menu[2], inline=False)
|
||||
|
||||
embed.set_footer(text="Omwille van de coronamaatregelen is er een beperkter aanbod, en kan je enkel nog eten afhalen. Ter plaatse eten is niet meer mogelijk.")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name="Les", aliases=["Class", "Classes", "Sched", "Schedule"], usage="[Jaargang]* [Dag]*")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.School)
|
||||
async def les(self, ctx, *day):
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
|
||||
# These are hardcoded because they don't change & don't matter anymore in a month
|
||||
embed.set_author(name="Examenrooster 2de Bachelor")
|
||||
|
||||
embed.add_field(name="Algoritmen en Datastructuren 2",
|
||||
value="Woensdag 06/01/2021\n13:00 - 16:00\nFlanders Expo, Hal 4D", inline=False)
|
||||
embed.add_field(name="Functioneel Programmeren",
|
||||
value="Dinsdag 12/01/2021\n13:00 - 16:00\nFlanders Expo, Hal 5C", inline=False)
|
||||
embed.add_field(name="Systeemprogrammeren",
|
||||
value="Maandag 18/01/2021\n08:30 - 11:30\nOnline, schriftelijk", inline=False)
|
||||
embed.add_field(name="Communicatienetwerken",
|
||||
value="Vrijdag 22/01/2021\n13:00 - 16:00\nFlanders Expo Hal 4A", inline=False)
|
||||
embed.add_field(name="Statistiek en Probabiliteit",
|
||||
value="Vrijdag 29/01/2021\n08:30 - 11:30\nCampus Ardoyen Locus, PC Resto Locus", inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
# Les code
|
||||
# parsed = les.parseArgs(day)
|
||||
#
|
||||
# # Invalid arguments
|
||||
# if not parsed[0]:
|
||||
# return await ctx.send(parsed[1])
|
||||
#
|
||||
# day, dayDatetime, semester, year = parsed[1:]
|
||||
#
|
||||
# # Customize the user's schedule
|
||||
# schedule = self.customizeSchedule(ctx, year, semester)
|
||||
#
|
||||
# # Create the embed
|
||||
# embed = les.createEmbed(day, dayDatetime, semester, year, schedule)
|
||||
#
|
||||
# await ctx.send(embed=embed)
|
||||
|
||||
# Add all the user's courses
|
||||
def customizeSchedule(self, ctx, year, semester):
|
||||
schedule = les.getSchedule(semester, year)
|
||||
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
|
||||
if COC is None:
|
||||
return schedule
|
||||
|
||||
member = COC.get_member(ctx.author.id)
|
||||
|
||||
for role in member.roles:
|
||||
for univYear in years:
|
||||
for course in univYear:
|
||||
if course.value["year"] < year and course.value["id"] == role.id and course.value["semester"] == semester:
|
||||
with open("files/schedules/{}{}.json".format(course.value["year"], course.value["semester"]),
|
||||
"r") as fp:
|
||||
sched2 = json.load(fp)
|
||||
|
||||
for val in sched2:
|
||||
if val["course"] == course.value["name"]:
|
||||
val["custom"] = course.value["year"]
|
||||
schedule.append(val)
|
||||
return schedule
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(School(client))
|
|
@ -1,102 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
from functions.database import githubs, twitch
|
||||
|
||||
|
||||
class SelfPromo(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.utilsCog = self.client.get_cog("Utils")
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="GitHub", aliases=["Git", "GitHubs", "Gh"], case_insensitive=True, usage="[@Persoon]*", invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def github(self, ctx, member: discord.Member = None):
|
||||
# Get a specific member's GitHub
|
||||
if member:
|
||||
user_git = githubs.get_user(member.id)
|
||||
if not user_git:
|
||||
return await ctx.send("**{}** heeft zijn GitHub link nog niet doorgegeven.".format(member.display_name))
|
||||
|
||||
return await self.createPersonalPromo(ctx, member, user_git[0][0], discord.Colour.from_rgb(250, 250, 250), "GitHub")
|
||||
|
||||
l = githubs.getAll()
|
||||
await self.createPromoEmbed(ctx, l, discord.Colour.from_rgb(250, 250, 250), "GitHub", "files/images/github.png")
|
||||
|
||||
@github.command(name="Add", aliases=["Insert", "Register", "Set"], usage="[Link]")
|
||||
async def githubadd(self, ctx, link):
|
||||
if "github.com" not in link.lower() and "github.ugent.be" not in link.lower() and "gitlab.com" not in link.lower():
|
||||
link = "https://github.com/{}".format(link)
|
||||
|
||||
githubs.add(ctx.author.id, link)
|
||||
await ctx.message.add_reaction("✅")
|
||||
|
||||
@commands.group(name="Twitch", aliases=["Streams"], case_insensitive=True, usage="[@Persoon]", invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def twitch(self, ctx, member: discord.Member = None):
|
||||
# Get a specific member's GitHub
|
||||
if member:
|
||||
user_twitch = twitch.get_user(member.id)
|
||||
if not user_twitch:
|
||||
return await ctx.send("**{}** heeft zijn Twitch link nog niet doorgegeven.".format(member.display_name))
|
||||
|
||||
return await self.createPersonalPromo(ctx, member, user_twitch[0][0], discord.Colour.from_rgb(100, 65, 165), "Twitch")
|
||||
|
||||
l = twitch.getAll()
|
||||
await self.createPromoEmbed(ctx, l, discord.Colour.from_rgb(100, 65, 165), "Twitch", "files/images/twitch.png")
|
||||
|
||||
@twitch.command(name="Add", aliases=["Insert", "Register", "Set"], usage="[Link]")
|
||||
async def twitchadd(self, ctx, link):
|
||||
if "twitch.tv" not in link.lower():
|
||||
link = "https://www.twitch.tv/{}".format(link)
|
||||
|
||||
twitch.add(ctx.author.id, link)
|
||||
await ctx.message.add_reaction("✅")
|
||||
|
||||
# Creates embed with everyone's links & a fancy image
|
||||
async def createPromoEmbed(self, ctx, users, colour, type, imageUrl=None):
|
||||
# Image file
|
||||
file = None
|
||||
|
||||
# Sort users by Discord name
|
||||
users = [[self.utilsCog.getMember(ctx, user[0]), user[1]] for user in users if self.utilsCog.getMember(ctx, user[0]) is not None]
|
||||
users.sort(key=lambda x: x[0].name)
|
||||
|
||||
embed = discord.Embed(colour=colour)
|
||||
if imageUrl is not None:
|
||||
# Link
|
||||
if "https" in imageUrl:
|
||||
embed.set_thumbnail(url=imageUrl)
|
||||
else:
|
||||
# Local file
|
||||
file = discord.File(imageUrl, filename="icon.png")
|
||||
embed.set_thumbnail(url="attachment://icon.png")
|
||||
embed.set_author(name="{} Links".format(type))
|
||||
for user in users:
|
||||
embed.add_field(name="{} ({})".format(
|
||||
user[0].display_name, user[0].name
|
||||
), value=user[1], inline=False)
|
||||
embed.set_footer(text="Wil je je eigen {0} hierin? Gebruik {0} Add [Link] of stuur een DM naar DJ STIJN.".format(type))
|
||||
if file is not None:
|
||||
await ctx.send(embed=embed, file=file)
|
||||
else:
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def createPersonalPromo(self, ctx, user, link, colour, type):
|
||||
embed = discord.Embed(colour=colour)
|
||||
embed.set_author(name="{} Links".format(type), icon_url=user.avatar_url)
|
||||
embed.add_field(name="{} link van {}".format(type, user.display_name), value=link)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(SelfPromo(client))
|
131
cogs/stats.py
131
cogs/stats.py
|
@ -1,131 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks
|
||||
from functions.database import stats
|
||||
import json
|
||||
|
||||
|
||||
class Stats(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Stats", usage="[Categorie]*", case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(category=Category.Other)
|
||||
async def stats(self, ctx):
|
||||
s = stats.getOrAddUser(ctx.author.id)
|
||||
|
||||
# Calculate the percentages
|
||||
robAttempts = int(s[2]) + int(s[3]) if int(s[2]) + int(s[3]) != 0 else 1
|
||||
robSuccessPercent = round(100 * int(s[2]) / robAttempts, 2)
|
||||
robFailedPercent = round(100 * int(s[3]) / robAttempts, 2)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="{}'s Stats".format(ctx.author.display_name))
|
||||
embed.add_field(name="Geslaagde Rob Pogingen", value="{} ({})%".format(s[2], robSuccessPercent))
|
||||
embed.add_field(name="Gefaalde Rob Pogingen", value="{} ({})%".format(s[3], robFailedPercent))
|
||||
embed.add_field(name="Aantal Dinks Gestolen", value="{:,}".format(round(s[4])))
|
||||
embed.add_field(name="Aantal Nightlies", value=str(s[6]))
|
||||
embed.add_field(name="Langste Nightly Streak", value=str(s[5]))
|
||||
embed.add_field(name="Totale Profit", value="{:,}".format(round(s[7])))
|
||||
embed.add_field(name="Aantal keer gepoked", value=str(s[1]))
|
||||
embed.add_field(name="Aantal Gewonnen Coinflips", value=str(s[8]))
|
||||
embed.add_field(name="Totale winst uit Coinflips", value="{:,}".format(round(s[9])))
|
||||
embed.add_field(name="Aantal Bails", value="{:,}".format(int(s[10])))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@stats.command(aliases=["Coinflip"], hidden=True)
|
||||
async def cf(self, ctx):
|
||||
with open("files/stats.json", "r") as fp:
|
||||
s = json.load(fp)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Coinflip Stats")
|
||||
embed.description = "**Kop**: {:,} ({}%)\n**Munt**: {:,} ({}%)".format(
|
||||
s["cf"]["h"], self.percent(s["cf"], "h"), s["cf"]["t"], self.percent(s["cf"], "t"))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@stats.command(aliases=["Roll"], hidden=True)
|
||||
async def dice(self, ctx):
|
||||
with open("files/stats.json", "r") as fp:
|
||||
s = json.load(fp)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Dice Stats")
|
||||
embed.description = "\n".join(["**{}**: {:,} ({}%)".format(
|
||||
i, s["dice"][i], self.percent(s["dice"], i)) for i in sorted(s["dice"].keys())])
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@stats.command(hidden=True)
|
||||
async def rob(self, ctx):
|
||||
with open("files/stats.json", "r") as fp:
|
||||
s = json.load(fp)["rob"]
|
||||
totalAttempts = s["robs_success"] + s["robs_failed"]
|
||||
successPercent = round(100 * s["robs_success"] / totalAttempts, 2)
|
||||
failedPercent = round(100 * s["robs_failed"] / totalAttempts, 2)
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Rob Stats")
|
||||
embed.description = "**Geslaagd**: {:,} ({}%)\n**Gefaald**: {:,} ({}%)\n**Borg betaald**: {:,}".format(
|
||||
s["robs_success"], successPercent, s["robs_failed"], failedPercent, round(s["bail_paid"])
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@stats.command(name="Channels", aliases=["C", "CA"], usage="[#Channel]*", hidden=True)
|
||||
@commands.check(checks.isMod)
|
||||
async def channels(self, ctx, channel: discord.TextChannel = None):
|
||||
res = stats.channel_activity(channel)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
|
||||
if channel:
|
||||
embed.set_author(name="Channel Activity - {}".format(channel.name))
|
||||
channel_instance = self.client.get_channel(int(res[0][0]))
|
||||
embed.add_field(name="Aantal berichten", value="{:,}".format(round(float(res[0][1]), 2)), inline=False)
|
||||
|
||||
try:
|
||||
last_message = await channel_instance.fetch_message(channel_instance.last_message_id)
|
||||
except discord.NotFound:
|
||||
last_message = None
|
||||
|
||||
if last_message is None:
|
||||
embed.add_field(name="Laatste bericht", value="[Verwijderd]", inline=False)
|
||||
else:
|
||||
embed.add_field(name="Laatste bericht", value="[Jump URL]({})".format(last_message.jump_url), inline=False)
|
||||
elif ctx.guild:
|
||||
embed.set_author(name="Channel Activity - {}".format(ctx.guild))
|
||||
|
||||
description = ""
|
||||
for c in sorted(res, key=lambda x: int(x[1]), reverse=True):
|
||||
if not any(tc.id == int(c[0]) for tc in ctx.guild.text_channels):
|
||||
continue
|
||||
|
||||
channel_instance = self.client.get_channel(int(c[0]))
|
||||
|
||||
description += "{}: {:,}\n".format(channel_instance.mention, round(float(c[1]), 2))
|
||||
|
||||
embed.description = description
|
||||
else:
|
||||
return await ctx.send("Dit commando werkt niet in DM's.")
|
||||
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
async def callStats(self, name, ctx):
|
||||
await [command for command in self.stats.commands if command.name == name][0](ctx)
|
||||
|
||||
def percent(self, dic, stat):
|
||||
total = sum([int(dic[s]) for s in dic])
|
||||
if total == 0:
|
||||
total = 1
|
||||
|
||||
return round(100 * int(dic[stat]) / total, 2)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Stats(client))
|
123
cogs/store.py
123
cogs/store.py
|
@ -1,123 +0,0 @@
|
|||
from converters.numbers import Abbreviated
|
||||
from data import storePages
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from enums.numbers import Numbers
|
||||
from functions import checks
|
||||
from functions.database import store, currency
|
||||
from functions.numbers import getRep
|
||||
|
||||
|
||||
class Store(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.group(name="Store", aliases=["Shop"], case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Currency)
|
||||
async def store(self, ctx):
|
||||
entries = store.getAllItems()
|
||||
await storePages.Pages(source=storePages.Source(entries), clear_reactions_after=True).start(ctx)
|
||||
|
||||
@store.command(name="Buy", aliases=["Get"], hidden=True)
|
||||
async def storeBuy(self, ctx, item, amount: Abbreviated = 1):
|
||||
if amount is None:
|
||||
return
|
||||
|
||||
await self.buy(ctx, item, amount)
|
||||
|
||||
@commands.command(name="Buy", aliases=["Get"], usage="[Item id] [Aantal]*")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Currency)
|
||||
async def buy(self, ctx, item, amount: Abbreviated = 1):
|
||||
if amount is None:
|
||||
return
|
||||
|
||||
try:
|
||||
item = int(item)
|
||||
except ValueError:
|
||||
return await ctx.send("Dit is geen geldig id.")
|
||||
|
||||
success, message = store.buy(ctx, ctx.author.id, item, amount)
|
||||
if not success:
|
||||
return await ctx.send(message)
|
||||
|
||||
rep = getRep(message["price"], Numbers.t.value)
|
||||
|
||||
return await ctx.send("**{}** heeft **{} {}{}** gekocht voor **{}** Didier Dink{}.".format(
|
||||
ctx.author.display_name, amount, message["name"], checks.pluralS(amount),
|
||||
rep, checks.pluralS(message["price"])
|
||||
))
|
||||
|
||||
@store.command(name="Sell", hidden=True)
|
||||
async def storeSell(self, ctx, itemid, amount: Abbreviated = 1):
|
||||
if amount is None:
|
||||
return
|
||||
await self.sell(ctx, itemid, amount)
|
||||
|
||||
@commands.command(name="Sell", usage="[Item id] [Aantal]")
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Currency)
|
||||
async def sell(self, ctx, itemid, amount: Abbreviated = 1):
|
||||
if amount is None:
|
||||
return
|
||||
|
||||
try:
|
||||
itemid = int(itemid)
|
||||
except ValueError:
|
||||
return await ctx.send("Dit is geen geldig id.")
|
||||
|
||||
inv = store.inventory(ctx.author.id)
|
||||
|
||||
if not inv or not any(int(item[0]) == itemid for item in inv):
|
||||
return await ctx.send("Je hebt geen item met dit id.")
|
||||
|
||||
item_tuple = None
|
||||
for item in inv:
|
||||
if item[0] == itemid:
|
||||
item_tuple = item
|
||||
break
|
||||
|
||||
if amount.lower() == "all":
|
||||
amount = int(item_tuple[2])
|
||||
|
||||
if int(item_tuple[2]) < amount:
|
||||
return await ctx.send("Je hebt niet zoveel {}s.".format(item_tuple[1]))
|
||||
|
||||
store.sell(int(ctx.author.id), itemid, int(amount), int(item_tuple[2]))
|
||||
price = int(store.getItemPrice(itemid)[0])
|
||||
returnValue = round(0.8 * (price * amount))
|
||||
|
||||
currency.update(ctx.author.id, "dinks", currency.dinks(ctx.author.id) + returnValue)
|
||||
|
||||
await ctx.send("**{}** heeft **{} {}{}** verkocht voor **{}** Didier Dinks!".format(
|
||||
ctx.author.display_name, amount, item_tuple[1], "s" if amount != 1 else "",
|
||||
getRep(returnValue, Numbers.t.value)
|
||||
))
|
||||
|
||||
@commands.command(name="Inventory", aliases=["Inv", "Items"])
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Currency)
|
||||
async def inventory(self, ctx, *args):
|
||||
inv = store.inventory(ctx.author.id)
|
||||
inv = sorted(inv, key=lambda x: x[1])
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Inventory van {}".format(ctx.author.display_name))
|
||||
embed.set_thumbnail(url=str(ctx.author.avatar_url))
|
||||
if len(inv) == 0:
|
||||
embed.description = "Je hebt nog niets gekocht!\n" \
|
||||
"Koop iets in de Store wanneer DJ STIJN niet langer te lui is om er iets in te steken."
|
||||
else:
|
||||
embed.description = "\n".join("#{} {}: {}".format(item[0], item[1], item[2]) for item in inv)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Store(client))
|
234
cogs/tasks.py
234
cogs/tasks.py
|
@ -1,234 +0,0 @@
|
|||
from data import constants
|
||||
from data.remind import Reminders
|
||||
from discord.ext import commands, tasks
|
||||
from enums.numbers import Numbers
|
||||
from functions import timeFormatters
|
||||
from functions.database import currency, poke, prison, birthdays, stats
|
||||
import json
|
||||
import random
|
||||
import requests
|
||||
import time
|
||||
|
||||
|
||||
class Tasks(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.bankInterest.start()
|
||||
self.resetPrison.start()
|
||||
self.resetLost.start()
|
||||
# self.resetPoke.start()
|
||||
self.checkBirthdays.start()
|
||||
self.updateMessageCounts.start()
|
||||
self.sendReminders.start()
|
||||
|
||||
@tasks.loop(hours=1.0)
|
||||
async def bankInterest(self):
|
||||
"""
|
||||
Task that gives daily interest
|
||||
"""
|
||||
# Don't do it multiple times a day if bot dc's, ...
|
||||
with open("files/lastTasks.json", "r") as fp:
|
||||
lastTasks = json.load(fp)
|
||||
if int(self.getCurrentHour()) == 0 and int(time.time()) - int(lastTasks["interest"]) > 10000:
|
||||
users = currency.getAllRows()
|
||||
bitcoinPrice = self.getCurrentBitcoinPrice()
|
||||
for user in users:
|
||||
# People in prison don't get interest
|
||||
if len(prison.getUser(int(user[0]))) != 0:
|
||||
continue
|
||||
|
||||
if float(user[3]) != 0.0:
|
||||
currency.update(user[0], "investeddays", int(user[4]) + 1)
|
||||
profit = ((float(user[3]) + float(user[5])) * (1 + (float(user[2]) * 0.01))) - float(user[3])
|
||||
# Can't exceed 1 quadrillion
|
||||
# Check BC as well so they can't put everything into BC to cheat the system
|
||||
if float(user[1]) + float(user[3]) + float(user[5]) + profit + (float(user[8]) * bitcoinPrice) > Numbers.q.value:
|
||||
# In case adding profit would exceed 1q, only add the difference
|
||||
profit = Numbers.q.value - float(user[1]) - float(user[3]) - float(user[5]) - (float(user[8]) * bitcoinPrice)
|
||||
# Don't reduce the current profit if Dinks were gained some other way (rob, bc, ...)
|
||||
if profit > 0:
|
||||
currency.update(user[0], "profit", float(user[5]) + profit)
|
||||
|
||||
await self.client.get_user(int(user[0])).send("Je hebt de invest-limiet van 1Q Didier Dinks bereikt.\nIndien je nog meer Didier Dinks wil sparen, kan je 1q Didier Dinks omruilen voor een Platinum Dink in de shop.")
|
||||
|
||||
else:
|
||||
currency.update(user[0], "profit", float(user[5]) + profit)
|
||||
lastTasks["interest"] = int(round(time.time()))
|
||||
with open("files/lastTasks.json", "w") as fp:
|
||||
json.dump(lastTasks, fp)
|
||||
|
||||
@bankInterest.before_loop
|
||||
async def beforeBankInterest(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
@tasks.loop(hours=1.0)
|
||||
async def resetLost(self):
|
||||
"""
|
||||
Task that resets Lost Today
|
||||
"""
|
||||
# Don't do it multiple times a day if bot dc's, ...
|
||||
with open("files/lastTasks.json", "r") as fp:
|
||||
lastTasks = json.load(fp)
|
||||
|
||||
if int(self.getCurrentHour()) == 0 and int(time.time()) - int(lastTasks["lost"]) > 10000:
|
||||
with open("files/lost.json", "r") as fp:
|
||||
fc = json.load(fp)
|
||||
fc["today"] = 0
|
||||
with open("files/lost.json", "w") as fp:
|
||||
json.dump(fc, fp)
|
||||
|
||||
lastTasks["lost"] = round(time.time())
|
||||
with open("files/lastTasks.json", "w") as fp:
|
||||
json.dump(lastTasks, fp)
|
||||
|
||||
@resetLost.before_loop
|
||||
async def beforeResetLost(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
@tasks.loop(hours=6.0)
|
||||
async def resetPoke(self):
|
||||
"""
|
||||
Task that resets Poke
|
||||
"""
|
||||
if int(time.time()) - int(poke.get()[1]) > 259200:
|
||||
await self.client.get_guild(int(self.client.constants.CallOfCode))\
|
||||
.get_channel(int(self.client.constants.DidierPosting))\
|
||||
.send("Poke is gereset door inactiviteit. <@!{}> is hem!".format(int(poke.reset())))
|
||||
|
||||
@resetPoke.before_loop
|
||||
async def beforeResetPoke(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
@tasks.loop(hours=1.0)
|
||||
async def resetPrison(self):
|
||||
"""
|
||||
Task that lowers prison time daily
|
||||
"""
|
||||
# Don't do it multiple times a day if bot dc's, ...
|
||||
with open("files/lastTasks.json", "r") as fp:
|
||||
lastTasks = json.load(fp)
|
||||
|
||||
if int(self.getCurrentHour()) == 0 and int(time.time()) - int(lastTasks["prison"]) > 10000:
|
||||
prison.dailyLowers()
|
||||
|
||||
with open("files/lastTasks.json", "w") as fp:
|
||||
lastTasks["prison"] = round(time.time())
|
||||
json.dump(lastTasks, fp)
|
||||
|
||||
@resetPrison.before_loop
|
||||
async def beforeResetPrison(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
@tasks.loop(hours=1.0)
|
||||
async def checkBirthdays(self):
|
||||
"""
|
||||
Task that wishes people a happy birthday
|
||||
"""
|
||||
# Don't do it multiple times a day if bot dc's, ...
|
||||
with open("files/lastTasks.json", "r") as fp:
|
||||
lastTasks = json.load(fp)
|
||||
if int(self.getCurrentHour()) == 6 and int(time.time()) - int(lastTasks["birthdays"]) > 10000:
|
||||
dt = timeFormatters.dateTimeNow()
|
||||
res = birthdays.get_users_on_date(dt.day, dt.month)
|
||||
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
people = [COC.get_member(int(user[0])) for user in res]
|
||||
general = COC.get_channel(int(constants.CoCGeneral))
|
||||
|
||||
lastTasks["birthdays"] = round(time.time())
|
||||
with open("files/lastTasks.json", "w") as fp:
|
||||
json.dump(lastTasks, fp)
|
||||
|
||||
if not people:
|
||||
return
|
||||
|
||||
if len(people) == 1:
|
||||
return await general.send("Gelukkige verjaardag {}!".format(people[0].mention))
|
||||
return await general.send("Gelukkige verjaardag {} en {}!".format(
|
||||
", ".join(user.mention for user in people[:-1]),
|
||||
people[-1].mention
|
||||
))
|
||||
|
||||
@checkBirthdays.before_loop
|
||||
async def beforecheckBirthdays(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
@tasks.loop(hours=1.0)
|
||||
async def updateMessageCounts(self):
|
||||
"""
|
||||
Task that updates the activity counter for channels
|
||||
"""
|
||||
# Don't do it multiple times a day if bot dc's, ...
|
||||
with open("files/lastTasks.json", "r") as fp:
|
||||
lastTasks = json.load(fp)
|
||||
if int(self.getCurrentHour()) == 0 and int(time.time()) - int(lastTasks["channels"]) > 10000:
|
||||
channels = stats.channel_activity()
|
||||
for channel in channels:
|
||||
stats.lower_channel(int(channel[0]), 0.95 * float(channel[1]))
|
||||
|
||||
with open("files/lastTasks.json", "w") as fp:
|
||||
lastTasks["channels"] = round(time.time())
|
||||
json.dump(lastTasks, fp)
|
||||
|
||||
@updateMessageCounts.before_loop
|
||||
async def beforeupdateMessageCounts(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
@tasks.loop(hours=1.0)
|
||||
async def sendReminders(self):
|
||||
"""
|
||||
Task that sends people daily reminders
|
||||
"""
|
||||
# Don't do it multiple times a day if bot dc's, ...
|
||||
with open("files/lastTasks.json", "r") as fp:
|
||||
lastTasks = json.load(fp)
|
||||
if int(self.getCurrentHour()) == 7 and int(time.time()) - int(lastTasks["remind"]) > 10000:
|
||||
reminders = Reminders()
|
||||
|
||||
weekday = self.getCurrentWeekday()
|
||||
|
||||
for category in reminders.categories:
|
||||
# Check if this reminder is temporarily disabled
|
||||
if category["disabled"]:
|
||||
continue
|
||||
|
||||
# Checks if this reminder can be sent on weekdays
|
||||
if (not category["weekends"]) and weekday > 4:
|
||||
continue
|
||||
|
||||
for user in category["users"]:
|
||||
userInstance = self.client.get_user(user)
|
||||
|
||||
# User can't be fetched for whatever reason, ignore instead of crashing
|
||||
if userInstance is None:
|
||||
continue
|
||||
|
||||
# Check if a special embed has to be attached for this reminder
|
||||
if "embed" not in category:
|
||||
await userInstance.send(random.choice(category["messages"]))
|
||||
else:
|
||||
await userInstance.send(random.choice(category["messages"]), embed=category["embed"])
|
||||
|
||||
with open("files/lastTasks.json", "w") as fp:
|
||||
lastTasks["remind"] = round(time.time())
|
||||
json.dump(lastTasks, fp)
|
||||
|
||||
@sendReminders.before_loop
|
||||
async def beforeSendReminders(self):
|
||||
await self.client.wait_until_ready()
|
||||
|
||||
def getCurrentHour(self):
|
||||
return timeFormatters.dateTimeNow().hour
|
||||
|
||||
def getCurrentWeekday(self):
|
||||
return timeFormatters.dateTimeNow().weekday()
|
||||
|
||||
def getCurrentBitcoinPrice(self):
|
||||
result = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json").json()
|
||||
currentPrice = result["bpi"]["EUR"]["rate_float"]
|
||||
return float(currentPrice)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Tasks(client))
|
|
@ -1,31 +0,0 @@
|
|||
from discord.ext import commands
|
||||
from functions import checks
|
||||
|
||||
|
||||
class TestCog(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def cog_check(self, ctx):
|
||||
"""
|
||||
Check executed for every command in this cog.
|
||||
|
||||
If necessary, create your own check here. A check is just a function
|
||||
that returns True or False, and takes ctx as an argument. A command will
|
||||
only be executed when this check returns True, which is why that is the default
|
||||
implementation for this function.
|
||||
"""
|
||||
return True
|
||||
|
||||
@commands.command()
|
||||
async def test(self, ctx):
|
||||
pass
|
||||
|
||||
@test.error
|
||||
async def test_handler(self, ctx, error):
|
||||
raise error
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(TestCog(client))
|
128
cogs/train.py
128
cogs/train.py
|
@ -1,128 +0,0 @@
|
|||
from data import paginatedLeaderboard
|
||||
import datetime
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands, menus
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, timeFormatters
|
||||
import requests
|
||||
|
||||
|
||||
class Train(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Train", aliases=["Trein"], usage="[Vertrek]* [Bestemming]")
|
||||
@help.Category(category=Category.School)
|
||||
async def train(self, ctx, *args):
|
||||
if not args or len(args) > 2:
|
||||
await ctx.send("Controleer je argumenten.")
|
||||
return
|
||||
destination = args[-1]
|
||||
departure = args[0] if len(args) > 1 else "Gent Sint-Pieters"
|
||||
|
||||
req = requests.get(
|
||||
"http://api.irail.be/connections/?from={}&to={}&alerts=true&lang=nl&format=json".format(departure,
|
||||
destination)).json()
|
||||
if "error" in req:
|
||||
embed = discord.Embed(colour=discord.Colour.red())
|
||||
embed.set_author(name="Treinen van {} naar {}".format(
|
||||
self.formatCity(departure), self.formatCity(destination)))
|
||||
embed.add_field(name="Error", value="Er ging iets fout, probeer het later opnieuw.", inline=False)
|
||||
await self.sendEmbed(ctx, embed)
|
||||
return
|
||||
|
||||
pages = paginatedLeaderboard.Pages(source=TrainPagination(self.formatConnections(req["connection"]),
|
||||
self.formatCity(departure),
|
||||
self.formatCity(destination)),
|
||||
clear_reactions_after=True)
|
||||
await pages.start(ctx)
|
||||
|
||||
def formatConnections(self, connections):
|
||||
response = []
|
||||
for connection in sorted(connections, key=lambda con: con["departure"]["time"]):
|
||||
conn = {}
|
||||
if connection["departure"]["canceled"] != "0" or connection["arrival"]["canceled"] != "0":
|
||||
conn = {"Canceled": "Afgeschaft"}
|
||||
dep = connection["departure"]
|
||||
arr = connection["arrival"]
|
||||
conn["depStation"] = self.formatCity(dep["station"])
|
||||
conn["depTime"] = self.formatTime(dep["time"])
|
||||
conn["delay"] = self.formatDelay(dep["delay"])
|
||||
conn["track"] = dep["platform"]
|
||||
conn["arrStation"] = self.formatCity(arr["station"])
|
||||
conn["direction"] = self.formatCity(dep["direction"]["name"])
|
||||
conn["arrTime"] = self.formatTime(arr["time"])
|
||||
conn["duration"] = self.formatTime(connection["duration"])
|
||||
response.append(conn)
|
||||
return response
|
||||
|
||||
def formatTime(self, timestamp):
|
||||
if int(timestamp) <= 86400:
|
||||
minutes = int(timestamp) // 60
|
||||
if minutes < 60:
|
||||
return str(minutes) + "m"
|
||||
return "{}h{:02}m".format(minutes // 60, minutes % 60)
|
||||
else:
|
||||
return timeFormatters.epochToDate(int(timestamp), "%H:%M")["date"]
|
||||
|
||||
def formatDelay(self, seconds):
|
||||
seconds = int(seconds)
|
||||
return self.sign(seconds) + self.formatTime(abs(seconds)) if seconds != 0 else ""
|
||||
|
||||
def sign(self, number):
|
||||
return "-" if int(number) < 0 else "+"
|
||||
|
||||
def formatCity(self, city):
|
||||
city = city[0].upper() + city[1:]
|
||||
arr = []
|
||||
for i, letter in enumerate(city):
|
||||
if (i > 0 and (city[i - 1] == " " or city[i - 1] == "-")) or i == 0:
|
||||
arr.append(letter.upper())
|
||||
else:
|
||||
arr.append(letter.lower())
|
||||
return "".join(arr)
|
||||
|
||||
async def sendEmbed(self, ctx, embed):
|
||||
if checks.allowedChannels(ctx):
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
await ctx.author.send(embed=embed)
|
||||
|
||||
|
||||
class TrainPagination(menus.ListPageSource):
|
||||
def __init__(self, data, departure, destination):
|
||||
super().__init__(data, per_page=3)
|
||||
self.departure = departure
|
||||
self.destination = destination
|
||||
|
||||
async def format_page(self, menu: menus.MenuPages, entries):
|
||||
offset = menu.current_page * self.per_page
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Treinen van {} naar {}".format(self.departure, self.destination))
|
||||
embed.set_footer(text="{}/{}".format(menu.current_page + 1, self.get_max_pages()))
|
||||
|
||||
for i, connection in enumerate(entries, start=offset):
|
||||
afgeschaft = "Canceled" in connection
|
||||
embed.add_field(name="Van", value=str(connection["depStation"]), inline=True)
|
||||
embed.add_field(name="Om", value=str(connection["depTime"]), inline=True)
|
||||
embed.add_field(name="Spoor", value=str(connection["track"]), inline=True)
|
||||
embed.add_field(name="Richting", value=str(connection["direction"]), inline=True)
|
||||
embed.add_field(name="Aankomst", value=(str(connection["arrTime"])
|
||||
if not afgeschaft else "**AFGESCHAFT**"), inline=True)
|
||||
embed.add_field(name="Vertraging", value=str(connection["delay"]) if connection["delay"] != "" else "0",
|
||||
inline=True)
|
||||
|
||||
# White space
|
||||
if i - offset < 2:
|
||||
embed.add_field(name="\u200b", value="\u200b", inline=False)
|
||||
return embed
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Train(client))
|
|
@ -1,114 +0,0 @@
|
|||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions.stringFormatters import titleCase as tc
|
||||
from googletrans import Translator, LANGUAGES
|
||||
import re
|
||||
|
||||
|
||||
class Translate(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Translate", aliases=["Tl", "Trans"], usage="[Tekst] [Van]* [Naar]*")
|
||||
@help.Category(Category.Words)
|
||||
async def translate(self, ctx, query=None, to="nl", fr="auto"):
|
||||
if query is None:
|
||||
return await ctx.send("Controleer je argumenten.")
|
||||
|
||||
success, query = await self.getQuery(ctx, query)
|
||||
if not success:
|
||||
return await ctx.send(query)
|
||||
|
||||
translator = Translator()
|
||||
|
||||
# From & To were provided, swap them
|
||||
if fr != "auto":
|
||||
temp = fr
|
||||
fr = to
|
||||
to = temp
|
||||
|
||||
try:
|
||||
translation = translator.translate(query, to, fr)
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Didier Translate")
|
||||
|
||||
if fr == "auto":
|
||||
language = translation.extra_data["original-language"]
|
||||
embed.add_field(name="Gedetecteerde taal", value=tc(LANGUAGES[language]))
|
||||
embed.add_field(name="Zekerheid", value="{}%".format(translation.extra_data["confidence"] * 100))
|
||||
|
||||
embed.add_field(name="Origineel ({})".format(translation.src.upper()), value=query, inline=False)
|
||||
embed.add_field(name="Vertaling ({})".format(to.upper()), value=translation.text)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
except ValueError as e:
|
||||
message = str(e)
|
||||
if "destination" in message:
|
||||
return await ctx.send("{} is geen geldige taal.".format(tc(to)))
|
||||
|
||||
if "source" in message:
|
||||
return await ctx.send("{} is geen geldige taal.".format(tc(fr)))
|
||||
|
||||
raise e
|
||||
|
||||
@commands.command(name="Detect", aliases=["Ld"], usage="[Tekst]")
|
||||
@help.Category(Category.Words)
|
||||
async def detect(self, ctx, query=None):
|
||||
if query is None:
|
||||
return await ctx.send("Controleer je argumenten.")
|
||||
|
||||
success, query = await self.getQuery(ctx, query)
|
||||
if not success:
|
||||
return await ctx.send(query)
|
||||
|
||||
translator = Translator()
|
||||
language = translator.detect(query)
|
||||
|
||||
confidence = language.confidence * 100
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name="Language Detection")
|
||||
embed.add_field(name="Zin", value=query, inline=False)
|
||||
embed.add_field(name="Gedetecteerde taal", value=tc(LANGUAGES[language.lang]))
|
||||
embed.add_field(name="Zekerheid", value="{}%".format(confidence))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
async def getQuery(self, ctx, query):
|
||||
# Check if it's a link to a message
|
||||
if re.match(r"^https://discord.com/channels/[0-9A-Za-z@]+/[0-9]+/[0-9]+$", query):
|
||||
spl = query.split("/")
|
||||
channel = self.client.get_channel(int(spl[-2]))
|
||||
if channel is None:
|
||||
return False, "Ik kan geen kanaal zien met dit id."
|
||||
|
||||
message = await channel.fetch_message(spl[-1])
|
||||
if message is None:
|
||||
return False, "Ik kan geen bericht zien met dit id."
|
||||
|
||||
query = message.content
|
||||
|
||||
else:
|
||||
try:
|
||||
# An id was passed instead
|
||||
query = int(query)
|
||||
message = await ctx.channel.fetch_message(query)
|
||||
if message is None:
|
||||
return False, "Ik kan geen bericht zien met dit id."
|
||||
query = message.content
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not query:
|
||||
return False, "Dit is geen geldig bericht."
|
||||
|
||||
return True, query
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Translate(client))
|
|
@ -1,67 +0,0 @@
|
|||
from data import constants
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from decorators import help
|
||||
from enums.help_categories import Category
|
||||
|
||||
|
||||
class Utils(commands.Cog):
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.client.locked = False
|
||||
self.client.lockedUntil = -1
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
# Marco Polo to check if bot is running & delay
|
||||
@commands.command(name="Marco")
|
||||
@help.Category(category=Category.Didier)
|
||||
async def marco(self, ctx):
|
||||
await ctx.send("Polo! {}ms".format(round(self.client.latency * 1000)))
|
||||
|
||||
async def removeMessage(self, message):
|
||||
try:
|
||||
await message.delete()
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
# Send a DM to a user
|
||||
async def sendDm(self, userid, message: str):
|
||||
user = self.client.get_user(int(userid))
|
||||
await user.send(message)
|
||||
|
||||
# Send an Embed to a user
|
||||
async def sendEmbed(self, userid, embed):
|
||||
await discord.utils.get(self.client.get_all_members(), id=int(userid)).send(embed=embed)
|
||||
|
||||
# Returns a member object of a user
|
||||
def getMember(self, ctx, memberid):
|
||||
if str(ctx.channel.type) == "private" or ctx.guild.get_member(int(memberid)) is None:
|
||||
if str(memberid) == str(ctx.author.id):
|
||||
return ctx.author
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
return COC.get_member(int(memberid))
|
||||
|
||||
return ctx.guild.get_member(int(memberid))
|
||||
|
||||
# Returns a user's display name if he's in this server, else COC
|
||||
def getDisplayName(self, ctx, memberid):
|
||||
# Checks if this is a DM, or the user is not in the guild
|
||||
if str(ctx.channel.type) == "private" or ctx.guild.get_member(int(memberid)) is None:
|
||||
if str(memberid) == str(ctx.author.id):
|
||||
return ctx.author.display_name
|
||||
COC = self.client.get_guild(int(constants.CallOfCode))
|
||||
member = COC.get_member(int(memberid))
|
||||
if member is not None:
|
||||
return member.display_name
|
||||
return "[Persoon die de server misschien geleaved is | {}]".format(memberid)
|
||||
|
||||
mem = ctx.guild.get_member(int(memberid))
|
||||
return mem.display_name
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Utils(client))
|
|
@ -1,52 +0,0 @@
|
|||
from decorators import help
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
import requests
|
||||
|
||||
|
||||
class Words(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
# Don't allow any commands to work when locked
|
||||
def cog_check(self, ctx):
|
||||
return not self.client.locked
|
||||
|
||||
@commands.command(name="Adjective", aliases=["Adj", "Adjectives"], usage="[Woord]")
|
||||
@help.Category(category=Category.Words)
|
||||
async def adjective(self, ctx, word=None):
|
||||
await self.getData(ctx, word, "rel_jjb")
|
||||
|
||||
@commands.command(name="Synonym", aliases=["Syn", "Synonyms"], usage="[Woord]")
|
||||
@help.Category(category=Category.Words)
|
||||
async def synonym(self, ctx, word=None):
|
||||
await self.getData(ctx, word, "rel_syn")
|
||||
|
||||
@commands.command(name="Antonym", aliases=["Ant", "Antonyms", "Opp", "Opposite"], usage="[Woord]")
|
||||
@help.Category(category=Category.Words)
|
||||
async def antonym(self, ctx, word=None):
|
||||
await self.getData(ctx, word, "rel_ant")
|
||||
|
||||
@commands.command(name="Rhyme", aliases=["Rhymes"], usage="[Woord]")
|
||||
@help.Category(category=Category.Words)
|
||||
async def rhyme(self, ctx, word=None):
|
||||
await self.getData(ctx, word, "rel_rhy")
|
||||
|
||||
# Contacts the API & returns the response, as these commands all do the same anyways
|
||||
async def getData(self, ctx, word, relation):
|
||||
if not word:
|
||||
await ctx.send("Geef een woord op.")
|
||||
return
|
||||
|
||||
res = requests.get("https://api.datamuse.com/words?{}={}".format(relation, word)).json()
|
||||
|
||||
# Only show top 20 results
|
||||
res = res if len(res) <= 15 else res[:15]
|
||||
|
||||
# Pull the words out of the dicts
|
||||
res = [word["word"] for word in res]
|
||||
await ctx.send(", ".join(res) if len(res) > 0 else "Geen resultaten gevonden.")
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Words(client))
|
45
cogs/xp.py
45
cogs/xp.py
|
@ -1,45 +0,0 @@
|
|||
from data import constants
|
||||
from decorators import help
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from enums.help_categories import Category
|
||||
from functions import checks, xp
|
||||
from functions.database import stats
|
||||
|
||||
|
||||
class Xp(commands.Cog):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
@commands.group(name="Xp", aliases=["Level", "Mc", "Mess", "Messages"], case_insensitive=True, invoke_without_command=True)
|
||||
@commands.check(checks.allowedChannels)
|
||||
@help.Category(Category.Other)
|
||||
async def xp(self, ctx, user: discord.Member = None):
|
||||
if user is not None and str(ctx.author.id) != constants.myId:
|
||||
return await ctx.send("Je hebt geen toegang tot dit commando.")
|
||||
|
||||
target = user if user is not None else ctx.author
|
||||
|
||||
target_stats = stats.getOrAddUser(target.id)
|
||||
|
||||
message_count = stats.getTotalMessageCount()
|
||||
|
||||
perc = round(int(target_stats[11]) * 100/message_count, 2)
|
||||
|
||||
embed = discord.Embed(colour=discord.Colour.blue())
|
||||
embed.set_author(name=target.display_name, icon_url=target.avatar_url)
|
||||
embed.add_field(name="Aantal Berichten", value="{:,} ({}%)".format(int(target_stats[11]), perc))
|
||||
embed.add_field(name="Level", value=str(xp.calculate_level(target_stats[12])))
|
||||
embed.add_field(name="XP", value="{:,}".format(int(target_stats[12])))
|
||||
embed.set_footer(text="*Sinds Didier 2.0 Launch")
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@xp.command(name="Leaderboard", aliases=["Lb"], hidden=True)
|
||||
async def xpleaderboard(self, ctx, *args):
|
||||
if any(alias in ctx.message.content for alias in ["mc", "mess", "messages"]):
|
||||
return await self.client.get_cog("Leaderboards").callLeaderboard("Messages", ctx)
|
||||
await self.client.get_cog("Leaderboards").callLeaderboard("Xp", ctx)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Xp(client))
|
|
@ -1,67 +0,0 @@
|
|||
import re
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
# Gets the numerical value of a string representation like 1.6k
|
||||
def abbreviated(rep):
|
||||
if rep.lower() == "all":
|
||||
return rep
|
||||
|
||||
validMatch = r"^-?[0-9]+\.?[0-9]*[A-Za-z]*$"
|
||||
numericalValue = r"^-?[0-9]+\.?[0-9]*"
|
||||
indicator = r"[A-Za-z]*$"
|
||||
|
||||
valid = re.match(validMatch, rep)
|
||||
|
||||
# Invalid representation
|
||||
if not valid:
|
||||
return None
|
||||
|
||||
numerical = re.search(numericalValue, rep)
|
||||
|
||||
# Invalid number
|
||||
if float(numerical[0]) == 0.0:
|
||||
return None
|
||||
|
||||
indic = re.search(indicator, rep)
|
||||
if not indic[0]:
|
||||
try:
|
||||
return int(rep)
|
||||
except ValueError:
|
||||
# If no indicator was passed, it has to be a whole number
|
||||
return None
|
||||
|
||||
# Invalid indicator
|
||||
if indic[0] not in exponents() and not any(exp.lower() == indic[0].lower() for exp in exponents()):
|
||||
return None
|
||||
|
||||
if indic[0] in exponents():
|
||||
try:
|
||||
return int(float(numerical[0]) * int("1" + ("0" * (exponents().index(indic[0]) + 1) * 3)))
|
||||
except ValueError:
|
||||
# Can't be cast to int
|
||||
return None
|
||||
|
||||
for i, exponent in enumerate(exponents()):
|
||||
if exponent.lower() == indic[0].lower():
|
||||
try:
|
||||
return int(float(numerical[0]) * int("1" + ("0" * (i + 1) * 3)))
|
||||
except ValueError:
|
||||
# Can't be cast to int
|
||||
return None
|
||||
|
||||
|
||||
class Abbreviated(commands.Converter):
|
||||
async def convert(self, ctx, argument):
|
||||
if argument is None:
|
||||
return None
|
||||
converted = abbreviated(argument)
|
||||
if converted is None:
|
||||
await ctx.send("Dit is geen geldig getal.")
|
||||
return None
|
||||
|
||||
return converted
|
||||
|
||||
|
||||
def exponents():
|
||||
return ["K", "M", "B", "t", "q", "Q", "s", "S", "o", "n", "d", "U", "D", "T", "Qt", "Qd", "Sd", "St", "O", "N", "v"]
|
|
@ -1,121 +0,0 @@
|
|||
# Creating Commands
|
||||
|
||||
The following document shows you how to create new commands, which kwargs to add, and what they do.
|
||||
|
||||
Make sure to properly register the appropriate help decorator to each command. More info on help decorators in `readme.md`.
|
||||
|
||||
Do _NOT_ create commands outside of `Cogs`!
|
||||
|
||||
### Commands
|
||||
|
||||
Commands require a set list of kwargs in order to work correctly, and show up in the help page.
|
||||
|
||||
- Name: The name of the command, with only the first letter capitalised.
|
||||
- Aliases: Optional, a list of all aliases the command can have. Make sure to check if the alias already exists (if not, Discord will throw an error when starting the bot.)
|
||||
|
||||
Aliases should only have the first letter capitalised, and be placed in alphabetical order.
|
||||
- Usage: Optional, only required when the command takes arguments. Show how the command has to be used. Arguments should be placed between \[square brackets\], and optional arguments need an asterisk* after the brackets.
|
||||
- Hidden: Optional, should only be added when you _don't_ want the command to show up in the help page.
|
||||
- Ignore_Extra: Optional, should only be used when you don't want the command to be called when too many arguments are passed.
|
||||
|
||||
This defaults to `True`, so if you don't care about extra arguments then adding this is obsolete. Setting this to `False` makes sentences like _Didier Bank is a cool command_ not trigger the `Didier Bank` command.
|
||||
|
||||
##### Checks
|
||||
Further, checks can be added to commands to make sure the function only executes in certain situations. There are already a few [built-in checks](https://discordpy.readthedocs.io/en/latest/ext/commands/api.html?highlight=checks#checks) to do commonly-used things, but you can easily create your own functions to do this.
|
||||
|
||||
New checks should be added to `/functions/checks.py`. Checks are just a basic function that returns `True` or `False`, and takes `ctx` as a parameter.
|
||||
|
||||
The example below shows how to create a command that only takes 1 required argument, 1 optional argument, and does not show up in the help page. The `check` makes sure the command does not work in DM's.
|
||||
|
||||
```python
|
||||
def check(ctx):
|
||||
return ctx.guild is not None
|
||||
|
||||
@commands.command(name="Name", aliases=["N"], usage=["[Arg], [Arg2]*"], hidden=True, ignore_extra=True)
|
||||
@commands.check(check=check)
|
||||
async def name(ctx, arg, arg2=None):
|
||||
pass
|
||||
```
|
||||
|
||||
### Groups
|
||||
|
||||
It's possible that you might want to create subcommands for a certain command. At first glance, you might want to do something like this:
|
||||
|
||||
```python
|
||||
@commands.command()
|
||||
async def parent_command(ctx, arg):
|
||||
if arg == "sub1":
|
||||
await sub1()
|
||||
elif arg == "sub2":
|
||||
await sub2()
|
||||
...
|
||||
|
||||
async def sub1():
|
||||
pass
|
||||
|
||||
async def sub2():
|
||||
pass
|
||||
```
|
||||
|
||||
Looking at this you might think that there must be a better way to do this, and there is. The only situation where this type of code will be accepted, is when there are too many possible subcommands to the point where it's not really doable to create all of them (think help & faq categories).
|
||||
Discord.py has implemented [Command Groups](https://discordpy.readthedocs.io/en/latest/ext/commands/api.html?highlight=group#discord.ext.commands.Group) for this purpose. Groups allow you to register commands to other commands in order to create subcommands (or even subgroups!).
|
||||
|
||||
Groups _do_ have a few extra kwargs on top of the usual `Command Kwargs` mentioned above:
|
||||
|
||||
- case_insensitive: Indicates whether or not subcommands should be case-insensitive (think `lb dinks` and `lb DINKS`). You are *required* to set this parameter to `True` so Didier will always work case-insensitively.
|
||||
- invoke_without_command: Indicates whether or not the group command should only execute if no subcommand was found. In most cases, this will be `True`.
|
||||
|
||||
The example below shows how to create a group so that `Didier A B D` will execute `b("D")`, but `Didier A C` will execute `a("C")`.
|
||||
|
||||
```python
|
||||
@commands.group(name="A", usage="[Args]", case_insensitive=True, invoke_without_command=True)
|
||||
async def a(ctx, args):
|
||||
print(args)
|
||||
# Prints "C"
|
||||
|
||||
@a.command(name="B")
|
||||
async def b(ctx, args):
|
||||
print(args)
|
||||
# Prints "D"
|
||||
```
|
||||
|
||||
### Unpacking Groups
|
||||
|
||||
It's possible that you want to create a group to organise subcommands, but want the subcommands to be listed in the category instead of under the group (or when the Cog and Group have the same name). An example is Didier's `Random` module.
|
||||
|
||||
When creating a group without doing anything, the help page will look like this:
|
||||
|
||||
Categories:
|
||||
Currency
|
||||
...
|
||||
Random (category)
|
||||
...
|
||||
|
||||
Help Random (category):
|
||||
Random (group)
|
||||
|
||||
Help Random (group):
|
||||
Random Choice
|
||||
...
|
||||
|
||||
This requires an unnecessary step to get to the commands (as there's only 1 result), and it would be nicer to have all subcommands list in the category instead. Seeing as the Cog & Group both have the same name, it also feels weird to do this (and would be near impossible to code in a user-friendly way). When the Cog has the same name, you expect all the commands to be right there immediately.
|
||||
|
||||
The `Help Decorator` has an optional argument that can do this for you. When registering a group to a category, you can add `unpack=True` in order to accomplish this.
|
||||
|
||||
```python
|
||||
@commands.group(name="Random")
|
||||
@help.Category(Category.Random, unpack=True)
|
||||
async def random(self, ctx):
|
||||
pass
|
||||
```
|
||||
|
||||
This way, the help page will look like this:
|
||||
|
||||
Random (category):
|
||||
Random Choice
|
||||
...
|
||||
|
||||
### Testing & Messing Around
|
||||
In case you want to quickly test 1 line of code, or test a quick idea, you'd have to create an entire Cog just to use once. With the Cog Template this is not _that_ bad, but it's still a bit cumbersome.
|
||||
|
||||
For this purpose, there is a Cog called `testCog.py`, with 1 command (`Test()`). Slap your code in there, and use `Didier Test` in Discord. Just remember to never commit these changes (right click in the file manager -> `Git` -> `Rollback`).
|
|
@ -1,68 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
myId = "171671190631481345"
|
||||
didierId = "680510935164911730"
|
||||
coolerDidierId = "728361496874057812"
|
||||
botIDs = [
|
||||
"155149108183695360",
|
||||
"234395307759108106",
|
||||
"239631525350604801",
|
||||
"408785106942164992",
|
||||
"679679176466235399",
|
||||
"680510935164911730",
|
||||
"706148003949314069",
|
||||
"728361496874057812"
|
||||
]
|
||||
BugReports = "762668401960812554"
|
||||
CallOfCode = "626699611192688641"
|
||||
CoCGeneral = "626699611813314561"
|
||||
DeZandbak = "728361030404538488"
|
||||
ErrorLogs = "762668505455132722"
|
||||
FeatureRequests = "762668473313787964"
|
||||
|
||||
mods = {
|
||||
626699611192688641: [384457911377854467, 171671190631481345],
|
||||
728361030404538488: [171671190631481345],
|
||||
689200072268841106: [332948151226990614, 171671190631481345, 280025474485321729, 237239312385703951],
|
||||
710515840566427721: [171671190631481345, 140186024075722752, 296197359539585024]
|
||||
}
|
||||
|
||||
allowedChannels = {
|
||||
"bot-games": 634327239361691658,
|
||||
"bot-commands": 629034637955694602,
|
||||
"bot-testing": 679701786189103106,
|
||||
"shitposting": 676713433567199232,
|
||||
"didier-testings": 689202224437395573,
|
||||
"freegames": 705745297908826113,
|
||||
"bot-commandsCOCGI": 714170653124722738,
|
||||
"generalZandbak": 728361031008780422,
|
||||
"freegamesZandbak": 728545397127118898,
|
||||
"spelenZandbak": 769248992957038612
|
||||
}
|
||||
|
||||
creationDate = 1582243200
|
||||
|
||||
holidayAPIKey = "af4e1ebe-465d-4b93-a828-b95df18e6424"
|
||||
|
||||
prefixes = ["big d", "didier"]
|
||||
|
||||
faq_channels = {
|
||||
727876753523081216: "ad2",
|
||||
727876779481497600: "comnet",
|
||||
727876797458284584: "funcprog",
|
||||
727876819264733244: "statprob",
|
||||
727876836587208714: "sysprog",
|
||||
676713433567199232: "didier"
|
||||
}
|
||||
|
||||
|
||||
class Live(Enum):
|
||||
CallOfCode = "626699611192688641"
|
||||
DidierPosting = "728361031008780422"
|
||||
General = "626699611813314561"
|
||||
|
||||
|
||||
class Zandbak(Enum):
|
||||
CallOfCode = "728361030404538488"
|
||||
DidierPosting = "728361031008780422"
|
||||
General = "728361031008780422"
|
|
@ -1,31 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import menus
|
||||
|
||||
|
||||
# https://github.com/Rapptz/discord-ext-menus
|
||||
class Source(menus.ListPageSource):
|
||||
def __init__(self, data, name, colour=discord.Colour.blue()):
|
||||
super().__init__(data, per_page=10)
|
||||
self.name = name
|
||||
self.colour = colour
|
||||
|
||||
async def format_page(self, menu: menus.MenuPages, entries):
|
||||
offset = menu.current_page * self.per_page
|
||||
|
||||
description = ""
|
||||
for i, v in enumerate(entries, start=offset):
|
||||
# Check if the person's name has to be highlighted
|
||||
if v.startswith("**") and v.endswith("**"):
|
||||
description += "**"
|
||||
v = v[2:]
|
||||
description += "{}: {}\n".format(i + 1, v)
|
||||
embed = discord.Embed(colour=self.colour)
|
||||
embed.set_author(name=self.name)
|
||||
embed.description = description
|
||||
embed.set_footer(text="{}/{}".format(menu.current_page + 1, self.get_max_pages()))
|
||||
return embed
|
||||
|
||||
|
||||
class Pages(menus.MenuPages):
|
||||
def __init__(self, source, clear_reactions_after, timeout=30.0):
|
||||
super().__init__(source, timeout=timeout, delete_message_after=True, clear_reactions_after=clear_reactions_after)
|
|
@ -1,24 +0,0 @@
|
|||
from functions import les
|
||||
from functions.database import remind
|
||||
|
||||
|
||||
class Reminders:
|
||||
def __init__(self):
|
||||
rows = remind.getAllRows()
|
||||
|
||||
self._nightlyUsers = [int(user[0]) for user in rows if user[1]]
|
||||
self._nightlyMessages = ["Dagelijkse herinnering om Didier Nightly te doen.", "Vrees niet, Nightly-streak-liefhebber! 't Zenne kik, Didier, me ne reminder!"]
|
||||
self.nightly = {"users": self._nightlyUsers, "messages": self._nightlyMessages, "weekends": True, "disabled": False}
|
||||
|
||||
self._les = [int(user[0]) for user in rows if user[2]]
|
||||
self._lesMessages = ["Lessenrooster voor vandaag:"]
|
||||
self.les = {"users": self._les, "messages": self._lesMessages, "embed": self.lesEmbed(), "weekends": False, "disabled": True}
|
||||
|
||||
self.categories = [self.nightly, self.les]
|
||||
|
||||
def lesEmbed(self):
|
||||
day, dayDatetime, semester, year = les.parseArgs([])[1:]
|
||||
|
||||
schedule = les.getSchedule(semester, year)
|
||||
|
||||
return les.createEmbed(day, dayDatetime, semester, year, schedule)
|
|
@ -1,28 +0,0 @@
|
|||
import discord
|
||||
from discord.ext import menus
|
||||
|
||||
|
||||
# https://github.com/Rapptz/discord-ext-menus
|
||||
class Source(menus.ListPageSource):
|
||||
def __init__(self, data):
|
||||
super().__init__(data, per_page=10)
|
||||
self.name = "Didier Store"
|
||||
self.colour = discord.Colour.blue()
|
||||
|
||||
async def format_page(self, menu: menus.MenuPages, entries):
|
||||
offset = menu.current_page * self.per_page
|
||||
|
||||
embed = discord.Embed(colour=self.colour)
|
||||
embed.set_author(name=self.name)
|
||||
embed.description = "Heb je een idee voor een item? DM DJ STIJN met je idee!"
|
||||
embed.set_footer(text="{}/{}".format(menu.current_page + 1, self.get_max_pages()))
|
||||
|
||||
for i, v in enumerate(entries, start=offset):
|
||||
embed.add_field(name="#{} - {}".format(v[0], v[1]), value="{:,} Didier Dinks".format(v[2]))
|
||||
|
||||
return embed
|
||||
|
||||
|
||||
class Pages(menus.MenuPages):
|
||||
def __init__(self, source, clear_reactions_after, timeout=30.0):
|
||||
super().__init__(source, timeout=timeout, delete_message_after=True, clear_reactions_after=clear_reactions_after)
|
|
@ -0,0 +1,45 @@
|
|||
import datetime
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import extract, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from database.crud import users
|
||||
from database.schemas import Birthday, User
|
||||
|
||||
__all__ = ["add_birthday", "get_birthday_for_user", "get_birthdays_on_day"]
|
||||
|
||||
|
||||
async def add_birthday(session: AsyncSession, user_id: int, birthday: date):
|
||||
"""Add a user's birthday into the database
|
||||
|
||||
If already present, overwrites the existing one
|
||||
"""
|
||||
user = await users.get_or_add_user(session, user_id, options=[selectinload(User.birthday)])
|
||||
|
||||
if user.birthday is not None:
|
||||
bd = user.birthday
|
||||
await session.refresh(bd)
|
||||
bd.birthday = birthday
|
||||
else:
|
||||
bd = Birthday(user_id=user_id, birthday=birthday)
|
||||
|
||||
session.add(bd)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_birthday_for_user(session: AsyncSession, user_id: int) -> Optional[Birthday]:
|
||||
"""Find a user's birthday"""
|
||||
statement = select(Birthday).where(Birthday.user_id == user_id)
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_birthdays_on_day(session: AsyncSession, day: datetime.date) -> list[Birthday]:
|
||||
"""Get all birthdays that happen on a given day"""
|
||||
days = extract("day", Birthday.birthday)
|
||||
months = extract("month", Birthday.birthday)
|
||||
|
||||
statement = select(Birthday).where((days == day.day) & (months == day.month))
|
||||
return list((await session.execute(statement)).scalars().all())
|
|
@ -0,0 +1,73 @@
|
|||
from typing import Optional
|
||||
|
||||
import sqlalchemy.exc
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.crud.users import get_or_add_user
|
||||
from database.exceptions import (
|
||||
DuplicateInsertException,
|
||||
Forbidden,
|
||||
ForbiddenNameException,
|
||||
NoResultFoundException,
|
||||
)
|
||||
from database.schemas import Bookmark
|
||||
|
||||
__all__ = ["create_bookmark", "get_bookmarks", "get_bookmark_by_name"]
|
||||
|
||||
|
||||
async def create_bookmark(session: AsyncSession, user_id: int, label: str, jump_url: str) -> Bookmark:
|
||||
"""Create a new bookmark to a message"""
|
||||
# Don't allow bookmarks with names of subcommands
|
||||
if label.lower() in ["create", "delete", "ls", "list", "Rm", "search"]:
|
||||
raise ForbiddenNameException
|
||||
|
||||
await get_or_add_user(session, user_id)
|
||||
|
||||
try:
|
||||
bookmark = Bookmark(label=label, jump_url=jump_url, user_id=user_id)
|
||||
session.add(bookmark)
|
||||
await session.commit()
|
||||
await session.refresh(bookmark)
|
||||
except sqlalchemy.exc.IntegrityError as e:
|
||||
raise DuplicateInsertException from e
|
||||
|
||||
return bookmark
|
||||
|
||||
|
||||
async def delete_bookmark_by_id(session: AsyncSession, user_id: int, bookmark_id: int):
|
||||
"""Find a bookmark by its id & delete it
|
||||
|
||||
This fails if you don't own this bookmark
|
||||
"""
|
||||
select_statement = select(Bookmark).where(Bookmark.bookmark_id == bookmark_id)
|
||||
bookmark = (await session.execute(select_statement)).scalar_one_or_none()
|
||||
|
||||
# No bookmark with this id
|
||||
if bookmark is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
# You don't own this bookmark
|
||||
if bookmark.user_id != user_id:
|
||||
raise Forbidden
|
||||
|
||||
# Delete it
|
||||
delete_statement = delete(Bookmark).where(Bookmark.bookmark_id == bookmark_id)
|
||||
await session.execute(delete_statement)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_bookmarks(session: AsyncSession, user_id: int, *, query: Optional[str] = None) -> list[Bookmark]:
|
||||
"""Get all a user's bookmarks"""
|
||||
statement = select(Bookmark).where(Bookmark.user_id == user_id)
|
||||
|
||||
if query is not None:
|
||||
statement = statement.where(Bookmark.label.ilike(f"%{query.lower()}%"))
|
||||
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def get_bookmark_by_name(session: AsyncSession, user_id: int, query: str) -> Optional[Bookmark]:
|
||||
"""Try to find a bookmark by its name"""
|
||||
statement = select(Bookmark).where(Bookmark.user_id == user_id).where(func.lower(Bookmark.label) == query.lower())
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
|
@ -0,0 +1,41 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.crud.users import get_or_add_user
|
||||
from database.schemas import CommandStats
|
||||
|
||||
__all__ = ["register_command_invocation"]
|
||||
|
||||
|
||||
CommandT = Union[commands.Command, app_commands.Command, app_commands.ContextMenu]
|
||||
|
||||
|
||||
async def register_command_invocation(
|
||||
session: AsyncSession, ctx: commands.Context, command: Optional[CommandT], timestamp: datetime
|
||||
):
|
||||
"""Create an entry for a command invocation"""
|
||||
if command is None:
|
||||
return
|
||||
|
||||
await get_or_add_user(session, ctx.author.id)
|
||||
|
||||
# Check the type of invocation
|
||||
context_menu = isinstance(command, app_commands.ContextMenu)
|
||||
|
||||
# (This is a bit uglier but it accounts for hybrid commands)
|
||||
slash = isinstance(command, app_commands.Command) or (ctx.interaction is not None and not context_menu)
|
||||
|
||||
stats = CommandStats(
|
||||
command=command.qualified_name.lower(),
|
||||
timestamp=timestamp,
|
||||
user_id=ctx.author.id,
|
||||
slash=slash,
|
||||
context_menu=context_menu,
|
||||
)
|
||||
|
||||
session.add(stats)
|
||||
await session.commit()
|
|
@ -0,0 +1,172 @@
|
|||
from datetime import date
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.crud import users
|
||||
from database.exceptions import currency as exceptions
|
||||
from database.schemas import Bank, NightlyData
|
||||
from database.utils.math.currency import (
|
||||
capacity_upgrade_price,
|
||||
interest_upgrade_price,
|
||||
rob_upgrade_price,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"add_dinks",
|
||||
"claim_nightly",
|
||||
"gamble_dinks",
|
||||
"get_bank",
|
||||
"get_nightly_data",
|
||||
"invest",
|
||||
"upgrade_capacity",
|
||||
"upgrade_interest",
|
||||
"upgrade_rob",
|
||||
"withdraw",
|
||||
"NIGHTLY_AMOUNT",
|
||||
]
|
||||
|
||||
NIGHTLY_AMOUNT = 420
|
||||
|
||||
|
||||
async def get_bank(session: AsyncSession, user_id: int) -> Bank:
|
||||
"""Get a user's bank info"""
|
||||
user = await users.get_or_add_user(session, user_id)
|
||||
return user.bank
|
||||
|
||||
|
||||
async def get_nightly_data(session: AsyncSession, user_id: int) -> NightlyData:
|
||||
"""Get a user's nightly info"""
|
||||
user = await users.get_or_add_user(session, user_id)
|
||||
return user.nightly_data
|
||||
|
||||
|
||||
async def invest(session: AsyncSession, user_id: int, amount: Union[str, int]) -> int:
|
||||
"""Invest some of your Dinks"""
|
||||
bank = await get_bank(session, user_id)
|
||||
if amount == "all":
|
||||
amount = bank.dinks
|
||||
|
||||
# Don't allow investing more dinks than you own
|
||||
amount = min(bank.dinks, int(amount))
|
||||
|
||||
bank.dinks -= amount
|
||||
bank.invested += amount
|
||||
|
||||
session.add(bank)
|
||||
await session.commit()
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
async def withdraw(session: AsyncSession, user_id: int, amount: Union[str, int]) -> int:
|
||||
"""Withdraw your invested Dinks"""
|
||||
bank = await get_bank(session, user_id)
|
||||
if amount == "all":
|
||||
amount = bank.invested
|
||||
|
||||
# Don't allow withdrawing more dinks than you own
|
||||
amount = min(bank.invested, int(amount))
|
||||
|
||||
bank.dinks += amount
|
||||
bank.invested -= amount
|
||||
|
||||
await session.commit()
|
||||
return amount
|
||||
|
||||
|
||||
async def add_dinks(session: AsyncSession, user_id: int, amount: int):
|
||||
"""Increase the Dinks counter for a user"""
|
||||
bank = await get_bank(session, user_id)
|
||||
bank.dinks += amount
|
||||
session.add(bank)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def claim_nightly(session: AsyncSession, user_id: int):
|
||||
"""Claim daily Dinks"""
|
||||
nightly_data = await get_nightly_data(session, user_id)
|
||||
|
||||
now = date.today()
|
||||
|
||||
if nightly_data.last_nightly is not None and nightly_data.last_nightly == now:
|
||||
raise exceptions.DoubleNightly
|
||||
|
||||
bank = await get_bank(session, user_id)
|
||||
bank.dinks += NIGHTLY_AMOUNT
|
||||
nightly_data.last_nightly = now
|
||||
|
||||
session.add(bank)
|
||||
session.add(nightly_data)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def upgrade_capacity(session: AsyncSession, user_id: int) -> int:
|
||||
"""Upgrade capacity level"""
|
||||
bank = await get_bank(session, user_id)
|
||||
upgrade_price = capacity_upgrade_price(bank.capacity_level)
|
||||
|
||||
# Can't afford this upgrade
|
||||
if upgrade_price > bank.dinks:
|
||||
raise exceptions.NotEnoughDinks
|
||||
|
||||
bank.dinks -= upgrade_price
|
||||
bank.capacity_level += 1
|
||||
|
||||
session.add(bank)
|
||||
await session.commit()
|
||||
|
||||
return bank.capacity_level
|
||||
|
||||
|
||||
async def upgrade_interest(session: AsyncSession, user_id: int) -> int:
|
||||
"""Upgrade interest level"""
|
||||
bank = await get_bank(session, user_id)
|
||||
upgrade_price = interest_upgrade_price(bank.interest_level)
|
||||
|
||||
# Can't afford this upgrade
|
||||
if upgrade_price > bank.dinks:
|
||||
raise exceptions.NotEnoughDinks
|
||||
|
||||
bank.dinks -= upgrade_price
|
||||
bank.interest_level += 1
|
||||
|
||||
session.add(bank)
|
||||
await session.commit()
|
||||
|
||||
return bank.interest_level
|
||||
|
||||
|
||||
async def upgrade_rob(session: AsyncSession, user_id: int) -> int:
|
||||
"""Upgrade rob level"""
|
||||
bank = await get_bank(session, user_id)
|
||||
upgrade_price = rob_upgrade_price(bank.rob_level)
|
||||
|
||||
# Can't afford this upgrade
|
||||
if upgrade_price > bank.dinks:
|
||||
raise exceptions.NotEnoughDinks
|
||||
|
||||
bank.dinks -= upgrade_price
|
||||
bank.rob_level += 1
|
||||
|
||||
session.add(bank)
|
||||
await session.commit()
|
||||
|
||||
return bank.rob_level
|
||||
|
||||
|
||||
async def gamble_dinks(
|
||||
session: AsyncSession, user_id: int, amount: Union[str, int], payout_factor: int, won: bool
|
||||
) -> int:
|
||||
"""Gamble some of your Dinks"""
|
||||
bank = await get_bank(session, user_id)
|
||||
if amount == "all":
|
||||
amount = bank.dinks
|
||||
|
||||
amount = min(int(amount), bank.dinks)
|
||||
|
||||
sign = 1 if won else -1
|
||||
factor = (payout_factor - 1) if won else 1
|
||||
await add_dinks(session, user_id, sign * amount * factor)
|
||||
|
||||
return amount * factor
|
|
@ -0,0 +1,105 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.exceptions.constraints import DuplicateInsertException
|
||||
from database.exceptions.not_found import NoResultFoundException
|
||||
from database.schemas import CustomCommand, CustomCommandAlias
|
||||
|
||||
__all__ = [
|
||||
"clean_name",
|
||||
"create_alias",
|
||||
"create_command",
|
||||
"edit_command",
|
||||
"get_all_commands",
|
||||
"get_command",
|
||||
"get_command_by_alias",
|
||||
"get_command_by_name",
|
||||
]
|
||||
|
||||
|
||||
def clean_name(name: str) -> str:
|
||||
"""Convert a name to lowercase & remove spaces to allow easier matching"""
|
||||
return name.lower().replace(" ", "")
|
||||
|
||||
|
||||
async def create_command(session: AsyncSession, name: str, response: str) -> CustomCommand:
|
||||
"""Create a new custom command"""
|
||||
# Check if command or alias already exists
|
||||
command = await get_command(session, name)
|
||||
if command is not None:
|
||||
raise DuplicateInsertException
|
||||
|
||||
command = CustomCommand(name=name, indexed_name=clean_name(name), response=response)
|
||||
session.add(command)
|
||||
await session.commit()
|
||||
|
||||
return command
|
||||
|
||||
|
||||
async def create_alias(session: AsyncSession, command: str, alias: str) -> CustomCommandAlias:
|
||||
"""Create an alias for a command"""
|
||||
# Check if the command exists
|
||||
command_instance = await get_command(session, command)
|
||||
if command_instance is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
# Check if the alias exists (either as an alias or as a name)
|
||||
if await get_command(session, alias) is not None:
|
||||
raise DuplicateInsertException
|
||||
|
||||
alias_instance = CustomCommandAlias(alias=alias, indexed_alias=clean_name(alias), command=command_instance)
|
||||
session.add(alias_instance)
|
||||
await session.commit()
|
||||
|
||||
return alias_instance
|
||||
|
||||
|
||||
async def get_all_commands(session: AsyncSession) -> list[CustomCommand]:
|
||||
"""Get a list of all commands"""
|
||||
statement = select(CustomCommand)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def get_command(session: AsyncSession, message: str) -> Optional[CustomCommand]:
|
||||
"""Try to get a command out of a message"""
|
||||
# Search lowercase & without spaces
|
||||
message = clean_name(message)
|
||||
return (await get_command_by_name(session, message)) or (await get_command_by_alias(session, message))
|
||||
|
||||
|
||||
async def get_command_by_name(session: AsyncSession, message: str) -> Optional[CustomCommand]:
|
||||
"""Try to get a command by its name"""
|
||||
statement = select(CustomCommand).where(CustomCommand.indexed_name == message)
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_command_by_alias(session: AsyncSession, message: str) -> Optional[CustomCommand]:
|
||||
"""Try to get a command by its alias"""
|
||||
statement = select(CustomCommandAlias).where(CustomCommandAlias.indexed_alias == message)
|
||||
alias = (await session.execute(statement)).scalar_one_or_none()
|
||||
if alias is None:
|
||||
return None
|
||||
|
||||
return alias.command
|
||||
|
||||
|
||||
async def edit_command(
|
||||
session: AsyncSession, original_name: str, new_name: Optional[str] = None, new_response: Optional[str] = None
|
||||
) -> CustomCommand:
|
||||
"""Edit an existing command"""
|
||||
# Check if the command exists
|
||||
command = await get_command(session, original_name)
|
||||
if command is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
if new_name is not None:
|
||||
command.name = new_name
|
||||
if new_response is not None:
|
||||
command.response = new_response
|
||||
|
||||
session.add(command)
|
||||
await session.commit()
|
||||
|
||||
return command
|
|
@ -0,0 +1,26 @@
|
|||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.exceptions.not_found import NoResultFoundException
|
||||
from database.schemas import DadJoke
|
||||
|
||||
__all__ = ["add_dad_joke", "get_random_dad_joke"]
|
||||
|
||||
|
||||
async def add_dad_joke(session: AsyncSession, joke: str) -> DadJoke:
|
||||
"""Add a new dad joke to the database"""
|
||||
dad_joke = DadJoke(joke=joke)
|
||||
session.add(dad_joke)
|
||||
await session.commit()
|
||||
|
||||
return dad_joke
|
||||
|
||||
|
||||
async def get_random_dad_joke(session: AsyncSession) -> DadJoke: # pragma: no cover # randomness is untestable
|
||||
"""Return a random database entry"""
|
||||
statement = select(DadJoke).order_by(func.random())
|
||||
row = (await session.execute(statement)).first()
|
||||
if row is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
return row[0]
|
|
@ -0,0 +1,41 @@
|
|||
import datetime
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil.parser import parse
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from database.schemas import Deadline, UforaCourse
|
||||
|
||||
__all__ = ["add_deadline", "get_deadlines"]
|
||||
|
||||
|
||||
async def add_deadline(session: AsyncSession, course_id: int, name: str, date_str: str):
|
||||
"""Add a new deadline"""
|
||||
date_dt = parse(date_str, dayfirst=True).replace(tzinfo=ZoneInfo("Europe/Brussels"))
|
||||
|
||||
# If we only have a day, assume it's the end of the day
|
||||
if date_dt.hour == date_dt.minute == date_dt.second == 0:
|
||||
date_dt.replace(hour=23, minute=59, second=59)
|
||||
|
||||
deadline = Deadline(course_id=course_id, name=name, deadline=date_dt)
|
||||
session.add(deadline)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_deadlines(
|
||||
session: AsyncSession, *, after: Optional[datetime.date] = None, course: Optional[UforaCourse] = None
|
||||
) -> list[Deadline]:
|
||||
"""Get a list of all upcoming deadlines"""
|
||||
statement = select(Deadline)
|
||||
|
||||
if after is not None:
|
||||
statement = statement.where(Deadline.deadline > after)
|
||||
|
||||
if course is not None:
|
||||
statement = statement.where(Deadline.course_id == course.course_id)
|
||||
|
||||
statement = statement.options(selectinload(Deadline.course))
|
||||
return list((await session.execute(statement)).scalars().all())
|
|
@ -0,0 +1,12 @@
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import EasterEgg
|
||||
|
||||
__all__ = ["get_all_easter_eggs"]
|
||||
|
||||
|
||||
async def get_all_easter_eggs(session: AsyncSession) -> list[EasterEgg]:
|
||||
"""Return a list of all easter eggs"""
|
||||
statement = select(EasterEgg)
|
||||
return list((await session.execute(statement)).scalars().all())
|
|
@ -0,0 +1,50 @@
|
|||
import datetime
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil.parser import parse
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import Event
|
||||
|
||||
__all__ = ["add_event", "delete_event_by_id", "get_event_by_id", "get_events", "get_next_event"]
|
||||
|
||||
|
||||
async def add_event(
|
||||
session: AsyncSession, *, name: str, description: Optional[str], date_str: str, channel_id: int
|
||||
) -> Event:
|
||||
"""Create a new event"""
|
||||
date_dt = parse(date_str, dayfirst=True).replace(tzinfo=ZoneInfo("Europe/Brussels"))
|
||||
|
||||
event = Event(name=name, description=description, timestamp=date_dt, notification_channel=channel_id)
|
||||
session.add(event)
|
||||
await session.commit()
|
||||
await session.refresh(event)
|
||||
|
||||
return event
|
||||
|
||||
|
||||
async def delete_event_by_id(session: AsyncSession, event_id: int):
|
||||
"""Delete an event by its id"""
|
||||
statement = delete(Event).where(Event.event_id == event_id)
|
||||
await session.execute(statement)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_event_by_id(session: AsyncSession, event_id: int) -> Optional[Event]:
|
||||
"""Get an event by its id"""
|
||||
statement = select(Event).where(Event.event_id == event_id)
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_events(session: AsyncSession, *, now: datetime.datetime) -> list[Event]:
|
||||
"""Get a list of all upcoming events"""
|
||||
statement = select(Event).where(Event.timestamp > now)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def get_next_event(session: AsyncSession, *, now: datetime.datetime) -> Optional[Event]:
|
||||
"""Get the first upcoming event"""
|
||||
statement = select(Event).where(Event.timestamp > now).order_by(Event.timestamp)
|
||||
return (await session.execute(statement)).scalars().first()
|
|
@ -0,0 +1,20 @@
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import FreeGame
|
||||
|
||||
__all__ = ["add_free_games", "filter_present_games"]
|
||||
|
||||
|
||||
async def add_free_games(session: AsyncSession, game_ids: list[int]):
|
||||
"""Bulk-add a list of IDs into the database"""
|
||||
games = [FreeGame(free_game_id=game_id) for game_id in game_ids]
|
||||
session.add_all(games)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def filter_present_games(session: AsyncSession, game_ids: list[int]) -> list[int]:
|
||||
"""Filter a list of game IDs down to the ones that aren't in the database yet"""
|
||||
statement = select(FreeGame.free_game_id).where(FreeGame.free_game_id.in_(game_ids))
|
||||
matches: list[int] = list((await session.execute(statement)).scalars().all())
|
||||
return list(set(game_ids).difference(matches))
|
|
@ -0,0 +1,51 @@
|
|||
from typing import Optional
|
||||
|
||||
import sqlalchemy.exc
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.exceptions import (
|
||||
DuplicateInsertException,
|
||||
Forbidden,
|
||||
NoResultFoundException,
|
||||
)
|
||||
from database.schemas import GitHubLink
|
||||
|
||||
__all__ = ["add_github_link", "delete_github_link_by_id", "get_github_links"]
|
||||
|
||||
|
||||
async def add_github_link(session: AsyncSession, user_id: int, url: str) -> GitHubLink:
|
||||
"""Add a new GitHub link into the database"""
|
||||
try:
|
||||
gh_link = GitHubLink(user_id=user_id, url=url)
|
||||
session.add(gh_link)
|
||||
await session.commit()
|
||||
await session.refresh(gh_link)
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
raise DuplicateInsertException
|
||||
|
||||
return gh_link
|
||||
|
||||
|
||||
async def delete_github_link_by_id(session: AsyncSession, user_id: int, link_id: int):
|
||||
"""Remove an existing link from the database
|
||||
|
||||
You can only remove links owned by you
|
||||
"""
|
||||
select_statement = select(GitHubLink).where(GitHubLink.github_link_id == link_id)
|
||||
gh_link: Optional[GitHubLink] = (await session.execute(select_statement)).scalar_one_or_none()
|
||||
if gh_link is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
if gh_link.user_id != user_id:
|
||||
raise Forbidden
|
||||
|
||||
delete_statement = delete(GitHubLink).where(GitHubLink.github_link_id == gh_link.github_link_id)
|
||||
await session.execute(delete_statement)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_github_links(session: AsyncSession, user_id: int) -> list[GitHubLink]:
|
||||
"""Get a user's GitHub links"""
|
||||
statement = select(GitHubLink).where(GitHubLink.user_id == user_id)
|
||||
return list((await session.execute(statement)).scalars().all())
|
|
@ -0,0 +1,45 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.exceptions import NoResultFoundException
|
||||
from database.schemas import Link
|
||||
|
||||
__all__ = ["add_link", "edit_link", "get_all_links", "get_link_by_name"]
|
||||
|
||||
|
||||
async def get_all_links(session: AsyncSession) -> list[Link]:
|
||||
"""Get a list of all links"""
|
||||
statement = select(Link)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def add_link(session: AsyncSession, name: str, url: str) -> Link:
|
||||
"""Add a new link into the database"""
|
||||
if name.islower():
|
||||
name = name.capitalize()
|
||||
|
||||
instance = Link(name=name, url=url)
|
||||
session.add(instance)
|
||||
await session.commit()
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
async def get_link_by_name(session: AsyncSession, name: str) -> Optional[Link]:
|
||||
"""Get a link by its name"""
|
||||
statement = select(Link).where(func.lower(Link.name) == name.lower())
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def edit_link(session: AsyncSession, name: str, new_url: str):
|
||||
"""Edit an existing link"""
|
||||
link: Optional[Link] = await get_link_by_name(session, name)
|
||||
|
||||
if link is None:
|
||||
raise NoResultFoundException
|
||||
|
||||
link.url = new_url
|
||||
session.add(link)
|
||||
await session.commit()
|
|
@ -0,0 +1,35 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import MemeTemplate
|
||||
|
||||
__all__ = ["add_meme", "get_all_memes", "get_meme_by_name"]
|
||||
|
||||
|
||||
async def add_meme(session: AsyncSession, name: str, template_id: int, field_count: int) -> Optional[MemeTemplate]:
|
||||
"""Add a new meme into the database"""
|
||||
try:
|
||||
meme = MemeTemplate(name=name, template_id=template_id, field_count=field_count)
|
||||
session.add(meme)
|
||||
await session.commit()
|
||||
return meme
|
||||
except IntegrityError:
|
||||
return None
|
||||
|
||||
|
||||
async def get_all_memes(session: AsyncSession) -> list[MemeTemplate]:
|
||||
"""Get a list of all memes"""
|
||||
statement = select(MemeTemplate)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def get_meme_by_name(session: AsyncSession, query: str) -> Optional[MemeTemplate]:
|
||||
"""Try to find a meme by its name
|
||||
|
||||
Returns the first match found by PSQL
|
||||
"""
|
||||
statement = select(MemeTemplate).where(MemeTemplate.name.ilike(f"%{query.lower()}%"))
|
||||
return (await session.execute(statement)).scalar()
|
|
@ -0,0 +1,42 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.crud.users import get_or_add_user
|
||||
from database.enums import ReminderCategory
|
||||
from database.schemas import Reminder
|
||||
|
||||
__all__ = ["get_all_reminders_for_category", "toggle_reminder"]
|
||||
|
||||
|
||||
async def get_all_reminders_for_category(session: AsyncSession, category: ReminderCategory) -> list[Reminder]:
|
||||
"""Get a list of all Reminders for a given category"""
|
||||
statement = select(Reminder).where(Reminder.category == category)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def toggle_reminder(session: AsyncSession, user_id: int, category: ReminderCategory) -> bool:
|
||||
"""Switch a category on/off
|
||||
|
||||
Returns the new value for the category
|
||||
"""
|
||||
await get_or_add_user(session, user_id)
|
||||
|
||||
select_statement = select(Reminder).where(Reminder.user_id == user_id).where(Reminder.category == category)
|
||||
reminder: Optional[Reminder] = (await session.execute(select_statement)).scalar_one_or_none()
|
||||
|
||||
# No reminder set yet
|
||||
if reminder is None:
|
||||
reminder = Reminder(user_id=user_id, category=category)
|
||||
session.add(reminder)
|
||||
await session.commit()
|
||||
|
||||
return True
|
||||
|
||||
# Reminder found -> delete it
|
||||
delete_statement = delete(Reminder).where(Reminder.reminder_id == reminder.reminder_id)
|
||||
await session.execute(delete_statement)
|
||||
await session.commit()
|
||||
|
||||
return False
|
|
@ -0,0 +1,32 @@
|
|||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.enums import TaskType
|
||||
from database.schemas import Task
|
||||
from database.utils.datetime import LOCAL_TIMEZONE
|
||||
|
||||
__all__ = ["get_task_by_enum", "set_last_task_execution_time"]
|
||||
|
||||
|
||||
async def get_task_by_enum(session: AsyncSession, task: TaskType) -> Optional[Task]:
|
||||
"""Get a task by its enum value, if it exists
|
||||
|
||||
Returns None if the task does not exist
|
||||
"""
|
||||
statement = select(Task).where(Task.task == task)
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def set_last_task_execution_time(session: AsyncSession, task: TaskType):
|
||||
"""Set the last time a specific task was executed"""
|
||||
_task = await get_task_by_enum(session, task)
|
||||
|
||||
if _task is None:
|
||||
_task = Task(task=task)
|
||||
|
||||
_task.previous_run = datetime.datetime.now(tz=LOCAL_TIMEZONE)
|
||||
session.add(_task)
|
||||
await session.commit()
|
|
@ -0,0 +1,38 @@
|
|||
import datetime
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import UforaAnnouncement, UforaCourse
|
||||
|
||||
__all__ = ["create_new_announcement", "get_courses_with_announcements", "remove_old_announcements"]
|
||||
|
||||
|
||||
async def get_courses_with_announcements(session: AsyncSession) -> list[UforaCourse]:
|
||||
"""Get all courses where announcements are enabled"""
|
||||
statement = select(UforaCourse).where(UforaCourse.log_announcements)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def create_new_announcement(
|
||||
session: AsyncSession, announcement_id: int, course: UforaCourse, publication_date: datetime.datetime
|
||||
) -> UforaAnnouncement:
|
||||
"""Add a new announcement to the database"""
|
||||
new_announcement = UforaAnnouncement(
|
||||
announcement_id=announcement_id, course=course, publication_date=publication_date
|
||||
)
|
||||
session.add(new_announcement)
|
||||
await session.commit()
|
||||
return new_announcement
|
||||
|
||||
|
||||
async def remove_old_announcements(session: AsyncSession):
|
||||
"""Delete all announcements that are > 8 days old
|
||||
|
||||
The RSS feed only goes back 7 days, so all of these old announcements never have to
|
||||
be checked again when checking if an announcement is fresh or not.
|
||||
"""
|
||||
limit = datetime.datetime.utcnow() - datetime.timedelta(days=8)
|
||||
statement = delete(UforaAnnouncement).where(UforaAnnouncement.publication_date < limit)
|
||||
await session.execute(statement)
|
||||
await session.commit()
|
|
@ -0,0 +1,38 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import UforaCourse, UforaCourseAlias
|
||||
|
||||
__all__ = ["get_all_courses", "get_course_by_code", "get_course_by_name"]
|
||||
|
||||
|
||||
async def get_all_courses(session: AsyncSession) -> list[UforaCourse]:
|
||||
"""Get a list of all courses in the database"""
|
||||
statement = select(UforaCourse)
|
||||
return list((await session.execute(statement)).scalars().all())
|
||||
|
||||
|
||||
async def get_course_by_code(session: AsyncSession, code: str) -> Optional[UforaCourse]:
|
||||
"""Try to find a course by its code"""
|
||||
statement = select(UforaCourse).where(UforaCourse.code == code)
|
||||
return (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_course_by_name(session: AsyncSession, query: str) -> Optional[UforaCourse]:
|
||||
"""Try to find a course by its name
|
||||
|
||||
This checks for regular name first, and then aliases
|
||||
"""
|
||||
# Search case-insensitively
|
||||
query = query.lower()
|
||||
|
||||
course_statement = select(UforaCourse).where(UforaCourse.name.ilike(f"%{query}%"))
|
||||
course_result = (await session.execute(course_statement)).scalars().first()
|
||||
if course_result:
|
||||
return course_result
|
||||
|
||||
alias_statement = select(UforaCourseAlias).where(UforaCourseAlias.alias.ilike(f"%{query}%"))
|
||||
alias_result = (await session.execute(alias_statement)).scalars().first()
|
||||
return alias_result.course if alias_result else None
|
|
@ -0,0 +1,47 @@
|
|||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.schemas import Bank, NightlyData, User
|
||||
|
||||
__all__ = [
|
||||
"get_or_add_user",
|
||||
]
|
||||
|
||||
|
||||
async def get_or_add_user(session: AsyncSession, user_id: int, *, options: Optional[list] = None) -> User:
|
||||
"""Get a user's profile
|
||||
|
||||
If it doesn't exist yet, create it (along with all linked datastructures)
|
||||
"""
|
||||
if options is None:
|
||||
options = []
|
||||
|
||||
statement = select(User).where(User.user_id == user_id).options(*options)
|
||||
|
||||
user: Optional[User] = (await session.execute(statement)).scalar_one_or_none()
|
||||
|
||||
# User exists
|
||||
if user is not None:
|
||||
return user
|
||||
|
||||
# Create new user
|
||||
user = User(user_id=user_id)
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
|
||||
# Add bank & nightly info
|
||||
bank = Bank(user_id=user_id)
|
||||
nightly_data = NightlyData(user_id=user_id)
|
||||
user.bank = bank
|
||||
user.nightly_data = nightly_data
|
||||
|
||||
session.add(bank)
|
||||
session.add(nightly_data)
|
||||
session.add(user)
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
|
||||
return user
|
|
@ -0,0 +1,24 @@
|
|||
from urllib.parse import quote_plus
|
||||
|
||||
from sqlalchemy.engine import URL
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
import settings
|
||||
|
||||
encoded_postgres_password = quote_plus(settings.POSTGRES_PASS)
|
||||
|
||||
# PostgreSQL engine
|
||||
postgres_engine = create_async_engine(
|
||||
URL.create(
|
||||
drivername="postgresql+asyncpg",
|
||||
username=settings.POSTGRES_USER,
|
||||
password=encoded_postgres_password,
|
||||
host=settings.POSTGRES_HOST,
|
||||
port=settings.POSTGRES_PORT,
|
||||
database=settings.POSTGRES_DB,
|
||||
),
|
||||
pool_pre_ping=True,
|
||||
future=True,
|
||||
)
|
||||
|
||||
DBSession = async_sessionmaker(autocommit=False, autoflush=False, bind=postgres_engine, expire_on_commit=False)
|
|
@ -0,0 +1,17 @@
|
|||
import enum
|
||||
|
||||
__all__ = ["ReminderCategory", "TaskType"]
|
||||
|
||||
|
||||
class ReminderCategory(enum.IntEnum):
|
||||
"""Enum for reminder categories"""
|
||||
|
||||
LES = enum.auto()
|
||||
|
||||
|
||||
class TaskType(enum.IntEnum):
|
||||
"""Enum for the different types of tasks"""
|
||||
|
||||
BIRTHDAYS = enum.auto()
|
||||
SCHEDULES = enum.auto()
|
||||
UFORA_ANNOUNCEMENTS = enum.auto()
|
|
@ -0,0 +1,13 @@
|
|||
from .constraints import DuplicateInsertException, ForbiddenNameException
|
||||
from .currency import DoubleNightly, NotEnoughDinks
|
||||
from .forbidden import Forbidden
|
||||
from .not_found import NoResultFoundException
|
||||
|
||||
__all__ = [
|
||||
"DuplicateInsertException",
|
||||
"ForbiddenNameException",
|
||||
"Forbidden",
|
||||
"DoubleNightly",
|
||||
"NotEnoughDinks",
|
||||
"NoResultFoundException",
|
||||
]
|
|
@ -0,0 +1,9 @@
|
|||
__all__ = ["DuplicateInsertException", "ForbiddenNameException"]
|
||||
|
||||
|
||||
class DuplicateInsertException(Exception):
|
||||
"""Exception raised when a value already exists"""
|
||||
|
||||
|
||||
class ForbiddenNameException(Exception):
|
||||
"""Exception raised when trying to insert something with a name that isn't allowed"""
|
|
@ -0,0 +1,9 @@
|
|||
__all__ = ["DoubleNightly", "NotEnoughDinks"]
|
||||
|
||||
|
||||
class DoubleNightly(Exception):
|
||||
"""Exception raised when claiming nightlies multiple times per day"""
|
||||
|
||||
|
||||
class NotEnoughDinks(Exception):
|
||||
"""Exception raised when trying to do something you don't have the Dinks for"""
|
|
@ -0,0 +1,5 @@
|
|||
__all__ = ["Forbidden"]
|
||||
|
||||
|
||||
class Forbidden(Exception):
|
||||
"""Exception raised when trying to access a resource that isn't yours"""
|
|
@ -0,0 +1,5 @@
|
|||
__all__ = ["NoResultFoundException"]
|
||||
|
||||
|
||||
class NoResultFoundException(Exception):
|
||||
"""Exception raised when nothing was found"""
|
|
@ -0,0 +1,53 @@
|
|||
import logging
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from alembic import command, script
|
||||
from alembic.config import Config
|
||||
from alembic.runtime import migration
|
||||
from database.engine import postgres_engine
|
||||
|
||||
__config_path__ = "alembic.ini"
|
||||
__migrations_path__ = "alembic/"
|
||||
|
||||
|
||||
cfg = Config(__config_path__)
|
||||
cfg.set_main_option("script_location", __migrations_path__)
|
||||
|
||||
|
||||
__all__ = ["ensure_latest_migration", "migrate"]
|
||||
|
||||
|
||||
async def ensure_latest_migration():
|
||||
"""Make sure we are currently on the latest revision, otherwise raise an exception"""
|
||||
alembic_script = script.ScriptDirectory.from_config(cfg)
|
||||
|
||||
async with postgres_engine.begin() as connection:
|
||||
current_revision = await connection.run_sync(
|
||||
lambda sync_connection: migration.MigrationContext.configure(sync_connection).get_current_revision()
|
||||
)
|
||||
|
||||
alembic_head = alembic_script.get_current_head()
|
||||
|
||||
if current_revision != alembic_head:
|
||||
error_message = (
|
||||
f"Pending migrations (current revision is {current_revision}, while head is at {alembic_head})"
|
||||
)
|
||||
logging.error(error_message)
|
||||
raise RuntimeError(error_message)
|
||||
|
||||
|
||||
def __execute_upgrade(connection: Session):
|
||||
cfg.attributes["connection"] = connection
|
||||
command.upgrade(cfg, "head")
|
||||
|
||||
|
||||
def __execute_downgrade(connection: Session):
|
||||
cfg.attributes["connection"] = connection
|
||||
command.downgrade(cfg, "base")
|
||||
|
||||
|
||||
async def migrate(up: bool):
|
||||
"""Migrate the database upwards or downwards"""
|
||||
async with postgres_engine.begin() as connection:
|
||||
await connection.run_sync(__execute_upgrade if up else __execute_downgrade)
|
|
@ -0,0 +1,336 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import BigInteger, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||
from sqlalchemy.types import DateTime
|
||||
|
||||
from database import enums
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"Bank",
|
||||
"Birthday",
|
||||
"Bookmark",
|
||||
"CommandStats",
|
||||
"CustomCommand",
|
||||
"CustomCommandAlias",
|
||||
"DadJoke",
|
||||
"Deadline",
|
||||
"EasterEgg",
|
||||
"Event",
|
||||
"FreeGame",
|
||||
"GitHubLink",
|
||||
"Link",
|
||||
"MemeTemplate",
|
||||
"NightlyData",
|
||||
"Reminder",
|
||||
"Task",
|
||||
"UforaAnnouncement",
|
||||
"UforaCourse",
|
||||
"UforaCourseAlias",
|
||||
"User",
|
||||
]
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""Required base class for all tables"""
|
||||
|
||||
# Make all DateTimes timezone-aware
|
||||
type_annotation_map = {datetime: DateTime(timezone=True)}
|
||||
|
||||
|
||||
class Bank(Base):
|
||||
"""A user's currency information"""
|
||||
|
||||
__tablename__ = "bank"
|
||||
|
||||
bank_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
|
||||
dinks: Mapped[int] = mapped_column(BigInteger, server_default="0", nullable=False)
|
||||
invested: Mapped[int] = mapped_column(BigInteger, server_default="0", nullable=False)
|
||||
|
||||
# Interest rate
|
||||
interest_level: Mapped[int] = mapped_column(server_default="1", nullable=False)
|
||||
|
||||
# Maximum amount that can be stored in the bank
|
||||
capacity_level: Mapped[int] = mapped_column(server_default="1", nullable=False)
|
||||
|
||||
# Maximum amount that can be robbed
|
||||
rob_level: Mapped[int] = mapped_column(server_default="1", nullable=False)
|
||||
|
||||
user: Mapped[User] = relationship(uselist=False, back_populates="bank", lazy="selectin")
|
||||
|
||||
|
||||
class Birthday(Base):
|
||||
"""A user's birthday"""
|
||||
|
||||
__tablename__ = "birthdays"
|
||||
|
||||
birthday_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
birthday: Mapped[date] = mapped_column(nullable=False)
|
||||
|
||||
user: Mapped[User] = relationship(uselist=False, back_populates="birthday", lazy="selectin")
|
||||
|
||||
|
||||
class Bookmark(Base):
|
||||
"""A bookmark to a given message"""
|
||||
|
||||
__tablename__ = "bookmarks"
|
||||
__table_args__ = (UniqueConstraint("user_id", "label"),)
|
||||
|
||||
bookmark_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
label: Mapped[str] = mapped_column(nullable=False)
|
||||
jump_url: Mapped[str] = mapped_column(nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="bookmarks", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class CommandStats(Base):
|
||||
"""Metrics on how often commands are used"""
|
||||
|
||||
__tablename__ = "command_stats"
|
||||
command_stats_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
command: Mapped[str] = mapped_column(nullable=False)
|
||||
timestamp: Mapped[datetime] = mapped_column(nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
slash: Mapped[bool] = mapped_column(nullable=False)
|
||||
context_menu: Mapped[bool] = mapped_column(nullable=False)
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="command_stats", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class CustomCommand(Base):
|
||||
"""Custom commands to fill the hole Dyno couldn't"""
|
||||
|
||||
__tablename__ = "custom_commands"
|
||||
|
||||
command_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
indexed_name: Mapped[str] = mapped_column(nullable=False, index=True)
|
||||
response: Mapped[str] = mapped_column(nullable=False)
|
||||
|
||||
aliases: Mapped[List[CustomCommandAlias]] = relationship(
|
||||
back_populates="command", uselist=True, cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
|
||||
|
||||
class CustomCommandAlias(Base):
|
||||
"""Aliases for custom commands"""
|
||||
|
||||
__tablename__ = "custom_command_aliases"
|
||||
|
||||
alias_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
alias: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
indexed_alias: Mapped[str] = mapped_column(nullable=False, index=True)
|
||||
command_id: Mapped[int] = mapped_column(ForeignKey("custom_commands.command_id"))
|
||||
|
||||
command: Mapped[CustomCommand] = relationship(back_populates="aliases", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class DadJoke(Base):
|
||||
"""When I finally understood asymptotic notation, it was a big "oh" moment"""
|
||||
|
||||
__tablename__ = "dad_jokes"
|
||||
|
||||
dad_joke_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
joke: Mapped[str] = mapped_column(nullable=False)
|
||||
|
||||
|
||||
class Deadline(Base):
|
||||
"""A deadline for a university project"""
|
||||
|
||||
__tablename__ = "deadlines"
|
||||
|
||||
deadline_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
course_id: Mapped[int] = mapped_column(ForeignKey("ufora_courses.course_id"))
|
||||
name: Mapped[str] = mapped_column(nullable=False)
|
||||
deadline: Mapped[datetime] = mapped_column(nullable=False)
|
||||
|
||||
course: Mapped[UforaCourse] = relationship(back_populates="deadlines", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class EasterEgg(Base):
|
||||
"""An easter egg response"""
|
||||
|
||||
__tablename__ = "easter_eggs"
|
||||
|
||||
easter_egg_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
match: Mapped[str] = mapped_column(nullable=False)
|
||||
response: Mapped[str] = mapped_column(nullable=False)
|
||||
exact: Mapped[bool] = mapped_column(nullable=False, server_default="1")
|
||||
startswith: Mapped[bool] = mapped_column(nullable=False, server_default="1")
|
||||
|
||||
|
||||
class Event(Base):
|
||||
"""A scheduled event"""
|
||||
|
||||
__tablename__ = "events"
|
||||
|
||||
event_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(nullable=True)
|
||||
notification_channel: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
timestamp: Mapped[datetime] = mapped_column(nullable=False)
|
||||
|
||||
|
||||
class FreeGame(Base):
|
||||
"""A temporarily free game"""
|
||||
|
||||
__tablename__ = "free_games"
|
||||
|
||||
free_game_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
|
||||
|
||||
class GitHubLink(Base):
|
||||
"""A user's GitHub link"""
|
||||
|
||||
__tablename__ = "github_links"
|
||||
|
||||
github_link_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
url: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="github_links", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class Link(Base):
|
||||
"""Useful links that go useful places"""
|
||||
|
||||
__tablename__ = "links"
|
||||
|
||||
link_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
url: Mapped[str] = mapped_column(nullable=False)
|
||||
|
||||
|
||||
class MemeTemplate(Base):
|
||||
"""A meme template for the Imgflip API"""
|
||||
|
||||
__tablename__ = "meme"
|
||||
|
||||
meme_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
template_id: Mapped[int] = mapped_column(nullable=False, unique=True)
|
||||
field_count: Mapped[int] = mapped_column(nullable=False)
|
||||
|
||||
|
||||
class NightlyData(Base):
|
||||
"""Data for a user's Nightly stats"""
|
||||
|
||||
__tablename__ = "nightly_data"
|
||||
|
||||
nightly_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
last_nightly: Mapped[Optional[date]] = mapped_column(nullable=True)
|
||||
count: Mapped[int] = mapped_column(server_default="0", nullable=False)
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="nightly_data", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class Reminder(Base):
|
||||
"""Something that a user should be reminded of"""
|
||||
|
||||
__tablename__ = "reminders"
|
||||
|
||||
reminder_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.user_id"))
|
||||
category: Mapped[enums.ReminderCategory] = mapped_column(nullable=False)
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="reminders", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class Task(Base):
|
||||
"""A Didier task"""
|
||||
|
||||
__tablename__ = "tasks"
|
||||
|
||||
task_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
task: Mapped[enums.TaskType] = mapped_column(nullable=False, unique=True)
|
||||
previous_run: Mapped[datetime] = mapped_column(nullable=True)
|
||||
|
||||
|
||||
class UforaCourse(Base):
|
||||
"""A course on Ufora"""
|
||||
|
||||
__tablename__ = "ufora_courses"
|
||||
|
||||
course_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
code: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
year: Mapped[int] = mapped_column(nullable=False)
|
||||
compulsory: Mapped[bool] = mapped_column(server_default="1", nullable=False)
|
||||
role_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True, unique=False)
|
||||
overarching_role_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True, unique=False)
|
||||
# This is not the greatest fix, but there can only ever be two, so it will do the job
|
||||
alternative_overarching_role_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True, unique=False)
|
||||
log_announcements: Mapped[bool] = mapped_column(server_default="0", nullable=False)
|
||||
|
||||
announcements: Mapped[List[UforaAnnouncement]] = relationship(
|
||||
back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
aliases: Mapped[List[UforaCourseAlias]] = relationship(
|
||||
back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
deadlines: Mapped[List[Deadline]] = relationship(
|
||||
back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||
)
|
||||
|
||||
|
||||
class UforaCourseAlias(Base):
|
||||
"""An alias for a course on Ufora that we use to refer to them"""
|
||||
|
||||
__tablename__ = "ufora_course_aliases"
|
||||
|
||||
alias_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
alias: Mapped[str] = mapped_column(nullable=False, unique=True)
|
||||
course_id: Mapped[int] = mapped_column(ForeignKey("ufora_courses.course_id"))
|
||||
|
||||
course: Mapped[UforaCourse] = relationship(back_populates="aliases", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class UforaAnnouncement(Base):
|
||||
"""An announcement sent on Ufora"""
|
||||
|
||||
__tablename__ = "ufora_announcements"
|
||||
|
||||
announcement_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
course_id: Mapped[int] = mapped_column(ForeignKey("ufora_courses.course_id"))
|
||||
publication_date: Mapped[date] = mapped_column()
|
||||
|
||||
course: Mapped[UforaCourse] = relationship(back_populates="announcements", uselist=False, lazy="selectin")
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""A Didier user"""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||
|
||||
bank: Mapped[Bank] = relationship(
|
||||
back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
birthday: Mapped[Optional[Birthday]] = relationship(
|
||||
back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
bookmarks: Mapped[List[Bookmark]] = relationship(
|
||||
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
command_stats: Mapped[List[CommandStats]] = relationship(
|
||||
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
github_links: Mapped[List[GitHubLink]] = relationship(
|
||||
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
nightly_data: Mapped[NightlyData] = relationship(
|
||||
back_populates="user", uselist=False, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
reminders: Mapped[List[Reminder]] = relationship(
|
||||
back_populates="user", uselist=True, lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.engine import DBSession
|
||||
from database.schemas import UforaCourse
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
|
||||
async def main():
|
||||
"""Example script: add a Ufora course"""
|
||||
session: AsyncSession
|
||||
async with DBSession() as session:
|
||||
modsim = UforaCourse(
|
||||
course_id=439235,
|
||||
code="C003786",
|
||||
name="Modelleren en Simuleren",
|
||||
year=3,
|
||||
compulsory=False,
|
||||
role_id=785577582561067028,
|
||||
)
|
||||
|
||||
session.add_all([modsim])
|
||||
await session.commit()
|
|
@ -0,0 +1,69 @@
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.engine import DBSession
|
||||
from database.schemas import EasterEgg
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
|
||||
async def main():
|
||||
"""Add the initial easter egg responses"""
|
||||
session: AsyncSession
|
||||
async with DBSession() as session:
|
||||
# https://www.youtube.com/watch?v=Vd6hVYkkq88
|
||||
do_not_cite_deep_magic = EasterEgg(
|
||||
match=r"((don'?t)|(do not)) cite the deep magic to me,? witch",
|
||||
response="_I was there when it was written_",
|
||||
exact=True,
|
||||
)
|
||||
|
||||
# https://www.youtube.com/watch?v=LrHTR22pIhw
|
||||
dormammu = EasterEgg(match=r"dormammu", response="_I've come to bargain_", exact=True)
|
||||
|
||||
# https://youtu.be/rEq1Z0bjdwc?t=7
|
||||
hello_there = EasterEgg(match=r"hello there", response="_General Kenobi_", exact=True)
|
||||
|
||||
# https://www.youtube.com/watch?v=_WZCvQ5J3pk
|
||||
hey = EasterEgg(
|
||||
match=r"hey,? ?(?:you)?",
|
||||
response="_You're finally awake!_",
|
||||
exact=True,
|
||||
)
|
||||
|
||||
# https://www.youtube.com/watch?v=2z5ZDC1eQEA
|
||||
is_this_the_kk = EasterEgg(
|
||||
match=r"is (this|dis) (.*)", response="No, this is Patrick.", exact=False, startswith=True
|
||||
)
|
||||
|
||||
# https://youtu.be/d6uckPRKvSg?t=4
|
||||
its_over_anakin = EasterEgg(
|
||||
match=r"it'?s over ", response="_I have the high ground_", exact=False, startswith=True
|
||||
)
|
||||
|
||||
# https://www.youtube.com/watch?v=Vx5prDjKAcw
|
||||
perfectly_balanced = EasterEgg(match=r"perfectly balanced", response="_As all things should be_", exact=True)
|
||||
|
||||
# ( ͡◉ ͜ʖ ͡◉)
|
||||
sixty_nine = EasterEgg(match=r"(^69$)|(^69 )|( 69 )|( 69$)", response="_Nice_", exact=False, startswith=False)
|
||||
|
||||
# https://youtu.be/7mbLzkNFDs8?t=19
|
||||
what_did_it_cost = EasterEgg(match=r"what did it cost\??", response="_Everything_", exact=True)
|
||||
|
||||
# https://youtu.be/EJfYh-JVbJA?t=10
|
||||
you_cant_defeat_me = EasterEgg(match=r"you can'?t defeat me", response="_I know, but he can_", exact=False)
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
do_not_cite_deep_magic,
|
||||
dormammu,
|
||||
hello_there,
|
||||
hey,
|
||||
is_this_the_kk,
|
||||
its_over_anakin,
|
||||
perfectly_balanced,
|
||||
sixty_nine,
|
||||
what_did_it_cost,
|
||||
you_cant_defeat_me,
|
||||
]
|
||||
)
|
||||
await session.commit()
|
|
@ -0,0 +1,599 @@
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.engine import DBSession
|
||||
from database.schemas import UforaCourse, UforaCourseAlias
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
|
||||
async def main():
|
||||
"""Add the Ufora courses for the 2022-2023 academic year
|
||||
|
||||
Course id's are only used to fetch announcements, and I can only fetch announcements of courses I enroll in,
|
||||
so other courses can use an auto-generated id
|
||||
|
||||
This will never clash as there will never be 650k regular courses
|
||||
"""
|
||||
session: AsyncSession
|
||||
async with DBSession() as session:
|
||||
"""3rd Bachelor"""
|
||||
artificiele_intelligentie = UforaCourse(
|
||||
code="C003756",
|
||||
name="Artificiële Intelligentie",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891743671022673920,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
algoritmen_datastructuren_3 = UforaCourse(
|
||||
code="C003782",
|
||||
name="Algoritmen en Datastructuren 3",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891743791466307654,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
automaten_berekenbaarheid_complexiteit = UforaCourse(
|
||||
code="C003785",
|
||||
name="Automaten, Berekenbaarheid en Complexiteit",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891744082404200539,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
besturingssystemen = UforaCourse(
|
||||
code="E019010",
|
||||
name="Besturingssystemen",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891743898291032114,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
computationele_biologie = UforaCourse(
|
||||
code="C003789",
|
||||
name="Computationele Biologie",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891744050988847135,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
logisch_programmeren = UforaCourse(
|
||||
code="C003783",
|
||||
name="Logisch Programmeren",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891743966482034800,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
software_engineering_lab_2 = UforaCourse(
|
||||
code="C003784",
|
||||
name="Software Engineering Lab 2",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=891744007300980737,
|
||||
overarching_role_id=891743208248324196,
|
||||
)
|
||||
|
||||
modelleren_en_simuleren = UforaCourse(
|
||||
course_id=636139,
|
||||
code="C003786",
|
||||
name="Modelleren en Simuleren",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=1024573730418069565,
|
||||
overarching_role_id=891744461405687808,
|
||||
log_announcements=True,
|
||||
)
|
||||
|
||||
informatiebeveiliging = UforaCourse(
|
||||
code="E019400",
|
||||
name="Informatiebeveiliging",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=1023333190678626314,
|
||||
overarching_role_id=891744461405687808,
|
||||
alternative_overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
parallelle_computersystemen = UforaCourse(
|
||||
code="E034140",
|
||||
name="Parallelle Computersystemen",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
role_id=1023300295918358691,
|
||||
overarching_role_id=891744461405687808,
|
||||
alternative_overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
inleiding_tot_elektrotechniek = UforaCourse(
|
||||
code="C003806",
|
||||
name="Inleiding tot de Elektrotechniek",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
overarching_role_id=891744390035415111,
|
||||
)
|
||||
|
||||
inleiding_tot_telecommunicatie = UforaCourse(
|
||||
code="C003787",
|
||||
name="Inleiding tot de Telecommunicatie",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
overarching_role_id=891744390035415111,
|
||||
)
|
||||
|
||||
wiskundige_modellering = UforaCourse(
|
||||
code="C003788",
|
||||
name="Wiskundige Modellering in de Ingenieurswetenschappen",
|
||||
year=3,
|
||||
compulsory=True,
|
||||
overarching_role_id=891744390035415111,
|
||||
)
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
artificiele_intelligentie,
|
||||
algoritmen_datastructuren_3,
|
||||
automaten_berekenbaarheid_complexiteit,
|
||||
besturingssystemen,
|
||||
computationele_biologie,
|
||||
logisch_programmeren,
|
||||
software_engineering_lab_2,
|
||||
modelleren_en_simuleren,
|
||||
informatiebeveiliging,
|
||||
parallelle_computersystemen,
|
||||
inleiding_tot_elektrotechniek,
|
||||
inleiding_tot_telecommunicatie,
|
||||
wiskundige_modellering,
|
||||
]
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
"""Aliases"""
|
||||
ai = UforaCourseAlias(course_id=artificiele_intelligentie.course_id, alias="AI")
|
||||
ad3 = UforaCourseAlias(course_id=algoritmen_datastructuren_3.course_id, alias="AD3")
|
||||
abc = UforaCourseAlias(course_id=automaten_berekenbaarheid_complexiteit.course_id, alias="ABC")
|
||||
bs = UforaCourseAlias(course_id=besturingssystemen.course_id, alias="BS")
|
||||
compbio = UforaCourseAlias(course_id=computationele_biologie.course_id, alias="Compbio")
|
||||
logprog = UforaCourseAlias(course_id=logisch_programmeren.course_id, alias="LogProg")
|
||||
prolog = UforaCourseAlias(course_id=logisch_programmeren.course_id, alias="Prolog")
|
||||
sel2 = UforaCourseAlias(course_id=software_engineering_lab_2.course_id, alias="SEL2")
|
||||
selab2 = UforaCourseAlias(course_id=software_engineering_lab_2.course_id, alias="SELab2")
|
||||
modsim = UforaCourseAlias(course_id=modelleren_en_simuleren.course_id, alias="ModSim")
|
||||
infosec = UforaCourseAlias(course_id=informatiebeveiliging.course_id, alias="InfoSec")
|
||||
information_security = UforaCourseAlias(course_id=informatiebeveiliging.course_id, alias="Information Security")
|
||||
pcs = UforaCourseAlias(course_id=parallelle_computersystemen.course_id, alias="PCS")
|
||||
parallel_computer_systems = UforaCourseAlias(
|
||||
course_id=parallelle_computersystemen.course_id, alias="Parallel Computer Systems"
|
||||
)
|
||||
elektro = UforaCourseAlias(course_id=inleiding_tot_elektrotechniek.course_id, alias="Elektro")
|
||||
elektrotechniek = UforaCourseAlias(course_id=inleiding_tot_elektrotechniek.course_id, alias="Elektrotechniek")
|
||||
telecom = UforaCourseAlias(course_id=inleiding_tot_telecommunicatie.course_id, alias="Telecom")
|
||||
wimo = UforaCourseAlias(course_id=wiskundige_modellering.course_id, alias="WiMo")
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
ai,
|
||||
ad3,
|
||||
abc,
|
||||
bs,
|
||||
compbio,
|
||||
logprog,
|
||||
prolog,
|
||||
sel2,
|
||||
selab2,
|
||||
modsim,
|
||||
infosec,
|
||||
information_security,
|
||||
pcs,
|
||||
parallel_computer_systems,
|
||||
elektro,
|
||||
elektrotechniek,
|
||||
telecom,
|
||||
wimo,
|
||||
]
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
"""1st Master CS"""
|
||||
fundamenten_van_programmeertalen = UforaCourse(
|
||||
course_id=633639,
|
||||
code="C003241",
|
||||
name="Fundamenten van Programmeertalen",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023298665416228876,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
machine_learning = UforaCourse(
|
||||
course_id=630807,
|
||||
code="C003758",
|
||||
name="Machine Learning (CS)",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023294041825235087,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
parallelle_en_gedistribueerde = UforaCourse(
|
||||
course_id=633583,
|
||||
code="E017930",
|
||||
name="Parallelle en Gedistribueerde Softwaresystemen",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023293978273136700,
|
||||
overarching_role_id=1023293447387496570,
|
||||
alternative_overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
discrete_algoritmen = UforaCourse(
|
||||
course_id=633675,
|
||||
code="C003349",
|
||||
name="Discrete Algoritmen",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023299229277487145,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
software_engineering_lab_3 = UforaCourse(
|
||||
course_id=631370,
|
||||
code="C004072",
|
||||
name="Software Engineering Lab 3",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023299234008678550,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
compilers = UforaCourse(
|
||||
course_id=633663,
|
||||
code="E018520",
|
||||
name="Compilers",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023299237003399249,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
datavisualisatie = UforaCourse(
|
||||
course_id=630803,
|
||||
code="C004041",
|
||||
name="Datavisualisatie",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023299239243161671,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
recht_van_intellectuele_eigendom = UforaCourse(
|
||||
course_id=633696,
|
||||
code="C000957",
|
||||
name="Recht van de Intellectuele Eigendom",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023299241457745930,
|
||||
overarching_role_id=1023293447387496570,
|
||||
)
|
||||
|
||||
"""2nd Master CS"""
|
||||
computergrafiek = UforaCourse(
|
||||
code="C004073",
|
||||
name="Computergrafiek",
|
||||
year=5,
|
||||
compulsory=True,
|
||||
role_id=1023303199609860268,
|
||||
overarching_role_id=1023302736210567208,
|
||||
)
|
||||
|
||||
big_data_science = UforaCourse(
|
||||
code="C004074",
|
||||
name="Big Data Science",
|
||||
year=5,
|
||||
compulsory=True,
|
||||
role_id=1023303190046851153,
|
||||
overarching_role_id=1023302736210567208,
|
||||
)
|
||||
|
||||
bedrijfsstage = UforaCourse(
|
||||
code="C004075",
|
||||
name="Bedrijfsstage",
|
||||
year=5,
|
||||
compulsory=True,
|
||||
role_id=1023303201807679598,
|
||||
overarching_role_id=1023302736210567208,
|
||||
)
|
||||
|
||||
masterproef = UforaCourse(
|
||||
code="C002309",
|
||||
name="Masterproef",
|
||||
year=5,
|
||||
compulsory=True,
|
||||
role_id=1023319264851144754,
|
||||
overarching_role_id=1023302736210567208,
|
||||
alternative_overarching_role_id=1023300434800164914,
|
||||
)
|
||||
|
||||
"""1st Master CSE"""
|
||||
design_of_multimedia_applications = UforaCourse(
|
||||
code="E017920",
|
||||
name="Design of Multimedia Applications",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023300418635317259,
|
||||
overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
research_project = UforaCourse(
|
||||
code="E031710",
|
||||
name="Research Project",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023300421776855160,
|
||||
overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
design_project = UforaCourse(
|
||||
code="E033710",
|
||||
name="Design Project",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023300424561852537,
|
||||
overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
mobile_and_broadband_access_networks = UforaCourse(
|
||||
code="E012320",
|
||||
name="Mobile and Broadband Access Networks",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023300427246223471,
|
||||
overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
information_theory = UforaCourse(
|
||||
code="E003600",
|
||||
name="Information Theory",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023300429469204480,
|
||||
overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
queueing_analysis_and_simulation = UforaCourse(
|
||||
code="E011322",
|
||||
name="Queueing Analysis and Simulation",
|
||||
year=4,
|
||||
compulsory=True,
|
||||
role_id=1023300431696371793,
|
||||
overarching_role_id=1023278462733127710,
|
||||
)
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
fundamenten_van_programmeertalen,
|
||||
machine_learning,
|
||||
parallelle_en_gedistribueerde,
|
||||
discrete_algoritmen,
|
||||
software_engineering_lab_3,
|
||||
compilers,
|
||||
datavisualisatie,
|
||||
recht_van_intellectuele_eigendom,
|
||||
computergrafiek,
|
||||
big_data_science,
|
||||
bedrijfsstage,
|
||||
masterproef,
|
||||
design_of_multimedia_applications,
|
||||
research_project,
|
||||
design_project,
|
||||
mobile_and_broadband_access_networks,
|
||||
information_theory,
|
||||
queueing_analysis_and_simulation,
|
||||
]
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
"""Master aliases"""
|
||||
fundamenten = UforaCourseAlias(course_id=fundamenten_van_programmeertalen.course_id, alias="Fundamenten")
|
||||
ml = UforaCourseAlias(course_id=machine_learning.course_id, alias="ML")
|
||||
pds = UforaCourseAlias(course_id=parallelle_en_gedistribueerde.course_id, alias="PDS")
|
||||
parallel_and_distributed = UforaCourseAlias(
|
||||
course_id=parallelle_en_gedistribueerde.course_id, alias="Parallel and Distributed Software Systems"
|
||||
)
|
||||
da = UforaCourseAlias(course_id=discrete_algoritmen.course_id, alias="DA")
|
||||
discalg = UforaCourseAlias(course_id=discrete_algoritmen.course_id, alias="DiscAlg")
|
||||
sel3 = UforaCourseAlias(course_id=software_engineering_lab_3.course_id, alias="SEL3")
|
||||
selab3 = UforaCourseAlias(course_id=software_engineering_lab_3.course_id, alias="SELab3")
|
||||
dv = UforaCourseAlias(course_id=datavisualisatie.course_id, alias="DV")
|
||||
datavis = UforaCourseAlias(course_id=datavisualisatie.course_id, alias="DataVis")
|
||||
recht = UforaCourseAlias(course_id=recht_van_intellectuele_eigendom.course_id, alias="Recht")
|
||||
computer_graphics = UforaCourseAlias(course_id=computergrafiek.course_id, alias="Computer Graphics")
|
||||
stage = UforaCourseAlias(course_id=bedrijfsstage.course_id, alias="Stage")
|
||||
thesis = UforaCourseAlias(course_id=masterproef.course_id, alias="Thesis")
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
fundamenten,
|
||||
ml,
|
||||
pds,
|
||||
parallel_and_distributed,
|
||||
da,
|
||||
discalg,
|
||||
sel3,
|
||||
selab3,
|
||||
dv,
|
||||
datavis,
|
||||
recht,
|
||||
computer_graphics,
|
||||
stage,
|
||||
thesis,
|
||||
]
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
"""Elective master's courses"""
|
||||
aanbevelingssystemen = UforaCourse(
|
||||
course_id=635444,
|
||||
code="E018230",
|
||||
name="Aanbevelingssystemen",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023303206572404817,
|
||||
)
|
||||
algoritmische_grafentheorie = UforaCourse(
|
||||
code="C000145", name="Algoritmische Grafentheorie", year=6, compulsory=False, role_id=1023304281094373436
|
||||
)
|
||||
artificial_intelligence = UforaCourse(
|
||||
code="E016330", name="Artificial Intelligence", year=6, compulsory=False, role_id=1023304874789703741
|
||||
)
|
||||
berekenbaarheid_en_complexiteit = UforaCourse(
|
||||
code="C000627",
|
||||
name="Berekenbaarheid en Complexiteit",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023304692861784064,
|
||||
)
|
||||
capita_selecta_bio = UforaCourse(
|
||||
code="C004122",
|
||||
name="Capita Selecta in Bioinformatics",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023305177727504444,
|
||||
)
|
||||
causal_machine_learning = UforaCourse(
|
||||
code="C004413", name="Causal Machine Learning", year=6, compulsory=False, role_id=1023304690491985961
|
||||
)
|
||||
computational_challenges = UforaCourse(
|
||||
code="C003711",
|
||||
name="Computational Challenges in Bioinformatics",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023304283413811411,
|
||||
)
|
||||
computeralgebra = UforaCourse(
|
||||
code="C001026", name="Computeralgebra", year=6, compulsory=False, role_id=1023304697928495164
|
||||
)
|
||||
computervisie = UforaCourse(
|
||||
code="E736020", name="Computervisie", year=6, compulsory=False, role_id=1023304274945511575
|
||||
)
|
||||
context_and_nuance = UforaCourse(
|
||||
code="A005503",
|
||||
name="Context and Nuance: A Critical Reflection on Current Topics",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023304898252648470,
|
||||
)
|
||||
deep_learning = UforaCourse(
|
||||
code="F000918", name="Deep Learning", year=6, compulsory=False, role_id=1023304893672464474
|
||||
)
|
||||
economie = UforaCourse(code="F000758", name="Economie", year=6, compulsory=False, role_id=1023305174506291290)
|
||||
geavanceerde_databanken = UforaCourse(
|
||||
code="E018441", name="Geavanceerde Databanken", year=6, compulsory=False, role_id=1023304259913134130
|
||||
)
|
||||
gevorderde_numerieke_methoden = UforaCourse(
|
||||
code="C004011", name="Gevorderde Numerieke Methoden", year=6, compulsory=False, role_id=1023304278410002482
|
||||
)
|
||||
gevorderd_wetenschappelijk_engels = UforaCourse(
|
||||
code="A003107",
|
||||
name="Gevorderd wetenschappelijk Engels",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023305170987257898,
|
||||
)
|
||||
internet_of_things = UforaCourse(
|
||||
code="E019170", name="Internet of Things", year=6, compulsory=False, role_id=1023304695416111224
|
||||
)
|
||||
medical_imaging = UforaCourse(
|
||||
code="E010371", name="Medical Imaging", year=6, compulsory=False, role_id=1023304900555317399
|
||||
)
|
||||
robotica = UforaCourse(
|
||||
course_id=634368, code="E019370", name="Robotica", year=6, compulsory=False, role_id=1023303205360246904
|
||||
)
|
||||
software_hacking = UforaCourse(
|
||||
course_id=635436,
|
||||
code="E017941",
|
||||
name="Softwarehacking en -Protectie",
|
||||
year=6,
|
||||
compulsory=False,
|
||||
role_id=1023303203913211905,
|
||||
)
|
||||
spraakverwerking = UforaCourse(
|
||||
code="E010220", name="Spraakverwerking", year=6, compulsory=False, role_id=1023304686704533576
|
||||
)
|
||||
sustainable_computing = UforaCourse(
|
||||
code="E034500", name="Sustainable Computing", year=6, compulsory=False, role_id=1023304895421481081
|
||||
)
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
aanbevelingssystemen,
|
||||
algoritmische_grafentheorie,
|
||||
artificial_intelligence,
|
||||
berekenbaarheid_en_complexiteit,
|
||||
capita_selecta_bio,
|
||||
causal_machine_learning,
|
||||
computational_challenges,
|
||||
computeralgebra,
|
||||
computervisie,
|
||||
context_and_nuance,
|
||||
deep_learning,
|
||||
economie,
|
||||
geavanceerde_databanken,
|
||||
gevorderde_numerieke_methoden,
|
||||
gevorderd_wetenschappelijk_engels,
|
||||
internet_of_things,
|
||||
medical_imaging,
|
||||
robotica,
|
||||
software_hacking,
|
||||
spraakverwerking,
|
||||
sustainable_computing,
|
||||
]
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
recommender_systems = UforaCourseAlias(course_id=aanbevelingssystemen.course_id, alias="Recommender Systems")
|
||||
cv = UforaCourseAlias(course_id=computervisie.course_id, alias="CV")
|
||||
comp_vis = UforaCourseAlias(course_id=computervisie.course_id, alias="CompVis")
|
||||
computer_vision = UforaCourseAlias(course_id=computervisie.course_id, alias="Computer Vision")
|
||||
context = UforaCourseAlias(course_id=context_and_nuance.course_id, alias="Context")
|
||||
eco = UforaCourseAlias(course_id=economie.course_id, alias="Eco")
|
||||
advanced_databases = UforaCourseAlias(course_id=geavanceerde_databanken.course_id, alias="Advanced Databases")
|
||||
advanced_academic_english = UforaCourseAlias(
|
||||
course_id=gevorderd_wetenschappelijk_engels.course_id, alias="Advanced Academic English"
|
||||
)
|
||||
iot = UforaCourseAlias(course_id=internet_of_things.course_id, alias="IoT")
|
||||
robotics = UforaCourseAlias(course_id=robotica.course_id, alias="Robotics")
|
||||
software_hacking_en = UforaCourseAlias(
|
||||
course_id=software_hacking.course_id, alias="Software Hacking and Protection"
|
||||
)
|
||||
speech_processing = UforaCourseAlias(course_id=spraakverwerking.course_id, alias="Speech Processing")
|
||||
|
||||
session.add_all(
|
||||
[
|
||||
recommender_systems,
|
||||
cv,
|
||||
comp_vis,
|
||||
computer_vision,
|
||||
context,
|
||||
eco,
|
||||
advanced_databases,
|
||||
advanced_academic_english,
|
||||
iot,
|
||||
robotics,
|
||||
software_hacking_en,
|
||||
speech_processing,
|
||||
]
|
||||
)
|
||||
|
||||
await session.commit()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue