aboutsummaryrefslogtreecommitdiff
path: root/programs/enchat3.lua
blob: b66549e370cc1bf634b45e9de466ac2634d02ce1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
--[[
 Enchat 3.0
 Get with:
  wget https://github.com/LDDestroier/enchat/raw/master/enchat3.lua enchat3.lua

This is a stable release. You fool!
--]]

local scr_x, scr_y = term.getSize()
CHATBOX_SAFEMODE = nil

-- non-changable settings
enchat = {
	connectToSkynet = true,
	version = 3.0,
	isBeta = false,
	port = 11000,
	skynetPort = "enchat3-default",
	url = "https://github.com/LDDestroier/enchat/raw/master/enchat3.lua",
	betaurl = "https://github.com/LDDestroier/enchat/raw/beta/enchat3.lua",
	ignoreModem = false,
	dataDir = "/.enchat",
	useChatbox = false,
	disableChatboxWithRedstone = false,
}

-- changable settings
local enchatSettings = {	-- DEFAULT settings.
	animDiv = 4,			-- divisor of text animation speed (scrolling from left)
	doAnimate = true,		-- whether or not to animate text moving from left side of screen
	reverseScroll = false,	-- whether or not to make scrolling up really scroll down
	redrawDelay = 0.1,		-- delay between redrawing
	useSetVisible = false,	-- whether or not to use term.current().setVisible(), which has performance and flickering improvements
	pageKeySpeed = 8,		-- how far PageUP or PageDOWN should scroll
	doNotif = true,			-- whether or not to use oveerlay glasses for notifications, if possible
	doKrazy = true,			-- whether or not to add &k obfuscation
	useSkynet = true,		-- whether or not to use gollark's Skynet in addition to modem calls
	extraNewline = true,	-- adds an extra newline after every message since setting to true
	acceptPictoChat = true,	-- whether or not to allow tablular enchat input, which is what /picto uses
	noRepeatNames = true,	-- whether or not to display the username in two or more consecutive messages by the same user
}

-- colors for various elements
palette = {
	bg = colors.black,
	txt = colors.white,
	promptbg = colors.gray,
	prompttxt = colors.white,
	scrollMeter = colors.lightGray,
	chevron = colors.black,
	title = colors.lightGray,
	titlebg = colors.gray,
}

-- UI adjustments, used to emulate the appearance of other chat programs
UIconf = {
	promptY = 1,
	chevron = ">",
	chatlogTop = 1,
	title = "Enchat 3",
	doTitle = false,
	titleY = 1,
	nameDecolor = false,
	centerTitle = true,
	prefix = "<",
	suffix = "> "
}

-- Attempt to get some slight optimization through localizing basic functions.
local mathmax, mathmin, mathrandom = math.max, math.min, math.random
local termblit, termwrite = term.blit, term.write
local termsetCursorPos, termgetCursorPos, termsetCursorBlink = term.setCursorPos, term.getCursorPos, term.setCursorBlink
local termsetTextColor, termsetBackgroundColor = term.setTextColor, term.setBackgroundColor
local termgetTextColor, termgetBackgroundColor = term.getTextColor, term.getBackgroundColor
local termclear, termclearLine = term.clear, term.clearLine
local tableinsert, tableremove, tableconcat = table.insert, table.remove, table.concat
local textutilsserialize, textutilsunserialize = textutils.serialize, textutils.unserialize
local stringsub, stringgsub, stringrep = string.sub, string.gsub, string.rep
local unpack = unpack
-- This better do something.

local initcolors = {
	bg = termgetBackgroundColor(),
	txt = termgetTextColor()
}

local tArg = {...}

local yourName = tArg[1]
local encKey = tArg[2]

local setEncKey = function(newKey)
	encKey = newKey
end

local saveSettings = function()
	local file = fs.open(fs.combine(enchat.dataDir, "settings"), "w")
	file.write(
		textutilsserialize({
			enchatSettings = enchatSettings,
			palette = palette,
			UIconf = UIconf,
		})
	)
	file.close()
end

local loadSettings = function()
	local contents
	if not fs.exists(fs.combine(enchat.dataDir, "settings")) then
		saveSettings()
	end
	local file = fs.open(fs.combine(enchat.dataDir, "settings"), "r")
	contents = file.readAll()
	file.close()
	local newSettings = textutilsunserialize(contents)
	if newSettings then
		for k,v in pairs(newSettings.enchatSettings) do
			enchatSettings[k] = v
		end
		for k,v in pairs(newSettings.palette) do
			palette[k] = v
		end
		for k,v in pairs(newSettings.UIconf) do
			UIconf[k] = v
		end
	else
		saveSettings()
	end
end

local updateEnchat = function(doBeta)
	local pPath = shell.getRunningProgram()
	local h = http.get((doBeta or enchat.isBeta) and enchat.betaurl or enchat.url)
	if not h then
		return false, "Could not connect."
	else
		local content = h.readAll()
		local file = fs.open(pPath, "w")
		file.write(content)
		file.close()
		return true, "Updated!"
	end
end

-- disables chat screen updating
local pauseRendering = true

-- primarily for use when using the pallete command, hoh hoh
local colors_strnames = {
	["white"] = colors.white,
	["pearl"] = colors.white,
	["silver"] = colors.white,
	["aryan"] = colors.white,
	["#f0f0f0"] = colors.white,

	["orange"] = colors.orange,
	["carrot"] = colors.orange,
	["fuhrer"] = colors.orange,
	["pumpkin"] = colors.orange,
	["#f2b233"] = colors.orange,

	["magenta"] = colors.magenta,
	["hotpink"] = colors.magenta,
	["lightpurple"] = colors.magenta,
	["light purple"] = colors.magenta,
	["#e57fd8"] = colors.magenta,

	["lightblue"] = colors.lightBlue,
	["light blue"] = colors.lightBlue,
	["skyblue"] = colors.lightBlue,
	["#99b2f2"] = colors.lightBlue,

	["yellow"] = colors.yellow,
	["piss"] = colors.yellow,
	["pee"] = colors.yellow,
	["lemon"] = colors.yellow,
	["spongebob"] = colors.yellow,
	["cowardice"] = colors.yellow,
	["#dede6c"] = colors.yellow,

	["lime"] = colors.lime,
	["lightgreen"] = colors.lime,
	["light green"] = colors.lime,
	["slime"] = colors.lime,
	["radiation"] = colors.lime,
	["#7fcc19"] = colors.lime,

	["pink"] = colors.pink,
	["lightishred"] = colors.pink,
	["lightish red"] = colors.pink,
	["communist"] = colors.pink,
	["commie"] = colors.pink,
	["patrick"] = colors.pink,
	["#f2b2cc"] = colors.pink,

	["gray"] = colors.gray,
	["grey"] = colors.gray,
	["graey"] = colors.gray,
	["gunmetal"] = colors.gray,
	["#4c4c4c"] = colors.gray,

	["lightgray"] = colors.lightGray,
	["lightgrey"] = colors.lightGray,
	["light gray"] = colors.lightGray,
	["light grey"] = colors.lightGray,
	["#999999"] = colors.lightGray,

	["cyan"] = colors.cyan,
	["seawater"] = colors.cyan,
	["brine"] = colors.cyan,
	["#4c99b2"] = colors.cyan,

	["purple"] = colors.purple,
	["purble"] = colors.purple,
	["obsidian"] = colors.purple,
	["diviner"] = colors.purple,
	["#b266e5"] = colors.purple,

	["blue"] = colors.blue,
	["blu"] = colors.blue,
	["azure"] = colors.blue,
	["sapphire"] = colors.blue,
	["lapis"] = colors.blue,
	["volnutt"] = colors.blue,
	["blueberry"] = colors.blue,
	["x"] = colors.blue,
	["megaman"] = colors.blue,
	["#3366bb"] = colors.blue,

	["brown"] = colors.brown,
	["shit"] = colors.brown,
	["dirt"] = colors.brown,
	["mud"] = colors.brown,
	["bricks"] = colors.brown,
	["#7f664c"] = colors.brown,

	["green"] = colors.green,
	["grass"] = colors.green,
	["#57a64e"] = colors.green,

	["red"] = colors.red,
	["crimson"] = colors.red,
	["vermillion"] = colors.red,
	["menstration"] = colors.red,
	["blood"] = colors.red,
	["marinara"] = colors.red,
	["zero"] = colors.red,
	["protoman"] = colors.red,
	["communism"] = colors.red,
	["#cc4c4c"] = colors.red,

	["black"] = colors.black,
	["dark"] = colors.black,
	["darkness"] = colors.black,
	["space"] = colors.black,
	["coal"] = colors.black,
	["onyx"] = colors.black,
	["#191919"] = colors.black,
}

local toblit = {
	[0] = " ",
	[1] = "0",
	[2] = "1",
	[4] = "2",
	[8] = "3",
	[16] = "4",
	[32] = "5",
	[64] = "6",
	[128] = "7",
	[256] = "8",
	[512] = "9",
	[1024] = "a",
	[2048] = "b",
	[4096] = "c",
	[8192] = "d",
	[16384] = "e",
	[32768] = "f"
}
local tocolors = {}
for k,v in pairs(toblit) do
	tocolors[v] = k
end

local codeNames = {
	["r"] = "reset",		-- Sets either the text (&) or background (~) colors to their original color.
	["{"] = "stopFormatting",	-- Toggles formatting text off
	["}"] = "startFormatting",	-- Toggles formatting text on
	["k"] = "krazy"			-- Makes the font kuh-razy!
}

-- indicates which character should turn into which random &k character
local kraziez = {
	["l"] = {
		"!",
		"l",
		"1",
		"|",
		"i",
		"I",
		":",
		";",
	},
	["m"] = {
		"M",
		"W",
		"w",
		"m",
		"X",
		"N",
		"_",
		"%",
		"@",
	},
	["all"] = {}
}

