Compare commits

...

478 Commits

Author SHA1 Message Date
20582defd5
Enclosing DEBUG statements in dbot.config.debugMode
Signed-off-by: Georg <georg@lysergic.dev>
2021-08-29 20:24:53 +02:00
ac3d2761bc
PASS authentication and fixed PING-PONG
Signed-off-by: Georg <georg@lysergic.dev>
2021-08-29 20:03:49 +02:00
70a21d2b18
Signed-off-by: Georg <georg@lysergic.dev>
Init

- Initial fixes in modules
- Bundled adapted jsbot
- Elaborative README.md
2021-08-24 21:16:26 +02:00
reality
77a9c346d8
Merge pull request #688 from powerjungle/master
Add new clear tells command
2021-03-15 13:53:05 +00:00
powerjungle
97ebd371bf
Add new clear tells command 2021-03-10 15:05:39 +01:00
reality
ec3d6ed13b
Merge pull request #687 from powerjungle/master
Add README.md to tell module
2021-02-28 14:24:15 +00:00
powerjungle
a245d385f7
Add README.md to tell module 2021-02-28 13:51:47 +01:00
Luke Slater
695398d8fb
Merge pull request #682 from Scritches/master
Initial commit of new todo list module
2019-04-24 22:46:53 +01:00
Scritches
08922c8e67 Initial commit of new todo list module 2018-11-02 16:21:51 -04:00
Luke Slater
dfdb31b31d
Merge pull request #680 from Scritches/master
Improved error messages for LastFM module.
2018-09-02 11:54:02 +01:00
Scritches
1b18c9f048 Improved error messages for LastFM module. 2018-09-01 22:21:55 -04:00
Luke Slater
8c97c665a6
Merge pull request #679 from Scritches/master
Urban dictionary API changed - removed result_type
2018-08-27 19:26:50 +01:00
Scritches
1657e7cc02 Urban dictionary API changed - removed result_type 2018-08-26 18:08:01 -04:00
Luke Slater
09f8f525e6
time is literally the stupidest thing there is 2018-06-06 23:20:39 +01:00
reality
d64375ca50 sasl hint 2018-05-28 14:35:11 +00:00
reality
a4697e8d36 Merge branch 'master' of https://github.com/reality/dbot 2018-05-20 21:46:26 +00:00
reality
a97bda3c20 HA 2018-05-20 21:46:19 +00:00
Luke Slater
ab6cb80ea2
Merge pull request #677 from Scritches/master
'kick' and 'kill_namespam' module changes
2018-05-16 10:34:38 +01:00
Scritches
c26be5410f
Merge branch 'master' into master 2018-05-15 20:12:40 -04:00
Scritches
e11abe5af4 kills are deferred until user joins a non-exempt channel 2018-05-15 20:09:43 -04:00
Luke Slater
6a255244df
fix save callback lack (lol) 2018-05-12 18:56:14 +01:00
Scritches
f3dcc0e693 ~nban no longer adds redundant +b if user was killed 2018-05-05 13:45:54 -04:00
Scritches
ed69582c7a 4-indent to 2-indent 2018-05-05 13:35:49 -04:00
Luke Slater
814a84ee8e
Merge pull request #676 from Scritches/master
Various module fixes / improvements
2018-05-05 16:34:04 +01:00
Scritches
a5f9998872 Added ~sp as alias to ~spotify 2018-05-01 16:58:52 -04:00
Scritches
d544dcf017 clikill spares SASL-authenticated users from match kills 2018-05-01 16:47:25 -04:00
Scritches
bbfc4c18a6 xkcd title / relevent text search feature done right. 2018-04-26 19:12:20 -04:00
Scritches
0c8c377b08 ~xkcd command now finds relevant XKCDs when given text instead of a number 2018-04-26 17:14:18 -04:00
Scritches
c298813b28 Adds OMDB module to replace the RottenTomatoes module.
~movie command replaces ~rt command. API key from http://www.omdbapi.com/apikey.aspx
2018-04-26 16:58:15 -04:00
Scritches
0f0877f27a Fixed YouTube video duration reporting 2018-04-26 16:50:21 -04:00
reality
554a9e995b do not fail broken regex 2018-04-20 21:01:11 +00:00
Luke Slater
ae989d60ae
Merge pull request #675 from Scritches/master
Adds a module that queries the Online Encyclopedia of Integer Sequences
2018-04-19 21:50:58 +01:00
Scritches
f7b83e98b7 support for random sequences 2018-04-18 00:50:22 -04:00
Scritches
b415ee086f Add OEIS module
## Queries the **Online Encyclopedia of Integer Sequences** for interesting number sequences.

#### Sequence by example:
```
~sequence 1 2 3 4 5
```

#### Sequence by ID number
```
~sequence a27
```

#### Sequence by keywords
```
~sequence positive integers
```

In all cases the following is returned:
```
[oeis] A000027: "The positive integers." - (1, 2, 3, 4, 5, 6, 7, 8, ...) https://oeis.org/A000027
```
2018-04-18 00:26:05 -04:00
Luke Slater
944827c4d9
Merge pull request #674 from Scritches/master
Bunch of little fixes and style updates
2018-04-14 20:14:57 +01:00
Scritches
97642f35a9 Got rid of all the const
But muh self-documenting code :'(
2018-04-14 15:07:26 -04:00
Scritches
2d20f19572 Fix for lingering votequiets 2018-04-14 14:54:23 -04:00
Luke Slater
98c8894f95
Merge pull request #673 from Scritches/master
Initial rewrite of goodreads module using async/await
2018-04-14 19:51:57 +01:00
Scritches
11a7f1d20e Fix ~lastfm registered date display 2018-04-14 14:01:36 -04:00
Scritches
86d0a6f31b Link module title regexp fix
Fix for sites that use <\/title> instead of </title> to close their title elements.
2018-04-14 13:38:28 -04:00
Scritches
71c2c52d47 Initial rewrite of goodreads module using async/await
Covers original functionality plus adds a new ~reading command.

There is probably a lot of duplication in the module itself that can be cleaned up with some additional metaprogramming but *eh* I'm tired tonight.
2018-04-12 01:11:15 -04:00
Luke Slater
fbc4ac5afe
Merge pull request #672 from Scritches/master
Some more music module fixes
2018-04-11 14:46:23 +01:00
Scritches
a574b7d2ed more reasonable ~define
Tell wordnik to use the canonical version of the word where available (to avoid responses like "the plural of 'cat'" when doing "~define cats" - just tell me what cats are dammit)
2018-04-10 16:06:38 -04:00
Scritches
93ee3cb08b lastfm and spotify fixes
### lastfm module

removed ~taste and taste compare functions from lastfm - their API no longer supports these calls

Improved error reporting for ~suggestion and also parallelized the youtube/spotify queries

### spotify module

removed link matching - the Link module provides the exact same information as directly querying spotify so there's no real point
2018-04-10 15:35:57 -04:00
Scritches
597171c37d set last link in channel when ~yt command is used 2018-04-10 13:30:20 -04:00
Luke Slater
8182980420
Merge pull request #671 from Scritches/master
Words module api support for phrases
2018-03-30 17:24:30 +01:00
Scritches
bb28cebde1 Words module api support for phrases
~define, ~example, and ~like now all support phrases.
2018-03-30 12:22:03 -04:00
Luke Slater
48728c5801
Merge pull request #670 from Scritches/master
New minify module
2018-03-28 21:52:55 +01:00
Scritches
43cfe3b637 Minify module
basic framework for more human-readable error responses from the bitly minifier
2018-03-26 16:23:03 -04:00
Scritches
19cc4c474d New minify module
Added a module that provides URL minifier support for dbot.

