mirror of
https://github.com/reality/dbot.git
synced 2025-08-02 00:37:25 +02:00
Compare commits
1909 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a77c6296f1 | ||
![]() |
2307f164f8 | ||
![]() |
7d9a9eeeff | ||
![]() |
0ac81b886e | ||
![]() |
77a9c346d8 | ||
![]() |
97ebd371bf | ||
![]() |
ec3d6ed13b | ||
![]() |
a245d385f7 | ||
![]() |
695398d8fb | ||
![]() |
08922c8e67 | ||
![]() |
dfdb31b31d | ||
![]() |
1b18c9f048 | ||
![]() |
8c97c665a6 | ||
![]() |
1657e7cc02 | ||
![]() |
09f8f525e6 | ||
![]() |
d64375ca50 | ||
![]() |
a4697e8d36 | ||
![]() |
a97bda3c20 | ||
![]() |
ab6cb80ea2 | ||
![]() |
c26be5410f | ||
![]() |
e11abe5af4 | ||
![]() |
6a255244df | ||
![]() |
f3dcc0e693 | ||
![]() |
ed69582c7a | ||
![]() |
814a84ee8e | ||
![]() |
a5f9998872 | ||
![]() |
d544dcf017 | ||
![]() |
bbfc4c18a6 | ||
![]() |
0c8c377b08 | ||
![]() |
c298813b28 | ||
![]() |
0f0877f27a | ||
![]() |
554a9e995b | ||
![]() |
ae989d60ae | ||
![]() |
f7b83e98b7 | ||
![]() |
b415ee086f | ||
![]() |
944827c4d9 | ||
![]() |
97642f35a9 | ||
![]() |
2d20f19572 | ||
![]() |
98c8894f95 | ||
![]() |
11a7f1d20e | ||
![]() |
86d0a6f31b | ||
![]() |
71c2c52d47 | ||
![]() |
fbc4ac5afe | ||
![]() |
a574b7d2ed | ||
![]() |
93ee3cb08b | ||
![]() |
597171c37d | ||
![]() |
8182980420 | ||
![]() |
bb28cebde1 | ||
![]() |
48728c5801 | ||
![]() |
43cfe3b637 | ||
![]() |
19cc4c474d | ||
![]() |
f5c9a275a9 | ||
![]() |
ff7be1f31d | ||
![]() |
0118f962b6 | ||
![]() |
167525b5b9 | ||
![]() |
25d572e8cb | ||
![]() |
72a4552742 | ||
![]() |
bd148915ed | ||
![]() |
354c0935b9 | ||
![]() |
4a4e41b702 | ||
![]() |
b87175e665 | ||
![]() |
9a7e1ab4bf | ||
![]() |
10c642c94f | ||
![]() |
dde0200032 | ||
![]() |
217e65df96 | ||
![]() |
03067f97d6 | ||
![]() |
00215f62f0 | ||
![]() |
9a4a86c6ba | ||
![]() |
7d31560458 | ||
![]() |
f55f645a3b | ||
![]() |
7debad9a1d | ||
![]() |
9faffa0d11 | ||
![]() |
3c22b811ad | ||
![]() |
329836d577 | ||
![]() |
5796308e62 | ||
![]() |
3972b5272b | ||
![]() |
17ecf62da0 | ||
![]() |
9b55509257 | ||
![]() |
69c06537e2 | ||
![]() |
8fe032579d | ||
![]() |
fe8168a237 | ||
![]() |
016c132158 | ||
![]() |
9df86cdd99 | ||
![]() |
00ac08a9da | ||
![]() |
eba4a9a4a1 | ||
![]() |
beaa420015 | ||
![]() |
709cd085a9 | ||
![]() |
56d12bd649 | ||
![]() |
510a02d08a | ||
![]() |
d7f212ea52 | ||
![]() |
3b6dd18611 | ||
![]() |
58256a8f62 | ||
![]() |
0da6af229f | ||
![]() |
8310dc38ff | ||
![]() |
ebb0a023c0 | ||
![]() |
f097685c3d | ||
![]() |
4f269a5c02 | ||
![]() |
777d738394 | ||
![]() |
e62a1c8484 | ||
![]() |
0f1c6c6782 | ||
![]() |
b8bbaab9d9 | ||
![]() |
3204c422bb | ||
![]() |
ec86b6e1a8 | ||
![]() |
07156b2eb3 | ||
![]() |
d37326c88e | ||
![]() |
1f0e13bd2a | ||
![]() |
0d233f69b7 | ||
![]() |
e22133d685 | ||
![]() |
0d2d00635c | ||
![]() |
d41836e937 | ||
![]() |
c1c54bbd59 | ||
![]() |
6a38eb5289 | ||
![]() |
d25ec221c7 | ||
![]() |
54efa27ff2 | ||
![]() |
f02d2b74df | ||
![]() |
330868686d | ||
![]() |
9a44c41fbd | ||
![]() |
92598b13c2 | ||
![]() |
a57b123fed | ||
![]() |
ef86954025 | ||
![]() |
7964bb9e88 | ||
![]() |
238c112986 | ||
![]() |
d4d101bc94 | ||
![]() |
5191eca0a4 | ||
![]() |
3f599ddfc2 | ||
![]() |
0e95f8e42a | ||
![]() |
485e08ff97 | ||
![]() |
ed24043a55 | ||
![]() |
e8f657de94 | ||
![]() |
aeea5a7e18 | ||
![]() |
2b043d018b | ||
![]() |
45a27904bd | ||
![]() |
cfbb71faa6 | ||
![]() |
d9093e6da8 | ||
![]() |
930607b4fe | ||
![]() |
601d10b0de | ||
![]() |
92c045d8c7 | ||
![]() |
face69d31a | ||
![]() |
45153a45d2 | ||
![]() |
e767e43771 | ||
![]() |
2ebeea1ef4 | ||
![]() |
a58e1fcc4b | ||
![]() |
7f2878ce58 | ||
![]() |
a5657a5a36 | ||
![]() |
bab36d5af3 | ||
![]() |
d19fc76088 | ||
![]() |
fc0fe6f5be | ||
![]() |
45e44bc7f8 | ||
![]() |
d442461076 | ||
![]() |
c47677a590 | ||
![]() |
f2c61c95b9 | ||
![]() |
e120869bcd | ||
![]() |
a14d042be2 | ||
![]() |
bc114a10e6 | ||
![]() |
5e8ccb6bcc | ||
![]() |
b61c482a32 | ||
![]() |
ba2ad2649f | ||
![]() |
0473597b0a | ||
![]() |
04fb1cc67b | ||
![]() |
72e6fca9e5 | ||
![]() |
71632f401b | ||
![]() |
d31a95b3a3 | ||
![]() |
fa431ac800 | ||
![]() |
aed72f52dc | ||
![]() |
b9064302ba | ||
![]() |
40e20f06b0 | ||
![]() |
84f8e7a151 | ||
![]() |
86c570a8c8 | ||
![]() |
fd1a76d3bc | ||
![]() |
266f6b58f4 | ||
![]() |
212ae87e8b | ||
![]() |
6038443225 | ||
![]() |
8ce27053ce | ||
![]() |
e2949094a2 | ||
![]() |
8095aea3d0 | ||
![]() |
2f6dc6f25f | ||
![]() |
d1b764d4ec | ||
![]() |
badf17b0ec | ||
![]() |
f4a203e416 | ||
![]() |
d4c3edca0a | ||
![]() |
c26c776b48 | ||
![]() |
c247f48fbe | ||
![]() |
da8c297973 | ||
![]() |
8be0261295 | ||
![]() |
991411f28d | ||
![]() |
c63c17d9d5 | ||
![]() |
4478f4e405 | ||
![]() |
660693281f | ||
![]() |
a4141288e5 | ||
![]() |
a107c81ad3 | ||
![]() |
ad448a51fa | ||
![]() |
affb89662f | ||
![]() |
2d2d00ccbf | ||
![]() |
da2ad191c9 | ||
![]() |
a52fdbcea5 | ||
![]() |
c16f65c5b8 | ||
![]() |
4afeb8ff38 | ||
![]() |
431266136c | ||
![]() |
e52699b211 | ||
![]() |
9ca81e3094 | ||
![]() |
c10b65ef3b | ||
![]() |
ac9488bad9 | ||
![]() |
3af2cbb678 | ||
![]() |
8c06ec9dcf | ||
![]() |
dc00730168 | ||
![]() |
eb35db1074 | ||
![]() |
890103e4ab | ||
![]() |
eebf1d9680 | ||
![]() |
44f46cfc75 | ||
![]() |
5abb1ab166 | ||
![]() |
92e4db287d | ||
![]() |
a378ec7ab4 | ||
![]() |
2ea263f997 | ||
![]() |
d473d95e42 | ||
![]() |
339fb3ddfb | ||
![]() |
ef500e2d96 | ||
![]() |
04c411c6c1 | ||
![]() |
04d797c6d3 | ||
![]() |
b49b9b99c4 | ||
![]() |
e88f2a70b1 | ||
![]() |
2d0fa7fe59 | ||
![]() |
5b7c52a63f | ||
![]() |
74377c1db7 | ||
![]() |
939797f5be | ||
![]() |
3a57f11f69 | ||
![]() |
bece1b245f | ||
![]() |
2e3d4582f1 | ||
![]() |
0069003cc7 | ||
![]() |
c6d352038c | ||
![]() |
25d26b5586 | ||
![]() |
11a0a76a9d | ||
![]() |
1252ef39d1 | ||
![]() |
bf54039724 | ||
![]() |
b82a3e72ff | ||
![]() |
d5b35a7b13 | ||
![]() |
deae0ae352 | ||
![]() |
8cd0b07c76 | ||
![]() |
db25eadedf | ||
![]() |
bfa4de5a1a | ||
![]() |
cf509cb3c5 | ||
![]() |
5f8cbe3fb1 | ||
![]() |
36496b64ac | ||
![]() |
7ce6cb5dc2 | ||
![]() |
5084722e0b | ||
![]() |
a3fd5e71f3 | ||
![]() |
fd0d3829d8 | ||
![]() |
7fcd277815 | ||
![]() |
050fa88e47 | ||
![]() |
24aaf298f7 | ||
![]() |
024ee7913e | ||
![]() |
f7785c95a3 | ||
![]() |
c584e7b8f5 | ||
![]() |
2483ec1ef6 | ||
![]() |
aba5ac4fa5 | ||
![]() |
4895c12b6d | ||
![]() |
b28a5f6655 | ||
![]() |
b440a232fc | ||
![]() |
a9dff5c39e | ||
![]() |
10f853c921 | ||
![]() |
b9786b1d2f | ||
![]() |
5bf1e24d02 | ||
![]() |
25347f9006 | ||
![]() |
a582600f0f | ||
![]() |
ea07d55b9e | ||
![]() |
613b9cd3bd | ||
![]() |
81cfa22dcf | ||
![]() |
2b7077642d | ||
![]() |
9e7153cf42 | ||
![]() |
06f6491d99 | ||
![]() |
ac2a6036eb | ||
![]() |
744e075697 | ||
![]() |
c0e8290bba | ||
![]() |
be71f31c07 | ||
![]() |
f4d25c0bfa | ||
![]() |
849067127f | ||
![]() |
3d92550c76 | ||
![]() |
cb2e4da2ae | ||
![]() |
42952a24e5 | ||
![]() |
9f685779d5 | ||
![]() |
e271ac2885 | ||
![]() |
0108de389b | ||
![]() |
2820dfb0c7 | ||
![]() |
548d4977f2 | ||
![]() |
79dd53e4c4 | ||
![]() |
15de4c4f5d | ||
![]() |
19442bfe8f | ||
![]() |
8e31ac0ed6 | ||
![]() |
6dd233d51f | ||
![]() |
24c792e4cf | ||
![]() |
9d682455f5 | ||
![]() |
e46135d1ec | ||
![]() |
80ebcb76fd | ||
![]() |
fcb32eaac4 | ||
![]() |
ff8dc8ce6d | ||
![]() |
4467e9fd5b | ||
![]() |
0384a45bf7 | ||
![]() |
57a2a12814 | ||
![]() |
f6f6de508b | ||
![]() |
25b026cf26 | ||
![]() |
91e6ef3612 | ||
![]() |
41c56be7da | ||
![]() |
7376c380e7 | ||
![]() |
8da13c35a7 | ||
![]() |
fc06811907 | ||
![]() |
063fb36567 | ||
![]() |
7a138804a8 | ||
![]() |
b54e5aa22d | ||
![]() |
0fdbc5fe68 | ||
![]() |
37d5fca1af | ||
![]() |
7ac7817101 | ||
![]() |
448309dc37 | ||
![]() |
64bffd534e | ||
![]() |
6b7ff075e4 | ||
![]() |
4cc8e26232 | ||
![]() |
baaa444f42 | ||
![]() |
0da96ea9ff | ||
![]() |
db0c61b369 | ||
![]() |
1756ae84ef | ||
![]() |
65d752484f | ||
![]() |
300a6c51a1 | ||
![]() |
58ef784a1e | ||
![]() |
c873d4888d | ||
![]() |
8bdb11e9e1 | ||
![]() |
0536ce1efa | ||
![]() |
6838e269b2 | ||
![]() |
db661c1229 | ||
![]() |
a07daddccc | ||
![]() |
e393fd6fdb | ||
![]() |
3347b67cea | ||
![]() |
999b6f44ad | ||
![]() |
135a4b421e | ||
![]() |
b7b57da78a | ||
![]() |
417cc7b5f8 | ||
![]() |
33923aee8b | ||
![]() |
febddfcac7 | ||
![]() |
a27f789a37 | ||
![]() |
e57b0107f4 | ||
![]() |
dc2b61b21d | ||
![]() |
148a7a6693 | ||
![]() |
0a9a5a95d0 | ||
![]() |
bcdf86d7ff | ||
![]() |
16e9b2d9bd | ||
![]() |
902cbdec3c | ||
![]() |
ac2f7bbccd | ||
![]() |
33c50671e6 | ||
![]() |
4d50f1eff2 | ||
![]() |
e892b0518a | ||
![]() |
04cf918f7a | ||
![]() |
17840f5c9c | ||
![]() |
a2ea8cd260 | ||
![]() |
323be545ab | ||
![]() |
b41d01b4ad | ||
![]() |
22c9a5e011 | ||
![]() |
b54774994b | ||
![]() |
028f56c289 | ||
![]() |
bde2e5f231 | ||
![]() |
0dac6bd576 | ||
![]() |
ce149998d8 | ||
![]() |
8070875937 | ||
![]() |
65d1c24adf | ||
![]() |
148e4a43db | ||
![]() |
46d5df86c2 | ||
![]() |
36372a511c | ||
![]() |
e523e35c73 | ||
![]() |
f3b1d0c4ef | ||
![]() |
b8e3f13816 | ||
![]() |
4aa2c05989 | ||
![]() |
96dc972637 | ||
![]() |
6fc93344a8 | ||
![]() |
89384492d1 | ||
![]() |
ad2448f2a0 | ||
![]() |
fb61ce21a5 | ||
![]() |
c779721d52 | ||
![]() |
840ed71188 | ||
![]() |
d83a2cab74 | ||
![]() |
6c671b1ba7 | ||
![]() |
1d55c1ebce | ||
![]() |
2f874686b6 | ||
![]() |
3d8700a6d2 | ||
![]() |
baeeee64d9 | ||
![]() |
cdc8cb0d74 | ||
![]() |
cafe173911 | ||
![]() |
d84b94f050 | ||
![]() |
dda7beea10 | ||
![]() |
77e67b637b | ||
![]() |
c2573f9043 | ||
![]() |
c7733ee382 | ||
![]() |
5aeeb77065 | ||
![]() |
e5ac647de4 | ||
![]() |
2d1cfdd567 | ||
![]() |
81520a3aab | ||
![]() |
10c300ff2b | ||
![]() |
76a59dee55 | ||
![]() |
6b7b5494f6 | ||
![]() |
2f9133fcee | ||
![]() |
e9e1e61540 | ||
![]() |
fc81fc38a1 | ||
![]() |
7749b2eb65 | ||
![]() |
e8a18fa72f | ||
![]() |
e831a7cfa5 | ||
![]() |
11c303bcaa | ||
![]() |
e426a306ab | ||
![]() |
7ad9a5c6a0 | ||
![]() |
931d2de4f1 | ||
![]() |
4446825458 | ||
![]() |
5cbb0362c0 | ||
![]() |
6adc93f043 | ||
![]() |
4a198e2f64 | ||
![]() |
164ec47337 | ||
![]() |
d97e4969f9 | ||
![]() |
720740de58 | ||
![]() |
b5bb0af0da | ||
![]() |
1cbb57e519 | ||
![]() |
29bdb0e91e | ||
![]() |
bf920874b1 | ||
![]() |
eab1004215 | ||
![]() |
57b6b1b7ee | ||
![]() |
c290b64285 | ||
![]() |
79a607ba1b | ||
![]() |
c87c4312ad | ||
![]() |
f440ffd405 | ||
![]() |
1d10ed86e6 | ||
![]() |
357fe04058 | ||
![]() |
501c8e66d9 | ||
![]() |
ff342bc932 | ||
![]() |
a101cfe437 | ||
![]() |
b3d7abbc3b | ||
![]() |
4e4da78e0b | ||
![]() |
c412ad0b23 | ||
![]() |
52a5023ef7 | ||
![]() |
552b87c29e | ||
![]() |
e5b8034e0f | ||
![]() |
3f6153a7cf | ||
![]() |
14f3a09c2c | ||
![]() |
84e7b5f06e | ||
![]() |
0e47ce22ce | ||
![]() |
4709a11fad | ||
![]() |
5b067094de | ||
![]() |
439fcf567c | ||
![]() |
97f44141b3 | ||
![]() |
333c8de2ad | ||
![]() |
b1697464d9 | ||
![]() |
4ca95aa1aa | ||
![]() |
d3633fc21d | ||
![]() |
5621bad05c | ||
![]() |
d732440e3e | ||
![]() |
b2188ea036 | ||
![]() |
0a6f1bd82a | ||
![]() |
d0db3b6a53 | ||
![]() |
0d86dc4119 | ||
![]() |
7e968d38bb | ||
![]() |
2c4aad8a23 | ||
![]() |
54f4d4979c | ||
![]() |
e2190444a8 | ||
![]() |
2b31ce12d9 | ||
![]() |
36b41156a5 | ||
![]() |
0f73bca593 | ||
![]() |
d647b35405 | ||
![]() |
047ed35f83 | ||
![]() |
72afc1ca0a | ||
![]() |
ba0d99b60d | ||
![]() |
f8cfc04ab2 | ||
![]() |
2d8a1da470 | ||
![]() |
0cf845f099 | ||
![]() |
f0398fe8fc | ||
![]() |
42b8b1f6ba | ||
![]() |
1a144c6459 | ||
![]() |
f7fbbc7bd0 | ||
![]() |
1b2f01af94 | ||
![]() |
27c150f877 | ||
![]() |
bdbd5ee8e4 | ||
![]() |
1b67b04630 | ||
![]() |
0c8d0c8143 | ||
![]() |
a45c578037 | ||
![]() |
d91c014b53 | ||
![]() |
81b352c306 | ||
![]() |
f309348e25 | ||
![]() |
977de65914 | ||
![]() |
d7939a59cc | ||
![]() |
cd92f0c4de | ||
![]() |
c4828f7ef4 | ||
![]() |
9705f308fd | ||
![]() |
a68108df07 | ||
![]() |
8a5089c0f6 | ||
![]() |
881f828f34 | ||
![]() |
5289efc72c | ||
![]() |
9ed4c582e2 | ||
![]() |
a7ccb5ff14 | ||
![]() |
80cd9ab9d6 | ||
![]() |
d0aab89435 | ||
![]() |
6d8224bdf1 | ||
![]() |
fb3ddf2da8 | ||
![]() |
80d9af6279 | ||
![]() |
0a914c820a | ||
![]() |
038af50eaa | ||
![]() |
9733fa6185 | ||
![]() |
6895ad66f6 | ||
![]() |
6b52141245 | ||
![]() |
0eb29caf44 | ||
![]() |
e1732ff26a | ||
![]() |
f8bc0be366 | ||
![]() |
d184a251e4 | ||
![]() |
84ddfb90a6 | ||
![]() |
83e5a35677 | ||
![]() |
5560f96744 | ||
![]() |
2660995aee | ||
![]() |
e3a6f53537 | ||
![]() |
2570eb99a6 | ||
![]() |
0ebdb2dbde | ||
![]() |
b660d9ce21 | ||
![]() |
ac603c3b9c | ||
![]() |
9a7827448d | ||
![]() |
a2a025ad55 | ||
![]() |
77d9997009 | ||
![]() |
402c8e80f4 | ||
![]() |
5551e8545f | ||
![]() |
a496526049 | ||
![]() |
03bab4e7db | ||
![]() |
63ded5b197 | ||
![]() |
57742d13f2 | ||
![]() |
1d9765c95b | ||
![]() |
64e7294233 | ||
![]() |
15e3971f11 | ||
![]() |
64cdd374bc | ||
![]() |
8d9ef6365e | ||
![]() |
bea3283b05 | ||
![]() |
8c5e871640 | ||
![]() |
7cb53a8b5a | ||
![]() |
24d14e7005 | ||
![]() |
e83b4af33f | ||
![]() |
8dfe1a37c4 | ||
![]() |
ef250fbc05 | ||
![]() |
e096daa605 | ||
![]() |
c519727b6c | ||
![]() |
78057dca3c | ||
![]() |
b18a61ebd1 | ||
![]() |
34acae025b | ||
![]() |
1037070bff | ||
![]() |
8ba4db33a7 | ||
![]() |
905ced9485 | ||
![]() |
0a4240f3db | ||
![]() |
be07c36294 | ||
![]() |
b950426d7b | ||
![]() |
1a590bd678 | ||
![]() |
b1a6a85ff7 | ||
![]() |
87bc11061d | ||
![]() |
c691ce38ed | ||
![]() |
ad08b24c3e | ||
![]() |
964467464c | ||
![]() |
311f8034f4 | ||
![]() |
dd8e1fccd7 | ||
![]() |
30ffd22599 | ||
![]() |
48ba900ea1 | ||
![]() |
45f1b3c311 | ||
![]() |
70040a0ed3 | ||
![]() |
7b3489225c | ||
![]() |
ba393020b2 | ||
![]() |
776bd6d7d6 | ||
![]() |
aab0e8b6db | ||
![]() |
80ff91b609 | ||
![]() |
ad35f9cc82 | ||
![]() |
c0e8a588ec | ||
![]() |
60f9c5ef29 | ||
![]() |
18f7f64b47 | ||
![]() |
427bb7c4b8 | ||
![]() |
1aec6dcd34 | ||
![]() |
7ca178fced | ||
![]() |
01b2d22059 | ||
![]() |
f621fbc38e | ||
![]() |
9fe8b3ab43 | ||
![]() |
4aed770b7e | ||
![]() |
9530bead9c | ||
![]() |
5ecd3054b3 | ||
![]() |
1af4788de0 | ||
![]() |
0697f0b5a0 | ||
![]() |
610a8cfabf | ||
![]() |
3250e25aa0 | ||
![]() |
ad0133b948 | ||
![]() |
3a2374ad59 | ||
![]() |
09e91bf817 | ||
![]() |
daa5c9ccc2 | ||
![]() |
abab37b71e | ||
![]() |
71bda91639 | ||
![]() |
c50975672e | ||
![]() |
fbdec17670 | ||
![]() |
ecec80ac58 | ||
![]() |
f1442edf34 | ||
![]() |
51426264d6 | ||
![]() |
58b9e430ad | ||
![]() |
628b7bc378 | ||
![]() |
6990a10d9c | ||
![]() |
131233e7d0 | ||
![]() |
80e9f245ab | ||
![]() |
4f8ce61909 | ||
![]() |
569265f1c7 | ||
![]() |
4acf17f636 | ||
![]() |
2864c0fa7f | ||
![]() |
6218def4df | ||
![]() |
b5a8929931 | ||
![]() |
639467fe27 | ||
![]() |
73ce7c6bfd | ||
![]() |
bc00fabb81 | ||
![]() |
64f5f61a0a | ||
![]() |
61471e80dc | ||
![]() |
fc4ba61e86 | ||
![]() |
358e8b95bb | ||
![]() |
f38af0836c | ||
![]() |
6b2f2f8484 | ||
![]() |
14bb252a73 | ||
![]() |
ef1e514c54 | ||
![]() |
f48e4c3942 | ||
![]() |
5644675b74 | ||
![]() |
c97d947087 | ||
![]() |
0069bdf5a4 | ||
![]() |
f2527a6363 | ||
![]() |
93022fa4f2 | ||
![]() |
8c93a75e04 | ||
![]() |
4f3b648bdd | ||
![]() |
8d407c39b8 | ||
![]() |
eb84abb470 | ||
![]() |
c8d46d73a3 | ||
![]() |
ee58d0fdb3 | ||
![]() |
b9894b5597 | ||
![]() |
b72562b0bf | ||
![]() |
721ed55598 | ||
![]() |
03f2ac16b5 | ||
![]() |
3ea9fb8c68 | ||
![]() |
408707157a | ||
![]() |
9ec94c889a | ||
![]() |
c6fdd3bdbc | ||
![]() |
fb14f24927 | ||
![]() |
7f8209544e | ||
![]() |
f21745305e | ||
![]() |
df87621241 | ||
![]() |
b369ce3a0c | ||
![]() |
d1b72fe443 | ||
![]() |
ade1dcd5e8 | ||
![]() |
ed433977c7 | ||
![]() |
8f8636c509 | ||
![]() |
b43be1f900 | ||
![]() |
3beee89f00 | ||
![]() |
7537d85084 | ||
![]() |
566c91a2b6 | ||
![]() |
2c1acab761 | ||
![]() |
a38f8df657 | ||
![]() |
e4997cd915 | ||
![]() |
0913f382d7 | ||
![]() |
21eab082bf | ||
![]() |
748b42cff0 | ||
![]() |
2bb307a462 | ||
![]() |
33c48f854c | ||
![]() |
1751c197b6 | ||
![]() |
27f26da0e5 | ||
![]() |
39d0bd7058 | ||
![]() |
8302c83001 | ||
![]() |
eaf5e97266 | ||
![]() |
68e13e7bcd | ||
![]() |
faaee59116 | ||
![]() |
349f283af6 | ||
![]() |
4b8363c9d1 | ||
![]() |
8934ebed1d | ||
![]() |
62efc4b2b9 | ||
![]() |
36aab19204 | ||
![]() |
502d9c5ae8 | ||
![]() |
b86efbc3c2 | ||
![]() |
2204c910b9 | ||
![]() |
a4a6689d38 | ||
![]() |
b9926a154c | ||
![]() |
023dafc4cb | ||
![]() |
2fac33c2e7 | ||
![]() |
02d18245c3 | ||
![]() |
130113db50 | ||
![]() |
036cdb0740 | ||
![]() |
5941d3f1bd | ||
![]() |
a4009d7ff3 | ||
![]() |
a554b8d1cf | ||
![]() |
1a5f809bdc | ||
![]() |
2677834aad | ||
![]() |
c73deb5974 | ||
![]() |
6204df19c0 | ||
![]() |
6d28aa272b | ||
![]() |
8e734d895f | ||
![]() |
2ae2b427b0 | ||
![]() |
6817c4568b | ||
![]() |
b74ba66a87 | ||
![]() |
e0c2ec36f8 | ||
![]() |
b008f28406 | ||
![]() |
5c34719ad1 | ||
![]() |
563a9adaf9 | ||
![]() |
18780b59b0 | ||
![]() |
76b6371498 | ||
![]() |
4b4f0a1608 | ||
![]() |
7a51fdf524 | ||
![]() |
75b4b4e2c2 | ||
![]() |
5a676220dc | ||
![]() |
0e46741d9a | ||
![]() |
aaa924d9d8 | ||
![]() |
fe0cbf048b | ||
![]() |
148535e7de | ||
![]() |
72ac41d632 | ||
![]() |
ca5357ea49 | ||
![]() |
4dd774f290 | ||
![]() |
c3ffe8d729 | ||
![]() |
47d1f3ea31 | ||
![]() |
633e233d9d | ||
![]() |
c1f3905672 | ||
![]() |
63e1344851 | ||
![]() |
3cd6c91eda | ||
![]() |
0422c3702d | ||
![]() |
44a3b4e7c6 | ||
![]() |
c48832a2bc | ||
![]() |
4089fee13c | ||
![]() |
b0d41fb490 | ||
![]() |
f04da64b21 | ||
![]() |
9c17803e03 | ||
![]() |
2cb8c4fc10 | ||
![]() |
5e1dcd4d30 | ||
![]() |
eb348848e6 | ||
![]() |
e66df67913 | ||
![]() |
05ebfd1dfd | ||
![]() |
61c4afd8aa | ||
![]() |
1ef1a6cf34 | ||
![]() |
068286744f | ||
![]() |
3d3203b823 | ||
![]() |
696aef6a6f | ||
![]() |
2ce0e67d4b | ||
![]() |
fc415aa6c6 | ||
![]() |
39f36e146e | ||
![]() |
f45084e01a | ||
![]() |
734f53d9fa | ||
![]() |
d093fafbe9 | ||
![]() |
0e72143945 | ||
![]() |
0ebae76732 | ||
![]() |
5541981d4e | ||
![]() |
9d7093e6f2 | ||
![]() |
ea0aab80e2 | ||
![]() |
ba8a603e26 | ||
![]() |
13a2c465ae | ||
![]() |
1920b4fd03 | ||
![]() |
93c53b93e2 | ||
![]() |
41fe000bf4 | ||
![]() |
7c1b1066f9 | ||
![]() |
055e865628 | ||
![]() |
e8c0d54b02 | ||
![]() |
c1ac7f035d | ||
![]() |
1d2a5f891e | ||
![]() |
912df73b56 | ||
![]() |
7a4dc61412 | ||
![]() |
dc0785fe2e | ||
![]() |
70a2a77cc0 | ||
![]() |
0fa228a749 | ||
![]() |
d910b116c6 | ||
![]() |
07206a0ac3 | ||
![]() |
0fac0ac948 | ||
![]() |
956b947a4c | ||
![]() |
a28ec17a6d | ||
![]() |
96926ab5fa | ||
![]() |
6d81b21b43 | ||
![]() |
cf0c6f19c4 | ||
![]() |
a1826e5c95 | ||
![]() |
8e791ed581 | ||
![]() |
614520443f | ||
![]() |
1c1ba2ef02 | ||
![]() |
93b751af29 | ||
![]() |
fbf1bc1ae2 | ||
![]() |
9d48917d09 | ||
![]() |
7f2d813c8c | ||
![]() |
1e995d7bba | ||
![]() |
80b76c9325 | ||
![]() |
b01fce2cc8 | ||
![]() |
4537651367 | ||
![]() |
9369fd8d05 | ||
![]() |
a3f1341daf | ||
![]() |
5c0a9d9300 | ||
![]() |
d044232cd0 | ||
![]() |
fdfb94371e | ||
![]() |
de1d8d67d8 | ||
![]() |
1c0b3d5780 | ||
![]() |
2bd9b081f5 | ||
![]() |
903427e1ef | ||
![]() |
35c59d4c72 | ||
![]() |
0088d88af6 | ||
![]() |
0649a2c0c9 | ||
![]() |
632f6e4218 | ||
![]() |
ead2d21a35 | ||
![]() |
d714b2c0be | ||
![]() |
fd400cf3ea | ||
![]() |
aed155d5a2 | ||
![]() |
b3a558d81c | ||
![]() |
f6b8ff9030 | ||
![]() |
f217289e9d | ||
![]() |
c56c7d2664 | ||
![]() |
c372efc3eb | ||
![]() |
9746641f48 | ||
![]() |
e45aa0bcde | ||
![]() |
a721d82e76 | ||
![]() |
5a3b936aff | ||
![]() |
2f3c6f9206 | ||
![]() |
708e3e0b96 | ||
![]() |
a2cb958b73 | ||
![]() |
e40a10dbdc | ||
![]() |
d568562cad | ||
![]() |
f492ead7d6 | ||
![]() |
c611487f42 | ||
![]() |
d0ac927b92 | ||
![]() |
54a0a96cbc | ||
![]() |
44316a5de1 | ||
![]() |
0db0abeb6a | ||
![]() |
906291234b | ||
![]() |
74af9642e1 | ||
![]() |
a828536e97 | ||
![]() |
10a46a3bde | ||
![]() |
bb37adc63d | ||
![]() |
1f3dd159b3 | ||
![]() |
2cd538e1eb | ||
![]() |
ffd60654b1 | ||
![]() |
fc17201f35 | ||
![]() |
d7d8c34167 | ||
![]() |
96db56a25c | ||
![]() |
785df48d7c | ||
![]() |
05f2fdb8a3 | ||
![]() |
3df467e35e | ||
![]() |
439d25f9b0 | ||
![]() |
3dfa3be2e1 | ||
![]() |
e9605b60d3 | ||
![]() |
9b036039c5 | ||
![]() |
110f23ca2f | ||
![]() |
56616d6bdf | ||
![]() |
7c621aeae2 | ||
![]() |
2f747d618c | ||
![]() |
7046539f29 | ||
![]() |
f87f6a37d4 | ||
![]() |
e43ead6832 | ||
![]() |
bf0c277ad6 | ||
![]() |
b6d7a92b32 | ||
![]() |
f8e4c52616 | ||
![]() |
6e1238206d | ||
![]() |
9e98f2799d | ||
![]() |
93915c7c5f | ||
![]() |
bf996f2fd1 | ||
![]() |
ef4ceac61e | ||
![]() |
3ff165f323 | ||
![]() |
a3f9e238bf | ||
![]() |
fbc0aa59c2 | ||
![]() |
d271b6672f | ||
![]() |
df7d4149c6 | ||
![]() |
46de1a5fac | ||
![]() |
64fdca5097 | ||
![]() |
4f36d2608f | ||
![]() |
6ded78bb5e | ||
![]() |
e4b46452e5 | ||
![]() |
3089d2bd1b | ||
![]() |
c97347a228 | ||
![]() |
1f0c11e8c3 | ||
![]() |
8db41809fd | ||
![]() |
e80b695dfa | ||
![]() |
c46f5c4f9b | ||
![]() |
7ec7537b7d | ||
![]() |
17bd41d3ef | ||
![]() |
6b9c7d6cf0 | ||
![]() |
a238994e0f | ||
![]() |
4940eec5ce | ||
![]() |
b1c41008ce | ||
![]() |
306b28bf93 | ||
![]() |
b4531e2c8f | ||
![]() |
9848785d38 | ||
![]() |
24ccdeac8f | ||
![]() |
f9ca9a76d9 | ||
![]() |
503326be04 | ||
![]() |
0cd9e6e343 | ||
![]() |
3b304bf769 | ||
![]() |
dbccd67b20 | ||
![]() |
d7640f1c20 | ||
![]() |
1675700073 | ||
![]() |
6b128ec798 | ||
![]() |
8e4ac8d503 | ||
![]() |
16b9e0e415 | ||
![]() |
9e67c94074 | ||
![]() |
c09e9de9f9 | ||
![]() |
a90083b287 | ||
![]() |
0ea80369cf | ||
![]() |
26821a3ac5 | ||
![]() |
7bc6fc988f | ||
![]() |
017822d422 | ||
![]() |
de3eb90ab5 | ||
![]() |
3ce44418e7 | ||
![]() |
8b8400f437 | ||
![]() |
20e76c3076 | ||
![]() |
6d1d5d0afe | ||
![]() |
26bce2e8a0 | ||
![]() |
2f2c702c8f | ||
![]() |
c5a3d75d29 | ||
![]() |
4b36fc33b1 | ||
![]() |
8aa9842091 | ||
![]() |
d718b81d79 | ||
![]() |
83a37a88f9 | ||
![]() |
0ced8cf27c | ||
![]() |
e6b764bd25 | ||
![]() |
78860ce01a | ||
![]() |
5cdd2d927c | ||
![]() |
4a3dbd8f3f | ||
![]() |
ee4ee92340 | ||
![]() |
d3d25164c3 | ||
![]() |
5de0e8ad7d | ||
![]() |
397b732341 | ||
![]() |
dd7abf2fff | ||
![]() |
fdd3187c82 | ||
![]() |
884158c27e | ||
![]() |
223a000c91 | ||
![]() |
1c31d46d58 | ||
![]() |
51a7cbaffa | ||
![]() |
459b6917b5 | ||
![]() |
96369ab604 | ||
![]() |
69f061c671 | ||
![]() |
cb38352519 | ||
![]() |
ab9cfb1469 | ||
![]() |
760780537a | ||
![]() |
ec5ef85023 | ||
![]() |
ecd30eb896 | ||
![]() |
5cf79312f4 | ||
![]() |
31f32ca8da | ||
![]() |
36aae7cad1 | ||
![]() |
f664ba5e5f | ||
![]() |
103407634d | ||
![]() |
9a4111c393 | ||
![]() |
dfde53b8ac | ||
![]() |
abee98b68d | ||
![]() |
14b1727122 | ||
![]() |
a6ff79e5cd | ||
![]() |
55ff01a618 | ||
![]() |
a10a6bf9e6 | ||
![]() |
2ab019008f | ||
![]() |
5358d1be96 | ||
![]() |
2263f884f0 | ||
![]() |
e56eaa7a9f | ||
![]() |
08b46b9a8b | ||
![]() |
de54ec5c61 | ||
![]() |
c397ba40f4 | ||
![]() |
83ffee2874 | ||
![]() |
570239cccf | ||
![]() |
3e8ba74459 | ||
![]() |
ec72489300 | ||
![]() |
16607a68d2 | ||
![]() |
5b571a0cb8 | ||
![]() |
51e5f620bf | ||
![]() |
779ace09bf | ||
![]() |
1d73115c33 | ||
![]() |
904b7d22d5 | ||
![]() |
13ff92899c | ||
![]() |
405b3d47e4 | ||
![]() |
a40abd4bbd | ||
![]() |
4f4cdea951 | ||
![]() |
7eec11197a | ||
![]() |
165c9491a9 | ||
![]() |
1ff9718039 | ||
![]() |
91d6f64520 | ||
![]() |
a5eda46339 | ||
![]() |
cc52b81eb2 | ||
![]() |
7bbea38454 | ||
![]() |
f3e18f63bb | ||
![]() |
6d4291c564 | ||
![]() |
66ead26635 | ||
![]() |
18d67e0f26 | ||
![]() |
dee39dd14a | ||
![]() |
fc345f4ebc | ||
![]() |
46231c9e56 | ||
![]() |
73ca19ca73 | ||
![]() |
0bcf220d91 | ||
![]() |
e5818e2f04 | ||
![]() |
ddc4b099ee | ||
![]() |
493f29ff57 | ||
![]() |
15d5c1ce16 | ||
![]() |
e34b46d4e4 | ||
![]() |
509911aae0 | ||
![]() |
a9fdd82911 | ||
![]() |
4d130df390 | ||
![]() |
284a316cd5 | ||
![]() |
fc4537bcea | ||
![]() |
0efa8d0217 | ||
![]() |
2c988b0f77 | ||
![]() |
71b7b3c570 | ||
![]() |
f2755468e0 | ||
![]() |
506ca60682 | ||
![]() |
d71906838d | ||
![]() |
e1c8966d9f | ||
![]() |
1d5f635f24 | ||
![]() |
74ec643208 | ||
![]() |
a7b2ba96e7 | ||
![]() |
0f53693c28 | ||
![]() |
5b82ab8690 | ||
![]() |
68902d74bd | ||
![]() |
c86dc4035b | ||
![]() |
fea9932631 | ||
![]() |
9a5a75c1c2 | ||
![]() |
0739997a59 | ||
![]() |
fe6141ea81 | ||
![]() |
5233481992 | ||
![]() |
239e94fe05 | ||
![]() |
74558aea20 | ||
![]() |
e04ffa5f25 | ||
![]() |
c513e8cd33 | ||
![]() |
e5c51682fb | ||
![]() |
45eb778560 | ||
![]() |
930ca1fbc0 | ||
![]() |
403fd035a7 | ||
![]() |
656861f6df | ||
![]() |
0d927881b4 | ||
![]() |
da025dafbb | ||
![]() |
fa1634d040 | ||
![]() |
de8285e199 | ||
![]() |
152db6995f | ||
![]() |
6fb7c8e91f | ||
![]() |
52d38aa1e7 | ||
![]() |
9d6b2cc3e1 | ||
![]() |
06053b9e17 | ||
![]() |
897b27f31e | ||
![]() |
98782fed57 | ||
![]() |
e086afc4d8 | ||
![]() |
f6c1f22b4f | ||
![]() |
745a763b7f | ||
![]() |
7517540cc5 | ||
![]() |
0a212bab1d | ||
![]() |
2ed6488d77 | ||
![]() |
e757ee34df | ||
![]() |
a3d18884aa | ||
![]() |
154a0b30fe | ||
![]() |
e06c1647c4 | ||
![]() |
3136db5cb4 | ||
![]() |
b7ccc8c0e6 | ||
![]() |
4dd6fab12f | ||
![]() |
bc23d64686 | ||
![]() |
c0865b1bea | ||
![]() |
eb0a61c25b | ||
![]() |
99eb1462ad | ||
![]() |
e9c6729894 | ||
![]() |
bb3d846cad | ||
![]() |
30a371d4d5 | ||
![]() |
9befd818d5 | ||
![]() |
8ca4796295 | ||
![]() |
36da81d003 | ||
![]() |
12c7721ef5 | ||
![]() |
450ffce0e2 | ||
![]() |
9a99d5eb47 | ||
![]() |
21b2b4eec3 | ||
![]() |
1b50a3583c | ||
![]() |
b069ce4b0d | ||
![]() |
329b45e895 | ||
![]() |
70f41df08a | ||
![]() |
2c6729a7f4 | ||
![]() |
13334e3b4d | ||
![]() |
abfcee0e48 | ||
![]() |
0b087e2f7b | ||
![]() |
652a9ef7a3 | ||
![]() |
78b9bf2525 | ||
![]() |
5214feba00 | ||
![]() |
036b926b28 | ||
![]() |
3788aa9399 | ||
![]() |
ea9d3ee0ff | ||
![]() |
914e5677b8 | ||
![]() |
b1916d6043 | ||
![]() |
e3b35adb1c | ||
![]() |
ba9921919f | ||
![]() |
12e11e78c1 | ||
![]() |
f53b826307 | ||
![]() |
d1d62be6f2 | ||
![]() |
fe965049f9 | ||
![]() |
dbbe33f965 | ||
![]() |
dfaa795b8f | ||
![]() |
ac8783dbd6 | ||
![]() |
b5c320a1b2 | ||
![]() |
a99a421767 | ||
![]() |
b54a5a5ccc | ||
![]() |
891812ba36 | ||
![]() |
ea11ed5ac6 | ||
![]() |
ae31b121d8 | ||
![]() |
4ae10a2b26 | ||
![]() |
ac2d010a57 | ||
![]() |
48a0187c0d | ||
![]() |
516ede704b | ||
![]() |
df43d580a7 | ||
![]() |
bad2662081 | ||
![]() |
26eebdab98 | ||
![]() |
55d8e9257e | ||
![]() |
f882d16313 | ||
![]() |
827e27bed9 | ||
![]() |
8e9a6e5c8e | ||
![]() |
302023e14e | ||
![]() |
0c6fdf826f | ||
![]() |
9bc4128e3a | ||
![]() |
8dd2a6a97c | ||
![]() |
aa40c78127 | ||
![]() |
79eaf8c56e | ||
![]() |
879bbf63b5 | ||
![]() |
a1a292846d | ||
![]() |
f2d93d2f18 | ||
![]() |
cd93987919 | ||
![]() |
2c2b501c73 | ||
![]() |
9dea85730d | ||
![]() |
d9d7d9bff7 | ||
![]() |
2203f50dea | ||
![]() |
08843950dd | ||
![]() |
b86987dc48 | ||
![]() |
2bdfc55879 | ||
![]() |
9abecc6232 | ||
![]() |
1171637e42 | ||
![]() |
f90a1b97b4 | ||
![]() |
8b436905b8 | ||
![]() |
4fbbe39e79 | ||
![]() |
73529a0c1f | ||
![]() |
b54070d7b3 | ||
![]() |
1f7c247b94 | ||
![]() |
0dd7d03850 | ||
![]() |
d673b9c8ec | ||
![]() |
3944906de4 | ||
![]() |
c63c78e086 | ||
![]() |
ad76a7b12d | ||
![]() |
91a121536c | ||
![]() |
7401dc0925 | ||
![]() |
68e0688e63 | ||
![]() |
a5d02d19ff | ||
![]() |
b677986c68 | ||
![]() |
9379b73bdf | ||
![]() |
da801cc72f | ||
![]() |
e1b15b7376 | ||
![]() |
3759b7b327 | ||
![]() |
8e46b21475 | ||
![]() |
b933981859 | ||
![]() |
519b7ca7b0 | ||
![]() |
9c73342176 | ||
![]() |
4b3d88ecb0 | ||
![]() |
9faee2c71a | ||
![]() |
878d3b29fb | ||
![]() |
0fed8c9923 | ||
![]() |
46d2415c2d | ||
![]() |
f753e6cb83 | ||
![]() |
97a35bb43b | ||
![]() |
15113ee2b1 | ||
![]() |
02164ab2a7 | ||
![]() |
86ff430169 | ||
![]() |
55c48bf8b0 | ||
![]() |
09dbe74662 | ||
![]() |
e29927b478 | ||
![]() |
2b8f1e4a92 | ||
![]() |
5e4199b736 | ||
![]() |
12a2ae0728 | ||
![]() |
c7c5fbb4ef | ||
![]() |
2beb87f86f | ||
![]() |
7eeb687385 | ||
![]() |
8688ee05ba | ||
![]() |
df9d9c4cef | ||
![]() |
58ddf5c9ae | ||
![]() |
291bff50da | ||
![]() |
62871dc80e | ||
![]() |
287b718b17 | ||
![]() |
9aec9f4df4 | ||
![]() |
299aafea87 | ||
![]() |
a42271dcf9 | ||
![]() |
7e1818d875 | ||
![]() |
c5448080bf | ||
![]() |
7dfa493f44 | ||
![]() |
134f2b76bb | ||
![]() |
eec997a0ac | ||
![]() |
b5d286b86e | ||
![]() |
6c3f3119ff | ||
![]() |
53defaaeea | ||
![]() |
f105afad17 | ||
![]() |
a821547838 | ||
![]() |
b6a56e23f6 | ||
![]() |
6efc01e90a | ||
![]() |
227dbbd8e4 | ||
![]() |
362510c468 | ||
![]() |
ffac946235 | ||
![]() |
b986f2eec9 | ||
![]() |
e05eb7396e | ||
![]() |
539d0106e2 | ||
![]() |
5a67f85ce1 | ||
![]() |
e0ece997f8 | ||
![]() |
2ee4acc994 | ||
![]() |
a9e9aaef36 | ||
![]() |
5cf5714640 | ||
![]() |
e1fe601269 | ||
![]() |
e11893c0ef | ||
![]() |
5d4faadc88 | ||
![]() |
3ed36188fa | ||
![]() |
7ec1e5f6bb | ||
![]() |
c1a63d331a | ||
![]() |
1d9c7fd40e | ||
![]() |
2c2e1aefa2 | ||
![]() |
fee7fedbf7 | ||
![]() |
8dcde52099 | ||
![]() |
d650f2a10a | ||
![]() |
1aa1800ea2 | ||
![]() |
6faedb59a8 | ||
![]() |
08d5f15679 | ||
![]() |
72200596de | ||
![]() |
dd64b5955a | ||
![]() |
7e2b717d0a | ||
![]() |
f924686871 | ||
![]() |
812504dcb2 | ||
![]() |
c1cd9755e4 | ||
![]() |
e1629f3a16 | ||
![]() |
68b0977bae | ||
![]() |
3021601c52 | ||
![]() |
9b8148f2e7 | ||
![]() |
4ffff09381 | ||
![]() |
fe76718780 | ||
![]() |
a65c96138e | ||
![]() |
39db5423df | ||
![]() |
df1404b3ae | ||
![]() |
66f5b1df43 | ||
![]() |
b3b91f189f | ||
![]() |
bfd11c5d4d | ||
![]() |
520be40504 | ||
![]() |
5f3c06a7a0 | ||
![]() |
efe54b5e35 | ||
![]() |
96a566290d | ||
![]() |
210f4936df | ||
![]() |
051b59504a | ||
![]() |
710b9bda49 | ||
![]() |
410eb58c8a | ||
![]() |
ce2f3c99d9 | ||
![]() |
9a531dab2f | ||
![]() |
7d79d2330f | ||
![]() |
77af2a1250 | ||
![]() |
b201c1aaad | ||
![]() |
cec7983a4c | ||
![]() |
dce6ec6c54 | ||
![]() |
051ea2ca28 | ||
![]() |
f6aebb61b4 | ||
![]() |
dc6489767f | ||
![]() |
654a04810a | ||
![]() |
1c84837611 | ||
![]() |
5f86eb225f | ||
![]() |
3be45d4692 | ||
![]() |
0544bb9d12 | ||
![]() |
ab520e625a | ||
![]() |
2b7b67b894 | ||
![]() |
9226da5b76 | ||
![]() |
6b9cf29e95 | ||
![]() |
9c6b78863e | ||
![]() |
a395b17218 | ||
![]() |
024b5a514c | ||
![]() |
33bde312e8 | ||
![]() |
ec73da976e | ||
![]() |
e22c42f560 | ||
![]() |
47e9672b32 | ||
![]() |
995a5125bc | ||
![]() |
32c5fe8bf1 | ||
![]() |
58786672fd | ||
![]() |
3e2f96fb11 | ||
![]() |
9dc9ebfd2e | ||
![]() |
8a7333cef3 | ||
![]() |
32b89794cb | ||
![]() |
ecfa6552a9 | ||
![]() |
aefc605ecb | ||
![]() |
cab231b647 | ||
![]() |
0421316002 | ||
![]() |
cd4b9460b1 | ||
![]() |
9093a69907 | ||
![]() |
f3a5c00be8 | ||
![]() |
9927f7add3 | ||
![]() |
f8768a7740 | ||
![]() |
f3fda2b696 | ||
![]() |
7592a6caa4 | ||
![]() |
3302298f45 | ||
![]() |
815d7a39cd | ||
![]() |
546ce64707 | ||
![]() |
97907603f5 | ||
![]() |
b7a3b6a139 | ||
![]() |
eafc2dbee1 | ||
![]() |
5879baeff5 | ||
![]() |
8805c7e081 | ||
![]() |
a93b5adbb8 | ||
![]() |
e03be0f7d6 | ||
![]() |
bff59d3424 | ||
![]() |
50801f7afb | ||
![]() |
e43b6ea715 | ||
![]() |
b77644d1d4 | ||
![]() |
a25620bf9e | ||
![]() |
4a7fcd57b3 | ||
![]() |
17070adcb6 | ||
![]() |
71b9e482c1 | ||
![]() |
2471e4bca3 | ||
![]() |
5ff5c8a464 | ||
![]() |
94e9355e7c | ||
![]() |
adc4f5c710 | ||
![]() |
ec0c4ad781 | ||
![]() |
39c298037d | ||
![]() |
dffdf7486f | ||
![]() |
505d69ef73 | ||
![]() |
b4581b1995 | ||
![]() |
a01438a879 | ||
![]() |
0d67f12991 | ||
![]() |
90d77e4c67 | ||
![]() |
553d3afccc | ||
![]() |
5eb4502aa5 | ||
![]() |
7303fd6a01 | ||
![]() |
378d3da0f0 | ||
![]() |
742774fbfe | ||
![]() |
ea3b90d6e3 | ||
![]() |
a977589f51 | ||
![]() |
62700862e4 | ||
![]() |
d825499651 | ||
![]() |
2cbf862434 | ||
![]() |
9d709cd42a | ||
![]() |
755099c5ee | ||
![]() |
1869e82b45 | ||
![]() |
080770768d | ||
![]() |
2ca2cc01d0 | ||
![]() |
e75c0a6223 | ||
![]() |
c52d38e29f | ||
![]() |
5856dcb415 | ||
![]() |
84645dae93 | ||
![]() |
ff9532d503 | ||
![]() |
5be4c5e9e7 | ||
![]() |
1b834c16ce | ||
![]() |
f576a9074a | ||
![]() |
0234d7a52a | ||
![]() |
d0205e0468 | ||
![]() |
6ad4b1976b | ||
![]() |
19332bf1d5 | ||
![]() |
b0852c2fac | ||
![]() |
150821cbb3 | ||
![]() |
3726be64b7 | ||
![]() |
429eafd8e4 | ||
![]() |
e1ea156a41 | ||
![]() |
d5a4b288f7 | ||
![]() |
7e4ae2460a | ||
![]() |
e7605e0c0e | ||
![]() |
0cb8147abb | ||
![]() |
3afb53939a | ||
![]() |
18e6053399 | ||
![]() |
1bb266d23a | ||
![]() |
d8191c8232 | ||
![]() |
1ce4b05932 | ||
![]() |
bc7bac1218 | ||
![]() |
3148b3e434 | ||
![]() |
9847a05301 | ||
![]() |
d9850d19d5 | ||
![]() |
f69f999118 | ||
![]() |
a851a2bee4 | ||
![]() |
5dddae4f00 | ||
![]() |
2fc5fc6992 | ||
![]() |
5bf6088453 | ||
![]() |
b71c9e4504 | ||
![]() |
712db49274 | ||
![]() |
1e2a823ef2 | ||
![]() |
9792daf669 | ||
![]() |
06c14d4fc5 | ||
![]() |
27ace10fe9 | ||
![]() |
6b15463657 | ||
![]() |
3eee7b6de2 | ||
![]() |
61bd1312a6 | ||
![]() |
e8757347ba | ||
![]() |
a140c19483 | ||
![]() |
66cd7b757a | ||
![]() |
818f6937ef | ||
![]() |
77eec1e07f | ||
![]() |
1f426fa722 | ||
![]() |
a446999285 | ||
![]() |
eb4e788b5c | ||
![]() |
36d40f65a3 | ||
![]() |
11b23ffc85 | ||
![]() |
90d34f3aa4 | ||
![]() |
0daa08b401 | ||
![]() |
33bd617a4f | ||
![]() |
4367a1a292 | ||
![]() |
39a10c131a | ||
![]() |
6d9f3b644c | ||
![]() |
0bc0a1bdb7 | ||
![]() |
48ce88490d | ||
![]() |
648b44b298 | ||
![]() |
b3d2b89734 | ||
![]() |
5fd7e2ca50 | ||
![]() |
388d51278d | ||
![]() |
d77565a04a | ||
![]() |
7303cfccf3 | ||
![]() |
be1ea0acde | ||
![]() |
4e53a06e75 | ||
![]() |
1f1287554f | ||
![]() |
fb24c4d8ee | ||
![]() |
329a0df7fb | ||
![]() |
3849df921c | ||
![]() |
3541534b76 | ||
![]() |
449ad684ad | ||
![]() |
e8c2db5488 | ||
![]() |
5bcd1d9c27 | ||
![]() |
5fd2a3923f | ||
![]() |
6dfa5de478 | ||
![]() |
1e52809e06 | ||
![]() |
ffec54ffe2 | ||
![]() |
8f2cc5bd4d | ||
![]() |
1e01aeb146 | ||
![]() |
0a8ad1e468 | ||
![]() |
5da4d88ad9 | ||
![]() |
1127d11a0e | ||
![]() |
086d4be6ab | ||
![]() |
f69d7058ba | ||
![]() |
d285aab97c | ||
![]() |
7578382bee | ||
![]() |
8d876d8d03 | ||
![]() |
df2b484a66 | ||
![]() |
15fd9c32e4 | ||
![]() |
33f8b21edd | ||
![]() |
f4f34d2852 | ||
![]() |
da3e99dd90 | ||
![]() |
7713169721 | ||
![]() |
28825fd22b | ||
![]() |
53b7bbe5a8 | ||
![]() |
be7d5a41cd | ||
![]() |
2b57300f65 | ||
![]() |
9d03364a23 | ||
![]() |
be23d97293 | ||
![]() |
eac0083bea | ||
![]() |
84bd092bae | ||
![]() |
cb0956405f | ||
![]() |
8f9b44d4f0 | ||
![]() |
1cc99d02e1 | ||
![]() |
ccc962d9f2 | ||
![]() |
41ec896dfc | ||
![]() |
0259584c25 | ||
![]() |
91f2730785 | ||
![]() |
9fc95cdd04 | ||
![]() |
15e6b039b9 | ||
![]() |
cef60ccb64 | ||
![]() |
b46f72a409 | ||
![]() |
cdfe353b94 | ||
![]() |
80b586ba60 | ||
![]() |
e8537a93b2 | ||
![]() |
5b0f4d3ae8 | ||
![]() |
75a909df64 | ||
![]() |
33b9b8d1b0 | ||
![]() |
9db515d44a | ||
![]() |
090f15e738 | ||
![]() |
ae22060fcb | ||
![]() |
da5e79ebeb | ||
![]() |
e426f4fae9 | ||
![]() |
571d132d70 | ||
![]() |
143df9ddca | ||
![]() |
e59e4c343f | ||
![]() |
9cf6769da5 | ||
![]() |
3097683e1c | ||
![]() |
22704a2158 | ||
![]() |
b08e12560f | ||
![]() |
4891b70ec3 | ||
![]() |
2afb1c6ba3 | ||
![]() |
7c31d5bbe8 | ||
![]() |
88c51209af | ||
![]() |
856d106f8b | ||
![]() |
ec300756aa | ||
![]() |
da0fcb4602 | ||
![]() |
a51bcb483a | ||
![]() |
00370ee41e | ||
![]() |
2e60f4e600 | ||
![]() |
301178cc81 | ||
![]() |
405f4ca5e4 | ||
![]() |
67d4a7b3b0 | ||
![]() |
5e37ff8b8b | ||
![]() |
63b4b3aa9c | ||
![]() |
a6569e0dbd | ||
![]() |
24d7ad310b | ||
![]() |
5627172087 | ||
![]() |
04b0387fca | ||
![]() |
35cd8eb19a | ||
![]() |
91a229453c | ||
![]() |
776a2b34d9 | ||
![]() |
b31ca20d92 | ||
![]() |
0c9936dce2 | ||
![]() |
738fa2f7b0 | ||
![]() |
ccd1f24104 | ||
![]() |
c4a0a019be | ||
![]() |
f2833d520f | ||
![]() |
40583ed7a3 | ||
![]() |
9625f1d316 | ||
![]() |
59982ecf38 | ||
![]() |
861f1e213f | ||
![]() |
9114ee25a9 | ||
![]() |
f129c36f5e | ||
![]() |
b9a9866e5e | ||
![]() |
388adda383 | ||
![]() |
8639a75081 | ||
![]() |
4361c61f6e | ||
![]() |
8475aa46e8 | ||
![]() |
c37e9cded7 | ||
![]() |
9f211c4da0 | ||
![]() |
639022fa55 | ||
![]() |
218509b786 | ||
![]() |
41f4a830b2 | ||
![]() |
194135791a | ||
![]() |
3f5bf0e5a5 | ||
![]() |
75e03c15b7 | ||
![]() |
fe57328e3d | ||
![]() |
ec450e4070 | ||
![]() |
cca4fadc42 | ||
![]() |
e4b543c302 | ||
![]() |
dab85057da | ||
![]() |
f98f7e435a | ||
![]() |
099d278cbc | ||
![]() |
f905c58877 | ||
![]() |
5eba864f22 | ||
![]() |
8e8953999d | ||
![]() |
09120f06db | ||
![]() |
347b434103 | ||
![]() |
af8c11fd86 | ||
![]() |
18c494dd55 | ||
![]() |
5322ec52bf | ||
![]() |
c3d6a52413 | ||
![]() |
ce4e69bdd3 | ||
![]() |
c5fb52a4a7 | ||
![]() |
f20f7918a1 | ||
![]() |
17e9e73617 | ||
![]() |
40911b01e6 | ||
![]() |
840f110317 | ||
![]() |
0376926dfc | ||
![]() |
0ccd0c6301 | ||
![]() |
0517ba687d | ||
![]() |
151d0e6fcb | ||
![]() |
65b410416c | ||
![]() |
420d40c848 | ||
![]() |
ad3e597ad2 | ||
![]() |
3130350f58 | ||
![]() |
5aaa9f8856 | ||
![]() |
0d76ce4166 | ||
![]() |
47d5404f38 | ||
![]() |
24387200bc | ||
![]() |
295c5ba2bc | ||
![]() |
27d1de25a1 | ||
![]() |
e5b2f8c8f2 | ||
![]() |
f3681953ed | ||
![]() |
0b44f7afb8 | ||
![]() |
ab0a110a8e | ||
![]() |
8e3b153f98 | ||
![]() |
9d727a211e | ||
![]() |
bd2d0a8953 | ||
![]() |
5f0faed078 | ||
![]() |
adcd5803b0 | ||
![]() |
123095f624 | ||
![]() |
ecd5b4b23c | ||
![]() |
135b6ecbba | ||
![]() |
3cd4513fe4 | ||
![]() |
466cb12c94 | ||
![]() |
4319ee18e1 | ||
![]() |
8712216948 | ||
![]() |
8d8d32fcc9 | ||
![]() |
95e7d0ee80 | ||
![]() |
f18c4d0f3a | ||
![]() |
1123071952 | ||
![]() |
44d42c5035 | ||
![]() |
0f6a486fe7 | ||
![]() |
e051714b40 | ||
![]() |
1e30fcfac3 | ||
![]() |
75b160f161 | ||
![]() |
cc0e2030f0 | ||
![]() |
b857eaf426 | ||
![]() |
ff52d7e6f1 | ||
![]() |
7106d4dd8b | ||
![]() |
146e7afa5b | ||
![]() |
24b7c2b6b4 | ||
![]() |
59b97befed | ||
![]() |
65302765f0 | ||
![]() |
f57a91e481 | ||
![]() |
bddb7ccce4 | ||
![]() |
a61a2300ab | ||
![]() |
eb1d12ec83 | ||
![]() |
75657c5782 | ||
![]() |
5108b811a1 | ||
![]() |
fd6b048d19 | ||
![]() |
b4fdfb5104 | ||
![]() |
940af07b78 | ||
![]() |
a5d65313a0 | ||
![]() |
93a483a4b3 | ||
![]() |
c1693a768f | ||
![]() |
ee28117276 | ||
![]() |
ec431bd257 | ||
![]() |
fd61f09654 | ||
![]() |
b2e51060f3 | ||
![]() |
6cf1966967 | ||
![]() |
e498fc7959 | ||
![]() |
1534d1451c | ||
![]() |
08685481a0 | ||
![]() |
abac2fd97a | ||
![]() |
54beca1501 | ||
![]() |
c157ddf08c | ||
![]() |
ef2b50863c | ||
![]() |
f7dbe77838 | ||
![]() |
985b53c768 | ||
![]() |
d3f8e295be | ||
![]() |
2a4698c982 | ||
![]() |
d87d6eeeb0 | ||
![]() |
5d41cc633c | ||
![]() |
787480647d | ||
![]() |
c9fd314ba4 | ||
![]() |
bbece26213 | ||
![]() |
46932f458c | ||
![]() |
4c26a0fc3a | ||
![]() |
eb151b658d | ||
![]() |
cd6678f12b | ||
![]() |
4b935bcd9b | ||
![]() |
06054bdbf1 | ||
![]() |
2b2d256081 | ||
![]() |
33e2e12086 | ||
![]() |
2b9dbea855 | ||
![]() |
e7540a3507 | ||
![]() |
9b3e3ec48e | ||
![]() |
52e6561f51 | ||
![]() |
dcf03287f8 | ||
![]() |
2f11e9fa0e | ||
![]() |
3cdaa4b9a6 | ||
![]() |
9b191bc89a | ||
![]() |
1a2595aaf2 | ||
![]() |
3b5ef9f55e | ||
![]() |
37a37c97b9 | ||
![]() |
3a6e86d7ad | ||
![]() |
6a416f4087 | ||
![]() |
8f3131a7eb | ||
![]() |
0f9da5c40e | ||
![]() |
04edd2cd7f | ||
![]() |
af1e357df4 | ||
![]() |
669623b7cd | ||
![]() |
b7dad66a26 | ||
![]() |
5420bd846b | ||
![]() |
77f67d64e1 | ||
![]() |
2c03cbbbf3 | ||
![]() |
1a1a08768a | ||
![]() |
a628d12361 | ||
![]() |
3923a38a17 | ||
![]() |
a30f1a6a77 | ||
![]() |
6493de52b9 | ||
![]() |
ce092b688e | ||
![]() |
f5fb2322b1 | ||
![]() |
a4cb96a6a4 | ||
![]() |
c1670dcbe2 | ||
![]() |
ce647263f1 | ||
![]() |
e40a8437a3 | ||
![]() |
1ba57f4d5d | ||
![]() |
9b0c390511 | ||
![]() |
732a1e3d3b | ||
![]() |
d8267316ef | ||
![]() |
b51f240061 | ||
![]() |
5e0f126693 | ||
![]() |
de2a22834d | ||
![]() |
69c7ddde55 | ||
![]() |
e99653076d | ||
![]() |
5e063ac24a | ||
![]() |
6fc43fbb81 | ||
![]() |
1a9f2d47ee | ||
![]() |
32a78a7872 | ||
![]() |
b24a9b56b1 | ||
![]() |
541e6a1798 | ||
![]() |
66de9992f8 | ||
![]() |
7d0162afff | ||
![]() |
c1f642084d | ||
![]() |
cd3b468ada | ||
![]() |
588ea41fb7 | ||
![]() |
1dada50559 | ||
![]() |
49beccf849 | ||
![]() |
a6ca9bb9c8 | ||
![]() |
234e84010c | ||
![]() |
a881e60c81 | ||
![]() |
8151122b80 | ||
![]() |
8a2dd076a4 | ||
![]() |
0e2e6a60db | ||
![]() |
b31d26b912 | ||
![]() |
3a567a35da | ||
![]() |
841ebc3f21 | ||
![]() |
a9acaeaebd | ||
![]() |
270ae138bf | ||
![]() |
677a820638 | ||
![]() |
d8342f4489 | ||
![]() |
aad0b5e372 | ||
![]() |
f17f926a3b | ||
![]() |
b1f450a3e1 | ||
![]() |
b8a25062b5 | ||
![]() |
36254c9e03 | ||
![]() |
7ceba078e2 | ||
![]() |
6134f97a97 | ||
![]() |
0f298bd061 | ||
![]() |
5d30261770 | ||
![]() |
e667d95ca8 | ||
![]() |
3a33c2be04 | ||
![]() |
74328ada86 | ||
![]() |
5892bd83aa | ||
![]() |
0098b07670 | ||
![]() |
11dcaac44c | ||
![]() |
38c18366a3 | ||
![]() |
58b58aa15f | ||
![]() |
23dace4257 | ||
![]() |
b376a8fa54 | ||
![]() |
cc230df8c7 | ||
![]() |
68cdf34cc3 | ||
![]() |
6bae0f47bf | ||
![]() |
96a339a8d4 | ||
![]() |
65be2ebcb5 | ||
![]() |
223b20b598 | ||
![]() |
fad99b8d03 | ||
![]() |
3d918414b8 | ||
![]() |
ee06dc634b | ||
![]() |
9c38316483 | ||
![]() |
8cd703940f | ||
![]() |
8f977f8923 | ||
![]() |
d82108de83 | ||
![]() |
88037de55e | ||
![]() |
4fa870e34e | ||
![]() |
53c8ca9672 | ||
![]() |
b56ac4164a | ||
![]() |
6cea1256ce | ||
![]() |
384fe3024a | ||
![]() |
228a2611a5 | ||
![]() |
5c48971034 | ||
![]() |
9952e4583c | ||
![]() |
86492e3efb | ||
![]() |
6638dec85a | ||
![]() |
b038ecf63f | ||
![]() |
52d232a2e6 | ||
![]() |
381a78e311 | ||
![]() |
a129083826 | ||
![]() |
46028d6019 | ||
![]() |
f882d330b7 | ||
![]() |
8a9256c838 | ||
![]() |
4d5fa04277 | ||
![]() |
694e1bf857 | ||
![]() |
cecfd781d6 | ||
![]() |
14e03313ec | ||
![]() |
05cd889a4e | ||
![]() |
4a27036307 | ||
![]() |
c1224a8f2c | ||
![]() |
24e6012feb | ||
![]() |
141cab407d | ||
![]() |
3c225defee | ||
![]() |
31cd3b5995 | ||
![]() |
e9b4a9f112 | ||
![]() |
6176dd5695 | ||
![]() |
c376ab97ac | ||
![]() |
e7be2f6b87 | ||
![]() |
e9c44bf702 | ||
![]() |
6c9115c30e | ||
![]() |
681f56f465 | ||
![]() |
d33fa23382 | ||
![]() |
67f88097df | ||
![]() |
9443970c89 | ||
![]() |
17c2c4b317 | ||
![]() |
3814df98f6 | ||
![]() |
ef9f5f2646 | ||
![]() |
edcbe501e8 | ||
![]() |
aa6a5fce42 | ||
![]() |
d30aa145aa | ||
![]() |
cbc42e6fc3 | ||
![]() |
71c0bdb760 | ||
![]() |
5cacab729d | ||
![]() |
57dcc0daf1 | ||
![]() |
7bcb136e26 | ||
![]() |
3ac1c8189f | ||
![]() |
0c263a2005 | ||
![]() |
4a7880d803 | ||
![]() |
84c311ee41 | ||
![]() |
ccc8d6c6e9 | ||
![]() |
8d7962acc8 | ||
![]() |
a5f2c35b4d | ||
![]() |
b8f089284f | ||
![]() |
eff41ee844 | ||
![]() |
eea6643028 | ||
![]() |
9034dbae6b | ||
![]() |
0bb331f87d | ||
![]() |
3193e45a95 | ||
![]() |
18227fa30b | ||
![]() |
774b6673c5 | ||
![]() |
cbe44895e8 | ||
![]() |
415047f765 | ||
![]() |
c26d0acfc0 | ||
![]() |
0f2c77187c | ||
![]() |
7fdf145e26 | ||
![]() |
ea948a4ae4 | ||
![]() |
e811c47a63 | ||
![]() |
42361e0c0c | ||
![]() |
af3a8bf1f2 | ||
![]() |
e90572aebf | ||
![]() |
d197a30b17 | ||
![]() |
ded2f09e87 | ||
![]() |
42de44b4de | ||
![]() |
fac4cb73b1 | ||
![]() |
4e6393dd9a | ||
![]() |
25b6099fbd | ||
![]() |
4a8e5d0cc0 | ||
![]() |
d87000f78d | ||
![]() |
d9a03d03b6 | ||
![]() |
ffeb42dde3 | ||
![]() |
8d8cf2bd9a | ||
![]() |
3dd24e832d | ||
![]() |
6a968d5945 | ||
![]() |
53bc594552 | ||
![]() |
163209afb9 | ||
![]() |
9b7098a318 | ||
![]() |
ccef54fd2f | ||
![]() |
a1749cf5e1 | ||
![]() |
a98c64ecf7 | ||
![]() |
dfa1e40a8b | ||
![]() |
a2aef2cf00 | ||
![]() |
46a0c7965b | ||
![]() |
ad196eb865 | ||
![]() |
0abccb2cf1 | ||
![]() |
6e727ed9d8 | ||
![]() |
689400081b | ||
![]() |
23c20f0937 | ||
![]() |
1ab10dcb04 | ||
![]() |
133f04462e | ||
![]() |
a77a569333 | ||
![]() |
f6a1cdba67 | ||
![]() |
24a955b2ab | ||
![]() |
4e1beb7580 | ||
![]() |
54e3b84902 | ||
![]() |
a5b65856a6 | ||
![]() |
7bb5cc722a | ||
![]() |
4bb9f90398 | ||
![]() |
c507e62a41 | ||
![]() |
253aa0f8ea | ||
![]() |
4b5bf08fe9 | ||
![]() |
a3cd2bc3ea | ||
![]() |
5a6f6ad39e | ||
![]() |
8404cb4245 | ||
![]() |
c26c52a33f | ||
![]() |
184d70d3a7 | ||
![]() |
7bda956dbb | ||
![]() |
1082ea65d4 | ||
![]() |
8eb60552fb | ||
![]() |
a074779441 | ||
![]() |
9d277c7ac6 | ||
![]() |
3cdf3fd417 | ||
![]() |
ab8e297769 | ||
![]() |
f6d89defe7 | ||
![]() |
57977ac182 | ||
![]() |
e648068b1c | ||
![]() |
238e204d85 | ||
![]() |
07f8a129d5 | ||
![]() |
5f8bde0db9 | ||
![]() |
66611c92a3 | ||
![]() |
f00edbb80d | ||
![]() |
ef4de65e7f | ||
![]() |
957c8593b1 | ||
![]() |
00deda35f9 | ||
![]() |
ea3338b2b9 | ||
![]() |
9715d78964 | ||
![]() |
d4f69eb54f | ||
![]() |
ae12e319a6 | ||
![]() |
80f8b95a06 | ||
![]() |
a00a5dafed | ||
![]() |
bcf2fd8f42 | ||
![]() |
b288487f23 | ||
![]() |
9f007c906f | ||
![]() |
a53eb5e0d2 | ||
![]() |
22238f95d1 | ||
![]() |
c79c595ee7 | ||
![]() |
77138d922e | ||
![]() |
87387878a7 | ||
![]() |
1b5c722c58 | ||
![]() |
532b1daaa6 | ||
![]() |
c6d3b5ccf4 | ||
![]() |
7053b34bc4 | ||
![]() |
c7a2b81e5e | ||
![]() |
4341cdc0fd | ||
![]() |
d2896797aa | ||
![]() |
8c828213d0 | ||
![]() |
f4a118db89 | ||
![]() |
c33d7b3720 | ||
![]() |
8fcd19cca9 | ||
![]() |
c813f6087c | ||
![]() |
be80b185ed | ||
![]() |
3bfc82c7bc | ||
![]() |
a38f807f22 | ||
![]() |
d7c193cbe9 | ||
![]() |
b02fe22d02 | ||
![]() |
4b7c2092b0 | ||
![]() |
451b7ea4d0 | ||
![]() |
bf2cff6eba | ||
![]() |
63943ca2dc | ||
![]() |
3f2bb2bde1 | ||
![]() |
feb51aaceb | ||
![]() |
c3e569be41 | ||
![]() |
5f0bcd7710 | ||
![]() |
8d3d5d08a5 | ||
![]() |
bcdca5d002 | ||
![]() |
5a83e78820 | ||
![]() |
67d7abbf84 | ||
![]() |
d7ce42d04c | ||
![]() |
a130c3e744 | ||
![]() |
73e9b21824 | ||
![]() |
178495bd69 | ||
![]() |
c9161c10ae | ||
![]() |
0e9827a2fc | ||
![]() |
e1a8360b27 | ||
![]() |
251d69194b | ||
![]() |
0a63518b62 | ||
![]() |
eb7a958352 | ||
![]() |
2683ce268c | ||
![]() |
4f24e6db4a | ||
![]() |
e3074d8054 | ||
![]() |
1a7825d8c6 | ||
![]() |
b9ad35b21f | ||
![]() |
334a3d083a | ||
![]() |
a8e8120a5c | ||
![]() |
c3c52692a6 | ||
![]() |
c4a98c0df7 | ||
![]() |
45d2bae004 | ||
![]() |
f7f9037e44 | ||
![]() |
53ef0288f5 | ||
![]() |
e587725322 | ||
![]() |
3c6a594c32 | ||
![]() |
b4fbc8d258 | ||
![]() |
d123fa95c6 | ||
![]() |
e1f4d0c200 |
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,5 +1,9 @@
|
||||
# Ignore the user config files
|
||||
./config.json
|
||||
|
||||
# Ignore the user database
|
||||
db.json
|
||||
|
||||
# ignore npm
|
||||
node_modules/
|
||||
|
||||
# ignore assets
|
||||
public/bootstrap/
|
||||
public/d3/
|
||||
|
11
.gitmodules
vendored
11
.gitmodules
vendored
@ -1,9 +1,6 @@
|
||||
[submodule "jsbot"]
|
||||
path = jsbot
|
||||
url = git://github.com/reality/jsbot.git
|
||||
[submodule "modules/stats"]
|
||||
path = modules/stats
|
||||
url = git://github.com/SamStudio8/stats.git
|
||||
path = jsbot
|
||||
url = git://github.com/reality/jsbot.git
|
||||
[submodule "modules/github"]
|
||||
path = modules/github
|
||||
url = git://github.com/zuzak/dbot-github.git
|
||||
path = modules/github
|
||||
url = git://github.com/zuzak/dbot-github.git
|
||||
|
26
LICENCE
26
LICENCE
@ -1,18 +1,14 @@
|
||||
Copyright (c) 2012 Luke Slater (tinmachin3@gmail.com)
|
||||
Copyright (c) 2012-2013 Luke Slater (tinmachin3@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
37
README.md
37
README.md
@ -1,8 +1,8 @@
|
||||
# Depressionbot IRC Bot
|
||||
# DBot IRC Bot
|
||||
|
||||
## Introduction
|
||||
|
||||
Depressionbot is an IRC bot which aims to be the fanciest IRC bot around - On
|
||||
DBot is an IRC bot which aims to be the fanciest IRC bot around - On
|
||||
the general standard of software fanciness, dbot is statistically rated as being
|
||||
'82% the same as bathing in fine, fine grape juice.'
|
||||
|
||||
@ -11,18 +11,27 @@ given I started it rather a long time after I began development of the project.
|
||||
Please don't judge me too harshly for this as I am, in fact, mildly allergic to
|
||||
writing documentation.
|
||||
|
||||
Requirements:
|
||||
## Getting Started
|
||||
|
||||
- Node JS
|
||||
- [JSBot](http://github.com/reality/JSBot "JSBot"), a Javascript library which
|
||||
handles the IRC protocol
|
||||
- Underscore JS library
|
||||
- Various modules have their own requirements also.
|
||||
To get started with DBot, you first need to decide on a database system to use.
|
||||
DBot uses the [databank](http://github.com/e14n/databank) library, and each
|
||||
module can be configured to use any database driver databank supports in its
|
||||
respective config.json file. There is currently no default database driver
|
||||
option.
|
||||
|
||||
### External Modules
|
||||
The default for all modules is the 'redis' driver, and you can simply install
|
||||
the Redis server to get going.
|
||||
|
||||
JSBot and externally developed modules can be imported by running the following
|
||||
commands in the cloned repository:
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
Once you have that set up, you can install DBot's dependencies, configure and
|
||||
run the bot for the first time with the following command:
|
||||
|
||||
```
|
||||
./install
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
If you have used a previous version of DBot, then you can migrate most data
|
||||
using the [dbot-migrate](https://github.com/reality/dbot-migrate) module.
|
||||
Instructions on how to run this are included in the repository - remember to
|
||||
remove db.json after migration, otherwise the instance will be slow!
|
||||
|
3
VERSION
3
VERSION
@ -1,5 +1,6 @@
|
||||
depressionbot version 0.3 (relentless)
|
||||
dbot version 0.5-dev
|
||||
|
||||
this.db.search('releases', { 'name': 'jimbletron' }, function(result) { "RELEASE" });
|
||||
"the database is a grilled cheese"
|
||||
_.each(dbot.modules, function(module) { "RESCORE EVERYTHING" });
|
||||
"He called his bot depressionbot, and that's when he was happy."
|
||||
|
@ -8,12 +8,20 @@
|
||||
"password": "lolturtles",
|
||||
"channels": [
|
||||
"#realitest"
|
||||
]
|
||||
],
|
||||
"admin_channel": "#realitest"
|
||||
}
|
||||
},
|
||||
"admins": [ "batman" ],
|
||||
"moderators": [ "whatever" ],
|
||||
"moduleNames": [ "ignore", "admin", "command", "dice", "js", "kick", "quotes", "spelling", "youare", "stats", "users" ],
|
||||
"language": "english",
|
||||
"debugMode": true
|
||||
"power_users": [],
|
||||
"moduleNames": [ "ignore", "admin", "command", "dice", "js", "kick", "quotes", "spelling", "users", "youare", "timers", "sstats", "link" ],
|
||||
"language": "en",
|
||||
"debugMode": false,
|
||||
"debugLevel": 1,
|
||||
"dbType": "redis",
|
||||
"redisPort": 6379,
|
||||
"timezone": "Europe/London",
|
||||
"repoRoot": "https://github.com/reality/depressionbot/",
|
||||
"version": "Depressionbot IRC bot 0.5-dev - Lovingly crafted by The DepressionBot Foundation (a charity arm of the Official Aberystwyth Open Source International Development League)."
|
||||
}
|
||||
|
33
database.js
Normal file
33
database.js
Normal file
@ -0,0 +1,33 @@
|
||||
var databank = require('databank'),
|
||||
Databank = databank.Databank,
|
||||
DatabankObject = databank.DatabankObject,
|
||||
_ = require('underscore')._;
|
||||
|
||||
/**
|
||||
* Multiplex databank objects
|
||||
*/
|
||||
var DatabaseDriver = function(config) {
|
||||
this.config = config;
|
||||
this.databank = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to or create a new DataBank
|
||||
*/
|
||||
DatabaseDriver.prototype.createDB = function(name, driver, schema, callback) {
|
||||
var params = { 'schema': schema };
|
||||
|
||||
if(driver == 'redis' && _.has(this.config, 'redisPort')) params.port = this.config.redisPort;
|
||||
if(driver == 'disk') params.dir = 'db';
|
||||
|
||||
this.databank = Databank.get(driver, params);
|
||||
this.databank.connect({}, function(err) {
|
||||
if(err) {
|
||||
console.log('Didn\'t manage to connect to the data source - ' + err);
|
||||
} else {
|
||||
callback(this.databank);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
exports.DatabaseDriver = DatabaseDriver;
|
48
install
Executable file
48
install
Executable file
@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
cat LICENCE
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
command -v node > /dev/null
|
||||
if [[ $? -gt 0 ]]; then
|
||||
echo 'node.js is not installed. Please install it before running install.sh.'
|
||||
exit 1
|
||||
fi
|
||||
command -v npm > /dev/null
|
||||
if [[ $? -gt 0 ]]; then
|
||||
echo 'npm is not installed. Please install it before running install.sh'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npm install googlemaps humanize feedparser node-units tvdb method-override 500px process async wordnik node-uuid underscore request request-promise-native vm2 express moment-timezone moment jade databank databank-redis ent passport passport-local password-hash connect-flash
|
||||
|
||||
cd public/
|
||||
wget https://github.com/twbs/bootstrap/releases/download/v3.3.2/bootstrap-3.3.2-dist.zip
|
||||
unzip bootstrap-3.3.2-dist.zip
|
||||
mv bootstrap-3.3.2-dist bootstrap
|
||||
rm bootstrap-3.3.2-dist.zip
|
||||
|
||||
mkdir d3
|
||||
cd d3
|
||||
wget https://github.com/mbostock/d3/releases/download/v3.5.5/d3.zip
|
||||
unzip d3.zip
|
||||
rm d3.zip
|
||||
|
||||
cd ../..
|
||||
|
||||
if [ ! -f config.json ];
|
||||
then
|
||||
echo 'Creating configuration file...'
|
||||
cp config.json.sample config.json
|
||||
$EDITOR config.json # Open config file with EDITOR variable, but if not available:
|
||||
if [[ $? != 0 ]]; then vim config.json; fi # Find the return code from previous command, if failed then use vim to edit.
|
||||
fi
|
||||
|
||||
read -p "Setup complete. Run dbot now? [y/N]"
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]
|
||||
then
|
||||
echo 'Okay. To run the bot, use "node run.js"'
|
||||
exit
|
||||
fi
|
||||
node run.js
|
||||
|
23
install.sh
23
install.sh
@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
npm install underscore request sandbox express moment jade@0.25
|
||||
|
||||
cd public/
|
||||
wget http://twitter.github.com/bootstrap/assets/bootstrap.zip
|
||||
unzip bootstrap.zip
|
||||
rm bootstrap.zip
|
||||
|
||||
mkdir d3
|
||||
cd d3
|
||||
wget http://d3js.org/d3.v3.zip
|
||||
unzip d3.v3.zip
|
||||
rm d3.v3.zip
|
||||
|
||||
cd ..
|
||||
|
||||
cp config.json.sample config.json
|
||||
|
||||
echo 'Setup complete. Now edit config.json with your preferences and run the bot with "node run.js"'
|
2
jsbot
2
jsbot
@ -1 +1 @@
|
||||
Subproject commit 35910d9025fa3af15b24cecc3f6e7ee897aee4dc
|
||||
Subproject commit 4e6af64655674ba1a331910f3ed35b935eaba147
|
28
modules/SAMPLE/README.md
Normal file
28
modules/SAMPLE/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
## FOO
|
||||
|
||||
bar.
|
||||
|
||||
### Description
|
||||
|
||||
This module provides a command which allows users to foobar.
|
||||
|
||||
### Dependencies
|
||||
|
||||
It has following dependencies:
|
||||
+ [foo](link)
|
||||
|
||||
### config.json
|
||||
|
||||
foo
|
||||
```
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
|
||||
#### ~foo [(bar]
|
||||
|
||||
Example:
|
||||
+ ~foo bar
|
||||
|
||||
### TODO
|
4
modules/SAMPLE/config
Normal file
4
modules/SAMPLE/config
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"foo": true,
|
||||
"foo": "bar"
|
||||
}
|
39
modules/SAMPLE/foo
Normal file
39
modules/SAMPLE/foo
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Module Name: foo
|
||||
* Description: bar.
|
||||
* Requires: foo [bar]
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._,
|
||||
bar = require('foo');//dependencies
|
||||
|
||||
var foo = function(dbot) { //name of module
|
||||
|
||||
this.ApiRoot = 'API_ROOT_HERE';
|
||||
|
||||
this.internalAPI = {
|
||||
//code for internal api here
|
||||
};
|
||||
|
||||
this.api = {
|
||||
//code for api here
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
//code for commands here
|
||||
};
|
||||
|
||||
this.onLoad = function() {
|
||||
//code for stuff to be done on load here
|
||||
};
|
||||
|
||||
this.onDestroy = function() {
|
||||
//stuff to be done on destroy here
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new foo(dbot); //name of module
|
||||
};
|
8
modules/SAMPLE/strings
Normal file
8
modules/SAMPLE/strings
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"foo": {
|
||||
"en": "{output} bar"
|
||||
},
|
||||
"foo2": {
|
||||
"en": "Something went wrong :( Example:'~foo bar'"
|
||||
}
|
||||
}
|
3
modules/SAMPLE/usage
Normal file
3
modules/SAMPLE/usage
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"~foo": "~foo [bar]"
|
||||
}
|
@ -26,6 +26,15 @@ channel, it will attempt to give the caller ops in the current channel.
|
||||
Perform a git pull, and then execute the 'reload' command. Saves a lot of time
|
||||
updating!
|
||||
|
||||
#### version [module]
|
||||
Shows the git version of the currently loaded revision of DBot. If module is
|
||||
provided, it will attempt to get the revision of the module (this is only useful
|
||||
for submodules).
|
||||
|
||||
#### status [module]
|
||||
Show the recorded status for a given module, this is helpful for debugging
|
||||
issues when loading or for checking if a module is loaded.
|
||||
|
||||
#### reload
|
||||
Reload all of the modules currently in use by DBot. By using this, all module
|
||||
functionality should be reloadable and replaceable without having to restart the
|
||||
@ -46,12 +55,18 @@ loaded by the standard DBot process.
|
||||
Unload a currently loaded module. This removes the module, and then triggers a
|
||||
reload of all modules.
|
||||
|
||||
#### ban [user] [command]
|
||||
Ban a user from using a command. Command may be replaced with '\*,' which will
|
||||
ban a user from use of all commands. Users banned from all commands will still
|
||||
be subject to module listeners.
|
||||
#### setconfig [path] [value]
|
||||
Set a config value at path to be a certain value persistently. For example, if
|
||||
you want to change the web module to listen on port 9001, you can run _setconfig
|
||||
web.webPort 9001_.
|
||||
|
||||
#### unban [user] [command]
|
||||
Unban a user from using a given command. If a user was previously banned using
|
||||
the '\*' wildcard, they may also be unbanned from such by replacing command with
|
||||
an asterisk here as well.
|
||||
#### pushconfig [path] [value]
|
||||
Push a new value to an existing config array. For example, if you want to add
|
||||
the user 'batman62' to the DBot moderators, you can run _pushconfig moderators
|
||||
batman62_.
|
||||
|
||||
#### showconfig [path]
|
||||
Use this to explore and view the DBot configuration. If called without a path,
|
||||
it will display the config keys in the root; if the path is a subkey, it will
|
||||
show all config keys under that key. If you give it an actual key, it'll show you
|
||||
the currently effective config value.
|
||||
|
@ -1,12 +1,62 @@
|
||||
/**
|
||||
* Module Name: Admin
|
||||
* Description: Set of commands which only one who is a DepressionBot
|
||||
* administrator can run - as such, it has its own command execution listener.
|
||||
* administrator can run.
|
||||
*/
|
||||
var fs = require('fs'),
|
||||
_ = require('underscore')._;
|
||||
|
||||
var admin = function(dbot) {
|
||||
this.internalAPI = {
|
||||
'getCurrentConfig': function(configKey, callback) {
|
||||
var configPath = dbot.config;
|
||||
configKey = configKey.split('.');
|
||||
|
||||
for(var i=0;i<configKey.length;i++) {
|
||||
if(_.has(configPath, configKey[i])) {
|
||||
configPath = configPath[configKey[i]];
|
||||
} else {
|
||||
configPath = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callback(configPath);
|
||||
},
|
||||
|
||||
'setConfig': function(configKey, newOption, callback) {
|
||||
var configPath = dbot.customConfig,
|
||||
oldOption = null;
|
||||
configKey = configKey.split('.');
|
||||
|
||||
for(var i=0;i<configKey.length-1;i++) {
|
||||
if(!_.has(configPath, configKey[i])) {
|
||||
configPath[configKey[i]] = {};
|
||||
}
|
||||
configPath = configPath[configKey[i]];
|
||||
}
|
||||
|
||||
if(_.has(configPath, configKey[i])) {
|
||||
oldOption = configPath[configKey[i]];
|
||||
}
|
||||
configPath[configKey[i]] = newOption;
|
||||
|
||||
this.internalAPI.saveConfig();
|
||||
dbot.reloadModules();
|
||||
callback(null, oldOption);
|
||||
}.bind(this),
|
||||
|
||||
'saveConfig': function() {
|
||||
var config = dbot.customConfig;
|
||||
fs.writeFileSync('config.json', JSON.stringify(config, null, ' '));
|
||||
}
|
||||
};
|
||||
|
||||
this.onLoad = function() {
|
||||
dbot.api.timers.addTimer(60000, function() {
|
||||
dbot.save(function() {});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
|
@ -1,52 +1,15 @@
|
||||
var fs = require('fs'),
|
||||
_ = require('underscore')._,
|
||||
sys = require('sys'),
|
||||
process = require('process'),
|
||||
exec = require('child_process').exec;
|
||||
|
||||
var commands = function(dbot) {
|
||||
var noChangeConfig = [ 'servers', 'name', 'moduleNames' ];
|
||||
|
||||
var getCurrentConfig = function(configKey) {
|
||||
var defaultConfigPath = dbot.config;
|
||||
var userConfigPath = dbot.db.config;
|
||||
|
||||
if(configKey) {
|
||||
var configKey = configKey.split('.');
|
||||
for(var i=0;i<configKey.length-1;i++) {
|
||||
if(_.has(defaultConfigPath, configKey[i])) {
|
||||
if(!_.has(userConfigPath, configKey[i])) {
|
||||
userConfigPath[configKey[i]] = {};
|
||||
}
|
||||
userConfigPath = userConfigPath[configKey[i]];
|
||||
defaultConfigPath = defaultConfigPath[configKey[i]];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentOption;
|
||||
if(configKey.length != 1) {
|
||||
configKey = _.last(configKey);
|
||||
if(_.has(userConfigPath, configKey) && !_.isUndefined(userConfigPath[configKey])) {
|
||||
currentOption = userConfigPath[configKey];
|
||||
} else if(_.has(defaultConfigPath, configKey)) {
|
||||
currentOption = defaultConfigPath[configKey];
|
||||
}
|
||||
} else {
|
||||
currentOption = defaultConfigPath[configKey];
|
||||
}
|
||||
|
||||
return {
|
||||
'user': userConfigPath,
|
||||
'default': defaultConfigPath,
|
||||
'value': currentOption
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var commands = {
|
||||
// Join a channel
|
||||
'join': function(event) {
|
||||
'~join': function(event) {
|
||||
var channel = event.params[1];
|
||||
if(_.has(event.allChannels, channel)) {
|
||||
event.reply(dbot.t('already_in_channel', {'channel': channel}));
|
||||
@ -57,7 +20,7 @@ var commands = function(dbot) {
|
||||
},
|
||||
|
||||
// Leave a channel
|
||||
'part': function(event) {
|
||||
'~part': function(event) {
|
||||
var channel = event.params[1];
|
||||
if(!_.has(event.allChannels, channel)) {
|
||||
event.reply(dbot.t('not_in_channel', {'channel': channel}));
|
||||
@ -68,7 +31,7 @@ var commands = function(dbot) {
|
||||
},
|
||||
|
||||
// Op admin caller in given channel
|
||||
'opme': function(event) {
|
||||
'~opme': function(event) {
|
||||
var channel = event.params[1];
|
||||
|
||||
// If given channel isn't valid just op in current one.
|
||||
@ -78,22 +41,29 @@ var commands = function(dbot) {
|
||||
dbot.instance.mode(event, channel, ' +o ' + event.user);
|
||||
},
|
||||
|
||||
// Op via OperServ in order to work with channels dbot is not joined to, or joined to without having +o itself
|
||||
'~forceopme': function(event) {
|
||||
var channel = event.params[1];
|
||||
|
||||
if(!_.has(event.allChannels, channel)) {
|
||||
channel = event.channel.name;
|
||||
}
|
||||
dbot.say('tripsit', 'OperServ', ' MODE ' + channel + ' +o ' + event.user);
|
||||
},
|
||||
|
||||
// Do a git pull and reload
|
||||
'greload': function(event) {
|
||||
'~greload': function(event) {
|
||||
exec("git pull", function (error, stdout, stderr) {
|
||||
exec("git submodule update", function (error, stdout, stderr) {
|
||||
event.reply(dbot.t('gpull'));
|
||||
commands.reload(event);
|
||||
event.message = 'version';
|
||||
event.action = 'PRIVMSG';
|
||||
event.params = event.message.split(' ');
|
||||
dbot.instance.emit(event);
|
||||
commands['~reload'](event);
|
||||
commands['~version'](event);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Display commit information for part of dbot
|
||||
'version': function(event){
|
||||
'~version': function(event){
|
||||
var cmd = "git log --pretty=format:'%h (%s): %ar' -n 1 -- ";
|
||||
if(event.params[1]){
|
||||
var input = event.params[1].trim();
|
||||
@ -110,199 +80,266 @@ var commands = function(dbot) {
|
||||
event.reply(stdout);
|
||||
}
|
||||
else{
|
||||
event.reply("No version information or queried module not loaded");
|
||||
event.reply(dbot.t("no_version"));
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
||||
'status': function(event) {
|
||||
'~status': function(event) {
|
||||
var moduleName = event.params[1];
|
||||
if(_.has(dbot.status, moduleName)) {
|
||||
var status = dbot.status[moduleName];
|
||||
if(status === true) {
|
||||
event.reply(moduleName + ' status: Shit looks good.');
|
||||
event.reply(dbot.t('status_good', {
|
||||
'module': moduleName,
|
||||
'reason': status
|
||||
}));
|
||||
} else {
|
||||
event.reply(moduleName + ' status: Failed to load: ' + status);
|
||||
event.reply(dbot.t('status_bad', {
|
||||
'module': moduleName,
|
||||
'reason': status
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
event.reply('Either that module wasn\'t on the roster or shit is totally fucked.');
|
||||
event.reply(dbot.t("status_unloaded"));
|
||||
}
|
||||
},
|
||||
|
||||
// Reload DB, translations and modules.
|
||||
'reload': function(event) {
|
||||
'~reload': function(event) {
|
||||
dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8'));
|
||||
dbot.reloadModules();
|
||||
event.reply(dbot.t('reload'));
|
||||
process.nextTick(function() {
|
||||
event.reply(dbot.t('reload'));
|
||||
});
|
||||
},
|
||||
|
||||
// Say something in a channel
|
||||
'say': function(event) {
|
||||
'~say': function(event) {
|
||||
var channel = event.params[1];
|
||||
if(event.params[1] === "@") {
|
||||
var channel = event.channel.name;
|
||||
channel = event.channel.name;
|
||||
}
|
||||
var message = event.params.slice(2).join(' ');
|
||||
dbot.say(event.server, channel, message);
|
||||
},
|
||||
|
||||
// Load new module
|
||||
'load': function(event) {
|
||||
'~load': function(event) {
|
||||
var moduleName = event.params[1];
|
||||
if(!_.include(dbot.config.moduleNames, moduleName)) {
|
||||
dbot.config.moduleNames.push(moduleName);
|
||||
dbot.customConfig.moduleNames.push(moduleName);
|
||||
this.internalAPI.saveConfig();
|
||||
dbot.reloadModules();
|
||||
if(dbot.status[moduleName] === true) {
|
||||
event.reply(dbot.t('load_module', {'moduleName': moduleName}));
|
||||
} else {
|
||||
event.reply('Failed to load ' + moduleName + '. See \'status ' + moduleName + '\'.');
|
||||
}
|
||||
process.nextTick(function() {
|
||||
if(dbot.status[moduleName] === true) {
|
||||
event.reply(dbot.t('load_module', { 'moduleName': moduleName }));
|
||||
} else {
|
||||
event.reply(dbot.t('load_failed', { 'module': moduleName }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if(moduleName == 'web') {
|
||||
event.reply(dbot.t('already_loaded_web'));
|
||||
} else {
|
||||
event.reply(dbot.t('already_loaded', {'moduleName': moduleName}));
|
||||
event.reply(dbot.t('already_loaded', { 'moduleName': moduleName }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Unload a loaded module
|
||||
'unload': function(event) {
|
||||
'~unload': function(event) {
|
||||
var moduleNames = dbot.config.moduleNames;
|
||||
var moduleName = event.params[1];
|
||||
if(_.include(moduleNames, moduleName)) {
|
||||
var moduleDir = '../' + moduleName + '/';
|
||||
var cacheKey = require.resolve(moduleDir + moduleName);
|
||||
delete require.cache[cacheKey];
|
||||
dbot.config.moduleNames = _.without(dbot.config.moduleNames, moduleName);
|
||||
try {
|
||||
var cacheKey = require.resolve(moduleDir + moduleName);
|
||||
delete require.cache[cacheKey];
|
||||
} catch(err) { }
|
||||
|
||||
dbot.customConfig.moduleNames = _.without(dbot.config.moduleNames, moduleName);
|
||||
this.internalAPI.saveConfig();
|
||||
dbot.reloadModules();
|
||||
|
||||
event.reply(dbot.t('unload_module', {'moduleName': moduleName}));
|
||||
process.nextTick(function() {
|
||||
event.reply(dbot.t('unload_module', { 'moduleName': moduleName }));
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('unload_error', {'moduleName': moduleName}));
|
||||
}
|
||||
},
|
||||
|
||||
// Ban user from command or *
|
||||
'ban': function(event) {
|
||||
var username = event.params[1];
|
||||
var command = event.params[2];
|
||||
|
||||
if(!_.has(dbot.db.bans, command)) {
|
||||
dbot.db.bans[command] = [ ];
|
||||
}
|
||||
dbot.db.bans[command].push(username);
|
||||
event.reply(dbot.t('banned', {'user': username, 'command': command}));
|
||||
},
|
||||
|
||||
// Unban a user from command or *
|
||||
'unban': function(event) {
|
||||
var username = event.params[1];
|
||||
var command = event.params[2];
|
||||
if(_.has(dbot.db.bans, command) && _.include(dbot.db.bans[command], username)) {
|
||||
_.reject(dbot.db.bans[command], function(bans) {
|
||||
return bans == username;
|
||||
}, this);
|
||||
event.reply(dbot.t('unbanned', {'user': username, 'command': command}));
|
||||
} else {
|
||||
event.reply(dbot.t('unban_error', {'user': username}));
|
||||
event.reply(dbot.t('unload_error', { 'moduleName': moduleName }));
|
||||
}
|
||||
},
|
||||
|
||||
/*** Config options ***/
|
||||
|
||||
'setconfig': function(event) {
|
||||
var configPathString = event.params[1],
|
||||
configKey = _.last(configPathString.split('.')),
|
||||
newOption = event.params[2];
|
||||
'~setconfig': function(event) {
|
||||
var configPath = event.input[1],
|
||||
newOption = event.input[2];
|
||||
|
||||
if(!_.include(noChangeConfig, configKey)) {
|
||||
var configPath = getCurrentConfig(configPathString);
|
||||
event.reply(event.input[1]);
|
||||
|
||||
if(configPath == false || _.isUndefined(configPath.value)) {
|
||||
event.reply("Config key doesn't exist bro");
|
||||
return;
|
||||
}
|
||||
var currentOption = configPath.value;
|
||||
if(!_.include(noChangeConfig, configPath)) {
|
||||
this.internalAPI.getCurrentConfig(configPath, function(config) {
|
||||
if(config !== null) {
|
||||
// Convert to boolean type if config item boolean
|
||||
if(_.isBoolean(config)) {
|
||||
newOption = (newOption == "true");
|
||||
}
|
||||
|
||||
// Convert to boolean type if config item boolean
|
||||
if(_.isBoolean(currentOption)) {
|
||||
newOption = (newOption == "true");
|
||||
}
|
||||
// Convert to integer type is config item integer
|
||||
if(_.isNumber(config)) {
|
||||
newOption = parseInt(newOption);
|
||||
}
|
||||
|
||||
if(_.isArray(currentOption)) {
|
||||
event.reply("Config option is an array. Try 'pushconfig'.");
|
||||
}
|
||||
|
||||
event.reply(configPathString + ": " + currentOption + " -> " + newOption);
|
||||
configPath['user'][configKey] = newOption;
|
||||
dbot.reloadModules();
|
||||
if(_.isArray(config)) {
|
||||
event.reply(dbot.t("config_array", { "alternate": "pushconfig" }));
|
||||
}
|
||||
} else {
|
||||
event.reply(dbot.t("no_config_key", {'path': configPath}));
|
||||
configPath = configPath.split('.');
|
||||
if(_.has(dbot.config.modules, configPath[0])) {
|
||||
configPath.splice(0, 0, 'modules');
|
||||
event.input[1] = configPath.join('.');
|
||||
commands['~setconfig'](event);
|
||||
return;
|
||||
} else {
|
||||
event.reply(dbot.t('new_config_key', { 'key': configPath }));
|
||||
}
|
||||
}
|
||||
|
||||
this.internalAPI.setConfig(configPath, newOption, function(err) {
|
||||
event.reply(configPath + ": " + config + " -> " + newOption);
|
||||
});
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply("This config option cannot be altered while the bot is running.");
|
||||
event.reply(dbot.t("config_lock"));
|
||||
}
|
||||
},
|
||||
|
||||
'pushconfig': function(event) {
|
||||
var configPathString = event.params[1],
|
||||
configKey = _.last(configPathString.split('.')),
|
||||
newOption = event.params[2];
|
||||
'~pushconfig': function(event) {
|
||||
var configPath = event.input[1],
|
||||
newOption = event.input[2];
|
||||
|
||||
if(!_.include(noChangeConfig, configKey)) {
|
||||
var configPath = getCurrentConfig(configPathString);
|
||||
if(configPath == false || _.isUndefined(configPath.value)) {
|
||||
event.reply("Config key doesn't exist bro");
|
||||
return;
|
||||
}
|
||||
var currentArray = configPath.value;
|
||||
|
||||
if(!_.isArray(currentArray)) {
|
||||
event.reply("Config option is not an array. Try 'setconfig'.");
|
||||
return
|
||||
}
|
||||
|
||||
event.reply(configPathString + ": " + currentArray + " << " + newOption);
|
||||
currentArray.push(newOption);
|
||||
dbot.reloadModules();
|
||||
if(!_.include(noChangeConfig, configPath)) {
|
||||
this.internalAPI.getCurrentConfig(configPath, function(config) {
|
||||
if(config !== null) {
|
||||
if(_.isArray(config)) {
|
||||
event.reply(configPath + ": " + config + " << " + newOption);
|
||||
config.push(newOption);
|
||||
this.internalAPI.setConfig(configPath, config, function(err) {});
|
||||
} else {
|
||||
event.reply(dbot.t("config_array", { "alternate": "setconfig" }));
|
||||
}
|
||||
} else {
|
||||
event.reply(dbot.t("no_config_key", { 'path': configPath }));
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply(dbot.t("config_lock"));
|
||||
}
|
||||
},
|
||||
|
||||
'showconfig': function(event) {
|
||||
var configPathString = event.params[1];
|
||||
var configPath = getCurrentConfig(configPathString);
|
||||
|
||||
if(configPathString) {
|
||||
var configKey = _.last(configPathString.split('.'));
|
||||
if(configKey == false) {
|
||||
event.reply("Config path doesn't exist");
|
||||
return;
|
||||
}
|
||||
'~rmconfig': function(event) {
|
||||
var configPath = event.input[1],
|
||||
rmOption = event.input[2];
|
||||
|
||||
if(_.isArray(configPath.value)) {
|
||||
event.reply(configKey + ': ' + configPath.value);
|
||||
} else if(_.isObject(configPath.value)) {
|
||||
event.reply('Config keys in ' + configPathString + ': ' + Object.keys(configPath.value));
|
||||
} else {
|
||||
event.reply(configKey + ': ' + configPath.value);
|
||||
}
|
||||
if(!_.include(noChangeConfig, configPath)) {
|
||||
this.internalAPI.getCurrentConfig(configPath, function(config) {
|
||||
if(config !== null) {
|
||||
if(_.isArray(config)) {
|
||||
event.reply(configPath + ": " + config + " - " + rmOption);
|
||||
config = _.without(config, rmOption)
|
||||
this.internalAPI.setConfig(configPath, config, function(err) {});
|
||||
} else {
|
||||
event.reply(dbot.t("config_array", { "alternate": "nope" }));
|
||||
}
|
||||
} else {
|
||||
event.reply(dbot.t("no_config_key", { 'path': configPath }));
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply('Config keys in root: ' + Object.keys(configPath['default']));
|
||||
event.reply(dbot.t("config_lock"));
|
||||
}
|
||||
},
|
||||
|
||||
'~showconfig': function(event) {
|
||||
var configPath = event.params[1];
|
||||
if(configPath.indexOf('servers') != -1) { return false; }
|
||||
if(configPath) {
|
||||
this.internalAPI.getCurrentConfig(configPath, function(config) {
|
||||
if(config !== null) {
|
||||
if(_.isArray(config)) {
|
||||
event.reply(dbot.t("config_keys_location", {
|
||||
"path": configPath,
|
||||
"value": config
|
||||
}));
|
||||
} else if(_.isObject(config)) {
|
||||
event.reply(dbot.t("config_keys_location", {
|
||||
"path": configPath,
|
||||
"value": _.keys(config)
|
||||
}));
|
||||
} else {
|
||||
event.reply(dbot.t("config_keys_location", {
|
||||
"path": configPath,
|
||||
"value": config
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
event.reply(dbot.t("no_config_key", {'path': configPath}));
|
||||
|
||||
configPath = configPath.split('.');
|
||||
if(_.has(dbot.config.modules, configPath[0])) {
|
||||
configPath.splice(0, 0, 'modules');
|
||||
} else {
|
||||
configPath.pop();
|
||||
}
|
||||
|
||||
event.params[1] = configPath.join('.');
|
||||
commands['~showconfig'](event);
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply(dbot.t("config_keys_location", {
|
||||
"path": "root",
|
||||
"value": _.keys(dbot.config)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
'~savemodules': function(event) {
|
||||
fs.readFile('config.json', 'utf-8', function(err, config) {
|
||||
config = JSON.parse(config);
|
||||
config.moduleNames = _.keys(dbot.modules);
|
||||
fs.writeFile('config.json', JSON.stringify(config, null, ' '), function() {
|
||||
event.reply(dbot.t('modules_saved', { 'modules': _.keys(dbot.modules) }));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'~die': function(event) {
|
||||
event.reply('BRB coconut hunting...');
|
||||
setTimeout(3000, function() {
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
commands['greload'].access = 'admin';
|
||||
commands['reload'].access = 'admin';
|
||||
commands['unload'].access = 'admin';
|
||||
commands['load'].access = 'admin';
|
||||
commands['setconfig'].access = 'admin';
|
||||
commands['showconfig'].access = 'moderator';
|
||||
commands['join'].access = 'moderator';
|
||||
commands['part'].access = 'moderator';
|
||||
commands['opme'].access = 'moderator';
|
||||
commands['say'].access = 'moderator';
|
||||
commands['ban'].access = 'moderator';
|
||||
commands['unban'].access = 'moderator';
|
||||
_.each(commands, function(command) {
|
||||
command.access = 'admin';
|
||||
});
|
||||
|
||||
commands['~showconfig'].access = 'admin';
|
||||
commands['~join'].access = 'power_user';
|
||||
commands['~part'].access = 'power_user';
|
||||
commands['~opme'].access = 'moderator';
|
||||
commands['~forceopme'].access = 'power_user';
|
||||
commands['~say'].access = 'power_user';
|
||||
commands['~pushconfig'].access = 'moderator';
|
||||
commands['~rmconfig'].access = 'moderator';
|
||||
|
||||
commands['~pushconfig'].regex = [/pushconfig ([^ ]+) ([^ ]+)/, 3];
|
||||
commands['~rmconfig'].regex = [/rmconfig ([^ ]+) ([^ ]+)/, 3];
|
||||
commands['~setconfig'].regex = [/setconfig ([^ ]+) ([^ ]+)/, 3];
|
||||
|
||||
return commands;
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
{
|
||||
"ignorable": false,
|
||||
"dbKeys": [ "bans" ],
|
||||
"dependencies": [ "command" ],
|
||||
"help": "http://github.com/reality/depressionbot/blob/master/modules/admin/README.md"
|
||||
"dbType": "redis",
|
||||
"dependencies": [ "command" ]
|
||||
}
|
||||
|
@ -1,80 +1,230 @@
|
||||
{
|
||||
"join": {
|
||||
"english": "Joined {channel}",
|
||||
"spanish" : "Entrado en {channel}",
|
||||
"en": "Joined {channel}",
|
||||
"es" : "Entrado en {channel}",
|
||||
"na'vi": "fpxäkìm {channel}(nemfa)",
|
||||
"welsh": "Wedi ymuno {channel}"
|
||||
"cy": "Wedi ymuno {channel}",
|
||||
"nl": "{channel} binnengekomen",
|
||||
"de": "{channel} beigetreten",
|
||||
"fr": "{channel} rejoint",
|
||||
"it": "Aderito a {channel}"
|
||||
},
|
||||
"part": {
|
||||
"english": "Left {channel}",
|
||||
"spanish" : "Abandonada {channel}",
|
||||
"en": "Left {channel}",
|
||||
"es" : "Abandonada {channel}",
|
||||
"na'vi": "Hum {channel}",
|
||||
"welsh": "Wedi gadael {channel}"
|
||||
"cy": "Wedi gadael {channel}",
|
||||
"nl": "{channel} verlaten",
|
||||
"de": "{channel} verlassen",
|
||||
"fr": "{channel} quitté",
|
||||
"it": "uscito da {channel}"
|
||||
},
|
||||
"gpull": {
|
||||
"english": "Git pulled that shit.",
|
||||
"spanish": "Hecho git pull en esta mierda.",
|
||||
"en": "Git pulled that shit.",
|
||||
"es": "Hecho git pull en esta mierda.",
|
||||
"na'vi": "Gìtìl fì'uti stamarsìm.",
|
||||
"welsh": "Wedi tynnu git yr cach na i gyd"
|
||||
"cy": "Wedi tynnu git yr cach na i gyd",
|
||||
"nl": "Git heeft die zooi binnengehaald.",
|
||||
"de": "Git hat es gezogen",
|
||||
"fr": "Git a pullé cette merde",
|
||||
"it": "Git ha pullato questa coglionata"
|
||||
},
|
||||
"reload": {
|
||||
"english": "Reloaded that shit.",
|
||||
"spanish": "Recargado esta mierda.",
|
||||
"en": "Reloaded that shit.",
|
||||
"es": "Recargado esta mierda.",
|
||||
"na'vi": "Oel fìuti stìyeftxaw.",
|
||||
"welsh": "Ail-lwytho'r cach na"
|
||||
"cy": "Ail-lwytho'r cach na",
|
||||
"nl": "Die zooi opnieuw geladen.",
|
||||
"de": "Neu geladen",
|
||||
"fr": "Bordel rechargé",
|
||||
"it": "Ricaricato questa puttanata"
|
||||
},
|
||||
"load_module": {
|
||||
"english": "Loaded new module: {moduleName}",
|
||||
"spanish": "Cargado módulo nuevo: {moduleName}",
|
||||
"en": "Loaded new module: {moduleName}",
|
||||
"es": "Cargado módulo nuevo: {moduleName}",
|
||||
"na'vi": "Oel {moduleName}it amip stìyeftxaw.",
|
||||
"welsh": "Wedi llwytho modiwl newydd: {moduleName}"
|
||||
"cy": "Wedi llwytho modiwl newydd: {moduleName}",
|
||||
"nl": "Nieuwe module geladen: {moduleName}",
|
||||
"de": "Neues Modul geladen: {moduleName}",
|
||||
"fr": "Nouveau module chargé : {moduleName}",
|
||||
"it": "Caricato nuovo modulo: {moduleName}"
|
||||
},
|
||||
"unload_module": {
|
||||
"english": "Turned off module: {moduleName}",
|
||||
"spanish": "Descargado módulo: {moduleName}",
|
||||
"en": "Turned off module: {moduleName}",
|
||||
"es": "Descargado módulo: {moduleName}",
|
||||
"na'vi": "Oel {moduleName} tswìya'.",
|
||||
"welsh": "Wedi troi ffwrdd y modiwl: {moduleName}"
|
||||
"cy": "Wedi troi ffwrdd y modiwl: {moduleName}",
|
||||
"nl": "Module uitgeschakeld: {moduleName}",
|
||||
"de": "Modul ausgeschaltet: {moduleName}",
|
||||
"fr": "Module déchargé : {moduleName}",
|
||||
"it": "Inabilitato modulo: {moduleName}"
|
||||
},
|
||||
"unload_error": {
|
||||
"english": "{moduleName} isn't loaded. Idiot.",
|
||||
"spanish": "{moduleName} no está cargado. Idiota.",
|
||||
"en": "{moduleName} isn't loaded. Idiot.",
|
||||
"es": "{moduleName} no está cargado. Idiota.",
|
||||
"na'vi": "Oel {moduleName}it omum. Nga skxawng lu.",
|
||||
"welsh": "Di {moduleName} ddim wedi llwytho. Twpsyn"
|
||||
"cy": "Di {moduleName} ddim wedi llwytho. Twpsyn",
|
||||
"nl": "{moduleName} is niet geladen. Idioot.",
|
||||
"de": "{moduleName} ist nicht geladen, du Idiot.",
|
||||
"fr": "{moduleName} n'est pas chargé. Idiot.",
|
||||
"it": "{moduleName} non è caricato. Idiota"
|
||||
},
|
||||
"banned": {
|
||||
"english": "{user} banned from {command}",
|
||||
"spanish": "{user} está prohibido de usar {command}",
|
||||
"en": "{user} banned from {command}",
|
||||
"es": "{user} está prohibido de usar {command}",
|
||||
"na'vi": "{command}ìri {user} ke tung.",
|
||||
"welsh": "{user} wedi ei gohurio o {command}"
|
||||
"cy": "{user} wedi ei gohurio o {command}",
|
||||
"nl": "{user} mag {command} niet meer gebruiken",
|
||||
"de": "{user} wurde von {command} gebannt",
|
||||
"fr": "{user} a été interdit d'utiliser {command}",
|
||||
"it": "{user} bandito da {command}"
|
||||
},
|
||||
"unbanned": {
|
||||
"english": "{user} unbanned from {command}",
|
||||
"spanish": "{user} no está prohibido de user {command}",
|
||||
"en": "{user} unbanned from {command}",
|
||||
"es": "{user} no está prohibido de user {command}",
|
||||
"na'vi": "{command}ìri {user} tung set.",
|
||||
"welsh": "{user} wedi ei dad-wahardd o {command}"
|
||||
"cy": "{user} wedi ei dad-wahardd o {command}",
|
||||
"nl": "{user} mag {command} weer gebruiken",
|
||||
"de": "{user} wurde von {command} entbannt",
|
||||
"fr": "{user} peut de nouveau utiliser {command}",
|
||||
"it": "{user} riammesso da {command}"
|
||||
},
|
||||
"unban_error": {
|
||||
"english": "{user} wasn't banned from that command, fool.",
|
||||
"spanish": "{user} no fue prohibido de esta instrucción, tont@.",
|
||||
"en": "{user} wasn't banned from that command, fool.",
|
||||
"es": "{user} no fue prohibido de esta instrucción, tont@.",
|
||||
"na'vi": "{user} fìtsu'oti tamung srekrr, nga skxawng lu.",
|
||||
"welsh": "Nid oedd {user} wedi ei wahardd o'r gyrchymun yna, fŵl"
|
||||
"cy": "Nid oedd {user} wedi ei wahardd o'r gorchymyn yna, twpsyn",
|
||||
"nl": "{user} mag dat commando sowieso al gebruiken, mafketel.",
|
||||
"de": "{user} wurde nicht von {command} gebannt, du Trottel",
|
||||
"fr": "{user} n'a pas été interdit d'utiliser cette commande, imbécile.",
|
||||
"it": "{user} non è stato bandito da utilizzare questo commando, imbecille."
|
||||
},
|
||||
"qlock": {
|
||||
"english": "Locked quote category: {category}",
|
||||
"spanish": "Cerrado la categoría: {category}",
|
||||
"en": "Locked quote category: {category}",
|
||||
"es": "Cerrado la categoría: {category}",
|
||||
"na'vi": "{category}ìri oel 'upxareti fmoli",
|
||||
"welsh": "Categori wedi cloi: {category}"
|
||||
"cy": "Categori wedi cloi: {category}",
|
||||
"nl": "Quote categorie vergrendeld: {category}",
|
||||
"de": "Zitat-Kategorie geschlossen: {category}",
|
||||
"fr": "Catégorie de citations verrouillée : {category}",
|
||||
"it": "Categoria di citazione bloccata : {category}"
|
||||
},
|
||||
"already_in_channel": {
|
||||
"english": "I'm already in {channel}"
|
||||
"en": "I'm already in {channel}",
|
||||
"na'vi": "Oel {channel}it tok li",
|
||||
"cy": "Rydw i eisoes yn {channel}",
|
||||
"nl": "Ik ben al in {channel}",
|
||||
"de": "Ich bin schon in {channel}",
|
||||
"fr": "Je suis déjà dans {channel}",
|
||||
"it": "Sono già in {channel}"
|
||||
},
|
||||
"not_in_channel": {
|
||||
"english": "I'm not in {channel}"
|
||||
"en": "I'm not in {channel}",
|
||||
"na'vi": "Oel {channel}it ke tok",
|
||||
"cy": "Rydw i ddim yn {channel}",
|
||||
"nl": "Ik ben niet aanwezig in {channel}",
|
||||
"de": "Ich bin noch nicht in {channel}",
|
||||
"fr": "Je ne suis pas dans {channel}",
|
||||
"it": "Non sono in {channel}"
|
||||
},
|
||||
"already_loaded_web": {
|
||||
"english": "WHY CAN'T I LOAD ALL THIS WEB? (web already loaded)"
|
||||
"en": "WHY CAN'T I LOAD ALL THIS WEB? (web already loaded)",
|
||||
"na'vi": "PELUN OEL KE TSUN OMUM FÌWETIT NÌWOTX (wetìri oe omum li)",
|
||||
"cy": "PAM ALLA I DDIM YN LWYTHO POB Y WE? (We eisoes yn lwytho)",
|
||||
"nl": "AL DIT WEB WORDT ME TOCH EEN BEETJE TE VEEL! (web is al geladen)",
|
||||
"de": "WARUM KANN DAS NICHT GELADEN WERDEN? (bereits geladen)",
|
||||
"fr": "POURQUOI EST-CE QUE JE PEUX PAS CHARGER TOUT CE WEB? (web déjà chargé)",
|
||||
"it": "PERCHÉ NON POSSO CARICARE TUTTE QUESTO WEB? (web già caricato)"
|
||||
},
|
||||
"already_loaded": {
|
||||
"english": "{moduleName} is already loaded."
|
||||
"en": "{moduleName} is already loaded.",
|
||||
"na'vi": "Oel omum teri {moduleName}it li.",
|
||||
"cy": "{moduleName} eisoes yn lwytho",
|
||||
"nl": "{moduleName} is al geladen.",
|
||||
"de": "{moduleName} ist bereits geladen.",
|
||||
"fr": "{moduleName} est déjà chargé.",
|
||||
"it": "{moduleName} già caricato."
|
||||
},
|
||||
"no_version": {
|
||||
"en": "No version information or queried module not loaded.",
|
||||
"cy": "Dim gwybodaeth fersiwn neu modiwl holodd dim yn lwytho",
|
||||
"de": "Keine Informationen verfügbar oder gewünschtes Modul wurde noch nicht geladen.",
|
||||
"fr": "Aucune information de version ou module demandé non chargé.",
|
||||
"it": "Informazione sulla versione non è disponibile o modulo richiesto non ancora caricato"
|
||||
},
|
||||
"status_good": {
|
||||
"en": "{module} status: Shit looks good",
|
||||
"cy": "{module} statws: Cachu yn edrych yn dda",
|
||||
"de": "Sieht gut aus",
|
||||
"fr": "Statut de {module}: Cette merde tourne bien",
|
||||
"it": "Stato di {modulo}: Funky gallo come sono bello stamattina"
|
||||
},
|
||||
"status_bad": {
|
||||
"en": "{module} status: Failed to load: {reason}",
|
||||
"cy": "{module} statws: Methu i lwytho: {reason}",
|
||||
"de": "{module} Status: Konnte nicht geladen werden: {reason}",
|
||||
"fr": "Statut de {module}: échec de chargement : {reason}",
|
||||
"it": "Stato di {module}: Caricamento fallito: {reason}"
|
||||
},
|
||||
"status_unloaded": {
|
||||
"en": "Either that module wasn't on the roster or shit is totally fucked.",
|
||||
"cy": "Naill ai heb fod modiwl oedd ar y rhestr, neu cachu yn gwbl torrodd",
|
||||
"de": "Entweder ist das Modul nicht auf der Liste oder die Kacke ist am dampfen",
|
||||
"fr": "Soit ce module n'est pas sur la liste, soit tout est complètement niqué",
|
||||
"it": "O questo modulo non è sulla lista, o sono cazzi amari"
|
||||
},
|
||||
"load_failed": {
|
||||
"en": "Failed to load {module}. See 'status {module}'.",
|
||||
"cy": "Methu i lwytho {module}. Gwelwch 'status {module}'.",
|
||||
"de": "Konnte {module} nicht laden. Siehe 'status {module}'.",
|
||||
"fr": "Echec du chargement de {module}. Voir 'status {module}'.",
|
||||
"it": "Caricamento di {module} non riuscito. Vedi 'stato {module}'."
|
||||
},
|
||||
"no_config_key": {
|
||||
"en": "{path} doesn't exist bro",
|
||||
"cy": "{path} cyfluniad yn bodoli, fy mrawd",
|
||||
"de": "{path} existiert nicht, Bruder",
|
||||
"fr": "{path} n'existe pas, fréro",
|
||||
"it": "{path} non esiste, fratello"
|
||||
},
|
||||
"config_array": {
|
||||
"en": "Config option is an array. Try '{alternate}'.",
|
||||
"cy": "Opsiwn cyfluniad ydy'r amrywiaeth. Rhowch gynnig ar '{alternate}'.",
|
||||
"de": "Einstellung ist ein Array, probiere '{alternate}' aus.",
|
||||
"fr": "L'option de configuration est un array. Essaye '{alternate}'.",
|
||||
"it": "L'opzione di configurazione è un array. Prova '{alternate}'."
|
||||
},
|
||||
"config_lock": {
|
||||
"en": "This config option cannot be altered while the bot is running.",
|
||||
"cy": "Ni all yr opsiwn cyfluniad yn cael ei newid tra bod y bot yn rhedeg.",
|
||||
"de": "Diese Option kann während der Benutzung des Bots nicht verändert werden",
|
||||
"fr": "Cette option de configuration ne peut pas être changée pendant que le bot est activé.",
|
||||
"it": "Questa opzione di configurazione non può essere alterata mentre il bot è attivo."
|
||||
},
|
||||
"no_config_path": {
|
||||
"en": "Config path doesn't exist bro",
|
||||
"cy": "Nid yw llwybr cyfluniad yn bodoli, fy mrawd",
|
||||
"de": "Konfigurationspfad nicht vorhanden, Bruder",
|
||||
"fr": "Le chemin de configuration n'existe pas, fréro",
|
||||
"it": "Percorso di configurazione non esiste, fratello"
|
||||
},
|
||||
"new_config_key": {
|
||||
"en": "Warning: Creating new config key: {key}.",
|
||||
"fr": "Attention : création d'une nouvelle clé de configuration : {key}.",
|
||||
"it": "Attenzione : Creazione di una nuova chiave di configurazione: {key}.",
|
||||
"de": "Achtung: Neuer Konfigurationsschlüssel erstellt: {key}."
|
||||
},
|
||||
"config_keys_location": {
|
||||
"en": "Config keys in {path}: {value}",
|
||||
"cy": "Allweddi cyfluniad yn {path}: {value}",
|
||||
"de": "Die Konfiguration in {path}: {value}",
|
||||
"fr": "Clés de configuration dans {path}: {value}",
|
||||
"it": "Chiave di configurazione in {path}: {value}"
|
||||
},
|
||||
"modules_saved": {
|
||||
"en": "Currently loaded modules now default: {modules}",
|
||||
"fr": "Les modules actuellement chargés sont maintenant chargés par défaut : {modules}",
|
||||
"it": "I moduli attualmente caricati sono adesso predefiniti: {modules}",
|
||||
"de": "Die derzeit geladenen Module sind nun der Standart: {modules}"
|
||||
}
|
||||
}
|
||||
|
38
modules/announce/announce.js
Normal file
38
modules/announce/announce.js
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Name: Announce
|
||||
* Description: Announce things every now and again
|
||||
*/
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var announce = function(dbot) {
|
||||
this.announces = dbot.config.modules.announce.announces;
|
||||
this.lineCount = 0;
|
||||
this.lastAnnounce = {};
|
||||
_.each(dbot.config.servers, function(v, k) {
|
||||
this.lastAnnounce[k] = {};
|
||||
_.each(this.announces[k], function(announce, channel) {
|
||||
this.lastAnnounce[k][channel] = announce.distance;
|
||||
}, this)
|
||||
}, this);
|
||||
|
||||
this.listener = function(event) {
|
||||
if(_.has(this.lastAnnounce[event.server], event.channel)) {
|
||||
this.lastAnnounce[event.server][event.channel]--;
|
||||
if(this.lastAnnounce[event.server][event.channel] == 0) {
|
||||
var announce = this.config.announces[event.server][event.channel];
|
||||
this.lastAnnounce[event.server][event.channel] = announce.distance;
|
||||
|
||||
dbot.api.quotes.getQuote(announce.category, function(quote) {
|
||||
if(quote) {
|
||||
dbot.say(event.server, event.channel, quote);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
this.on = 'PRIVMSG';
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new announce(dbot);
|
||||
};
|
10
modules/announce/config.json
Normal file
10
modules/announce/config.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"announces": {
|
||||
"aberwiki": {
|
||||
"#test": {
|
||||
"category": "test",
|
||||
"distance": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
modules/api/README.md
Normal file
45
modules/api/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
## API
|
||||
|
||||
Creates external REST APIs for module API functions.
|
||||
|
||||
### Description
|
||||
|
||||
This module uses the web module to expose module API functionality externally
|
||||
through a REST API. As it stands, it's only really useful for viewing various
|
||||
information returned by API functions, as there is no system for API keys or
|
||||
anything like that to protect against misuse of functionality which modifies
|
||||
data.
|
||||
|
||||
To externalise an API function, two properties must be set on a particular API
|
||||
function, like so:
|
||||
|
||||
api['resolveUser'].external = true;
|
||||
api['resolveUser'].extMap = [ 'server', 'nick', 'callback' ];
|
||||
|
||||
The first, 'external' flag simply lets the API module know that this function is
|
||||
intended to be exposed externally - and functions will always be considered not
|
||||
to be externally available unless this flag is explicitly set.
|
||||
|
||||
The second is a mapping of parameters to the module. This should match the
|
||||
function prototype given when the function is declared (unfortunately these
|
||||
can't be mapped automatically because the closure use means we get 'native code'
|
||||
returned and can't scan the function headers for the parameter names).
|
||||
|
||||
Then, to access this function remotely we can simply make a GET request to the
|
||||
web counterpart to the internal API function path. So, internally you'd access
|
||||
the resolveUser function at _dbot.api.users.resolveUser_, we can get to it
|
||||
externally with _/api/users/resolveUser_ - supplying parameters as they are
|
||||
named in the extMap.
|
||||
|
||||
The response to the API call will be given in the form of JSON:
|
||||
|
||||
{
|
||||
err: Error, such as 'API function not enabled for external access'
|
||||
data: API call response
|
||||
}
|
||||
|
||||
If there is a _callback_ parameter named in the extMap, then the API module
|
||||
automatically hijacks this parameter and uses the data it's called with to
|
||||
supply the response to the API call with data. If there is no callback
|
||||
parameter, then it's a blocking API request and the response will be the return
|
||||
value of the call.
|
73
modules/api/api.js
Normal file
73
modules/api/api.js
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Name: API
|
||||
* Description: Expose DBot API functionality with a REST API
|
||||
*/
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var api = function(dbot) {
|
||||
this.onLoad = function() {
|
||||
dbot.modules.web.app.get('/api', function(req, res) {
|
||||
var externalApi = {};
|
||||
_.each(dbot.api, function(moduleApi, moduleName) {
|
||||
externalApi[moduleName] = {};
|
||||
_.each(moduleApi, function(method, methodName) {
|
||||
if(method.external == true) {
|
||||
externalApi[moduleName][methodName] = method.extMap;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
res.render('api/api', { 'name': dbot.config.name, 'api': externalApi });
|
||||
});
|
||||
|
||||
dbot.modules.web.app.get('/api/:module/:method', function(req, res) {
|
||||
var module = req.params.module,
|
||||
method = req.params.method,
|
||||
reqArgs = req.query,
|
||||
body = { 'err': null, 'data': null };
|
||||
|
||||
if(!_.has(dbot.api, module)) {
|
||||
body.err = 'No such API module';
|
||||
} else if(!_.has(dbot.api[module], method)) {
|
||||
body.err = 'No such API function in ' + module;
|
||||
} else if(dbot.api[module][method].external !== true) {
|
||||
body.err = 'API function ' + module + '.' + method +
|
||||
' not enabled for external access';
|
||||
}
|
||||
|
||||
if(!body.err) {
|
||||
var func = dbot.api[module][method],
|
||||
paramNames = func.extMap,
|
||||
args = [];
|
||||
|
||||
_.each(reqArgs, function(arg, name) {
|
||||
var callbackIndex = paramNames.indexOf(name);
|
||||
if(callbackIndex != -1) {
|
||||
args[callbackIndex] = decodeURIComponent(arg);
|
||||
}
|
||||
});
|
||||
|
||||
var callbackIndex = paramNames.indexOf('callback');
|
||||
if(callbackIndex != -1) {
|
||||
args[callbackIndex] = function() {
|
||||
body.data = Array.prototype.slice.call(arguments, 0);
|
||||
if(_.isObject(body.data[0]) && _.has(body.data[0], 'err')) {
|
||||
body.err = body.data[0].err;
|
||||
}
|
||||
res.json(body);
|
||||
};
|
||||
func.apply(null, args);
|
||||
} else {
|
||||
body.data = func.apply(null, args);
|
||||
res.json(body);
|
||||
}
|
||||
} else {
|
||||
res.json(body);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new api(dbot);
|
||||
};
|
16
modules/april/april.js
Normal file
16
modules/april/april.js
Normal file
@ -0,0 +1,16 @@
|
||||
var april = function(dbot) {
|
||||
this.listener = function(event) {
|
||||
var match = event.message.match(/^i'?( a)?m (an? )?([^ ]+)/i);
|
||||
if(match) {
|
||||
dbot.say(event.server, 'operserv', 'svsnick ' + event.user + ' ' + match[3]);
|
||||
setTimeout(function() {
|
||||
event.reply('Hi ' + match[3] + ', I\'m ' + dbot.config.name + '!');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
this.on = 'PRIVMSG';
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new april(dbot);
|
||||
};
|
157
modules/atheme/atheme.js
Normal file
157
modules/atheme/atheme.js
Normal file
@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Module Name: atheme
|
||||
* Description: atheme mode references & retrieve channel flags
|
||||
*/
|
||||
var _ = require('underscore')._,
|
||||
async = require('async');
|
||||
|
||||
var atheme = function(dbot) {
|
||||
this.flagStack = {};
|
||||
this.hostStack = {};
|
||||
|
||||
this.api = {
|
||||
'getChannelFlags': function(server, channel, callback) {
|
||||
if(!_.has(this.flagStack, server)) this.flagStack[server] = {};
|
||||
if(_.has(this.flagStack[server], channel)) { // Already an active flag call
|
||||
this.flagStack[server][channel].callbacks.push(callback);
|
||||
} else {
|
||||
this.flagStack[server][channel] = {
|
||||
'flags': {},
|
||||
'callbacks': [ callback ],
|
||||
'timeout': null,
|
||||
'working': false
|
||||
};
|
||||
}
|
||||
|
||||
dbot.say(server, 'chanserv', 'FLAGS ' + channel);
|
||||
this.flagStack[server][channel].timeout = setTimeout(function() { // Delete callback if no response
|
||||
if(_.has(this.flagStack[server], channel) && this.flagStack[server][channel].working == false) {
|
||||
_.each(this.flagStack[server][channel].callbacks, function(callback) {
|
||||
callback(true, null);
|
||||
});
|
||||
delete this.flagStack[server][channel];
|
||||
}
|
||||
}.bind(this), 20000);
|
||||
},
|
||||
|
||||
'getVHosts': function(server, mask, callback) {
|
||||
if(!_.has(this.hostStack, server)) this.hostStack[server] = {};
|
||||
if(_.has(this.hostStack[server], mask)) { // Already an active host call
|
||||
this.hostStack[server][channel].callbacks.push(callback);
|
||||
} else {
|
||||
this.hostStack[server][mask] = {
|
||||
'users': [],
|
||||
'callbacks': [ callback ],
|
||||
'timeout': null
|
||||
};
|
||||
}
|
||||
|
||||
dbot.say(server, 'hostserv', 'LISTVHOST ' + mask);
|
||||
this.hostStack[server][mask].timeout = setTimeout(function() { // Delete callback if no response
|
||||
if(_.has(this.hostStack[server], mask)) {
|
||||
_.each(this.hostStack[server][mask].callbacks, function(callback) {
|
||||
callback(true, null);
|
||||
});
|
||||
delete this.hostStack[server][mask];
|
||||
}
|
||||
}.bind(this), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
'~chanserv': function(event) {
|
||||
if(_.has(this.config.chanserv, event.input[1])) {
|
||||
event.reply('ChanServ flag ' + event.input[1] + ': ' + this.config.chanserv[event.input[1]]);
|
||||
} else {
|
||||
event.reply('I don\'t know anything about ' + event.input[1]);
|
||||
}
|
||||
},
|
||||
|
||||
'~chanmode': function(event) {
|
||||
if(_.has(this.config.chanmodes, event.input[1])) {
|
||||
event.reply('Channel Mode ' + event.input[1] + ': ' + this.config.chanmodes[event.input[1]]);
|
||||
} else {
|
||||
event.reply('I don\'t know anything about ' + event.input[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.commands['~chanserv'].regex = [/^chanserv (\+.)/, 2];
|
||||
this.commands['~chanmode'].regex = [/^chanmode (\+.)/, 2];
|
||||
|
||||
this.listener = function(event) {
|
||||
if(event.action === 'NOTICE') {
|
||||
if(event.user === 'ChanServ') {
|
||||
var flags = event.params.match(/(\d+)\s+([^ ]+)\s+(\+\w+)\s+\((\#[\w\.]+)\)/),
|
||||
end = event.params.match(/end of \u0002(\#[\w\.]+)\u0002 flags listing/i);
|
||||
|
||||
if(flags && _.has(this.flagStack[event.server], flags[4])) {
|
||||
this.flagStack[event.server][flags[4]].flags[flags[2]] = flags[3];
|
||||
} else if(end) {
|
||||
if(_.has(this.flagStack[event.server], end[1])) {
|
||||
this.flagStack[event.server][end[1]].working = true;
|
||||
// Parse wildcard hostmasks to nicks
|
||||
var allFlags = this.flagStack[event.server][end[1]].flags,
|
||||
hostMasks = {};
|
||||
|
||||
_.each(allFlags, function(f, u) { // TODO: combine to one loop
|
||||
if(u.indexOf('*!*@') !== -1) {
|
||||
hostMasks[u] = f;
|
||||
delete allFlags[u];
|
||||
}
|
||||
});
|
||||
async.each(_.keys(hostMasks), function(hostMask, done) {
|
||||
this.api.getVHosts(event.server, hostMask.split('@')[1], function(err, users) {
|
||||
_.each(users, function(user) {
|
||||
allFlags[user] = hostMasks[hostMask];
|
||||
});
|
||||
done();
|
||||
});
|
||||
}.bind(this), function() {
|
||||
_.each(this.flagStack[event.server][end[1]].callbacks, function(callback) {
|
||||
callback(null, this.flagStack[event.server][end[1]].flags);
|
||||
}.bind(this));
|
||||
clearTimeout(this.flagStack[event.server][end[1]].timeout);
|
||||
delete this.flagStack[event.server][end[1]];
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
} else if(event.user === 'HostServ') {
|
||||
_.each(this.hostStack[event.server], function(el, mask) {
|
||||
if(event.params.match(mask)) {
|
||||
var user = event.params.match(/- ([^ ]+)/),
|
||||
end = event.params.match(/matches for pattern/);
|
||||
|
||||
if(user) {
|
||||
this.hostStack[event.server][mask].users.push(user[1]);
|
||||
} else if(end) {
|
||||
_.each(this.hostStack[event.server][mask].callbacks, function(callback) {
|
||||
callback(null, this.hostStack[event.server][mask].users);
|
||||
}, this);
|
||||
clearTimeout(this.hostStack[event.server][mask].timeout);
|
||||
delete this.hostStack[event.server][mask];
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
} else { // PRIVMSG
|
||||
console.log(event.message);
|
||||
var akill = event.message.match(/([^ ]+) AKILL:ADD: ([^ ]+) \(reason: (.+)(\) )\(duration: ([^,)]+)/);
|
||||
if(event.channel == '#services' && akill) {
|
||||
console.log(akill);
|
||||
var channel = dbot.config.servers[event.server].admin_channel;
|
||||
dbot.api.users.getUser(akill[1] + '.' + event.server, function(err, user) {
|
||||
dbot.api.report.notify('ban', 'tripsit', user, channel, dbot.t('akill', {
|
||||
'host': akill[2],
|
||||
'reason': akill[3],
|
||||
'duration': akill[5]
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
this.on = ['NOTICE', 'PRIVMSG'];
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new atheme(dbot);
|
||||
};
|
50
modules/atheme/config.json
Normal file
50
modules/atheme/config.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"chanserv": {
|
||||
"+v": "Enables use of the voice/devoice commands.",
|
||||
"+V": "Enables automatic voice.",
|
||||
"+h": "Enables use of the halfop/dehalfop commands.",
|
||||
"+H": "Enables automatic halfop.",
|
||||
"+o": "Enables use of the op/deop commands.",
|
||||
"+O": "Enables automatic op.",
|
||||
"+a": "Enables use of the protect/deprotect commands.",
|
||||
"+q": "Enables use of the owner/deowner commands.",
|
||||
"+s": "Enables use of the set command.",
|
||||
"+i": "Enables use of the invite and getkey commands.",
|
||||
"+r": "Enables use of the kick, kickban, ban and unban commands.",
|
||||
"+r": "Enables use of the ban and unban commands.",
|
||||
"+r": "Enables use of the unban command.",
|
||||
"+R": "Enables use of the recover and clear commands.",
|
||||
"+f": "Enables modification of channel access lists.",
|
||||
"+t": "Enables use of the topic and topicappend commands.",
|
||||
"+A": "Enables viewing of channel access lists.",
|
||||
"+S": "Marks the user as a successor.",
|
||||
"+F": "Grants full founder access.",
|
||||
"+b": "Enables automatic kickban."
|
||||
},
|
||||
"chanmodes": {
|
||||
"+b": "channel ban",
|
||||
"+c": "colour filter",
|
||||
"+e": "ban exemption",
|
||||
"+f": "channel forwarding",
|
||||
"+F": "allow anybody to forward to this",
|
||||
"+g": "allow anybody to invite",
|
||||
"+i": "invite only",
|
||||
"+I": "invite exception (invex)",
|
||||
"+j": "join throttling",
|
||||
"+k": "key (channel password)",
|
||||
"+l": "channel member limit",
|
||||
"+L": "large ban list",
|
||||
"+m": "moderated",
|
||||
"+n": "no external messages",
|
||||
"+o": "channel operator",
|
||||
"+p": "paranoid channel",
|
||||
"+P": "permanent channel",
|
||||
"+q": "quiet",
|
||||
"+Q": "block forwarded users",
|
||||
"+r": "block unidentified",
|
||||
"+s": "secret channel",
|
||||
"+t": "topic limit",
|
||||
"+v": "voice",
|
||||
"+z": "reduced moderation"
|
||||
}
|
||||
}
|
5
modules/atheme/strings.json
Normal file
5
modules/atheme/strings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"akill": {
|
||||
"en": "{host} has been AKilled for {duration} due to \"{reason}\""
|
||||
}
|
||||
}
|
@ -21,6 +21,17 @@ Command flow:
|
||||
This is the only module which is force loaded, even if it's not specified in
|
||||
the configuration file.
|
||||
|
||||
### Config
|
||||
|
||||
#### useNickserv: false
|
||||
Use the nickserv module to ensure a user is logged into their account before
|
||||
running any elevated commands. Note you will still have to load and configure
|
||||
the nickserv module yourself.
|
||||
|
||||
#### accessOutput: false
|
||||
Show a message to a user if they attempt to run a command they don't have the
|
||||
access level for.
|
||||
|
||||
### Commands
|
||||
|
||||
#### ~usage [command]
|
||||
|
@ -2,44 +2,40 @@ var _ = require('underscore')._;
|
||||
|
||||
var api = function(dbot) {
|
||||
return {
|
||||
'isBanned': function(user, command) {
|
||||
var banned = false;
|
||||
if(_.has(dbot.db.bans, command)) {
|
||||
if(_.include(dbot.db.bans[command], user) || _.include(dbot.db.bans['*'], user)) {
|
||||
banned = true;
|
||||
}
|
||||
}
|
||||
return banned;
|
||||
},
|
||||
|
||||
/**
|
||||
* Does the user have the correct access level to use the command?
|
||||
*/
|
||||
'hasAccess': function(user, command) {
|
||||
var access = true;
|
||||
var accessNeeded = dbot.commands[command].access;
|
||||
'hasAccess': function(event, command, callback) {
|
||||
var accessNeeded = dbot.commands[command].access,
|
||||
allowedNicks,
|
||||
user = event.rUser;
|
||||
|
||||
if(accessNeeded == 'admin') {
|
||||
if(!_.include(dbot.config.admins, user)) {
|
||||
access = false;
|
||||
}
|
||||
} else if(accessNeeded == 'moderator') {
|
||||
if(!_.include(dbot.config.moderators, user) &&
|
||||
!_.include(dbot.config.admins, user)) {
|
||||
access = false;
|
||||
if(_.isUndefined(accessNeeded) || accessNeeded == null) {
|
||||
return callback(true);
|
||||
} else if(!_.isFunction(accessNeeded)) {
|
||||
if(_.has(dbot.access, accessNeeded)) {
|
||||
accessNeeded = dbot.access[accessNeeded];
|
||||
} else {
|
||||
return callback(true);
|
||||
}
|
||||
}
|
||||
allowedNicks = accessNeeded(event);
|
||||
|
||||
return access;
|
||||
},
|
||||
|
||||
/**
|
||||
* Is item (user or channel) ignoring command?
|
||||
*/
|
||||
'isIgnoring': function(item, command) {
|
||||
var module = dbot.commands[command].module;
|
||||
return (_.has(dbot.db.ignores, item) &&
|
||||
_.include(dbot.db.ignores[item], module));
|
||||
if(!_.include(allowedNicks, user.primaryNick) && !_.include(allowedNicks, user.currentNick)) {
|
||||
callback(false);
|
||||
} else {
|
||||
if(_.has(dbot.modules, 'nickserv') && this.config.useNickserv == true) {
|
||||
dbot.api.nickserv.auth(user.server, user.currentNick, function(result, primary) {
|
||||
if(result == true && primary == user.primaryNick) {
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -48,27 +44,26 @@ var api = function(dbot) {
|
||||
*/
|
||||
'applyRegex': function(commandName, event) {
|
||||
var applies = false;
|
||||
event.message = event.message.substring(1);
|
||||
if(_.has(dbot.commands[commandName], 'regex')) {
|
||||
var cRegex = dbot.commands[commandName].regex;
|
||||
var q = event.message.valMatch(cRegex[0], cRegex[1]);
|
||||
if(q) {
|
||||
applies = true;
|
||||
event.input = q;
|
||||
if(_.isArray(cRegex) && cRegex.length === 2) {
|
||||
var q = event.message.valMatch(cRegex[0], cRegex[1]);
|
||||
if(q) {
|
||||
applies = true;
|
||||
event.input = q;
|
||||
}
|
||||
} else {
|
||||
var q = event.message.match(cRegex);
|
||||
if(q) {
|
||||
applies = true;
|
||||
event.input = q;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
applies = true;
|
||||
}
|
||||
return applies;
|
||||
},
|
||||
|
||||
'addHook': function(command, callback) {
|
||||
console.log('adding hook');
|
||||
if(_.has(dbot.commands, command)) {
|
||||
if(!_.has(dbot.commands[command], 'hooks')) {
|
||||
dbot.commands[command].hooks = [];
|
||||
}
|
||||
dbot.commands[command].hooks.push(callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -4,60 +4,158 @@
|
||||
* command and then runs that command, given the user isn't banned from or
|
||||
* ignoring that command.
|
||||
*/
|
||||
var _ = require('underscore')._;
|
||||
var _ = require('underscore')._,
|
||||
cDomain = require('domain').create();
|
||||
|
||||
var command = function(dbot) {
|
||||
this.dbot = dbot;
|
||||
|
||||
/**
|
||||
* Run the appropriate command given the input.
|
||||
*/
|
||||
this.listener = function(event) {
|
||||
var commandName = event.params[0];
|
||||
if(!_.has(dbot.commands, commandName)) {
|
||||
if(_.has(dbot.modules, 'quotes')) {
|
||||
commandName = '~';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.api.isBanned(event.user, commandName)) {
|
||||
event.reply(dbot.t('command_ban', {'user': event.user}));
|
||||
} else {
|
||||
if(!this.api.isIgnoring(event.user, commandName) &&
|
||||
!this.api.isIgnoring(event.channel, commandName) &&
|
||||
this.api.hasAccess(event.user, commandName) &&
|
||||
dbot.commands[commandName].disabled !== true) {
|
||||
if(this.api.applyRegex(commandName, event)) {
|
||||
try {
|
||||
var command = dbot.commands[commandName];
|
||||
var results = command.apply(dbot.modules[command.module], [event]);
|
||||
if(_.has(command, 'hooks') && results !== false) {
|
||||
_.each(command['hooks'], function(hook) {
|
||||
hook.apply(hook.module, _.values(results));
|
||||
}, this);
|
||||
if(commandName.charAt(0) != this.config.commandPrefix || this.config.passiveMode == true) {
|
||||
return;
|
||||
}
|
||||
commandName = commandName.substring(1);
|
||||
|
||||
this.api.hasAccess(event, commandName, function(hasAccess) {
|
||||
dbot.api.ignore.isUserIgnoring(event.rUser, commandName, function(isIgnoring) {
|
||||
dbot.api.ignore.isUserBanned(event.rUser, commandName, function(isBanned) {
|
||||
|
||||
if(isBanned) {
|
||||
if(this.config.banOutput && commandName != this.config.commandPrefix) {
|
||||
event.reply(dbot.t('command_ban', {'user': event.user}));
|
||||
}
|
||||
} catch(err) {
|
||||
if(dbot.config.debugMode == true) {
|
||||
event.reply('- Error in ' + commandName + ':');
|
||||
event.reply('- Message: ' + err);
|
||||
event.reply('- Top of stack: ' + err.stack.split('\n')[1].trim());
|
||||
} else if(!hasAccess) {
|
||||
if(this.config.accessOutput) {
|
||||
event.reply(dbot.t('access_denied', { 'user': event.user }));
|
||||
}
|
||||
}
|
||||
dbot.save();
|
||||
} else {
|
||||
if(commandName !== '~') {
|
||||
if(_.has(dbot.usage, commandName)) {
|
||||
event.reply('Usage: ' + dbot.usage[commandName]);
|
||||
} else if(!isIgnoring && _.has(dbot.commands, commandName) && !dbot.commands[commandName].disabled) {
|
||||
if(!_.has(dbot.commands, commandName)) {
|
||||
if(_.has(dbot.modules, 'quotes')) {
|
||||
var key = event.message.substring(1);
|
||||
dbot.api.quotes.getInterpolatedQuote(event.server,
|
||||
event.channel.name, event.user, key, function(quote) {
|
||||
if(quote) {
|
||||
event.reply(key + ': ' + quote);
|
||||
} else if(_.has(dbot.modules, 'spelling')) {
|
||||
var commands = _.keys(dbot.commands),
|
||||
winner = false,
|
||||
closestMatch = Infinity;
|
||||
|
||||
_.each(commands, function(command) {
|
||||
var distance = dbot.api.spelling.distance(commandName, command);
|
||||
if(distance < closestMatch) {
|
||||
closestMatch = distance;
|
||||
winner = command;
|
||||
}
|
||||
});
|
||||
|
||||
if(closestMatch < 1) {
|
||||
event.reply(commandName + ' not found. Did you mean ' + winner + '?');
|
||||
return;
|
||||
} else if(_.has(dbot.modules, 'quotes')) {
|
||||
dbot.api.link.udLookup(key, function(word, definition) {
|
||||
if(word) {
|
||||
event.reply(key + '[UD]: ' + definition);
|
||||
} else {
|
||||
event.reply(dbot.t('category_not_found', { 'category': key }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
event.reply(dbot.t('syntax_error'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.api.applyRegex(commandName, event)) {
|
||||
try {
|
||||
cDomain.run(function() {
|
||||
var command = dbot.commands[commandName],
|
||||
results;
|
||||
if(_.has(command, 'resolver')) {
|
||||
event.res = [];
|
||||
command.resolver(event, function(err) {
|
||||
if(!err) {
|
||||
results = command.apply(dbot.modules[command.module], [event]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
results = command.apply(dbot.modules[command.module], [event]);
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
if(dbot.config.debugMode == true) {
|
||||
var stack = err.stack.split('\n').slice(1, dbot.config.debugLevel + 1);
|
||||
|
||||
event.reply('- Error in ' + commandName + ':');
|
||||
event.reply('- Message: ' + err);
|
||||
|
||||
_.each(stack, function(stackLine, index) {
|
||||
event.reply('- Stack[' + index + ']: ' +
|
||||
stackLine.trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
if(!_.include(['reload', 'load', 'unload', 'setconfig'], commandName)) dbot.api.event.emit('command', [ event ]);
|
||||
} else {
|
||||
if(commandName !== this.config.commandPrefix) {
|
||||
if(_.has(dbot.usage, commandName)) {
|
||||
event.reply('Usage: ' + dbot.usage[commandName]);
|
||||
} else {
|
||||
event.reply(dbot.t('syntax_error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
this.on = 'PRIVMSG';
|
||||
|
||||
this.onLoad = function() {
|
||||
// Not sure this is the right place for this. Perhaps they should be in
|
||||
// another file?
|
||||
|
||||
cDomain.on('error', function(err) {
|
||||
console.log(err); // Hmm
|
||||
if(_.has(dbot.modules, 'log')) {
|
||||
// can't really get context
|
||||
var server = _.keys(dbot.config.servers)[0];
|
||||
dbot.api.log.log(server, dbot.config.name, '[\u00034ERR\u000f] ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
dbot.access = {
|
||||
'admin': function(event) {
|
||||
return dbot.config.admins;
|
||||
},
|
||||
|
||||
'moderator': function(event) {
|
||||
return [].concat(dbot.access.admin(event), dbot.config.moderators);
|
||||
},
|
||||
|
||||
'power_user': function(event) {
|
||||
return [].concat(dbot.access.admin(event), dbot.access.moderator(event), dbot.config.power_users);
|
||||
},
|
||||
|
||||
'voice': function(event) {
|
||||
return [].concat(dbot.access.admin(event), dbot.access.moderator(event), dbot.access.power_user(event),
|
||||
_.chain(event.channel.nicks)
|
||||
.filter(function(nick) {
|
||||
return nick.op == true || nick.voice == true;
|
||||
})
|
||||
.pluck('name')
|
||||
.value());
|
||||
}
|
||||
};
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
|
@ -1,8 +1,9 @@
|
||||
var _ = require('underscore')._;
|
||||
var _ = require('underscore')._,
|
||||
request = require('request');
|
||||
|
||||
var commands = function(dbot) {
|
||||
return {
|
||||
'~usage': function(event) {
|
||||
var commands = {
|
||||
'usage': function(event) {
|
||||
var commandName = event.params[1];
|
||||
if(_.has(dbot.usage, commandName)) {
|
||||
event.reply(dbot.t('usage', {
|
||||
@ -16,26 +17,52 @@ var commands = function(dbot) {
|
||||
}
|
||||
},
|
||||
|
||||
'~help': function(event) {
|
||||
var moduleName = event.params[1];
|
||||
if(!_.has(dbot.modules, moduleName)) {
|
||||
var moduleName = dbot.commands[moduleName].module;
|
||||
}
|
||||
|
||||
if(moduleName && _.has(dbot.config[moduleName], 'help')) {
|
||||
var help = dbot.config[moduleName].help;
|
||||
event.reply(dbot.t('help_link', {
|
||||
'module': moduleName,
|
||||
'link': help
|
||||
'~commands': function(event) {
|
||||
var name = event.params[1];
|
||||
if(_.has(dbot.modules, name)) {
|
||||
var commands = _.keys(dbot.commands);
|
||||
commands = _.filter(commands, function(cName) {
|
||||
return dbot.commands[cName].module == name;
|
||||
});
|
||||
event.reply(dbot.t('module_commands', {
|
||||
'module': name,
|
||||
'commands': commands.join(', ')
|
||||
}));
|
||||
} else {
|
||||
if(!moduleName) {
|
||||
moduleName = event.params[1];
|
||||
}
|
||||
event.reply(dbot.t('no_help', { 'module': moduleName }))
|
||||
event.reply(dbot.t('loaded_modules', {
|
||||
'modules': _.keys(dbot.modules).join(', ')
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
'~help': function(event) {
|
||||
var moduleName = event.params[1];
|
||||
if(!moduleName || !_.has(dbot.modules, moduleName)) {
|
||||
event.reply(dbot.t('usage', {
|
||||
'command': this.config.commandPrefix + 'help',
|
||||
'usage': this.config.commandPrefix + 'help [module]'
|
||||
}));
|
||||
event.reply(dbot.t('loaded_modules', {
|
||||
'modules': _.keys(dbot.modules).join(', ')
|
||||
}));
|
||||
} else {
|
||||
var helpLink = dbot.config.repoRoot +
|
||||
'blob/master/modules/' + moduleName + '/README.md';
|
||||
if(dbot.config.modules[moduleName].help) {
|
||||
helpLink = dbot.config.modules[moduleName].help;
|
||||
}
|
||||
|
||||
// TODO: Check it exists
|
||||
event.reply(dbot.t('help_link', {
|
||||
'module': moduleName,
|
||||
'link': helpLink
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
commands['usage'].regex = [/usage ([^ ]+)/, 2];
|
||||
|
||||
return commands;
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
|
@ -1,5 +1,9 @@
|
||||
{
|
||||
"ignorable": false,
|
||||
"help": "http://github.com/reality/depressionbot/blob/master/modules/command/README.md",
|
||||
"dbKeys": [ "ignores" ]
|
||||
"dependencies": [ "event", "ignore", "users" ],
|
||||
"useNickserv": false,
|
||||
"accessOutput": false,
|
||||
"banOutput": false,
|
||||
"passiveMode": false,
|
||||
"commandPrefix": "~"
|
||||
}
|
||||
|
@ -1,26 +1,78 @@
|
||||
{
|
||||
"command_ban": {
|
||||
"english": "{user} is banned from using this command. Commence incineration.",
|
||||
"spanish": "{user} está prohibido de usar esta instrucción. Comenzar incineración.",
|
||||
"en": "{user} is banned from using this command. Commence incineration.",
|
||||
"es": "{user} está prohibido de usar esta instrucción. urrently loaded modules now default: {modules}.",
|
||||
"na'vi": "Tsu'ori {user} ke tung. Nga skxawng lu.",
|
||||
"welsh": "Mae {user} wedi ei gohurio gan ddefnyddio'r gorchymun yma. Cychwyn orfflosgiad"
|
||||
"cy": "Mae {user} wedi ei gohurio gan ddefnyddio'r gorchymun yma. Cychwyn orfflosgiad",
|
||||
"nl": "{user} mag dit commando niet meer gebruiken. Bereid het verbrandingsritueel voor.",
|
||||
"de": "{user} darf diesen Befehl nicht benutzen. Verbrennung einleiten",
|
||||
"fr": "{user} est interdit d'utiliser cette commande. Commencer l'incinération.",
|
||||
"it": "A {user} è stato interdetto di utilizzare questo commando. Iniziare incenerimento."
|
||||
},
|
||||
"syntax_error": {
|
||||
"english": "Invalid syntax. Initiate incineration.",
|
||||
"spanish": "Sintaxis no válida. Iniciar incineración.",
|
||||
"syntax_error": {
|
||||
"en": "Invalid syntax. Initiate incineration.",
|
||||
"es": "Sintaxis no válida. Iniciar incineración.",
|
||||
"na'vi": "Ngeyä pamrel keyawr lu. Nga skxawng lu.",
|
||||
"welsh": "Cystrawen annilys. Cychwyn orfflosgiad"
|
||||
"cy": "Cystrawen annilys. Cychwyn orfflosgiad",
|
||||
"nl": "Ongeldige syntax. Bereid het verbrandingsritueel voor.",
|
||||
"de": "Syntax ungültig. Verbrennung einleiten",
|
||||
"fr": "Syntaxe invalide. Initier l'incinération.",
|
||||
"it": "Sintassi non valida. Iniziare incenerimento"
|
||||
},
|
||||
"usage": {
|
||||
"english": "Usage for {command}: {usage}."
|
||||
"en": "Usage for {command}: {usage}.",
|
||||
"na'vi": "Nga tsun sivar ìlä {command}: {usage}.",
|
||||
"cy": "Defnydd o {command}: {usage}.",
|
||||
"nl": "{command} wordt op de volgende manier gebruikt: {usage}.",
|
||||
"de": "Benutzung von {command}: [usage}.",
|
||||
"fr": "Utilisation de {command}: {usage}.",
|
||||
"it": "Utilizzo di {command}: {usage}."
|
||||
},
|
||||
"no_usage_info": {
|
||||
"english": "No usage information found for {command}."
|
||||
"en": "No usage information found for {command}.",
|
||||
"na'vi": "Oel ke tsun sivar {comamnd}it",
|
||||
"cy": "Ni chanfuwyd gwybodaeth am ddefnydd o {command}",
|
||||
"nl": "Geen gebruiksinformatie gevonden voor {command}.",
|
||||
"de": "Keine Gebrauchsanweisung gefunden für {command}.",
|
||||
"fr": "Aucune information d'utilisation trouvée pour {command}",
|
||||
"it": "Nessuna informazione d' utilizzo trovata per {command}"
|
||||
},
|
||||
"help_link": {
|
||||
"english": "Help for {module}: {link}"
|
||||
"en": "Help for {module}: {link}",
|
||||
"na'vi": "{module}ä srungìl {link} it tok",
|
||||
"cy": "Cymorth am {module}: {link}",
|
||||
"nl": "Hulp voor {module}: {link}",
|
||||
"de": "Hilfe für {module}: {link}",
|
||||
"fr": "Aide pour {module}: {link}",
|
||||
"it": "Aiuto per {module}: {link}"
|
||||
},
|
||||
"no_help": {
|
||||
"english": "No help found for {module}."
|
||||
"en": "No help found for {module}.",
|
||||
"na'vi": "Fì{module}ìri oel ke tsun run srungit",
|
||||
"cy": "Ni chanfuwyd cymorth am {module}",
|
||||
"nl": "Geen hulp gevonden voor {module}.",
|
||||
"de": "Keine Hilfe gefunden für {module}.",
|
||||
"fr": "Aucune aide trouvée pour {module}.",
|
||||
"it": "Nessun aiuto trovato per {module}."
|
||||
},
|
||||
"loaded_modules": {
|
||||
"en": "Loaded modules: {modules}.",
|
||||
"cy": "Modiwlau sy'n lwythodd: {modules}.",
|
||||
"nl": "Geladen modules: {modules}.",
|
||||
"de": "Module geladen: {modules}.",
|
||||
"fr": "Modules chargés: {modules}.",
|
||||
"it": "Moduli caricati: {modules}."
|
||||
},
|
||||
"access_denied": {
|
||||
"en": "{user}: You don't have the access level to run this command.",
|
||||
"fr": "{user}: Vous n'avez pas le niveau d'accès requis pour utiliser cette commande.",
|
||||
"it": "{user}: Non hai il livello d'accesso neccessario per utilizzare questo commando.",
|
||||
"de": "{user}: Du hast nicht die notwendigen Rechte um diesen Befehl zu benutzen."
|
||||
},
|
||||
"module_commands": {
|
||||
"en": "Commands in {module}: {commands}.",
|
||||
"fr": "Commandes de {module}: {commands}.",
|
||||
"it": "Commandi di {module}: {commands}.",
|
||||
"de": "Befehle in {module}: {commands}."
|
||||
}
|
||||
}
|
||||
|
4
modules/command/usage.json
Normal file
4
modules/command/usage.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"~usage": "~usage [command]",
|
||||
"~help": "~help [module]"
|
||||
}
|
15
modules/crypto/README.md
Normal file
15
modules/crypto/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
## Crypto
|
||||
|
||||
Cryptography!
|
||||
|
||||
### Description
|
||||
This module calculates different hashes or ciphertexts for some algorithms.
|
||||
|
||||
### Commands
|
||||
|
||||
#### ~hash [algorithm] [text]
|
||||
Calculate the hash of the given text using [algorithm].
|
||||
|
||||
#### ~random [number]
|
||||
Gives [number] bytes of cryptographically strong pseudo-random data as hex string.
|
||||
|
44
modules/crypto/crypto.js
Normal file
44
modules/crypto/crypto.js
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Module Name: Crypto
|
||||
* Description: Allows the magic of cryptography to take place.
|
||||
*/
|
||||
|
||||
var cr = require('crypto');
|
||||
|
||||
var crypto = function(dbot) {
|
||||
this.commands = {
|
||||
'~hash': function(event) {
|
||||
var hash = event.params[1];
|
||||
try {
|
||||
var h = cr.createHash(hash);
|
||||
var tohash = event.params.splice(2, event.params.length-1).join(' ');
|
||||
h.update(tohash);
|
||||
event.reply(hash+" of \""+tohash+"\" is: "+h.digest('hex'));
|
||||
} catch(err) {
|
||||
event.reply(err);
|
||||
}
|
||||
},
|
||||
'~randomdata': function(event) {
|
||||
try {
|
||||
var count = parseInt(event.params[1]);
|
||||
if(count > 222) {
|
||||
event.reply("Sorry man, I can't paste that much random data.");
|
||||
return;
|
||||
}
|
||||
cr.randomBytes(count, function(err,buf) {
|
||||
if(err) {
|
||||
event.reply(err);
|
||||
return;
|
||||
}
|
||||
event.reply(buf.toString('hex'));
|
||||
});
|
||||
} catch (err) {
|
||||
event.reply(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new crypto(dbot);
|
||||
};
|
6
modules/crypto/usage.json
Normal file
6
modules/crypto/usage.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"~md5": "~md5 [text]",
|
||||
"~sha1": "~sha1 [text]",
|
||||
"~sha256": "~sha256 [text]",
|
||||
"~aes": "~aes \"[text]\" \"[key]\""
|
||||
}
|
3
modules/cspeed/config.json
Normal file
3
modules/cspeed/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"outputChannel": "#realitree"
|
||||
}
|
62
modules/cspeed/cspeed.js
Normal file
62
modules/cspeed/cspeed.js
Normal file
@ -0,0 +1,62 @@
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var cspeed = function(dbot) {
|
||||
this.watches = dbot.db.cspeed;
|
||||
this.outputChannel = dbot.config.modules.cspeed.outputChannel;
|
||||
this.counts = {};
|
||||
|
||||
this.api = {
|
||||
'getCounts': function(callback) {
|
||||
callback(this.counts);
|
||||
}
|
||||
};
|
||||
this.api['getCounts'].external = true;
|
||||
this.api['getCounts'].extMap = [ 'callback' ];
|
||||
|
||||
this.commands = {
|
||||
'addlpmwatch': function(event) {
|
||||
var channel = event.params[1];
|
||||
var key = event.server + '.' + channel;
|
||||
|
||||
if(!_.has(this.watches, key)) {
|
||||
this.watches[key] = {
|
||||
'server': event.server,
|
||||
'channel': channel
|
||||
}; // to be extended with warn nums etc
|
||||
|
||||
this.counts[key] = 0;
|
||||
dbot.api.timers.addTimer(60000, function() {
|
||||
dbot.say(event.server, this.outputChannel, channel + ' currently : ' + this.counts[key] + ' LPM');
|
||||
this.counts[key] = 0;
|
||||
}.bind(this));
|
||||
|
||||
event.reply('Added speed watch for ' + channel);
|
||||
} else {
|
||||
event.reply('Already watching that channel');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.listener = function(event) {
|
||||
var key = event.server + '.' + event.channel;
|
||||
if(_.has(this.watches, key)) {
|
||||
this.counts[key]++;
|
||||
}
|
||||
}.bind(this);
|
||||
this.on = 'PRIVMSG';
|
||||
|
||||
this.onLoad = function() {
|
||||
var watches = dbot.db.cspeed;
|
||||
_.each(watches, function(watch) {
|
||||
var key = watch.server + '.' + watch.channel;
|
||||
this.counts[key] = 0;
|
||||
dbot.api.timers.addTimer(60000, function() {
|
||||
dbot.say(watch.server, dbot.db.cspeed.outputChannel, watch.channel + ': ' + this.counts[key] + 'LPM');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new cspeed(dbot);
|
||||
};
|
5
modules/ctcp/README.md
Normal file
5
modules/ctcp/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
## CTCP
|
||||
Responds to CTCP commands.
|
||||
|
||||
## Description
|
||||
At the moment only CTCP VERSION, CTCP PING is handled in JSBot.
|
3
modules/ctcp/config.json
Normal file
3
modules/ctcp/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"ignorable": true
|
||||
}
|
39
modules/ctcp/ctcp.js
Normal file
39
modules/ctcp/ctcp.js
Normal file
@ -0,0 +1,39 @@
|
||||
var ctcp = function(dbot) {
|
||||
this.listener = function(event) {
|
||||
var matches = event.message.match(/\u0001[\w]+\u0001/);
|
||||
if(matches) {
|
||||
// We need the CTCP command
|
||||
var question = matches[0];
|
||||
// Cut \u0001 characters from command
|
||||
question = question.slice(1,question.length-1);
|
||||
switch(question) {
|
||||
case 'CLIENTINFO':
|
||||
event.replyNotice("\u0001CLIENTINFO SOURCE VERSION USERINFO\u0001");
|
||||
break;
|
||||
case 'FINGER':
|
||||
event.replyNotice("\u0001FINGER STOP FINGERING ME BRO\u0001");
|
||||
break;
|
||||
case 'SOURCE':
|
||||
event.replyNotice("\u0001SOURCE "+dbot.config.repoRoot+"\u0001");
|
||||
break;
|
||||
case 'TIME':
|
||||
var d = new Date();
|
||||
event.replyNotice("\u0001TIME "+d.toISOString()+"\u0001");
|
||||
break;
|
||||
case 'USERINFO':
|
||||
event.replyNotice("\u0001USERINFO "+dbot.config.name+"\u0001");
|
||||
break;
|
||||
case 'VERSION':
|
||||
event.replyNotice("\u0001VERSION "+dbot.config.version+"\u0001");
|
||||
break;
|
||||
default:
|
||||
event.replyNotice("\u0001"+question+" Idk what you want. Try CLIENTINFO.\u0001");
|
||||
}
|
||||
}
|
||||
};
|
||||
this.on = 'PRIVMSG';
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new ctcp(dbot);
|
||||
};
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"username": "youruserhere",
|
||||
"password": "yourpasswordhere",
|
||||
"dependencies": [ "command" ],
|
||||
"dependencies": [ "link" ],
|
||||
"ignorable": true,
|
||||
"dentQuotes": false
|
||||
"dentQuotes": true,
|
||||
"api": "http://quitter.se/"
|
||||
}
|
||||
|
@ -2,19 +2,27 @@ var request = require('request');
|
||||
_ = require('underscore')._;
|
||||
|
||||
var dent = function(dbot) {
|
||||
this.dbot = dbot;
|
||||
this.StatusRegex = {
|
||||
identica: /\bhttps?:\/\/identi\.ca\/notice\/(\d+)\b/ig,
|
||||
twitter: /\bhttps?:\/\/twitter\.com\/\w+\/status\/(\d+)\b/ig
|
||||
};
|
||||
|
||||
this.StatusAPI = {
|
||||
identica: "http://identi.ca/api/statuses/show.json",
|
||||
twitter: "https://api.twitter.com/1/statuses/show.json"
|
||||
};
|
||||
|
||||
this.api = {
|
||||
'post': function(content) {
|
||||
var username = dbot.config.dent.username,
|
||||
password = dbot.config.dent.password,
|
||||
var username = this.config.username,
|
||||
password = this.config.password,
|
||||
info,
|
||||
auth = "Basic " +
|
||||
auth = "Basic " +
|
||||
new Buffer(username + ":" + password).toString("base64");
|
||||
|
||||
request.post({
|
||||
'url': 'http://identi.ca/api/statuses/update.json?status=' +
|
||||
content,
|
||||
'url': this.config.api + '/statuses/update.json?status=' +
|
||||
escape(content),
|
||||
'headers': {
|
||||
'Authorization': auth
|
||||
}
|
||||
@ -25,18 +33,40 @@ var dent = function(dbot) {
|
||||
}
|
||||
};
|
||||
|
||||
this.lookup = function(id, service, callback) {
|
||||
request({
|
||||
url: this.StatusAPI[service],
|
||||
qs: {"id": id},
|
||||
json: true
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
if (_.has(body, 'text')) {
|
||||
callback(service + " [" + body.user.screen_name + '] ' + body.text);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
'~dent': function(event) {
|
||||
this.api.post(event.input[1]);
|
||||
event.reply('Dent posted (probably).');
|
||||
}
|
||||
};
|
||||
this.commands['~dent'].regex = [/^~dent (.+)$/, 2];
|
||||
this.commands['~dent'].regex = [/^dent (.+)$/, 2];
|
||||
|
||||
this.onLoad = function() {
|
||||
if(dbot.config.dent.dentQuotes === true && _.has(dbot.modules, 'quotes')) {
|
||||
dbot.api.command.addHook('~qadd', function(key, text) {
|
||||
this.api.post(key + ': ' + text);
|
||||
if(this.config.dentQuotes === true && _.has(dbot.modules, 'quotes')) {
|
||||
dbot.api.event.addHook('qadd', function(key, text) {
|
||||
if(text.indexOf('~~') == -1) {
|
||||
this.api.post(key + ': ' + text);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
for(s in this.StatusRegex) {
|
||||
dbot.api.link.addHandler(s, this.StatusRegex[s], function(matches, name, callback) {
|
||||
this.lookup(matches[1], name, callback);
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this);
|
||||
|
@ -17,14 +17,14 @@ var parseDiceSpec = function (specString) {
|
||||
var normalizeDiceSpec = function (specString) {
|
||||
var diceSpec = parseDiceSpec(specString);
|
||||
|
||||
if (diceSpec["sides"] > 10000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (diceSpec["count"] > 1000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (diceSpec["sides"] > 10000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (diceSpec["count"] > 1000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (diceSpec["count"] > 1) {
|
||||
var count = diceSpec["count"];
|
||||
} else {
|
||||
@ -74,7 +74,7 @@ var dice = function(dbot) {
|
||||
event.reply(rolls[i][0] + ": invalid dice spec");
|
||||
} else {
|
||||
if (rolls[i][1].length > 1) {
|
||||
var total = " (total " + rolls[i][1].sum();
|
||||
var total = " (total " + _.reduce(rolls[i][1], function(memo, num){ return memo + num; }, 0);
|
||||
if (rolls[i][2] != 0) {
|
||||
if (rolls[i][2] > 0) {
|
||||
total += " + ";
|
||||
|
17
modules/dns/README.md
Normal file
17
modules/dns/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
## DNS
|
||||
|
||||
Performs and reports upon basic DNS functions.
|
||||
|
||||
### Description
|
||||
|
||||
This module utilises the domain name system to discover basic information about
|
||||
domain names and IP addresses.
|
||||
|
||||
### Commands
|
||||
|
||||
#### ~lookup [domain name]
|
||||
Looks up the specified domain name in the domain name system. If a match is found,
|
||||
the first corresponding A or AAAA record is displayed.
|
||||
#### ~rdns [IP address]
|
||||
Looks up the specified IP address in the domain name system. If a match is found,
|
||||
the first corresponding rDNS domain name is displayed.
|
96
modules/dns/dns.js
Normal file
96
modules/dns/dns.js
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Module Name: DNS
|
||||
* Description: Performs and reports on basic DNS functions.
|
||||
*/
|
||||
var dnsm = require('dns'),
|
||||
request = require('request'),
|
||||
http = require('http');
|
||||
|
||||
var dns = function(dbot) {
|
||||
if(!_.has(dbot.db, 'ip')) {
|
||||
dbot.db.ip = {};
|
||||
}
|
||||
var ips = dbot.db.ip;
|
||||
|
||||
this.api = {
|
||||
'getGeoIp': function(ip, callback) {
|
||||
if(_.has(ips, ip)) {
|
||||
body = ips[ip];
|
||||
callback(ip + ' is located in '+ body.city + ', ' + body.country + '. Hostname: ' + body.hostname + '. ISP: ' + body.org);
|
||||
} else {
|
||||
request.get('http://ipinfo.io/'+ip, {
|
||||
'json': true
|
||||
}, function(err, res, body) {
|
||||
if(!err && body) {
|
||||
callback(ip + ' is located in '+ body.city + ', ' + body.country + '. Hostname: ' + body.hostname + '. ISP: ' + body.org);
|
||||
} else {
|
||||
callback('No info about ' + ip);
|
||||
}
|
||||
ips[ip] = body;
|
||||
});
|
||||
}
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
var commands = {
|
||||
'~lookup': function(event) {
|
||||
domain = event.params[1];
|
||||
dnsm.lookup(domain, function (error, addr) {
|
||||
if (error) {
|
||||
event.reply(dbot.t("lookup-error",{"domain": domain, "code": error.code}));
|
||||
} else {
|
||||
event.reply(dbot.t("lookup",{"domain": domain, "address": addr}));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'~rdns': function(event) {
|
||||
ip = event.params[1];
|
||||
dnsm.reverse(ip, function (error, domain) {
|
||||
if (error) {
|
||||
event.reply(dbot.t("rdns-error",{"domain": domain, "ip": ip, "error": error.code}));
|
||||
} else {
|
||||
event.reply(dbot.t("rdns",{"domain": domain, "ip": ip}));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'~geoip': function(event) {
|
||||
var ip = event.params[1];
|
||||
this.api.getGeoIp(ip, function(result) { event.reply(result); });
|
||||
},
|
||||
|
||||
'~dnsbl': function(event) {
|
||||
var revIp = event.input[1].trim().split('.').reverse().join('.');
|
||||
dnsm.lookup(revIp + '.cbl.abuseat.org', function(err, res) {
|
||||
if(!err && res) {
|
||||
event.reply(event.input[1] + ' is listed as an abusive IP.');
|
||||
} else {
|
||||
event.reply(event.input[1] + ' does not seem to be a Naughty Nancy.');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
commands['~dnsbl'].regex = [/^dnsbl ([\d\w\s\.-]*)/, 2];
|
||||
this.commands = commands;
|
||||
|
||||
if(dbot.config.modules.dns.dnsblconn == true) {
|
||||
this.listener = function(event) {
|
||||
if(event.message.match('CLICONN')) {
|
||||
var ip = event.message.match('CLICONN ([^ ]+).*?((?:[0-9]{1,3}\.){3}[0-9]{1,3}) users');
|
||||
revIp = ip[2].trim().split('.').reverse().join('.');
|
||||
dbot.say(event.server, '#dnsbl', 'DEBUG: Looking up ' + ip[2] + ' for ' + ip[1] + ' @ ' + revIp);
|
||||
dnsm.lookup(revIp + '.cbl.abuseat.org', function(err, res) {
|
||||
if(!err && res) {
|
||||
dbot.say(event.server, '#dnsbl', 'ALERT: ' + ip[1] + ' connecting from ' + ip[2] + ' may well be NAUGHTY.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.on = 'NOTICE';
|
||||
}
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new dns(dbot);
|
||||
};
|
32
modules/dns/strings.json
Normal file
32
modules/dns/strings.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"lookup-error": {
|
||||
"en": "{domain} is \u000303AVAILABLE! \u000314({code})",
|
||||
"cy": "{domain} \u000303AR GAEL! \u000314({code})",
|
||||
"nl": "{domain} is \u000303BESCHIKBAAR! \u000314({code})",
|
||||
"de": "{domain} ist \u000303VERFÜGBAR! \u000314({code})",
|
||||
"fr": "{domain} est \u000303DISPONIBLE! \u000314({code})",
|
||||
"it": "{domain} è \u000303DISPONIBILE! \u000314({code})"
|
||||
},
|
||||
"lookup": {
|
||||
"en": "{domain} is \u000305TAKEN! \u000314({address})",
|
||||
"cy": "Dydy {domain} \u000305DDIM AR GAEL! \u000314({address})",
|
||||
"nl": "{domain} is \u000305BEZET! \u000314({address})",
|
||||
"de": "{domain} ist \u000305BELEGT! \u000314({address})",
|
||||
"fr": "{domain} est \u000305PRIS! \u000314({address})",
|
||||
"it": "{domain} èt \u000305RISERVATO! \u000314({address})"
|
||||
},
|
||||
"rdns": {
|
||||
"en": "{ip} \u2192 {domain}",
|
||||
"fr": "{ip} \u2192 {domain}",
|
||||
"it": "{ip} \u2192 {domain}",
|
||||
"de":"{ip} \u2192 {domain}"
|
||||
},
|
||||
"rdns-error": {
|
||||
"en": "Unable to lookup {ip}. \u000314({error})",
|
||||
"cy": "Methu am-edrych {ip}. \u000314({error})",
|
||||
"nl": "{ip} kan niet worden opgezocht. \u000314({error})",
|
||||
"de": "Kann {ip} nicht auflösen. \u000314({error})",
|
||||
"fr": "Impossible de rechercher {ip}. \u000314({error})",
|
||||
"it": "Non è possibile cercare {ip}. \u000314({error})"
|
||||
}
|
||||
}
|
9
modules/flashy/README.md
Normal file
9
modules/flashy/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
## Flashy
|
||||
|
||||
Make big flashy text at people.
|
||||
|
||||
### Commands
|
||||
|
||||
#### ~flashy [color] [message]
|
||||
Give a link to a page hosted by the flashy module which produces big flashing
|
||||
text in the given colour.
|
45
modules/flashy/flashy.js
Normal file
45
modules/flashy/flashy.js
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Module Name: Flashy
|
||||
* Description: Makes pages with flashing text and that innit.
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var flashy = function(dbot) {
|
||||
this.colourMap = {
|
||||
'white': 'FFFFFF',
|
||||
'red': 'FF0000',
|
||||
'green': '00FF00',
|
||||
'blue': '0000FF',
|
||||
'yellow': 'FFFF00',
|
||||
'pink': 'FFC0CB',
|
||||
'magenta': 'FF00FF',
|
||||
'purple': 'AA00FF',
|
||||
'cyan': '00FFFF',
|
||||
'orange': 'FFAA00',
|
||||
'lime': 'AAFF00',
|
||||
'grey': 'AAAAAA',
|
||||
'infrared': '000000'
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
'~flashy': function(event) {
|
||||
var colour = event.input[1];
|
||||
var text = event.input[2].trim().toUpperCase();
|
||||
|
||||
if(_.has(this.colourMap, colour)) {
|
||||
event.reply(dbot.api.web.getUrl('flashy/' + colour + '/' +
|
||||
encodeURIComponent(text)));
|
||||
} else {
|
||||
var possibleColours = _.keys(this.colourMap).join(', ') + '.';
|
||||
event.reply('No such colour, brah. Available colours are: ' + possibleColours);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.commands['~flashy'].regex = [/^flashy ([^ ]+) (.+)$/, 3];
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new flashy(dbot);
|
||||
};
|
21
modules/flashy/pages.js
Normal file
21
modules/flashy/pages.js
Normal file
@ -0,0 +1,21 @@
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var pages = function(dbot) {
|
||||
return {
|
||||
'/flashy/:colour/:text': function(req, res) {
|
||||
if(!_.has(this.colourMap, req.params.colour)) {
|
||||
req.params.colour = 'red';
|
||||
}
|
||||
var colour = this.colourMap[req.params.colour];
|
||||
res.render('flashy', {
|
||||
'name': dbot.config.name,
|
||||
'colour': colour,
|
||||
'text': decodeURIComponent(req.params.text)
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return pages(dbot);
|
||||
};
|
4
modules/food/config.json
Normal file
4
modules/food/config.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"outputPrefix": "\u000311food\u000f",
|
||||
"api_key": "http://food2fork.com/about/api"
|
||||
}
|
60
modules/food/food.js
Normal file
60
modules/food/food.js
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Module name: Food
|
||||
* Description: recipe search
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._,
|
||||
request = require('request');
|
||||
|
||||
var food = function(dbot) {
|
||||
this.commands = {
|
||||
'~recipe': function(event) {
|
||||
request.get('http://food2fork.com/api/search', {
|
||||
'qs': {
|
||||
'key': this.config.api_key,
|
||||
'q': event.input[1]
|
||||
},
|
||||
'json': true
|
||||
}, function(error, response, body) {
|
||||
if(_.isObject(body) && _.has(body, 'recipes') && body.recipes.length > 0) {
|
||||
var num = _.random(0, body.recipes.length - 1),
|
||||
recipe = body.recipes[num];
|
||||
|
||||
event.reply(dbot.t('recipe', {
|
||||
'title': recipe.title,
|
||||
'link': recipe.source_url
|
||||
}));
|
||||
} else {
|
||||
event.reply(dbot.t('no_recipe'));
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
this.commands['~recipe'].regex = [/^recipe (.+)$/, 2];
|
||||
|
||||
this.listener = function(event) {
|
||||
var match = event.message.match(new RegExp(dbot.config.name + ': what should i (have|eat|make)\\??( for (dinner|lunch|breakfast))?\\??', 'i'));
|
||||
if(match) {
|
||||
var page = _.random(0, 200);
|
||||
request.get('http://food2fork.com/api/search', {
|
||||
'qs': {
|
||||
'key': this.config.api_key,
|
||||
'page': page
|
||||
},
|
||||
'json': true
|
||||
}, function(error, response, body) {
|
||||
if(_.isObject(body) && _.has(body, 'recipes') && body.recipes.length > 0) {
|
||||
var num = _.random(0, body.recipes.length - 1),
|
||||
recipe = body.recipes[num];
|
||||
|
||||
event.reply(event.user + ': You should make ' + recipe.title + '. See: ' + recipe.source_url);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this);
|
||||
this.on = 'PRIVMSG';
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new food(dbot);
|
||||
};
|
8
modules/food/strings.json
Normal file
8
modules/food/strings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"recipe": {
|
||||
"en": "{title} - {link}"
|
||||
},
|
||||
"no_recipe": {
|
||||
"en": "No recipes found."
|
||||
}
|
||||
}
|
34
modules/fpx/README.md
Normal file
34
modules/fpx/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
## 500px
|
||||
|
||||
Adds various 500px functionality.
|
||||
|
||||
### Description
|
||||
|
||||
This module provides a command which allows users to search for a random popular 500px photo.
|
||||
|
||||
### Dependencies
|
||||
|
||||
It has following dependencies:
|
||||
+ [node-500px](https://github.com/ro-ka/node-500px)
|
||||
|
||||
### config.json
|
||||
|
||||
ignorable and consumerKey has to be configurated. It can be obtained at http://developers.500px.com
|
||||
```
|
||||
{
|
||||
"ignorable": true,
|
||||
"api_key": "CONSUMERKEY_HERE"
|
||||
}
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
|
||||
~r500px
|
||||
Responds with a random popular 500px photo.
|
||||
Example:
|
||||
+ ~r500px
|
||||
|
||||
### TODO
|
||||
|
||||
Photo by user etc.
|
4
modules/fpx/config.json
Normal file
4
modules/fpx/config.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"ignorable": true,
|
||||
"api_key": "CONSUMERKEY_HERE"
|
||||
}
|
33
modules/fpx/fpx.js
Normal file
33
modules/fpx/fpx.js
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Module Name: 500px
|
||||
* Description: Adds various 500px functionality.
|
||||
* Requires: node-500px [http://mjgil.github.io/five-px/]
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._,
|
||||
API500px = require('500px').API500px;
|
||||
|
||||
var fpx = function(dbot) {
|
||||
this.commands = {
|
||||
'~r500px': function(event) {
|
||||
var random = Math.floor(Math.random() * 30);
|
||||
this.api500px.photos.getPopular({'sort': 'created_at', 'rpp': '30'}, function(error, results) {
|
||||
if (error) {
|
||||
event.reply(dbot.t('5px_error'));
|
||||
console.log(error);
|
||||
} else {
|
||||
var name = results.photos[random].name,
|
||||
id = results.photos[random].id;
|
||||
event.reply(dbot.t('5px_result',{'name':name,'id':id}));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
this.onLoad = function() {
|
||||
this.api500px = new API500px(this.config.api_key);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new fpx(dbot);
|
||||
};
|
8
modules/fpx/strings.json
Normal file
8
modules/fpx/strings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"5px_result":{
|
||||
"en": "{name} - http://500px.com/photo/{id}"
|
||||
},
|
||||
"5px_error": {
|
||||
"en": "Something went wrong :( Example: '~r500px'"
|
||||
}
|
||||
}
|
3
modules/fpx/usage.json
Normal file
3
modules/fpx/usage.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"~r500px": "~r500px"
|
||||
}
|
@ -1 +0,0 @@
|
||||
Subproject commit d8f3039e4e636e0fc82f87028b26f1e622273dd5
|
18
modules/github/LICENSE
Normal file
18
modules/github/LICENSE
Normal file
@ -0,0 +1,18 @@
|
||||
Copyright (c) 2013 Douglas Gardner <douglas@chippy.ch>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
modules/github/README.md
Normal file
28
modules/github/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
## Github
|
||||
|
||||
Grabs interesting data from the GitHub API.
|
||||
|
||||
### Description
|
||||
|
||||
This module for [depressionbot](https://github.com/reality/depressionbot) takes some interesting information about Github and parses it in a pleasing manner.
|
||||
|
||||
### Configuration
|
||||
#### defaultrepo
|
||||
When repository information is lacking from the command, this repository will be used.
|
||||
#### sortorder
|
||||
Defines the behaviour of ~issue when no arguments are given. Options are ``created``, ``updated``, or ``comments``.
|
||||
### Commands
|
||||
#### ~commits
|
||||
Returns the number of commits in the repository of the current depressionbot instance.
|
||||
#### ~gstatus
|
||||
Returns the [current status of Github](https://status.github.com), and a message explaining the current state of affairs.
|
||||
#### ~issue (user/repo) [id]
|
||||
Gives information about the isse pecified, from the default repository if one is not explicitly stated.
|
||||
#### ~milestone [milestone name]
|
||||
Returns milestone progress for any given milestone, with a link to the milestone in question.
|
||||
#### ~repo (repo name)
|
||||
Returns information about the repo given as a parameter. The repo should be specified as ``user/name``; for example, ``twitter/snowflake``.
|
||||
#### ~repocount [user]
|
||||
Returns the number of public Github repositories for the specified user.
|
||||
### Dependencies
|
||||
* [request](https://github.com/mikeal/request/):``$ npm install request``
|
8
modules/github/config.json
Normal file
8
modules/github/config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": [ "command" ],
|
||||
"ignorable": true,
|
||||
"help": "http://github.com/zuzak/dbot-github/blob/master/README.md",
|
||||
"defaultrepo": "reality/dbot",
|
||||
"sortorder": "updated",
|
||||
"useragent": "reality/depressionbot github module"
|
||||
}
|
192
modules/github/github.js
Normal file
192
modules/github/github.js
Normal file
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Module Name: Github
|
||||
* Description: Retrieves interesting Github information
|
||||
*/
|
||||
var request = require('request'),
|
||||
exec = require('child_process').exec;
|
||||
|
||||
var github = function(dbot) {
|
||||
this.api = {
|
||||
"githubStatus": function(callback){
|
||||
var reqUrl = "https://status.github.com/api/last-message.json";
|
||||
request({"url": reqUrl, "headers": {"User-Agent": this.config.useragent}}, function(error, response, body) {
|
||||
callback(JSON.parse(body));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var commands = {
|
||||
'~repocount': function(event) {
|
||||
var reqUrl = "https://api.github.com/users/" + event.params[1] + "/repos";
|
||||
request({"url": reqUrl, "headers": {"User-Agent": this.config.useragent}}, function(error, response, body) {
|
||||
if(response.statusCode == "200") {
|
||||
var result = JSON.parse(body);
|
||||
event.reply(dbot.t("repocount",{"user": event.params[1], "count": result.length}));
|
||||
} else {
|
||||
event.reply(dbot.t("usernotfound"));
|
||||
}
|
||||
});
|
||||
},
|
||||
'~repo': function(event) {
|
||||
var repo = event.params[1];
|
||||
if (typeof repo == 'undefined') {
|
||||
repo = this.config.defaultrepo;
|
||||
}
|
||||
|
||||
var reqUrl = "https://api.github.com/";
|
||||
reqUrl += "repos/" + repo;
|
||||
request({"url": reqUrl, "headers": {"User-Agent": this.config.useragent}}, function(error, response, body) {
|
||||
|
||||
var data = JSON.parse(body);
|
||||
if (data["fork"]) {
|
||||
event.reply(dbot.t("forkedrepo",data));
|
||||
} else {
|
||||
event.reply(dbot.t("unforkedrepo",data));
|
||||
}
|
||||
// TODO: move this shizz into an api call
|
||||
var longurl = "http://github.com/" + repo;
|
||||
event.reply(dbot.t('location')+" "+longurl);
|
||||
});
|
||||
},
|
||||
'~gstatus': function(event) {
|
||||
data = this.api.githubStatus(function(data){
|
||||
event.reply(dbot.t("status"+data["status"]));
|
||||
event.reply(data["body"]);
|
||||
}.bind(this));
|
||||
},
|
||||
'~milestone': function(event) {
|
||||
var repo = this.config.defaultrepo;
|
||||
var name = event.params[1];
|
||||
if (event.params[2]){
|
||||
repo = name;
|
||||
name = event.params[2];
|
||||
}
|
||||
var reqUrl = "https://api.github.com/repos/";
|
||||
reqUrl += repo + "/milestones";
|
||||
|
||||
request({"url": reqUrl, "headers":{"User-Agent": this.config.useragent}}, function(error, response, body) {
|
||||
var data = JSON.parse(body);
|
||||
for (var section in data) {
|
||||
var milestone = data[section];
|
||||
if (milestone["title"] == name){
|
||||
var str = "Milestone " + milestone["title"];
|
||||
var progress = milestone["closed_issues"] / (milestone["open_issues"] + milestone["closed_issues"]);
|
||||
progress = Math.round(progress*100);
|
||||
var bar = "[";
|
||||
for (var i = 10; i < 100; i += 10) {
|
||||
if ((progress/i) > 1) {
|
||||
bar += "█";
|
||||
} else {
|
||||
bar += " ";
|
||||
}
|
||||
}
|
||||
bar += "]";
|
||||
str += " is " + bar + progress + "% complete";
|
||||
|
||||
event.reply(str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
'~repocount': function(event) {
|
||||
// TODO: add handling for non existent user
|
||||
var reqUrl = "https://api.github.com/users/" + event.params[1] + "/repos";
|
||||
request({"url": reqUrl,"headers": { "User-Agent": this.config.useragent}}, function(error, response, body) {
|
||||
var result = JSON.parse(body);
|
||||
event.reply(event.params[1] + " has " + result.length + " public repositories.");
|
||||
});
|
||||
},
|
||||
'~grate': function(event) {
|
||||
request.get({"url":"https://api.github.com/rate_limit", "headers":{"User-Agent": this.config.useragent}}, function(error, response, body) {
|
||||
var data = JSON.parse(body);
|
||||
if (data.message){
|
||||
event.reply(data.message);
|
||||
} else {
|
||||
event.reply(data.rate.remaining + " requests of " + data.rate.limit + " remaining.");
|
||||
}
|
||||
});
|
||||
},
|
||||
'~issue': function(event) {
|
||||
var repo, issue, randflag;
|
||||
if (isNaN(event.params[1]) && event.params[1]){ // if ~issue foo/bar
|
||||
repo = event.params[1];
|
||||
issue = event.params[2];
|
||||
} else {
|
||||
repo = this.config.defaultrepo;
|
||||
issue = event.params[1];
|
||||
}
|
||||
|
||||
if (issue == "*" || issue == "random" || issue == "0") {
|
||||
issue = "";
|
||||
randflag = true;
|
||||
} else if (!issue) { // issue is undefined
|
||||
issue = "";
|
||||
} else {
|
||||
issue = "/" + issue; // got to be a better way
|
||||
}
|
||||
|
||||
var reqUrl = "https://api.github.com/repos/" + repo + "/issues" +
|
||||
issue + "?sort=" + this.config.sortorder;
|
||||
request.get({"url": reqUrl, headers: { "User-Agent": this.config.useragent}}, function(error,response, body) {
|
||||
if (response.statusCode == "200") {
|
||||
var data = JSON.parse(body);
|
||||
if (!issue){
|
||||
if (randflag) {
|
||||
data = data[Math.floor(Math.random() * data.length)];
|
||||
} else {
|
||||
data = data[0];
|
||||
}
|
||||
}
|
||||
if (_.has(data["pull_request"], "html_url")){
|
||||
data["pull_request"] = " with code";
|
||||
} else {
|
||||
data["pull_request"] = "";
|
||||
}
|
||||
if (data["state"]=="open") {
|
||||
data["state"] = "\u000303" + data["state"];
|
||||
} else {
|
||||
data["state"] = "\u000304" + data["state"];
|
||||
}
|
||||
var labels = "";
|
||||
for (var i=0; i < data["labels"].length; i++) { // for-in doesn't like me
|
||||
var color = "\u0003" + (parseInt(data["labels"][i]["color"],16) % 15);
|
||||
labels += " " + color + data["labels"][i]["name"];
|
||||
}
|
||||
data["label"] = labels;
|
||||
event.reply(dbot.t("issue",data));
|
||||
event.reply(data["html_url"]);
|
||||
} else {
|
||||
event.reply(dbot.t("issuenotfound"));
|
||||
}
|
||||
});
|
||||
},
|
||||
'~commits': function(event) {
|
||||
exec("git rev-list --all | wc -l", function(error, stdout, stderr) {
|
||||
stdout = stdout.trim();
|
||||
request({"url":"http://numbersapi.com/" + stdout + "?fragment&default=XXX"}, function(error, response, body){
|
||||
if (body != "XXX"){
|
||||
event.reply(dbot.t("commitcountfun",{"fact": body, "count": stdout}));
|
||||
} else {
|
||||
// nothing fun about the number, let's try the year
|
||||
request({"url":"http://numbersapi.com/" + stdout + "/year?fragment&default=XXX"}, function(error, response, body){
|
||||
if (body != "XXX"){
|
||||
event.reply(dbot.t("commitcountyear",{"fact": body, "count": stdout}));
|
||||
} else {
|
||||
event.reply(dbot.t("commitcountboring",{"count": stdout}));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
this.commands = commands;
|
||||
|
||||
this.on = 'PRIVMSG';
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new github(dbot);
|
||||
};
|
94
modules/github/strings.json
Normal file
94
modules/github/strings.json
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"repocount": {
|
||||
"en": "{user} has {count} public repos.",
|
||||
"cy": "Mae {count} archifdai cyhoeddus gan {user}.",
|
||||
"de": "{user} hat {count} öffnetliche Repos.",
|
||||
"fr": "{user} a {count} dépôt(s) public(s).",
|
||||
"it": "{user} ha {count} deposito/i pubblico/i."
|
||||
},
|
||||
"statusgood": {
|
||||
"en": "\u000309Shit's fine",
|
||||
"cy": "\u000309Cachu'n ddirwy",
|
||||
"de": "\u000309Alles in Ordnung",
|
||||
"fr": "\u000309Cette merde tourne bien",
|
||||
"it": "\u000309Funziona a meraviglia."
|
||||
},
|
||||
"statusminor": {
|
||||
"en": "\u000308Shit's touchy",
|
||||
"cy": "\u000308Cachu'n fregus",
|
||||
"de": "\u000308Kleinere Probleme vorhanden",
|
||||
"fr": "\u000308Cette merde a un petit problème",
|
||||
"it": "\u000308Piccoli problemi all' orizzonte"
|
||||
},
|
||||
"statusmajor": {
|
||||
"en": "\u000304Shit's fucked:",
|
||||
"cy": "\u000304Cachu wedi cyrraedd y ffan:",
|
||||
"de": "\u000304Du bist am Arsch",
|
||||
"fr": "\u000304Cette merde est foutue : ",
|
||||
"it": "\u000304Sei nella merda : "
|
||||
},
|
||||
"location": {
|
||||
"en": "You can find that shit at:",
|
||||
"cy": "Gallwch ddod o hyd y cachu yn:",
|
||||
"de": "Kann gefunden werden unter:",
|
||||
"fr": "Tu peux trouver cette merde ici : ",
|
||||
"it": "Puoi trovare questa coglionata a: "
|
||||
},
|
||||
"forkedrepo": {
|
||||
"en": "{name} is a forked {language} repo with {open_issues} unresolved issues [{forks}F {watchers}W]",
|
||||
"cy": "{name} ydy archif {language} fforchog gyda {open_issues} materion heb eu datrys [{forks}F {watchers}W]",
|
||||
"de": "{name} ist eine geteilte {language} Repo mit {open_issues} ungelösten Problemen [{forks}F {watchers}W]",
|
||||
"fr": "{name} est un dépôt fourché {language} avec {open_issues} problème(s) non résolu(s) [{forks}F {watchers}W]",
|
||||
"it": "{name} è un deposito biforcato {language} con {open_issues} problema/i irrisolto/i [{forks}F {watchers}W]"
|
||||
},
|
||||
"unforkedrepo": {
|
||||
"en": "{name} is a {language} repo with {open_issues} unresolved issues [{forks}F {watchers}W]",
|
||||
"cy": "{name} ydy archif {language} gyda {open_issues} materion heb eu datrys [{forks}F {watchers}W]",
|
||||
"de": "{name} ist eine {language} Repo mit {open_issues} ungelösten Problemen [{forks}F {watchers}W]",
|
||||
"fr": "{name} est un dépôt {language} avec {open_issues} problème(s) non résolu(s) [{forks}F {watchers}W]",
|
||||
"it": "{name} è un deposito {language} con {open_issues} problema/i irrisolto/i [{forks}F {watchers}W]"
|
||||
},
|
||||
"usernotfound": {
|
||||
"en": "User not found.",
|
||||
"cy": "Defnyddiwr heb ei ganfod.",
|
||||
"de": "Benutzer nicht gefunden.",
|
||||
"fr": "Utilisateur non trouvé.",
|
||||
"it": "Utente non trovato."
|
||||
},
|
||||
"issuenotfound": {
|
||||
"en": "Unable to find that issue.",
|
||||
"cy": "Wedi methu dod o hyd mater hwnnw",
|
||||
"de": "Kann dieses Problem nicht finden.",
|
||||
"fr": "Impossible de trouver ce problème.",
|
||||
"it": "Impossibile trovare questo problema."
|
||||
},
|
||||
"issue": {
|
||||
"en": "Issue \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} comments]{label}",
|
||||
"cy": "Mater \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} sylwadau]{label}",
|
||||
"de": "Problem \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} comments]{label}",
|
||||
"fr": "Problème \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} commentaires]{label}",
|
||||
"it": "Problema \u000308{number}\u0003: {title} [{state}{pull_request}\u000315; {comments} commento/i]{label}"
|
||||
},
|
||||
"commitcountboring": {
|
||||
"en": "My code has been committed {count} times.",
|
||||
"cy": "Mae fy cod wedi cael ei gyflawni ar {count} adegau.",
|
||||
"de": "Mein Code wurde {count} mal bestätigt.",
|
||||
"fr": "Mon code a été modifié {count} fois.",
|
||||
"it": "Il mio codice è stato modificato {count} volta/e."
|
||||
},
|
||||
"commitcountfun": {
|
||||
"en": "My repository has the same number of commits as {fact} ({count}).",
|
||||
"cy": "Yr un nifer o ymrwymo fel {fact} gan fy archif ({count}).",
|
||||
"de": "Meine Repository hat die gleiche Anzahl Commits wie {fact} ({count}).",
|
||||
"fr": "Mon dépôt a le même nombre de modifications que {fact} ({count}).",
|
||||
"it": "Il mio deposito ha lo stesso numero di modifiche come {fact} ({count})."
|
||||
},
|
||||
"commitcountyear": {
|
||||
"en": "My repository's commits number {count}, the year that {fact}.",
|
||||
"cy": "Nifer o ymrwymo gan fy archif: {count}, y flwyddyn y {fact}.",
|
||||
"de": "Anzahl der Commits in meinem Repository {count}, des Jahres {fact}",
|
||||
"fr": "Mon dépot compte {count} modifications, l'année où {fact}.",
|
||||
"it": "Il mio deposito ha {count} modifica/che, l' anno che {fact}."
|
||||
}
|
||||
}
|
||||
|
29
modules/gmaps/gmaps.js
Normal file
29
modules/gmaps/gmaps.js
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Module Name: Google Maps
|
||||
* Description: GMaps and ting
|
||||
*/
|
||||
var gm = require('googlemaps'),
|
||||
_ = require('underscore')._;
|
||||
|
||||
var gmaps = function(dbot) {
|
||||
this.commands = {
|
||||
'~from': function(event) {
|
||||
var from = event.input[1],
|
||||
to = event.input[2],
|
||||
departureNow = Math.floor((new Date()).getTime()/1000);
|
||||
|
||||
gm.directions(from, to, function(err, result) {
|
||||
if(!err && result && result.status !== 'ZERO_RESULTS') {
|
||||
event.reply('If you leave right now, it will take ' + result.routes[0].legs[0].duration.text + ' to get from ' + from + ' to ' + to + ' via public transport.');
|
||||
} else {
|
||||
event.reply('Apparently one cannot get from ' + from + ' to ' + to + ' using public transport. Do you accept the challenge?');
|
||||
}
|
||||
}, 'false', 'transit', null, null,null, null, null, departureNow);
|
||||
}
|
||||
};
|
||||
this.commands['~from'].regex = [/^from (.*) to (.*)/, 3];
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new gmaps(dbot);
|
||||
};
|
4
modules/goodreads/config.json
Normal file
4
modules/goodreads/config.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"api_key": "sethere",
|
||||
"outputPrefix": "\u00033goodreads\u000f"
|
||||
}
|
293
modules/goodreads/goodreads.js
Normal file
293
modules/goodreads/goodreads.js
Normal file
@ -0,0 +1,293 @@
|
||||
/**
|
||||
* Module Name: GoodReads
|
||||
* Description: Interacts with the GoodReads API to provide book-oriented functionality to dbot
|
||||
*/
|
||||
|
||||
var util = require('util'),
|
||||
_ = require('underscore')._,
|
||||
rp = require('request-promise-native'),
|
||||
parseString = util.promisify(require('xml2js').parseString);
|
||||
|
||||
var GoodReads = function(dbot) {
|
||||
this.apiRoot = 'https://www.goodreads.com';
|
||||
|
||||
this.internalAPI = {
|
||||
'outputError': (evt, e) => {
|
||||
switch(e) {
|
||||
case 'goodreads-error': evt.reply('Error talking to GoodReads.'); return;
|
||||
case 'book-not-found': evt.reply(dbot.t('gr_nobook')); return;
|
||||
case 'no-description': evt.reply('No description was found for the book you asked for.'); return;
|
||||
case 'author-not-found': evt.reply(dbot.t('gr_noauthor')); return;
|
||||
}
|
||||
|
||||
console.log(e);
|
||||
evt.reply('Something went wrong and I don\'t know what.');
|
||||
},
|
||||
|
||||
'formatProfile': profile => {
|
||||
var shelves = {};
|
||||
_.each(profile.user_shelves.user_shelf, shelf => {
|
||||
shelves[shelf.name] = shelf.book_count['_'];
|
||||
});
|
||||
profile.user_shelves = shelves;
|
||||
return profile;
|
||||
}
|
||||
};
|
||||
|
||||
this.api = {
|
||||
'findBook': async term => {
|
||||
//https://www.goodreads.com/search/index.xml
|
||||
var body = await rp({
|
||||
uri: this.apiRoot + '/search/index.xml',
|
||||
qs: {
|
||||
key: this.config.api_key,
|
||||
q: term.split(' ').join('+')
|
||||
}
|
||||
});
|
||||
|
||||
var response = await parseString(body, { explicitArray: false });
|
||||
if(!_.has(response, 'GoodreadsResponse')) throw 'goodreads-error';
|
||||
|
||||
var result = response.GoodreadsResponse.search.results;
|
||||
if(!result || !_.has(result, 'work')) throw 'book-not-found';
|
||||
if(!result.work[0]) throw 'book-not-found';
|
||||
|
||||
return {
|
||||
id: result.work[0].best_book.id['_'],
|
||||
title: result.work[0].best_book.title,
|
||||
author: result.work[0].best_book.author.name,
|
||||
rating: result.work[0].average_rating
|
||||
};
|
||||
},
|
||||
|
||||
'getSummaryForBook': async id => {
|
||||
//https://www.goodreads.com/book/show.xml
|
||||
var body = await rp({
|
||||
uri: this.apiRoot + '/book/show.xml',
|
||||
qs: {
|
||||
key: this.config.api_key,
|
||||
id: id
|
||||
}
|
||||
});
|
||||
|
||||
var response = await parseString(body, { explicitArray: false });
|
||||
if(!_.has(response, 'GoodreadsResponse')) throw 'goodreads-error';
|
||||
|
||||
var result = response.GoodreadsResponse.book;
|
||||
if(!result) throw 'book-not-found';
|
||||
if(!_.has(result, 'description')) throw 'no-description';
|
||||
|
||||
return result.description;
|
||||
},
|
||||
|
||||
'findAuthor': async term => {
|
||||
//https://www.goodreads.com/api/author_url/<ID>
|
||||
var body = await rp({
|
||||
url: this.apiRoot + '/api/author_url/' + term,
|
||||
qs: {
|
||||
key: this.config.api_key
|
||||
}
|
||||
});
|
||||
|
||||
var response = await parseString(body, {explicitArray: false });
|
||||
if(!_.has(response, 'GoodreadsResponse')) throw 'goodreads-error';
|
||||
|
||||
var result = response.GoodreadsResponse.author;
|
||||
if(!result) throw 'author-not-found';
|
||||
|
||||
return {
|
||||
id: result['$'].id,
|
||||
author: result.name
|
||||
};
|
||||
},
|
||||
|
||||
'getProfileById': async id => {
|
||||
//https://www.goodreads.com/user/show.xml
|
||||
try {
|
||||
var body = await rp({
|
||||
url: this.apiRoot + '/user/show.xml',
|
||||
qs: {
|
||||
key: this.config.api_key,
|
||||
id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
if(e.statusCode && e.statusCode == 404) {
|
||||
throw 'user-not-found';
|
||||
return;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
var response = await parseString(body, { explicitArray: false });
|
||||
if(!_.has(response, 'GoodreadsResponse')) throw 'goodreads-error';
|
||||
|
||||
var result = response.GoodreadsResponse.user;
|
||||
if(!result) throw 'user-not-found';
|
||||
|
||||
return this.internalAPI.formatProfile(result);
|
||||
},
|
||||
|
||||
'getProfileByName': async username => {
|
||||
//https://www.goodreads.com/user/show.xml
|
||||
try {
|
||||
var body = await rp({
|
||||
url: this.apiRoot + '/user/show.xml',
|
||||
qs: {
|
||||
key: this.config.api_key,
|
||||
username: username
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
if(e.statusCode && e.statusCode == 404) {
|
||||
throw 'user-not-found';
|
||||
return;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
var response = await parseString(body, { explicitArray: false });
|
||||
if(!_.has(response, 'GoodreadsResponse')) throw 'goodreads-error';
|
||||
|
||||
var result = response.GoodreadsResponse.user;
|
||||
if(!result) throw 'user-not-found';
|
||||
|
||||
return this.internalAPI.formatProfile(result);
|
||||
},
|
||||
|
||||
'getShelfForUserId': async (id, shelf) => {
|
||||
//https://www.goodreads.com/review/list.xml?v=2
|
||||
var body = await rp({
|
||||
url: this.apiRoot + '/review/list.xml',
|
||||
qs: {
|
||||
v: '2',
|
||||
key: this.config.api_key,
|
||||
id: id,
|
||||
shelf: shelf
|
||||
}
|
||||
});
|
||||
|
||||
var response = await parseString(body, { explicitArray: false });
|
||||
if(!_.has(response, 'GoodreadsResponse')) throw 'goodreads-error';
|
||||
|
||||
let result = response.GoodreadsResponse.reviews.review;
|
||||
if(!result) return [];
|
||||
|
||||
if(!_.isArray(result)) {
|
||||
result = [result];
|
||||
}
|
||||
|
||||
return _.map(result, r => {
|
||||
return {
|
||||
id: r.book.id['_'],
|
||||
title: r.book.title_without_series
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
'~book' : async evt => {
|
||||
try {
|
||||
var book = await this.api.findBook(evt.input[1]);
|
||||
evt.reply(dbot.t('gr_book', {
|
||||
author: book.author,
|
||||
title: book.title,
|
||||
rating: book.rating,
|
||||
link: this.apiRoot + '/book/show/' + book.id
|
||||
}));
|
||||
}
|
||||
catch(e) { this.internalAPI.outputError(evt, e); }
|
||||
},
|
||||
|
||||
'~booksummary': async evt => {
|
||||
try {
|
||||
var book = await this.api.findBook(evt.input[1]);
|
||||
var summary = await this.api.getSummaryForBook(book.id);
|
||||
evt.reply(dbot.t('gr_summary', {
|
||||
title: book.title,
|
||||
summary: summary,
|
||||
link: this.apiRoot + '/book/show/' + book.id
|
||||
}));
|
||||
}
|
||||
catch(e) { this.internalAPI.outputError(evt, e); }
|
||||
},
|
||||
|
||||
'~author' : async evt => {
|
||||
try {
|
||||
evt.reply(dbot.t('gr_author', await this.api.findAuthor(evt.input[1])));
|
||||
}
|
||||
catch(e) { this.internalAPI.outputError(evt, e); }
|
||||
},
|
||||
|
||||
'~reading': async (evt, profile) => {
|
||||
try {
|
||||
let books = await this.api.getShelfForUserId(profile.id, 'currently-reading');
|
||||
var booksCount = books.length;
|
||||
if(!booksCount) {
|
||||
evt.reply(dbot.t('gr_not_reading', { user: evt.rUser.currentNick }));
|
||||
return;
|
||||
}
|
||||
|
||||
let tooMany = booksCount > 5;
|
||||
if (tooMany) books = _.sample(books, 5);
|
||||
|
||||
evt.reply(dbot.t('gr_is_reading', { user: evt.rUser.currentNick, count: booksCount }));
|
||||
_.each(books, b => {
|
||||
evt.reply(ostr = b.title + ' - https://www.goodreads.com/book/show/' + b.id);
|
||||
});
|
||||
|
||||
if (tooMany) {
|
||||
evt.reply('... And ' + (booksCount - 5) + ' more - https://www.goodreads.com/review/list/' + profile.id + '?shelf=currently-reading');
|
||||
}
|
||||
}
|
||||
catch(e) { this.internalAPI.outputError(evt, e); }
|
||||
}
|
||||
};
|
||||
|
||||
this.commands['~book'].regex = [/^book (.*)/, 2];
|
||||
this.commands['~booksummary'].regex = [/^booksummary (.*)/, 2];
|
||||
this.commands['~author'].regex = [/^author ([\d\w\s-]*)/, 2];
|
||||
|
||||
this.commands['~reading'].requiresProfile = true;
|
||||
|
||||
_.each(this.commands, ((cmd, cmdName) => {
|
||||
if(cmd.requiresProfile) {
|
||||
this.commands[cmdName] = (async evt => {
|
||||
var grUsername = evt.rProfile.goodreads;
|
||||
|
||||
if(!grUsername) {
|
||||
evt.reply(evt.rUser.currentNick + ': Set a Goodreads username with "~set goodreads username"');
|
||||
return;
|
||||
}
|
||||
|
||||
let grId = evt.rProfile.goodreads_id;
|
||||
|
||||
try {
|
||||
var profile;
|
||||
if(grId) {
|
||||
profile = await this.api.getProfileById(grId);
|
||||
} else {
|
||||
profile = await this.api.getProfileByName(grUsername);
|
||||
grId = profile.id;
|
||||
dbot.api.profile.setProperty(evt.server, evt.user, 'goodreads_id', grId, function(){});
|
||||
}
|
||||
|
||||
await cmd(evt, profile);
|
||||
}
|
||||
catch(e) {
|
||||
if(e === 'user-not-found') evt.reply('User not found. Is your GoodReads username set correctly?');
|
||||
else this.internalAPI.outputError(evt, e);
|
||||
}
|
||||
}).bind(this);
|
||||
}
|
||||
}).bind(this))
|
||||
|
||||
}
|
||||
|
||||
|
||||
exports.fetch = dbot => new GoodReads(dbot);
|
23
modules/goodreads/strings.json
Normal file
23
modules/goodreads/strings.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"gr_book": {
|
||||
"en": "[{title} by {author} - {rating}] - {link}"
|
||||
},
|
||||
"gr_summary": {
|
||||
"en": "[{title}] - {summary} - {link}"
|
||||
},
|
||||
"gr_author": {
|
||||
"en": "[{author}] - https://www.goodreads.com/author/show/{id}"
|
||||
},
|
||||
"gr_nobook": {
|
||||
"en": "No book by that name was found."
|
||||
},
|
||||
"gr_noauthor": {
|
||||
"en": "No author by that name was found."
|
||||
},
|
||||
"gr_not_reading": {
|
||||
"en": "{user} is not currently reading any books."
|
||||
},
|
||||
"gr_is_reading": {
|
||||
"en": "{user} is currently reading the following {count} books:"
|
||||
}
|
||||
}
|
6
modules/goodreads/usage.json
Normal file
6
modules/goodreads/usage.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"~book": "~book [bookname] - returns the title, author, rating, and GoodReads link",
|
||||
"~booksummary": "~booksummary [bookname] - returns the summary for the requested book",
|
||||
"~author": "~author [authorname] - returns the GoodReads link for the requested author",
|
||||
"~reading": "~reading - displays up to 5 of the books you are currently reading"
|
||||
}
|
49
modules/ignore/api.js
Normal file
49
modules/ignore/api.js
Normal file
@ -0,0 +1,49 @@
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var api = function(dbot) {
|
||||
return {
|
||||
// Is user ignoring command/module?
|
||||
'isUserIgnoring': function(user, item, callback) {
|
||||
this.internalAPI.isUserImpeded(user, item, 'ignores', callback);
|
||||
},
|
||||
|
||||
// Is user banned from command/module?
|
||||
'isUserBanned': function(user, item, callback) {
|
||||
this.internalAPI.isUserImpeded(user, item, 'bans', callback);
|
||||
},
|
||||
|
||||
// Is channel ignoring module?
|
||||
// TODO: Command support
|
||||
'isChannelIgnoring': function(channelName, item, callback) {
|
||||
var isIgnoring = false,
|
||||
channel = false;
|
||||
|
||||
this.db.search('channel_ignores', {
|
||||
'server': server,
|
||||
'name': channel
|
||||
}, function(result) {
|
||||
channel = result;
|
||||
}, function(err) {
|
||||
if(!err && channel && _.include(channel.ignores, item)) {
|
||||
isIgnoring = true;
|
||||
}
|
||||
callback(isIgnoring);
|
||||
});
|
||||
},
|
||||
|
||||
// Resolve a nick and return their user and ignores object
|
||||
'getUserIgnores': function(user, callback) {
|
||||
this.db.read('ignores', user.id, function(err, ignores) {
|
||||
if(!err && ignores) {
|
||||
callback(false, ignores);
|
||||
} else {
|
||||
callback(true, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return api(dbot);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"ignorable": false,
|
||||
"dependencies": [ "command" ],
|
||||
"dbKeys": [ "ignores" ],
|
||||
"help": "http://github.com/reality/depressionbot/blob/master/modules/ignore/README.md"
|
||||
"dependencies": [ "users" ],
|
||||
"dbKeys": [ "ignores", "bans" ],
|
||||
"dbType": "redis"
|
||||
}
|
||||
|
@ -5,15 +5,35 @@
|
||||
* this information, since that actually performs the ignorance. Also provides
|
||||
* commands for moderators to choose the bot to ignore certain channels.
|
||||
*/
|
||||
var _ = require('underscore')._;
|
||||
var _ = require('underscore')._,
|
||||
databank = require('databank'),
|
||||
uuid = require('node-uuid'),
|
||||
NoSuchThingError = databank.NoSuchThingError;
|
||||
|
||||
var ignore = function(dbot) {
|
||||
this.internalAPI = {
|
||||
'isUserImpeded': function(user, item, by, callback) {
|
||||
this.api.getUserIgnores(user, function(err, ignores) {
|
||||
var isImpeded = false;
|
||||
if(!err && ignores) {
|
||||
if(_.has(dbot.commands, item) && !_.include(ignores[by], item)) {
|
||||
item = dbot.commands[item].module;
|
||||
}
|
||||
if(_.include(ignores[by], item) || _.include(ignores[by], '*')) {
|
||||
isImpeded = true;
|
||||
}
|
||||
}
|
||||
callback(isImpeded);
|
||||
});
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
var commands = {
|
||||
'~ignore': function(event) {
|
||||
var module = event.params[1];
|
||||
var ignorableModules = _.chain(dbot.modules)
|
||||
.filter(function(module, name) {
|
||||
return dbot.config[module].ignorable === true;
|
||||
return dbot.config.modules[module].ignorable === true;
|
||||
})
|
||||
.pluck('name')
|
||||
.value();
|
||||
@ -24,22 +44,31 @@ var ignore = function(dbot) {
|
||||
'modules': ignorableModules.join(', ')
|
||||
}));
|
||||
} else {
|
||||
if(_.include(ignorableModules, module)) {
|
||||
if(_.has(dbot.db.ignores, event.user) && _.include(dbot.db.ignores[event.user], module)) {
|
||||
event.reply(dbot.t('already_ignoring', { 'user': event.user }));
|
||||
} else {
|
||||
if(_.has(dbot.db.ignores, module)) {
|
||||
dbot.db.ignores[event.user].push(module);
|
||||
} else {
|
||||
dbot.db.ignores[event.user] = [module];
|
||||
if(module == '*' || _.include(ignorableModules, module)) {
|
||||
this.api.getUserIgnores(event.rUser, function(err, ignores) {
|
||||
if(!ignores) {
|
||||
ignores = {
|
||||
'id': event.rUser.id,
|
||||
'ignores': [],
|
||||
'bans': []
|
||||
};
|
||||
}
|
||||
|
||||
dbot.instance.ignoreTag(event.user, module);
|
||||
event.reply(dbot.t('ignored', {
|
||||
'user': event.user,
|
||||
'module': module
|
||||
}));
|
||||
}
|
||||
if(!_.include(ignores.ignores, module)) {
|
||||
ignores.ignores.push(module);
|
||||
this.db.save('ignores', event.rUser.id, ignores, function(err) {
|
||||
if(!err) {
|
||||
dbot.instance.ignoreTag(event.user, module);
|
||||
event.reply(dbot.t('ignored', {
|
||||
'user': event.user,
|
||||
'module': module
|
||||
}));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('already_ignoring', { 'user': event.user }));
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply(dbot.t('invalid_ignore', { 'user': event.user }));
|
||||
}
|
||||
@ -47,77 +76,192 @@ var ignore = function(dbot) {
|
||||
},
|
||||
|
||||
'~unignore': function(event) {
|
||||
var ignoredModules = [];
|
||||
if(_.has(dbot.db.ignores, event.user)) {
|
||||
ignoredModules = dbot.db.ignores[event.user];
|
||||
}
|
||||
var module = event.params[1];
|
||||
|
||||
if(_.isUndefined(module)) {
|
||||
event.reply(dbot.t('unignore_usage', {
|
||||
'user': event.user,
|
||||
'modules': ignoredModules.join(', ')
|
||||
}));
|
||||
} else {
|
||||
if(_.include(ignoredModules, module)) {
|
||||
dbot.db.ignores[event.user].splice(dbot.db.ignores[event.user].indexOf(module), 1);
|
||||
dbot.instance.removeIgnore(event.user, module)
|
||||
event.reply(dbot.t('unignored', {
|
||||
'user': event.user,
|
||||
'module': module
|
||||
}));
|
||||
this.api.getUserIgnores(event.rUser, function(err, ignores) {
|
||||
if(err || !ignores || _.isUndefined(module)) {
|
||||
if(ignores) {
|
||||
event.reply(dbot.t('unignore_usage', {
|
||||
'user': event.user,
|
||||
'modules': ignores.ignores.join(', ')
|
||||
}));
|
||||
} else {
|
||||
event.reply(dbot.t('empty_unignore_usage', {
|
||||
'user': event.user
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
event.reply(dbot.t('invalid_unignore', { 'user': event.user }));
|
||||
if(_.include(ignores.ignores, module)) {
|
||||
ignores.ignores = _.without(ignores.ignores, module);
|
||||
this.db.save('ignores', event.rUser.id, ignores, function(err) {
|
||||
if(!err) {
|
||||
dbot.instance.removeIgnore(event.user, module)
|
||||
event.reply(dbot.t('unignored', {
|
||||
'user': event.user,
|
||||
'module': module
|
||||
}));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('invalid_unignore', { 'user': event.user }));
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'~ban': function(event) {
|
||||
var nick = event.input[1],
|
||||
item = event.input[2];
|
||||
|
||||
if(item == '*' || _.include(dbot.config.moduleNames, item) || _.include(dbot.commands, item)) {
|
||||
dbot.api.users.resolveUser(event.server, nick, function(err, user) {
|
||||
this.api.getUserIgnores(user, function(err, ignores) {
|
||||
if(!ignores) {
|
||||
ignores = {
|
||||
'id': user.id,
|
||||
'ignores': [],
|
||||
'bans': []
|
||||
};
|
||||
}
|
||||
|
||||
if(!_.include(ignores.bans, item)) {
|
||||
ignores.bans.push(item);
|
||||
this.db.save('ignores', user.id, ignores, function(err) {
|
||||
if(!err) {
|
||||
event.reply(dbot.t('banned_success', {
|
||||
'user': event.user,
|
||||
'banned': nick,
|
||||
'module': item
|
||||
}));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('already_banned', {
|
||||
'user': event.user,
|
||||
'banned': nick
|
||||
}));
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply(dbot.t('invalid_ban', { 'user': event.user }));
|
||||
}
|
||||
},
|
||||
|
||||
'~unban': function(event) {
|
||||
var nick = event.input[1],
|
||||
item = event.input[2];
|
||||
|
||||
dbot.api.users.resolveUser(event.server, nick, function(err, user) {
|
||||
this.api.getUserIgnores(user, function(err, ignores) {
|
||||
if(err || !ignores) {
|
||||
event.reply(dbot.t('invalid_unban', {
|
||||
'user': event.user,
|
||||
'banned': nick
|
||||
}));
|
||||
} else {
|
||||
if(_.include(ignores.bans, item)) {
|
||||
ignores.bans = _.without(ignores.bans, item);
|
||||
this.db.save('ignores', user.id, ignores, function(err) {
|
||||
event.reply(dbot.t('unbanned_success', {
|
||||
'user': event.user,
|
||||
'banned': nick,
|
||||
'module': item
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('invalid_unban', {
|
||||
'user': event.user,
|
||||
'banned': nick
|
||||
}));
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'~ignorechannel': function(event) {
|
||||
var channel = ((event.params[1] == '@') ? event.channel.name : event.params[1]);
|
||||
var module = event.params[2];
|
||||
var channelName = event.input[1],
|
||||
module = event.input[2];
|
||||
|
||||
// Ignoring the value of 'ignorable' at the moment
|
||||
if(_.include(dbot.config.moduleNames, module)) {
|
||||
if(!_.has(dbot.db.ignores, channel)) dbot.db.ignores[channel] = [];
|
||||
if(!_.include(dbot.db.ignores[channel], module)) {
|
||||
dbot.db.ignores[channel].push(module);
|
||||
dbot.instance.ignoreTag(channel, module);
|
||||
event.reply(dbot.t('ignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channel
|
||||
}));
|
||||
} else {
|
||||
event.reply(dbot.t('already_ignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channel
|
||||
}));
|
||||
}
|
||||
if(module == '*' || _.include(dbot.config.moduleNames, module)) {
|
||||
var channel = false;
|
||||
|
||||
this.db.search('channel_ignores', {
|
||||
'server': event.server,
|
||||
'name': channelName
|
||||
}, function(result) {
|
||||
channel = result;
|
||||
}, function(err) {
|
||||
if(!channel) {
|
||||
var id = uuid.v4();
|
||||
channel = {
|
||||
'id': id,
|
||||
'server': event.server,
|
||||
'name': channelName,
|
||||
'ignores': []
|
||||
};
|
||||
}
|
||||
|
||||
if(!_.include(channel.ignores, module)) {
|
||||
channel.ignores.push(module);
|
||||
this.db.save('channel_ignores', channel.id, channel, function(err) {
|
||||
dbot.instance.ignoreTag(channel.name, module);
|
||||
event.reply(dbot.t('ignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channelName
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('already_ignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channelName
|
||||
}));
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply(dbot.t('module_not_exist', { 'module': module }));
|
||||
}
|
||||
},
|
||||
|
||||
'~unignorechannel': function(event) {
|
||||
var channel = ((event.params[1] == '@') ? event.channel.name : event.params[1]);
|
||||
var module = event.params[2];
|
||||
var channelName = event.input[1],
|
||||
module = event.input[2],
|
||||
channel = false;
|
||||
|
||||
if(!_.has(dbot.db.ignores, channel)) dbot.db.ignores[channel] = [];
|
||||
if(_.include(dbot.db.ignores[channel], module)) {
|
||||
dbot.db.ignores[channel] = _.without(dbot.db.ignores[channel], module);
|
||||
dbot.instance.removeIgnore(channel, module);
|
||||
event.reply(dbot.t('unignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channel
|
||||
}));
|
||||
} else {
|
||||
event.reply(dbot.t('not_ignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channel
|
||||
}));
|
||||
}
|
||||
this.db.search('channel_ignores', {
|
||||
'server': event.server,
|
||||
'name': channelName
|
||||
}, function(result) {
|
||||
channel = result;
|
||||
}, function(err) {
|
||||
if(channel && _.include(channel.ignores, module)) {
|
||||
channel.ignores = _.without(channel.ignores, module);
|
||||
this.db.save('channel_ignores', channel.id, channel, function(err) {
|
||||
dbot.instance.removeIgnore(channel.name, module);
|
||||
event.reply(dbot.t('unignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channelName
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
event.reply(dbot.t('not_ignoring_channel', {
|
||||
'module': module,
|
||||
'channel': channelName
|
||||
}));
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
commands['~ban'].regex = [/^ban ([^ ]+) ([^ ]+)$/, 3];
|
||||
commands['~unban'].regex = [/^unban ([^ ]+) ([^ ]+)$/, 3];
|
||||
commands['~ignorechannel'].regex = [/^ignorechannel ([^ ]+) ([^ ]+)$/, 3];
|
||||
commands['~unignorechannel'].regex = [/^unignorechannel ([^ ]+) ([^ ]+)$/, 3];
|
||||
|
||||
commands['~ban'].access = 'power_user';
|
||||
commands['~unban'].access = 'power_user';
|
||||
commands['~ignorechannel'].access = 'moderator';
|
||||
commands['~unignorechannel'].access = 'moderator';
|
||||
|
||||
@ -125,12 +269,34 @@ var ignore = function(dbot) {
|
||||
|
||||
this.onLoad = function() {
|
||||
dbot.instance.clearIgnores();
|
||||
_.each(dbot.db.ignores, function(ignores, item) {
|
||||
_.each(ignores, function(ignore) {
|
||||
dbot.instance.ignoreTag(item, ignore);
|
||||
}, this);
|
||||
}, this);
|
||||
};
|
||||
|
||||
this.db.scan('ignores', function(ignores) {
|
||||
dbot.api.users.getUser(ignores.id, function(err, user) {
|
||||
if(user) {
|
||||
_.each(ignores.ignores, function(module) {
|
||||
dbot.instance.ignoreTag(user.currentNick, module);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, function(err) { });
|
||||
|
||||
this.db.scan('channel_ignores', function(channel) {
|
||||
_.each(channel.ignores, function(module) {
|
||||
dbot.instance.ignoreTag(channel.name, module);
|
||||
});
|
||||
}, function(err) { });
|
||||
|
||||
dbot.api.event.addHook('new_current_nick', function(user, oldNick) {
|
||||
this.api.getUserIgnores(user, function(err, ignores) {
|
||||
if(ignores) {
|
||||
_.each(ignores.ignores, function(module) {
|
||||
dbot.instance.removeIgnore(oldNick, module);
|
||||
dbot.instance.ignoreTag(user.currentNick, module);
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
|
@ -1,59 +1,183 @@
|
||||
{
|
||||
"ignore_usage": {
|
||||
"english": "{user}: Usage: ~ignore [module]. Modules you can ignore are: {modules}.",
|
||||
"spanish": "{user}: Modo de empleo: ~ignore [módulo]. Módulos que tú puedes ignorar son: {modules}.",
|
||||
"en": "{user}: Usage: ~ignore [module]. Modules you can ignore are: {modules}.",
|
||||
"es": "{user}: Modo de empleo: ~ignore [módulo]. Módulos que tú puedes ignorar son: {modules}.",
|
||||
"na'vi": "{user}: Sar: ~ignore ['u]. U, nga ke tìng mikyun: {modules}.",
|
||||
"welsh": "{user}: Defnydd: ~ignore [modiwl]. Modiwlau a allech anwybyddu yw: {modules}."
|
||||
"cy": "{user}: Defnydd: ~ignore [modiwl]. Modiwlau a allech anwybyddu yw: {modules}.",
|
||||
"nl": "{user}: Gebruik: ~ignore [module]. Modules die negeert kunnen worden zijn: {modules}.",
|
||||
"de": "{user}: Benutzung: ~ignore [module]. Module, die ausgeschaltet werden können: {modules}.",
|
||||
"fr": "{user}: Utilisation: ~ignore [module]. Les modules que vous pouvez ignorer sont: {modules}.",
|
||||
"it": "{user}: Uso: ~ignore [module]. I moduli che puoi ignorare sono: {modules}."
|
||||
},
|
||||
"already_ignoring": {
|
||||
"english": "{user}: You're already ignoring that module.",
|
||||
"spanish": "{user}: Ya ignoras este módulo.",
|
||||
"en": "{user}: You're already ignoring that module.",
|
||||
"es": "{user}: Ya ignoras este módulo.",
|
||||
"na'vi": "{user}: 'uri nga ke tìng mikyun srekrr.",
|
||||
"welsh": "{user}: Mi rwyt ti'n anwybyddu'r modiwl yna'n barod."
|
||||
"cy": "{user}: Mi rwyt ti'n anwybyddu'r modiwl yna'n barod.",
|
||||
"nl": "{user}: Je negeert deze module al.",
|
||||
"de": "{user}: Dieses Modul ist bereits ausgeschaltet.",
|
||||
"fr": "{user}: Vous ignorez déjà ce module.",
|
||||
"it": "{user}: Stai già ignorando questo modulo"
|
||||
},
|
||||
"ignored": {
|
||||
"english": "{user}: Now ignoring {module}.",
|
||||
"spanish": "{user}: Estás ignorando {module}.",
|
||||
"en": "{user}: Now ignoring {module}.",
|
||||
"es": "{user}: Estás ignorando {module}.",
|
||||
"na'vi": "{user}: Nga ke terìng mikyun {module}ne set.",
|
||||
"welsh": "{user}: Nawr yn anwybyddu {module}"
|
||||
"cy": "{user}: Nawr yn anwybyddu {module}",
|
||||
"nl": "{user}: {module} wordt nu genegeerd.",
|
||||
"de": "{user}: {module} wird nun ausgeschaltet.",
|
||||
"fr": "{user}: {module} désormais ignoré.",
|
||||
"it": "{user}: {module} sarà adesso ignorato"
|
||||
},
|
||||
"invalid_ignore": {
|
||||
"english": "{user}: That isn't a valid module name.",
|
||||
"spanish": "{user}: Ese no es un nombre de un módulo valido.",
|
||||
"en": "{user}: That isn't a valid module name.",
|
||||
"es": "{user}: Ese no es un nombre de un módulo valido.",
|
||||
"na'vi": "{user}: Tsatstxo eyawr ke lu.",
|
||||
"welsh": "{user}: Nid oedd hwna'n modiwl dilys"
|
||||
"cy": "{user}: Nid oedd hwna'n modiwl dilys",
|
||||
"nl": "{user}: Dat is geen geldige modulenaam.",
|
||||
"de": "{user}: Dies ist kein Name eines Moduls.",
|
||||
"fr": "{user}: Ceci ne correspond pas à un nom de module valide.",
|
||||
"it": "{user}: Questo non è un nome di modulo valido"
|
||||
},
|
||||
"unignore_usage": {
|
||||
"english": "{user}: Usage: ~unignore [module]. Modules you are currently ignoring: {modules}.",
|
||||
"spanish": "{user}: Modo de empleo: ~unignore [módulo]. Módulos que ignoras ahora mismo: {modules}.",
|
||||
"en": "{user}: Usage: ~unignore [module]. Modules you are currently ignoring: {modules}.",
|
||||
"es": "{user}: Modo de empleo: ~unignore [módulo]. Módulos que ignoras ahora mismo: {modules}.",
|
||||
"na'vi": "{user}: Sar: ~unignore ['u]. Uri, nga ke terìng mikyun: {modules}.",
|
||||
"welsh": "{user}: Defnydd ~unignore [modiwl]. Modiwlau rydech yn anwybyddu ar hyn o bryd: {modules}"
|
||||
"cy": "{user}: Defnydd: ~unignore [modiwl]. Modiwlau rydech yn anwybyddu ar hyn o bryd: {modules}",
|
||||
"nl": "{user}: Gebruik: ~unignore [module]. Modules die momenteel worden genegeerd: {modules}.",
|
||||
"de": "{user}: Benutzung: ~unignore [module]. Module, die im Moment ausgeschaltet sind: {modules}.",
|
||||
"fr": "{user}: Utilisation: ~unignore [module]. Modules que vous ignorez actuellement: {modules}.",
|
||||
"it": "{user}: Uso: ~unignore [module]. Module che ignori attualmente: {modules}."
|
||||
},
|
||||
"empty_unignore_usage": {
|
||||
"en": "{user}: Usage: ~unignore [module].",
|
||||
"es": "{user}: Modo de empleo: ~unignore [módulo].",
|
||||
"na'vi": "{user}: Sar: ~unignore ['u].",
|
||||
"cy": "{user}: Defnydd: ~unignore [modiwl].",
|
||||
"nl": "{user}: Gebruik: ~unignore [module].",
|
||||
"de": "{user}: Benutzung: ~unignore [module].",
|
||||
"fr": "{user}: Utilisation: ~unignore [module].",
|
||||
"it": "{user}: Uso: ~unignore [module]."
|
||||
},
|
||||
"invalid_unignore": {
|
||||
"english": "{user}: You're not ignoring that module or it doesn't exist.",
|
||||
"spanish": "{user}: No ignoras este módulo o no existe.",
|
||||
"en": "{user}: You're not ignoring that module or it doesn't exist.",
|
||||
"es": "{user}: No ignoras este módulo o no existe.",
|
||||
"na'vi":"{user}: Nga terìng mikyun fu fì'ul fìtsengit ke tok.",
|
||||
"welsh": "{user}: Nid wyt ti'n anwybyddu'r modiwl yna neu nid yw e'n bodoli"
|
||||
"cy": "{user}: Nid wyt ti'n anwybyddu'r modiwl yna neu nid yw e'n bodoli",
|
||||
"nl": "{user}: Deze module bestaat niet of wordt niet genegeerd.",
|
||||
"de": "{user}: Dieses Modul ist entweder ausgeschaltet oder existiert nicht.",
|
||||
"fr": "{user}: Vous n'ignorez pas ce module ou il n'existe pas.",
|
||||
"it": "{user}: Non stai ignorando questo modulo o non esiste."
|
||||
},
|
||||
"unignored": {
|
||||
"english": "{user}: No longer ignoring {module}.",
|
||||
"spanish": "{user}: Ya no ignoras {module}.",
|
||||
"en": "{user}: No longer ignoring {module}.",
|
||||
"es": "{user}: Ya no ignoras {module}.",
|
||||
"na'vi": "{user}: Nga terìng mikyun {module}ne set",
|
||||
"welsh": "{user}: Ddim yn anwybyddu {module} bellach"
|
||||
"cy": "{user}: Ddim yn anwybyddu {module} bellach",
|
||||
"nl": "{user}: {module} wordt niet langer genegeerd.",
|
||||
"de": "{user}: {module} ist nicht länger ausgeschaltet.",
|
||||
"fr": "{user}: {module} n'est plus ignoré à présent.",
|
||||
"it": "{user}: {module} non viene attualmente più ignorato"
|
||||
},
|
||||
"ban_usage": {
|
||||
"en": "{user}: Usage: ~ban [user] [module/command]. Use * for all modules and commands.",
|
||||
"cy": "{user}: Defnydd: ~ban [defnyddiwr] [modiwl/gorchymyn]. Defnyddio * am pob modiwlau a gorchmynion.",
|
||||
"nl": "{user}: Gebruik: ~ban [gebruiker] [module/commando]. Gebruik * voor alle modules en alle commandos.",
|
||||
"de": "{user}: Benutzung ~ban [Benutzer] [module/Befehl]. Benutze * für alle Module und Befehle.",
|
||||
"fr": "{user}: Utilisation: ~ban [user] [module/command]. Utilisez * pour tous les modules et commandes.",
|
||||
"it": "{user}: Uso: ~ban [user] [module/command]. Utilizza * per tutti i moduli e commandi."
|
||||
},
|
||||
"already_banned": {
|
||||
"en": "{user}: {banned} is already banned from that module.",
|
||||
"cy": "{user}: {banned} eisoes wedi ei wahardd o'r modiwl.",
|
||||
"nl": "{user}: {banned} is al geband van deze module.",
|
||||
"de": "{user}: {banned} ist bereits von diesem Modul gebannt.",
|
||||
"fr": "{user}: {banned} est déjà interdit d'utiliser ce module.",
|
||||
"it": "{user}: {banned} è già bandito da usare questo modulo."
|
||||
},
|
||||
"banned_success": {
|
||||
"en": "{user}: {banned} is now banned from {module}.",
|
||||
"cy": "{user}: {banned} ei wahardd yn awr am {module}.",
|
||||
"nl": "{user}: {banned} mag {module} nu niet meer gebruiken.",
|
||||
"de": "{user}: {banned} ist nun von {module} gebannt.",
|
||||
"fr": "{user}: {banned} est maintenant interdit d'utiliser {module}.",
|
||||
"it": "{user}: {banned} è stato adesso bandito da usare {module}."
|
||||
},
|
||||
"invalid_ban": {
|
||||
"en": "{user}: That isn't a valid module name.",
|
||||
"cy": "{user}: Nid oedd hwna'n modiwl dilys",
|
||||
"nl": "{user}: Dat is geen geldige modulenaam.",
|
||||
"de": "{user}: Dies ist kein Name eines Moduls.",
|
||||
"fr": "{user}: Ceci n'est pas un nom de module valide.",
|
||||
"it": "{user}: Queso non è il nome di un modulo valido."
|
||||
},
|
||||
"unban_usage": {
|
||||
"en": "{user}: Usage: ~unban [user] [module].",
|
||||
"cy": "{user}: Defnydd: ~unban [defnyddiwr] [modiwl].",
|
||||
"nl": "{user}: Gebruik: ~unban [gebruiker] [module].",
|
||||
"de": "{user}: Benutzung: ~unban [Benutzer] [module].",
|
||||
"fr": "{user}: Utilisation: ~unban [user] [module].",
|
||||
"it": "{user}: Uso: ~unban [user] [module]."
|
||||
},
|
||||
"invalid_unban": {
|
||||
"en": "{user}: {banned} is not banned from that module or it doesn't exist.",
|
||||
"cy": "{user}: Nid oedd {banned} wedi ei wahardd o'r modiwl, neu nid yw'n bodoli.",
|
||||
"nl": "{user}: {banned} is niet geband van die module of de module bestaat niet.",
|
||||
"de": "{user}: {banned} ist von diesem Modul nicht gebannt, oder es existiert nicht.",
|
||||
"fr": "{user}: {banned} n'est pas interdit d'utiliser ce module, ou il n'existe pas.",
|
||||
"it": "{user}: {banned} non è stato bandito da questo modulo o non esiste."
|
||||
},
|
||||
"unbanned_success": {
|
||||
"en": "{user}: {banned} is no longer banned from {module}.",
|
||||
"cy": "{user}: Nid yw {banned} yn cael ei wahardd mwyach.",
|
||||
"nl": "{user}: {banned} mag {module} weer gebruiken.",
|
||||
"de": "{user}: {banned} wurde von {module} entbannt.",
|
||||
"fr": "{user}: {banned} n'est plus interdit d'utiliser {module}.",
|
||||
"it": "{user}: {banned} non è più bandito dall' utilizzare {module}."
|
||||
},
|
||||
"ignoring_channel": {
|
||||
"english": "Now ignoring {module} in {channel}"
|
||||
"en": "Now ignoring {module} in {channel}.",
|
||||
"na'vi": "Oe ke stayawm {module}ur mì {channel}",
|
||||
"cy": "Bellach yn anwybyddu {module} yn {channel}.",
|
||||
"nl": "{module} wordt nu genegeerd in {channel}.",
|
||||
"de": "{module} in {channel} ist nun ausgeschaltet.",
|
||||
"fr": "{module} dans {channel} maintenant ignoré.",
|
||||
"it": "{module} in {channel} viene adesso ignorato."
|
||||
},
|
||||
"already_ignoring_channel": {
|
||||
"english": "Already ignoring {module} in {channel}"
|
||||
"en": "Already ignoring {module} in {channel}.",
|
||||
"na'vi": "Oe ke stayawm {module}ur mì {channel} li",
|
||||
"cy": "Eisoes anwybyddu {module} yn {channel}",
|
||||
"nl": "{module} wordt al genegeerd in {channel}.",
|
||||
"de": "{module} in {channel} ist bereits ausgeschaltet.",
|
||||
"fr": "{module} dans {channel} déjà ignoré.",
|
||||
"it": "{module} in {channel} già ignorato."
|
||||
},
|
||||
"module_not_exist": {
|
||||
"english": "{module} isn't loaded or doesn't exist."
|
||||
"en": "{module} isn't loaded or doesn't exist.",
|
||||
"na'vi": "Oel ke omum teri {module}it fu {module} ke fkeytok",
|
||||
"cy": "Dydy {module} ddim yn lwythodd, neu ddim yn bodoli.",
|
||||
"nl": "{module} is niet geladen of bestaat niet.",
|
||||
"de": "{module} ist nicht geladen oder existiert nicht.",
|
||||
"fr": "{module} n'est pas chargé ou n'existe pas.",
|
||||
"it": "{module} non caricato o non esiste"
|
||||
},
|
||||
"unignoring_channel": {
|
||||
"english": "No longer ignoring {module} in {channel}"
|
||||
"en": "No longer ignoring {module} in {channel}.",
|
||||
"na'vi": "Oel stayawm {module}ur mì {channel} set.",
|
||||
"cy": "Nid anwybyddu {module} yn {channel} mwyach.",
|
||||
"nl": "{module} wordt niet meer genegeerd in {channel}.",
|
||||
"de": "{module} in {channel} ist nicht länger ausgeschaltet.",
|
||||
"fr": "{module} dans {channel} n'est plus ignoré à présent.",
|
||||
"it": "{module} in {channel} non viene più ignorato."
|
||||
},
|
||||
"not_ignoring_channel": {
|
||||
"english": "{module} wasn't being ignored in {channel}"
|
||||
"en": "{module} wasn't being ignored in {channel}.",
|
||||
"na'vi": "Oel stayawm {module}ur mì {channel} li.",
|
||||
"cy": "Nid yw {module} yn cael ei anwybyddu yn {channel}.",
|
||||
"nl": "{module} werd niet genegeerd in {channel}.",
|
||||
"de": "{module} war in {channel} nicht ausgeschaltet.",
|
||||
"fr": "{module} n'était pas ignoré dans {channel}.",
|
||||
"it": "{module} non veniva ignorato in {channel}"
|
||||
}
|
||||
}
|
||||
|
65
modules/imgur/README.md
Normal file
65
modules/imgur/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
## imgur
|
||||
|
||||
Various imgur functionality.
|
||||
|
||||
### Description
|
||||
|
||||
Posts information on imgur links which are pasted into the channel and provides
|
||||
functionality to generate a random imgur link.
|
||||
|
||||
### Config
|
||||
|
||||
#### imagelength: 5
|
||||
Length of slugs generated by the random imgur functionality.
|
||||
|
||||
#### nsfwwarn: true
|
||||
Warn that images generated by the ~ri command may be NSFW.
|
||||
|
||||
#### apikey
|
||||
Key to use with the imgur API.
|
||||
|
||||
#### highscore: ricount
|
||||
Quote category to use for a 'highscore;' used to run games with the web
|
||||
/random page, by storing a highscore based on some arbitrary rule in the
|
||||
chosen quote category (say, how far can you get before seeing a turtle). Then,
|
||||
on the imgur random page you can press 'c' to see a countdown towards the last
|
||||
stored value in the highscore quote category. If you beat the highscore, simply
|
||||
add the winning score to the quote category.
|
||||
|
||||
### Commands
|
||||
|
||||
#### ~ri
|
||||
Generate a random imgur image and post a link to it in the channel.
|
||||
|
||||
### API
|
||||
|
||||
#### getRandomImage(callback)
|
||||
Generate a random imgur image by generating random slugs and then testing for
|
||||
their existence until it finds one which exists (and hasn't been deleted).
|
||||
Callback is given with two parameters, the URL of the generated image, and the
|
||||
slug for the generated image.
|
||||
|
||||
#### getImageInfoString(slug, callback)
|
||||
Return a string containing info about the image with the given slug from the
|
||||
imgur API. Callback is called with one argument, the info string.
|
||||
|
||||
#### getImageInfo(slug, callback)
|
||||
Return data from the imgur API on an image with the given slug. Callback is
|
||||
called with one argument, the information returned by the API.
|
||||
|
||||
### Web
|
||||
|
||||
#### /imgur/random
|
||||
|
||||
A web page which loads a random image from imgur. You can press the space bar to
|
||||
load a new image, and information about the images are shown on the top-left of
|
||||
the page. You can press 'c' to view a highscore count (as documented above).
|
||||
|
||||
#### /imgur/stats
|
||||
|
||||
Show statistics on the total use of the imgur module.
|
||||
|
||||
### Hooks
|
||||
|
||||
#### link
|
||||
Posts information about an imgur link when one is linked in the channel.
|
14
modules/imgur/config.json
Normal file
14
modules/imgur/config.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"dbKeys": [ "imgur" ],
|
||||
"dependencies": [ "web", "api", "link" ],
|
||||
"imagelength": 5,
|
||||
"ricachelength": 7,
|
||||
"nsfwwarn": true,
|
||||
"apikey": "86fd3a8da348b65",
|
||||
"highscore": "ricount",
|
||||
"autoadd": {
|
||||
"e49e686582ce3f60cb51d00c10924861": "facebookman",
|
||||
"b11b634c74562bcd4e5d17b0d90987be": "raffleguy"
|
||||
},
|
||||
"outputPrefix": "\u00038imgur\u000f"
|
||||
}
|
330
modules/imgur/imgur.js
Normal file
330
modules/imgur/imgur.js
Normal file
@ -0,0 +1,330 @@
|
||||
/**
|
||||
* Module Name: imgur
|
||||
* Description: Various imgur functionality
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._,
|
||||
request = require('request'),
|
||||
async = require('async'),
|
||||
crypto = require('crypto'),
|
||||
humanise = require('humanize');
|
||||
|
||||
var imgur = function(dbot) {
|
||||
this.ApiRoot = 'https://api.imgur.com/3/';
|
||||
this.ExcludeRes = [
|
||||
{ 'w': 800, 'h': 600 },
|
||||
{ 'w': 1024, 'h': 768 },
|
||||
{ 'w': 1280, 'h': 768 },
|
||||
{ 'w': 1280, 'h': 960 },
|
||||
{ 'w': 1366, 'h': 768 },
|
||||
{ 'w': 1600, 'h': 900 },
|
||||
{ 'w': 1680, 'h': 1050 },
|
||||
{ 'w': 1920, 'h': 1080 },
|
||||
{ 'w': 1024, 'h': 640 }
|
||||
];
|
||||
this.riCache = [];
|
||||
|
||||
this.internalAPI = {
|
||||
'infoString': function(imgData) {
|
||||
info = '';
|
||||
if(!_.isUndefined(imgData) && _.has(imgData, 'data') && !_.isUndefined(imgData.data.type)) {
|
||||
imgData = imgData.data;
|
||||
if(imgData.title) {
|
||||
info += imgData.title + ' - ';
|
||||
}
|
||||
if(imgData.type) {
|
||||
if(imgData.animated) {
|
||||
info += 'an animated ' + imgData.type.split('/')[1] + ' with ';
|
||||
} else {
|
||||
info += 'a ' + imgData.type.split('/')[1] + ' with ';
|
||||
}
|
||||
} else {
|
||||
info += 'an image with ';
|
||||
}
|
||||
info += imgData.views + ' views (';
|
||||
info += imgData.width + 'x' + imgData.height + ')';
|
||||
|
||||
info += ' ('+humanise.filesize(imgData.size)+')';
|
||||
}
|
||||
|
||||
return info;
|
||||
}.bind(this),
|
||||
|
||||
'albumInfoString': function(albumData) {
|
||||
var info = '';
|
||||
if(!_.isUndefined(albumData) && _.has(albumData, 'data') && !_.isUndefined(albumData.data.id)) {
|
||||
albumData = albumData.data;
|
||||
if(albumData.title) {
|
||||
info += albumData.title + ' - ';
|
||||
}
|
||||
if(albumData.description) {
|
||||
info += albumData.description.split('\n')[0] + ' is ';
|
||||
}
|
||||
info += 'an album with ' + albumData.images_count + ' images ';
|
||||
info += 'and ' + albumData.views + ' views';
|
||||
if(albumData.nsfw) {
|
||||
info += ' - NSFW';
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}.bind(this),
|
||||
|
||||
'galleryInfoString': function(galData) {
|
||||
var info = '';
|
||||
if(!_.isUndefined(galData) && _.has(galData, 'data') && !_.isUndefined(galData.data.is_album)) {
|
||||
if(galData.data.is_album === true) {
|
||||
info = this.internalAPI.albumInfoString(galData);
|
||||
} else {
|
||||
info = this.internalAPI.infoString(galData);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
this.api = {
|
||||
'getRandomImage': function(callback) {
|
||||
var random = function(len) {
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
|
||||
return len ? chars.charAt(~~(Math.random()*chars.length)) + random(len-1) : "";
|
||||
};
|
||||
|
||||
var ext = [ 'gif', 'png', 'jpg' ];
|
||||
var testSlug = random(5);
|
||||
var testUrl = 'http://i.imgur.com/' +
|
||||
testSlug +
|
||||
'.' + ext[_.random(0, ext.length - 1)],
|
||||
fbman = false;
|
||||
dbot.db.imgur.totalHttpRequests += 1;
|
||||
|
||||
request(testUrl, function(error, response, body) {
|
||||
// 492 is body.length of a removed image
|
||||
if(!error && response.statusCode == 200 && body.length != 492) {
|
||||
dbot.db.imgur.totalImages += 1;
|
||||
var hash = crypto.createHash('md5').update(body).digest("hex");
|
||||
if(_.has(dbot.modules, 'quotes')){
|
||||
// autoadd: {"abcdef": "facebookman"}
|
||||
if(_.has(dbot.config.modules.imgur.autoadd,hash)){
|
||||
fbman = true;
|
||||
var category = this.config.autoadd[hash];
|
||||
if (_.contains(category, testUrl)){
|
||||
// there's probably less than 62^5 chance of this happening
|
||||
} else {
|
||||
dbot.api.quotes.addQuote(category, testUrl,
|
||||
dbot.config.name, function() { });
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(testUrl, testSlug, hash, fbman);
|
||||
} else {
|
||||
this.api.getRandomImage(callback);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'getGoodRandomImage': function(callback) {
|
||||
this.api.getRandomImage(function(url, slug, hash, fbman) {
|
||||
this.api.getImageInfo(slug, function(imgData) {
|
||||
if(!_.isUndefined(imgData) &&
|
||||
imgData.data &&
|
||||
imgData.data.height > 500 && imgData.data.width > 500 &&
|
||||
!_.any(this.ExcludeRes, function(res) {
|
||||
return imgData.data.height == res.h && imgData.data.width == res.w;
|
||||
})) {
|
||||
callback(url, imgData);
|
||||
} else if(fbman === true) {
|
||||
callback(url, imgData);
|
||||
} else {
|
||||
this.api.getGoodRandomImage(callback);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'getImageInfoString': function(slug, callback) {
|
||||
this.api.getImageInfo(slug, function(imgData) {
|
||||
callback(this.internalAPI.infoString(imgData));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'getImageInfo': function(slug, callback) {
|
||||
request.get({
|
||||
'url': 'https://api.imgur.com/3/image/' + slug + '.json',
|
||||
'json': true,
|
||||
'headers': {
|
||||
'Authorization': 'Client-ID ' + this.config.apikey
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
dbot.db.imgur.totalApiRequests += 1;
|
||||
callback(body);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'getAlbumInfo': function(slug, callback) {
|
||||
request.get({
|
||||
'url': 'https://api.imgur.com/3/album/' + slug + '.json',
|
||||
'json': true,
|
||||
'headers': {
|
||||
'Authorization': 'Client-ID ' + this.config.apikey
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
this.db.totalApiRequests += 1;
|
||||
callback(body);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'getGalleryInfo': function(slug, callback) {
|
||||
request.get({
|
||||
'url': 'https://api.imgur.com/3/gallery/' + slug + '.json',
|
||||
'json': true,
|
||||
'headers': {
|
||||
'Authorization': 'Client-ID ' + this.config.apikey
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
this.db.totalApiRequests += 1;
|
||||
callback(body);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
this.api['getRandomImage'].external = true;
|
||||
this.api['getRandomImage'].extMap = [ 'callback' ];
|
||||
this.api['getImageInfoString'].external = true;
|
||||
this.api['getImageInfoString'].extMap = [ 'slug', 'callback' ];
|
||||
|
||||
this.commands = {
|
||||
'~ri': function(event) {
|
||||
var local = event.user;
|
||||
if(event.params[1]) {
|
||||
local = event.params.splice(1, event.params.length - 1).join(' ').trim();
|
||||
}
|
||||
|
||||
var postImage = function(link, imgData) {
|
||||
var info = this.internalAPI.infoString(imgData);
|
||||
event.reply('['+this.config.outputPrefix + '] ' + local + ': ' + link + ' [' + info + ']');
|
||||
}.bind(this);
|
||||
var newCacheImage = function(link, imgData) {
|
||||
this.riCache.push([link, imgData]);
|
||||
}.bind(this);
|
||||
var callback = postImage;
|
||||
|
||||
if(this.riCache.length > 0) {
|
||||
var image = this.riCache.pop();
|
||||
postImage(image[0], image[1]);
|
||||
callback = newCacheImage;
|
||||
}
|
||||
|
||||
this.api.getGoodRandomImage(callback);
|
||||
},
|
||||
|
||||
// Legacy RI
|
||||
'~lri': function(event) {
|
||||
var local = event.user;
|
||||
if(event.params[1]) {
|
||||
local = event.params.splice(1, event.params.length - 1).join(' ').trim();
|
||||
}
|
||||
this.api.getRandomImage(function(link, slug) {
|
||||
this.api.getImageInfo(slug, function(imgData) {
|
||||
var info = this.internalAPI.infoString(imgData);
|
||||
event.reply('['+this.config.outputPrefix + '] ' + local + ': ' + link + ' [' + info + ']');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Super RI
|
||||
'~sri': function(event) {
|
||||
var local = event.user;
|
||||
if(event.params[1]) {
|
||||
local = event.params.splice(1, event.params.length - 1).join(' ').trim();
|
||||
}
|
||||
request.get({
|
||||
'url': this.ApiRoot + 'gallery/random/random/',
|
||||
'json': true,
|
||||
'headers': {
|
||||
'Authorization': 'Client-ID ' + this.config.apikey
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
if(!_.isUndefined(body) && body.data && body.data[0] != undefined) {
|
||||
var num = _.random(0, body.data.length - 1);
|
||||
this.api.getGalleryInfo(body.data[num].id, function(gal) {
|
||||
event.reply('['+this.config.outputPrefix + '] ' + local + ': ' + gal.data.link + ' [' +
|
||||
this.internalAPI.galleryInfoString(gal) + ']');
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'~imgur': function(event) {
|
||||
var term = event.input[1];
|
||||
request.get({
|
||||
'url': this.ApiRoot + 'gallery/search/',
|
||||
'json': true,
|
||||
'headers': {
|
||||
'Authorization': 'Client-ID ' + this.config.apikey
|
||||
},
|
||||
'qs': {
|
||||
'q': term
|
||||
}
|
||||
}, function(err, response, body) {
|
||||
if(!_.isUndefined(body) && body.data && body.data[0] != undefined) {
|
||||
var num = _.random(0, body.data.length - 1);
|
||||
this.api.getGalleryInfo(body.data[num].id, function(gal) {
|
||||
event.reply(dbot.t('imgurinfo', {
|
||||
'info': this.internalAPI.galleryInfoString(gal)
|
||||
}) + ' - ' + gal.data.link);
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply(dbot.t('imgur_noresults'));
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
this.commands['~imgur'].regex = [/^imgur ([\d\w\s-]*)/, 2];
|
||||
|
||||
this.onLoad = function() {
|
||||
var imgurHandler = function(matches, name, callback) {
|
||||
if(matches[1]) {
|
||||
var dataCallback = function(data) {
|
||||
var info;
|
||||
if(name == 'imgurimage') {
|
||||
info = this.internalAPI.infoString(data);
|
||||
} else if(name == 'imguralbum') {
|
||||
info = this.internalAPI.albumInfoString(data);
|
||||
} else if(name == 'imgurgallery') {
|
||||
info = this.internalAPI.galleryInfoString(data);
|
||||
}
|
||||
|
||||
if(info) callback(dbot.t('imgurinfo', { 'info': info }));
|
||||
}.bind(this);
|
||||
|
||||
if(name == 'imgurimage') {
|
||||
this.api.getImageInfo(matches[1], dataCallback);
|
||||
} else if(name == 'imguralbum') {
|
||||
this.api.getAlbumInfo(matches[1], dataCallback);
|
||||
} else if(name == 'imgurgallery') {
|
||||
this.api.getGalleryInfo(matches[1], dataCallback);
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
dbot.api.link.addHandler('imguralbum', /https?:\/\/imgur\.com\/a\/([a-zA-Z0-9]+)/, imgurHandler);
|
||||
dbot.api.link.addHandler('imgurgallery', /https?:\/\/imgur\.com\/gallery\/([a-zA-Z0-9]+)/, imgurHandler);
|
||||
dbot.api.link.addHandler('imgurimage', /https?:\/\/i\.imgur\.com\/([a-zA-Z0-9]+)\.([jpg|png|gif])/, imgurHandler);
|
||||
dbot.api.link.addHandler('imgurimage', /https?:\/\/imgur\.com\/([a-zA-Z0-9]+)/, imgurHandler);
|
||||
|
||||
async.times(this.config.ricachelength, function(n, next) {
|
||||
this.api.getGoodRandomImage(function(link, imgData) {
|
||||
this.riCache.push([ link, imgData ]);
|
||||
next();
|
||||
}.bind(this));
|
||||
}.bind(this), function() {});
|
||||
|
||||
if(!_.has(dbot.db.imgur, 'totalHttpRequests')) dbot.db.imgur.totalHttpRequests = 0;
|
||||
if(!_.has(dbot.db.imgur, 'totalApiRequests')) dbot.db.imgur.totalApiRequests = 0;
|
||||
if(!_.has(dbot.db.imgur, 'totalImages')) dbot.db.imgur.totalImages = 0;
|
||||
this.db = dbot.db.imgur;
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new imgur(dbot);
|
||||
}
|
23
modules/imgur/pages.js
Normal file
23
modules/imgur/pages.js
Normal file
@ -0,0 +1,23 @@
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var pages = function(dbot) {
|
||||
return {
|
||||
'/imgur/random': function(req, res) {
|
||||
var highScore = 0;
|
||||
res.render('imgurr', { "highscore" : highScore });
|
||||
},
|
||||
|
||||
'/imgur/stats': function(req, res) {
|
||||
res.render('imgurstats', {
|
||||
'name': dbot.config.name,
|
||||
'totalHttpRequests': this.db.totalHttpRequests,
|
||||
'totalApiRequests': this.db.totalApiRequests,
|
||||
'totalImages': this.db.totalImages
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return pages(dbot);
|
||||
};
|
22
modules/imgur/strings.json
Normal file
22
modules/imgur/strings.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"nsfw": {
|
||||
"en": "might be NSFW",
|
||||
"na'vi": "kxawm ke wivìntxu evengur",
|
||||
"cy": "Gallai fod yn anniogel ar gwaith",
|
||||
"nl": "bevat mogelijk gevoelige beelden",
|
||||
"de": "Könnte 18+ Material enthalten",
|
||||
"fr": "peut être risqué pour le travail (NSFW)",
|
||||
"it": "può essere rischioso al lavoro (NSFW)"
|
||||
},
|
||||
"imgurinfo": {
|
||||
"en": "[{info}]",
|
||||
"de": "[{info}]",
|
||||
"fr": "[{info}]",
|
||||
"it": "[{info}]"
|
||||
},
|
||||
"imgur_noresults": {
|
||||
"en": "No results found.",
|
||||
"de": "Kein Suchergebnis.",
|
||||
"it": "Nessun risultato"
|
||||
}
|
||||
}
|
@ -5,6 +5,5 @@
|
||||
}
|
||||
},
|
||||
"dependencies": [ "command" ],
|
||||
"ignorable": true,
|
||||
"help": "http://github.com/reality/depressionbot/blob/master/modules/js/README.md"
|
||||
"ignorable": true
|
||||
}
|
||||
|
@ -4,31 +4,38 @@
|
||||
* the channel. Also allows admins to run un-sandboxed Javascript code with
|
||||
* access to the DepressionBot instance memory.
|
||||
*/
|
||||
var vm = require('vm');
|
||||
var sbox = require('sandbox');
|
||||
var VM = require('vm2').VM;
|
||||
|
||||
var js = function(dbot) {
|
||||
var commands = {
|
||||
// Run JS code sandboxed, return result to channel.
|
||||
'~js': function(event) {
|
||||
try {
|
||||
var s = new sbox();
|
||||
s.run(event.input[1], function(output) {
|
||||
event.reply(output.result);
|
||||
}.bind(this));
|
||||
} catch(err) {}
|
||||
var s = new VM({timeout: 1000, sandbox: {}});
|
||||
event.reply(s.run(event.input[1]));
|
||||
} catch(err) { event.reply(err); }
|
||||
},
|
||||
|
||||
// Run JS code un-sandboxed, with access to DBot memory (admin-only).
|
||||
'~ajs': function(event) {
|
||||
var callback = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
for(var i=0;i<args.length;i++) {
|
||||
var arg = args[i];
|
||||
if(_.isObject(arg) && !_.isArray(arg)) {
|
||||
arg = '[object Object]: ' + _.keys(arg).join(', ');
|
||||
}
|
||||
event.reply('Callback[' + i + ']: ' + arg);
|
||||
}
|
||||
};
|
||||
var ret = eval(event.input[1]);
|
||||
if(ret !== undefined) {
|
||||
event.reply(ret);
|
||||
}
|
||||
}
|
||||
};
|
||||
commands['~js'].regex = [/^~js (.*)/, 2];
|
||||
commands['~ajs'].regex = [/^~ajs (.*)/, 2];
|
||||
commands['~js'].regex = [/^js (.*)/, 2];
|
||||
commands['~ajs'].regex = [/^ajs (.*)/, 2];
|
||||
commands['~ajs'].access = 'admin';
|
||||
|
||||
this.name = 'js';
|
||||
|
5
modules/karma/config.json
Normal file
5
modules/karma/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"announce": [ { "server": "tripsit", "name": "#dbot" } ],
|
||||
"dbKeys": [ "karma" ],
|
||||
"dbType": "redis"
|
||||
}
|
201
modules/karma/karma.js
Normal file
201
modules/karma/karma.js
Normal file
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Module Name: Karma
|
||||
* Description: Thanking, with Karma!
|
||||
*/
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var karma = function(dbot) {
|
||||
this.lastKarma = {};
|
||||
|
||||
this.internalAPI = {
|
||||
'getKarma': function(item, callback) {
|
||||
this.db.read('karma', item.toLowerCase(), callback);
|
||||
}.bind(this),
|
||||
|
||||
'setKarma': function(item, value, callback) {
|
||||
this.db.save('karma', item.toLowerCase(), {
|
||||
'item': item.toLowerCase(),
|
||||
'karma': value
|
||||
}, callback);
|
||||
}.bind(this)
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
'karma': function(event) {
|
||||
var item = event.params[1] || event.user;
|
||||
this.internalAPI.getKarma(item, function(err, karma) {
|
||||
if(!err && karma) {
|
||||
karma = karma.karma;
|
||||
} else {
|
||||
karma = 0;
|
||||
}
|
||||
|
||||
event.reply(dbot.t('karma', {
|
||||
'item': item,
|
||||
'karma': karma
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
'setkarma': function(event) {
|
||||
var item = event.params[1],
|
||||
value = parseInt(event.params[2]);
|
||||
|
||||
this.internalAPI.setKarma(item, value, function(err, karma) {
|
||||
event.reply(dbot.t('newkarma', {
|
||||
'item': item,
|
||||
'value': value
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
'unkarmaiest': function(event) {
|
||||
var karmas = {};
|
||||
this.db.scan('karma', function(karma) {
|
||||
if(karma && !_.isUndefined(karma.item)) {
|
||||
karmas[karma.item] = karma.karma;
|
||||
}
|
||||
}.bind(this), function(err) {
|
||||
var qSizes = _.chain(karmas)
|
||||
.pairs()
|
||||
.sortBy(function(category) { return category[1]; })
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
var qString = 'Unkarmaiest: ';
|
||||
for(var i=0;i<qSizes.length;i++) {
|
||||
qString += qSizes[i][0] + " (" + qSizes[i][1] + "), ";
|
||||
}
|
||||
|
||||
event.reply(qString.slice(0, -2));
|
||||
});
|
||||
},
|
||||
|
||||
'karmaiest': function(event) {
|
||||
var karmas = {};
|
||||
this.db.scan('karma', function(karma) {
|
||||
if(karma && !_.isUndefined(karma.item)) {
|
||||
karmas[karma.item] = karma.karma;
|
||||
}
|
||||
}.bind(this), function(err) {
|
||||
var qSizes = _.chain(karmas)
|
||||
.pairs()
|
||||
.sortBy(function(category) { return category[1]; })
|
||||
.reverse()
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
var qString = 'Karmaiest: ';
|
||||
for(var i=0;i<qSizes.length;i++) {
|
||||
qString += qSizes[i][0] + " (" + qSizes[i][1] + "), ";
|
||||
}
|
||||
|
||||
event.reply(qString.slice(0, -2));
|
||||
});
|
||||
},
|
||||
|
||||
'wattest': function(event) {
|
||||
var karmas = {};
|
||||
this.db.scan('karma', function(karma) {
|
||||
if(karma && !_.isUndefined(karma.item)) {
|
||||
if(karma.item.match(/_wat$/)) {
|
||||
karmas[karma.item] = karma.karma;
|
||||
}
|
||||
}
|
||||
}.bind(this), function(err) {
|
||||
var qSizes = _.chain(karmas)
|
||||
.pairs()
|
||||
.sortBy(function(category) { return category[1]; })
|
||||
.reverse()
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
var qString = 'Karmaiest: ';
|
||||
for(var i=0;i<qSizes.length;i++) {
|
||||
qString += qSizes[i][0] + " (" + qSizes[i][1] + "), ";
|
||||
}
|
||||
|
||||
event.reply(qString.slice(0, -2));
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
this.commands.setkarma.access = 'admin';
|
||||
|
||||
this.listener = function(event) {
|
||||
dbot.api.ignore.isUserBanned(event.rUser, 'karma', function(isBanned) {
|
||||
if(!isBanned) {
|
||||
var match = event.message.match(/^(.+)(\+\+|\-\-)$/);
|
||||
if(event.user !== dbot.config.name && match && match[1].length < 25) {
|
||||
match[1] = match[1].replace(/(\+|\-)/g,'').replace(/:/g,'').trim();
|
||||
|
||||
var timeout = 5000;
|
||||
/* if(event.channel.name == '#stims' || event.channel.name == '##meth' || event.channel.name == '##sweden') {
|
||||
timeout = 20000;
|
||||
}*/
|
||||
if(_.has(this.lastKarma, event.rUser.id) && this.lastKarma[event.rUser.id]+ timeout > Date.now()) {
|
||||
return event.reply('Try again in a few seconds : - )');
|
||||
} else if(event.rUser.currentNick.toLowerCase() === match[1].toLowerCase() || event.rUser.primaryNick.toLowerCase() === match[1].toLowerCase()) {
|
||||
return event.reply('Stop playing with yourself : - )');
|
||||
} else if(event.channel == event.user) {
|
||||
return event.reply('Don\'t be a Secretive Sally : - )');
|
||||
}
|
||||
|
||||
if(event.channel.name == '##wat') {
|
||||
match[1] = match[1].replace(/_wat$/, '');
|
||||
match[1] += '_wat';
|
||||
}
|
||||
|
||||
this.internalAPI.getKarma(match[1], function(err, karma) {
|
||||
if(!karma) {
|
||||
karma = 0;
|
||||
} else {
|
||||
karma = karma.karma;
|
||||
}
|
||||
|
||||
if(match[2] === '--') {
|
||||
if(match[1].toLowerCase() =='weed') {
|
||||
karma -= 2;
|
||||
} else {
|
||||
karma -= 1;
|
||||
}
|
||||
} else {
|
||||
if(match[1].toLowerCase() == 'weed') {
|
||||
karma += 2;
|
||||
} else {
|
||||
karma += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.internalAPI.setKarma(match[1], karma, function(err, karma) {
|
||||
this.lastKarma[event.rUser.id] = Date.now();
|
||||
var pre;
|
||||
if(karma.karma > 0) {
|
||||
pre = '[\u00039karma\u000f]';
|
||||
karma.karma = '\u00039 '+karma.karma+'\u000f';
|
||||
} else if(karma.karma < 0) {
|
||||
pre = '[\u00034karma\u000f]';
|
||||
karma.karma = '\u00034 '+karma.karma+'\u000f';
|
||||
} else {
|
||||
pre = '[\u00036karma\u000f]';
|
||||
karma.karma = '\u00036 '+karma.karma+'\u000f';
|
||||
}
|
||||
event.reply(pre + ' ' + dbot.t('newkarma', {
|
||||
'item': match[1],
|
||||
'value': karma.karma
|
||||
}));
|
||||
if(_.has(dbot.modules, 'log')) {
|
||||
dbot.api.log.logWithChannel(event.server, event.channel, event.rUser.primaryNick, event.message);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
this.on = 'PRIVMSG';
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new karma(dbot);
|
||||
};
|
8
modules/karma/strings.json
Normal file
8
modules/karma/strings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"karma": {
|
||||
"en": "[karma] {item} has {karma} karma"
|
||||
},
|
||||
"newkarma": {
|
||||
"en": "{item} now has{value} karma"
|
||||
}
|
||||
}
|
@ -15,3 +15,15 @@ people.
|
||||
|
||||
#### ~kickstats
|
||||
Show a list of top kickers and kickees.
|
||||
|
||||
#### ~ckick [#channel] [username] [reason]
|
||||
Kick a user from a channel.
|
||||
|
||||
#### ~cban [#channel] [username] [reason]
|
||||
Ban a user from a channel.
|
||||
|
||||
#### ~nban {optional: Duration in Hours} [username] [reason]
|
||||
Ban a user from the network.
|
||||
|
||||
#### ~nunban [username] [reason]
|
||||
Unban a user from the network.
|
||||
|
512
modules/kick/commands.js
Normal file
512
modules/kick/commands.js
Normal file
@ -0,0 +1,512 @@
|
||||
var _ = require('underscore')._,
|
||||
uuid = require('node-uuid');
|
||||
|
||||
var commands = function (dbot) {
|
||||
var commands = {
|
||||
/*** Kick Management ***/
|
||||
'~quiet': function (event) {
|
||||
var server = event.server,
|
||||
quieter = event.rUser,
|
||||
duration = event.input[1],
|
||||
channel = (event.input[2] || event.channel.name).trim(),
|
||||
quietee = event.input[3].trim(),
|
||||
reason = event.input[4] || "N/A";
|
||||
|
||||
this.api.quietUser(server, quieter, duration, channel, quietee, reason, function (response) {
|
||||
event.reply(response);
|
||||
});
|
||||
},
|
||||
|
||||
'~timeout': function (event) {
|
||||
var server = event.server,
|
||||
quieter = event.rUser,
|
||||
duration = this.config.timeoutTime,
|
||||
channel = event.channel.name,
|
||||
quietee = event.input[1],
|
||||
reason = event.input[2] || "N/A";
|
||||
|
||||
reason += ' #timeout';
|
||||
|
||||
dbot.api.users.resolveUser(server, quietee, function (err, user) {
|
||||
if (!err && user) {
|
||||
if (!_.has(this.recentTimeouts, user.id)) {
|
||||
this.recentTimeouts[user.id] = 0;
|
||||
}
|
||||
|
||||
this.recentTimeouts[user.id] += 1;
|
||||
setTimeout(function () {
|
||||
this.recentTimeouts[user.id] -= 1;
|
||||
if (this.recentTimeouts[user.id] == 0) {
|
||||
delete this.recentTimeouts[user.id];
|
||||
}
|
||||
}
|
||||
.bind(this), 3600000);
|
||||
|
||||
if (this.recentTimeouts[user.id] == 3) {
|
||||
duration = null;
|
||||
reason += ' #permatimeout';
|
||||
dbot.say(event.server, dbot.config.servers[event.server].admin_channel, quietee + ' has been given three timeouts in the last hour, and so has been quieted indefinitely in ' + channel + '. Please review.');
|
||||
}
|
||||
|
||||
this.api.quietUser(server, quieter, duration, channel, quietee, reason, function (response) {
|
||||
event.reply(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
},
|
||||
|
||||
'~unquiet': function (event) {
|
||||
var server = event.server,
|
||||
quieter = event.user,
|
||||
channel = (event.input[1] || event.channel.name).trim(),
|
||||
quietee = event.input[2].trim();
|
||||
|
||||
if (_.has(this.hosts[server], quietee)) {
|
||||
if (_.include(this.config.quietBans, channel)) {
|
||||
this.api.unban(server, this.hosts[server][quietee], channel);
|
||||
} else {
|
||||
this.api.unquiet(server, this.hosts[server][quietee], channel);
|
||||
}
|
||||
event.reply(dbot.t('unquieted', {
|
||||
'quietee': quietee
|
||||
}));
|
||||
dbot.api.report.notify('unquiet', server, event.rUser, channel,
|
||||
dbot.t('unquiet_notify', {
|
||||
'unquieter': quieter,
|
||||
'quietee': quietee
|
||||
}), false, quietee);
|
||||
}
|
||||
},
|
||||
|
||||
'~ckick': function (event) {
|
||||
var server = event.server,
|
||||
kicker = event.user,
|
||||
kickee = event.input[2],
|
||||
channel = event.input[1],
|
||||
reason = event.input[3];
|
||||
|
||||
if (_.isUndefined(channel)) {
|
||||
channel = event.channel.name;
|
||||
}
|
||||
channel = channel.trim();
|
||||
|
||||
this.api.kick(server, kickee, channel, reason + ' (requested by ' + kicker + ')');
|
||||
|
||||
dbot.api.report.notify('kick', server, event.rUser, channel, dbot.t('ckicked', {
|
||||
'kicker': kicker,
|
||||
'kickee': kickee,
|
||||
'reason': reason
|
||||
}), false, kickee);
|
||||
},
|
||||
|
||||
// Kick and ban from all channels on the network.
|
||||
'~nban': function (event) {
|
||||
if (!event.input)
|
||||
return;
|
||||
|
||||
var server = event.server,
|
||||
banner = event.user,
|
||||
timeout = event.input[1],
|
||||
banee = event.input[2],
|
||||
reason = event.input[3],
|
||||
adminChannel = dbot.config.servers[server].admin_channel,
|
||||
channels = _.keys(dbot.instance.connections[server].channels),
|
||||
network = event.server;
|
||||
|
||||
if (this.config.network_name[event.server]) {
|
||||
network = this.config.network_name[event.server];
|
||||
}
|
||||
|
||||
dbot.api.nickserv.getUserHost(event.server, banee, function (host) {
|
||||
// Add host record entry
|
||||
if (host) {
|
||||
var didKill = false;
|
||||
|
||||
if ((reason.match('#line') || reason.match('#specialk') || reason.match('#kline')) && _.include(dbot.access.moderator(), event.rUser.primaryNick)) {
|
||||
didKill = true;
|
||||
var t = ' !P ';
|
||||
if (timeout) {
|
||||
t = ' !T ' + timeout + ' ';
|
||||
}
|
||||
dbot.say(event.server, 'operserv', 'akill add ' + banee + t + banee + ' banned by ' + banner + ': ' + reason);
|
||||
}
|
||||
|
||||
// Do not ban if user was killed - redundant
|
||||
if(!didKill) {
|
||||
// Ban from current channel first
|
||||
this.api.ban(server, host, event.channel);
|
||||
this.api.kick(server, banee, event.channel, reason +
|
||||
' (network-wide ban)');
|
||||
channels = _.without(channels, event.channel);
|
||||
if (!_.isUndefined(adminChannel)) {
|
||||
channels = _.without(channels, adminChannel);
|
||||
} else {
|
||||
adminChannel = event.channel.name;
|
||||
}
|
||||
|
||||
// Ban the user from all channels
|
||||
var i = 0;
|
||||
var banChannel = function (channels) {
|
||||
if (i >= channels.length)
|
||||
return;
|
||||
var channel = channels[i];
|
||||
this.api.ban(server, host, channel);
|
||||
this.api.kick(server, banee, channel, reason +
|
||||
' (network-wide ban)');
|
||||
i++;
|
||||
banChannel(channels);
|
||||
}
|
||||
.bind(this);
|
||||
banChannel(channels);
|
||||
}
|
||||
|
||||
this.hosts[event.server][banee] = host;
|
||||
|
||||
// Create notify string
|
||||
if (!_.isUndefined(timeout)) {
|
||||
timeout = timeout.trim();
|
||||
|
||||
var msTimeout = new Date(new Date().getTime() + (parseFloat(timeout) * 3600000));
|
||||
if (_.has(dbot.modules, 'remind')) {
|
||||
msTimeout = dbot.api.remind.parseTime(timeout);
|
||||
if (!msTimeout) {
|
||||
return event.reply('Invalid time. Remember you must give e.g. 5m now.');
|
||||
}
|
||||
timeout = timeout.replace(/([\d]+)d/, '$1 days').replace(/([\d]+)h/, '$1 hours ').replace(/([\d]+)m/, '$1 minutes ').replace(/([\d]+)s/, '$1 seconds').trim();
|
||||
} else {
|
||||
timeout += ' hours';
|
||||
}
|
||||
|
||||
// Do not schedule unbans if the user was killed as no ban was put in place
|
||||
if(!didKill) {
|
||||
if (!_.has(this.tempBans, event.server))
|
||||
this.tempBans[event.server] = {};
|
||||
this.tempBans[event.server][banee] = msTimeout;
|
||||
this.internalAPI.addTempBan(event.server, banee, msTimeout);
|
||||
}
|
||||
|
||||
var notifyString = dbot.t('tbanned', {
|
||||
'network': network,
|
||||
'banner': banner,
|
||||
'banee': banee,
|
||||
'hours': timeout,
|
||||
'host': host,
|
||||
'reason': reason
|
||||
});
|
||||
} else {
|
||||
var notifyString = dbot.t('nbanned', {
|
||||
'network': network,
|
||||
'banner': banner,
|
||||
'banee': banee,
|
||||
'host': host,
|
||||
'reason': reason
|
||||
});
|
||||
}
|
||||
|
||||
// Add db entry documenting ban
|
||||
if (this.config.document_bans) {
|
||||
var id = uuid.v4();
|
||||
var banRecord = {
|
||||
'id': id,
|
||||
'time': new Date().getTime(),
|
||||
'server': server,
|
||||
'banee': banee,
|
||||
'banner': banner,
|
||||
'host': host,
|
||||
'reason': reason
|
||||
};
|
||||
this.db.save('nbans', id, banRecord, function () {});
|
||||
}
|
||||
|
||||
// Notify moderators, banee
|
||||
if (!_.isUndefined(adminChannel)) {
|
||||
channels = _.without(channels, adminChannel);
|
||||
} else {
|
||||
adminChannel = event.channel.name;
|
||||
}
|
||||
|
||||
dbot.api.report.notify('ban', server, event.rUser, adminChannel, notifyString, false, banee);
|
||||
dbot.say(event.server, adminChannel, notifyString);
|
||||
|
||||
if (!_.isUndefined(timeout)) {
|
||||
dbot.say(event.server, banee, dbot.t('tbanned_notify', {
|
||||
'network': network,
|
||||
'banner': banner,
|
||||
'reason': reason,
|
||||
'hours': timeout,
|
||||
'admin_channel': adminChannel
|
||||
}));
|
||||
} else {
|
||||
dbot.say(event.server, banee, dbot.t('nbanned_notify', {
|
||||
'network': network,
|
||||
'banner': banner,
|
||||
'reason': reason,
|
||||
'hours': timeout,
|
||||
'admin_channel': adminChannel
|
||||
}));
|
||||
}
|
||||
|
||||
// err
|
||||
dbot.say(event.server, 'NickServ', 'FREEZE ' + banee + ' ON ' + reason);
|
||||
} else {
|
||||
event.reply(dbot.t('no_user', {
|
||||
'user': banee
|
||||
}));
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
},
|
||||
|
||||
'~nunban': function (event) {
|
||||
var unbanee = event.params[1],
|
||||
host = event.params[2] || undefined,
|
||||
unbanner = event.rUser;
|
||||
|
||||
this.api.networkUnban(event.server, unbanee, unbanner, host, function (err) {
|
||||
if (err) {
|
||||
event.reply(dbot.t('nunban_error', {
|
||||
'unbanee': unbanee
|
||||
}));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*** Kick Stats ***/
|
||||
|
||||
// Give the number of times a given user has been kicked and has kicked
|
||||
// other people.
|
||||
'~kickcount': function (event) {
|
||||
var username = event.params[1];
|
||||
|
||||
if (!_.has(dbot.db.kicks, username)) {
|
||||
var kicks = '0';
|
||||
} else {
|
||||
var kicks = dbot.db.kicks[username];
|
||||
}
|
||||
|
||||
if (!_.has(dbot.db.kickers, username)) {
|
||||
var kicked = '0';
|
||||
} else {
|
||||
var kicked = dbot.db.kickers[username];
|
||||
}
|
||||
|
||||
event.reply(dbot.t('user_kicks', {
|
||||
'user': username,
|
||||
'kicks': kicks,
|
||||
'kicked': kicked
|
||||
}));
|
||||
},
|
||||
|
||||
// Output a list of the people who have been kicked the most and those
|
||||
// who have kicked other people the most.
|
||||
'~kickstats': function (event) {
|
||||
var orderedKickLeague = function (list, topWhat) {
|
||||
var kickArr = _.chain(list)
|
||||
.pairs()
|
||||
.sortBy(function (kick) {
|
||||
return kick[1]
|
||||
})
|
||||
.reverse()
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
var kickString = "Top " + topWhat + ": ";
|
||||
for (var i = 0; i < kickArr.length; i++) {
|
||||
kickString += kickArr[i][0] + " (" + kickArr[i][1] + "), ";
|
||||
}
|
||||
|
||||
return kickString.slice(0, -2);
|
||||
};
|
||||
|
||||
event.reply(orderedKickLeague(dbot.db.kicks, 'Kicked'));
|
||||
event.reply(orderedKickLeague(dbot.db.kickers, 'Kickers'));
|
||||
},
|
||||
|
||||
'~votequiet': function (event) {
|
||||
var target = event.input[1],
|
||||
reason = event.input[2];
|
||||
|
||||
if (_.has(event.channel.nicks, target)) {
|
||||
dbot.api.users.resolveUser(event.server, target, function (err, user) {
|
||||
if (!err && user) {
|
||||
if (_.include(dbot.access.power_user(), user.primaryNick) || target == dbot.config.name) {
|
||||
return event.reply('User is immune to votequiet.');
|
||||
}
|
||||
|
||||
if (!_.has(this.voteQuiets, user.id)) {
|
||||
this.voteQuiets[user.id] = {
|
||||
'user': user.id,
|
||||
'reason': reason,
|
||||
'channel': event.channel,
|
||||
'yes': [event.rUser.primaryNick],
|
||||
'no': []
|
||||
};
|
||||
event.reply(event.user + ' has started a vote to quiet ' + target + ' for "' + reason + '." Type either "~voteyes ' + target + '" or "~voteno ' + target + '" in the next 90 seconds.');
|
||||
|
||||
this.voteQuiets[user.id].timer = setTimeout(function () {
|
||||
var vq = this.voteQuiets[user.id];
|
||||
vq.spent = true;
|
||||
if (vq.yes.length >= 3 && vq.no.length < 2) {
|
||||
event.reply('Attempt to quiet ' + target + ' succeeded. Count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
|
||||
this.api.quietUser(event.server, event.rUser, '10m', event.channel, target, reason + '[votequiet]', function (response) {
|
||||
clearTimeout(vq.timer);
|
||||
event.reply(response);
|
||||
});
|
||||
} else {
|
||||
event.reply('Attempt to quiet ' + target + ' failed. Count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
}
|
||||
|
||||
var nString = 'A votequiet was attempted on ' + target + ' in ' + event.channel + '. It was initiated by ' + event.rUser.primaryNick + '. ' +
|
||||
vq.yes.join(', ') + ' voted yes (' + vq.yes.length + '). ';
|
||||
if (vq.no.length > 0) {
|
||||
nString += vq.no.join(', ') + ' voted no (' + vq.no.length + ').'
|
||||
}
|
||||
|
||||
dbot.api.report.notify('votequiet', event.server, event.rUser, event.channel, nString, false, target);
|
||||
|
||||
setTimeout(function () {
|
||||
delete this.voteQuiets[user.id];
|
||||
}
|
||||
.bind(this), 600000);
|
||||
}
|
||||
.bind(this), 90000);
|
||||
} else {
|
||||
if (this.voteQuiets[user.id].spent) {
|
||||
event.reply('A votequiet attempt has already been made on this user in the last 10 minutes.');
|
||||
} else {
|
||||
var vq = this.voteQuiets[user.id]
|
||||
if (!_.include(vq.yes, event.rUser.primaryNick)) {
|
||||
vq.yes.push(event.rUser.primaryNick);
|
||||
|
||||
event.reply('There is already a votequiet attempt active for this user, adding yes vote to existing poll.');
|
||||
event.reply('Voted yes on votequiet for ' + target + '. New count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
|
||||
if (vq.yes.length == 4) {
|
||||
event.reply('Attempt to quiet ' + target + ' succeeded. Count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
this.api.quietUser(event.server, event.rUser, '10m', event.channel, target, reason + '[votequiet]', function (response) {
|
||||
clearTimeout(vq.timer);
|
||||
vq.spent = true;
|
||||
setTimeout(function () {
|
||||
delete this.voteQuiets[user.id];
|
||||
}
|
||||
.bind(this), 600000);
|
||||
event.reply(response);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
event.reply('There is already a votequiet attempt active for this user, and you already voted yes!');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.reply('Target does not seem to be in the channel.');
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
} else {
|
||||
event.reply('Target does not seem to be in the channel.');
|
||||
}
|
||||
},
|
||||
|
||||
'~voteyes': function (event) {
|
||||
var target = event.params[1];
|
||||
|
||||
dbot.api.users.resolveUser(event.server, target, function (err, user) {
|
||||
if (!err && user) {
|
||||
if (user.id == event.rUser.id) {
|
||||
return event.reply('You cannot vote on your own silencing. Be good.');
|
||||
}
|
||||
if (_.has(this.voteQuiets, user.id) && !this.voteQuiets[user.id].spent) {
|
||||
var vq = this.voteQuiets[user.id];
|
||||
if (event.channel != vq.channel) {
|
||||
return event.reply('Vote must be in ' + vq.channel);
|
||||
}
|
||||
if (!_.include(vq.yes, event.rUser.primaryNick) && !_.include(vq.no, event.rUser.primaryNick)) {
|
||||
vq.yes.push(event.rUser.primaryNick);
|
||||
event.reply('Voted yes on votequiet for ' + target + '. New count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
|
||||
if (vq.yes.length == 4) {
|
||||
event.reply('Attempt to quiet ' + target + ' succeeded. Count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
this.api.quietUser(event.server, event.rUser, '10m', event.channel, target, vq.reason + '[votequiet]', function (response) {
|
||||
clearTimeout(vq.timer);
|
||||
vq.spent = true;
|
||||
setTimeout(function () {
|
||||
delete this.voteQuiets[user.id];
|
||||
}
|
||||
.bind(this), 600000);
|
||||
event.reply(response);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
event.reply('You have already voted.');
|
||||
}
|
||||
} else {
|
||||
event.reply('There is no active votequiet for this user. You can start one by typing "~votequiet ' + target + ' [reason].');
|
||||
}
|
||||
} else {
|
||||
event.reply('No idea who that is m8');
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
},
|
||||
|
||||
'~voteno': function (event) {
|
||||
var target = event.params[1];
|
||||
|
||||
dbot.api.users.resolveUser(event.server, target, function (err, user) {
|
||||
if (!err && user) {
|
||||
if (user.id == event.rUser.id) {
|
||||
return event.reply('You cannot vote on your own silencing. Be good.');
|
||||
}
|
||||
if (_.has(this.voteQuiets, user.id) && !this.voteQuiets[user.id].spent) {
|
||||
var vq = this.voteQuiets[user.id];
|
||||
if (event.channel != vq.channel) {
|
||||
return event.reply('Vote must be in ' + vq.channel);
|
||||
}
|
||||
if (!_.include(vq.yes, event.rUser.primaryNick) && !_.include(vq.no, event.rUser.primaryNick)) {
|
||||
vq.no.push(event.rUser.primaryNick);
|
||||
event.reply('Voted no on votequiet for ' + target + '. New count: Yes (' + vq.yes.length + '). No (' + vq.no.length + ').');
|
||||
} else {
|
||||
event.reply('You have already voted.');
|
||||
}
|
||||
} else {
|
||||
event.reply('There is no active votequiet for this user. You can start one by typing "~votequiet ' + target + ' [reason].');
|
||||
}
|
||||
} else {
|
||||
event.reply('No idea who that is m8');
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
_.each(commands, function (command) {
|
||||
command.access = 'moderator';
|
||||
});
|
||||
|
||||
commands['~kickcount'].access = 'regular';
|
||||
commands['~kickstats'].access = 'regular';
|
||||
commands['~votequiet'].access = 'regular';
|
||||
commands['~voteyes'].access = 'regular';
|
||||
commands['~voteno'].access = 'regular';
|
||||
commands['~quiet'].access = 'voice';
|
||||
commands['~timeout'].access = 'voice';
|
||||
commands['~unquiet'].access = 'voice';
|
||||
commands['~nban'].access = 'power_user';
|
||||
commands['~nunban'].access = 'power_user';
|
||||
|
||||
commands['~ckick'].regex = /^ckick (#[^ ]+ )?([^ ]+) ?(.*)?$/;
|
||||
commands['~nban'].regex = /^nban (\d[\d\.dhmsy]+)? ?([^ ]+) (.+)$/;
|
||||
commands['~quiet'].regex = /^quiet (\d[\d\.hmsy]+)? ?(#[^ ]+ )?([^ ]+) ?(.*)?$/;
|
||||
commands['~timeout'].regex = /^timeout ([^ ]+) ?(.*)?$/;
|
||||
commands['~unquiet'].regex = /^unquiet (#[^ ]+ )?([^ ]+) ?$/;
|
||||
commands['~votequiet'].regex = [/^votequiet ([^ ]+) (.+)$/, 3];
|
||||
|
||||
return commands;
|
||||
};
|
||||
|
||||
exports.fetch = function (dbot) {
|
||||
return commands(dbot);
|
||||
};
|
@ -1,6 +1,18 @@
|
||||
{
|
||||
"dbKeys": [ "kicks", "kickers" ],
|
||||
"dependencies": [ "command" ],
|
||||
"help": "http://github.com/reality/depressionbot/blob/master/modules/kick/README.md",
|
||||
"ignorable": true
|
||||
"dbKeys": [ "kicks", "kickers", "hosts", "tempBans" ],
|
||||
"dependencies": [ "command", "report", "users" ],
|
||||
"ignorable": true,
|
||||
"countSilently": true,
|
||||
"quietBans": [
|
||||
"#wherever"
|
||||
],
|
||||
"network_name": {
|
||||
"aberwiki": "OAOSIDL"
|
||||
},
|
||||
"chanserv": "ChanServ",
|
||||
"document_bans": false,
|
||||
"dbType": "redis",
|
||||
"requireWebLogin": true,
|
||||
"webAccess": "power_user",
|
||||
"timeoutTime": "10m"
|
||||
}
|
||||
|
@ -1,84 +1,270 @@
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var kick = function(dbot) {
|
||||
var commands = {
|
||||
// Give the number of times a given user has been kicked and has kicked
|
||||
// other people.
|
||||
'~kickcount': function(event) {
|
||||
var username = event.params[1];
|
||||
var kick = function (dbot) {
|
||||
if (!_.has(dbot.db, 'recentTimeouts')) {
|
||||
dbot.db.recentTimeouts = {};
|
||||
}
|
||||
this.recentTimeouts = dbot.db.recentTimeouts;
|
||||
|
||||
if(!_.has(dbot.db.kicks, username)) {
|
||||
var kicks = '0';
|
||||
this.api = {
|
||||
'ban': function (server, host, channel) {
|
||||
dbot.instance.connections[server].send('MODE ' + channel + ' +b *!*@' + host);
|
||||
},
|
||||
|
||||
'quiet': function (server, host, channel) {
|
||||
dbot.instance.connections[server].send('MODE ' + channel + ' +q *!*@' + host);
|
||||
},
|
||||
|
||||
'unquiet': function (server, host, channel) {
|
||||
dbot.instance.connections[server].send('MODE ' + channel + ' -q *!*@' + host);
|
||||
},
|
||||
|
||||
'devoice': function (server, nick, channel) {
|
||||
dbot.instance.connections[server].send('MODE ' + channel + ' -v ' + nick);
|
||||
},
|
||||
|
||||
'voice': function (server, nick, channel) {
|
||||
dbot.instance.connections[server].send('MODE ' + channel + ' +v ' + nick);
|
||||
},
|
||||
|
||||
'kick': function (server, user, channel, msg) {
|
||||
dbot.instance.connections[server].send('KICK ' + channel + ' ' + user + ' :' + msg);
|
||||
},
|
||||
|
||||
'kill': function (server, user, reason) {
|
||||
dbot.instance.connections[server].send('kill ' + user + ' ' + reason);
|
||||
},
|
||||
|
||||
'unban': function (server, host, channel) {
|
||||
// TODO: Wrest control from chanserv
|
||||
//dbot.say(server, this.config.chanserv, 'unban ' + channel + ' *!*@' + host);
|
||||
dbot.instance.connections[server].send('MODE ' + channel + ' -b *!*@' + host);
|
||||
},
|
||||
|
||||
'quietUser': function (server, quieter, duration, channel, quietee, reason, callback) {
|
||||
dbot.api.nickserv.getUserHost(server, quietee, function (host) {
|
||||
// Add host record entry
|
||||
if (host) {
|
||||
this.hosts[server][quietee] = host;
|
||||
|
||||
if (!_.isUndefined(duration) && !_.isNull(duration)) {
|
||||
duration = duration.trim();
|
||||
var msTimeout = new Date(new Date().getTime() + (parseFloat(duration) * 60000));
|
||||
if (_.has(dbot.modules, 'remind')) {
|
||||
msTimeout = dbot.api.remind.parseTime(duration);
|
||||
if (!msTimeout) {
|
||||
return callback('Invalid time. Remember you must give e.g. 5m now.');
|
||||
}
|
||||
duration = duration.replace(/([\d]+)d/, '$1 years').replace(/([\d]+)d/, '$1 days').replace(/([\d]+)h/, '$1 hours ').replace(/([\d]+)m/, '$1 minutes ').replace(/([\d]+)s/, '$1 seconds').trim();
|
||||
} else {
|
||||
var kicks = dbot.db.kicks[username];
|
||||
duration += ' minutes';
|
||||
}
|
||||
|
||||
if(!_.has(dbot.db.kickers, username)) {
|
||||
var kicked = '0';
|
||||
} else {
|
||||
var kicked = dbot.db.kickers[username];
|
||||
}
|
||||
|
||||
event.reply(dbot.t('user_kicks', {
|
||||
'user': username,
|
||||
'kicks': kicks,
|
||||
'kicked': kicked
|
||||
}));
|
||||
},
|
||||
|
||||
// Output a list of the people who have been kicked the most and those
|
||||
// who have kicked other people the most.
|
||||
'~kickstats': function(event) {
|
||||
var orderedKickLeague = function(list, topWhat) {
|
||||
var kickArr = _.chain(list)
|
||||
.pairs()
|
||||
.sortBy(function(kick) { return kick[1] })
|
||||
.reverse()
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
var kickString = "Top " + topWhat + ": ";
|
||||
for(var i=0;i<kickArr.length;i++) {
|
||||
kickString += kickArr[i][0] + " (" + kickArr[i][1] + "), ";
|
||||
var vStatus = dbot.instance.connections[server].channels[channel].nicks[quietee].voice;
|
||||
dbot.api.timers.addTimeout(msTimeout, function () {
|
||||
if (_.has(this.hosts[server], quietee)) {
|
||||
if (_.include(this.config.quietBans, channel)) {
|
||||
this.api.unban(server, this.hosts[server][quietee], channel);
|
||||
this.api.voice(server, quietee, channel);
|
||||
} else {
|
||||
this.api.unquiet(server, this.hosts[server][quietee], channel);
|
||||
}
|
||||
|
||||
return kickString.slice(0, -2);
|
||||
};
|
||||
dbot.api.users.resolveUser(server, dbot.config.name, function (err, user) {
|
||||
dbot.api.report.notify('unquiet', server, user, channel,
|
||||
dbot.t('unquiet_notify', {
|
||||
'unquieter': dbot.config.name,
|
||||
'quietee': quietee
|
||||
}), false, quietee);
|
||||
});
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
callback(dbot.t('tquieted', {
|
||||
'quietee': quietee,
|
||||
'minutes': duration
|
||||
}));
|
||||
dbot.api.report.notify('quiet', server, quieter.primaryNick, channel,
|
||||
dbot.t('tquiet_notify', {
|
||||
'minutes': duration,
|
||||
'quieter': quieter.primaryNick,
|
||||
'quietee': quietee,
|
||||
'reason': reason
|
||||
}), false, quietee);
|
||||
} else {
|
||||
callback(dbot.t('quieted', {
|
||||
'quietee': quietee
|
||||
}));
|
||||
dbot.api.report.notify('quiet', server, quieter.primaryNick, channel,
|
||||
dbot.t('quiet_notify', {
|
||||
'quieter': quieter.primaryNick,
|
||||
'quietee': quietee,
|
||||
'reason': reason
|
||||
}), false, quietee);
|
||||
}
|
||||
|
||||
event.reply(orderedKickLeague(dbot.db.kicks, 'Kicked'));
|
||||
event.reply(orderedKickLeague(dbot.db.kickers, 'Kickers'));
|
||||
}
|
||||
};
|
||||
this.commands = commands;
|
||||
|
||||
this.listener = function(event) {
|
||||
if(event.kickee == dbot.config.name) {
|
||||
dbot.instance.join(event, event.channel);
|
||||
event.reply(dbot.t('kicked_dbot', { 'botname': dbot.config.name }));
|
||||
dbot.db.kicks[dbot.config.name] += 1;
|
||||
this.api.devoice(server, quietee, channel);
|
||||
|
||||
if (_.include(this.config.quietBans, channel)) {
|
||||
this.api.ban(server, this.hosts[server][quietee], channel);
|
||||
} else {
|
||||
this.api.quiet(server, host, channel);
|
||||
}
|
||||
|
||||
if (reason.indexOf('#warn') !== -1) {
|
||||
dbot.api.warning.warn(server, quieter, quietee,
|
||||
'Quieted in ' + channel + ' for ' + reason, channel,
|
||||
function () {});
|
||||
}
|
||||
} else {
|
||||
if(!_.has(dbot.db.kicks, event.kickee)) {
|
||||
dbot.db.kicks[event.kickee] = 1;
|
||||
} else {
|
||||
dbot.db.kicks[event.kickee] += 1;
|
||||
}
|
||||
|
||||
if(!_.has(dbot.db.kickers, event.user)) {
|
||||
dbot.db.kickers[event.user] = 1;
|
||||
} else {
|
||||
dbot.db.kickers[event.user] += 1;
|
||||
}
|
||||
|
||||
event.reply(event.kickee + '-- (' + dbot.t('user_kicks', {
|
||||
'user': event.kickee,
|
||||
'kicks': dbot.db.kicks[event.kickee],
|
||||
'kicked': dbot.db.kickers[event.kickee]
|
||||
}) + ')');
|
||||
event.reply(dbot.t('no_user', {
|
||||
'user': quietee
|
||||
}));
|
||||
}
|
||||
};
|
||||
this.on = 'KICK';
|
||||
}
|
||||
.bind(this));
|
||||
},
|
||||
|
||||
'networkUnban': function (server, unbanee, unbanner, manualHost, callback) {
|
||||
var channels = dbot.config.servers[server].channels,
|
||||
network = this.config.network_name[server] || server,
|
||||
adminChannel = dbot.config.servers[server].admin_channel;
|
||||
|
||||
if (!_.isUndefined(manualHost)) {
|
||||
this.hosts[server][unbanee] = manualHost;
|
||||
}
|
||||
|
||||
if (_.has(this.hosts, server) && _.has(this.hosts[server], unbanee) && _.isString(this.hosts[server][unbanee])) {
|
||||
var host = this.hosts[server][unbanee];
|
||||
|
||||
// Notify Staff
|
||||
if (_.isUndefined(adminChannel)) {
|
||||
adminChannel = event.channel.name;
|
||||
}
|
||||
|
||||
var notifyString = dbot.t('nunbanned', {
|
||||
'network': network,
|
||||
'unbanee': unbanee,
|
||||
'host': host,
|
||||
'unbanner': unbanner.currentNick
|
||||
});
|
||||
dbot.api.report.notify('unban', server, unbanner, adminChannel, notifyString, false, unbanee);
|
||||
dbot.say(server, adminChannel, notifyString);
|
||||
|
||||
// Notify Unbanee
|
||||
dbot.say(server, unbanee, dbot.t('nunban_notify', {
|
||||
'network': network,
|
||||
'unbanee': unbanee,
|
||||
'unbanner': unbanner.currentNick
|
||||
}));
|
||||
|
||||
// Unban
|
||||
var i = 0;
|
||||
var unbanChannel = function (channels) {
|
||||
if (i >= channels.length)
|
||||
return;
|
||||
var channel = channels[i];
|
||||
this.api.unban(server, host, channel);
|
||||
setTimeout(function () {
|
||||
i++;
|
||||
unbanChannel(channels);
|
||||
}, 1000);
|
||||
}
|
||||
.bind(this);
|
||||
unbanChannel(channels);
|
||||
|
||||
dbot.say(server, 'NickServ', 'FREEZE ' + unbanee + ' OFF');
|
||||
callback(null); // Success
|
||||
} else {
|
||||
// Attempt to look up the host on-the-fly
|
||||
dbot.api.nickserv.getUserHost(server, unbanee, unbanner, function (host) {
|
||||
if (host) {
|
||||
if (!_.has(this.hosts, server))
|
||||
this.hosts[server] = {};
|
||||
this.hosts[server][unbanee] = host;
|
||||
this.api.networkUnban(server, unbanee, unbanner);
|
||||
} else {
|
||||
callback(true); // No host could be found
|
||||
}
|
||||
}
|
||||
.bind(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.internalAPI = {
|
||||
'addTempBan': function (server, banee, timeout) {
|
||||
dbot.api.users.resolveUser(server, dbot.config.name, function (err, bot) {
|
||||
dbot.api.timers.addTimeout(timeout, function () {
|
||||
this.api.networkUnban(server, banee, bot, undefined, function (err) {});
|
||||
delete this.tempBans[server][banee];
|
||||
}
|
||||
.bind(this));
|
||||
}
|
||||
.bind(this));
|
||||
}
|
||||
.bind(this)
|
||||
};
|
||||
|
||||
this.listener = function (event) {
|
||||
if (event.kickee == dbot.config.name) {
|
||||
dbot.instance.join(event, event.channel.name);
|
||||
event.reply(dbot.t('kicked_dbot', {
|
||||
'botname': dbot.config.name
|
||||
}));
|
||||
dbot.db.kicks[dbot.config.name] += 1;
|
||||
} else {
|
||||
if (!_.has(dbot.db.kicks, event.kickee)) {
|
||||
dbot.db.kicks[event.kickee] = 1;
|
||||
} else {
|
||||
dbot.db.kicks[event.kickee] += 1;
|
||||
}
|
||||
|
||||
if (!_.has(dbot.db.kickers, event.user)) {
|
||||
dbot.db.kickers[event.user] = 1;
|
||||
} else {
|
||||
dbot.db.kickers[event.user] += 1;
|
||||
}
|
||||
|
||||
if (!this.config.countSilently) {
|
||||
event.reply(event.kickee + '-- (' + dbot.t('user_kicks', {
|
||||
'user': event.kickee,
|
||||
'kicks': dbot.db.kicks[event.kickee],
|
||||
'kicked': dbot.db.kickers[event.kickee]
|
||||
}) + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
.bind(this);
|
||||
this.on = 'KICK';
|
||||
|
||||
this.onLoad = function () {
|
||||
if (!_.has(dbot.db, 'hosts')) {
|
||||
dbot.db.hosts = {};
|
||||
_.each(dbot.config.servers, function (v, k) {
|
||||
dbot.db.hosts[k] = {};
|
||||
}, this);
|
||||
}
|
||||
if (!_.has(dbot.db, 'tempBans'))
|
||||
dbot.db.tempBans = {};
|
||||
this.hosts = dbot.db.hosts;
|
||||
this.tempBans = dbot.db.tempBans;
|
||||
this.voteQuiets = {};
|
||||
|
||||
_.each(this.tempBans, function (bans, server) {
|
||||
_.each(bans, function (timeout, nick) {
|
||||
timeout = new Date(timeout);
|
||||
this.internalAPI.addTempBan(server, nick, timeout);
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
if (_.has(dbot.modules, 'web')) {
|
||||
dbot.api.web.addIndexLink('/bans', 'Ban List');
|
||||
}
|
||||
}
|
||||
.bind(this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new kick(dbot);
|
||||
exports.fetch = function (dbot) {
|
||||
return new kick(dbot);
|
||||
};
|
||||
|
46
modules/kick/pages.js
Normal file
46
modules/kick/pages.js
Normal file
@ -0,0 +1,46 @@
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var pages = function (dbot) {
|
||||
return {
|
||||
'/bans': function (req, res) {
|
||||
res.render('servers', {
|
||||
'servers': _.keys(dbot.config.servers)
|
||||
});
|
||||
},
|
||||
|
||||
'/underbans': function (req, res) {
|
||||
this.db.search('nbans', {
|
||||
'server': server
|
||||
}, function (ban) {
|
||||
if (ban.reason.match('#underban')) {
|
||||
bans.push(ban);
|
||||
}
|
||||
}, function () {
|
||||
res.render('bans', {
|
||||
'server': server,
|
||||
'bans': bans
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'/bans/:server': function (req, res) {
|
||||
var server = req.params.server,
|
||||
bans = [];
|
||||
|
||||
this.db.search('nbans', {
|
||||
'server': server
|
||||
}, function (ban) {
|
||||
bans.push(ban);
|
||||
}, function () {
|
||||
res.render('bans', {
|
||||
'server': server,
|
||||
'bans': bans
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.fetch = function (dbot) {
|
||||
return pages(dbot);
|
||||
};
|
@ -1,14 +1,115 @@
|
||||
{
|
||||
"user_kicks": {
|
||||
"english": "{user} has been kicked {kicks} times and has kicked people {kicked} times.",
|
||||
"spanish": "Se ha expulsado {user} {kicks} veces y {user} ha expulsado personas {kicked} veces.",
|
||||
"en": "{user} has been kicked {kicks} times and has kicked people {kicked} times.",
|
||||
"es": "Se ha expulsado {user} {kicks} veces y {user} ha expulsado personas {kicked} veces.",
|
||||
"na'vi": "Tuteol {user}it tsrame'i {kicks} hìmtxan ulte sute tsrame'i {kicked} hìmtxan.",
|
||||
"welsh": "Cafwyd {user} ei gicio {kicks} gwaith ac wedi cicio pobl {kicked} gwaith."
|
||||
"cy": "Cafwyd {user} ei gicio {kicks} gwaith ac wedi cicio pobl {kicked} gwaith.",
|
||||
"nl": "{user} is {kicks} keer gekickt en heeft anderen {kicked} keer gekickt.",
|
||||
"de": "{user} wurde {kicks} mal gekickt und hat {kicked} mal andere Benutzer gekickt.",
|
||||
"fr": "{user} a été kické {kicks} fois et a kické des personnes {kicked} fois.",
|
||||
"it": "{user} ha ricevuto {kicks} pedata/e e ha dato {kicked} pedata/e a altri utenti"
|
||||
},
|
||||
"quieted": {
|
||||
"en": "Quieted {quietee}. Remember: don't be a coconut.",
|
||||
"fr": "{quietee} a été rendu silencieux. Rappelle-toi : ne sois pas têtu.",
|
||||
"it": "{quietee} è stato silenziato. Ricordati: non essere testardo",
|
||||
"de": "{quietee} stummgestellt. Denk dran: Sei kein Arschloch."
|
||||
},
|
||||
"tquieted": {
|
||||
"en": "Quieted {quietee} for {minutes}. Remember: don't be a coconut.",
|
||||
"fr": "{quietee} a été rendu silencieux pour {minutes} minutes. Rappelle-toi : ne sois pas têtu.",
|
||||
"it": "{quietee} è stato silenziato per {minutes} minuto/i. Ricordati: non essere testardo",
|
||||
"de": "{quietee} für {minutes} Minuten stummgestellt. Denk dran: Sei kein Arschloch."
|
||||
},
|
||||
"quiet_notify": {
|
||||
"en": "{quieter} has quieted {quietee}. The reason given was \"{reason}\".",
|
||||
"de": "{quieter} hat {quietee} stummgestellt. Der Grund ist \"{reason}\"."
|
||||
},
|
||||
"tquiet_notify": {
|
||||
"en": "{quieter} has quieted {quietee} for {minutes} minutes. The reason given was \"{reason}\".",
|
||||
"de": "{quieter} hat {quietee} für {minutes} Minuten stummgestellt. Der Grund ist \"{reason}\"."
|
||||
},
|
||||
"unquieted": {
|
||||
"en": "Unquieted {quietee}. Remember: don't be a coconut.",
|
||||
"fr": "{quietee} peut maintenant parler. Rappelle-toi : ne sois pas têtu.",
|
||||
"it": "{quietee} può nuovamente parlare. Ricordati: non essere testardo.",
|
||||
"de": "{quietee} ist nicht mehr stummgestellt. Denk dran: Sei kein Arschloch."
|
||||
},
|
||||
"unquiet_notify": {
|
||||
"en": "{unquieter} has unquieted {quietee}.",
|
||||
"de": "{unquieter} hat Stummstellung von {quietee} aufgehoben."
|
||||
},
|
||||
"kicked_dbot": {
|
||||
"english": "Thou shalt not kick {botname}",
|
||||
"spanish": "No expulsás {botname}",
|
||||
"en": "Thou shalt not kick {botname}",
|
||||
"es": "No expulsás {botname}",
|
||||
"na'vi": "Ngal {botname}it ke tsun tsrive'i",
|
||||
"welsh": "Ni ddylech cicio {botname}"
|
||||
"cy": "Ni ddylech cicio {botname}",
|
||||
"nl": "Gij zult {botname} niet kicken",
|
||||
"de": "Du sollst {botname} nicht kicken",
|
||||
"fr": "Tu ne kickeras pas {botname}",
|
||||
"it": "Non dare pedata a {botname}"
|
||||
},
|
||||
"ckicked": {
|
||||
"en": "{kicker} has kicked {kickee}. The reason given was: \"{reason}\".",
|
||||
"cy": "Sylw: {kicker} wedi cicio'r {kickee} o {channel}. Y rheswm a roddwyd oedd: \"{reason}\".",
|
||||
"de": "Achtung: {kicker} hat {kickee} von {channel} verwiesen. Grund: \"{reason}\".",
|
||||
"fr": "Attention : {kicker} a kické {kickee} de {channel}. Raison donnée : \"{reason}\".",
|
||||
"it": "Attenzione : {kicker} ha dato una pedata a {kickee} di {channel}. Motivo : \"{reason}\"."
|
||||
},
|
||||
"cbanned": {
|
||||
"en": "Attention: {banner} has banned {banee} from {channel}. The reason given was \"{reason}\".",
|
||||
"cy": "Sylw: {banner} wedi gwahardd {banee} o {channel}. Y rheswm a roddwyd oedd: \"{reason}\".",
|
||||
"de": "Achtung: {banner} hat {banee} von {channel} gebannt. Grund: \"{reason}\".",
|
||||
"fr": "Attention : {banner} a banni {banee} de {channel}. Raison donnée : \"{reason}\".",
|
||||
"it": "Attenzione : {banner} ha bandito {banee} da {channel}. Motivo : \"{reason}\"."
|
||||
},
|
||||
"tbanned": {
|
||||
"en": "Attention: {banner} has banned {banee} (host: {host}) from the {network} network for {hours}. The reason given was \"{reason}\".",
|
||||
"de": "Achtung: {banner} hat {banee} vom {network} Netzwerk für {hours} Stunden verbannt. Der Grund war \"{reason}\".",
|
||||
"fr": "Attention : {banner} a banni {banee} du réseau {network} pour {hours} heures. Raison donnée : \"{reason}\".",
|
||||
"it": "Attenzione : {banner} ha bandito {banee} dalla rete {network} per {hours} ora/e. Motivo : \"{reason}\"."
|
||||
},
|
||||
"tbanned_notify": {
|
||||
"en": "You have been banned from the {network} network for {hours} by {banner}. The reason given was \"{reason}\". You can join {admin_channel} for more information or to discuss the ban.",
|
||||
"de": "Du wurdest von {banner} im {network} Netzwerk für {hours} verbannt. Der Grund war \"{reason}\". Du kannst {admin_channel} beitreten um mehr Informatonen zu erhalten oder über die Verbannung zu diskutieren.",
|
||||
"fr": "Vous avez été banni du réseau {network} pour {hours} heures par {banner}. La raison donnée était \"{reason}\". Vous pouvez rejoindre {admin_channel} pour plus d'information or pour discuter du ban.",
|
||||
"it": "Sei stato bandito dalla rete {network} per {hours} ora/e da {banner}. Motivo: \"{reason}\". Puoi ricongiungere {admin_channel} per ulteriori informazioni o discutere sulla messa al bando."
|
||||
},
|
||||
"nbanned": {
|
||||
"en": "Attention: {banner} has banned {banee} (host: {host}) from the {network} network. The reason given was \"{reason}\".",
|
||||
"cy": "Sylw: {banner} wedi gwahardd {banee} ledled y rhwydwaith. Y rheswm a roddwyd oedd: \"{reason}\".",
|
||||
"de": "Achtung: {banner} hat {banee} auf dem gesamten Netzwerk gebannt. Grund: \"{reason}\".",
|
||||
"fr": "Attention : {banner} a banni {banee} du réseau {network}. Raison donnée : \"{reason}\".",
|
||||
"it": "Attentione : {banner} ha bandito {banee} dalla rete {network}. Motivo : \"{reason}\"."
|
||||
},
|
||||
"nbanned_notify": {
|
||||
"en": "You have been banned from the {network} network by {banner}. The reason given was \"{reason}\". You can join {admin_channel} for more information or to discuss the ban.",
|
||||
"de": "Du wurdest von {banner} im {network} Netzwerk verbannt. Der Grund war \"{reason}\". Du kannst {admin_channel} beitreten um mehr Informatonen zu erhalten oder über die Verbannung zu diskutieren.",
|
||||
"fr": "Vous avez été banni du réseau {network} par {banner}. La raison donnée était \"{reason}\". Vous pouvez rejoindre {admin_channel} pour plus d'information or pour discuter du ban.",
|
||||
"it": "Sei stato bandito dalla rete {network} da {banner}. Motivo: \"{reason}\". Puoi ricongiungere {admin_channel} per ulteriori informazioni o discutere sulla messa al bando."
|
||||
},
|
||||
"no_user": {
|
||||
"en": "{user} doesn't seem to be online on this server.",
|
||||
"de": "{user} scheint auf diesen Server nicht online zu sein.",
|
||||
"fr": "{user} ne semble pas être connecté à ce serveur.",
|
||||
"it": "{user} sembra non essere connesso a questo server"
|
||||
},
|
||||
"nunbanned": {
|
||||
"en": "Attention: {unbanee} (host: {host}) has been unbanned from the {network} network by {unbanner}.",
|
||||
"de": "Achtung: {unbanee} wurde im {network} Netzwerk durch {unbanner} entsperrt.",
|
||||
"fr": "Attention : {unbanee} a été débanni du réseau {network} par {unbanner}.",
|
||||
"it": "Attenzione : {unbanee} è stato riammesso alla rete {network} da {unbanner}."
|
||||
},
|
||||
"nunban_notify": {
|
||||
"en": "You have been unbanned from the {network} network by {unbanner}.",
|
||||
"de": "Du wurdest im {network} Netzwerk durch {unbanner} entsperrt.",
|
||||
"fr": "Vous avez été débanni du réseau {network} par {unbanner}.",
|
||||
"it": "Sei stato riammesso alla rete {network} da {unbanner}."
|
||||
},
|
||||
"nunban_error": {
|
||||
"en": "It appears {unbanee} was not banned using the ~nban command.",
|
||||
"de": "Es sieht so aus als ob {unbanee} nicht durch die Verwendung des ~nban Befehls verbannt wurde.",
|
||||
"fr": "Il semble que {unbanee} n'a pas été banni en utilisant la commande ~nban.",
|
||||
"it": "Sembar che {unbanee} non è stato bandito usano il commando ~nban."
|
||||
}
|
||||
}
|
||||
|
9
modules/kick/usage.json
Normal file
9
modules/kick/usage.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"~ckick": "~ckick [#channel] [username] [reason]",
|
||||
"~cban": "~cban [#channel] [username] [reason]",
|
||||
"~nban": "~nban ([time in hours]) [username] [reason]",
|
||||
"~kickstats": "~kickstats",
|
||||
"~kickcount": "~kickcount [user]",
|
||||
"~quiet": "~quiet (time) (#channel) username (reason)",
|
||||
"~unquiet": "~unquiet (#channel) username"
|
||||
}
|
11
modules/kill_namespam/config.json
Normal file
11
modules/kill_namespam/config.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"action": "kill",
|
||||
"sensitivity": 10,
|
||||
"exempt": [],
|
||||
"advert_content": [
|
||||
"________ ______"
|
||||
],
|
||||
"cliconn_channel": "#dnsbl",
|
||||
"cliconn_patterns": [],
|
||||
"exempt_channels": []
|
||||
}
|
159
modules/kill_namespam/kill_namespam.js
Normal file
159
modules/kill_namespam/kill_namespam.js
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Module name: kill_namespam
|
||||
* Description: destroy those wot hilight too many nicks at once . usually
|
||||
* advertising their rubbish irc server (do not)
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._;
|
||||
|
||||
var kill_namespam = function(dbot) {
|
||||
this.saveConfig = function() { // eugh
|
||||
dbot.customConfig.modules.kill_namespam = this.config;
|
||||
dbot.modules.admin.internalAPI.saveConfig();
|
||||
}.bind(this);
|
||||
|
||||
this.matchedKill = {};
|
||||
|
||||
this.listener = function(event) {
|
||||
if(event.action == 'PRIVMSG') {
|
||||
// Here we listen for atropos
|
||||
if(event.channel == this.config.cliconn_channel) {
|
||||
if(event.message.match('▶')) {
|
||||
var matchedPattern = _.find(this.config.cliconn_patterns,
|
||||
function(p) { try { return event.message.match(p); } catch(e) {}; }); // ok.jpg
|
||||
if(matchedPattern) {
|
||||
var nick = event.message.split(' ')[2];
|
||||
dbot.api.nickserv.getUserHost(event.server, nick, function(host) {
|
||||
var userIsAuthenticated = host && host.startsWith('tripsit/');
|
||||
if (userIsAuthenticated) {
|
||||
event.reply(dbot.t('clikill_spared', {
|
||||
'user': nick,
|
||||
'pattern': matchedPattern
|
||||
}));
|
||||
} else {
|
||||
if(!this.matchedKill[host]) {
|
||||
// Defer killing this connection until after they join a non-exempted channel
|
||||
this.matchedKill[host] = {
|
||||
ip: event.message.split(' ')[1],
|
||||
server: event.server,
|
||||
matchedPattern: matchedPattern,
|
||||
rUser: event.rUser
|
||||
};
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the namespam
|
||||
if(event.channel == event.user) return; // return if pm
|
||||
if(_.includes(this.config.exempt, event.user)) return;
|
||||
|
||||
var message;
|
||||
var naughty = false;
|
||||
|
||||
// Check distinctive spam content match
|
||||
if(_.any(this.config.advert_content, function(spam) { return event.message.indexOf(spam) != -1; })) {
|
||||
message = dbot.t('spamcont_act', {
|
||||
'user': event.user,
|
||||
'channel': event.channel,
|
||||
'action': this.config.action
|
||||
}) + ' ' + dbot.t('sasl_hint');
|
||||
naughty = true;
|
||||
}
|
||||
|
||||
// Name highlight spam
|
||||
if(_.filter(event.message.split(' '), function(word) { return _.has(event.channel.nicks, word); }).length > this.config.sensitivity) {
|
||||
message = dbot.t('namespam_act', {
|
||||
'user': event.user,
|
||||
'channel': event.channel,
|
||||
'action': this.config.action,
|
||||
'sensitivity': this.config.sensitivity
|
||||
}) + ' ' + dbot.t('sasl_hint');
|
||||
naughty = true;
|
||||
}
|
||||
|
||||
if(naughty) {
|
||||
switch(this.config.action) {
|
||||
case 'kickban':
|
||||
dbot.api.kick.ban(event.server, event.host, event.channel);
|
||||
dbot.api.kick.kick(event.server, event.user, message);
|
||||
break;
|
||||
case 'kill':
|
||||
dbot.api.kick.kill(event.server, event.user, message);
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
dbot.api.report.notify('spam', event.server, event.user, event.channel, message, event.host, event.user);
|
||||
}
|
||||
} else if (event.action == 'JOIN') {
|
||||
|
||||
if(this.matchedKill[event.host]) {
|
||||
if(this.config.exempt_channels.indexOf(event.channel) == -1) {
|
||||
var kill = this.matchedKill[event.host];
|
||||
delete this.matchedKill[event.host];
|
||||
|
||||
// Alternatively you can just do dbot.api.kick.kill(event.server, event.user, message);
|
||||
dbot.say(event.server, 'operserv', 'akill add *@'+ kill.ip +' !P Naughty Nelly Auto-kill v6.2. Matched pattern: /'+ kill.matchedPattern +'/');
|
||||
|
||||
var msg = dbot.t('clikill_act', {
|
||||
'ip': kill.ip,
|
||||
'pattern': kill.matchedPattern
|
||||
});
|
||||
dbot.api.report.notify('autokill', kill.server, kill.rUser,
|
||||
dbot.config.servers[kill.server].admin_channel, msg, kill.ip, kill.ip);
|
||||
}
|
||||
}
|
||||
} else if (event.action == 'QUIT') {
|
||||
if(this.matchedKill[event.host]) {
|
||||
delete this.matchedKill[event.host];
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
this.on = ['PRIVMSG', 'JOIN', 'QUIT'];
|
||||
|
||||
this.commands = {
|
||||
'~add_spamkill': function(event) {
|
||||
this.config.advert_content.push(event.params.slice(1).join(' '))
|
||||
this.saveConfig();
|
||||
event.reply('Users daring to utter the above to be classified as spam.');
|
||||
},
|
||||
|
||||
'~del_spamkill': function(event) {
|
||||
this.config.advert_content = _.without(this.config.advert_content, event.params.slice(1).join(' '));
|
||||
this.saveConfig();
|
||||
event.reply('Users will no longer be killed for this utterance.');
|
||||
},
|
||||
|
||||
'~add_clikill': function(event) {
|
||||
var pattern = event.params.slice(1).join(' ');
|
||||
this.config.cliconn_patterns.push(pattern);
|
||||
this.saveConfig();
|
||||
event.reply('Client connection notices matching pattern /'+ pattern +'/ shall henceforth get rekt.');
|
||||
},
|
||||
|
||||
'~del_clikill': function(event) {
|
||||
var pattern = event.params.slice(1).join(' ');
|
||||
this.config.cliconn_patterns = _.without(this.config.cliconn_patterns, pattern);
|
||||
this.saveConfig();
|
||||
event.reply('Client connection notices matching pattern /'+ pattern +'/ will no longer get rekt.');
|
||||
},
|
||||
|
||||
'~list_clikill': function(event) {
|
||||
event.reply('Currently active "cliconn" kills (to use add/del, provide the pattern, not the index, and do not include the surrounding //');
|
||||
_.each(this.config.cliconn_patterns, function(pattern, i) {
|
||||
event.reply('['+i+'] /' + pattern + '/');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_.each(this.commands, function(c) {
|
||||
c.access = 'moderator';
|
||||
});
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new kill_namespam(dbot);
|
||||
};
|
17
modules/kill_namespam/strings.json
Normal file
17
modules/kill_namespam/strings.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"namespam_act": {
|
||||
"en": "{user} triggered nickname hilight anti-spam (greater than {sensitivity}). Action: {action}."
|
||||
},
|
||||
"spamcont_act": {
|
||||
"en": "{user} triggered advertising anti-spam (content). Action: {action}"
|
||||
},
|
||||
"clikill_act": {
|
||||
"en": "Added K-Line for {ip}, due to matching pattern: /{pattern}/"
|
||||
},
|
||||
"clikill_spared": {
|
||||
"en": "{user} spared from clikill matched pattern: /{pattern}/"
|
||||
},
|
||||
"sasl_hint": {
|
||||
"en": "You have been killed by our spam filter. You can avoid broad VPN filters by using SASL."
|
||||
}
|
||||
}
|
66
modules/lastfm/README.md
Normal file
66
modules/lastfm/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
## LastFM
|
||||
|
||||
Adds various LastFM functionalities.
|
||||
|
||||
### Description
|
||||
|
||||
This module provides a command which allows users to show stats of LastFM and such stuff.
|
||||
|
||||
### Dependencies
|
||||
|
||||
It has following dependencies:
|
||||
+ [request](https://github.com/mikeal/request)
|
||||
+ [async](https://github.com/caolan/async)
|
||||
+ [moment](https://github.com/moment/moment)
|
||||
|
||||
### config.json
|
||||
|
||||
api_key and output prefix can be set.
|
||||
Example:
|
||||
```
|
||||
{
|
||||
"dependencies": [ "profile" ],
|
||||
"api_key": "blah",
|
||||
"outputPrefix": "\u000315,5last.fm\u000f"
|
||||
}
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
|
||||
#### ~lastfm [user]
|
||||
Display all scrobbles of a user.
|
||||
Example:
|
||||
+ ~lastfm reality
|
||||
|
||||
#### ~scrobbliest
|
||||
Displays the users with the most scrobbles.
|
||||
Example:
|
||||
+ ~scrobbliest
|
||||
|
||||
#### ~suggestion
|
||||
Displays a suggestion based on the listened scrobbles.
|
||||
Example:
|
||||
+ ~suggestion
|
||||
|
||||
#### ~listening
|
||||
Displays the currently/last played song of the posting user.
|
||||
Example:
|
||||
+ ~listening
|
||||
|
||||
#### ~taste [user]
|
||||
Compares two users (the posting user and the defined user).
|
||||
Example:
|
||||
+ ~taste reality
|
||||
|
||||
#### ~tastiest
|
||||
Displays the users that matches the most in music taste.
|
||||
Example:
|
||||
+ ~tastiest
|
||||
|
||||
#### ~artists [user]
|
||||
Compares two users (the posting user and the defined user) and displays their matching artists.
|
||||
Example:
|
||||
+ ~artists reality
|
||||
|
||||
### TODO
|
5
modules/lastfm/config.json
Normal file
5
modules/lastfm/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": [ "profile", "youtube", "spotify" ],
|
||||
"api_key": "blah",
|
||||
"outputPrefix": "\u000315,5last.fm\u000f"
|
||||
}
|
526
modules/lastfm/lastfm.js
Normal file
526
modules/lastfm/lastfm.js
Normal file
@ -0,0 +1,526 @@
|
||||
/**
|
||||
* Module Name: Last.FM
|
||||
* Description: Various lastfm functionality.
|
||||
*/
|
||||
|
||||
var _ = require('underscore')._,
|
||||
request = require('request'),
|
||||
async = require('async'),
|
||||
moment = require('moment');
|
||||
|
||||
var lastfm = function(dbot) {
|
||||
this.ApiRoot = 'http://ws.audioscrobbler.com/2.0/';
|
||||
|
||||
this.internalAPI = {
|
||||
'getLastFM': function(server, nick, callback) {
|
||||
dbot.api.profile.getProfile(server, nick, function(err, user, profile) {
|
||||
if(user) {
|
||||
if(profile && _.has(profile.profile, 'lastfm') && _.isString(profile.profile.lastfm)) {
|
||||
callback(user, profile.profile.lastfm.toLowerCase());
|
||||
} else {
|
||||
callback(user, null);
|
||||
}
|
||||
} else {
|
||||
callback(null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.api = {
|
||||
'getRandomArtistTrack': function(mbid, callback) {
|
||||
request.get(this.ApiRoot, {
|
||||
'qs': {
|
||||
'method': 'artist.gettoptracks',
|
||||
'mbid': mbid,
|
||||
'api_key': this.config.api_key,
|
||||
'format': 'json',
|
||||
'limit': 10
|
||||
},
|
||||
'json': true
|
||||
}, function(err, res, body) {
|
||||
if(_.has(body, 'toptracks') && _.has(body.toptracks, 'track')) {
|
||||
var tracks = body.toptracks.track;
|
||||
choice = _.random(0, tracks.length - 1),
|
||||
track = tracks[choice];
|
||||
callback(null, track);
|
||||
} else {
|
||||
callback('idk', body);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'getSimilarArtists': function(mbid, callback) {
|
||||
request.get(this.ApiRoot, {
|
||||
'qs': {
|
||||
'method': 'artist.getsimilar',
|
||||
'mbid': mbid,
|
||||
'api_key': this.config.api_key,
|
||||
'format': 'json',
|
||||
'limit': 10
|
||||
},
|
||||
'json': true
|
||||
}, function(err, res, body) {
|
||||
if(_.has(body, 'similarartists') && _.has(body.similarartists, 'artist')) {
|
||||
callback(null, body.similarartists.artist);
|
||||
} else {
|
||||
callback('idk', body);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'getListening': function(username, callback) {
|
||||
request.get(this.ApiRoot, {
|
||||
'qs': {
|
||||
'user': username,
|
||||
'limit': 2,
|
||||
'nowplaying': true,
|
||||
'method': 'user.getrecenttracks',
|
||||
'api_key': this.config.api_key,
|
||||
'format': 'json'
|
||||
},
|
||||
'json': true
|
||||
}, function(err, res, body) {
|
||||
if(_.has(body, 'error') && body.error == 6) {
|
||||
callback('no_user', null);
|
||||
} else if(_.has(body, 'recenttracks') && _.has(body.recenttracks, 'track')
|
||||
&& !_.isUndefined(body.recenttracks.track[0])) {
|
||||
callback(null, body.recenttracks.track[0]);
|
||||
} else {
|
||||
callback('no_listen', null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
'tasteCompare': function(user, oUser, callback) {
|
||||
request.get(this.ApiRoot, {
|
||||
'qs': {
|
||||
'type1': 'user',
|
||||
'type2': 'user',
|
||||
'value1': user,
|
||||
'value2': oUser,
|
||||
'method': 'tasteometer.compare',
|
||||
'api_key': this.config.api_key,
|
||||
'format': 'json'
|
||||
},
|
||||
'json': true
|
||||
}, function(err, res, body) {
|
||||
console.log(body);
|
||||
if(_.has(body, 'error') && body.error == 6 || body.error == 7) {
|
||||
callback('no_user', user, null);
|
||||
} else if(_.has(body, 'comparison') && _.has(body.comparison, 'result')) {
|
||||
callback(null, body.comparison.result);
|
||||
} else {
|
||||
callback('idk', null);
|
||||
}
|
||||
});
|
||||
},
|
||||
*/
|
||||
|
||||
'getInfo': function(lfm, callback) {
|
||||
request.get(this.ApiRoot, {
|
||||
'qs': {
|
||||
'user': lfm,
|
||||
'method': 'user.getinfo',
|
||||
'api_key': this.config.api_key,
|
||||
'format': 'json'
|
||||
},
|
||||
'json': true
|
||||
}, function(err, res, body) {
|
||||
if(_.has(body, 'error') && body.error == 6 || body.error == 7) {
|
||||
callback('no_user', null);
|
||||
} else if(_.has(body, 'user')) {
|
||||
callback(null, body.user);
|
||||
} else {
|
||||
callback('idk', null);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.commands = {
|
||||
'~lastfm': function(event) {
|
||||
var user = event.rUser,
|
||||
lfm = event.rProfile.lastfm;
|
||||
if(event.res[0]) {
|
||||
user = event.res[0].user;
|
||||
lfm = event.res[0].lfm;
|
||||
}
|
||||
|
||||
this.api.getInfo(lfm, function(err, profile) {
|
||||
if(!err) {
|
||||
console.log(profile);
|
||||
event.reply(dbot.t('lfm_profile', {
|
||||
'user': user.currentNick,
|
||||
'plays': profile.playcount.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"),
|
||||
'date': moment(profile.registered['#text'] * 1000).format('DD/MM/YYYY'),
|
||||
'link': profile.url
|
||||
}));
|
||||
} else {
|
||||
if(err == 'no_user') {
|
||||
event.reply(dbot.t('lfm_unknown'));
|
||||
} else if(err == 'no_listen') {
|
||||
event.reply(dbot.t('no_listen', { 'user': event.user }));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'~scrobbliest': function(event) {
|
||||
dbot.api.profile.getAllProfilesWith('lastfm', function(profiles) {
|
||||
if(profiles) {
|
||||
var plays = [];
|
||||
async.each(profiles, function(profile, done) {
|
||||
this.api.getInfo(profile.profile.lastfm, function(err, lProfile) {
|
||||
if(!err) {
|
||||
plays.push({
|
||||
'user': profile.id,
|
||||
'plays': parseInt(lProfile.playcount)
|
||||
});
|
||||
}
|
||||
done();
|
||||
});
|
||||
}.bind(this), function() {
|
||||
var scrobbliest = _.chain(plays)
|
||||
.sortBy(function(p) { return p.plays; })
|
||||
.reverse()
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
async.each(scrobbliest, function(item, done) {
|
||||
dbot.api.users.getUser(item.user, function(err, user) {
|
||||
item.user = user;
|
||||
done();
|
||||
});
|
||||
}, function() {
|
||||
var output = dbot.t('lfm_scrobbliest');
|
||||
_.each(scrobbliest, function(item) {
|
||||
output += item.user.currentNick + ' (' +
|
||||
item.plays.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")+ '), ';
|
||||
});
|
||||
event.reply(output.slice(0, -2));
|
||||
});
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply('no suitable profiles');
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'~suggestion': function(event) {
|
||||
this.api.getListening(event.rProfile.lastfm, function(err, track) {
|
||||
if(!err) {
|
||||
this.api.getSimilarArtists(track.artist.mbid, function(err, similar) {
|
||||
if(!err) {
|
||||
var choice = _.random(0, similar.length - 1);
|
||||
this.api.getRandomArtistTrack(similar[choice].mbid, function(err, track) {
|
||||
if(!err) {
|
||||
var output = dbot.t('lfm_suggestion', {
|
||||
'user': event.user,
|
||||
'name': track.name,
|
||||
'artist': track.artist.name
|
||||
});
|
||||
var term = track.name + ' ' + track.artist.name;
|
||||
|
||||
async.parallel({
|
||||
youtube: function(cb) {
|
||||
dbot.api.youtube.search(term, function(body) {
|
||||
if(_.isObject(body) && _.has(body, 'items') && body.items.length > 0) {
|
||||
var link = body.items[0].id.videoId
|
||||
if(link) {
|
||||
cb(null,"https://youtu.be/" + link);
|
||||
} else {
|
||||
cb(null, undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
spotify: function(cb) {
|
||||
dbot.api.spotify.spotifySearch(term, function(body, url, uri) {
|
||||
if(body) {
|
||||
if (!dbot.modules.minify) {
|
||||
cb(null, { url: url, uri:uri });
|
||||
} else {
|
||||
dbot.modules.minify.api.minify(url, "bitly", function(mini) {
|
||||
cb(null, { url:mini || url, uri:uri });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cb(null, undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (results.youtube || results.spotify) output += " - "
|
||||
|
||||
if (results.youtube) output += results.youtube;
|
||||
if (results.spotify) {
|
||||
if (results.youtube) output += " | ";
|
||||
output += results.spotify.url + " - " + results.spotify.uri;
|
||||
}
|
||||
|
||||
event.reply(output);
|
||||
});
|
||||
} else {
|
||||
event.reply('Couldn\'t get any suggested tracks.');
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
event.reply('Couldn\'t find any similar artists to what you\'re listening to.');
|
||||
console.log(err);
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
if(err == 'no_user') {
|
||||
event.reply(dbot.t('lfm_unknown'));
|
||||
} else if(err == 'no_listen') {
|
||||
event.reply(dbot.t('no_listen', { 'user': event.user }));
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
'~listening': function(event) {
|
||||
var user = event.rUser,
|
||||
lfm = event.rProfile.lastfm;
|
||||
if(event.res[0]) {
|
||||
user = event.res[0].user;
|
||||
lfm = event.res[0].lfm;
|
||||
}
|
||||
|
||||
this.api.getListening(lfm, function(err, track) {
|
||||
if(!err) {
|
||||
var term = track.name + ' ' + track.artist['#text'],
|
||||
output = '';
|
||||
if(_.has(track, '@attr') && _.has(track['@attr'], 'nowplaying') && track['@attr'].nowplaying == 'true') {
|
||||
output = dbot.t('now_listening', {
|
||||
'user': user.currentNick,
|
||||
'track': track.name,
|
||||
'artist': track.artist['#text']
|
||||
});
|
||||
} else {
|
||||
output = dbot.t('last_listened', {
|
||||
'user': user.currentNick,
|
||||
'track': track.name,
|
||||
'artist': track.artist['#text']
|
||||
});
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
youtube: function(cb) {
|
||||
dbot.api.youtube.search(term, function(body) {
|
||||
if(_.isObject(body) && _.has(body, 'items') && body.items.length > 0) {
|
||||
var link = body.items[0].id.videoId
|
||||
if(link) {
|
||||
cb(null,"https://youtu.be/" + link);
|
||||
} else {
|
||||
cb(null, undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
spotify: function(cb) {
|
||||
dbot.api.spotify.spotifySearch(term, function(body, url, uri) {
|
||||
if(body) {
|
||||
if (!dbot.modules.minify) {
|
||||
cb(null, { url: url, uri:uri });
|
||||
} else {
|
||||
dbot.modules.minify.api.minify(url, "bitly", function(mini) {
|
||||
cb(null, { url:mini || url, uri:uri });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cb(null, undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (results.youtube || results.spotify) output += " - "
|
||||
|
||||
if (results.youtube) output += results.youtube;
|
||||
if (results.spotify) {
|
||||
if (results.youtube) output += " | ";
|
||||
output += results.spotify.url + " - " + results.spotify.uri;
|
||||
}
|
||||
|
||||
event.reply(output);
|
||||
});
|
||||
|
||||
} else {
|
||||
if(err == 'no_user') {
|
||||
event.reply(dbot.t('lfm_unknown'));
|
||||
} else if(err == 'no_listen') {
|
||||
event.reply(dbot.t('no_listen', { 'user': user.currentNick }));
|
||||
}
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
'~taste': function(event) {
|
||||
var u1 = event.rUser,
|
||||
lfm1 = event.rProfile.lastfm,
|
||||
u2 = event.res[0].user,
|
||||
lfm2 = event.res[0].lfm;
|
||||
|
||||
this.api.tasteCompare(event.rProfile.lastfm, lfm2, function(err, comp) {
|
||||
if(!err) {
|
||||
var score = Math.floor(comp.score * 100);
|
||||
event.reply(dbot.t('taste_compat', {
|
||||
'user1': event.user,
|
||||
'user2': u2.currentNick,
|
||||
'score': score
|
||||
}));
|
||||
} else {
|
||||
if(err == 'no_user') {
|
||||
event.reply('Unknown Last.FM user.');
|
||||
} else {
|
||||
event.reply('Well something went wrong and I don\'t know what it means');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'~tastiest': function(event) {
|
||||
var sortGoodScores = function(goodScores) {
|
||||
var tastiest = _.chain(goodScores)
|
||||
.sortBy(function(p) { return p.score; })
|
||||
.reverse()
|
||||
.first(10)
|
||||
.value();
|
||||
|
||||
async.each(tastiest, function(pair, done) {
|
||||
if(!_.isObject(pair.p1)) { // fix this
|
||||
dbot.api.users.getUser(pair.p1, function(err, user) {
|
||||
pair.p1 = user;
|
||||
dbot.api.users.getUser(pair.p2, function(err, user) {
|
||||
pair.p2 = user;
|
||||
done();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}, function() {
|
||||
var output = 'Most musically compatible users: ';
|
||||
_.each(tastiest, function(pair) {
|
||||
output += pair.p1.currentNick + ' & ' +
|
||||
pair.p2.currentNick + ' (' + pair.score +
|
||||
'%), ';
|
||||
});
|
||||
event.reply(output.slice(0, -2));
|
||||
});
|
||||
};
|
||||
|
||||
if(this.tastyCache && Date.now() - this.tastyCacheStamp <= 1800000) {
|
||||
sortGoodScores(this.tastyCache);
|
||||
} else {
|
||||
event.reply('Updating tasty cache... Hold onto your coconuts...');
|
||||
dbot.api.profile.getAllProfilesWith('lastfm', function(profiles) {
|
||||
if(profiles) {
|
||||
var scores = {}; // Using this structure first for easier testing in the async
|
||||
async.eachSeries(profiles, function(p1, next) {
|
||||
scores[p1.id] = {};
|
||||
async.eachSeries(profiles, function(p2, subnext) {
|
||||
if(p1.id == p2.id || p1.profile.lastfm == p2.profile.lastfm || _.has(scores, p2.id) && _.has(scores[p2.id], p1.id)) {
|
||||
subnext();
|
||||
} else {
|
||||
this.api.tasteCompare(p1.profile.lastfm, p2.profile.lastfm, function(err, comp) {
|
||||
if(!err) {
|
||||
var score = Math.floor(comp.score * 100);
|
||||
scores[p1.id][p2.id] = score;
|
||||
}
|
||||
subnext();
|
||||
});
|
||||
}
|
||||
}.bind(this), function() { next(); });
|
||||
}.bind(this), function(err) {
|
||||
// Now we better structure the scores for sorting
|
||||
var goodScores = [];
|
||||
_.each(scores, function(subscores, p1) {
|
||||
_.each(subscores, function(aScore, p2) {
|
||||
goodScores.push({
|
||||
'p1': p1,
|
||||
'p2': p2,
|
||||
'score': aScore
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.tastyCache = goodScores;
|
||||
this.tastyCacheStamp = new Date().getTime();
|
||||
sortGoodScores(goodScores);
|
||||
}.bind(this));
|
||||
} else {
|
||||
event.reply('No suitable profiles');
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
'~artists': function(event) {
|
||||
var u1 = event.rUser,
|
||||
lfm1 = event.rProfile.lastfm,
|
||||
u2 = event.res[0].user,
|
||||
lfm2 = event.res[0].lfm;
|
||||
|
||||
this.api.tasteCompare(event.rProfile.lastfm, lfm2, function(err, comp) {
|
||||
if(!err) {
|
||||
var artists = _.pluck(comp.artists.artist, 'name').join(', ');
|
||||
event.reply(dbot.t('common_artists', {
|
||||
'user1': event.user,
|
||||
'user2': u2.currentNick,
|
||||
'common': artists
|
||||
}));
|
||||
} else {
|
||||
if(err == 'no_user') {
|
||||
event.reply(dbot.t('lfm_unknown'));
|
||||
} else {
|
||||
event.reply('Well something went wrong and I don\'t know what it means');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
//this.commands['~taste'].regex = [/^taste ([\d\w[\]{}^|\\`_-]+?)/, 2];
|
||||
this.commands['~artists'].regex = [/^artists ([\d\w[\]{}^|\\`_-]+?)/, 2];
|
||||
|
||||
_.each(this.commands, function(command) {
|
||||
command.resolver = function(event, callback) {
|
||||
if(event.rProfile && _.has(event.rProfile, 'lastfm')) {
|
||||
if(event.params[1]) {
|
||||
this.internalAPI.getLastFM(event.server, event.params[1], function(user, lfm) {
|
||||
if(user && lfm) {
|
||||
event.res.push({
|
||||
'user': user,
|
||||
'lfm': lfm
|
||||
});
|
||||
callback(false);
|
||||
} else {
|
||||
if(!user) {
|
||||
event.reply('Unknown user.');
|
||||
} else {
|
||||
event.reply(user.currentNick + ': Set a lastfm username with "~set lastfm username"');
|
||||
}
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(false);
|
||||
}
|
||||
} else {
|
||||
event.reply(event.user + ': Set a lastfm username with "~set lastfm username"');
|
||||
callback(true);
|
||||
}
|
||||
}.bind(this);
|
||||
}, this);
|
||||
};
|
||||
|
||||
exports.fetch = function(dbot) {
|
||||
return new lastfm(dbot);
|
||||
};
|
35
modules/lastfm/strings.json
Normal file
35
modules/lastfm/strings.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"now_listening": {
|
||||
"en": "{user} is listening to {track} by {artist}",
|
||||
"de": "{user} hört {track} von {artist}"
|
||||
},
|
||||
"last_listened": {
|
||||
"en": "{user} last listened to {track} by {artist}",
|
||||
"de": "{user} hörte zuletzt {track} von {artist}"
|
||||
},
|
||||
"no_listen": {
|
||||
"en": "{user} doesn't seem to have listened to anything recently :'(",
|
||||
"de": "{user} scheint in letzter Zeit nichts gehört zu haben :'("
|
||||
},
|
||||
"taste_compat": {
|
||||
"en": "{user1} and {user2} are {score}% musically compatible!",
|
||||
"de": "{user1} und {user2} sind {score}% musikalisch kompatibel!"
|
||||
},
|
||||
"common_artists": {
|
||||
"en": "Artists {user1} and {user2} have in common: {common}",
|
||||
"de": "Künstler, die {user1} und {user2} gemeinsam haben: {common}"
|
||||
},
|
||||
"lfm_suggestion": {
|
||||
"en": "{user}: Try listening to {name} by {artist}",
|
||||
"de": "{user}: Versuche mal, {name} von {artist} anzuhören"
|
||||
},
|
||||
"lfm_profile": {
|
||||
"en": "{user} has scrobbled {plays} tracks since {date} - {link}"
|
||||
},
|
||||
"lfm_scrobbliest": {
|
||||
"en": "Users with the most played tracks: "
|
||||
},
|
||||
"lfm_unknown": {
|
||||
"en": "Unknown Last.FM user. Please set your Last.FM user by typing '~set lastfm <name>' where <name> is your Last.FM username."
|
||||
}
|
||||
}
|
5
modules/leafly/config.json
Normal file
5
modules/leafly/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"outputPrefix": "\u00033weed\u000f",
|
||||
"app_key": "",
|
||||
"app_id": ""
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user