for a = 1, #kraziez["l"] do
	kraziez[kraziez["l"][a]] = kraziez["l"]
end
for k,v in pairs(kraziez) do
	for a = 1, #v do
		kraziez[kraziez[k][a]] = v
	end
end

-- check if using older CC version, and omit special characters if it's too old to avoid crash
if tonumber(_CC_VERSION or 0) >= 1.76 then
	for a = 1, 255 do
		if (a ~= 32) and (a ~= 13) and (a ~= 10) then
			kraziez["all"][#kraziez["all"]+1] = string.char(a)
		end
	end
else
	for a = 33, 126 do
		kraziez["all"][#kraziez["all"]+1] = string.char(a)
	end
end

local makeRandomString = function(length, begin, stop)
	local output = ""
	for a = 1, length do
		output = output .. string.char(math.random(begin or 1, stop or 255))
	end
	return output
end

local personalID = makeRandomString(64, 32, 128)

local explode = function(div, str, replstr, includeDiv)
	if (div == '') then
		return false
	end
	local pos, arr = 0, {}
	for st, sp in function() return string.find(str, div, pos, false) end do
		tableinsert(arr, string.sub(replstr or str, pos, st - 1 + (includeDiv and #div or 0)))
		pos = sp + 1
	end
	tableinsert(arr, string.sub(replstr or str, pos))
	return arr
end

local parseKrazy = function(c)
	if kraziez[c] then
		return kraziez[c][mathrandom(1, #kraziez[c])]
	else
		return kraziez.all[mathrandom(1, #kraziez.all)]
	end
end

-- my main man, the function that turns unformatted strings into formatted strings
local textToBlit = function(input, onlyString, initText, initBack, checkPos, useJSONformat)
	if not input then return end
	checkPos = checkPos or -1
	initText, initBack = initText or toblit[term.getTextColor()], initBack or toblit[term.getBackgroundColor()]
	tcode, bcode = "&", "~"
	local cpos, cx = 0, 0
	local skip, ignore, ex = nil, false, nil
	local text, back, nex = initText, initBack, nil

	local charOut, textOut, backOut = {}, {}, {}
	local JSONoutput = {}

	local krazy = false
	local bold = false
	local strikethrough = false
	local underline = false
	local italic = false

	local codes = {}
	codes["r"] = function(prev)
		if not ignore then
			if prev == tcode then
				text = initText
				bold = false
				strikethrough = false
				underline = false
				italic = false
			elseif prev == bcode then
				if useJSONformat then
					return 0
				else
					back = initBack
				end
			end
			krazy = false
		else
			return 0
		end
	end
	codes["k"] = function(prev)
		if not ignore then
			krazy = not krazy
		else
			return 0
		end
	end
	codes["{"] = function(prev)
		if not ignore then
			ignore = true
		else
			return 0
		end
	end
	codes["}"] = function(prev)
		if ignore then
			ignore = false
		else
			return 0
		end
	end

	if useJSONformat then
		codes["l"] = function(prev)
			bold = true
		end
		codes["m"] = function(prev)
			strikethrough = true
		end
		codes["n"] = function(prev)
			underline = true
		end
		codes["o"] = function(prev)
			italic = true
		end
	end

	local sx, str = 0
	input = stringgsub(input, "(\\)(%d%d?%d?)", function(cap, val)
		if tonumber(val) < 256 then
			cpos = cpos - #val
			return string.char(val)
		else
			return cap..val
		end
	end)

	local MCcolors = {
		["0"] = "white",
		["1"] = "gold",
		["2"] = "light_purple",
		["3"] = "aqua",
		["4"] = "yellow",
		["5"] = "green",
		["6"] = "light_purple",
		["7"] = "dark_gray",
		["8"] = "gray",
		["9"] = "dark_aqua",
		["a"] = "dark_purple",
		["b"] = "dark_blue",
		["c"] = "gold",
		["d"] = "dark_green",
		["e"] = "red",
		["f"] = "black",
	}

	for cx = 1, #input do
		str = stringsub(input,cx,cx)
		if skip then
			if tocolors[str] and not ignore then
				if skip == tcode then
					text = str == " " and initText or str
					if sx < checkPos then
						cpos = cpos - 2
					end
				elseif skip == bcode then
					back = str == " " and initBack or str
					if sx < checkPos then
						cpos = cpos - 2
					end
				end
			elseif codes[str] and not (ignore and str == "{") then
				ex = codes[str](skip) or 0
				sx = sx + ex
    				if sx < checkPos then
					cpos = cpos - ex - 2
				end
			else
				sx = sx + 1
				if useJSONformat then
					JSONoutput[sx] = {
						text = (skip..str),
						color = onlyString and "f" or MCcolors[text],
						bold = (not onlyString) and bold,
						italic = (not onlyString) and italic,
						underline = (not onlyString) and underline,
						obfuscated = (not onlyString) and krazy,
						strikethrough = (not onlyString) and strikethrough
					}
				else
					charOut[sx] = krazy and parseKrazy(prev..str) or (skip..str)
					textOut[sx] = stringrep(text,2)
					backOut[sx] = stringrep(back,2)
				end
			end
			skip = nil
		else
			if (str == tcode or str == bcode) and (codes[stringsub(input, 1+cx, 1+cx)] or tocolors[stringsub(input,1+cx,1+cx)]) then
				skip = str
			else
				sx = sx + 1
				if useJSONformat then
					JSONoutput[sx] = {
						text = str,
						color = onlyString and "f" or MCcolors[text],
						bold = (not onlyString) and bold,
						italic = (not onlyString) and italic,
						underline = (not onlyString) and underline,
						obfuscated = (not onlyString) and krazy,
						strikethrough = (not onlyString) and strikethrough
					}
				else
					charOut[sx] = krazy and parseKrazy(str) or str
					textOut[sx] = text
					backOut[sx] = back
				end
			end
		end
	end
	if useJSONformat then
		return textutils.serializeJSON(JSONoutput)
	else
		if onlyString then
			return tableconcat(charOut), (checkPos > -1) and cpos or nil
		else
--			return {tableconcat(charOut), tableconcat(textOut):gsub(" ", initText), tableconcat(backOut):gsub(" ", initBack)}, (checkPos > -1) and cpos or nil
			return {tableconcat(charOut), tableconcat(textOut), tableconcat(backOut)}, (checkPos > -1) and cpos or nil
		end
	end
end
_G.textToBlit = textToBlit

-- convoluted read function that renders color codes as they are written.
-- struggles with \123 codes, but hey, fuck you
local colorRead = function(maxLength, _history)
	local output = ""
	local history, _history = {}, _history or {}
	for a = 1, #_history do
		history[a] = _history[a]
	end
	history[#history+1] = ""
	local hPos = #history
	local cx, cy = termgetCursorPos()
	local x, xscroll = 1, 1
	local ctrlDown = false
	termsetCursorBlink(true)
	local evt, key, bout, xmod, timtam
	while true do
		termsetCursorPos(cx, cy)
		bout, xmod = textToBlit(output, false, nil, nil, x)
		for a = 1, #bout do
			bout[a] = stringsub(bout[a], xscroll, xscroll + scr_x - cx)
		end
		termblit(unpack(bout))
		termwrite((" "):rep(scr_x - cx))
		termsetCursorPos(cx + x + xmod - xscroll, cy)
		evt = {os.pullEvent()}
		if evt[1] == "char" or evt[1] == "paste" then
			output = (output:sub(1, x-1)..evt[2]..output:sub(x)):sub(1, maxLength or -1)
			x = mathmin(x + #evt[2], #output+1)
		elseif evt[1] == "key" then
			key = evt[2]
			if key == keys.leftCtrl then
				ctrlDown = true
			elseif key == keys.left then
				x = mathmax(x - 1, 1)
			elseif key == keys.right then
				x = mathmin(x + 1, #output+1)
			elseif key == keys.backspace then
				if x > 1 then
					repeat
						output = output:sub(1,x-2)..output:sub(x)
						x = x - 1
					until output:sub(x-1,x-1) == " " or (not ctrlDown) or (x == 1)
				end
			elseif key == keys.delete then
				if x < #output+1 then
					repeat
						output = output:sub(1,x-1)..output:sub(x+1)
					until output:sub(x,x) == " " or (not ctrlDown) or (x == #output+1)
				end
			elseif key == keys.enter then
				termsetCursorBlink(false)
				return output
			elseif key == keys.home then
				x = 1
			elseif key == keys["end"] then
				x = #output+1
			elseif key == keys.up then
				if history[hPos-1] then
					hPos = hPos - 1
					output = history[hPos]
					x = #output+1
				end
			elseif key == keys.down then
				if history[hPos+1] then
					hPos = hPos + 1
					output = history[hPos]
					x = #output+1
				end
			end
		elseif evt[1] == "key_up" then
			if evt[2] == keys.leftCtrl then
				ctrlDown = false
			end
		end
		if hPos > 1 then
			history[hPos] = output
		end
		if x+cx-xscroll+xmod > scr_x then
			xscroll = x-(scr_x-cx)+xmod
		elseif x-xscroll+xmod < 0 then
			repeat
				xscroll = xscroll - 1
			until x-xscroll-xmod >= 0
		end
		xscroll = math.max(1, xscroll)
	end
end
_G.colorRead = colorRead

local checkValidName = function(_nayme)
	local nayme = textToBlit(_nayme,true)
	if type(nayme) ~= "string" then
		return false
	else
		return (#nayme >= 2 and #nayme <= 32 and nayme:gsub(" ","") ~= "")
	end
end

if tArg[1] == "update" then
	local res, message = updateEnchat(tArg[2] == "beta")
	return print(message)
end

local prettyClearScreen = function(start, stop)
	termsetTextColor(colors.lightGray)
	termsetBackgroundColor(colors.gray)
	if _VERSION then
		for y = start or 1, stop or scr_y do
			termsetCursorPos(1,y)
			if y == (start or 1) then
				termwrite(("\135"):rep(scr_x))
			elseif y == (stop or scr_y) then
				termsetTextColor(colors.gray)
				termsetBackgroundColor(colors.lightGray)
				termwrite(("\135"):rep(scr_x))
			else
				termclearLine()
			end
		end
	else
		termclear()
	end
end

local cwrite = function(text, y)
	local cx, cy = termgetCursorPos()
	termsetCursorPos((scr_x/2) - math.ceil(#text/2), y or cy)
	return write(text)
end

local prettyCenterWrite = function(text, y)
	local words = explode(" ", text, nil, true)
	local buff = ""
	local lines = 0
	for w = 1, #words do
		if #buff + #words[w] > scr_x then
			cwrite(buff, y + lines)
			buff = ""
			lines = lines + 1
		end
		buff = buff..words[w]
	end
	cwrite(buff, y + lines)
	return lines
end

local prettyPrompt = function(prompt, y, replchar, doColor)
	local cy, cx = termgetCursorPos()
	termsetBackgroundColor(colors.gray)
	termsetTextColor(colors.white)
	local yadj = 1 + prettyCenterWrite(prompt, y or cy)
	termsetCursorPos(1, y + yadj)
	termsetBackgroundColor(colors.lightGray)
	termclearLine()
	local output
	if doColor then
		output = colorRead()
	else
		output = read(replchar)
	end
	return output
end

local fwrite = function(text)
	local b = textToBlit(text)
	return termblit(unpack(b))
end

local cfwrite = function(text, y)
	local cx, cy = termgetCursorPos()
	termsetCursorPos((scr_x/2) - math.ceil(#textToBlit(text,true)/2), y or cy)
	return fwrite(text)
end

-- execution start!

if not checkValidName(yourName) then -- not so fast, evildoers
	yourName = nil
end

local currentY = 2

if not (yourName and encKey) then
	prettyClearScreen()
end

if not yourName then
    cfwrite("&8~7Text = &, Background = ~", scr_y-3)
	cfwrite("&8~7&{Krazy = &k, Reset = &r", scr_y-2)
    cfwrite("&7~00~11~22~33~44~55~66&8~77&7~88~99~aa~bb~cc~dd~ee~ff", scr_y-1)
	yourName = prettyPrompt("Enter your name.", currentY, nil, true)
	if not checkValidName(yourName) then
		while true do
			yourName = prettyPrompt("That name isn't valid. Enter another.", currentY, nil, true)
			if checkValidName(yourName) then
				break
			end
		end
	end
	currentY = currentY + 3
end

if not encKey then
	setEncKey(prettyPrompt("Enter an encryption key.", currentY, "*"))
	currentY = currentY + 3
end

-- prevents terminating. it is reversed upon exit.
local oldePullEvent = os.pullEvent
os.pullEvent = os.pullEventRaw

local bottomMessage = function(text)
	termsetCursorPos(1,scr_y)
	termsetTextColor(colors.gray)
	termclearLine()
	termwrite(text)
end

loadSettings()
saveSettings()

termsetBackgroundColor(colors.black)
termclear()

local getAPI = function(apiname, apipath, apiurl, doDoFile, doScroll)
	apipath = fs.combine(fs.combine(enchat.dataDir,"api"), apipath)
	if (not fs.exists(apipath)) then
		if doScroll then term.scroll(1) end
		bottomMessage(apiname .. " API not found! Downloading...")
		local prog = http.get(apiurl)
		if not prog then
			if doScroll then term.scroll(1) end
			bottomMessage("Failed to download " .. apiname .. " API. Abort.")
			termsetCursorPos(1,1)
			return
		end
		local file = fs.open(apipath,"w")
		file.write(prog.readAll())
		file.close()
	end
	if doDoFile then
		return dofile(apipath)
	else
		os.loadAPI(apipath)
	end
	if not _ENV[fs.getName(apipath)] then
		if doScroll then term.scroll(1) end
		bottomMessage("Failed to load " .. apiname .. " API. Abort.")
		termsetCursorPos(1,1)
		return
	else
		return _ENV[fs.getName(apipath)]
	end
end

local skynet, aes, bigfont
-- _G.skynet_CBOR_path = fs.combine(enchat.dataDir,"/api/cbor")
aes = getAPI("AES", "aes", "http://pastebin.com/raw/9E5UHiqv", false, false)
if enchat.connectToSkynet and http.websocket then
	skynet = getAPI("Skynet", "skynet", "https://raw.githubusercontent.com/LDDestroier/CC/master/API/skynet.lua", true, true)
end
bigfont = getAPI("BigFont", "bigfont", "https://pastebin.com/raw/3LfWxRWh", false, true)

if encKey and skynet and enchat.connectToSkynet then
	bottomMessage("Connecting to Skynet...")
	local success = parallel.waitForAny(
		function()
			skynet.open(enchat.skynetPort)
		end,
		function()
			sleep(3)
		end
	)
	if success == 2 then
		term.scroll(1)
		bottomMessage("Failed to connect to skynet.")
		skynet = nil
		sleep(0.5)
	end
end

local log = {} 			-- Records all sorts of data on text.
local renderlog = {} 	-- Only records straight terminal output. Generated from 'log'
local IDlog = {} 		-- Really only used with skynet, will prevent duplicate messages.

local scroll = 0
local maxScroll = 0

local getModem = function()
	if enchat.ignoreModem then
		return nil
	else
		local modems = {peripheral.find("modem")}
		return modems[1]
	end
end

local getChatbox = function()
	if enchat.useChatbox then
		if commands then -- oh baby, a command computer, now we're talkin'
			-- mind you, you still need a chatbox to get chat input...
			return {
				say = function(text)
					commands.tellraw("@a", textToBlit(text, false, "0", "f", nil, true))
				end,
				tell = function(player, text)
					commands.tellraw(player, textToBlit(text, false, "0", "f", nil, true))
				end
			}
		else
			local cb = chatbox or peripheral.find("chat_box")
			if cb then
				if cb.setName then -- Computronics
					cb.setName(yourName)
					return {
						say = cb.say,
						tell = cb.say -- why is there no tell command???
					}
				else -- whatever whackjob mod SwitchCraft uses I forget
					return {
						say = function(text, block)
							if CHATBOX_SAFEMODE then
--								if CHATBOX_SAFEMODE ~= block then
									cb.tell(CHATBOX_SAFEMODE, text)
--								end
							else
								local players = cb.getPlayerList()
								for i = 1, #players do
									if players[i] ~= block then
										cb.tell(players[i], text)
									end
								end
							end
						end,
						tell = cb.tell
					}
				end
			else
				return nil
			end
		end
	else
		return nil
	end
end

local modem = getModem()
local chatbox = getChatbox()

if (not modem) and (not enchat.ignoreModem) then
	if ccemux and (not enchat.ignoreModem) then
		ccemux.attach("top", "wireless_modem")
		modem = getModem()
	elseif not skynet then
		error("You should get a modem.")
	end
end

if modem then modem.open(enchat.port) end

local modemTransmit = function(freq, repfreq, message)
	if modem then
		modem.transmit(freq, repfreq, message)
	end
end

local encrite = function(input) -- standardized encryption function
	if not input then return input end
	return aes.encrypt(encKey, textutilsserialize(input))
end

local decrite = function(input) -- redundant comments cause tuberculosis
	if not input then return input end
	return textutilsunserialize(aes.decrypt(encKey, input) or "")
end

local dab = function(func, ...) -- "do and back", not...never mind
	local x, y = termgetCursorPos()
	local b, t = termgetBackgroundColor(), termgetTextColor()
	local output = {func(...)}
	termsetCursorPos(x,y)
	termsetTextColor(t)
	termsetBackgroundColor(b)
	return unpack(output)
end

local splitStr = function(str, maxLength)
	local output = {}
	for l = 1, #str, maxLength do
		output[#output+1] = str:sub(l,l+maxLength+-1)
	end
	return output
end

local splitStrTbl = function(tbl, maxLength)
	local output, tline = {}
	for w = 1, #tbl do
		tline = splitStr(tbl[w], maxLength)
		for t = 1, #tline do
			output[#output+1] = tline[t]
		end
	end
	return output
end

-- same as term.blit, but wraps by-word.
local blitWrap = function(char, text, back, noWrite) -- where ALL of the onscreen wrapping is done
	local cWords = splitStrTbl(explode(" ",char,nil, true), scr_x)
	local tWords = splitStrTbl(explode(" ",char,text,true), scr_x)
	local bWords = splitStrTbl(explode(" ",char,back,true), scr_x)

	local ox,oy = termgetCursorPos()
	local cx,cy,ty = ox,oy,1
	local output = {}
	local length = 0
	local maxLength = 0
	for a = 1, #cWords do
		length = length + #cWords[a]
		maxLength = mathmax(maxLength, length)
		if ((cx + #cWords[a]) > scr_x) then
			cx = 1
			length = 0
			if (cy == scr_y) then
				term.scroll(1)
			end
			cy = mathmin(cy+1, scr_y)
			ty = ty + 1
		end
		if not noWrite then
			termsetCursorPos(cx,cy)
			termblit(cWords[a],tWords[a],bWords[a])
		end
		cx = cx + #cWords[a]
		output[ty] = output[ty] or {"","",""}
		output[ty][1] = output[ty][1]..cWords[a]
		output[ty][2] = output[ty][2]..tWords[a]
		output[ty][3] = output[ty][3]..bWords[a]
	end
	return output, maxLength
end

-- simple picture drawing function, for /picto
local pictochat = function(xsize, ysize)
	local output = {{},{},{}}
	local maxWidth, minMargin = 0, math.huge
	for y = 1, ysize do
		output[1][y] = {}
		output[2][y] = {}
		output[3][y] = {}
		for x = 1, xsize do
			output[1][y][x] = " "
			output[2][y][x] = " "
			output[3][y][x] = " "
		end
	end

	termsetBackgroundColor(colors.gray)
	termsetTextColor(colors.black)
	for y = 1, scr_y do
		termsetCursorPos(1, y)
		termwrite(("/"):rep(scr_x))
	end
	cwrite(" [ENTER] to finish. ", scr_y)
	cwrite("Push a key to change char.", scr_y-1)

	local cx, cy = math.floor((scr_x/2)-(xsize/2)), math.floor((scr_y/2)-(ysize/2))

	local allCols = "0123456789abcdef"
	local tPos, bPos = 16, 1
	local char, text, back = " ", allCols:sub(tPos,tPos), allCols:sub(bPos,bPos)

	local render = function()
		termsetTextColor(colors.white)
		termsetBackgroundColor(colors.black)
		local mx, my
		for y = 1, ysize do
			for x = 1, xsize do
				mx, my = x+cx+-1, y+cy+-1
				termsetCursorPos(mx,my)
				termblit(output[1][y][x], output[2][y][x], output[3][y][x])
			end
		end
		termsetCursorPos((scr_x/2)-5,ysize+cy+1)
		termwrite("Char = '")
		termblit(char, text, back)
		termwrite("'")
	end
	local evt, butt, mx, my
	local isShiftDown = false

	render()

	while true do
		evt = {os.pullEvent()}
		if evt[1] == "mouse_click" or evt[1] == "mouse_drag" then
			butt, mx, my = evt[2], evt[3]-cx+1, evt[4]-cy+1
			if mx >= 1 and mx <= xsize and my >= 1 and my <= ysize then
				if butt == 1 then
					output[1][my][mx] = char
					output[2][my][mx] = text
					output[3][my][mx] = back
				elseif butt == 2 then
					output[1][my][mx] = " "
					output[2][my][mx] = " "
					output[3][my][mx] = " "
				end
				render()
			end
		elseif evt[1] == "mouse_scroll" then
			local oldTpos, oldBpos = tPos, bPos
			if isShiftDown then
				tPos = mathmax(1, mathmin(16, tPos + evt[2]))
			else
				bPos = mathmax(1, mathmin(16, bPos + evt[2]))
			end
			text, back = stringsub(allCols,tPos,tPos), stringsub(allCols,bPos,bPos)
			if oldTpos ~= tPos or oldBpos ~= bPos then
				render()
			end
		elseif evt[1] == "key" then
			if evt[2] == keys.enter then
				for y = 1, ysize do
					output[1][y] = table.concat(output[1][y])
					output[2][y] = table.concat(output[2][y])
					output[3][y] = table.concat(output[3][y])
					maxWidth  = math.max(maxWidth,  #stringgsub(output[3][y], " +$", ""))
					minMargin = math.min(minMargin, output[3][y]:find("[^ ]") or math.huge)
				end
				--error(minMargin)
				local croppedOutput = {}
				local touched = false
				local crY = 0
				for a = 1, ysize do
					if output[1][1] == (" "):rep(xsize) and output[3][1] == (" "):rep(xsize) then
						tableremove(output[1],1)
						tableremove(output[2],1)
						tableremove(output[3],1)
					else
						for y = #output[1], 1, -1 do
							if output[1][y] == (" "):rep(xsize) and output[3][y] == (" "):rep(xsize) then
								tableremove(output[1],y)
								tableremove(output[2],y)
								tableremove(output[3],y)
							else
								break
							end
						end
						break
					end
				end
				for y = 1, #output[1] do
					output[1][y] = output[1][y]:sub(minMargin, maxWidth)
					output[2][y] = output[2][y]:sub(minMargin, maxWidth)
					output[3][y] = output[3][y]:sub(minMargin, maxWidth)
				end
				return output
			elseif evt[2] == keys.leftShift then
				isShiftDown = true
			elseif evt[2] == keys.left or evt[2] == keys.right then
				local oldTpos, oldBpos = tPos, bPos
				if isShiftDown then
					tPos = mathmax(1, mathmin(16, tPos + (evt[2] == keys.right and 1 or -1)))
				else
					bPos = mathmax(1, mathmin(16, bPos + (evt[2] == keys.right and 1 or -1)))
				end
				text, back = allCols:sub(tPos,tPos), allCols:sub(bPos,bPos)
				if oldTpos ~= tPos or oldBpos ~= bPos then
					render()
				end
			end
		elseif evt[1] == "key_up" then
			if evt[2] == keys.leftShift then
				isShiftDown = false
			end
		elseif evt[1] == "char" then
			if char ~= evt[2] then
				char = evt[2]
				render()
			end
		end
	end
end

-- notifications will only appear if you have plethora's neural connector and overlay glasses on your person

local notif = {}
notif.alpha = 248
notif.height = 10
notif.width = 6
notif.time = 40
notif.wrapX = 350
notif.maxNotifs = 15
local nList = {}
local colorTranslate = {
	[" "] = {240, 240, 240},
	["0"] = {240, 240, 240},
	["1"] = {242, 178, 51 },
	["2"] = {229, 127, 216},
	["3"] = {153, 178, 242},
	["4"] = {222, 222, 108},
	["5"] = {127, 204, 25 },
	["6"] = {242, 178, 204},
	["7"] = {76,  76,  76 },
	["8"] = {153, 153, 153},
	["9"] = {76,  153, 178},
	["a"] = {178, 102, 229},
	["b"] = {51,  102, 204},
	["c"] = {127, 102, 76 },
	["d"] = {87,  166, 78 },
	["e"] = {204, 76,  76 },
	["f"] = {25,  25,  25 }
}
local interface, canvas = peripheral.find("neuralInterface")
if interface then
	if interface.canvas then
		canvas = interface.canvas()
		notif.newNotification = function(char, text, back, time)
			if #nList > notif.maxNotifs then
				tableremove(nList, 1)
			end
			nList[#nList+1] = {char,text,back,time,1} -- the last one is the alpha multiplier
		end
		notif.displayNotifications = function(doCountDown)
			local adjList = {
				["i"] = -4,
				["l"] = -3,
				["I"] = -1,
				["t"] = -2,
				["k"] = -1,
				["!"] = -4,
				["|"] = -4,
				["."] = -4,
				[","] = -4,
				[":"] = -4,
				[";"] = -4,
				["f"] = -1,
				["'"] = -3,
				["\""] = -1,
				["<"] = -1,
				[">"] = -1,
			}
			local drawEdgeLine = function(y,alpha)
				local l = canvas.addRectangle(notif.wrapX, 1+(y-1)*notif.height, 1, notif.height)
				l.setColor(unpack(colorTranslate["0"]))
				l.setAlpha(alpha / 2)
			end
			local getWordWidth = function(str)
				local output = 0
				for a = 1, #str do
					output = output + notif.width + (adjList[stringsub(str,a,a)] or 0)
				end
				return output
			end
			canvas.clear()
			local xadj, charadj, wordadj, t, r
			local x, y, words, txtwords, bgwords = 0, 0
			for n = 1, mathmin(#nList, notif.maxNotifs) do
				xadj, charadj = 0, 0
				y = y + 1
				x = 0
				words = explode(" ",nList[n][1],nil,true)
				txtwords = explode(" ",nList[n][1],nList[n][2],true)
				bgwords = explode(" ",nList[n][1],nList[n][3],true)
				local char, text, back
				local currentX = 0
				for w = 1, #words do
					char = words[w]
					text = txtwords[w]
					back = bgwords[w]
					if currentX + getWordWidth(char) > notif.wrapX then
						y = y + 1
						x = 2
						xadj = 0
						currentX = x * notif.width
					end
					for cx = 1, #char do
						x = x + 1
						charadj = (adjList[stringsub(char,cx,cx)] or 0)
						r = canvas.addRectangle(xadj+1+(x-1)*notif.width, 1+(y-1)*notif.height, charadj+notif.width, notif.height)
						if stringsub(back,cx,cx) ~= " " then
							r.setAlpha(notif.alpha * nList[n][5])
							r.setColor(unpack(colorTranslate[stringsub(back,cx,cx)]))
						else
							r.setAlpha(100 * nList[n][5])
							r.setColor(unpack(colorTranslate["7"]))
						end
						drawEdgeLine(y,notif.alpha * nList[n][5])
						t = canvas.addText({xadj+1+(x-1)*notif.width,2+(y-1)*notif.height}, stringsub(char,cx,cx))
						t.setAlpha(notif.alpha * nList[n][5])
						t.setColor(unpack(colorTranslate[stringsub(text,cx,cx)]))
						xadj = xadj + charadj
						currentX = currentX + charadj+notif.width
					end
				end
			end
			for n = mathmin(#nList, notif.maxNotifs), 1, -1 do
				if doCountDown then
					if nList[n][4] > 1 then
						nList[n][4] = nList[n][4] - 1
					else
						if nList[n][5] > 0 then
							while true do
								nList[n][5] = mathmax(nList[n][5] - 0.2, 0)
								notif.displayNotifications(false)
								if nList[n][5] == 0 then break else sleep(0.05) end
							end
						end
						tableremove(nList,n)
					end
				end
			end
		end
	end
end

local darkerCols = {
	["0"] = "8",
	["1"] = "c",
	["2"] = "a",
	["3"] = "b",
	["4"] = "1",
	["5"] = "d",
	["6"] = "2",
	["7"] = "f",
	["8"] = "7",
	["9"] = "b",
	["a"] = "7",
	["b"] = "7",
	["c"] = "f",
	["d"] = "7",
	["e"] = "7",
	["f"] = "f"
}

-- used for regular chat. they can be disabled if you hate fun
local animations = {
	slideFromLeft = function(char, text, back, frame, maxFrame, length)
		return {
			stringsub(char, (length or #char) - ((frame/maxFrame)*(length or #char))),
			stringsub(text, (length or #text) - ((frame/maxFrame)*(length or #text))),
			stringsub(back, (length or #back) - ((frame/maxFrame)*(length or #back)))
		}
	end,
	fadeIn = function(char, text, back, frame, maxFrame, length)
		-- a good example:
		-- &1what &2in &3the &4world &5are &6you &7doing &8in &9my &aswamp
		for i = 1, 3 - math.ceil(frame/maxFrame * 3) do
			text = stringgsub(text, ".", darkerCols)
		end
		return {
			char,
			text,
			back
		}
	end,
	flash = function(char, text, back, frame, maxFrame, length)
		local t = palette.txt
		if frame ~= maxFrame then
			t = (frame % 2 == 0) and t or palette.bg
		end
		return {
			char,
			toblit[t]:rep(#text),
			(frame % 2 == 0) and back or (" "):rep(#back)
		}
	end,
	none = function(char, text, back, frame, maxFrame, length)
		return {
			char,
			text,
			back
		}
	end
}

local inAnimate = function(animType, buff, frame, maxFrame, length)
	local char, text, back = buff[1], buff[2], buff[3]
	if enchatSettings.doAnimate and (frame >= 0) and (maxFrame > 0) then
		return animations[animType or "slideFromleft"](char, text, back, frame, maxFrame, length)
	else
		return {char,text,back}
	end
end

local genRenderLog = function()
	local buff, prebuff, maxLength, lastUser
	local scrollToBottom = scroll == maxScroll
	renderlog = {}
	local dcName, dcMessage
	for a = 1, #log do
		if not ((lastUser == log[a].personalID and log[a].personalID) and log[a].name == "" and log[a].message == " ") then
			termsetCursorPos(1,1)
			if UIconf.nameDecolor then
				if lastUser == log[a].personalID and log[a].personalID then
					dcName = ""
				else
					dcName = textToBlit(table.concat({log[a].prefix,log[a].name,log[a].suffix}), true, toblit[palette.txt], toblit[palette.bg])
				end
				dcMessage = textToBlit(log[a].message, false, toblit[palette.txt], toblit[palette.bg])
				prebuff = {
					dcName..dcMessage[1],
					toblit[palette.chevron]:rep(#dcName)..dcMessage[2],
					toblit[palette.bg]:rep(#dcName)..dcMessage[3]
				}
			else
				if lastUser == log[a].personalID and log[a].personalID then
					prebuff = textToBlit(" " .. log[a].message, false, toblit[palette.txt], toblit[palette.bg])
				else
					prebuff = textToBlit(table.concat({
						log[a].prefix,
						"&}&r~r",
						log[a].name,
						"&}&r~r",
						log[a].suffix,
						"&}&r~r",
						log[a].message
					}),
					false, toblit[palette.txt], toblit[palette.bg])
				end
			end
			if log[a].message ~= " " and enchatSettings.noRepeatNames then
				lastUser = log[a].personalID
			end
			if (log[a].frame == 0) and (canvas and enchatSettings.doNotif) then
				if not (log[a].name == "" and log[a].message == " ") then
					notif.newNotification(prebuff[1], prebuff[2], prebuff[3], notif.time * 4)
				end
			end
			if log[a].maxFrame == true then
				log[a].maxFrame = math.floor(mathmin(#prebuff[1], scr_x) / enchatSettings.animDiv)
			end
			if log[a].ignoreWrap then
				buff, maxLength = {prebuff}, mathmin(#prebuff[1], scr_x)
			else
				buff, maxLength = blitWrap(prebuff[1], prebuff[2], prebuff[3], true)
			end
			-- repeat every line in multiline entries
			for l = 1, #buff do
				-- holy shit, two animations, lookit mr. roxas over here
				if log[a].animType then
					renderlog[#renderlog + 1] = inAnimate(log[a].animType, buff[l], log[a].frame, log[a].maxFrame, maxLength)
				else
					renderlog[#renderlog + 1] = inAnimate("fadeIn", inAnimate("slideFromLeft", buff[l], log[a].frame, log[a].maxFrame, maxLength), log[a].frame, log[a].maxFrame, maxLength)
				end
			end
			if (log[a].frame < log[a].maxFrame) and log[a].frame >= 0 then
				log[a].frame = log[a].frame + 1
			else
				log[a].frame = -1
			end
		end
	end
	maxScroll = mathmax(0, #renderlog - (scr_y - 2))
	if scrollToBottom then
		scroll = maxScroll
	end
end

-- there is probably a much better way of doing this, but I don't care at the moment
local tsv = function(visible)
	if term.current().setVisible and enchatSettings.useSetVisible then
		return term.current().setVisible(visible)
	end
end

local renderChat = function(doScrollBackUp)
	tsv(false)
	termsetCursorBlink(false)
	genRenderLog(log)
	local ry
	termsetBackgroundColor(palette.bg)
	for y = UIconf.chatlogTop, (scr_y-UIconf.promptY) - 1 do
		ry = (y + scroll - (UIconf.chatlogTop - 1))
		termsetCursorPos(1,y)
		termclearLine()
		if renderlog[ry] then
			termblit(unpack(renderlog[ry]))
		end
	end
	if UIconf.promptY ~= 0 then
		termsetCursorPos(1,scr_y)
		termsetTextColor(palette.scrollMeter)
		termclearLine()
		termwrite(scroll.." / "..maxScroll.."  ")
	end

	local _title = UIconf.title:gsub("YOURNAME", yourName.."&}&r~r"):gsub("ENCKEY", encKey.."&}&r~r"):gsub("PORT", tostring(enchat.port))
	if UIconf.doTitle then
		termsetTextColor(palette.title)
		term.setBackgroundColor(palette.titlebg)
		if UIconf.nameDecolor then
			if UIconf.centerTitle then
				cwrite((" "):rep(scr_x)..textToBlit(_title, true)..(" "):rep(scr_x), UIconf.titleY or 1)
			else
				termsetCursorPos(1, UIconf.titleY or 1)
				termwrite(textToBlit(_title, true)..(" "):rep(scr_x))
			end
		else
			local blTitle = textToBlit(_title)
			termsetCursorPos(UIconf.centerTitle and ((scr_x/2) - math.ceil(#blTitle[1]/2)) or 1, UIconf.titleY or 1)
			termclearLine()
			termblit(unpack(blTitle))
		end
	end
	termsetCursorBlink(true)
	tsv(true)
end

local logadd = function(name, message, animType, maxFrame, ignoreWrap, _personalID)
	log[#log + 1] = {
		prefix = name and UIconf.prefix or "",
		suffix = name and UIconf.suffix or "",
		name = name or "",
		message = message or " ",
		ignoreWrap = ignoreWrap,
		frame = 0,
		maxFrame = maxFrame or true,
		animType = animType,
		personalID = _personalID
	}
end

local logaddTable = function(name, message, animType, maxFrame, ignoreWrap, _personalID)
	if type(message) == "table" and type(name) == "string" then
		if #message > 0 then
			local isGood = true
			for l = 1, #message do
				if type(message[l]) ~= "string" then
					isGood = false
					break
				end
			end
			if isGood then
				logadd(name, message[1], animType, maxFrame, ignoreWrap, _personalID)
				for l = 2, #message do
					logadd(nil, message[l], animType, maxFrame, ignoreWrap, _personalID)
				end
			end
		end
	end
end

local enchatSend = function(name, message, option, doLog, animType, maxFrame, crying, recipient, ignoreWrap, omitPersonalID)
	option = option or {}
	if option.doLog then
		if type(message) == "string" then
			logadd(name, message, option.animType, option.maxFrame, option.ignoreWrap, (not option.omitPersonalID) and personalID)
		else
			logaddTable(name, message, option.animType, option.maxFrame, option.ignoreWrap, (not option.omitPersonalID) and personalID)
		end
	end
	local messageID = makeRandomString(64)
	local outmsg = encrite({
		name = name,
		message = message,
		animType = option.animType,
		maxFrame = option.maxFrame,
		messageID = messageID,
		recipient = option.recipient,
		ignoreWrap = option.ignoreWrap,
		personalID = (not option.omitPersonalID) and personalID,
		cry = option.crying,
		simCommand = option.simCommand,
		simArgument = option.simArgument,
	})
	IDlog[messageID] = true
	if not enchat.ignoreModem then
		modemTransmit(enchat.port, enchat.port, outmsg)
	end
	if skynet and enchatSettings.useSkynet then
		skynet.send(enchat.skynetPort, outmsg)
	end
end

local cryOut = function(name, crying)
	enchatSend(name, nil, {crying = crying})
end

local getPictureFile = function(path) -- ONLY NFP or NFT, fuck BLT
	if not fs.exists(path) then
		return false, "No such image."
	else
		local file = fs.open(path,"r")
		local content = file.readAll()
		file.close()
		local output
		if content:find("\31") and content:find("\30") then
			output = explode("\n",content:gsub("\31","&"):gsub("\30","~"),nil,false)
		else
			if content:lower():gsub("[0123456789abcdef\n ]","") ~= "" then
				return false, "Invalid image."
			else
				output = explode("\n",content:gsub("[^\n]","~%1 "),nil,false)
			end
		end
		return output
	end
end

local getTableLength = function(tbl)
	local output = 0
	for k,v in pairs(tbl) do
		output = output + 1
	end
	return output
end

local userCryList = {}

local commandInit = "/"
local commands = {}
local simmableCommands = {
	big = true
}
-- Commands only have one argument, being a single string.
-- Separate arguments can be extrapolated with the explode() function.
commands.about = function()
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	logadd(nil,"Enchat "..enchat.version.." by LDDestroier.")
	logadd(nil,"'Encrypted, decentralized, &1c&2o&3l&4o&5r&6i&7z&8e&9d&r chat program'")
	logadd(nil,"Made in 2018, out of gum and procrastination.")
	logadd(nil,nil)
	logadd(nil,"AES Lua implementation made by SquidDev.")
	logadd(nil,"'Skynet' (enables HTTP chat) belongs to gollark (osmarks).")
end
commands.exit = function()
	enchatSend("*", "'"..yourName.."&}&r~r' buggered off. (disconnect)")
	return "exit"
end
commands.me = function(msg)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if msg then
		enchatSend("&2*", yourName.."~r&2 "..msg, {doLog = true})
	else
		logadd("*",commandInit.."me [message]")
	end
end
commands.tron = function()
  local url = "https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua"
  local prog, contents = http.get(url)
  if prog then
    enchatSend("*", yourName .. "&}&r~r has started a game of TRON.", {doLog = true})
    contents = prog.readAll()
    pauseRendering = true
    prog = load(contents, nil, nil, _ENV)(enchatSettings.useSkynet and "skynet", "quick", yourName)
  else
    logadd("*", "Could not download TRON.")
  end
  pauseRendering = false
  doRender = true
end
commands.colors = function()
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	logadd("*", "&{Color codes: (use & or ~)&}")
	logadd(nil, " &7~11~22~33~44~55~66~7&87~8&78~99~aa~bb~cc~dd~ee~ff")
	logadd(nil, " &{Reset text/BG with &r and ~r.&}")
	logadd(nil, " &{Use &k for krazy text.&}")
end
commands.update = function()
	local res, message = updateEnchat()
	if res then
		enchatSend("*", yourName.."&}&r~r has updated and exited.")
		termsetBackgroundColor(colors.black)
		termsetTextColor(colors.white)
		termclear()
		termsetCursorPos(1,1)
		print(message)
		return "exit"
	else
		logadd("*", res)
	end
end
commands.picto = function(filename)
	local image, output, res
	local isEmpty
	if filename then
		output, res = getPictureFile(filename)
		if not output then
			logadd("*",res)
			logadd(nil,nil)
			return
		else
			tableinsert(output,1,"")
		end
	else
		isEmpty = true
		output = {""}
		pauseRendering = true
		local image = pictochat(26,11)
		pauseRendering = false
		for y = 1, #image[1] do
			output[#output+1] = ""
			for x = 1, #image[1][1] do
				output[#output] = table.concat({
					output[#output],
					"&",
					image[2][y]:sub(x,x),
					"~",
					image[3][y]:sub(x,x),
					image[1][y]:sub(x,x)
				})
				isEmpty = isEmpty and (image[1][y]:sub(x,x) == " " and image[3][y]:sub(x,x) == " ")
			end
		end
	end
	if not isEmpty then
		enchatSend(yourName, output, {doLog = true, animType = "slideFromLeft", ignoreWrap = true})
	end
end
commands.list = function()
	userCryList = {}
	local tim = os.startTimer(0.5)
	cryOut(yourName, true)
	while true do
		local evt = {os.pullEvent()}
		if evt[1] == "timer" then
			if evt[2] == tim then
				break
			end
		end
	end
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if getTableLength(userCryList) == 0 then
		logadd(nil,"Nobody's there.")
	else
		for k,v in pairs(userCryList) do
			logadd(nil,"+'"..k.."'")
		end
	end
end
commands.nick = function(newName)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if newName then
		if checkValidName(newName) then
			if newName == yourName then
				logadd("*","But you're already called that!")
			else
				enchatSend("*", "'"..yourName.."&}&r~r' is now known as '"..newName.."&}&r~r'.", {doLog = true})
				yourName = newName
			end
		else
			if #newName < 2 then
				logadd("*", "That name is too damned small.")
			elseif #newName > 32 then
				logadd("*", "Woah there, that name is too large.")
			end
		end
	else
		logadd("*",commandInit.."nick [newName]")
	end
end
commands.whoami = function(now)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if now == "now" then
		logadd("*","You are still '"..yourName.."&}&r~r'!")
	else
		logadd("*","You are '"..yourName.."&}&r~r'!")
	end
end
commands.key = function(newKey)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if newKey then
		if newKey ~= encKey then
			enchatSend("*", "'"..yourName.."&}&r~r' buggered off. (keychange)")
			setEncKey(newKey)
			logadd("*", "Key changed to '"..encKey.."&}&r~r'.")
			enchatSend("*", "'"..yourName.."&}&r~r' has moseyed on over.", {omitPersonalID = true})
		else
			logadd("*", "That's already the key, though.")
		end
	else
		logadd("*","Key = '"..encKey.."&}&r~r'")
		logadd("*","Channel = '"..enchat.port.."'")
	end
end
commands.shrug = function(face)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	enchatSend(yourName, \\_"..(face and ("("..face..")") or "\2").."_/¯", {doLog = true})
end
commands.asay = function(_argument)
	local sPoint = (_argument or ""):find(" ")
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if not sPoint then
		logadd("*","Animation types:")
		for k,v in pairs(animations) do
			logadd(nil," '"..k.."'")
		end
	else
		local animType = _argument:sub(1,sPoint-1)
		local message = _argument:sub(sPoint+1)
		local animFrameMod = {
			flash = 8,
			fadeIn = 4,
		}
		if animations[animType] then
			if textToBlit(message,true):gsub(" ","") ~= "" then
				enchatSend(yourName, message, {doLog = true, animType = animType, maxFrame = animFrameMod[animType]})
			else
				logadd("*","That message is no good.")
			end
		else
			logadd("*","Invalid animation type.")
		end
	end
end
commands.big = function(_argument, simUser)
	local sPoint = (_argument or ""):find(" ")
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if not sPoint then
		logadd("*",commandInit .. "big <size> <text>")
	else
		local fontSize = tonumber(_argument:sub(1,sPoint-1))
		local message = _argument:sub(sPoint+1)
		if not fontSize then
			logadd("*","Size must be number between 0 and 2.")
		elseif fontSize < 0 or fontSize > 2 then
			logadd("*","Size must be number between 0 and 2.")
		else
			fontSize = math.floor(.5+fontSize)
			local tOutput
			if fontSize > 0 then
				message = textToBlit(message, false, "0", "f")
				local output = {{},{},{}}
				local x, y = 1, 1
				local char
				for i = 1, #message[1] do
					char = bigfont.makeBlittleText(
						fontSize,
						stringsub(message[1],i,i),
						stringsub(message[2],i,i),
						stringsub(message[3],i,i)
					)
					x = x + char.width
					if x >= scr_x then
						y = y + char.height
						x = char.width
					end
					for charY = 1, char.height do
						output[1][y+charY-1] = (output[1][y+charY-1] or " ") .. char[1][charY]
						output[2][y+charY-1] = (output[2][y+charY-1] or " ") .. char[2][charY]
						output[3][y+charY-1] = (output[3][y+charY-1] or " ") .. char[3][charY]
					end
				end
				tOutput = {""}
				local yy = 1
				for y = 1, #output[1] do
					tOutput[#tOutput+1] = ""
					for x = 1, #output[1][y] do
						tOutput[#tOutput] = table.concat({tOutput[#tOutput],"&",output[2][yy]:sub(x,x),"~",output[3][yy]:sub(x,x),output[1][yy]:sub(x,x)})
					end
					yy = yy + 1
				end
			else
				tOutput = message
			end
			if simUser then
				logaddTable(simUser, tOutput)
			else
				logaddTable(yourName, tOutput)
				enchatSend(yourName, nil, {simCommand = "big", simArgument = _argument})
			end
		end
	end
end
commands.msg = function(_argument)
	local sPoint = (_argument or ""):find(" ")
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if not sPoint then
		logadd("*",commandInit.."msg <recipient> <message>")
	else
		local recipient = _argument:sub(1,sPoint-1)
                local message = _argument:sub(sPoint+1)
		if not message then
			logadd("*","You got half of the arguments down pat, at least.")
		else
			if textToBlit(message,true):gsub(" ","") == "" then
				logadd("*","That message is no good.")
			else
				enchatSend(yourName, message, {recipient = recipient})
				logadd("*","to '"..recipient.."': "..message)
			end
		end
	end
end
commands.palette = function(_argument)
	local argument = _argument or ""
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if argument:gsub("%s","") == "" then
		local buff = ""
		for k,v in pairs(palette) do
			buff = buff..k..", "
		end
		buff = buff:sub(1,-3)
		logadd("*",commandInit.."palette "..buff.." <colorcode>")
	else
		argument = explode(" ",argument)
		if #argument == 1 then
			if argument[1]:gsub("%s",""):lower() == "reset" or argument[1]:gsub("%s",""):lower() == "enchat3" then
				palette = {
					bg = colors.black,
					txt = colors.white,
					promptbg = colors.gray,
					prompttxt = colors.white,
					scrollMeter = colors.lightGray,
					chevron = colors.black,
					title = colors.lightGray,
					titlebg = colors.gray,
				}
				UIconf = {
					promptY = 1,
					chevron = ">",
					chatlogTop = 1,
					title = "Enchat 3",
					doTitle = false,
					titleY = 1,
					nameDecolor = false,
					centerTitle = true,
					prefix = "<",
					suffix = "> "
				}
				termsetBackgroundColor(palette.bg)
				termclear()
				logadd("*","You cleansed your palette.")
				saveSettings()
			elseif argument[1]:gsub("%s",""):lower() == "enchat2" then
				palette = {
					bg = colors.gray,
					txt = colors.white,
					promptbg = colors.white,
					prompttxt = colors.black,
					scrollMeter = colors.white,
					chevron = colors.lightGray,
					title = colors.yellow,
					titlebg = colors.gray,
				}
				UIconf = {
					promptY = 1,
					chevron = ">",
					chatlogTop = 1,
					title = "Enchat 2",
					doTitle = false,
					titleY = 1,
					nameDecolor = false,
					centerTitle = false,
					prefix = "<",
					suffix = "> "
				}
				termsetBackgroundColor(palette.bg)
				termclear()
				logadd("*","Switched to the old Enchat2 palette.")
				saveSettings()
			elseif argument[1]:gsub("%s",""):lower() == "enchat1" then
				logadd("*","We don't talk about that one.")
			elseif argument[1]:gsub("%s",""):lower() == "enchat4" then
				logadd("*","Let's leave that to future LDD.")
			elseif argument[1]:gsub("%s",""):lower() == "chat.lua" then
				palette = {
					bg = colors.black,
					txt = colors.white,
					promptbg = colors.black,
					prompttxt = colors.white,
					scrollMeter = colors.white,
					chevron = colors.yellow,
					title = colors.yellow,
					titlebg = colors.black,
				}
				UIconf = {
					promptY = 0,
					chevron = ": ",
					chatlogTop = 2,
					title = "YOURNAME on ENCKEY",
					doTitle = true,
					titleY = 1,
					nameDecolor = true,
					centerTitle = true,
					prefix = "<",
					suffix = "> "
				}
				termsetBackgroundColor(palette.bg)
				termclear()
				logadd("*","Switched to /rom/programs/rednet/chat.lua palette.")
				saveSettings()
			elseif argument[1]:gsub("%s",""):lower() == "talk" then
				palette = {
					bg = colors.black,
					txt = colors.white,
					promptbg = colors.black,
					prompttxt = colors.white,
					scrollMeter = colors.white,
					chevron = colors.white,
					title = colors.black,
					titlebg = colors.white,
				}
				UIconf = {
					promptY = 0,
					chevron = "",
					chatlogTop = 1,
					title = " enchat v3.0     channel: ENCKEY:PORT",
					titleY = scr_y - 1,
					doTitle = true,
					nameDecolor = false,
					centerTitle = false,
					prefix = "<",
					suffix = "> "
				}
				termsetBackgroundColor(palette.bg)
				termclear()
				logadd("*","Switched to Talk palette.")
				saveSettings()
			elseif argument[1]:gsub("%s",""):lower() == "darkchat" then
				palette = {
					bg = colors.black,
					txt = colors.white,
					promptbg = colors.black,
					prompttxt = colors.white,
					scrollMeter = colors.white,
					chevron = colors.white,
					title = colors.white,
					titlebg = colors.blue,
				}
				UIconf = {
					promptY = 0,
					chevron = "Message: ",
					chatlogTop = 1,
					title = "<User: YOURNAME> <Channel: ENCKEY>",
					titleY = scr_y - 1,
					doTitle = true,
					nameDecolor = false,
					centerTitle = true,
					prefix = "",
					suffix = ": "
				}
				termsetBackgroundColor(palette.bg)
				termclear()
				logadd("*","Switched to DarkChat palette.")
				saveSettings()
			else
				if not palette[argument[1]] then
					logadd("*","There's no such palette option.")
				else
					logadd("*","'"..argument[1].."' = '"..toblit[palette[argument[1]]].."'")
				end
			end
		else
			if #argument > 2 then
				argument = {argument[1], table.concat(argument," ",2)}
			end
			argument[1] = argument[1]:lower()
			local newcol = argument[2]:lower()
			if not palette[argument[1]] then
				logadd("*","That's not a valid palette choice.")
			else
				if not (tocolors[newcol] or colors_strnames[newcol]) then
					logadd("*","That isn't a valid color code. (0-f)")
				else
					palette[argument[1]] = (tocolors[newcol] or colors_strnames[newcol])
					logadd("*","Palette changed.",false)
					saveSettings()
				end
			end
		end
	end
end
commands.clear = function()
	log = {}
	IDlog = {}
end
commands.ping = function(pong)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	logadd(nil, pong or "Pong!")
end
commands.set = function(_argument)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	argument = _argument or ""
	local collist = {
		["string"] = function() return "0" end,
		["table"] = function() return "5" end,
		["number"] = function() return "0" end,
		["boolean"] = function(val) if val then return "d" else return "e" end end,
		["function"] = function() return "c" end,
		["nil"] = function() return "8" end,
		["thread"] = function() return "d" end,
		["userdata"] = function() return "c" end, -- ha
	}
	local custColorize = function(input)
		return "&"..collist[type(input)](input)
	end
	local contextualQuote = function(judgetxt,txt)
		if type(judgetxt) == "string" then
			return table.concat({"'",txt,"'"})
		else
			return txt
		end
	end
	local arguments = explode(" ",argument)
	if #argument == 0 then
		for k,v in pairs(enchatSettings) do
			logadd(nil,"&4'"..k.."'&r = "..contextualQuote(v,custColorize(v)..tostring(v).."&r"))
		end
	else
		if enchatSettings[arguments[1]] ~= nil then
			if #arguments >= 2 then
				local newval = table.concat(arguments," ",2)
				if tonumber(newval) then
					newval = tonumber(newval)
				elseif textutilsunserialize(newval) ~= nil then
					newval = textutilsunserialize(newval)
				end
				if type(enchatSettings[arguments[1]]) == type(newval) then
					enchatSettings[arguments[1]] = newval
					logadd("*","Set '&4"..arguments[1].."&r' to &{"..contextualQuote(newval,textutilsserialize(newval).."&}").." ("..type(newval)..")")
					saveSettings()
				else
					logadd("*","Wrong value type (it's "..type(enchatSettings[arguments[1]])..")")
				end
			else
				logadd("*","'"..arguments[1].."' is set to "..contextualQuote(enchatSettings[arguments[1]],custColorize(enchatSettings[arguments[1]])..textutilsserialize(enchatSettings[arguments[1]]).."&r").." ("..type(enchatSettings[arguments[1]])..")")
			end
		else
			logadd("*","No such setting.")
		end
	end
	if enchatSettings.useSkynet and (not skynet) then
		pauseRendering = true
		termsetBackgroundColor(colors.black)
		termclear()
		pauseRendering = false
	end
end
commands.help = function(cmdname)
	if enchatSettings.extraNewline then
		logadd(nil,nil)
	end
	if cmdname then
		local helpList = {
			exit = "Exits Enchat and returns to loader (most likely CraftOS)",
			about = "Tells you a bit about this here Enchat.",
			me = "Sends a message in the format of \"* yourName message\"",
			colors = "Lists all the colors you can use.",
			update = "Updates and overwrites Enchat, then exits if successful.",
			list = "Lists all users in range using the same key.",
			nick = "Give yourself a different username.",
			whoami = "Tells you your current username.",
			key = "Change the current encryption key. Tells you the key, if without argument.",
			clear = "Clears the local chat log. Not your inventory, I swear.",
			ping = "Pong. *sigh*",
			shrug = "Sends out a shrugging emoticon.",
			set = "Changes config options during the current session. Lists all options, if without argument.",
			msg = "Sends a message that is only logged by a specific user.",
			picto = "Opens an image maker and sends the result. Use the scroll wheel to change color, and hold left shift to change text color. If argument given, will look for an image at the given path and use that instead.",
			tron = "Starts up a game of TRON.",
			big = "Sends your message, but enlarged by a specified amount via Wojbie's BigFont API.",
			help = "Shows every command, or describes a specific command.",
		}
		cmdname = cmdname:gsub(" ",""):gsub("/","")
		if helpList[cmdname] then
			logadd("*", helpList[cmdname])
		else
			if commands[cmdname] then
				logadd("*", "No help info for that command.")
			else
				logadd("*", "No such command to get help for.")
			end
		end
	else
		logadd("*","All commands:")
		local output = ""
		for k,v in pairs(commands) do
			output = output.." "..commandInit..k..","
		end
		logadd(nil, output:sub(1,-2))
	end
end
commandAliases = {
	quit = commands.exit,
	colours = commands.colors,
	ls = commands.list,
	cry = commands.list,
	nickname = commands.nick,
	channel = commands.key,
	palate = commands.palette,
	tell = commands.msg,
	whisper = commands.msg,
	["?"] = commands.help,
	porn = function() 	logadd("*","Yeah, no.") end,
	whoareyou = function() 	logadd("*", "I'm Enchat. But surely, you know this?") end,
	fuck = function() 	logadd("*","A mind is a terrible thing to waste.") end,
	hello = function() 	logadd("*","Hey.") end,
	hi = function() 	logadd("*","Hiya.") end,
	hey = function() 	logadd("*","That's for horses.") end,
	bye = function() 	logadd("*","You know, you can use /exit.") end,
	die = function() 	logadd("*","You wish.") end,
	nap = function() 	logadd("*","The time for napping has passed.") end,
	sorry = function() 	logadd("*","That's okay.") end,
	jump = function() 	logadd("*","Sorry. This program is in a NO JUMPING zone.") end,
	enchat = function() 	logadd("*","At your service!") end,
	win = function() 	logadd("*","Naturally!") end,
	lose = function() 	logadd("*","Preposterous!") end,
	xyzzy = function() 	logadd("*","A hollow voice says \"Fool.\"") end,
	wait = function() 	logadd("*","Time passes...") end,
	stop = function() 	logadd("*","Hammertime!","fadeIn") end,
	shit = function() 	logadd("*","Man, you're telling me!") end,
	eat = function() 	logadd("*","You're not hungry.") end,
	what = function() 	logadd("*","What indeed.") end,
	ldd = function()	logadd(nil,"& that's me") end,
	OrElseYouWill = function()
		enchatSend("*", "'"..yourName.."&}&r~r' buggered off. (disconnect)")
		error("DIE")
	end
}

local checkIfCommand = function(input)
	if input:sub(1,#commandInit) == commandInit then
		return true
	else
		return false
	end
end

local parseCommand = function(input)
	local sPos1, sPos2 = input:find(" ")
	local cmdName, cmdArgs
	if sPos1 then
		cmdName = input:sub(#commandInit+1, sPos1-1)
		cmdArgs = input:sub(sPos2+1)
	else
		cmdName = input:sub(#commandInit+1)
		cmdArgs = nil
	end

	local res
	local CMD = commands[cmdName] or commandAliases[cmdName]
	if CMD then
		res = CMD(cmdArgs)
		if res == "exit" then
			return "exit"
		end
	else
		logadd("*", "No such command.")
	end
end

local main = function()
	termsetBackgroundColor(palette.bg)
	termclear()
	os.queueEvent("render_enchat")
	local mHistory = {}

	while true do

		termsetCursorPos(1, scr_y-UIconf.promptY)
		termsetBackgroundColor(palette.promptbg)
		termclearLine()
		termsetTextColor(palette.chevron)
		termwrite(UIconf.chevron)
		termsetTextColor(palette.prompttxt)

		local input = colorRead(nil, mHistory)
		if textToBlit(input,true):gsub(" ","") ~= "" then -- people who send blank messages in chat programs deserve to die
			if checkIfCommand(input) then
				local res = parseCommand(input)
				if res == "exit" then
					return "exit"
				end
			else
				if enchatSettings.extraNewline then
					logadd(nil,nil,nil,nil,nil,personalID) -- readability is key
				end
				enchatSend(yourName, input, {doLog = true})
			end
			if mHistory[#mHistory] ~= input then
				mHistory[#mHistory+1] = input
			end
		elseif input == "" then
			logadd(nil,nil,nil,nil,nil,personalID)
		end
		os.queueEvent("render_enchat")

	end

end

local handleReceiveMessage = function(user, message, animType, maxFrame, _personalID)
	if enchatSettings.extraNewline then
		logadd(nil,nil,nil,nil,nil,_personalID) -- readability is still key
	end
	logadd(user, message, animations[animType] and animType or nil, (type(maxFrame) == "number") and maxFrame or nil, nil, _personalID)
	os.queueEvent("render_enchat")
end

local adjScroll = function(distance)
	scroll = mathmin(maxScroll, mathmax(0, scroll + distance))
end

local checkRSinput = function()
	return (
		rs.getInput("front") or
		rs.getInput("back")  or
		rs.getInput("left")  or
		rs.getInput("right") or
		rs.getInput("top")   or
		rs.getInput("bottom")
	)
end

local handleEvents = function()
	local oldScroll
	local keysDown = {}
	while true do
		local evt = {os.pullEvent()}
		if evt[1] == "enchat_receive" then
			if type(evt[2]) == "string" and type(evt[3]) == "string" then
				handleReceiveMessage(evt[2], evt[3])
			end
		elseif evt[1] == "chat" and ((not checkRSinput()) or (not enchat.disableChatboxWithRedstone)) then
			if enchat.useChatbox then
				if enchatSettings.extraNewline then
					logadd(nil,nil) -- readability is key
				end
				enchatSend(evt[2], evt[3], {doLog = true})
			end
		elseif evt[1] == "chat_message" and ((not checkRSinput()) or (not enchat.disableChatboxWithRedstone)) then -- computronics
			if enchat.useChatbox then
				if enchatSettings.extraNewline then
					logadd(nil,nil) -- readability is key
				end
				enchatSend(evt[3], evt[4], {doLog = true})
			end
		elseif (evt[1] == "modem_message") or (evt[1] == "skynet_message" and enchatSettings.useSkynet) then
			local side, freq, repfreq, msg, distance
			if evt[1] == "modem_message" then
				side, freq, repfreq, msg, distance = evt[2], evt[3], evt[4], evt[5], evt[6]
			else
				freq, msg = evt[2], evt[3]
			end
			if (freq == enchat.port) or (freq == enchat.skynetPort) then
				msg = decrite(msg)
				if type(msg) == "table" then
					if (type(msg.name) == "string") then
						if #msg.name <= 32 then
							if msg.messageID and (not IDlog[msg.messageID]) then
								userCryList[msg.name] = true
								IDlog[msg.messageID] = true
								if ((not msg.recipient) or (msg.recipient == yourName or msg.recipient == textToBlit(yourName,true))) then
									if type(msg.message) == "string" then
										handleReceiveMessage(msg.name, tostring(msg.message), msg.animType, msg.maxFrame, msg.personalID)
										if chatbox and enchat.useChatbox and ((not checkRSinput()) or (not enchat.disableChatboxWithRedstone)) then
											chatbox.say(UIconf.prefix .. msg.name .. UIconf.suffix .. msg.message, msg.name)
										end
									elseif type(msg.message) == "table" and enchatSettings.acceptPictoChat and #msg.message <= 64 then
										logaddTable(msg.name, msg.message, msg.animType, msg.maxFrame, msg.ignoreWrap, msg.personalID)
										if enchatSettings.extraNewline then
											logadd(nil,nil)
										end
									elseif commands[msg.simCommand or false] and type(msg.simArgument) == "string" then
										if simmableCommands[msg.simCommand or false] then
											commands[msg.simCommand](msg.simArgument, msg.name)
										end
									end
								end
								if (msg.cry == true) then
									cryOut(yourName, false)
								end
							end
						end
					end
				end
			end
		elseif evt[1] == "mouse_scroll" and (not pauseRendering) then
			local dist = evt[2]
			oldScroll = scroll
			adjScroll(enchatSettings.reverseScroll and -dist or dist)
			if scroll ~= oldScroll then
				dab(renderChat)
			end
		elseif evt[1] == "key" and (not pauseRendering) then
			local key = evt[2]
			keysDown[key] = true
			oldScroll = scroll
			local pageSize = (scr_y-UIconf.promptY) - UIconf.chatlogTop
			if key == keys.pageUp then
				adjScroll(-(keysDown[keys.leftCtrl] and pageSize or enchatSettings.pageKeySpeed))
			elseif key == keys.pageDown then
				adjScroll(keysDown[keys.leftCtrl] and pageSize or enchatSettings.pageKeySpeed)
			end
			if scroll ~= oldScroll then
				dab(renderChat)
			end
		elseif evt[1] == "key_up" then
			local key = evt[2]
			keysDown[key] = nil
		elseif (evt[1] == "render_enchat") and (not pauseRendering) then
			dab(renderChat)
		elseif (evt[1] == "tron_complete") then
			if evt[3] then
				if enchatSettings.extraNewline then
					logadd(nil,nil)
				end
				if evt[2] == "win" then
					enchatSend("*", yourName .. "&}&r~r beat " .. (evt[4] or "someone") .. "&}&r~r in TRON!", {doLog = true})
				elseif evt[2] == "lose" then
					enchatSend("*", (evt[4] or "Someone") .. "&}&r~r beat " .. yourName .. "&}&r~r in TRON!", {doLog = true})
				elseif evt[2] == "tie" then
					enchatSend("*", yourName .. "&}&r~r tied with " .. (evt[4] or "someone") .. "&}&r~r in TRON!", {doLog = true})
				end
			elseif evt[2] == "timeout" then
				if enchatSettings.extraNewline then
					logadd(nil,nil)
				end
				enchatSend("*", yourName .. "&}&r~r timed out against " .. (evt[4] or "someone") .. "&}&r~r in TRON...", {doLog = true})
			end
		elseif evt[1] == "terminate" then
			return "exit"
		end
	end
end

local keepRedrawing = function()
	while true do
		sleep(enchatSettings.redrawDelay)
		if not pauseRendering then
			os.queueEvent("render_enchat")
		end
	end
end

local handleNotifications = function()
	while true do
		os.pullEvent("render_enchat")
		if canvas and enchatSettings.doNotif then
			notif.displayNotifications(true)
		end
	end
end

getModem()

enchatSend("*", "'"..yourName.."&}&r~r' has moseyed on over.", {doLog = true, omitPersonalID = true})

local funky = {
	main,
	handleEvents,
	keepRedrawing,
	handleNotifications
}

if skynet then
	funky[#funky+1] = function()
		while true do
			if skynet then
				pcall(skynet.listen)
				local success, msg = pcall(skynet.open, enchat.skynetPort)
                        	if not success then
                        		skynet = nil
				end
			end
			sleep(5)
		end
	end
end

pauseRendering = false

local res, outcome = pcall(function()
	return parallel.waitForAny(unpack(funky))
end)

os.pullEvent = oldePullEvent
if skynet then
	if skynet.socket then
		skynet.socket.close()
	end
end

if canvas then
	canvas.clear()
end

tsv(true) -- in case it's false, y'know

if not res then
	prettyClearScreen(1,scr_y-1)
	termsetTextColor(colors.white)
	termsetBackgroundColor(colors.gray)
	cwrite("There was an error.",2)
	cfwrite("Report this to &3@LDDestroier#2901&r",3)
	cwrite("on Discord,",4)
	cwrite("if you feel like it.",5)
	termsetCursorPos(1,7)
	printError(outcome)
	termsetTextColor(colors.lightGray)
	cwrite("I'll probably fix it, maybe.",10)
end

termsetCursorPos(1, scr_y)
termsetBackgroundColor(initcolors.bg)
termsetTextColor(initcolors.txt)
termclearLine()