Updated spotify and lastfm modules with support for url minimization via the include 'bitly' minimizer (which minimizes spotify.com links to spoti.fi - very nice)
2018-03-26 13:52:06 -04:00
Luke Slater
f5c9a275a9
Merge pull request #669 from Scritches/master
Bunch of spotify, youtube, and lastfm improvements
2018-03-25 03:28:22 +01:00
Scritches
ff7be1f31d https instead of http for ~listening youtube link 2018-03-24 22:10:57 -04:00
Scritches
0118f962b6 Include spotify desktop URI in ~syt results 2018-03-24 21:11:46 -04:00
Scritches
167525b5b9 Added ~ytpl command to search youtube for playlists 2018-03-24 20:57:57 -04:00
Scritches
25d572e8cb lastfm ~listening: make youtube and spotify api calls in parallel 2018-03-24 19:24:57 -04:00
Scritches
72a4552742 Revert "test"
This reverts commit bd148915ed0bb814971e8031cef5b5312502500c.
2018-03-24 17:22:22 -04:00
Scritches
bd148915ed test 2018-03-24 17:21:26 -04:00
Scritches
354c0935b9 Move spotify api key into config; Restore spotify functionality to lastfm module ~listening command 2018-03-24 16:20:33 -04:00
Luke Slater
4a4e41b702
Merge pull request #668 from Scritches/master
Added padding around the urls in the output
2018-03-24 01:42:18 +00:00
Scritches
b87175e665 Added padding around the urls in the output 2018-03-23 21:37:43 -04:00
Luke Slater
9a7e1ab4bf
Merge pull request #666 from Scritches/master
fixed tabs/spaces and added spotify uri for desktop app linkability
2018-03-22 11:03:55 +00:00
Scritches
10c642c94f fixed tabs/spaces and added spotify uri for desktop app linkability 2018-03-22 03:04:14 -04:00
Luke Slater
dde0200032
Merge pull request #665 from Scritches/master
Added support for the minified youtube links returned by the ~yt command to the ~syt command
2018-03-08 21:25:03 +00:00
Scritches
217e65df96 Added necessary authentication for spotify API calls and fixed youtube link regex. 2018-03-08 16:11:12 -05:00
Scritches
03067f97d6 Added support for the minified youtube links returned by the ~yt command 2018-03-07 17:30:35 -05:00
reality
00215f62f0 that should learn it 2018-03-01 14:21:09 +00:00
reality
9a4a86c6ba weird config 2018-03-01 14:15:35 +00:00
reality
7d31560458 clean 2018-03-01 14:01:00 +00:00
reality
f55f645a3b some smal fixes and clarities 2018-03-01 13:57:45 +00:00
reality
7debad9a1d oopsie 2018-03-01 13:53:02 +00:00
reality
9faffa0d11 add cliconn autorek 2018-03-01 13:41:59 +00:00
reality
3c22b811ad clean up the bug with fresh whowas lookup 2018-02-28 17:42:08 +00:00
reality
329836d577 Merge branch 'master' of https://github.com/reality/dbot 2018-02-28 17:39:31 +00:00
reality
5796308e62 fix temp unban 2018-02-28 17:39:21 +00:00
Luke Slater
3972b5272b
the start of yo callback ain't a valid host 2018-02-24 00:56:26 +00:00
reality
17ecf62da0 finish alsuti 2018-02-23 14:03:16 +00:00
reality
9b55509257 alsuti sustatus 2018-02-23 13:47:23 +00:00
reality
69c06537e2 sustatus alsuti 2018-02-23 12:30:18 +00:00
reality
8fe032579d prevent multiple yes votes by using votequiet fallback (lol) 2018-02-22 18:28:10 +00:00
reality
fe8168a237 whowas works correctly as fallback for host lookup 2018-02-22 17:45:04 +00:00
reality
016c132158 start fix whowas 2018-02-22 17:30:15 +00:00
reality
9df86cdd99 ability to pass host manually to nunban, and extra info about host in notifies 2018-02-22 16:01:40 +00:00
reality
00ac08a9da autoquiet at 4 on votequiet, increase period to 1.5 mins 2018-02-17 18:41:04 +00:00
reality
eba4a9a4a1 votequiet on already polled user adds yes vote 2018-02-17 18:06:30 +00:00
reality
beaa420015 i am not very smart 2018-02-16 14:36:56 +00:00
reality
709cd085a9 add content kills 2018-02-16 14:29:17 +00:00
reality
56d12bd649 also add check for distinctive spam content 2018-02-16 14:22:16 +00:00
reality
510a02d08a exempt 2018-02-16 13:44:47 +00:00
reality
d7f212ea52 haha check out the bug there, that is actually really funny 2018-02-16 13:37:26 +00:00
reality
3b6dd18611 more syntax 2018-02-16 13:30:56 +00:00
reality
58256a8f62 appleg 2018-02-16 13:29:04 +00:00
reality
0da6af229f syntax! 2018-02-16 13:19:12 +00:00
reality
8310dc38ff module to deal with ppl hilighting lots of ppl at once. bad 2018-02-16 13:17:52 +00:00
reality
ebb0a023c0 more appropriate note string 2018-02-10 12:59:02 +00:00
reality
f097685c3d notes fixes 2018-02-10 12:47:54 +00:00
reality
4f269a5c02 wtf was that there for? 2018-02-10 12:40:27 +00:00
Luke Slater
777d738394 new notes functionality 2018-02-10 12:35:31 +00:00
reality
e62a1c8484 external lpmcount 2017-11-02 18:22:05 +00:00
krn
0f1c6c6782 Merge pull request #664 from somehibs/master
install script uses command -v for executable detection
2017-07-31 18:17:01 +01:00
alex
b8bbaab9d9 install script uses command -v for executable detection 2017-07-22 03:56:17 +01:00
reality
3204c422bb fixes 2017-07-18 19:03:00 +00:00
reality
ec86b6e1a8 fix indentation and stuff 2017-07-18 14:56:01 +00:00
reality
07156b2eb3 this 2017-07-18 14:32:42 +00:00
reality
d37326c88e fix all ban 2017-07-18 14:28:42 +00:00
reality
1f0e13bd2a new jsbot 2017-07-18 13:19:23 +00:00
reality
0d233f69b7 fix for nick tracking wrt new jsbot 2017-07-18 03:11:18 +00:00
reality
e22133d685 fix ignore 2017-07-18 02:52:59 +00:00
reality
0d2d00635c fixes 2017-07-18 02:51:45 +00:00
reality
d41836e937 index for aliases 2017-07-18 02:16:07 +00:00
reality
c1c54bbd59 fixes 2017-07-11 14:36:14 +00:00
reality
6a38eb5289 update with fixes 2017-07-11 14:22:18 +00:00
reality
d25ec221c7 fix for new jsbot 2017-07-11 14:21:59 +00:00
reality
54efa27ff2 update jsbot 2017-07-11 12:44:38 +00:00
reality
f02d2b74df curse you spotify 2017-06-05 17:29:12 +00:00
reality
330868686d oops 2017-03-25 00:43:31 +00:00
reality
9a44c41fbd qsearch random quote 2017-03-25 00:40:52 +00:00
reality
92598b13c2 channel with log 2016-12-14 22:09:19 +00:00
reality
a57b123fed two space indent for log and karma, karma logging 2016-12-14 21:58:42 +00:00
reality
ef86954025 fix for new web api 2016-12-05 20:04:37 +00:00
reality
7964bb9e88 spotify link in listening out 2016-12-05 19:48:08 +00:00
rlty
238c112986 Merge pull request #658 from speeddefrost/master
Fix broken regex. Misc cleanup. Add some brackets.
2016-10-17 06:53:48 +00:00
speeddefrost
d4d101bc94 Fix broken regex. Misc cleanup. Add some brackets. 2016-10-16 23:43:16 -07:00
rlty
5191eca0a4 Merge pull request #654 from speeddefrost/master
Merge common time parameters for remind commands. - see code review for additional comments
2016-10-12 10:49:59 +00:00
speeddefrost
3f599ddfc2 Merge common time parameters for remind commands. 2016-10-04 05:49:38 -07:00
reality
0e95f8e42a cspeed 2016-06-21 08:22:49 +00:00
reality
485e08ff97 fix unban 2016-05-12 07:39:03 +00:00
reality
ed24043a55 fix ban 2016-05-12 07:38:18 +00:00
reality
e8f657de94 that should learn it 2016-05-07 16:29:18 +00:00
reality
aeea5a7e18 multiple udp clients 2016-05-07 16:04:10 +00:00
reality
2b043d018b remove console log 2016-04-24 00:16:53 +00:00
reality
45a27904bd changes 2016-04-24 00:14:54 +00:00
reality
cfbb71faa6 that should learn it? 2016-04-13 22:25:51 +00:00
reality
d9093e6da8 that should learn it? 2016-04-13 22:19:14 +00:00
reality
930607b4fe that should learn it? 2016-04-13 22:15:03 +00:00
reality
601d10b0de test reddit colour 2016-04-13 22:09:20 +00:00
reality
92c045d8c7 fix object object nonsense 2016-04-12 19:59:08 +00:00
reality
face69d31a underban 2016-04-11 22:01:59 +00:00
reality
45153a45d2 regex missing 2016-04-11 21:53:57 +00:00
reality
e767e43771 years 2016-04-11 21:51:45 +00:00
reality
2ebeea1ef4 test 2016-04-11 21:49:17 +00:00
reality
a58e1fcc4b new jsbot 2016-04-11 21:34:16 +00:00
reality
7f2878ce58 api function 2016-04-11 18:23:02 +00:00
reality
a5657a5a36 cached 2016-04-11 18:15:10 +00:00
reality
bab36d5af3 better geoip 2016-04-11 18:08:20 +00:00
reality
d19fc76088 new jsbot 2016-04-06 00:53:19 +00:00
reality
fc0fe6f5be fix 2016-03-31 22:00:55 +00:00
reality
45e44bc7f8 fix 2016-03-31 21:59:31 +00:00
reality
d442461076 no 2016-03-23 17:32:05 +00:00
reality
c47677a590 remove file 2016-03-23 15:43:47 +00:00
reality
f2c61c95b9 summary 2016-03-23 15:36:15 +00:00
reality
e120869bcd redirects 2016-03-22 23:03:46 +00:00
reality
a14d042be2 redirects 2016-03-22 22:56:29 +00:00
reality
bc114a10e6 redirects 2016-03-22 22:54:17 +00:00
reality
5e8ccb6bcc redirects 2016-03-22 22:47:21 +00:00
reality
b61c482a32 redirects 2016-03-22 22:30:06 +00:00
reality
ba2ad2649f working 2016-03-22 21:36:26 +00:00
reality
0473597b0a better ~lol 2016-03-22 21:31:59 +00:00
reality
04fb1cc67b add channel name 2016-03-20 17:51:48 +00:00
reality
72e6fca9e5 timeout 2016-03-20 17:48:07 +00:00
reality
71632f401b or is it that 2016-03-12 22:51:08 +00:00
reality
d31a95b3a3 wrong order for tetris scores 2016-03-12 22:30:37 +00:00
reality
fa431ac800 vitetris 2016-03-12 22:16:02 +00:00
reality
aed72f52dc fix flashy 2016-03-04 17:18:35 +00:00
reality
b9064302ba fix spaces 2016-03-03 17:41:17 +00:00
reality
40e20f06b0 ~w 2016-03-03 17:39:08 +00:00
reality
84f8e7a151 ~w 2016-03-03 17:36:21 +00:00
reality
86c570a8c8 ~wikipedia 2016-03-03 17:31:42 +00:00
reality
fd1a76d3bc translate module 2016-03-03 16:02:10 +00:00
reality
266f6b58f4 translate module 2016-03-03 15:55:47 +00:00
reality
212ae87e8b translate module 2016-03-03 15:52:29 +00:00
reality
6038443225 rookie mistake 2016-02-21 20:06:51 +00:00
rlty
8ce27053ce Merge pull request #652 from EddieCurtis/patch-3
Fixing url for m.links.alternate
2016-02-20 23:07:22 +00:00
Eddie Curtis
e2949094a2 Fixing url for m.links.alternate 2016-02-20 23:06:28 +00:00
reality
8095aea3d0 rookie mistake 2016-02-19 19:28:01 +00:00
reality
2f6dc6f25f rookie mistake 2016-02-19 19:24:01 +00:00
reality
d1b764d4ec rookie mistake 2016-02-19 19:18:43 +00:00
reality
badf17b0ec rookie mistake 2016-02-19 19:17:05 +00:00
reality
f4a203e416 rookie mistake 2016-02-19 19:15:06 +00:00
reality
d4c3edca0a ffs lol 2016-02-19 19:14:10 +00:00
reality
c26c776b48 oops 2016-02-19 19:13:32 +00:00
reality
c247f48fbe fff 2016-02-19 19:12:22 +00:00
reality
da8c297973 things 2016-02-19 18:59:22 +00:00
reality
8be0261295 things 2016-02-19 18:57:11 +00:00
reality
991411f28d thing 2016-02-19 18:55:43 +00:00
reality
c63c17d9d5 things 2016-02-16 17:39:10 +00:00
reality
4478f4e405 things 2016-02-16 17:37:09 +00:00
reality
660693281f things 2016-02-16 17:32:29 +00:00
reality
a4141288e5 batchstatus 2016-02-02 17:31:36 +00:00
reality
a107c81ad3 batchstatus 2016-02-02 15:03:57 +00:00
reality
ad448a51fa batchstatus 2016-02-02 15:00:34 +00:00
reality
affb89662f time thing 2016-01-29 22:33:58 +00:00
reality
2d2d00ccbf fix order 2016-01-29 22:19:35 +00:00
reality
da2ad191c9 fix ban count 2016-01-29 22:13:44 +00:00
reality
a52fdbcea5 i like colours 2016-01-29 18:05:12 +00:00
reality
c16f65c5b8 i like colours 2016-01-29 18:03:22 +00:00
reality
4afeb8ff38 sustatus thing 2016-01-29 17:58:04 +00:00
reality
431266136c sustatus thing 2016-01-29 17:56:38 +00:00
reality
e52699b211 sustatus thing 2016-01-29 17:55:03 +00:00
reality
9ca81e3094 fix regex 2016-01-28 18:41:44 +00:00
reality
c10b65ef3b various fixes to the reporting system and related stuff 2016-01-28 18:29:51 +00:00
reality
ac9488bad9 offlineReporting configurable 2016-01-28 17:36:19 +00:00
reality
3af2cbb678 remove url from warn 2016-01-26 16:57:23 +00:00
reality
8c06ec9dcf helpful output 2016-01-15 23:22:59 +00:00
reality
dc00730168 remind-like time periods for nban and quiet 2016-01-15 21:36:24 +00:00
reality
eb35db1074 oops 2015-12-09 20:11:33 +00:00
reality
890103e4ab oops 2015-12-09 19:00:39 +00:00
reality
eebf1d9680 tell 2015-12-09 18:53:32 +00:00
reality
44f46cfc75 yes 2015-11-17 18:52:49 +00:00
reality
5abb1ab166 who cares about floods 2015-10-27 19:38:10 +00:00
reality
92e4db287d oops 2015-10-27 19:36:51 +00:00
reality
a378ec7ab4 oops 2015-10-27 19:33:08 +00:00
reality
2ea263f997 oops 2015-10-27 19:30:32 +00:00
reality
d473d95e42 even quicker like 2015-10-27 19:26:58 +00:00
reality
339fb3ddfb ban from current channel first 2015-10-27 19:22:29 +00:00
reality
ef500e2d96 mesg 2015-10-20 18:59:55 +00:00
reality
04c411c6c1 mesg 2015-10-20 18:58:04 +00:00
reality
04d797c6d3 mesg 2015-10-20 18:56:00 +00:00
reality
b49b9b99c4 mesg 2015-10-20 18:30:15 +00:00
reality
e88f2a70b1 mesg 2015-10-20 18:29:05 +00:00
reality
2d0fa7fe59 name clash 2015-10-20 18:26:39 +00:00
reality
5b7c52a63f oops 2015-10-20 18:24:24 +00:00
reality
74377c1db7 dnsbl 2015-10-20 18:22:40 +00:00
reality
939797f5be ignore pms for stats 2015-10-13 05:36:49 +00:00
reality
3a57f11f69 ~myreminders 2015-10-08 18:07:50 +00:00
reality
bece1b245f ~random moved to ~randomdata 2015-08-19 19:11:33 +00:00
reality
2e3d4582f1 derp 2015-08-13 21:03:21 +00:00
reality
0069003cc7 scope 2015-08-13 21:02:14 +00:00
reality
c6d352038c run ncount on other users 2015-08-13 20:59:56 +00:00
reality
25d26b5586 quote interpolation recursion 2015-08-13 14:36:22 +00:00
reality
11a0a76a9d auth external 2015-07-24 01:02:21 +00:00
reality
1252ef39d1 add target to record 2015-07-23 09:58:31 +00:00
reality
bf54039724 oops 2015-07-22 23:24:40 +00:00
reality
b82a3e72ff properly resolve unbans 2015-07-22 23:21:56 +00:00
reality
d5b35a7b13 change the input for wolfram alpha command 2015-07-21 13:40:02 +00:00
reality
deae0ae352 ye 2015-07-20 15:21:56 +00:00
reality
8cd0b07c76 ? 2015-07-20 15:19:57 +00:00
reality
db25eadedf only post a radio update every 2 minutes 2015-07-20 15:13:01 +00:00
reality
bfa4de5a1a filesize in imgur 2015-07-19 21:34:09 +00:00
reality
cf509cb3c5 that was dumb 2015-07-19 17:03:18 +00:00
reality
5f8cbe3fb1 only save the db to disk every minute 2015-07-19 16:56:08 +00:00
reality
36496b64ac fix predo for quit 2015-07-19 00:15:31 +00:00
reality
7ce6cb5dc2 import moment 2015-07-17 23:39:19 +00:00
reality
5084722e0b import moment 2015-07-17 23:38:17 +00:00
reality
a3fd5e71f3 record daily last used [#650] 2015-07-17 23:37:24 +00:00
reality
fd0d3829d8 add time to ~concerning 2015-07-17 22:52:35 +00:00
reality
7fcd277815 ~rmlastwarning 2015-07-08 20:47:15 +00:00
reality
050fa88e47 working flag 2015-07-07 18:17:10 +00:00
reality
24aaf298f7 fix another potential race condition 2015-07-07 17:58:56 +00:00
reality
024ee7913e fix race condition in getting flag stack 2015-07-07 17:45:12 +00:00
reality
f7785c95a3 oops 2015-06-25 14:30:14 +00:00
reality
c584e7b8f5 tagging at the back@ too 2015-06-25 14:28:21 +00:00
reality
2483ec1ef6 idk 2015-05-29 17:26:04 +00:00
reality
aba5ac4fa5 oops 2015-05-06 18:20:25 +00:00
reality
4895c12b6d fix for nowplaying 2015-05-06 18:18:27 +00:00
reality
b28a5f6655 fix for nowplaying 2015-05-06 18:17:06 +00:00
reality
b440a232fc [Close #639] 2015-05-03 09:29:36 +00:00
reality
a9dff5c39e merge nicks 2015-05-01 07:58:36 +00:00
reality
10f853c921 fix for below one minite 2015-04-27 09:51:45 +00:00
reality
b9786b1d2f fix for below one minite 2015-04-27 09:50:51 +00:00
reality
5bf1e24d02 fix for below one minite 2015-04-27 09:49:53 +00:00
reality
25347f9006 fix for below one minite 2015-04-27 09:32:22 +00:00
reality
a582600f0f fix for below one minite 2015-04-27 09:30:59 +00:00
reality
ea07d55b9e fix youtube stuff 2015-04-27 09:23:27 +00:00
reality
613b9cd3bd Merge branch 'master' of https://github.com/reality/dbot 2015-04-27 09:21:14 +00:00
reality
81cfa22dcf fix youtube stuff 2015-04-27 09:20:53 +00:00
Luke Slater
2b7077642d Merge pull request #649 from amki/master
Fixed CTCP module and added some new commands
2015-04-26 05:04:03 +03:00
amki
9e7153cf42 Fixed CTCP module and added some new commands 2015-04-26 02:07:38 +02:00
Luke Slater
06f6491d99 Merge pull request #648 from amki/crypto
Moved crypto module to internal node crypto api, remove ciphers, add random
2015-04-25 21:05:17 +03:00
reality
ac2a6036eb meh 2015-04-25 11:05:59 +00:00
reality
744e075697 meh 2015-04-25 11:05:03 +00:00
reality
c0e8290bba meh 2015-04-25 11:03:26 +00:00
reality
be71f31c07 meh 2015-04-25 11:00:51 +00:00
reality
f4d25c0bfa find alias notifies 2015-04-25 10:58:45 +00:00
reality
849067127f find alias notifies 2015-04-25 10:42:30 +00:00
reality
3d92550c76 meh 2015-04-25 10:36:21 +00:00
reality
cb2e4da2ae meh 2015-04-25 10:35:40 +00:00
reality
42952a24e5 meh 2015-04-25 10:34:34 +00:00
reality
9f685779d5 meh 2015-04-25 10:31:44 +00:00
reality
e271ac2885 meh 2015-04-25 10:30:42 +00:00
reality
0108de389b meh 2015-04-25 10:28:47 +00:00
reality
2820dfb0c7 more random recipes 2015-04-25 10:27:22 +00:00
reality
548d4977f2 order 2015-04-25 10:26:35 +00:00
amki
79dd53e4c4 Moved crypto module to internal node crypto api, removed (pretty) senseless cipher command and added random command 2015-04-25 05:48:19 +02:00
reality
15de4c4f5d more random recipes 2015-04-23 15:37:42 +00:00
reality
19442bfe8f typical 2015-04-23 15:18:28 +00:00
reality
8e31ac0ed6 typical 2015-04-23 15:17:38 +00:00
reality
6dd233d51f typical 2015-04-23 15:17:04 +00:00
reality
24c792e4cf oops 2015-04-23 15:13:02 +00:00
reality
9d682455f5 food suggest 2015-04-23 15:11:45 +00:00
reality
e46135d1ec lol 2015-04-23 15:00:56 +00:00
reality
80ebcb76fd hehe 2015-04-23 14:56:47 +00:00
reality
fcb32eaac4 hehe 2015-04-23 14:56:13 +00:00
reality
ff8dc8ce6d oops 2015-04-23 14:54:27 +00:00
reality
4467e9fd5b food 2015-04-23 14:53:33 +00:00
reality
0384a45bf7 hmm 2015-04-21 23:40:45 +00:00
reality
57a2a12814 fix seconds counter (thanks trapdoor) 2015-04-20 20:16:03 +00:00
reality
f6f6de508b change to new youtube api 2015-04-20 20:10:55 +00:00
Luke Slater
25b026cf26 Merge pull request #646 from EddieCurtis/patch-1
Moved period out of ban/kick reason
2015-04-19 15:49:41 +03:00
Eddie Curtis
91e6ef3612 Moved period out of ban/kick reason 2015-04-19 13:47:57 +01:00
Luke Slater
41c56be7da Merge pull request #642 from amki/rss
Updated RSS README to fix #630
2015-04-19 15:44:51 +03:00
Luke Slater
7376c380e7 Update pages.js 2015-04-14 01:50:37 +03:00
Luke Slater
8da13c35a7 Merge pull request #645 from amki/remind
Timers set through remind are now persistent
2015-04-07 00:39:42 +03:00
amki
fc06811907 Fixed bugs, removed debug output, timers now persistent 2015-04-06 23:38:21 +02:00
amki
063fb36567 still doesn't work 2015-04-06 21:25:33 +02:00
amki
7a138804a8 New iteration 2015-04-06 20:59:40 +02:00
amki
b54e5aa22d Started work on persistent timers 2015-04-06 20:46:21 +02:00
Luke Slater
0fdbc5fe68 Update kick.js 2015-04-06 00:51:06 +03:00
Luke Slater
37d5fca1af Merge pull request #644 from amki/remind
Remind now supports messages with spaces and calls the user reminding you
2015-04-05 20:50:07 +03:00
amki
7ac7817101 Remind now supports messages with spaces and calls the user reminding you 2015-04-05 19:48:47 +02:00
Luke Slater
448309dc37 Merge pull request #643 from amki/remind
Added remind module according to #171
2015-04-05 20:38:56 +03:00
amki
64bffd534e Added remind module with ~remind and ~remindme commands 2015-04-05 19:36:56 +02:00
Luke Slater
6b7ff075e4 Update rss.js
lol
2015-04-05 19:36:56 +02:00
amki
4cc8e26232 Updated RSS README to fix #630 2015-04-05 07:00:32 +02:00
Luke Slater
baaa444f42 Update rss.js
lol
2015-04-04 02:29:48 +03:00
Luke Slater
0da96ea9ff Merge pull request #641 from amki/rss
RSS module: URL shortener is now kinda optional + 2 bonus crashfixes
2015-04-04 02:27:55 +03:00
amki
db0c61b369 URL shortener is now optional, errors are being pasted to console 2015-04-04 01:24:50 +02:00
amki
1756ae84ef Fixed 2 crashes when rss database was empty 2015-04-04 00:28:36 +02:00
reality
65d752484f m is not n 2015-04-02 18:46:42 +00:00
reality
300a6c51a1 m is not n 2015-04-02 18:45:32 +00:00
reality
58ef784a1e m is not n 2015-04-02 18:44:37 +00:00
reality
c873d4888d blah 2015-04-02 18:41:38 +00:00
reality
8bdb11e9e1 remember your imports kids 2015-04-02 18:37:52 +00:00
reality
0536ce1efa tz 2015-04-02 18:35:40 +00:00
Luke Slater
6838e269b2 Update april.js 2015-04-01 00:43:06 +03:00
Luke Slater
db661c1229 Update april.js 2015-04-01 00:42:46 +03:00
reality
a07daddccc push 2015-03-31 18:51:53 +00:00
reality
e393fd6fdb ye 2015-03-31 18:50:44 +00:00
reality
3347b67cea optinal single quote 2015-03-31 18:48:57 +00:00
reality
999b6f44ad an support 2015-03-31 18:41:57 +00:00
reality
135a4b421e oops 2015-03-31 18:36:08 +00:00
reality
b7b57da78a shorter timeout and parse out a 2015-03-31 18:35:19 +00:00
reality
417cc7b5f8 set a small timeout 2015-03-31 18:32:18 +00:00
reality
33923aee8b OMG 2015-03-31 18:29:45 +00:00
reality
febddfcac7 update nban usage 2015-03-30 19:02:06 +00:00
reality
a27f789a37 get a random post from a given subreddit 2015-03-28 12:55:06 +00:00
reality
e57b0107f4 bleh 2015-03-24 19:57:49 +00:00
reality
dc2b61b21d bleh 2015-03-24 19:39:09 +00:00
reality
148a7a6693 interpol quote 2015-03-24 19:36:36 +00:00
reality
0a9a5a95d0 more likely 2015-03-24 19:34:12 +00:00
reality
bcdf86d7ff i am not wise 2015-03-24 19:33:06 +00:00
reality
16e9b2d9bd i am not wise 2015-03-24 19:32:19 +00:00
reality
902cbdec3c idk 2015-03-24 19:30:46 +00:00
reality
ac2f7bbccd fix regex examples 2015-03-24 19:26:41 +00:00
reality
33c50671e6 fix regex examples 2015-03-24 19:23:02 +00:00
reality
4d50f1eff2 random should response whatsit 2015-03-24 19:17:54 +00:00
reality
e892b0518a oops 2015-03-23 16:19:57 +00:00
reality
04cf918f7a oops 2015-03-23 16:17:32 +00:00
reality
17840f5c9c karma colour 2015-03-23 16:14:41 +00:00
reality
a2ea8cd260 extract err 2015-03-15 18:52:33 +00:00
reality
323be545ab extract err 2015-03-15 18:51:54 +00:00
reality
b41d01b4ad extract err 2015-03-15 18:49:32 +00:00
reality
22c9a5e011 extract err 2015-03-15 18:46:58 +00:00
reality
b54774994b in days 2015-03-14 14:00:56 +00:00
reality
028f56c289 in days 2015-03-14 13:59:45 +00:00
reality
bde2e5f231 colour and diff 2015-03-14 13:58:15 +00:00
reality
0dac6bd576 b& for 45 years 2015-03-14 13:50:31 +00:00
reality
ce149998d8 more trix 2015-03-14 13:49:30 +00:00
reality
8070875937 trix 2015-03-14 13:47:31 +00:00
reality
65d1c24adf ban thing 2015-03-14 13:45:15 +00:00
reality
148e4a43db oops 2015-03-14 10:17:19 +00:00
reality
46d5df86c2 yes 2015-03-14 10:16:17 +00:00
reality
36372a511c also glob name 2015-03-14 09:59:16 +00:00
reality
e523e35c73 today i have a serious case of the stupids. fix host var 2015-03-14 09:56:55 +00:00
reality
f3b1d0c4ef i actually am an idiot 2015-03-14 09:55:39 +00:00
reality
b8e3f13816 i actually am an idiot 2015-03-14 09:54:41 +00:00
reality
4aa2c05989 concerning 2015-03-14 09:52:51 +00:00
reality
96dc972637 concerning 2015-03-14 09:51:22 +00:00
reality
6fc93344a8 concerning 2015-03-14 09:49:15 +00:00
reality
89384492d1 concerning 2015-03-14 09:47:59 +00:00
reality
ad2448f2a0 concerning 2015-03-14 09:45:13 +00:00
reality
fb61ce21a5 concerning 2015-03-14 09:43:34 +00:00
reality
c779721d52 concerning 2015-03-14 09:42:13 +00:00
reality
840ed71188 store host in notify 2015-03-14 09:24:51 +00:00
Luke Slater
d83a2cab74 Merge pull request #638 from shymega/urlupd1
Updated d3.js again
2015-03-11 11:15:26 +03:00
reality
6c671b1ba7 oops 2015-03-09 13:35:38 +00:00
shymega
1d55c1ebce Updated d3.js again 2015-02-28 16:54:43 +00:00
reality
2f874686b6 fix security bug in imgur module 2015-02-27 09:49:46 +00:00
reality
3d8700a6d2 fix security bug in imgur module 2015-02-27 09:48:21 +00:00
reality
baeeee64d9 no karma from bot (quote abuse) 2015-02-19 17:14:27 +00:00
reality
cdc8cb0d74 limit length of karma thing 2015-02-19 17:12:25 +00:00
reality
cafe173911 Merge branch 'master' of github.com:reality/dbot 2015-02-18 16:12:54 +00:00
reality
d84b94f050 fixnomissingchan 2015-02-18 16:12:48 +00:00
Luke Slater
dda7beea10 Merge pull request #637 from Kagammor/patch-1
Don't show comma if no city is given
2015-02-17 12:13:06 +03:00
reality
77e67b637b oops 2015-02-17 08:49:16 +00:00
reality
c2573f9043 oops 2015-02-17 08:39:57 +00:00
Kagammor
c7733ee382 Don't show comma if no city is given
The OpenWeatherMap API may return weather for areas unlinked to a city, on which body.name will be undefined. This would previously show up as (for example) "[ , United Kingdom]". This simple ternary will only return a comma if it is also returning a city name.
2015-02-17 01:23:51 +01:00
reality
5aeeb77065 geoip 2015-02-15 18:26:25 +00:00
reality
e5ac647de4 oops 2015-02-13 00:17:38 +00:00
reality
2d1cfdd567 no more : 2015-02-11 22:24:53 +00:00
reality
81520a3aab fix indentation 2015-02-09 16:56:29 +00:00
reality
10c300ff2b only one connection to the database across all modules. master db driver in dbType of main config.json 2015-02-09 16:54:59 +00:00
reality
76a59dee55 #note #quiet warns 2015-02-08 21:10:23 +00:00
reality
6b7b5494f6 return fbman to ri results 2015-02-07 21:54:16 +00:00
reality
2f9133fcee return fbman to ri results 2015-02-07 21:53:27 +00:00
reality
e9e1e61540 ext quotecat 2015-02-06 12:40:15 +00:00
reality
fc81fc38a1 trim space from end of karma 2015-02-05 19:49:38 +00:00
reality
7749b2eb65 nope 2015-02-04 21:30:52 +00:00
reality
e8a18fa72f nope 2015-02-04 21:29:32 +00:00
reality
e831a7cfa5 /i 2015-02-04 18:52:35 +00:00
reality
11c303bcaa karma 2015-02-04 17:32:31 +00:00
reality
e426a306ab karma 2015-02-04 17:13:41 +00:00
reality
7ad9a5c6a0 karma 2015-02-04 17:07:18 +00:00
reality
931d2de4f1 karma 2015-02-04 17:06:00 +00:00
reality
4446825458 karma 2015-02-04 17:05:20 +00:00
reality
5cbb0362c0 karma 2015-02-04 17:04:50 +00:00
reality
6adc93f043 lc 2015-02-04 17:02:39 +00:00
reality
4a198e2f64 more karma 2015-02-04 17:00:47 +00:00
reality
164ec47337 fix thing +_+++++ 2015-02-04 16:38:56 +00:00
reality
d97e4969f9 karma 2015-02-04 16:34:59 +00:00
reality
720740de58 -b on thing 2015-01-30 00:07:45 +00:00
reality
b5bb0af0da idk 2015-01-29 23:59:55 +00:00
reality
1cbb57e519 inbuilt -b 2015-01-29 22:24:45 +00:00
reality
29bdb0e91e quietbans 2015-01-29 22:06:17 +00:00
reality
bf920874b1 derp 2015-01-29 21:47:18 +00:00
Luke Slater
eab1004215 Merge branch 'master' of https://github.com/reality/dbot 2015-01-29 21:42:18 +00:00
Luke Slater
57b6b1b7ee fixes 2015-01-29 21:42:09 +00:00
reality
c290b64285 full redir 2015-01-29 21:41:01 +00:00
reality
79a607ba1b weather country 2015-01-28 07:22:32 +00:00
reality
c87c4312ad weather better 2015-01-28 06:26:14 +00:00
reality
f440ffd405 Merge branch 'master' of github.com:reality/dbot 2015-01-28 06:16:25 +00:00
reality
1d10ed86e6 weather 2015-01-28 06:16:17 +00:00
Luke Slater
357fe04058 Merge pull request #635 from shymega/newinstall
Install script fix -URL's and config.json edit
2015-01-26 22:34:24 +00:00
shymega
501c8e66d9 Updated download URL's, and now edits config.json with $EDITOR variable,
or vim if the variable doesn't exist.
2015-01-24 18:19:40 +00:00
reality
ff342bc932 [Close #634] 2015-01-24 17:34:30 +00:00
reality
a101cfe437 domain err catch 2015-01-19 22:59:26 +00:00
reality
b3d7abbc3b debug 2015-01-19 22:33:28 +00:00
reality
4e4da78e0b debug 2015-01-19 22:31:18 +00:00
reality
c412ad0b23 revert 2015-01-19 22:30:04 +00:00
reality
52a5023ef7 asynx != async 2015-01-19 22:28:53 +00:00
reality
552b87c29e async output next 1s delay 2015-01-19 22:28:18 +00:00
reality
e5b8034e0f order by int not string 2015-01-19 22:25:03 +00:00
reality
3f6153a7cf order by int not string 2015-01-19 22:19:34 +00:00
reality
14f3a09c2c date formatting 2015-01-19 22:16:22 +00:00
reality
84e7b5f06e date formatting 2015-01-19 22:12:05 +00:00
reality
0e47ce22ce sort is one of the few functions afforded to the array object naturally 2015-01-19 22:08:54 +00:00
reality
4709a11fad time 2015-01-19 22:06:33 +00:00
reality
5b067094de reports 2015-01-19 21:57:27 +00:00
reality
439fcf567c ye 2015-01-15 02:46:02 +00:00
reality
97f44141b3 Revert "access"
This reverts commit 333c8de2ad973833163211c1617b9f1505ec61a5.
2015-01-15 02:45:07 +00:00
reality
333c8de2ad access 2015-01-15 02:44:00 +00:00
reality
b1697464d9 sustatus 2015-01-15 02:41:11 +00:00
reality
4ca95aa1aa wordcrimes initial 2015-01-12 04:30:31 +00:00
reality
d3633fc21d new db without resolvchan 2015-01-09 21:25:33 +00:00
reality
5621bad05c currency 2015-01-09 01:43:20 +00:00
reality
d732440e3e yeah yeah 2015-01-09 01:11:47 +00:00
reality
b2188ea036 r2rio 2015-01-09 00:53:14 +00:00
reality
0a6f1bd82a gmaps 2015-01-08 18:06:20 +00:00
reality
d0db3b6a53 yes yes 2015-01-07 21:47:12 +00:00
reality
0d86dc4119 yes yes 2015-01-07 21:45:54 +00:00
reality
7e968d38bb period 2015-01-02 21:40:29 +00:00
reality
2c4aad8a23 whoops 2015-01-02 21:39:45 +00:00
reality
54f4d4979c types 2015-01-02 21:38:37 +00:00
reality
e2190444a8 ye 2014-12-24 00:19:25 +00:00
reality
2b31ce12d9 yeh 2014-12-16 02:50:03 +00:00
reality
36b41156a5 Merge branch 'master' of github.com:reality/dbot 2014-12-03 04:16:45 +00:00
reality
0f73bca593 yeh 2014-12-03 04:14:14 +00:00
Luke Slater
d647b35405 Update words.js 2014-11-30 04:01:14 +00:00
Luke Slater
047ed35f83 Update words.js 2014-11-30 03:59:19 +00:00
Luke Slater
72afc1ca0a Update words.js 2014-11-30 03:58:22 +00:00
Luke Slater
ba0d99b60d Update words.js 2014-11-30 03:57:41 +00:00
Luke Slater
f8cfc04ab2 Update words.js 2014-11-30 03:56:25 +00:00
reality
2d8a1da470 like 2014-11-29 05:10:34 +00:00
reality
0cf845f099 like 2014-11-29 05:08:26 +00:00
reality
f0398fe8fc like 2014-11-29 05:03:25 +00:00
reality
42b8b1f6ba like 2014-11-29 05:02:11 +00:00
reality
1a144c6459 etymology 2014-11-28 14:43:16 +00:00
reality
f7fbbc7bd0 and the strings... 2014-11-09 14:05:32 +00:00
reality
1b2f01af94 tell user about warn 2014-11-09 14:04:04 +00:00
Luke Slater
27c150f877 some ufixes 2014-11-09 13:59:00 +00:00
reality
bdbd5ee8e4 Words updates 2014-10-31 17:02:17 +00:00
reality
1b67b04630 like 2014-10-31 04:27:08 +00:00
reality
0c8d0c8143 like 2014-10-31 04:21:33 +00:00
reality
a45c578037 like 2014-10-31 04:19:05 +00:00
reality
d91c014b53 fix web import and bootstrap dl #629 2014-10-27 17:07:04 +00:00
Luke Slater
81b352c306 Merge pull request #627 from reality/betterusers
merge new users into master
2014-10-27 16:41:40 +00:00
Luke Slater
80d9af6279 Merge pull request #624 from amki/rss
Don't crash when rss feed item does not contain pubdate
2014-09-19 14:45:18 +01:00
amki
0a914c820a Don't crash when rss feed item does not contain pubdate 2014-09-18 21:07:29 +00:00
Luke Slater
a2a025ad55 Update install 2014-09-11 20:21:16 +01:00
382 changed files with 22118 additions and 1333 deletions

View File

@ -1,3 +1,47 @@
### dbot - revived
The motivation for this fork came from trying to make the trusty bot of our IRC network work with new infrastructure, until all features made their way into its successor.
Note that this project is not production worthy - it contains a lot of hardcoded debug lines and untidy code. Whether it will see prettification anytime soon, I cannot tell.
So far, the following issues were tackled successfully:
- NickServ identification
- NickServ user retrieval
- Quote shortcuts (~foo)
- Channel statistics (cstats)
This makes the bot usable on an IRC network powered by the Ergo IRCd, which is dbot was not happy communicate with at all when I first tried to run it.
The following issues are known, but are assigned low priority:
- No PASS/SASL authentication - it is hackable with a PASS line right after the USER command, however that prevents some parts of the bot from initializing. To use NickServ authentication, allowing the bot to start properly, the strict username checking feature in Ergo needs to be disabled
- Lots of ENOENT errors on startup
- Channel mode detection and assignment
- Some sites in the webinterface don't load / show errors
The following issues are known, and await investigation:
- Upon issuing some administrative commands, apparently a sort of "queue flush" gets trigered, causing dozens of unban events to make the bot unresponsive for a while
The following issues are resolved, but are not available in this repository, due to files not being accessible:
- WHO parsing for user management commands
- nban/nunban, with newly added ipban
The directory modules-stock/ is the stock modules directory, kept for safekeeping.
The directory modules/ contains changed modules.
The jsbot/ directory is not populated in the stock repository, however its contents are included here, as a few essential changes have been implemented there as well.
Tested with node v10.
Note: TripSit IRC related modules are to be found in a seperate repository.
---
### Original README.md
# DBot IRC Bot
## Introduction

View File

@ -19,6 +19,7 @@
"language": "en",
"debugMode": false,
"debugLevel": 1,
"dbType": "redis",
"redisPort": 6379,
"timezone": "Europe/London",
"repoRoot": "https://github.com/reality/depressionbot/",

View File

@ -8,30 +8,26 @@ var databank = require('databank'),
*/
var DatabaseDriver = function(config) {
this.config = config;
this.databanks = {};
this.databank = null;
};
/**
* Connect to or create a new DataBank
*/
DatabaseDriver.prototype.createDB = function(name, driver, schema, callback) {
if(!_.has(this.databanks, name)) {
var params = { 'schema': schema };
var params = { 'schema': schema };
if(driver == 'redis' && _.has(this.config, 'redisPort')) params.port = this.config.redisPort;
if(driver == 'disk') params.dir = 'db';
if(driver == 'redis' && _.has(this.config, 'redisPort')) params.port = this.config.redisPort;
if(driver == 'disk') params.dir = 'db';
this.databanks[name] = Databank.get(driver, params);
this.databanks[name].connect({}, function(err) {
if(err) {
console.log('Didn\'t manage to connect to the data source - ' + err);
} else {
callback(this.databanks[name]);
}
}.bind(this));
} else {
callback(this.databanks[name]);
}
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;

26
install
View File

@ -3,29 +3,28 @@ cat LICENCE
git submodule init
git submodule update
if [ ! -e /usr/bin/node ] && [ ! -e /usr/local/bin/node ];
then
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
if [ ! -e /usr/bin/npm ] && [ ! -e /usr/local/bin/npm ];
then
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 feedparser node-units tvdb crypto-js 500px process async wordnik node-uuid underscore request sandbox express moment-timezone moment jade databank databank-redis ent passport passport-local password-hash connect-flash
npm install googlemaps humanize feedparser node-units tvdb method-override 500px process async wordnik node-uuid underscore request request-promise-native sandbox express moment-timezone moment jade databank databank-redis ent passport passport-local password-hash connect-flash
cd public/
#wget http://twitter.github.com/bootstrap/assets/bootstrap.zip
wget https://github.com/twbs/bootstrap/releases/download/v3.2.0/bootstrap-3.2.0-dist.zip
unzip bootstrap-3.2.0-dist.zip
rm bootstrap.zip
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 http://d3js.org/d3.v3.zip
wget https://github.com/mbostock/d3/releases/download/v3.4.11/d3.zip
wget https://github.com/mbostock/d3/releases/download/v3.5.5/d3.zip
unzip d3.zip
rm d3.zip
@ -35,10 +34,11 @@ if [ ! -f config.json ];
then
echo 'Creating configuration file...'
cp config.json.sample config.json
vim 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 depressionbot now? [y/N]"
read -p "Setup complete. Run dbot now? [y/N]"
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
echo 'Okay. To run the bot, use "node run.js"'

1
jsbot

@ -1 +0,0 @@
Subproject commit 606d0cdcddfda9d7328cfa0b65bb4a3079d77d9e

40
jsbot/README.md Normal file
View File

@ -0,0 +1,40 @@
## JSBot
JSBot is an IRC bot library written in Node JS.
With features like multiple server support and being 'pretty good, I guess,'
JSBot is designed to be the IRC bot library of the future! For an example of a
large project which uses JSBot, take a look at
[DepressionBot](http://github.com/reality/depressionbot/ "DepressionBot").
To get started with JSBot, take a look at the 'run.js' example provided with the
code, then head on over to the
[online documentation](https://github.com/reality/jsbot/wiki/Documentation "JSBot Docs").
## ChangeLog
### 0.3 I guess
* Fixed an edge case with the IRC line tokenisation /potentially/ causing events to be parsed twice
* Isolated all core channel/nick list logic in JOIN/PART/KICK/QUIT/NICK handlers
* Removed useless timeout in 004 handler, switched it to handle 001 instead
* Removed a duplicate send() call for IDENTIFY
* Semantically reorganised source code
* Various other improvements
speeddefrost <3
### 0.2
* Multiple server support.
* Functionality for certain users to ignore listeners with certain tags.
* Better 'event' object passed to listeners.
* Ability to 'reply' to events.
### 0.1
* It connects to a server
* Listeners
* Ping/Pong
* Some of the other functionality you'd expect, like, what do you want from me?

572
jsbot/jsbot.js Normal file
View File

@ -0,0 +1,572 @@
var _ = require('underscore')._,
net = require('net'),
async = require('async'),
tls = require('tls'),
Tokenizer = require('./tokenizer');
var JSBot = function(nick) {
this.nick = nick;
this.connections = {};
this.ignores = {};
this.preEmitHooks = [];
this.events = {
'JOIN': [],
'PART': [],
'QUIT': [],
'NICK': [],
'PRIVMSG': [],
'MODE': [],
'KICK': []
};
this.addDefaultListeners();
};
// connections
var Connection = function(name, instance, host, port, owner, onReady, nickserv, password, tlsOptions) {
this.name = name;
this.instance = instance;
this.host = host;
this.port = port;
this.owner = owner;
this.onReady = onReady;
this.nickserv = nickserv;
this.password = password;
this.tlsOptions = tlsOptions;
this.channels = {};
this.commands = {};
this.encoding = 'utf8';
this.netBuffer = '';
this.conn = null;
this.lastSent = Date.now();
};
Connection.prototype.connect = function() {
if((typeof this.port == 'string' || this.port instanceof String) && this.port.substring(0, 1) == '+') {
this.conn = tls.connect(parseInt(this.port.substring(1)), this.host, this.tlsOptions);
} else {
this.conn = net.createConnection(this.port, this.host);
}
console.log('trying to connect' + this.port + ' ' + this.host);
this.conn.setTimeout(60 * 60 * 1000);
this.conn.setEncoding(this.encoding);
this.conn.setKeepAlive(enable=true, 10000);
connectListener = function() {
this.send('NICK', this.instance.nick);
//this.send('USER', this.instance.nick, '0', '*', this.instance.nick);
this.send('PASS ' + this.instance.nick + ':' + this.password);
this.send('USER', this.instance.nick + ' ' + this.instance.nick + ' ' + this.instance.nick, ' ', this.instance.nick);
}.bind(this);
this.conn.addListener('connect', connectListener.bind(this));
this.conn.addListener('secureConnect', connectListener.bind(this));
this.conn.addListener('error', function(err) {
console.log('hi')
console.log(err);
console.error(err);
});
this.conn.addListener('data', function(chunk) {
this.netBuffer += chunk;
console.log(chunk)
var t = new Tokenizer(this.netBuffer);
while(true) {
var line = t.tokenize('\r\n');
if(line == null) {
this.netBuffer = t.tokenize(null);
break;
}
this.instance.parse(this, line);
}
}.bind(this));
};
Connection.prototype.send = function() {
var message = [].splice.call(arguments, 0).join(' ');
//if(Date.now() > this.lastSent + 500) {
message += '\r\n';
this.conn.write(message, this.encoding);
this.lastSent = Date.now();
//} else {
/* setImmediate(function() {
this.send(message);
}.bind(this));
}*/
};
Connection.prototype.pong = function(message) {
this.send('PONG', ':' ); //+ message.split(':')[1]);
};
Connection.prototype.join = function(channel) {
this.send('JOIN', channel);
};
Connection.prototype.part = function(channel) {
this.send('PART', channel);
};
JSBot.prototype.addConnection = function(name, host, port, owner, onReady, nickserv, password, tlsOptions) {
tlsOptions = tlsOptions || {};
tlsOptions = _.defaults(tlsOptions, {rejectUnauthorized: false});
this.connections[name] = new Connection(name, this, host, port, owner, onReady,
nickserv, password, tlsOptions);
};
JSBot.prototype.connect = function(name) {
var conn = this.connections[name];
this.addListener('001', 'onReady', function(event) {
conn.instance.say(conn.name, conn.nickserv, 'IDENTIFY ' + this.nick + " " + conn.password);
setTimeout(function() {
if(conn.onReady != null)
conn.onReady(event);
}, 5000);
});
conn.connect();
};
JSBot.prototype.connectAll = function() {
_.each(this.connections, function(connection, name) {
this.connect(name);
}, this);
};
// event parsing and processing
JSBot.prototype.parse = function(connection, input) {
var event = new Event(this),
t = new Tokenizer(input);
event.server = connection.name;
event.allChannels = this.connections[event.server].channels;
if(input[0] == ':') {
// consume to next whitespace, strip leading ':'
var prefix = t.tokenize(' '),
maskMatch = prefix.match(/:(.+)!(.+)@(.+)/);
if(maskMatch && maskMatch.length == 4) {
event.user = maskMatch[1];
event.ident = maskMatch[2];
event.host = maskMatch[3];
}
else {
event.host = prefix.substring(1);
}
}
/* parameter string extraction */
// try consuming to beginning of a message
var paramsStr = t.tokenize(' :');
if(!paramsStr) {
// if that fails (no message), fall back to line ending
paramsStr = t.tokenize(null);
} else {
// first attempt succeeded, extract message
event.message = t.tokenize(null);
}
// split the parameter string
event.args = paramsStr.split(' ');
// use first item as action, remove from list
event.action = event.args.shift();
// -- Common Event Variables --
// All channel/nick/target parameters in server-to-client events are accounted for here.
// Others need to be handled manually via event.params.
if (/^\d+$/.test(event.action)) {
var rsp = parseInt(event.action),
nickRsps = [ 301, 311, 312, 313, 317, 318, 319, 314,
369, 322, 324, 338, 401, 406, 432, 433, 436 ],
channelRsps = [ 322, 324, 331, 332, 346, 347, 348, 349,
366, 367, 368, 403, 404, 405, 467, 471,
473, 474, 475, 476, 477, 478, 482 ],
channelNickRsps = [ 325, 341 ],
targetRsps = [ 407, 437 ];
if(nickRsps.indexOf(rsp) != -1) {
event.user = event.args[0];
}
else if(channelRsps.indexOf(rsp) != -1) {
event.channel = event.args[0];
}
else if(channelNickRsps.indexOf(rsp) != -1) {
event.channel = event.args[0];
event.user = event.args[1];
}
else if(targetRsps.indexOf(rsp) != -1) {
if ('&#!+.~'.indexOf(event.args[0][0]) != -1) {
event.channel = event.args[0];
} else {
event.user = event.args[0];
}
}
else if(rsp == 352) {
event.channel = event.args[0];
event.user = event.args[4];
}
else if(rsp == 353) {
event.channel = event.args[2];
}
else if(rsp == 441) {
event.user = event.args[0];
event.channel = event.args[1];
}
}
else {
if(event.action == 'PRIVMSG') {
if('&#!+.~'.indexOf(event.args[0][0]) != -1) {
event.channel = event.args[0];
}
}
else if(event.action == 'JOIN' ||
event.action == 'PART' ||
event.action == 'TOPIC')
{
event.channel = event.args[0];
}
else if(event.action == 'KICK') {
event.channel = event.args[0];
event.targetUser = event.args[1];
}
else if(event.action == 'NICK') {
event.newNick = event.args[1];
event.multiChannel = true;
}
else if(event.action == 'MODE') {
event.channel = event.args[0];
event.modeChanges = event.args[1];
if(event.args.length > 2) {
event.targetUsers = event.args.slice(2);
}
}
else if(event.action == 'QUIT') {
event.multiChannel = true;
}
if(event.multiChannel) {
// populate a list of channels this event applies to
event.channels = [];
for(var ch in event.allChannels) {
for(var nick in event.allChannels[ch].nicks) {
if(nick == event.user) {
event.channels.push(event.allChannels[ch]);
}
}
}
}
else if(event.channel && event.channel in event.allChannels) {
// replace the channel name with it's coresponding object
event.channel = event.allChannels[event.channel];
} else {
event.channel = {
'name': event.user,
'nicks': {},
'toString': function() {
return this.name;
}
}
}
}
// Run any pre-emit hooks
async.eachSeries(this.preEmitHooks, function(hook, callback) {
hook(event, callback);
}, function(err) {
this.emit(event);
}.bind(this));
// for handlers
if(event.message) {
event.params = event.message.split(' ');
} else {
event.params = [];
}
};
JSBot.prototype.addPreEmitHook = function(func) {
this.preEmitHooks.push(func);
};
JSBot.prototype.clearHooks = function() {
this.preEmitHooks = [];
};
JSBot.prototype.emit = function(event) {
if(event.action in this.events) {
_.each(this.events[event.action], function(listener) {
var eventFunc = listener.listener;
var channel = false;
if(event.channel) {
channel = event.channel.name;
}
if(_.isFunction(eventFunc) && this.ignores &&
(_.has(this.ignores, event.user) && _.include(this.ignores[event.user], listener.tag)) == false &&
(_.has(this.ignores, channel) && _.include(this.ignores[channel], listener.tag)) == false) {
try {
eventFunc.call(this, event);
} catch(err) {
console.log('ERROR: ' + eventFunc + '\n' + err);
console.log(err.stack.split('\n')[1].trim());
}
}
}.bind(this));
}
};
// client functionality
JSBot.prototype.say = function(server, channel, msg) {
var event = new Event(this);
event.server = server;
event.channel = channel;
event.msg = msg;
event.reply(msg);
};
JSBot.prototype.reply = function(event, msg) {
this.connections[event.server].send('PRIVMSG ' + event.channel + ' ' + ':' + msg);
};
JSBot.prototype.act = function(event, msg) {
this.connections[event.server].send('PRIVMSG', event.channel, '\001ACTION ' + msg + '\001');
}
JSBot.prototype.replyNotice = function(event, msg) {
this.connections[event.server].send('NOTICE', event.user , ':' + msg);
}
JSBot.prototype.join = function(event, channel) {
this.connections[event.server].join(channel);
};
JSBot.prototype.part = function(event, channel) {
this.connections[event.server].part(channel);
};
JSBot.prototype.mode = function(event, channel, msg) {
this.connections[event.server].send('MODE', channel, msg);
}
JSBot.prototype.nick = function(event, nick) {
this.connections[event.server].send('NICK', nick);
}
// listeners
JSBot.prototype.addListener = function(index, tag, func) {
if(!(index instanceof Array)) {
index = [index];
}
var listener = {
'listener': func,
'tag': tag
};
_.each(index, function(type) {
if(!_.has(this.events, type)) {
this.events[type] = [];
}
this.events[type].push(listener);
}, this);
};
JSBot.prototype.removeListeners = function() {
this.events = {
'JOIN': [],
'PART': [],
'QUIT': [],
'NICK': [],
'PRIVMSG': [],
'MODE': [],
'KICK': []
};
this.addDefaultListeners();
};
JSBot.prototype.addDefaultListeners = function() {
// PING
this.addListener('PING', 'pong', function(event) {
this.connections[event.server].pong(event.message);
}.bind(this));
// JOIN
this.addListener('JOIN', 'joinname', function(event) {
if(event.user != this.nick) {
if(!_.has(this.connections[event.server].channels[event.channel].nicks, event.user)) {
this.connections[event.server].channels[event.channel].nicks[event.user] = {
'name': event.user,
'op': false,
'voice': false,
'toString': function() {
return this.name;
}
};
}
event.user = this.connections[event.server].channels[event.channel].nicks[event.user];
}
}.bind(this));
// PART
this.addListener('PART', 'partname', function(event) {
if(event.user == this.nick)
delete this.connections[event.server].channels[event.channel];
else
delete event.channel.nicks[event.user];
}.bind(this));
// KICK
this.addListener('KICK', 'kickname', function(event) {
if(event.targetUser == this.nick)
delete this.connections[event.server].channels[event.channel];
else
delete event.channel.nicks[event.user];
}.bind(this));
// QUIT
this.addListener('QUIT', 'quitname', function(event) {
_.each(event.allChannels, function(channel) {
delete event.allChannels[channel].nicks[event.user];
});
});
// NICK
this.addListener('NICK', 'nickchan', function(event) {
if(event.user == this.nick) {
this.nick = event.message;
} else {
_.each(event.allChannels, function(channel) {
if(_.has(channel, 'nicks')) {
if(event.user in channel.nicks) {
channel.nicks[event.message] = channel.nicks[event.user];
channel.nicks[event.message].name = event.message;
delete channel.nicks[event.user];
}
}
});
}
}.bind(this));
// MODE
this.addListener('MODE', 'modop', function(event) {
if(!event.modeChanges || !event.targetUsers)
return;
var changeSets = event.modeChanges.match(/[+-][ov]+/);
if(!changeSets)
return;
for(var i=0; i < changeSets.length && i < event.targetUsers.length; ++i) {
var chanUser = event.channel.nicks[event.targetUsers[i]],
prefix = changeSets[i].match(/[+-]/)[0],
flags = changeSets[i].match(/[ov]+/)[0],
value = prefix == '+';
if(!chanUser) {
event.channel.nicks[event.targetUsers[i]] = {
'name': event.targetUsers[i],
'op': false,
'voice': false,
'toString': function() {
return this.name;
}
};
chanUser = event.channel.nicks[event.targetUsers[i]];
}
for(var f=0; f < flags.length; ++f) {
if(flags[f] == 'o') {
chanUser.op = value;
}
else if(flags[f] == 'v')
chanUser.voice = value;
}
}
});
// 353 replies
this.addListener('353', 'names', function(event) {
if(!_.has(this.connections[event.server].channels, event.channel)) {
this.connections[event.server].channels[event.channel] = {
'name': event.channel,
'nicks': {},
'toString': function() {
return this.name;
}
};
}
event.channel = this.connections[event.server].channels[event.channel];
for(var i=0; i < event.params.length; ++i) {
var hasFlag = '~&@%+'.indexOf(event.params[i][0]) != -1,
name = hasFlag ? event.params[i].slice(1) : event.params[i];
event.channel.nicks[name] = {
'name': name,
'op': hasFlag && event.params[i][0] == '@',
'voice': hasFlag && event.params[i][0] == '+',
'toString': function() {
return this.name;
}
};
}
});
this.addListener('PRIVMSG', 'ping', function(event) {
//if(event.message.match(/\x01PING .+\x01/) != null)
event.replyNotice(event.message);
});
};
// ignore functionality
JSBot.prototype.ignoreTag = function(item, tag) {
if(_.has(this.ignores, item) == false)
this.ignores[item] = [];
this.ignores[item].push(tag);
}
JSBot.prototype.clearIgnores = function() {
this.ignores = {};
}
JSBot.prototype.removeIgnore = function(item, tag) {
if(_.has(this.ignores, item) && _.include(this.ignores[item], tag))
this.ignores[item].slice(this.ignores[item].indexOf(tag), 1);
}
// events
var Event = function(instance) {
this.instance = instance;
};
Event.prototype.reply = function(msg) {
this.instance.reply(this, msg);
};
Event.prototype.replyNotice = function(msg) {
this.instance.replyNotice(this, msg);
}
// export that shit
exports.createJSBot = function(name) {
return new JSBot(name);
};

29
jsbot/package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "jsbot",
"version": "0.3.0",
"description": "It's a JS IRC library.",
"main": "jsbot.js",
"dependencies": {
"async": "~0.2.9",
"underscore": "~1.4.4"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/reality/jsbot.git"
},
"keywords": [
"irc",
"ssl",
"reality",
"dbot"
],
"author": "Luke Slater (reality) <tinmachin3@gmail.com>",
"license": "GPL",
"bugs": {
"url": "https://github.com/reality/jsbot/issues"
}
}

28
jsbot/run.js Normal file
View File

@ -0,0 +1,28 @@
jsbot = require('./jsbot');
var instance = jsbot.createJSBot('jsbottest');
instance.addConnection('aberwiki', 'irc.aberwiki.org', 6667, 'reality', function(event) {
instance.join(event, '#realitest');
}.bind(this));
instance.addConnection('freenode', 'irc.freenode.net', 6667, 'reality', function(event) {
instance.join(event, '#realitest');
}.bind(this));
instance.addConnection('darchoods', 'irc.darchoods.net', '+6697', 'reality', function(event) {
instance.join(event, '#realitest');
}.bind(this));
instance.addPreEmitHook(function(event, callback) {
if(event.user) event.user = event.user.toLowerCase();
callback(false);
});
instance.addListener('JOIN', 'join', function(event) {
event.reply('I love ' + event.user);
});
instance.ignoreTag('jsbottest', 'join');
instance.connectAll();

38
jsbot/tokenizer.js Normal file
View File

@ -0,0 +1,38 @@
/*
* String tokenizer thing.
* by speeddefrost <origin206@protonmail.ch>
*/
function Tokenizer(str) {
this.str = str;
this.pos = 0;
}
Tokenizer.prototype.tokenize = function(delim) {
if(this.pos == -1)
return null;
if(!delim) {
var leftover = this.str.slice(this.pos);
this.pos = -1;
return leftover;
}
var i = this.pos,
j = this.pos + delim.length
z = this.str.length - delim.length;
while(i <= z) {
if(this.str.substring(i,j) == delim) {
var token = this.str.substring(this.pos, i);
this.pos = j;
return token;
}
++i; ++j;
}
return null;
}
module.exports = Tokenizer;

View 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

View File

@ -0,0 +1,4 @@
{
"foo": true,
"foo": "bar"
}

39
modules-stock/SAMPLE/foo Normal file
View 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
};

View File

@ -0,0 +1,8 @@
{
"foo": {
"en": "{output} bar"
},
"foo2": {
"en": "Something went wrong :( Example:'~foo bar'"
}
}

View File

@ -0,0 +1,3 @@
{
"~foo": "~foo [bar]"
}

View File

@ -0,0 +1,72 @@
## Admin
Administrator functionality.
### Description
Various administration functionality such as banning users, hot-reloading the
code and ordering him to talk. Note that commands added here are handled with
their own listener, rather than being part of the command logic which is handled
by the Command module. Functionality in this module can be slightly unsafe as
not everything is thoroughly sanity checked.
### Commands
#### join [#channel]
Join the given channel.
#### part [#channel]
Leave the given channel.
#### opme [#channel]
Gives the caller ops in a given channel if possible. If called without a
channel, it will attempt to give the caller ops in the current channel.
#### greload
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
bot or interrupt the connection to the server.
#### say [#channel] [message]
Have DBot post the given message in the given channel (uses the server from
which you are sending the message). You may replace channel with '@' to have him
post the message in the current channel. Channel may also be replaced with a
nick on the server.
#### load [module]
Load a new module. This works by adding a module name to the roster and then
triggering a reload of all modules, at which point the new module is actually
loaded by the standard DBot process.
#### unload [module]
Unload a currently loaded module. This removes the module, and then triggers a
reload of all modules.
#### 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_.
#### 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.

View File

@ -0,0 +1,64 @@
/**
* Module Name: Admin
* Description: Set of commands which only one who is a DepressionBot
* 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) {
return new admin(dbot);
};

View File

@ -0,0 +1,311 @@
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 commands = {
// Join a channel
'~join': function(event) {
var channel = event.params[1];
if(_.has(event.allChannels, channel)) {
event.reply(dbot.t('already_in_channel', {'channel': channel}));
} else {
dbot.instance.join(event, channel);
event.reply(dbot.t('join', {'channel': channel}));
}
},
// Leave a channel
'~part': function(event) {
var channel = event.params[1];
if(!_.has(event.allChannels, channel)) {
event.reply(dbot.t('not_in_channel', {'channel': channel}));
} else {
event.instance.part(event, channel);
event.reply(dbot.t('part', {'channel': channel}));
}
},
// Op admin caller in given channel
'~opme': function(event) {
var channel = event.params[1];
// If given channel isn't valid just op in current one.
if(!_.has(event.allChannels, channel)) {
channel = event.channel.name;
}
dbot.instance.mode(event, channel, ' +o ' + event.user);
},
// Do a git pull and reload
'~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);
commands['~version'](event);
}.bind(this));
}.bind(this));
},
// Display commit information for part of dbot
'~version': function(event){
var cmd = "git log --pretty=format:'%h (%s): %ar' -n 1 -- ";
if(event.params[1]){
var input = event.params[1].trim();
if(_.has(dbot.modules, input.split("/")[0])){
cmd += "modules/"+input;
}
else{
cmd += input;
}
}
exec(cmd, function(error, stdout, stderr){
if(stdout.length > 0){
event.reply(stdout);
}
else{
event.reply(dbot.t("no_version"));
}
}.bind(this));
},
'~status': function(event) {
var moduleName = event.params[1];
if(_.has(dbot.status, moduleName)) {
var status = dbot.status[moduleName];
if(status === true) {
event.reply(dbot.t('status_good', {
'module': moduleName,
'reason': status
}));
} else {
event.reply(dbot.t('status_bad', {
'module': moduleName,
'reason': status
}));
}
} else {
event.reply(dbot.t("status_unloaded"));
}
},
// Reload DB, translations and modules.
'~reload': function(event) {
dbot.db = JSON.parse(fs.readFileSync('db.json', 'utf-8'));
dbot.reloadModules();
process.nextTick(function() {
event.reply(dbot.t('reload'));
});
},
// Say something in a channel
'~say': function(event) {
var channel = event.params[1];
if(event.params[1] === "@") {
channel = event.channel.name;
}
var message = event.params.slice(2).join(' ');
dbot.say(event.server, channel, message);
},
// Load new module
'~load': function(event) {
var moduleName = event.params[1];
if(!_.include(dbot.config.moduleNames, moduleName)) {
dbot.customConfig.moduleNames.push(moduleName);
this.internalAPI.saveConfig();
dbot.reloadModules();
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 }));
}
}
},
// Unload a loaded module
'~unload': function(event) {
var moduleNames = dbot.config.moduleNames;
var moduleName = event.params[1];
if(_.include(moduleNames, moduleName)) {
var moduleDir = '../' + 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();
process.nextTick(function() {
event.reply(dbot.t('unload_module', { 'moduleName': moduleName }));
});
} else {
event.reply(dbot.t('unload_error', { 'moduleName': moduleName }));
}
},
/*** Config options ***/
'~setconfig': function(event) {
var configPath = event.input[1],
newOption = event.input[2];
event.reply(event.input[1]);
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 integer type is config item integer
if(_.isNumber(config)) {
newOption = parseInt(newOption);
}
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(dbot.t("config_lock"));
}
},
'~pushconfig': function(event) {
var configPath = event.input[1],
newOption = event.input[2];
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 configPath = event.params[1];
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);
});
}
};
_.each(commands, function(command) {
command.access = 'admin';
});
commands['~showconfig'].access = 'moderator';
commands['~join'].access = 'moderator';
commands['~part'].access = 'moderator';
commands['~opme'].access = 'moderator';
commands['~say'].access = 'moderator';
commands['~pushconfig'].regex = [/pushconfig ([^ ]+) ([^ ]+)/, 3];
commands['~setconfig'].regex = [/setconfig ([^ ]+) ([^ ]+)/, 3];
return commands;
};
exports.fetch = function(dbot) {
return commands(dbot);
}

