1 | commit a2d36f86df617efad0dcace56862f0a8c147d3da (HEAD -> master) |
2 | Author: acidvegas <acid.vegas@acid.vegas> |
3 | Date: Thu Jun 27 23:11:58 2019 -0400 |
4 | |
5 | Initial commit |
6 | --- |
7 | LICENSE | 15 ++ |
8 | README.md | 43 +++++ |
9 | ircs/core/config.py | 20 +++ |
10 | ircs/core/debug.py | 70 ++++++++ |
11 | ircs/core/functions.py | 113 +++++++++++++ |
12 | ircs/core/irc.py | 433 +++++++++++++++++++++++++++++++++++++++++++++++++ |
13 | ircs/data/.gitignore | 1 + |
14 | ircs/ircs.py | 20 +++ |
15 | 8 files changed, 715 insertions(+) |
16 | |
17 | diff --git a/LICENSE b/LICENSE |
18 | new file mode 100644 |
19 | index 0000000..69997e8 |
20 | --- /dev/null |
21 | +++ b/LICENSE |
22 | @@ -0,0 +1,15 @@ |
23 | +ISC License |
24 | + |
25 | +Copyright (c) 2019, acidvegas <acid.vegas@acid.vegas> |
26 | + |
27 | +Permission to use, copy, modify, and/or distribute this software for any |
28 | +purpose with or without fee is hereby granted, provided that the above |
29 | +copyright notice and this permission notice appear in all copies. |
30 | + |
31 | +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
32 | +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
33 | +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
34 | +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
35 | +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
36 | +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
37 | +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
38 | diff --git a/README.md b/README.md |
39 | new file mode 100644 |
40 | index 0000000..6369d0d |
41 | --- /dev/null |
42 | +++ b/README.md |
43 | @@ -0,0 +1,43 @@ |
44 | +###### Information |
45 | +This project is no longer being maintained & is made available for historical purposes only. |
46 | + |
47 | +The IRCS project is basically a stripped down version of [Anope](https://www.anope.org/)'s bots all crammed into one & was developed for usage with [UnrealIRCd](https://www.unrealircd.org/) 4. |
48 | + |
49 | +###### Setup |
50 | +You will get the lowest ping having the bot connect to localhost on the same box as the IRCd is running. |
51 | + |
52 | +The bot *will* require network operator privledges in order to work, so make sure you add that into your IRCd configuration. |
53 | + |
54 | +Edit [`config.py`](https://github.com/acidvegas/ircs/blob/master/ircs/core/config.py) and change the `oper_passwd` and the `admin_host` settings. |
55 | + |
56 | +###### Commands |
57 | +| Mode Command | Description | Restriction | |
58 | +| --- | --- | --- | |
59 | +| !mode \<chan> | Read all the auto-mode hosts for \<channel>. | *+q only* | |
60 | +| !mode \<chan> \<mode> | Read all the \<mode> auto-mode hosts for \<channel>. | *+q only* | |
61 | +| !mode \<chan> \<mode> +\<ident> | Automatically +\<mode> a user matching \<ident>. | *+q only* | |
62 | +| !mode \<chan> \<mode> -\<ident> | Remove automatic +\<mode> from a user matching \<ident>. | *+q only* | |
63 | +| !sync \<chan> | Set all the channels stored in the database for \<channel>. | *+q only* | |
64 | + |
65 | +| Vhost Command | Description | Restriction | |
66 | +| --- | --- | --- | |
67 | +| !vhost add \<ident> \<vhost> | Change the host of \<ident> to \<vhost> on connect. | *admin only*| |
68 | +| !vhost drop \<ident> | Delete the VHOST registered to \<ident>. | *admin only* | |
69 | +| !vhost list | Return a list of all activated VHOSTs. | *admin only* | |
70 | +| !vhost on | Turn on your VHOST. | *vhost users only* | |
71 | +| !vhost off | Turn off your VHOST. | *vhost users only*| |
72 | +| !vhost sync | Change your current hostmask to your VHOST. | *vhost users only* | |
73 | + |
74 | +| Admin Command | Description | Restriction | |
75 | +| --- | --- | --- | |
76 | +| !husers | List all users connected but not joined to any channel(s). | *admin only* | |
77 | +| !husers join \<channel> | Force join all hidden users into \<channe>. | *admin only* | |
78 | +| !husers kill | Kill the connection of all hidden users. | *admin only* | |
79 | +| !husers gline | G:Line the connection of all hidden users. | *admin only* | |
80 | +| !husers gzline | GZ:Line the connection of all hidden users. | *admin only* | |
81 | + |
82 | +###### Mirrors |
83 | +- [acid.vegas](https://acid.vegas/ircs) *(main)* |
84 | +- [SuperNETs](https://git.supernets.org/acidvegas/ircs) |
85 | +- [GitHub](https://github.com/acidvegas/ircs) |
86 | +- [GitLab](https://gitlab.com/acidvegas/ircs) |
87 | diff --git a/ircs/core/config.py b/ircs/core/config.py |
88 | new file mode 100644 |
89 | index 0000000..f1af76f |
90 | --- /dev/null |
91 | +++ b/ircs/core/config.py |
92 | @@ -0,0 +1,20 @@ |
93 | +#!/usr/bin/env python |
94 | +# IRC Services (IRCS) - Developed by acidvegas in Python (https://acid.vegas/ircs) |
95 | +# config.py |
96 | + |
97 | +# Connection |
98 | +server = 'localhost' |
99 | +port = 6667 |
100 | +use_ipv6 = False |
101 | +use_ssl = False |
102 | +vhost = None |
103 | +password = None |
104 | + |
105 | +# Identity |
106 | +nickname = 'IRCS' |
107 | +username = 'ircs' |
108 | +realname = 'IRC Services Bot' |
109 | + |
110 | +# Admin |
111 | +admin_host = 'CHANGEME' |
112 | +oper_passwd = 'CHANGEME' |
113 | diff --git a/ircs/core/debug.py b/ircs/core/debug.py |
114 | new file mode 100644 |
115 | index 0000000..07ce3f0 |
116 | --- /dev/null |
117 | +++ b/ircs/core/debug.py |
118 | @@ -0,0 +1,70 @@ |
119 | +#!/usr/bin/env python |
120 | +# IRC Services (IRCS) - Developed by acidvegas in Python (https://acid.vegas/ircs) |
121 | +# debug.py |
122 | + |
123 | +import ctypes |
124 | +import os |
125 | +import sys |
126 | +import time |
127 | +import string |
128 | + |
129 | +def check_data(data): |
130 | + if all(c in string.printable for c in data): |
131 | + return True |
132 | + else: |
133 | + return False |
134 | + |
135 | +def check_privileges(): |
136 | + if check_windows(): |
137 | + if ctypes.windll.shell32.IsUserAnAdmin() != 0: |
138 | + return True |
139 | + else: |
140 | + return False |
141 | + else: |
142 | + if os.getuid() == 0 or os.geteuid() == 0: |
143 | + return True |
144 | + else: |
145 | + return False |
146 | + |
147 | +def check_version(major): |
148 | + if sys.version_info.major == major: |
149 | + return True |
150 | + else: |
151 | + return False |
152 | + |
153 | +def check_windows(): |
154 | + if os.name == 'nt': |
155 | + return True |
156 | + else: |
157 | + return False |
158 | + |
159 | +def clear(): |
160 | + if check_windows(): |
161 | + os.system('cls') |
162 | + else: |
163 | + os.system('clear') |
164 | + |
165 | +def error(msg, reason=None): |
166 | + if reason: |
167 | + print('{0} | [!] - {1} ({2})'.format(get_time(), msg, str(reason))) |
168 | + else: |
169 | + print('{0} | [!] - {1}'.format(get_time(), msg)) |
170 | + |
171 | +def error_exit(msg): |
172 | + raise SystemExit('{0} | [!] - {1}'.format(get_time(), msg)) |
173 | + |
174 | +def get_time(): |
175 | + return time.strftime('%I:%M:%S') |
176 | + |
177 | +def info(): |
178 | + clear() |
179 | + print(''.rjust(56, '#')) |
180 | + print('#{0}#'.format(''.center(54))) |
181 | + print('#{0}#'.format('IRC Services (IRCS)'.center(54))) |
182 | + print('#{0}#'.format('Developed by acidvegas in Python'.center(54))) |
183 | + print('#{0}#'.format('https://acid.vegas/ircs'.center(54))) |
184 | + print('#{0}#'.format(''.center(54))) |
185 | + print(''.rjust(56, '#')) |
186 | + |
187 | +def irc(msg): |
188 | + print('{0} | [~] - {1}'.format(get_time(), msg)) |
189 | diff --git a/ircs/core/functions.py b/ircs/core/functions.py |
190 | new file mode 100644 |
191 | index 0000000..0af9b41 |
192 | --- /dev/null |
193 | +++ b/ircs/core/functions.py |
194 | @@ -0,0 +1,113 @@ |
195 | +#!/usr/bin/env python |
196 | +# IRC Services (IRCS) - Developed by acidvegas in Python (https://acid.vegas/ircs) |
197 | +# functions.py |
198 | + |
199 | +import inspect |
200 | +import os |
201 | +import sqlite3 |
202 | +import string |
203 | + |
204 | +# Database |
205 | +database_dir = os.path.join(os.path.dirname(os.path.realpath(inspect.stack()[-1][1])), 'data') |
206 | +database_file = os.path.join(database_dir, 'ircs.db') |
207 | + |
208 | +# Globals |
209 | +db = sqlite3.connect(database_file) |
210 | +sql = db.cursor() |
211 | + |
212 | +class Database: |
213 | + def check(): |
214 | + tables = sql.execute('SELECT name FROM sqlite_master WHERE type=\'table\'').fetchall() |
215 | + if len(tables): |
216 | + return True |
217 | + else: |
218 | + return False |
219 | + |
220 | + def create(): |
221 | + sql.execute('CREATE TABLE CHANSERV (CHANNEL TEXT NOT NULL, IDENT TEXT NOT NULL, MODE TEXT NOT NULL);') |
222 | + sql.execute('CREATE TABLE HOSTSERV (IDENT TEXT NOT NULL, VHOST TEXT NOT NULL, STATUS TEXT NOT NULL);') |
223 | + db.commit() |
224 | + |
225 | + |
226 | + |
227 | +class ChanServ: |
228 | + def add_mode(chan, ident, mode): |
229 | + sql.execute('INSERT INTO CHANSERV (CHANNEL,IDENT,MODE) VALUES (?, ?, ?)', (chan, ident, mode)) |
230 | + db.commit() |
231 | + |
232 | + def channels(): |
233 | + return set(list(item[0] for item in sql.execute('SELECT CHANNEL FROM CHANSERV ORDER BY CHANNEL ASC').fetchall())) |
234 | + |
235 | + def del_mode(chan, ident): |
236 | + sql.execute('DELETE FROM CHANSERV WHERE CHANNEL=? AND IDENT=?', (chan, ident)) |
237 | + db.commit() |
238 | + |
239 | + def drop(chan): |
240 | + sql.execute('DELETE FROM CHANSERV WHERE CHANNEL=?', (chan,)) |
241 | + db.commit() |
242 | + |
243 | + def get_mode(chan, ident): |
244 | + data = sql.execute('SELECT MODE FROM CHANSERV WHERE CHANNEL=? AND IDENT=?', (chan, ident)).fetchone() |
245 | + if data: |
246 | + return data[0] |
247 | + else: |
248 | + return None |
249 | + |
250 | + def hosts(): |
251 | + return set(list(item[0].split('@')[1] for item in sql.execute('SELECT IDENT FROM CHANSERV', (channel,)).fetchall())) |
252 | + |
253 | + def idents(chan, mode=None): |
254 | + if mode: |
255 | + return list(item[0] for item in sql.execute('SELECT IDENT FROM CHANSERV WHERE CHANNEL=? AND MODE=?', (channel, mode)).fetchall()) |
256 | + else: |
257 | + return list(item[0] for item in sql.execute('SELECT IDENT FROM CHANSERV WHERE CHANNEL=?', (channel,)).fetchall()) |
258 | + |
259 | + def read(channel, mode=None): |
260 | + if mode: |
261 | + return sql.execute('SELECT IDENT FROM CHANSERV WHERE CHANNEL=? AND MODE=? ORDER BY CHANNEL ASC, MODE ASC, IDENT ASC', (channel, mode)).fetchall() |
262 | + else: |
263 | + return sql.execute('SELECT IDENT,MODE FROM CHANSERV WHERE CHANNEL=? ORDER BY CHANNEL ASC, MODE ASC, IDENT ASC', (channel,)).fetchall() |
264 | + |
265 | + |
266 | + |
267 | +class HostServ: |
268 | + def add(ident, vhost): |
269 | + sql.execute('INSERT INTO HOSTSERV (IDENT,VHOST,STATUS) VALUES (?, ?, \'pending\')', (ident, vhost)) |
270 | + db.commit() |
271 | + |
272 | + def delete(ident): |
273 | + sql.execute('DELETE FROM HOSTSERV WHERE IDENT=?', (ident,)) |
274 | + db.commit() |
275 | + |
276 | + def get_vhost(ident, active=False): |
277 | + data = sql.execute('SELECT VHOST FROM HOSTSERV WHERE IDENT=? AND STATUS=\'on\'', (ident,)).fetchone() |
278 | + if data: |
279 | + return data[0] |
280 | + else: |
281 | + return None |
282 | + |
283 | + def get_status(ident): |
284 | + data = sql.execute('SELECT STATUS FROM HOSTSERV WHERE IDENT=?', (ident,)).fetchone() |
285 | + if data: |
286 | + return data[0] |
287 | + else: |
288 | + return None |
289 | + |
290 | + def hosts(): |
291 | + return set(list(item[0].split('@')[1] for item in sql.execute('SELECT IDENT FROM CHANSERV', (channel,)).fetchall())) |
292 | + |
293 | + def idents(): |
294 | + return list(item[0] for item in sql.execute('SELECT IDENT FROM HOSTSERV').fetchall()) |
295 | + |
296 | + def pending(): |
297 | + return sql.execute('SELECT IDENT,VHOST FROM HOSTSERV WHERE STATUS=\'pending\' ORDER BY IDENT ASC').fetchall() |
298 | + |
299 | + def read(): |
300 | + return sql.execute('SELECT IDENT,VHOST FROM HOSTSERV ORDER BY IDENT ASC').fetchall() |
301 | + |
302 | + def set_status(ident, status): |
303 | + sql.execute('UPDATE HOSTSERV SET STATUS=? WHERE IDENT=?', (status, ident)) |
304 | + db.commit() |
305 | + |
306 | + def vhosts(): |
307 | + return list(item[0] for item in sql.execute('SELECT VHOST FROM HOSTSERV').fetchall()) |
308 | diff --git a/ircs/core/irc.py b/ircs/core/irc.py |
309 | new file mode 100644 |
310 | index 0000000..e0834e5 |
311 | --- /dev/null |
312 | +++ b/ircs/core/irc.py |
313 | @@ -0,0 +1,433 @@ |
314 | +#!/usr/bin/env python |
315 | +# IRC Services (IRCS) - Developed by acidvegas in Python (https://acid.vegas/ircs) |
316 | +# irc.py |
317 | + |
318 | +import socket |
319 | +import ssl |
320 | +import time |
321 | + |
322 | +import config |
323 | +import debug |
324 | +from functions import Database, ChanServ, HostServ |
325 | + |
326 | +# Formatting Control Characters / Color Codes |
327 | +bold = '\x02' |
328 | +italic = '\x1D' |
329 | +underline = '\x1F' |
330 | +reverse = '\x16' |
331 | +reset = '\x0f' |
332 | +white = '00' |
333 | +black = '01' |
334 | +blue = '02' |
335 | +green = '03' |
336 | +red = '04' |
337 | +brown = '05' |
338 | +purple = '06' |
339 | +orange = '07' |
340 | +yellow = '08' |
341 | +light_green = '09' |
342 | +cyan = '10' |
343 | +light_cyan = '11' |
344 | +light_blue = '12' |
345 | +pink = '13' |
346 | +grey = '14' |
347 | +light_grey = '15' |
348 | + |
349 | +class IRC(object): |
350 | + server = config.server |
351 | + port = config.port |
352 | + use_ipv6 = config.use_ipv6 |
353 | + use_ssl = config.use_ssl |
354 | + vhost = config.vhost |
355 | + password = config.password |
356 | + nickname = config.nickname |
357 | + username = config.username |
358 | + realname = config.realname |
359 | + oper_passwd = config.oper_passwd |
360 | + admin_host = config.admin_host |
361 | + |
362 | + def __init__(self): |
363 | + self.husers = list() |
364 | + self.last = dict() |
365 | + self.sock = None |
366 | + |
367 | + def action(self, chan, msg): |
368 | + self.sendmsg(chan, '\x01ACTION {0}\x01'.format(msg)) |
369 | + |
370 | + def chghost(self, nick, host): |
371 | + self.raw('CHGHOST {0} {1}'.format(nick, host)) |
372 | + |
373 | + def color(self, msg, foreground, background=None): |
374 | + if background: |
375 | + return '\x03{0},{1}{2}{3}'.format(foreground, background, msg, reset) |
376 | + else: |
377 | + return '\x03{0}{1}{2}'.format(foreground, msg, reset) |
378 | + |
379 | + def connect(self): |
380 | + try: |
381 | + self.create_socket() |
382 | + self.sock.connect((self.server, self.port)) |
383 | + if self.password: |
384 | + self.raw('PASS ' + self.password) |
385 | + self.raw('USER {0} 0 * :{1}'.format(self.username, self.realname)) |
386 | + self.raw('NICK ' + self.nickname) |
387 | + except socket.error as ex: |
388 | + debug.error('Failed to connect to IRC server.', ex) |
389 | + self.event_disconnect() |
390 | + else: |
391 | + self.listen() |
392 | + |
393 | + def create_socket(self): |
394 | + if self.use_ipv6: |
395 | + self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) |
396 | + else: |
397 | + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
398 | + if self.vhost: |
399 | + self.sock.bind((self.vhost, 0)) |
400 | + if self.use_ssl: |
401 | + self.sock = ssl.wrap_socket(self.sock) |
402 | + |
403 | + def error(self, target, msg, reason=None): |
404 | + if reason: |
405 | + self.sendmsg(target, '[{0}] {1} {2}'.format(self.color('ERROR', red), msg, self.color('({0})'.format(str(reason)), grey))) |
406 | + else: |
407 | + self.sendmsg(target, '[{0}] {1}'.format(self.color('ERROR', red), msg)) |
408 | + |
409 | + def event_connect(self): |
410 | + self.mode(self.nickname, '+Bd') |
411 | + self.oper(self.username, self.oper_passwd) |
412 | + if Database.check(): |
413 | + for channel in ChanServ.channels(): |
414 | + self.join(channel) |
415 | + else: |
416 | + Database.create() |
417 | + |
418 | + def event_connection(self, nick, ident): |
419 | + vhost = HostServ.get_vhost(ident, True) |
420 | + if vhost: |
421 | + self.chghost(nick, vhost) |
422 | + |
423 | + def event_disconnect(self): |
424 | + self.sock.close() |
425 | + time.sleep(10) |
426 | + self.connect() |
427 | + |
428 | + def event_end_of_who(self): |
429 | + if self.last['cmd'] == 'husers': |
430 | + if self.husers: |
431 | + self.sendmsg(self.last['nick'], '{0} {1}'.format(self.color('Total:', light_blue), self.color(len(self.husers), grey))) |
432 | + else: |
433 | + self.error(self.last['nick'], 'No hidden users found.') |
434 | + |
435 | + def event_join(self, nick, ident, chan): |
436 | + mode = ChanServ.get_mode(chan, ident) |
437 | + if mode: |
438 | + self.mode(chan, '+{0} {1}'.format(mode, nick)) |
439 | + |
440 | + def event_kick(self, chan, kicked): |
441 | + if kicked == self.nickname: |
442 | + if chan in Database.channels(): |
443 | + self.join(chan) |
444 | + |
445 | + def event_nick_in_use(self): |
446 | + debug.error_exit('IRCS is already running.') |
447 | + |
448 | + def event_notice(self, nick, data): |
449 | + if '.' in nick or nick == self.server: |
450 | + args = data.split() |
451 | + if 'Client connecting' in data: |
452 | + nick = args[6] |
453 | + ident = args[7][1:][:-1] |
454 | + self.event_connection(nick, ident) |
455 | + |
456 | + def event_private(self, nick, ident, msg): |
457 | + try: |
458 | + args = msg.split() |
459 | + cmd = args[0][1:] |
460 | + host = ident.split('@')[1] |
461 | + if cmd == 'husers' and host == self.admin_host: |
462 | + if len(args) == 1: |
463 | + self.husers = list() |
464 | + self.last = {'nick':nick,'cmd':'husers'} |
465 | + self.who('I', '*') |
466 | + elif len(args) == 2: |
467 | + if args[1] == 'kill': |
468 | + if self.husers: |
469 | + self.action(nick, 'Killing all hidden users...') |
470 | + for item in self.husers: |
471 | + self.kill(item['nick'], 'Killed by IRCS anti-bot protection.') |
472 | + else: |
473 | + self.error(nick, 'Hidden users list is empty.', 'Make sure you run !husers first') |
474 | + elif args[1] == 'gzline': |
475 | + if self.husers: |
476 | + self.action(nick, 'Z:Lining all hidden users...') |
477 | + for item in self.husers: |
478 | + self.gzline(item['host'], '1d', 'Banned by IRCS anti-bot protection.') |
479 | + else: |
480 | + self.error(nick, 'Hidden users list is empty.', 'Make sure you run !husers first') |
481 | + elif len(args) == 3: |
482 | + if args [1] == 'join': |
483 | + channel = args[2] |
484 | + if channel.startswith('#') and len(channel) <= 20: |
485 | + if self.husers: |
486 | + self.action(nick, 'Joining all hidden users to {0}...'.format(channel)) |
487 | + for item in self.husers: |
488 | + self.sajoin(item['nick'], channel) |
489 | + else: |
490 | + self.error(nick, 'Hidden users list is empty.', 'Make sure you run !husers first') |
491 | + else: |
492 | + self.error(nick, 'Invalid arguments.') |
493 | + else: |
494 | + self.error(nick, 'Invalid arguments.') |
495 | + else: |
496 | + self.error(nick, 'Invalid arguments.') |
497 | + elif cmd == 'mode': |
498 | + if len(args) > 1: |
499 | + channel = args[1] |
500 | + if channel[:1] == '#' and len(channel) <= 20 and debug.check_data(channel): |
501 | + if ChanServ.get_mode(channel, ident) == 'q' or host == self.admin_host: |
502 | + if len(args) == 2: |
503 | + if channel in ChanServ.channels(): |
504 | + data = ChanServ.read(channel) |
505 | + self.sendmsg(nick, '[{0}]'.format(self.color(channel, purple))) |
506 | + for row in data: |
507 | + self.sendmsg(nick, '{0} | {1}'.format(self.color('+' + row[1], grey), self.color(row[0], yellow))) |
508 | + self.sendmsg(nick, '{0} {1}'.format(self.color('Total:', light_blue), self.color(len(data), grey))) |
509 | + else: |
510 | + self.error(nick, self.color(channel, purple) + ' does not exist.') |
511 | + elif len(args) == 3: |
512 | + if args[2] in ('a','h','o','v','q'): |
513 | + if channel in ChanServ.channels(): |
514 | + mode = args[2] |
515 | + data = ChanServ.read(channel, mode) |
516 | + if data: |
517 | + self.sendmsg(nick, '[{0}] {1}'.format(self.color(channel, purple) , self.color('(+{0})'.format(mode), grey))) |
518 | + for row in data: |
519 | + self.sendmsg(nick, self.color(row[0], yellow)) |
520 | + self.sendmsg(nick, '{0} {1}'.format(self.color('Total:', light_blue), self.color(len(data), grey))) |
521 | + else: |
522 | + self.error(nick, self.color('+{0}'.format(mode), grey) + ' is empty.') |
523 | + else: |
524 | + self.error(nick, self.color(channel, purple) + ' does not exist.') |
525 | + else: |
526 | + self.error(nick, 'Invalid arguments.') |
527 | + elif len(args) == 4: |
528 | + if args[2] in ('a','h','o','v','q') and args[3][:1] in '+-' and len(args[3]) <= 63 and debug.check_data(args[3]): |
529 | + mode = args[2] |
530 | + if mode == 'q' and host != self.admin_host: |
531 | + self.error(nick, 'You do not have permission to change this mode.') |
532 | + else: |
533 | + action = args[3][:1] |
534 | + ident = args[3][1:] |
535 | + if action == '+': |
536 | + if not ChanServ.get_mode(channel, ident): |
537 | + ChanServ.add_mode(channel, ident, mode) |
538 | + self.sendmsg(nick, '{0} {1} has been {2} to the {3} database.'.format(self.color(ident, light_blue), self.color('(+{0})'.format(mode), grey), self.color('added', green), self.color(channel, purple))) |
539 | + else: |
540 | + self.error(nick, '{0} already exists in the {1} database.'.format(self.color(ident, light_blue), self.color(channel, purple))) |
541 | + elif action == '-': |
542 | + if ChanServ.get_mode(channel, ident): |
543 | + ChanServ.del_mode(channel, ident) |
544 | + self.sendmsg(nick, '{0} {1} has been {2} from the {3} database.'.format(self.color(ident, light_blue), self.color('(+{0})'.format(mode), grey), self.color('removed', red), self.color(channel, purple))) |
545 | + else: |
546 | + self.error(nick, '{0} does not exist in the {1} database.'.format(self.color(ident, light_blue), self.color(channel, purple))) |
547 | + else: |
548 | + self.error(nick, 'Invalid arguments.') |
549 | + else: |
550 | + self.error(nick, 'Invalid arguments.') |
551 | + else: |
552 | + self.error(nick, 'You do not have permission to use this command.') |
553 | + else: |
554 | + self.error(nick, 'Invalid arguments.') |
555 | + else: |
556 | + self.error(nick, 'Invalid arguments.') |
557 | + elif cmd == 'sync': |
558 | + if len(args) == 2: |
559 | + channel = args[1] |
560 | + if channel[:1] == '#' and len(channel) <= 20 and debug.check_data(channel): |
561 | + if channel in ChanServ.channels(): |
562 | + if ChanServ.get_mode(channel, ident) == 'q' or host == self.admin_host: |
563 | + self.action(nick, 'Syncing all modes in {0}...'.format(color(channel, purple))) |
564 | + self.last['cmd'] = 'sync ' + channel |
565 | + self.who('h', '*') |
566 | + else: |
567 | + self.error(nick, 'You do not have permission to use this command.') |
568 | + else: |
569 | + self.error(nick, '{0} does not exist.'.format(color(channel, purple))) |
570 | + else: |
571 | + self.error(nick, 'Invalid arguments.') |
572 | + else: |
573 | + self.error(nick, 'Invalid arguments.') |
574 | + elif cmd == 'vhost': |
575 | + if len(args) == 2: |
576 | + if args[1] == 'list': |
577 | + if host == self.admin_host: |
578 | + vhosts = HostServ.read() |
579 | + if vhosts: |
580 | + self.sendmsg(nick, '[{0}]'.format(self.color('Registered Vhosts', purple))) |
581 | + for vhost in vhosts: |
582 | + self.sendmsg(nick, '{0} {1}'.format(self.color(vhost[0], yellow), self.color('({0})'.format(vhost[1]), grey))) |
583 | + self.sendmsg(nick, '{0} {1}'.format(self.color('Total:', light_blue), self.color(len(vhosts), grey))) |
584 | + else: |
585 | + self.error(nick, 'Vhost list is empty.') |
586 | + else: |
587 | + self.error(nick, 'You do not have permission to use this command.') |
588 | + elif args[1] == 'off': |
589 | + status = HostServ.get_status(ident) |
590 | + if status == 'off': |
591 | + self.error(nick, 'VHOST is already turned off.') |
592 | + elif status == 'on': |
593 | + HostServ.set_status(ident, 'off') |
594 | + self.sendmsg(nick, 'VHOST has been turned ' + color('off', red)) |
595 | + else: |
596 | + self.error(nick, 'You do not have a registered VHOST.') |
597 | + elif args[1] == 'on': |
598 | + status = HostServ.get_status(ident) |
599 | + if status == 'off': |
600 | + HostServ.set_status(ident, 'on') |
601 | + self.sendmsg(nick, 'VHOST has been turned ' + color('on', green)) |
602 | + elif status == 'on': |
603 | + self.error(nick, 'Your VHOST is already turned on.') |
604 | + else: |
605 | + self.error(nick, 'You do not have a registered VHOST.') |
606 | + elif args[1] == 'sync': |
607 | + vhost = HostServ.get_vhost(ident) |
608 | + if host == vhost: |
609 | + self.error(nick, 'Your VHOST is already synced and working.') |
610 | + elif vhost: |
611 | + self.action(nick, 'Syncing VHOST...') |
612 | + self.chghost(nick, vhost) |
613 | + else: |
614 | + self.error(nick, 'You do not have a registered VHOST.') |
615 | + elif len(args) == 3: |
616 | + if args[1] == 'drop': |
617 | + if host == self.admin_host: |
618 | + ident = args[2] |
619 | + if ident in HostServ.idents(): |
620 | + HostServ.delete(ident) |
621 | + self.sendmsg(nick, '{0} has been {1} from the vhost database.'.format(self.color(ident, light_blue), self.color('removed', red))) |
622 | + else: |
623 | + self.error(nick, '{0} does not have a vhost.'.format(self.color(ident, light_blue))) |
624 | + else: |
625 | + self.error(nick, 'You do not have permission to use this command.') |
626 | + elif len(args) == 4: |
627 | + if args[1] == 'add': |
628 | + if host == self.admin_host: |
629 | + ident = args[2] |
630 | + vhost = args[3] |
631 | + if ident not in HostServ.idents(): |
632 | + HostServ.add(ident, vhost) |
633 | + self.sendmsg(nick, '{0} has been {1} from the database.'.format(self.color(ident, light_blue), self.color('added', green))) |
634 | + else: |
635 | + self.error(nick, '{0} is already registered.'.format(color(ident, light_blue))) |
636 | + else: |
637 | + self.error(nick, 'You do not have permission to use this command.') |
638 | + else: |
639 | + self.error(nick, 'Invalid arguments.') |
640 | + else: |
641 | + self.error(nick, 'Invalid arguments.') |
642 | + except Exception as ex: |
643 | + self.error(nick, 'Unexpected error has occured.', ex) |
644 | + |
645 | + def event_who(self, chan, user, host, nick): |
646 | + if self.last: |
647 | + if self.last['cmd'] == 'husers': |
648 | + if chan == '*': |
649 | + self.husers.append({'user':user,'host':host,'nick':nick}) |
650 | + self.sendmsg(self.last['nick'], '{0} {1}'.format(self.color(nick, yellow), self.color('({0}@{1})'.format(user, host), grey))) |
651 | + elif self.last['cmd'].startswith('sync'): |
652 | + channel = self.last['cmd'].split()[1] |
653 | + if chan == channel: |
654 | + mode = ChanServ.mode(chan, '{0}@{1]'.format(user, host)) |
655 | + if mode: |
656 | + self.mode(chan, '+{0} {1}'.format(mode, nick)) |
657 | + |
658 | + def gzline(self, host, duration, msg): |
659 | + self.raw('gzline *@{1} {2} {3}'.format(user, host, duration, msg)) |
660 | + |
661 | + def handle_events(self, data): |
662 | + args = data.split() |
663 | + if args[0] == 'PING': |
664 | + self.raw('PONG ' + args[1][1:]) |
665 | + elif args[1] == '001': |
666 | + self.event_connect() |
667 | + elif args[1] == '315': |
668 | + self.event_end_of_who() |
669 | + elif args[1] == '352': |
670 | + chan = args[3] |
671 | + user = args[4] |
672 | + host = args[5] |
673 | + nick = args[7] |
674 | + self.event_who(chan, user, host, nick) |
675 | + elif args[1] == '433': |
676 | + self.event_nick_in_use() |
677 | + elif args[1] == 'NOTICE': |
678 | + nick = args[0][1:] |
679 | + self.event_notice(nick, data) |
680 | + elif args[1] in ('JOIN','KICK','PRIVMSG'): |
681 | + nick = args[0].split('!')[0][1:] |
682 | + if nick != self.nickname: |
683 | + chan = args[2] |
684 | + if args[1] == 'JOIN': |
685 | + host = args[0].split('!')[1] |
686 | + self.event_join(nick, host, chan[1:]) |
687 | + elif args[1] == 'KICK': |
688 | + kicked = args[3] |
689 | + self.event_kick(chan, kicked) |
690 | + elif args[1] == 'PRIVMSG': |
691 | + ident = args[0].split('!')[1] |
692 | + msg = data.split('{0} PRIVMSG {1} :'.format(args[0], chan))[1] |
693 | + if msg.startswith('!'): |
694 | + if chan == self.nickname: |
695 | + self.event_private(nick, ident, msg) |
696 | + |
697 | + def join(self, chan): |
698 | + self.raw('JOIN ' + chan) |
699 | + self.mode(chan, '+q ' + self.nickname) |
700 | + |
701 | + def kill(self, nick, reason): |
702 | + self.raw('KILL {0} {1}'.format(nick, reason)) |
703 | + |
704 | + def listen(self): |
705 | + while True: |
706 | + try: |
707 | + data = self.sock.recv(1024).decode('utf-8') |
708 | + if data: |
709 | + for line in (line for line in data.split('\r\n') if line): |
710 | + debug.irc(line) |
711 | + if line.startswith('ERROR :Closing Link:'): |
712 | + raise Exception('Connection has closed.') |
713 | + elif len(line.split()) >= 2: |
714 | + self.handle_events(line) |
715 | + else: |
716 | + debug.error('No data recieved from server.') |
717 | + break |
718 | + except (UnicodeDecodeError,UnicodeEncodeError): |
719 | + debug.error('Unicode error has occured.') |
720 | + except Exception as ex: |
721 | + debug.error('Unexpected error occured.', ex) |
722 | + break |
723 | + self.event_disconnect() |
724 | + |
725 | + def mode(self, target, mode): |
726 | + self.raw('MODE {0} {1}'.format(target, mode)) |
727 | + |
728 | + def oper(self, nick, password): |
729 | + self.raw('OPER {0} {1}'.format(nick, password)) |
730 | + |
731 | + def part(self, chan, msg): |
732 | + self.raw('PART {0} {1}'.format(chan, msg)) |
733 | + |
734 | + def raw(self, msg): |
735 | + self.sock.send(bytes(msg + '\r\n', 'utf-8')) |
736 | + |
737 | + def sajoin(self, nick, chan): |
738 | + self.raw('SAJOIN {0} {1}'.format(nick, chan)) |
739 | + |
740 | + def sendmsg(self, target, msg): |
741 | + self.raw('PRIVMSG {0} :{1}'.format(target, msg)) |
742 | + |
743 | + def who(self, flag, args): |
744 | + self.raw('who +{0} {1}'.format(flag, args)) |
745 | + |
746 | +IRCS = IRC() |
747 | diff --git a/ircs/data/.gitignore b/ircs/data/.gitignore |
748 | new file mode 100644 |
749 | index 0000000..b722e9e |
750 | --- /dev/null |
751 | +++ b/ircs/data/.gitignore |
752 | @@ -0,0 +1 @@ |
753 | +!.gitignore |
754 | diff --git a/ircs/ircs.py b/ircs/ircs.py |
755 | new file mode 100644 |
756 | index 0000000..0ec026f |
757 | --- /dev/null |
758 | +++ b/ircs/ircs.py |
759 | @@ -0,0 +1,20 @@ |
760 | +#!/usr/bin/env python |
761 | +# IRC Services (IRCS) - Developed by acidvegas in Python (https://acid.vegas/ircs) |
762 | +# ircs.py |
763 | + |
764 | +import os |
765 | +import sys |
766 | + |
767 | +sys.dont_write_bytecode = True |
768 | +os.chdir(sys.path[0] or '.') |
769 | +sys.path += ('core',) |
770 | + |
771 | +import debug |
772 | +import irc |
773 | + |
774 | +debug.info() |
775 | +if not debug.check_version(3): |
776 | + debug.error_exit('IRCS requires Python 3!') |
777 | +if debug.check_privileges(): |
778 | + debug.error_exit('Do not run IRCS as admin/root!') |
779 | +irc.IRCS.connect() |