←︎ random :: a2d36f8


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()