View File

@ -0,0 +1,5 @@
{
"ignorable": false,
"dbType": "redis",
"dependencies": [ "command" ]
}

View File

@ -0,0 +1,230 @@
{
"join": {
"en": "Joined {channel}",
"es" : "Entrado en {channel}",
"na'vi": "fpxäkìm {channel}(nemfa)",
"cy": "Wedi ymuno {channel}",
"nl": "{channel} binnengekomen",
"de": "{channel} beigetreten",
"fr": "{channel} rejoint",
"it": "Aderito a {channel}"
},
"part": {
"en": "Left {channel}",
"es" : "Abandonada {channel}",
"na'vi": "Hum {channel}",
"cy": "Wedi gadael {channel}",
"nl": "{channel} verlaten",
"de": "{channel} verlassen",
"fr": "{channel} quitté",
"it": "uscito da {channel}"
},
"gpull": {
"en": "Git pulled that shit.",
"es": "Hecho git pull en esta mierda.",
"na'vi": "Gìtìl fì'uti stamarsìm.",
"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": {
"en": "Reloaded that shit.",
"es": "Recargado esta mierda.",
"na'vi": "Oel fìuti stìyeftxaw.",
"cy": "Ail-lwytho'r cach na",
"nl": "Die zooi opnieuw geladen.",
"de": "Neu geladen",
"fr": "Bordel rechargé",
"it": "Ricaricato questa puttanata"
},
"load_module": {
"en": "Loaded new module: {moduleName}",
"es": "Cargado módulo nuevo: {moduleName}",
"na'vi": "Oel {moduleName}it amip stìyeftxaw.",
"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": {
"en": "Turned off module: {moduleName}",
"es": "Descargado módulo: {moduleName}",
"na'vi": "Oel {moduleName} tswìya'.",
"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": {
"en": "{moduleName} isn't loaded. Idiot.",
"es": "{moduleName} no está cargado. Idiota.",
"na'vi": "Oel {moduleName}it omum. Nga skxawng lu.",
"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": {
"en": "{user} banned from {command}",
"es": "{user} está prohibido de usar {command}",
"na'vi": "{command}ìri {user} ke tung.",
"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": {
"en": "{user} unbanned from {command}",
"es": "{user} no está prohibido de user {command}",
"na'vi": "{command}ìri {user} tung set.",
"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": {
"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.",
"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": {
"en": "Locked quote category: {category}",
"es": "Cerrado la categoría: {category}",
"na'vi": "{category}ìri oel 'upxareti fmoli",
"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": {
"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": {
"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": {
"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": {
"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}"
}
}

View 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);
};

View File

@ -0,0 +1,10 @@
{
"announces": {
"aberwiki": {
"#test": {
"category": "test",
"distance": 1
}
}
}
}

View 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-stock/api/api.js Normal file
View 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);
};

View 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);
};

View 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);
};

View 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"
}
}

View File

@ -0,0 +1,5 @@
{
"akill": {
"en": "{host} has been AKilled for {duration} due to \"{reason}\""
}
}

View File

@ -0,0 +1,78 @@
## Command
Handles the command execution logic for DBot.
### Description
Command flow:
1. Does the input match a command key in the loaded commands?
* If command not found and quotes is loaded, attempt to print quote of given
command name
2. Is the user banned from running the given command?
3. Is the user ignoring the command?
4. Is the channel ignoring the command?
5. Does the use have the access level to run the command?
6. Is the command set as disabled?
7. Apply regex to the command, pass into event object.
* If regex does not apply, show usage info.
8. Run the command.
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]
Show usage information for a given command.
#### ~help [command|module]
Link module help for a module given either the module name or the name of a
command belonging to a module.
### API
#### isBanned(user, command)
Return whether a user is currently banned from a given commands.
#### hasAccess(user, command)
Return whether a user has the access level (moderator, admin) to run a given
command.
#### isIgnoring(user, command)
Return whether a user is currently marked as ignoring a given command.
#### addHook(command, callback)
This API function allows you to hook functions into DBot commands. For example,
you may add a hook to post on Identica when a new quote is added to the database
with the ~qadd command. As a less useful example, here is how you might add a
hook to log to the console every time someone uses the reload command:
dbot.api.command.addHook('reload', function() {
console.log('Reload run!');
});
Hook arguments are populated by the return values of the functions they are
hooked into, and command hooks are not run if the command explicitly returns
'false.' For example, the ~qadd command returns *[ key, quote ]*, and the hook
function will be called with these variables given in the order they were
returned, so you would retrieve the key and the quote from a hook to ~qadd like
this:
dbot.api.command.addHook('~qadd', function(key, quote) { ...
The best place to add hooks to commands is in the 'onLoad' function of your
module, as this ensures it will be run while all other modules are loaded. If
the target command does not exist (for example if its module was not loaded),
the hook will not be added and no errors will be thrown.

View File

@ -0,0 +1,73 @@
var _ = require('underscore')._;
var api = function(dbot) {
return {
/**
* Does the user have the correct access level to use the command?
*/
'hasAccess': function(event, command, callback) {
var accessNeeded = dbot.commands[command].access,
allowedNicks,
user = event.rUser;
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);
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);
}
}
},
/**
* Apply Regex to event message, store result. Return false if it doesn't
* apply.
*/
'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;
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;
}
};
};
exports.fetch = function(dbot) {
return api(dbot);
};

View File

@ -0,0 +1,163 @@
/**
* Module Name: Command
* Description: An essential module which maps PRIVMSG input to an appropriate
* command and then runs that command, given the user isn't banned from or
* ignoring that command.
*/
var _ = require('underscore')._,
cDomain = require('domain').create();
var command = function(dbot) {
/**
* Run the appropriate command given the input.
*/
this.listener = function(event) {
var commandName = event.params[0];
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}));
}
} else if(!hasAccess) {
if(this.config.accessOutput) {
event.reply(dbot.t('access_denied', { 'user': event.user }));
}
} 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 {
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) {
return new command(dbot);
};

View File

@ -0,0 +1,70 @@
var _ = require('underscore')._,
request = require('request');
var commands = function(dbot) {
var commands = {
'usage': function(event) {
var commandName = event.params[1];
if(_.has(dbot.usage, commandName)) {
event.reply(dbot.t('usage', {
'command': commandName,
'usage': dbot.usage[commandName]
}));
} else {
event.reply(dbot.t('no_usage_info', {
'command': commandName
}));
}
},
'~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 {
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) {
return commands(dbot);
};

View File

@ -0,0 +1,9 @@
{
"ignorable": false,
"dependencies": [ "event", "ignore", "users" ],
"useNickserv": false,
"accessOutput": false,
"banOutput": false,
"passiveMode": false,
"commandPrefix": "~"
}

View File

@ -0,0 +1,78 @@
{
"command_ban": {
"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.",
"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": {
"en": "Invalid syntax. Initiate incineration.",
"es": "Sintaxis no válida. Iniciar incineración.",
"na'vi": "Ngeyä pamrel keyawr lu. Nga skxawng lu.",
"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": {
"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": {
"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": {
"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": {
"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}."
}
}

View File

@ -0,0 +1,4 @@
{
"~usage": "~usage [command]",
"~help": "~help [module]"
}

View 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.

View 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);
};

View File

@ -0,0 +1,6 @@
{
"~md5": "~md5 [text]",
"~sha1": "~sha1 [text]",
"~sha256": "~sha256 [text]",
"~aes": "~aes \"[text]\" \"[key]\""
}

View File

@ -0,0 +1,3 @@
{
"outputChannel": "#realitree"
}

View 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);
};

View File

@ -0,0 +1,5 @@
## CTCP
Responds to CTCP commands.
## Description
At the moment only CTCP VERSION, CTCP PING is handled in JSBot.

View File

@ -0,0 +1,3 @@
{
"ignorable": true
}

View 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);
};

View File

@ -0,0 +1,31 @@
## Dent
Post dents.
### Description
Allows the posting of dents to Identica. Easily abused for posting status
messages to Twitter by linking the Identica account.
### Commands
#### ~dent [text]
Post the given text to Identica.
### Configuration
#### username
Identica username to post with.
#### password
Identica password to post with.
### API
#### post(content)
Post the given content to Identica.
### Hooks
#### ~qadd
Posts new quote additions.

View File

@ -0,0 +1,8 @@
{
"username": "youruserhere",
"password": "yourpasswordhere",
"dependencies": [ "link" ],
"ignorable": true,
"dentQuotes": true,
"api": "http://quitter.se/"
}

View File

@ -0,0 +1,77 @@
var request = require('request');
_ = require('underscore')._;
var dent = function(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 = this.config.username,
password = this.config.password,
info,
auth = "Basic " +
new Buffer(username + ":" + password).toString("base64");
request.post({
'url': this.config.api + '/statuses/update.json?status=' +
escape(content),
'headers': {
'Authorization': auth
}
},
function(error, response, body) {
console.log(body);
}.bind(this));
}
};
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.onLoad = function() {
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);
};
exports.fetch = function(dbot) {
return new dent(dbot);
};

View File

@ -0,0 +1,9 @@
## Dice
Rolls virtual dice.
### Description
Rolls a virtual die and outputs the result to the channel.
### Commands
#### ~roll <die type>
Rolls a die. 1d6 will be rolled by default.

View File

@ -0,0 +1,4 @@
{
"ignorable": true,
"dependencies": [ "command" ]
}

100
modules-stock/dice/dice.js Normal file
View File

@ -0,0 +1,100 @@
var parseDiceSpec = function (specString) {
var rawSpec = specString.valMatch(/^([0-9]*)d(%|[0-9]*)(|[+-][0-9]+)$/i, 4);
if (rawSpec !== false) {
if (rawSpec[2] === "%") {
rawSpec[2] = 100;
}
return {
"count": parseInt(rawSpec[1] || 1),
"sides": parseInt(rawSpec[2] || 6),
"modifier": parseInt(rawSpec[3] || 0)
};
} else {
return false;
}
};
var normalizeDiceSpec = function (specString) {
var diceSpec = parseDiceSpec(specString);
if (diceSpec["sides"] > 10000) {
return false;
}
if (diceSpec["count"] > 1000) {
return false;
}
if (diceSpec["count"] > 1) {
var count = diceSpec["count"];
} else {
var count = "";
}
if (diceSpec["sides"] === 100) {
var sides = "%";
} else {
var sides = diceSpec["sides"];
}
if (diceSpec["modifier"] > 0) {
var modifier = "+" + diceSpec["modifier"];
} else if (diceSpec["modifier"] < 0) {
var modifier = diceSpec["modifier"];
} else {
var modifier = "";
}
return (count + "d" + sides + modifier);
};
var dice = function(dbot) {
var commands = {
'~roll': function (event) {
var rolls = [];
if (event.params.length === 1) {
event.params.push("d6");
}
for (var i = 1; i < event.params.length; i++) {
var diceSpec = parseDiceSpec(event.params[i]);
if (diceSpec === false) {
rolls.push([event.params[i], false]);
} else {
rolls.push([normalizeDiceSpec(event.params[i]), [], diceSpec["modifier"]]);
for (var j = 0; j < diceSpec["count"] ; j++) {
rolls[rolls.length-1][1].push(Math.ceil(Math.random() * diceSpec["sides"]));
}
}
}
for (var i = 0; i < rolls.length; i++) {
if (rolls[i][1] === false) {
event.reply(rolls[i][0] + ": invalid dice spec");
} else {
if (rolls[i][1].length > 1) {
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 += " + ";
} else {
total += " - ";
}
total += Math.abs(rolls[i][2]) + " -> " + (rolls[i][1].sum() + rolls[i][2]);
}
total += ")"
} else {
var total = "";
}
event.reply(rolls[i][0] + ": " + rolls[i][1].join(" ") + total);
}
}
}
};
this.commands = commands;
}
exports.fetch = function(dbot) {
return new dice(dbot);
};

View 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-stock/dns/dns.js Normal file
View 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);
};

View 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})"
}
}

View File

@ -0,0 +1,30 @@
## Event
Emit events for whatever you want man idk.
### Description
This is a library module designed for other modules to use to emit various
events at any point, and also to attach functions to said events. These are
similar to command hooks, however their advantage is that they may be called
anywhere in your code; they are particularly useful when you want to attach a
callback to a listener.
### API
#### addHook(eventName, callback)
This function will set a given callback to be executed every time the
emit API function is executed with the given event name. The arguments of your
callback are defined as an array in the emit call.
The best place to add hooks to commands is in the 'onLoad' function of your
module, as this ensures it will be run while all other modules are loaded so
nothing will be missed.
#### emit(eventName, [ arguments ])
This function executes all of the functions associated with the given eventName,
passing your given array of arguments.
For example, to emit an event when you detect a nick change:
dbot.api.event.emit('nick_changed', [ event.server, newNick ]);

View File

@ -0,0 +1,28 @@
/**
* Module Name: event
* Description: Allow other modules to emit events and that
*/
var _ = require('underscore')._;
var event = function(dbot) {
this.dbot = dbot;
this.hooks = {};
this.api = {
'addHook': function(eventName, callback) {
if(!_.has(this.hooks, eventName)) this.hooks[eventName] = [];
this.hooks[eventName].push(callback);
},
'emit': function(eventName, args) {
if(_.has(this.hooks, eventName)) {
_.each(this.hooks[eventName], function(callback) {
callback.apply(callback.module, args);
});
}
}
};
}
exports.fetch = function(dbot) {
return new event(dbot);
};

View 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.

View 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);
};

View 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);
};

View File

@ -0,0 +1,4 @@
{
"outputPrefix": "\u000311food\u000f",
"api_key": "http://food2fork.com/about/api"
}

View 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);
};

View File

@ -0,0 +1,8 @@
{
"recipe": {
"en": "{title} - {link}"
},
"no_recipe": {
"en": "No recipes found."
}
}

View 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.

View File

@ -0,0 +1,4 @@
{
"ignorable": true,
"api_key": "CONSUMERKEY_HERE"
}

33
modules-stock/fpx/fpx.js Normal file
View 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);
};

View File

@ -0,0 +1,8 @@
{
"5px_result":{
"en": "{name} - http://500px.com/photo/{id}"
},
"5px_error": {
"en": "Something went wrong :( Example: '~r500px'"
}
}

View File

@ -0,0 +1,3 @@
{
"~r500px": "~r500px"
}

View 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.

View 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``

View 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"
}

View 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);
};

View 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}."
}
}

View 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);
};

View File

@ -0,0 +1,4 @@
{
"api_key": "sethere",
"outputPrefix": "\u00033goodreads\u000f"
}

View 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);

View 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:"
}
}

View 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"
}

View File

@ -0,0 +1,26 @@
## Ignore
Ignore modules.
### Description
Commands with which users can choose to ignore listeners and commands from
certain modules persistently, by storing their choices in the database. This is
an interface for the JSBot ignoreTag functionality which actually implements
the ignoration.
### Configuration
All modules may return with them an 'ignorable' property, which defines whether
or not their functionality may be ignored by users.
### Commands
#### ~ignore [module]
Ignore a given module. If the user does not specify a module, or provides an
invalid one a list of modules which are available to ignore will be given.
#### ~unignore [module]
Unignore a previously ignored module. If the user does not specify a module, or
provides an invalid choice a list of modules which are currently ignored will be
given.

View 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);
};

View File

@ -0,0 +1,6 @@
{
"ignorable": false,
"dependencies": [ "users" ],
"dbKeys": [ "ignores", "bans" ],
"dbType": "redis"
}

View File

@ -0,0 +1,304 @@
/**
* Module Name: Ignore
* Description: Handles commands in which users can choose to ignore listeners
* and commands from certain modules. It also populates the JSBot instance with
* this information, since that actually performs the ignorance. Also provides
* commands for moderators to choose the bot to ignore certain channels.
*/
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.modules[module].ignorable === true;
})
.pluck('name')
.value();
if(_.isUndefined(module)) {
event.reply(dbot.t('ignore_usage', {
'user': event.user,
'modules': ignorableModules.join(', ')
}));
} else {
if(module == '*' || _.include(ignorableModules, module)) {
this.api.getUserIgnores(event.rUser, function(err, ignores) {
if(!ignores) {
ignores = {
'id': event.rUser.id,
'ignores': [],
'bans': []
};
}
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 }));
}
}
},
'~unignore': function(event) {
var module = event.params[1];
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 {
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 channelName = event.input[1],
module = event.input[2];
// Ignoring the value of 'ignorable' at the moment
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 channelName = event.input[1],
module = event.input[2],
channel = false;
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';
this.commands = commands;
this.onLoad = function() {
dbot.instance.clearIgnores();
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) {
return new ignore(dbot);
};

View File

@ -0,0 +1,183 @@
{
"ignore_usage": {
"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}.",
"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": {
"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.",
"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": {
"en": "{user}: Now ignoring {module}.",
"es": "{user}: Estás ignorando {module}.",
"na'vi": "{user}: Nga ke terìng mikyun {module}ne set.",
"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": {
"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.",
"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": {
"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}.",
"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": {
"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.",
"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": {
"en": "{user}: No longer ignoring {module}.",
"es": "{user}: Ya no ignoras {module}.",
"na'vi": "{user}: Nga terìng mikyun {module}ne set",
"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": {
"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": {
"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": {
"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": {
"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": {
"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}"
}
}

View 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.

View 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"
}

View 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);
}

View 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);
};

View 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"
}
}

View File

@ -0,0 +1,40 @@
## JS
Run JavaScript.
### Description
This module provides two commands which allow the execution of Javascript code
from the bot.
### Commands
#### ~js [code]
For regular users, there is the *~js* command, which is completely sandboxed,
but can still be used for calculation and the like.
> ~js Array(16).join('wat'-1) + " Batman!";
'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
This feature is fairly safe as the user doesn't have access to anything
dangerous, and is safe from infinite loops or locking DBot up because the code
which is run is killed if it does not finish within a short amount of time.
#### ~ajs [code]
For administrators, the incredibly useful *~ajs* command is also available. The
input for this command is simply 'eval'-ed and therefore has full access to
DBot's memory. Of course, this is incredibly unsafe, but I find it rather fun;
remember to only give extremely trusted friends administrator access to your
DBot instance, as there's nothing to stop them wiping the database or probably
even your hard drive - if you're worried about that kind of thing, do not load
this module.
However, it's useful for many things, such as administrative activity for
which there isn't a command in the admin module. For example, you could hot-add
a new administrator like this:
> ~ajs dbot.admin.push('batman');
2
You can also use it for debugging, or even adding new commands while DBot is
running.

View File

@ -0,0 +1,9 @@
{
"commands": {
"~js": {
"disabled": true
}
},
"dependencies": [ "command" ],
"ignorable": true
}

51
modules-stock/js/js.js Normal file
View File

@ -0,0 +1,51 @@
/**
* Module Name: JS
* Description: Allows users to run sandboxed JS code, printing the result in
* 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 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) {}
},
// 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['~ajs'].access = 'admin';
this.name = 'js';
this.ignorable = true;
this.commands = commands;
};
exports.fetch = function(dbot) {
return new js(dbot);
};

View File

@ -0,0 +1,4 @@
{
"~js": "~js [command]",
"~ajs": "~ajs [command]"
}

View File

@ -0,0 +1,5 @@
{
"announce": [ { "server": "tripsit", "name": "#dbot" } ],
"dbKeys": [ "karma" ],
"dbType": "redis"
}

View 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);
};

View File

@ -0,0 +1,8 @@
{
"karma": {
"en": "[karma] {item} has {karma} karma"
},
"newkarma": {
"en": "{item} now has{value} karma"
}
}

View File

@ -0,0 +1,29 @@
## Kick
Kicking and kicking-related accessories.
### Description
This module counts the number of times people are kicked from and kick people
from channels, and provides commands for viewing this data. It also has the bot
attempt to rejoin the channel if it is kicked.
### Commands
#### ~kickcount [username]
Show the number of times a given user has been kicked and has kicked other
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.

View 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);
};

View File

@ -0,0 +1,18 @@
{
"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"
}

270
modules-stock/kick/kick.js Normal file
View File

@ -0,0 +1,270 @@
var _ = require('underscore')._;
var kick = function (dbot) {
if (!_.has(dbot.db, 'recentTimeouts')) {
dbot.db.recentTimeouts = {};
}
this.recentTimeouts = dbot.db.recentTimeouts;
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 {
duration += ' minutes';
}
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);
}
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);
}
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 {
event.reply(dbot.t('no_user', {
'user': quietee
}));
}
}
.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);
};

View 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);
};

View File

@ -0,0 +1,115 @@
{
"user_kicks": {
"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.",
"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": {
"en": "Thou shalt not kick {botname}",
"es": "No expulsás {botname}",
"na'vi": "Ngal {botname}it ke tsun tsrive'i",
"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."
}
}

View 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"
}

View File

@ -0,0 +1,11 @@
{
"action": "kill",
"sensitivity": 10,
"exempt": [],
"advert_content": [
"________ ______"
],
"cliconn_channel": "#dnsbl",
"cliconn_patterns": [],
"exempt_channels": []
}

Some files were not shown because too many files have changed in this diff Show More