first commit
This commit is contained in:
commit
8090033f6f
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.#*
|
||||
conf.yml
|
||||
BotDB.json
|
||||
*.pyc
|
25
Pipfile
Normal file
25
Pipfile
Normal file
@ -0,0 +1,25 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
irc = "*"
|
||||
ipython = "*"
|
||||
pyyaml = "*"
|
||||
emoji = "*"
|
||||
beautifulsoup4 = "*"
|
||||
dnspython = "*"
|
||||
py-expression-eval = "*"
|
||||
pyroute2 = "*"
|
||||
numexpr = "*"
|
||||
brainfuck-interpreter = "*"
|
||||
nltk = "*"
|
||||
tinydb = "*"
|
||||
feedparser = "*"
|
||||
requests = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
512
Pipfile.lock
generated
Normal file
512
Pipfile.lock
generated
Normal file
@ -0,0 +1,512 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ab5714630e56605de2816326ee14b4d91085312571d4992a97d243fb04a0547e"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"backcall": {
|
||||
"hashes": [
|
||||
"sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e",
|
||||
"sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"
|
||||
],
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
"sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35",
|
||||
"sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25",
|
||||
"sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.9.3"
|
||||
},
|
||||
"brainfuck-interpreter": {
|
||||
"hashes": [
|
||||
"sha256:6059fef0f12628ccd8e2fa66e8b8947af56d79373ae7f0a8a2cd89ae05c2f317"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
||||
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
||||
],
|
||||
"version": "==2020.11.8"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
|
||||
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
|
||||
"sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
|
||||
],
|
||||
"version": "==4.4.2"
|
||||
},
|
||||
"dnspython": {
|
||||
"hashes": [
|
||||
"sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7",
|
||||
"sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"emoji": {
|
||||
"hashes": [
|
||||
"sha256:e42da4f8d648f8ef10691bc246f682a1ec6b18373abfd9be10ec0b398823bd11"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"feedparser": {
|
||||
"hashes": [
|
||||
"sha256:1b00a105425f492f3954fd346e5b524ca9cef3a4bbf95b8809470e9857aa1074",
|
||||
"sha256:f596c4b34fb3e2dc7e6ac3a8191603841e8d5d267210064e94d4238737452ddd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.0.2"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:590690d61efdd716ff82c39ca9a9d4209252adfe288a4b5721181050acbd4175",
|
||||
"sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"importlib-resources": {
|
||||
"hashes": [
|
||||
"sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592",
|
||||
"sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64",
|
||||
"sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.16.1"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
|
||||
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
|
||||
],
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"irc": {
|
||||
"hashes": [
|
||||
"sha256:99fd5d1fa1d054dee4fbb81e0d5193dc1e8200db751d5da9a97850a62162b9ab",
|
||||
"sha256:b5179428448947d364edcdcef8cb34a2a7033b4e78e58fec32d092091eab786f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.0.1"
|
||||
},
|
||||
"jaraco.classes": {
|
||||
"hashes": [
|
||||
"sha256:116429c2047953f525afdcae165475c4589c7b14870e78b2d068ecb01018827e",
|
||||
"sha256:c38698ff8ef932eb33d91c0e8fc192ad7c44ecee03f7f585afd4f35aeaef7aab"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"jaraco.collections": {
|
||||
"hashes": [
|
||||
"sha256:a7889f28c80c4875bd6256d9924e8526dacfef22cd7b80ff8469b4d312f9f144",
|
||||
"sha256:be570ef4f2e7290b757449395238fa63d70a9255574624e73c5ff9f1ee554721"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"jaraco.functools": {
|
||||
"hashes": [
|
||||
"sha256:9fedc4be3117512ca3e03e1b2ffa7a6a6ffa589bfb7d02bfb324e55d493b94f4",
|
||||
"sha256:d3dc9f6c1a1d45d7f59682a3bf77aceb685c1a60891606c7e4161e72ecc399ad"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"jaraco.logging": {
|
||||
"hashes": [
|
||||
"sha256:31716fe84d3d5df39d95572942513bd4bf8ae0a478f64031eff4c2ea9e83434e",
|
||||
"sha256:b05ed07101883997a30e05c2472798d86129803d9961a0d1081a3236ad37c52a"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"jaraco.stream": {
|
||||
"hashes": [
|
||||
"sha256:287e1cba9f278e0146fdded6bc40518930813a5584579769aeaa1d0bfd178a73",
|
||||
"sha256:a42357141288bbd55938b9ff464173c078038374ce5ffa1bf31895138acc0f30"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"jaraco.text": {
|
||||
"hashes": [
|
||||
"sha256:c87569c9afae14f71b2e1c57f316770ab6981ab675d9c602be1c7981161bacdd",
|
||||
"sha256:e5078b1126cc0f166c7859aa75103a56c0d0f39ebcafc21695615472e0f810ec"
|
||||
],
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
"sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20",
|
||||
"sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.17.2"
|
||||
},
|
||||
"joblib": {
|
||||
"hashes": [
|
||||
"sha256:698c311779f347cf6b7e6b8a39bb682277b8ee4aba8cf9507bc0cf4cd4737b72",
|
||||
"sha256:9e284edd6be6b71883a63c9b7f124738a3c16195513ad940eae7e3438de885d5"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.17.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330",
|
||||
"sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==8.6.0"
|
||||
},
|
||||
"nltk": {
|
||||
"hashes": [
|
||||
"sha256:845365449cd8c5f9731f7cb9f8bd6fd0767553b9d53af9eb1b3abf7700936b35"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5"
|
||||
},
|
||||
"numexpr": {
|
||||
"hashes": [
|
||||
"sha256:0eb8d1a949dcc3eea633438af939f406aaaf240ae69b4ab85ed0c11b8d5e77ee",
|
||||
"sha256:208597cacb191fe983b4ae05dc9ae8177b17d82a0d9f34719d71ac614744f53b",
|
||||
"sha256:280c316d56903d20a474c5e03c073371b8879842b8070606cef0c1ea7371933a",
|
||||
"sha256:2cb778a74f315aafc8eded19781e444269bd45f4ce3095697595e5000dc20f8a",
|
||||
"sha256:33a610bb775a84ab8ded0af4041df2e931ce7edf5b465ccd9851511429c86d0f",
|
||||
"sha256:3d83f6f3d6d449eb82a4a5bd56b9d61c9e1ade65b1188052700171051329888b",
|
||||
"sha256:4655276892b5274015377a4487e1c57cc257c666e5578e12679029cc1124fb08",
|
||||
"sha256:49f835568c864b444fa6fccf64cc01ff51a6171311742451ac4a176df471f9d8",
|
||||
"sha256:57b7fcf2d0a1370bc9a380f3a96f6d10e4dfab5081b61a198a8d23b80c33e634",
|
||||
"sha256:57d9ccd0820b7f5b1bed5100dd54a5ae52c39eb5b7e54317ae29e31ed9bd9edf",
|
||||
"sha256:583fcf614521edf6eb1326e982d6fe3951dbd451d63e51f7438f0142b491d43f",
|
||||
"sha256:59984617a50369670a88a0f0b6decdf59a93828dc42e29c8851bcffcedf0695b",
|
||||
"sha256:659cee220ebe4bf88cb527ca9723d7cb390e93cbae8729ff5e927d06713bad26",
|
||||
"sha256:687fa9521dbafb130f42d61462f968f211f7eb364f2789c5fbe65d82809ad6b2",
|
||||
"sha256:78c7040baf20036f0d85308fd5f8322e30d553b8daff1de264394014feb62cc0",
|
||||
"sha256:7cd5369c2f8cb4bac57571e52bca1a9ccc0260567cefa39ac40680dad0e9df4c",
|
||||
"sha256:81ff83abc969288673ad37055fef3e5e80cdc87f90245b76c0af9bdef6d5c509",
|
||||
"sha256:841c23811b00f35b4ce2c330b57c4398ff4a61af4488ce0e013e5039bba68188",
|
||||
"sha256:84d10e27833a5be6c9a61350cba2acb2f36af1e71c4d47c390b4cc80704ccb55",
|
||||
"sha256:9e7dbf2a849c34f5e61f9b8119688108f7b5dec97ee8ea2946440bc69a4b28d0",
|
||||
"sha256:9f91ea6385f743d5ef5ef0a074270a057115d8a4c57625800dd25b5912f563b2",
|
||||
"sha256:a478e224a23609e1bef45b44a65aad2f158a3072947fc0085c231953b1fafdcd",
|
||||
"sha256:b0d239d9827e1bcee08344fd05835823bc60aff97232e35a928214d03ff802b1",
|
||||
"sha256:c169e1424d495b7efefe69c046cbf89ae0dc7a071a89b6b844ae328ac48fccbc",
|
||||
"sha256:e518918a077478523d89060a8eb59178fd80f7f1273fe1a74088c46163fa49b5",
|
||||
"sha256:e6a7d0c269a3d9e117072551e78ec5332ece7297f80acf6447d701de0328e7df",
|
||||
"sha256:eb2bd8656ee2a92b2e928904d6b7ad434f559b1f74a381ff5f36ad987badd1a6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.7.1"
|
||||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
"sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db",
|
||||
"sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce",
|
||||
"sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1",
|
||||
"sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512",
|
||||
"sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2",
|
||||
"sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757",
|
||||
"sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9",
|
||||
"sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2",
|
||||
"sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08",
|
||||
"sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b",
|
||||
"sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb",
|
||||
"sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc",
|
||||
"sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac",
|
||||
"sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83",
|
||||
"sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36",
|
||||
"sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387",
|
||||
"sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f",
|
||||
"sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad",
|
||||
"sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c",
|
||||
"sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414",
|
||||
"sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37",
|
||||
"sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764",
|
||||
"sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753",
|
||||
"sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909",
|
||||
"sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6",
|
||||
"sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63",
|
||||
"sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9",
|
||||
"sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949",
|
||||
"sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab",
|
||||
"sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c",
|
||||
"sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3",
|
||||
"sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893",
|
||||
"sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15",
|
||||
"sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.19.4"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea",
|
||||
"sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
|
||||
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
|
||||
],
|
||||
"markers": "sys_platform != 'win32'",
|
||||
"version": "==4.8.0"
|
||||
},
|
||||
"pickleshare": {
|
||||
"hashes": [
|
||||
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
|
||||
"sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
|
||||
],
|
||||
"version": "==0.7.5"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
|
||||
"sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.8"
|
||||
},
|
||||
"ptyprocess": {
|
||||
"hashes": [
|
||||
"sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
|
||||
"sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"py-expression-eval": {
|
||||
"hashes": [
|
||||
"sha256:43038326b686df697f9533895184c15d18769e215abbd8bcecaea607483f35b3",
|
||||
"sha256:fcbd26d015568752e1f41b263c722acbcc06b55ef2b4a8d0af5aabbc6b4ba16f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.10"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0",
|
||||
"sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.7.2"
|
||||
},
|
||||
"pyroute2": {
|
||||
"hashes": [
|
||||
"sha256:774c5ecf05fe40f0f601a7ab33c19ca0b24f00bf4a094e58deaa5333b7ca49b5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.14"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||
],
|
||||
"version": "==2020.4"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
|
||||
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
|
||||
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
|
||||
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
|
||||
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
|
||||
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
|
||||
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
|
||||
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
|
||||
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
|
||||
"sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
|
||||
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
|
||||
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
|
||||
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.3.1"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538",
|
||||
"sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4",
|
||||
"sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc",
|
||||
"sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa",
|
||||
"sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444",
|
||||
"sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1",
|
||||
"sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af",
|
||||
"sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8",
|
||||
"sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9",
|
||||
"sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88",
|
||||
"sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba",
|
||||
"sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364",
|
||||
"sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e",
|
||||
"sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7",
|
||||
"sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0",
|
||||
"sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31",
|
||||
"sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683",
|
||||
"sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee",
|
||||
"sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b",
|
||||
"sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884",
|
||||
"sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c",
|
||||
"sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e",
|
||||
"sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562",
|
||||
"sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85",
|
||||
"sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c",
|
||||
"sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6",
|
||||
"sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d",
|
||||
"sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b",
|
||||
"sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70",
|
||||
"sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b",
|
||||
"sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b",
|
||||
"sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f",
|
||||
"sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0",
|
||||
"sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5",
|
||||
"sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5",
|
||||
"sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f",
|
||||
"sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e",
|
||||
"sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512",
|
||||
"sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d",
|
||||
"sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917",
|
||||
"sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"
|
||||
],
|
||||
"version": "==2020.11.13"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
|
||||
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.25.0"
|
||||
},
|
||||
"sgmllib3k": {
|
||||
"hashes": [
|
||||
"sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55",
|
||||
"sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"
|
||||
],
|
||||
"markers": "python_version >= '3.0'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"tempora": {
|
||||
"hashes": [
|
||||
"sha256:9af06854fafb26d3d40d3dd6402e8baefaf57f90e48fdc9a94f6b22827a60fb3",
|
||||
"sha256:e319840007c2913bf2f14ebf1f71b94812335d1ef4ca178e1c65c445a2d63da8"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.1"
|
||||
},
|
||||
"tinydb": {
|
||||
"hashes": [
|
||||
"sha256:1d102d06f9bb22d739d8061b490c64d420de70dca5f95ebd43a492c43c7bd303",
|
||||
"sha256:c8a8887269927e077f3aa16fddbf4debd176c10edc4ac8a5ce48ced0b10adf8c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:5c0d04e06ccc0da1bd3fa5ae4550effcce42fcad947b4a6cafa77bdc9b09ff22",
|
||||
"sha256:9e7b8ab0ecbdbf0595adadd5f0ebbb9e69010e0bd48bbb0c15e550bf2a5292df"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.54.0"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
|
||||
"sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"
|
||||
],
|
||||
"version": "==4.3.3"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
|
||||
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.2"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
|
||||
"sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
|
||||
],
|
||||
"version": "==0.2.5"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108",
|
||||
"sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.4.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
111
README.md
Normal file
111
README.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Quickstart
|
||||
|
||||
- This bot uses notices to send information that is personalized or private to you, use one of the following two
|
||||
sections (irssi or weechat)
|
||||
|
||||
## Irssi
|
||||
- `/WINDOW LEVEL +NOTICES`
|
||||
|
||||
## Weechat
|
||||
- `/set irc.msgbuffer.notice = current`
|
||||
|
||||
## Bot
|
||||
- `pipenv` is required, install it with `pip install pipenv` or your OS package manager.
|
||||
- Clone this repository
|
||||
- copy the configuration example, to `config.yml` and edit it. Not much is required to start, just the obvious settiings.
|
||||
- run `pipenv shell`
|
||||
- run `pipenv sync`
|
||||
- run `python -m mcmxi.mcmxi`
|
||||
|
||||
### Console
|
||||
- the bot uses IPython embedded for control and development
|
||||
|
||||
```
|
||||
❯ python -m mcmxi.mcmxi
|
||||
INFO|mcmxi-><module> bot is initialized, press C-d at any point for a smooth and easy exit, starting REPL...
|
||||
Python 3.6.10 (default, Jan 16 2020, 09:12:04) [GCC]
|
||||
Type 'copyright', 'credits' or 'license' for more information
|
||||
IPython 7.16.1 -- An enhanced Interactive Python. Type '?' for help.
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
### Extensibility
|
||||
- To extend the bot, create a script to the following effect:
|
||||
|
||||
```
|
||||
from mcmxi import mcmxi
|
||||
if __name__ == "__main__":
|
||||
l = UTIL.setup_logging(bot_config())
|
||||
|
||||
bots = mcmxi.start_bots()
|
||||
|
||||
l.info("bot is initialized, press C-d at any point for a smooth and easy exit, starting REPL...")
|
||||
|
||||
IPYSH()
|
||||
|
||||
mcmxi.stop_bots(bots)
|
||||
S.exit()
|
||||
|
||||
```
|
||||
|
||||
- then add a tuple containing an instance of a class that contains your fantasy command functions, the prefix of the method
|
||||
names if any or "", and a lambda method call wrapper to any or all of the bot instance's executables list. Each bot has an
|
||||
executable list that looks like this:
|
||||
|
||||
```
|
||||
self.executables = [
|
||||
(self,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
(CARD,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
(EXP_EVAL,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
(HTTP,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
(IRC_TXT,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
(TERM_BIN,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
(UTIL,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ) ]
|
||||
```
|
||||
|
||||
- `CARD` is the singleton class
|
||||
- `cmd` is the prefix for methods that should be exposed as fantasy commands
|
||||
- `lambda event, instance: instance(event)` is a method call wrapper (default example)
|
||||
|
||||
When fantasy commands are sent to a channel, an executor defined in the `UTILS` class is invoked,
|
||||
which is technically also extensible / customizable. The default simply looks for methods in
|
||||
each of the whitelisted classes (in `self.executables`) for method's prefixed with `cmd` or
|
||||
whatever prefix is specified, the rest of the fantasy command that was specified corresponds
|
||||
directly to the rest of the method name:`
|
||||
|
||||
```
|
||||
self.executor = (
|
||||
lambda bot, obj, method_prefix, call_wrapper, event: call_wrapper(
|
||||
event,
|
||||
obj.__getattribute__(next(filter(lambda t: (
|
||||
method_prefix != ""
|
||||
and "{prefix}_{command}"
|
||||
or "{prefix}{command}").format(
|
||||
prefix = method_prefix,
|
||||
command = t[ 0 ]
|
||||
) == t[ 1 ] and t[ 1 ], zip(ITER.cycle([event.data(
|
||||
).arguments[ 0 ].split(" "
|
||||
)[ 0 ].strip(next(filter(lambda x: x.channel_name(
|
||||
).lower(
|
||||
) == event.target().lower(
|
||||
), bot.conf.channels() )
|
||||
).command_prefix() ) ]
|
||||
), dir(obj) ) )
|
||||
)[ 1 ] ) ) )
|
||||
```
|
108
conf.example.yml
Normal file
108
conf.example.yml
Normal file
@ -0,0 +1,108 @@
|
||||
bot:
|
||||
name: 'MMXXI'
|
||||
dns_servers:
|
||||
- '4.2.2.1'
|
||||
- '8.8.4.4'
|
||||
- '4.2.2.2'
|
||||
- '8.8.8.8'
|
||||
log_level: 'info'
|
||||
networks:
|
||||
- efnet:
|
||||
nickname: 'MMXXI'
|
||||
alt_nickname: 'MMXXI_'
|
||||
username: 'MMXXI/whatevernet'
|
||||
password: 'password'
|
||||
servers:
|
||||
- 'znc':
|
||||
retry_connect: true
|
||||
retry_connect_delay: 2
|
||||
retry_connect_max_tries: 5
|
||||
address: '127.0.0.1'
|
||||
port: 6667
|
||||
channels:
|
||||
- '#bot-testing':
|
||||
command_prefix: "|"
|
||||
preview_url: true
|
||||
protect_topic: true
|
||||
command_delay: 3
|
||||
modes:
|
||||
- '+s'
|
||||
- '+p'
|
||||
- '+i'
|
||||
- '+n'
|
||||
- '#foo':
|
||||
command_prefix: "|"
|
||||
preview_url: false
|
||||
protect_topic: true
|
||||
command_delay: 3
|
||||
modes:
|
||||
- '+s'
|
||||
- '+p'
|
||||
- '+i'
|
||||
- '+n'
|
||||
- '#bar':
|
||||
ignore:
|
||||
- 'spammer!*@*'
|
||||
- 'anotherbot!*@*'
|
||||
command_prefix: "%"
|
||||
preview_url: false
|
||||
command_delay: 3
|
||||
modes:
|
||||
- '+s'
|
||||
- '+p'
|
||||
- '+i'
|
||||
- '+n'
|
||||
users:
|
||||
- 'lame':
|
||||
hostmask: 'lame!lamer@lame.host'
|
||||
modes:
|
||||
- '+o'
|
||||
owners:
|
||||
- 'lame':
|
||||
hostmask: 'lame!lamer@lame.host'
|
||||
users:
|
||||
- 'default':
|
||||
hostmask: 'testuser!*@*'
|
||||
modes:
|
||||
- '+v'
|
||||
- 'lame':
|
||||
hostmask: 'lame!lamer@lame.host'
|
||||
modes:
|
||||
- '+o'
|
||||
- honk:
|
||||
nickname: 'XXXVII'
|
||||
username: 'XXXVII/othernetwork'
|
||||
alt_nickname: 'XXXVII_'
|
||||
password: 'changeme'
|
||||
servers:
|
||||
- 'znc':
|
||||
address: '127.0.0.1'
|
||||
port: 7000
|
||||
channels:
|
||||
- '&':
|
||||
ignore:
|
||||
- 'CDIX!*@*'
|
||||
command_prefix: "%"
|
||||
preview_url: true
|
||||
command_delay: 3
|
||||
modes:
|
||||
- '+n'
|
||||
- '+t'
|
||||
users:
|
||||
- 'lame':
|
||||
hostmask: 'lamer!lame@lame.host'
|
||||
modes:
|
||||
- '+o'
|
||||
owners:
|
||||
- 'lamer':
|
||||
hostmask: 'lamer!lame@lame.host'
|
||||
users:
|
||||
- 'default':
|
||||
hostmask: 'testuser!*@*'
|
||||
modes:
|
||||
- '+v'
|
||||
- 'lame':
|
||||
hostmask: 'lame!lamer@lame.host'
|
||||
modes:
|
||||
- '+o'
|
||||
|
288
dev.md
Normal file
288
dev.md
Normal file
@ -0,0 +1,288 @@
|
||||
# Notes for development
|
||||
```
|
||||
Various JSON APIs
|
||||
https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=Craig%20Noone&format=json
|
||||
https://www.coingecko.com/en/api#
|
||||
https://api.coingecko.com/api/v3/coins/bitcoin?tickers=true&market_data=true&community_data=true&developer_data=true&sparkline=true
|
||||
https://searx.everdot.org/?q=hi&categories=general&language=en-US&format=json
|
||||
https://searx.everdot.org/?q=site%3Atextfiles.com&categories=files&language=en-US&format=json
|
||||
|
||||
<form action="https://tinyurl.com/create.php" method="post" target="_blank">
|
||||
<table align="center" cellpadding="5" bgcolor="#E7E7F7"><tr><td>
|
||||
<b>Enter a long URL to make <a href="https://tinyurl.com">tiny</a>:</b><br/>
|
||||
<input type="text" name="url" size="30"><input type="submit" name="submit" value="Make TinyURL!">
|
||||
</td></tr></table>
|
||||
</form>
|
||||
|
||||
bot API docs and examples
|
||||
https://github.com/jaraco/irc/blob/master/irc/events.py
|
||||
https://pypi.org/project/unshare/
|
||||
https://github.com/docker/docker-py
|
||||
https://dnspython.readthedocs.io/en/stable/installation.html
|
||||
https://dnspython.readthedocs.io/en/latest/resolver-class.html
|
||||
https://github.com/jaraco/irc/blob/71891ea3034067d6678da21bd6d6046ee2d06df5/irc/bot.py#L254
|
||||
|
||||
character sets
|
||||
https://jrgraphix.net/research/unicode_blocks.php
|
||||
|
||||
Notes on bs4:
|
||||
[ meta.attrs['content'] for meta in doc.find_all('meta') if 'name' in meta.attrs and meta.attrs['name'] == 'description' ]
|
||||
could provide additional info
|
||||
|
||||
https://api.coingecko.com/api/v3/coins/bitcoin?tickers=true&market_data=true&community_data=true&developer_data=true&sparkline=true
|
||||
notes:
|
||||
from statistics import mean
|
||||
set([x['market']['name'] for x in b['tickers']])
|
||||
mean(sorted([x['converted_last']['usd'] for x in b['tickers']]))
|
||||
|
||||
In [13]: [ x.attrs for x in HTML(bots[0][0].grab("https://www.youtube.com/watch?v=rvfEldHBnqM"), features = "html.parser").find("head") if type(x) == SOUP.element.Tag if x.attrs.get("property") !
|
||||
...: = None and x.attrs.get("property").startswith("og:") ]
|
||||
DEBUG|mcmxi->grab https://www.youtube.com/watch?v=rvfEldHBnqM
|
||||
DEBUG|connectionpool->_new_conn Starting new HTTPS connection (1): www.youtube.com:443
|
||||
DEBUG|connectionpool->_make_request https://www.youtube.com:443 "GET /watch?v=rvfEldHBnqM HTTP/1.1" 200 None
|
||||
DEBUG|mcmxi->grab <Response [200]>
|
||||
Out[13]:
|
||||
[{'property': 'og:site_name', 'content': 'YouTube'},
|
||||
{'property': 'og:url',
|
||||
'content': 'https://www.youtube.com/watch?v=rvfEldHBnqM'},
|
||||
{'property': 'og:title', 'content': 'Still A G Thang'},
|
||||
{'property': 'og:image',
|
||||
'content': 'https://i.ytimg.com/vi/rvfEldHBnqM/maxresdefault.jpg'},
|
||||
{'property': 'og:image:width', 'content': '1280'},
|
||||
{'property': 'og:image:height', 'content': '720'},
|
||||
{'property': 'og:description',
|
||||
'content': 'Provided to YouTube by Universal Music Group Still A G Thang · Snoop Dogg The Best Of Snoop Dogg ℗ 1998 Priority Records, LLC Released on: 2005-01-01 Associa...'},
|
||||
{'property': 'og:type', 'content': 'video.other'},
|
||||
{'property': 'og:video:url',
|
||||
'content': 'https://www.youtube.com/embed/rvfEldHBnqM'},
|
||||
{'property': 'og:video:secure_url',
|
||||
'content': 'https://www.youtube.com/embed/rvfEldHBnqM'},
|
||||
{'property': 'og:video:type', 'content': 'text/html'},
|
||||
{'property': 'og:video:width', 'content': '960'},
|
||||
{'property': 'og:video:height', 'content': '720'},
|
||||
{'property': 'og:video:tag', 'content': 'Snoop Dogg'},
|
||||
{'property': 'og:video:tag', 'content': 'スヌープ・ドッグ'},
|
||||
{'property': 'og:video:tag', 'content': 'スヌープドッグ'},
|
||||
{'property': 'og:video:tag', 'content': 'The Best Of Snoop Dogg'},
|
||||
{'property': 'og:video:tag', 'content': 'Still A G Thang'},
|
||||
{'property': 'og:video:tag', 'content': 'スティル・ア・G・サング'},
|
||||
{'property': 'og:video:tag', 'content': 'スティル・ア・ジー・サング'}]
|
||||
|
||||
In [23]: (lambda l: [x.attrs for x in HTML(bots[0][0].grab(l), features = "html.parser").find("head") if type(x) == SOUP.element.Tag if x.attrs.get("property") != None and x.attrs.get("property")
|
||||
...: .startswith("og:")])("https://www.microsoft.com")
|
||||
DEBUG|mcmxi->grab https://www.microsoft.com
|
||||
DEBUG|connectionpool->_new_conn Starting new HTTPS connection (1): www.microsoft.com:443
|
||||
DEBUG|connectionpool->_make_request https://www.microsoft.com:443 "GET / HTTP/1.1" 302 0
|
||||
DEBUG|mcmxi->grab https://www.microsoft.com/en-au/
|
||||
DEBUG|connectionpool->_new_conn Starting new HTTPS connection (1): www.microsoft.com:443
|
||||
DEBUG|connectionpool->_make_request https://www.microsoft.com:443 "GET /en-au/ HTTP/1.1" 200 39573
|
||||
DEBUG|mcmxi->grab <Response [200]>
|
||||
Out[23]:
|
||||
[{'property': 'og:url', 'content': 'https://www.microsoft.com/en-au'},
|
||||
{'property': 'og:title', 'content': 'Microsoft - Official Home Page'},
|
||||
{'property': 'og:description',
|
||||
'content': 'At Microsoft our mission and values are to help people and businesses throughout the world realize their full potential.'},
|
||||
{'property': 'og:type', 'content': 'website'}]
|
||||
|
||||
In [24]:
|
||||
|
||||
In [77]: [ x for x in HTML(bots[0][0].grab("https://imgur.com/a/AjZCl0b"), features="html.parser").find_all("meta") if type(x) == SOUP.element.Tag if x.get("property") != None and x.get("property
|
||||
...: ").startswith("og:")]
|
||||
DEBUG|mcmxi->grab https://imgur.com/a/AjZCl0b
|
||||
DEBUG|connectionpool->_new_conn Starting new HTTPS connection (1): imgur.com:443
|
||||
DEBUG|connectionpool->_make_request https://imgur.com:443 "GET /a/AjZCl0b HTTP/1.1" 200 None
|
||||
DEBUG|mcmxi->grab <Response [200]>
|
||||
Out[77]:
|
||||
[<meta content="Imgur" property="og:site_name"> <meta content="12331492" property="fb:admins"/> <meta content="12301369" property="fb:admins"/> <meta content="127621437303857" property="fb:app_id"/> <meta content="imgur://imgur.com/?from=fbreferral" property="al:android:url"/> <meta content="Imgur" property="al:android:app_name"/> <meta content="com.imgur.mobile" property="al:android:package"/> <meta content="imgur://imgur.com/?from=fbreferral" property="al:ios:url"/> <meta content="639881495" property="al:ios:app_store_id"/> <meta content="Imgur" property="al:ios:app_name"/> <meta content="https://imgur.com/" property="al:web:url"/> <meta content="@imgur" name="twitter:site"/> <meta content="imgur.com" name="twitter:domain"/> <meta content="com.imgur.mobile" name="twitter:app:id:googleplay"/> <meta content="Imgur" property="author"/> <meta content="Imgur" property="article:author"/> <meta content="https://www.facebook.com/imgur" property="article:publisher"/> <title>Imgur: The magic of the Internet</title><meta content="https://imgur.com/a/AjZCl0b" data-react-helmet="true" property="og:url"/><meta content="https://i.imgur.com/V0jL8lNh.jpg" data-react-helmet="true" name="twitter:image"/><link href="https://api.imgur.com/oembed.json?url=https://imgur.com/a/AjZCl0b" rel="alternate" title="Imgur: The magic of the Internet" type="application/json+oembed"/><link href="https://api.imgur.com/oembed.xml?url=https://imgur.com/a/AjZCl0b" rel="alternate" title="Imgur: The magic of the Internet" type="application/xml+oembed"/><meta href="https://imgur.com/a/AjZCl0b" rel="canonical"/><meta content="600" data-react-helmet="true" property="og:image:width"/><meta content="315" data-react-helmet="true" property="og:image:height"/><meta content="https://i.imgur.com/V0jL8lN.png?fb" data-react-helmet="true" property="og:image"/><meta content="article" data-react-helmet="true" property="og:type"/><meta content="summary_large_image" data-react-helmet="true" name="twitter:card"/><meta content="imgur.com" data-react-helmet="true" name="twitter:title"/><meta content="Imgur: The magic of the Internet" data-react-helmet="true" name="twitter:description"/><meta content="imgur.com" data-react-helmet="true" property="og:title"/><meta content="Imgur: The magic of the Internet" data-react-helmet="true" property="og:description"/> <script>dataLayer=[];var pbjs=pbjs||{};pbjs.que=pbjs.que||[]</script> <script async="true">!function(){var e=document.createElement("script"),t=document.getElementsByTagName("script")[0],n="https://quantcast.mgr.consensu.org".concat("/choice/","f8oruOqDFlMeI","/","imgur.com","/choice.js"),a=0;e.async=!0,e.type="text/javascript",e.src=n,e.onload=function(){var e=document.createEvent("Event");e.initEvent("cmpLoaded",!0,!0),window.dispatchEvent(e)},t.parentNode.insertBefore(e,t),function(){for(var e,i="__tcfapiLocator",a=[],o=window;o;){try{if(o.frames[i]){e=o;break}}catch(e){}if(o===window.top)break;o=o.parent}e||(function e(){var t=o.document,n=!!o.frames[i];if(!n)if(t.body){var a=t.createElement("iframe");a.style.cssText="display:none",a.name=i,t.body.appendChild(a)}else setTimeout(e,5);return!n}(),o.__tcfapi=function(){var e,t=arguments;if(!t.length)return a;if("setGdprApplies"===t[0])3<t.length&&2===t[2]&&"boolean"==typeof t[3]&&(e=t[3],"function"==typeof t[2]&&t[2]("set",!0));else if("ping"===t[0]){var n={gdprApplies:e,cmpLoaded:!1,cmpStatus:"stub"};"function"==typeof t[2]&&t[2](n)}else a.push(t)},o.addEventListener("message",function(a){var i="string"==typeof a.data,e={};try{e=i?JSON.parse(a.data):a.data}catch(e){}var o=e.__tcfapiCall;o&&window.__tcfapi(o.command,o.version,function(e,t){var n={__tcfapiReturn:{returnValue:e,success:t,callId:o.callId}};i&&(n=JSON.stringify(n)),a.source.postMessage(n,"*")},o.parameter)},!1))}();var i=function(){var e=arguments;typeof window.__uspapi!==i&&setTimeout(function(){void 0!==window.__uspapi&&window.__uspapi.apply(window.__uspapi,e)},500)};if(void 0===window.__uspapi){window.__uspapi=i;var o=setInterval(function(){a++,window.__uspapi===i&&a<3?console.warn("USP is not accessible"):clearInterval(o)},6e3)}}()</script> <link href="https://s.imgur.com/desktop-assets/css/styles.ebc99cf807f6b7c8c39c.css" rel="stylesheet"/></meta>,
|
||||
<meta content="https://imgur.com/a/AjZCl0b" data-react-helmet="true" property="og:url"/>,
|
||||
<meta content="600" data-react-helmet="true" property="og:image:width"/>,
|
||||
<meta content="315" data-react-helmet="true" property="og:image:height"/>,
|
||||
<meta content="https://i.imgur.com/V0jL8lN.png?fb" data-react-helmet="true" property="og:image"/>,
|
||||
<meta content="article" data-react-helmet="true" property="og:type"/>,
|
||||
<meta content="imgur.com" data-react-helmet="true" property="og:title"/>,
|
||||
<meta content="Imgur: The magic of the Internet" data-react-helmet="true" property="og:description"/>]
|
||||
|
||||
```
|
||||
|
||||
#NLP
|
||||
- https://medium.com/@ritidass29/create-your-chatbot-using-python-nltk-88809fa621d1
|
||||
|
||||
|
||||
# cards
|
||||
```
|
||||
'''
|
||||
clubs (lowest), followed by diamonds, hearts, and spades (highest).
|
||||
[(chr(x), x) for x in [9827, 9830, 9829, 9824]]
|
||||
[(chr(x), x) for x in [9831, 9826, 9825, 9828]]
|
||||
52 card deck
|
||||
list(itertools.chain.from_iterable(map(lambda c: [ x for x in [
|
||||
(c, "A"),
|
||||
(c, "K"),
|
||||
(c, "Q"),
|
||||
(c, "J"),
|
||||
(c, "10"),
|
||||
(c, "9"),
|
||||
(c, "8"),
|
||||
(c, "7"),
|
||||
(c, "6"),
|
||||
(c, "5"),
|
||||
(c, "4"),
|
||||
(c, "3"),
|
||||
(c, "2") ]
|
||||
], [ chr(x) for x in [0x2660, 0x2661, 0x2666, 0x2667 ] ] ) ) )
|
||||
|
||||
Bridge (for bidding and scoring) and occasionally poker: spades, hearts, diamonds, clubs; 'notrump'
|
||||
ranks above all the suits[clarification needed]
|
||||
|
||||
Preferans: hearts, diamonds, clubs, spades. Only used for bidding, and No
|
||||
Trump[clarification needed] is considered higher than hearts.
|
||||
Five Hundred: hearts, diamonds, clubs, spades (for bidding and scoring)
|
||||
|
||||
Ninety-nine: clubs, hearts, spades, diamonds (supposedly mnemonic as they have
|
||||
respectively 3, 2, 1, 0 lobes; see article for how this scoring is used)
|
||||
|
||||
Skat: clubs, spades, hearts, diamonds; or acorns, leaves, hearts, bells
|
||||
(for bidding and to determine which Jack beats which in play)
|
||||
Big Two: spades, hearts, clubs, diamonds (Presidents reverses suit strength:
|
||||
hearts, diamonds, spades, clubs)
|
||||
|
||||
Teen patti: In the case where two players have flushes with cards of the same
|
||||
rank, the winning hand is based on suit color as ranked by clubs, hearts, spades, diamonds.
|
||||
Thirteen: hearts, diamonds, clubs, spades.
|
||||
|
||||
durak deck:
|
||||
|
||||
durak_deck = lambda: [(x, z, ord(x) | rank_value) for x, z, rank_value in itertools.chain.from_iterable(map(lambda c: [ x for x in [ (c,"A", 0x8192), (c, "K", 0x4096), (c, "Q", 0x2048), (c, "J", 0x1024), (c, "10", 0x512), (c, "9", 0x256), (c, "8", 0x128), (c, "7", 0x64), (c, "6", 0x32), (c, "5", 0x16), (c, "4", 0x08), (c, "3", 0x04), (c, "2", 0x02) ] ], [ chr(x) for x in [0x2660, 0x2661, 0x2666, 0x2667 ] ] ) ) if len([y for y in ["A", "K", "Q", "J", "10", "9", "8", "7", "6"] if y == z]) > 0 ]
|
||||
|
||||
players = ["deuce", "spigot", "rands"]
|
||||
|
||||
current_game_deck = random.sample(durak_deck(), 36)
|
||||
|
||||
The game is typically played with two to five people, with six players if desired, using a deck of 36 cards,
|
||||
for example a standard 52-card deck from which the numerical cards 2 through 5 have been removed.
|
||||
In theory the limit for a game with one deck of 36 cards is six players, but this extends a
|
||||
considerable advantage to the player who attacks first, and a considerable disadvantage to the
|
||||
player who defends first. Variants exist that use more than one deck.
|
||||
|
||||
durak_deck = players > 6 and durak_deck * 2 or durak_deck (NOTE to self, color offest inverted?)
|
||||
|
||||
The deck is shuffled, and each player is dealt six cards. The bottom card of the stock is
|
||||
turned and placed face up on the table, its suit determining the trump suit for the current deal.
|
||||
For example, if it is the 7 of diamonds, then diamonds rank higher than all plain-suit cards.
|
||||
The rest of the pack is then placed on half over the turnup and at right angles to it,
|
||||
so that it remains visible. These cards form the prikup or talon. The turnup remains part
|
||||
of the talon and is drawn as the last card. Cards discarded due to successful defences are
|
||||
placed in a discard pile next to the talon.
|
||||
|
||||
dealer = lambda cards, players: [ cards[y : y + 6 ] for x, y in zip(range(players), [ 0, 6, 12, 18, 24, 30 ] ) ]
|
||||
|
||||
hands = dict(zip(players, dealer(current_game_deck, len(players) ) ) )
|
||||
|
||||
# note this pack/unpack of the deck in a lambda isn't actually necesarry since the turn up / trump card
|
||||
# is actually considered to be part of the talon and doesn't need to be "popped" but "peeked",
|
||||
# but the example remains for other games where a card maybe selectively removed at the same time that
|
||||
# the stock is assigned:
|
||||
|
||||
talon, turn_up = (lambda s: (s, s[ -1 ] ) )(list(itertools.chain.from_iterable(dealer(current_game_deck,
|
||||
6 )[ len(players) : 6 ])))
|
||||
|
||||
self.connection.privmsg(target, "trump card is {1}//{0}".format(turn_up))
|
||||
|
||||
The player who has the lowest trump card will be the first attacker (note that there is no obligation to
|
||||
play that lowest trump card as the first card). The player to the attacker's left is always the defender.
|
||||
After each round of attack play proceeds clockwise. If the attack succeeds (see below), the defender
|
||||
loses their turn and the attack passes to the player on the defender's left. If the attack fails,
|
||||
the defender becomes the next attacker.
|
||||
|
||||
In [63]: turn_up Out[64]: ('♠', '9', 9846)
|
||||
|
||||
next(enumerate(sorted(itertools.chain.from_iterable([[(x, suite, rank, rank_val
|
||||
) for suite, rank, rank_val in hands.get(x) if suite == turn_up[0] ] for x in hands.keys()]
|
||||
), key = lambda k: k[3])))
|
||||
|
||||
Out[65]: (0, ('deuce', '♠', '6', 9842))
|
||||
|
||||
for reference the hands in this game are:
|
||||
|
||||
{ 'deuce': [
|
||||
('♡', 'A', 42995),
|
||||
('♦', 'J', 13926),
|
||||
('♠', 'J', 13924), <-- trump
|
||||
('♡', '6', 9843),
|
||||
('♧', 'A', 42999),
|
||||
('♠', '6', 9842)], <-- trump (lowest)
|
||||
'spigot': [
|
||||
('♦', 'A', 42998),
|
||||
('♡', 'J', 13925),
|
||||
('♡', 'Q', 9833),
|
||||
('♡', '7', 9829),
|
||||
('♦', '10', 10102),
|
||||
('♧', 'J', 13927)],
|
||||
'rands': [
|
||||
('♦', '8', 10094),
|
||||
('♧', '8', 10095),
|
||||
('♦', '7', 9830),
|
||||
('♦', 'K', 26358),
|
||||
('♧', 'K', 26359),
|
||||
('♧', '6', 9847)] }
|
||||
|
||||
Cards are ranked 6 7 8 9 10 J Q K A. A trump card of any rank beats all cards in the other three suits.
|
||||
For example, a 6 of trumps beats an ace of any other suit.
|
||||
|
||||
The attacker opens their turn by playing a card face up on the table as an attacking card. The player to the
|
||||
attacker's left is the defender. They respond to the attack with a defending card.
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
# Chess
|
||||
## cracked out chess board
|
||||
```
|
||||
(lambda l: [y for y, _, _ in [(l[x], l[x].append(chr(ord(l[x][1]) + 6)), l[x].append(chr(ord(l[x][0]) + 6))) for x in l]])(dict(list(zip(["a", "b", "c", "d", "e", "f", "g", "h"], [[chr(0x2656), chr(0x2659)] + [None] * 4, [chr(0x2658), chr(0x2659)] + [None] * 4, [chr(0x2657), chr(0x2659)] + [None] * 4, [chr(0x2655), chr(0x2659)] + [None] * 4, [chr(0x2654), chr(0x2659)] + [None] * 4, [chr(0x2657), chr(0x2659)] + [None] * 4, [chr(0x2658), chr(0x2659)] + [None] * 4, [chr(0x2656), chr(0x2659)] + [None] * 4] ) ) ) )
|
||||
|
||||
```
|
||||
|
||||
|
||||
# more notes
|
||||
|
||||
```
|
||||
In [12]: test = JSON.loads(bots[0][0].grab("https://en.wikipedia.org/w/api.php?action=query
|
||||
...: &list=search&srsearch=wtf&format=json") )
|
||||
I-search backward: test =
|
||||
|
||||
In [11]: next(enumerate(test.get("query").get("search")))
|
||||
Out[11]:
|
||||
(0,
|
||||
{'ns': 0,
|
||||
'title': 'WTF',
|
||||
'pageid': 423373,
|
||||
'size': 2534,
|
||||
'wordcount': 333,
|
||||
'snippet': '<span class="searchmatch">WTF</span> usually refers to: "What the fuck?", an expression of disbelief <span class="searchmatch">WTF</span> may also refer to: Work Time Fun, a video game for the PlayStation Portable WTF',
|
||||
'timestamp': '2020-08-04T23:25:04Z'})
|
||||
|
||||
In [12]:
|
||||
|
||||
```
|
||||
|
||||
## executables notes
|
||||
|
||||
```
|
||||
(M,
|
||||
"",
|
||||
lambda target, event, instance: self.connection.privmsg(
|
||||
target,
|
||||
self.is_func_or_meth(instance)
|
||||
and (_ for _ in ()
|
||||
).throw(Exception("only property references allowed") )
|
||||
or str(instance) ) ),
|
||||
(STAT,
|
||||
"",
|
||||
lambda target, event, instance: self.connection.privmsg(
|
||||
target,
|
||||
self.is_func_or_meth(instance)
|
||||
and (_ for _ in ()
|
||||
).throw(Exception("only property references allowed") )
|
||||
or str(instance) ) ),
|
||||
```
|
1
mcmxi/__init__.py
Normal file
1
mcmxi/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
#
|
403
mcmxi/cards.py
Normal file
403
mcmxi/cards.py
Normal file
@ -0,0 +1,403 @@
|
||||
import logging as L
|
||||
import random as R
|
||||
import itertools as ITER
|
||||
from mcmxi.irc_text_formatting import IRC_TXT
|
||||
'''
|
||||
'''
|
||||
class cards():
|
||||
'''
|
||||
'''
|
||||
card_table = {}
|
||||
def __init__(self):
|
||||
self.l = L.getLogger(__name__)
|
||||
|
||||
# Pretty printer for playing card, every other suite alternates color, order is black, red, black, red
|
||||
# This will work fine for a 52 card deck, if the unicode values of the enumerated value use the correct
|
||||
# suit code, eg: (black spade) 0x2660, (red diamond) 0x2661...
|
||||
self.card_formatter = lambda i, c, index_separator=".": (
|
||||
"""
|
||||
{bold} {index} {index_separator}
|
||||
{_s_} {color} 98,98 {card_edge} {reset}
|
||||
{color} {color_card_fg},98 {suit} {reset}
|
||||
{color} 98,98 {separator} {pad} {reset}
|
||||
{color} {color_card_fg},98 {rank} {reset}
|
||||
{color} 98,98 {card_edge} {reset}
|
||||
{_s_}
|
||||
""").replace(
|
||||
" ",
|
||||
""
|
||||
).replace(
|
||||
"\n",
|
||||
""
|
||||
).format(
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
index = i,
|
||||
index_separator = index_separator,
|
||||
color = IRC_TXT.IRC_FMT_COLOR,
|
||||
color_card_fg = ( len( [x for x in [ 0x2660, 0x2663 ] if chr(x) == c[ 0 ] ] ) > 0
|
||||
and 88
|
||||
or IRC_TXT.RED[ 3 ][ 0 ] ),
|
||||
suit = c[ 0 ],
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
separator = chr(0xFF0F),
|
||||
card_edge = chr(0x2595),
|
||||
rank = c[ 1 ],
|
||||
_s_ = " ",
|
||||
pad = ( ( c[ 1 ].isdigit()
|
||||
and int(c[ 1 ]) != 10
|
||||
or not c[ 1 ].isdigit() )
|
||||
and " "
|
||||
or "") )
|
||||
|
||||
# 36 card deck suitable for playing durak,
|
||||
# format is character / rank / weight mask / suit mask
|
||||
# Suit mask may also be used for weighting.
|
||||
self.durak_deck = lambda: [ (
|
||||
x,
|
||||
z,
|
||||
ord(x[0]) | rank_value,
|
||||
rank_value
|
||||
) for x, z, rank_value, suite_value in ITER.chain.from_iterable(map(
|
||||
lambda c: [ x for x in [ (
|
||||
c, "A", 0x16777216, c[ 1 ],
|
||||
), (c[ 0 ], "K", 0x8388608, c[ 1 ],
|
||||
), (c[ 0 ], "Q", 0x4194304, c[ 1 ],
|
||||
), (c[ 0 ], "J", 0x2097152, c[ 1 ],
|
||||
), (c[ 0 ], "10", 0x1048576, c[ 1 ],
|
||||
), (c[ 0 ], "9", 0x524288, c[ 1 ],
|
||||
), (c[ 0 ], "8", 0x262144, c[ 1 ],
|
||||
), (c[ 0 ], "7", 0x131072, c[ 1 ],
|
||||
), (c[ 0 ], "6", 0x65536, c[ 1 ],
|
||||
), (c[ 0 ], "5", 0x32768, c[ 1 ],
|
||||
), (c[ 0 ], "4", 0x16384, c[ 1 ],
|
||||
), (c[ 0 ], "3", 0x8192, c[ 1 ],
|
||||
), (c[ 0 ], "2", 0x4096, c[ 1 ] ) ]
|
||||
], [ (chr(x), w) for x, w in [
|
||||
( 0x2660, 0x16 ),
|
||||
( 0x2665, 0x08 ),
|
||||
( 0x2666, 0x04 ),
|
||||
( 0x2663, 0x02 ) ] ] )
|
||||
) if len([ y for y in [
|
||||
"A",
|
||||
"K",
|
||||
"Q",
|
||||
"J",
|
||||
"10",
|
||||
"9",
|
||||
"8",
|
||||
"7",
|
||||
"6"
|
||||
] if y == z ] ) > 0 ]
|
||||
|
||||
# Durak dealer
|
||||
self.durak_dealer = lambda cards, players: [ cards[ y : y + 6
|
||||
] for x, y in zip(range(players
|
||||
), [
|
||||
0,
|
||||
6,
|
||||
12,
|
||||
18,
|
||||
24,
|
||||
30 ] ) ]
|
||||
'''
|
||||
Reset the card table
|
||||
'''
|
||||
def cmd_reset_card_table(self, event):
|
||||
self.card_table[event.target()] = {}
|
||||
|
||||
event.bot().connection.privmsg(event.target(), "".join([chr(x) for x in [
|
||||
0x0028,
|
||||
0x256F,
|
||||
0x00B0,
|
||||
0x25A1,
|
||||
0x00B0,
|
||||
0x0029,
|
||||
0x256F,
|
||||
0xFE35,
|
||||
0x0020,
|
||||
0x253B,
|
||||
0x2501,
|
||||
0x253B ] ] ) )
|
||||
|
||||
'''
|
||||
Sets the status of the queued card game to started
|
||||
'''
|
||||
def cmd_card_start(self, event):
|
||||
if self.card_table.get(
|
||||
event.target()
|
||||
) and self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"active"
|
||||
) and not self.card_table.get(
|
||||
event.target()
|
||||
).get("started"):
|
||||
if len(self.card_table[event.target()]["players"]) >= self.card_table[event.target()]["min_players"]:
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {bold}{title}{reset} the game has started, dealing...".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
title = self.card_table[event.target()]["title"],
|
||||
reset = IRC_TXT.IRC_FMT_RESET ) )
|
||||
|
||||
self.cmd_card_deal(event)
|
||||
|
||||
else:
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {bold}{title}{reset} there are only {count} players joined, {required} more required to start game".format(
|
||||
prefix = self.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
title = self.card_table[event.target()]["title"],
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
count = len(self.card_table[event.target()]["players"]),
|
||||
required = self.card_table[event.target()]["min_players"]
|
||||
- len(self.card_table[event.target()]["players"] ) ) )
|
||||
'''
|
||||
deals the first or next hand for the queued card game
|
||||
'''
|
||||
def cmd_card_deal(self, event):
|
||||
if self.card_table.get(
|
||||
event.target()
|
||||
) and self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"active"
|
||||
) and not self.card_table.get(
|
||||
event.target()
|
||||
).get("started"):
|
||||
hands, stock, trump = self.card_table[event.target()]["deal"](
|
||||
self.card_table[event.target()]["players"],
|
||||
self.card_table[event.target()]["current_game_deck"] )
|
||||
|
||||
self.card_table[event.target()]["hands"] = hands
|
||||
self.card_table[event.target()]["talon"] = stock
|
||||
self.card_table[event.target()]["trump"] = trump
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {bold}{title}{reset} the trump card for this round is {trump_card}".format(
|
||||
title = self.card_table[event.target()]["title"],
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
trump_card = self.card_formatter(
|
||||
"",
|
||||
self.card_table[event.target()]["trump"], "") ) )
|
||||
|
||||
[ event.bot().connection.notice(x, "".join( [ self.card_formatter(
|
||||
y,
|
||||
z
|
||||
) for y, z in zip(range(1, len(self.card_table[event.target()]["hands"].get(x) ) + 1
|
||||
), self.card_table[event.target()]["hands"].get(x) ) ] )
|
||||
) for x in self.card_table[event.target()]["hands"].keys() ]
|
||||
|
||||
'''
|
||||
takes a turn at the card table
|
||||
'''
|
||||
def cmd_card_play(self, event):
|
||||
cards = self.card_table.get(event.target() ).get("play_cards")(
|
||||
event.event_arguments(),
|
||||
self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"hands"
|
||||
).get(event.source(
|
||||
).split("!")[ 0 ] ),
|
||||
self.card_table.get(event.target() ).get("cards_played") )
|
||||
|
||||
if len(cards) > 0:
|
||||
self.l.debug(cards)
|
||||
[ (self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"hands"
|
||||
).get(event.source_nickname().remove(
|
||||
x
|
||||
), self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"cards_played"
|
||||
).append( (
|
||||
event.source_nickname(),
|
||||
x[ 0 ],
|
||||
x[ 1 ],
|
||||
x[ 2 ] ) ) ) ) for x in cards ]
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {bold}{title}{reset} this round: {played}".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
title = self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"title"
|
||||
), played = self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"end_turn"
|
||||
)(self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"cards_played"
|
||||
), self.card_formatter) ) )
|
||||
|
||||
'''
|
||||
Starts a game of Durak (card game)
|
||||
'''
|
||||
def cmd_durak(self, event):
|
||||
if not self.card_table.get(event.target()).get("active"):
|
||||
self.cmd_card_game_instructions(event)
|
||||
|
||||
self.card_table[event.target()] = {
|
||||
"active" : True,
|
||||
"started" : False,
|
||||
"title" : "durak",
|
||||
"current_game_deck" : R.sample(self.durak_deck(), 36),
|
||||
"players" : [],
|
||||
"min_players" : 1,
|
||||
"max_players" : 6,
|
||||
"talon" : None,
|
||||
"hands" : None,
|
||||
"trump" : None,
|
||||
"cards_played" : [],
|
||||
"end_turn" : lambda cards_played, formatter: "".join( [ formatter(name, (suit, rank), ":")
|
||||
for name, suit, rank, _, _ in cards_played ] ),
|
||||
"can_join" : lambda players, min_players, max_players: len(players) < max_players,
|
||||
"play_cards" : lambda args, hand, played: ( len(played) % 2 == 0
|
||||
and [ ( hand[ int(args[ 0 ].strip()) - 1 ] ) ] ),
|
||||
"current_player" : lambda hands, played: next(enumerate(sorted(ITER.chain.from_iterable( [ [ (
|
||||
x,
|
||||
suite,
|
||||
rank,
|
||||
rank_val
|
||||
) for suite, rank, rank_val in hands.get(x
|
||||
) if suite == turn_up[ 0 ] ] for x in hands.keys()] ), key = lambda k: k[ 3 ] ) ) ),
|
||||
"deal" : lambda players, deck: (dict(zip(
|
||||
players,
|
||||
self.durak_dealer(deck, len(players) ) )
|
||||
), list(ITER.chain.from_iterable(
|
||||
self.durak_dealer(deck, 6) )
|
||||
)[ len(players) : 36 ], list(ITER.chain.from_iterable(
|
||||
self.durak_dealer(deck, 6) ) )[ -1 ] )
|
||||
}
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"""
|
||||
{prefix}
|
||||
{_s_} {bold} durak {reset}
|
||||
{_s_} initialized
|
||||
{_s_} and
|
||||
{_s_} waiting
|
||||
{_s_} for
|
||||
{_s_} players
|
||||
{_s_} to
|
||||
{_s_} join.
|
||||
{_s_} Use
|
||||
{_s_} the
|
||||
{_s_} {bold} {cmd_prefix} card_join {reset}
|
||||
{_s_} command
|
||||
{_s_} to
|
||||
{_s_} join
|
||||
{_s_} the
|
||||
{_s_} game
|
||||
{_s_} and
|
||||
{_s_} then
|
||||
{_s_} use
|
||||
{_s_} the
|
||||
{_s_} {bold} {cmd_prefix} card_start {reset}
|
||||
{_s_} command
|
||||
{_s_} to
|
||||
{_s_} start
|
||||
{_s_} the
|
||||
{_s_} game
|
||||
{_s_} when
|
||||
{_s_}ready.
|
||||
""".replace(
|
||||
" ",
|
||||
""
|
||||
).replace(
|
||||
"\n",
|
||||
""
|
||||
).format(
|
||||
_s_ = " ",
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
cmd_prefix = next(filter(lambda f: f.channel_name(
|
||||
) == event.target(), event.bot().conf.channels() )
|
||||
).command_prefix() ) )
|
||||
|
||||
self.cmd_card_join(event)
|
||||
|
||||
'''
|
||||
Shows optimal IRC client configuration instructions for playing the card games
|
||||
'''
|
||||
def cmd_card_game_instructions(self, event):
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"""
|
||||
{prefix}
|
||||
{_s_} {italic} set
|
||||
{_s_} your
|
||||
{_s_} client
|
||||
{_s_} NOTICE's
|
||||
{_s_} to
|
||||
{_s_} current
|
||||
{_s_} window. {reset}
|
||||
{_s_} {bold} weechat: {reset}
|
||||
{_s_} /set
|
||||
{_s_} irc.msgbuffer.notice
|
||||
{_s_} =
|
||||
{_s_} current
|
||||
{_s_} {bold} irssi: {reset}
|
||||
{_s_} /WINDOW
|
||||
{_s_} LEVEL
|
||||
{_s_} +NOTICES
|
||||
{_s_} (
|
||||
{_s_} {italic} more
|
||||
{_s_} info: {reset}
|
||||
{_s_} {underline} https://irssi.org/documentation/help/window_properties/ {reset}
|
||||
{_s_} )
|
||||
""".replace(
|
||||
"\n",
|
||||
""
|
||||
).replace(
|
||||
" ",
|
||||
""
|
||||
).format(
|
||||
_s_ = " ",
|
||||
prefix = IRC_TXT.meander,
|
||||
italic = IRC_TXT.IRC_FMT_ITALIC,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
underline = IRC_TXT.IRC_FMT_UNDERLINE) )
|
||||
|
||||
'''
|
||||
Joins user to the card table
|
||||
'''
|
||||
def cmd_card_join(self, event):
|
||||
if self.card_table.get(
|
||||
event.target()
|
||||
) and self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"active"
|
||||
) and not self.card_table.get(
|
||||
event.target()
|
||||
).get(
|
||||
"started"):
|
||||
self.card_table[event.target()]["players"].append(event.source_nickname())
|
||||
|
||||
event.bot().connection.privmsg(event.target(), "{prefix} {bold}durak{reset} {player} joined the game".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
player = event.source_nickname() ) )
|
||||
|
||||
CARD = cards()
|
39
mcmxi/expression_evaluators.py
Normal file
39
mcmxi/expression_evaluators.py
Normal file
@ -0,0 +1,39 @@
|
||||
import numexpr as NE
|
||||
import brainfuck as BF
|
||||
from py_expression_eval import Parser as EVAL
|
||||
|
||||
'''
|
||||
'''
|
||||
class expression_evaluators():
|
||||
'''
|
||||
'''
|
||||
def __self__(self):
|
||||
self.l = L.getLogger(__name__)
|
||||
|
||||
'''
|
||||
Math expression evaluator
|
||||
'''
|
||||
def cmd_eval(self, event) -> None:
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{}".format(EVAL(
|
||||
).parse(" ".join(
|
||||
event.event_arguments() )
|
||||
).evaluate(
|
||||
{} ) ) )
|
||||
|
||||
'''
|
||||
Brainfuck interpreter/evaluator
|
||||
'''
|
||||
def cmd_brainfuck(self, event):
|
||||
raise NotImplementedError()
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{}".format(BF.evaluate("".join(event.event_arguments() ) )
|
||||
).strip(
|
||||
"\r"
|
||||
).strip(
|
||||
"\n") )
|
||||
|
||||
EXP_EVAL = expression_evaluators()
|
245
mcmxi/http_requests.py
Normal file
245
mcmxi/http_requests.py
Normal file
@ -0,0 +1,245 @@
|
||||
import requests as WWW
|
||||
import json as JSON
|
||||
import logging as L
|
||||
from bs4 import BeautifulSoup as HTML
|
||||
from mcmxi.irc_text_formatting import IRC_TXT
|
||||
from mcmxi.utilities import UTIL
|
||||
|
||||
'''
|
||||
'''
|
||||
class http_requests():
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.l = L.getLogger(__name__)
|
||||
|
||||
'''
|
||||
Gets a preview of a link shared by a chatter
|
||||
'''
|
||||
def cmd_display_www_page_title(self, event) -> None:
|
||||
try:
|
||||
doc = HTML(UTIL.grab(
|
||||
event.url(),
|
||||
event,
|
||||
), features = "html.parser" )
|
||||
|
||||
title = None
|
||||
|
||||
url = "{}".format(event.url() )
|
||||
|
||||
_title = doc.find("title").text
|
||||
|
||||
self.l.debug(_title)
|
||||
|
||||
if _title != None:
|
||||
if len(_title.split(" ") ) > 1:
|
||||
title = "{bold}{title_first_word}{reset} {title_remaining}".format(
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
title_first_word = _title.split(" ")[ 0 ],
|
||||
title_remaining = " ".join(_title.split(" ")[ 1: ] ) )
|
||||
else:
|
||||
title = "{}".format(
|
||||
_title.split(" ")[ 0 ] )
|
||||
else:
|
||||
title = "Untitled"
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {formatted_title} {underline}{url}{reset}".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
formatted_title = title.strip(),
|
||||
underline = IRC_TXT.IRC_FMT_UNDERLINE,
|
||||
url = url.strip(),
|
||||
reset = IRC_TXT.IRC_FMT_RESET ) )
|
||||
|
||||
return(doc)
|
||||
|
||||
except Exception as ex:
|
||||
self.l.exception(ex)
|
||||
|
||||
'''
|
||||
Look up info for an IP address
|
||||
'''
|
||||
def cmd_ip(self, event) -> None:
|
||||
res = JSON.loads(WWW.get(
|
||||
"http://api.bgpview.io/ip/{ip_address}".format(
|
||||
ip_address = event.event_arguments()[ 0 ]) ).text )
|
||||
|
||||
self.l.debug(res)
|
||||
|
||||
[ event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {bold}{ptr_record}{reset} AS{as_number} {prefix_sample}".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
ptr_record = res.get("data"
|
||||
).get("ptr_record"),
|
||||
as_number = x.get("asn"
|
||||
).get("asn"),
|
||||
prefix_sample = " ".join(set([x.get("prefix"
|
||||
) for x in res.get("data"
|
||||
).get("prefixes") ] ) ) )
|
||||
) for x in res.get("data").get("prefixes")[ :1 ] ]
|
||||
|
||||
'''
|
||||
Look up BGP info by ASN
|
||||
'''
|
||||
def cmd_asn(self, event) -> None:
|
||||
res = JSON.loads(WWW.get(
|
||||
"https://api.bgpview.io/asn/{as_number}/prefixes".format(
|
||||
as_number = int(event.event_arguments()[ 0 ]) ) ).text )
|
||||
|
||||
paste = TERM_BIN.term_bin(JSON.dumps(
|
||||
res,
|
||||
sort_keys = True,
|
||||
indent = True ) )
|
||||
|
||||
self.l.debug(paste)
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
" ".join([
|
||||
"""
|
||||
{prefix}
|
||||
{_s_}{bold} {name} {reset}
|
||||
{_s_} {description}
|
||||
{_s_} {country}
|
||||
{_s_} {cidr}
|
||||
{_s_} {parent}
|
||||
{_s_}{italic} continued: {reset}
|
||||
{_s_}{underline} {paste_link} {reset}
|
||||
""".replace(
|
||||
" ",
|
||||
""
|
||||
).format(
|
||||
_s_ = " ",
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
italic = IRC_TXT.IRC_FMT_ITALIC,
|
||||
underline = IRC_TXT.IRC_FMT_UNDERLINE,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
name = x.get("name"),
|
||||
description = x.get("description"),
|
||||
country = x.get("country_code"),
|
||||
cidr = x.get("prefix"),
|
||||
parent = x.get("parent"
|
||||
).get("rir_name"),
|
||||
paste_link = paste ) for x in list(res.get("data"
|
||||
).get("ipv4_prefixes"
|
||||
) + res.get("data"
|
||||
).get("ipv6_prefixes")
|
||||
)[ :1 ] ] ).replace(
|
||||
"\r\n",
|
||||
""
|
||||
).replace(
|
||||
"\n",
|
||||
"") )
|
||||
'''
|
||||
'''
|
||||
def cmd_btc(self, event):
|
||||
raise NotImplementedError()
|
||||
|
||||
'''
|
||||
Gives a free proxy server IP
|
||||
'''
|
||||
def cmd_proxy(self, event) -> {}:
|
||||
res = JSON.loads(WWW.get(
|
||||
"http://pubproxy.com/api/proxy").text)
|
||||
|
||||
self.l.debug(res)
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {underline}{bold}{protocol_handler}{reset}{underline}://{url}:{port}".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
underline = IRC_TXT.IRC_FMT_UNDERLINE,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
protocol_handler = res.get(
|
||||
"data"
|
||||
)[ 0 ].get(
|
||||
"type"
|
||||
),
|
||||
url = res.get(
|
||||
"data"
|
||||
)[ 0 ].get(
|
||||
"ip"
|
||||
),
|
||||
port = res.get("data"
|
||||
)[ 0 ].get(
|
||||
"port"
|
||||
) ) )
|
||||
'''
|
||||
'''
|
||||
def cmd_wikipedia(self, event) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
resp = JSON.loads(UTIL.grab(
|
||||
"https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch={question}&format=json".format(
|
||||
question = "".join(event.event_arguments() ) ) ).text )
|
||||
|
||||
'''
|
||||
Searx meta search (YaCY based)
|
||||
'''
|
||||
def cmd_searx(self, event) -> None:
|
||||
req = WWW.get(
|
||||
"https://searx.everdot.org/?q={}&format=json".format(
|
||||
" ".join(event.event_arguments() ) ) )
|
||||
|
||||
res = JSON.loads(req.text)
|
||||
|
||||
self.l.debug(res)
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"""
|
||||
{prefix}
|
||||
{_s_}{bold} {results} {reset} {_s_} results
|
||||
{_s_}{bold} {answers} {reset} {_s_} answers
|
||||
{_s_}{bold} {corrections} {reset} {_s_} corrections
|
||||
{_s_}{bold} {info_boxes} {reset} {_s_} info {_s_} boxes
|
||||
{_s_}{bold} {suggestions} {reset} {_s_} suggestions
|
||||
{_s_}{bold} {unresponsive_engines} {reset} {_s_} unresponsive {_s_} engines
|
||||
{_s_} were {_s_} found {_s_} for {_s_} query
|
||||
{_s_}{italic} {question} {reset}
|
||||
{_s_}{underline} {url} {reset}
|
||||
""".replace(
|
||||
"\n",
|
||||
" "
|
||||
).replace(
|
||||
" ",
|
||||
""
|
||||
).strip(
|
||||
" "
|
||||
).format(
|
||||
_s_ = " ",
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
italic = IRC_TXT.IRC_FMT_ITALIC,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
underline = IRC_TXT.IRC_FMT_UNDERLINE,
|
||||
results = len(res.get(
|
||||
"results") )
|
||||
or 0,
|
||||
answers = len(res.get(
|
||||
"answers") )
|
||||
or 0,
|
||||
corrections = len(res.get(
|
||||
"corrections") )
|
||||
or 0,
|
||||
info_boxes = len(res.get(
|
||||
"infoboxes") )
|
||||
or 0,
|
||||
suggestions = len(res.get(
|
||||
"suggestions") )
|
||||
or 0,
|
||||
unresponsive_engines = len(res.get(
|
||||
"unresponsive_engines") )
|
||||
or 0,
|
||||
question = res.get(
|
||||
"query"),
|
||||
url = req.url.replace("&format=json", "") ) )
|
||||
|
||||
HTTP = http_requests()
|
215
mcmxi/irc_text_formatting.py
Normal file
215
mcmxi/irc_text_formatting.py
Normal file
@ -0,0 +1,215 @@
|
||||
import itertools as ITER
|
||||
import logging as L
|
||||
import textwrap as TXT
|
||||
'''
|
||||
'''
|
||||
class irc_text_formatting():
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.l = L.getLogger(__name__)
|
||||
|
||||
'''
|
||||
'''
|
||||
self.default_bg_color = ITER.cycle([ 99 ])
|
||||
|
||||
'''
|
||||
'''
|
||||
self.art = lambda a: [ (x, chr(y)
|
||||
) for x, y in zip(range(len(range(0x2500, 0x25FF) )
|
||||
), range(0x2500, 0x25FF)
|
||||
) if x == a ][ 0 ][ 1 ]
|
||||
|
||||
'''
|
||||
'''
|
||||
self.art_reverse = lambda z: [ y for x, y in zip(range(
|
||||
0x2500,
|
||||
0x25FE
|
||||
), range(len(range(
|
||||
0x2500,
|
||||
0x25FE) ) )
|
||||
) if ord(self.art(y)
|
||||
) == z
|
||||
or self.art(y
|
||||
) == z ]
|
||||
|
||||
'''
|
||||
Generic error message
|
||||
'''
|
||||
self.fallback = "".join([ chr(x) for x in [
|
||||
0x00AF,
|
||||
0x005C,
|
||||
0x005F,
|
||||
0x0028,
|
||||
0x30C4,
|
||||
0x0029,
|
||||
0x005F,
|
||||
0x002F,
|
||||
0x00AF ] ] )
|
||||
|
||||
'''
|
||||
Default color map for IRC color interpolation
|
||||
'''
|
||||
self.default_color_map = (
|
||||
lambda bg = None: ITER.cycle(ITER.chain.from_iterable([list(map(lambda x: x != 88 and (
|
||||
x,
|
||||
(bg != None)
|
||||
and next(bg)
|
||||
or next(self.default_color_map)
|
||||
) or (x - 12, 88), x) ) for x in map(lambda z: [index for index in list(list(ITER.accumulate(range(
|
||||
z,
|
||||
z + 7
|
||||
), lambda x, y: x == 0 and z or x + 12) ),
|
||||
) ], range(16, 28) ) ] ) ) )
|
||||
|
||||
'''
|
||||
IRC text formatting class property names (bound later)
|
||||
'''
|
||||
self.format_chars = [
|
||||
"IRC_FMT_BOLD",
|
||||
"IRC_FMT_COLOR",
|
||||
"IRC_FMT_ITALIC",
|
||||
"IRC_FMT_REVERSE_COLOR",
|
||||
"IRC_FMT_UNDERLINE",
|
||||
"IRC_FMT_STRIKETHROUGH",
|
||||
"IRC_FMT_MONOSPACE",
|
||||
"IRC_FMT_RESET" ]
|
||||
|
||||
'''
|
||||
color palette table names, used to store interpolated color palettes as individual lists in class
|
||||
properties (initialized in constructor)
|
||||
'''
|
||||
self.palletes = [
|
||||
"RED",
|
||||
"ORANGE",
|
||||
"YELLOW",
|
||||
"GREEN",
|
||||
"GREEN_2",
|
||||
"GREEN_3",
|
||||
"AQUA",
|
||||
"BLUE",
|
||||
"BLUE_2",
|
||||
"PURPLE",
|
||||
"PURPLE_2",
|
||||
"RED_2" ]
|
||||
|
||||
# Color PRIVMSG
|
||||
self.color_say = (
|
||||
lambda bot, message, target, pallete = ITER.cycle( [ (0, 1) ]
|
||||
): [bot.connection.privmsg(target, "".join(["{}{},{}{}{}".format(
|
||||
self.IRC_FMT_COLOR,
|
||||
x[ 1 ][ 0 ],
|
||||
x[ 1 ][ 1 ],
|
||||
x[ 0 ],
|
||||
self.IRC_FMT_RESET
|
||||
) for x in zip(list(index
|
||||
), pallete ) ] ) ) for index in TXT.wrap(message, 53) ] )
|
||||
|
||||
# Random light shades from random color palettes
|
||||
self.random_light = lambda: ITER.cycle(R.sample([self.__getattribute__(x
|
||||
)[ 4 ] for x in self.palletes ], len(self.palletes) ) )
|
||||
|
||||
# Default message prefix art
|
||||
self.meander = "{}{}{}".format(
|
||||
self.art(155),
|
||||
self.art(156),
|
||||
self.art(159) )
|
||||
|
||||
# Μαίανδρος (3-liner)
|
||||
self.meandros = [[
|
||||
self.art(84),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(87),
|
||||
" ",
|
||||
self.art(81)
|
||||
],[ self.art(81),
|
||||
" ",
|
||||
self.art(84),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(93),
|
||||
" ",
|
||||
self.art(81)
|
||||
],[ self.art(81),
|
||||
" ",
|
||||
self.art(90),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(80),
|
||||
self.art(93) ] ]
|
||||
|
||||
# trans flag
|
||||
self.trans_flag = lambda: ["".join(map(lambda l: "{color}{fg},{bg}{back_fill}{reset}".format(
|
||||
color = self.IRC_FMT_COLOR,
|
||||
fg = x[ 1 ][ 0 ],
|
||||
bg = x[ 1 ][ 0 ],
|
||||
back_fill = l,
|
||||
reset = self.IRC_FMT_RESET ), x[ 0 ])
|
||||
) for x in zip( [ [ self.art(136) ] * 30 ] * 5, (
|
||||
self.BLUE[ 5 ],
|
||||
self.RED_2[ 4 ],
|
||||
self.RED_2[ 6 ],
|
||||
self.RED_2[ 4 ],
|
||||
self.BLUE[ 5 ] ) ) ]
|
||||
|
||||
# IRC text formatting escape characters
|
||||
[ self.__setattr__(y, "{}".format(x) ) for x, y in zip( [
|
||||
chr(0x02),
|
||||
chr(0x03),
|
||||
chr(0x1D),
|
||||
chr(0x16),
|
||||
chr(0x1F),
|
||||
chr(0x1E),
|
||||
chr(0x11),
|
||||
chr(0x0F)
|
||||
], self.format_chars ) ]
|
||||
|
||||
# Create color palette class properties from color palette names
|
||||
[ (not self.__dict__.get(x[ 1 ][ 0 ]
|
||||
) and (self.__setattr__(x[ 1 ][ 0 ], list()
|
||||
) == None and self.__getattribute__(x[ 1 ][ 0 ])
|
||||
) or self.__getattribute__(x[ 1 ][ 0 ] )
|
||||
).append((x[ 1 ][ 1 ], x[ 1 ][ 2 ] )
|
||||
) for x in zip(range(7 * 12
|
||||
), ITER.cycle(ITER.chain.from_iterable([list(map(lambda x: x[ 1 ] != 88 and (x[ 0 ], x[ 1 ], next(self.default_bg_color)
|
||||
) or (x[ 0 ], x[ 1 ] - 12, next(self.default_bg_color) ), x ) ) for x in map(lambda z: [(z[ 1 ], index
|
||||
) for index in list(ITER.accumulate(range(z[ 0 ], z[ 0 ] + 7
|
||||
), lambda x, y: x == 0 and z[ 0 ] or x + 12 )
|
||||
) ], zip(range(16, 28), self.palletes ) ) ] ) ) ) ]
|
||||
|
||||
'''
|
||||
Simple greeter, runs on !greet command
|
||||
'''
|
||||
def cmd_greet(self, target, event) -> None:
|
||||
self.l.debug(event)
|
||||
|
||||
self.color_say(
|
||||
self,
|
||||
"ohai2u =)",
|
||||
target,
|
||||
ITER.cycle(self.GREEN_2) )
|
||||
|
||||
'''
|
||||
Shrug ASCII
|
||||
'''
|
||||
def cmd_shrug(self, target, event) -> None:
|
||||
self.l.debug(event)
|
||||
self.color_say(
|
||||
self,
|
||||
self.fallback,
|
||||
target,
|
||||
self.random_light() )
|
||||
|
||||
'''
|
||||
Gives a random unicode symbol to add to your collection
|
||||
'''
|
||||
def cmd_give(self, target, event) -> None:
|
||||
self.l.debug(event)
|
||||
self.connection.action(target, "gives you a {unicode_artifact}".format(
|
||||
unicode_artifact = R.sample(list(EMO.EMOJI_UNICODE.values() ), 1)[ 0 ]) )
|
||||
|
||||
IRC_TXT = irc_text_formatting()
|
295
mcmxi/mcmxi.py
Normal file
295
mcmxi/mcmxi.py
Normal file
@ -0,0 +1,295 @@
|
||||
import threading as T
|
||||
import queue as Q
|
||||
import sys as S
|
||||
|
||||
import irc.client as C
|
||||
import logging as L
|
||||
import time as TIME
|
||||
import itertools as ITER
|
||||
from IPython import embed as IPYSH
|
||||
from dns import resolver as DNS
|
||||
from mcmxi.objects import *
|
||||
from mcmxi.cards import CARD
|
||||
from mcmxi.expression_evaluators import EXP_EVAL
|
||||
from mcmxi.http_requests import HTTP
|
||||
from mcmxi.irc_text_formatting import IRC_TXT
|
||||
from mcmxi.term_bin import TERM_BIN
|
||||
from mcmxi.utilities import UTIL
|
||||
|
||||
'''
|
||||
'''
|
||||
class mcmxi(C.SimpleIRCClient):
|
||||
conf = None
|
||||
l = None
|
||||
state = None
|
||||
|
||||
'''
|
||||
Whitelisted executable modules, tuple format is:
|
||||
module
|
||||
method prefix or empty string
|
||||
lambda wrapper function
|
||||
'''
|
||||
executables = None
|
||||
|
||||
def __str__(self):
|
||||
return(self.conf.parent().bot_name())
|
||||
|
||||
'''
|
||||
'''
|
||||
def __init__(self, conf):
|
||||
self.state = bot_state(self)
|
||||
self.l = L.getLogger(__name__)
|
||||
self.conf = conf
|
||||
UTIL.DNS.nameservers = self.conf.parent().name_servers()
|
||||
|
||||
self.executables = [
|
||||
( self,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
( CARD,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
( EXP_EVAL,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
( HTTP,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
( IRC_TXT,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
( TERM_BIN,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ),
|
||||
( UTIL,
|
||||
"cmd",
|
||||
lambda event, instance: instance(event) ) ]
|
||||
|
||||
self.event_handlers = [
|
||||
( self.state,
|
||||
"on",
|
||||
lambda cls, event, prefix: (
|
||||
[ getattr(cls, x)(
|
||||
event) for x in dir(cls)
|
||||
if x.startswith(prefix)
|
||||
and "{}_{}".format(
|
||||
prefix,
|
||||
event.event_type()
|
||||
) == x ] ) ),
|
||||
( bot_event,
|
||||
"on",
|
||||
lambda cls, event, prefix: (
|
||||
[ getattr(cls, x)(
|
||||
event) for x in dir(cls)
|
||||
if x.startswith(prefix)
|
||||
and "{}_{}".format(
|
||||
prefix,
|
||||
event.event_type()
|
||||
) == x ] ) ),
|
||||
( self,
|
||||
"on",
|
||||
lambda cls, event, prefix: (
|
||||
super()._dispatcher(
|
||||
cls.connection, event.inner() ) ) ) ]
|
||||
|
||||
C.SimpleIRCClient.__init__(self)
|
||||
|
||||
'''
|
||||
appends an event to the event_history list
|
||||
'''
|
||||
def add_event_to_event_buffer(self, event):
|
||||
self.l.info("{}".format(event) )
|
||||
|
||||
self.state.event_history.append(event)
|
||||
|
||||
return(event)
|
||||
|
||||
'''
|
||||
override for _dispatcher method (jaraco super class)
|
||||
attempts to find an executable callback handler from
|
||||
self.event_handlers
|
||||
'''
|
||||
def _dispatcher(self, connection, event):
|
||||
try:
|
||||
mcmxi_event = bot_event(
|
||||
event_type = event.type,
|
||||
bot = self,
|
||||
data = event.arguments,
|
||||
target = event.target,
|
||||
inner = event)
|
||||
[ (
|
||||
event.type,
|
||||
event.arguments,
|
||||
cls,
|
||||
prefix,
|
||||
wrapper(
|
||||
cls = cls,
|
||||
event = self.add_event_to_event_buffer(mcmxi_event),
|
||||
prefix = prefix) ) for cls, prefix, wrapper in self.event_handlers ]
|
||||
|
||||
except Exception as ex:
|
||||
self.l.exception(ex)
|
||||
|
||||
'''
|
||||
bot channel loop, each channel has it's own thread bot_connected this loop and awaits
|
||||
events from it's allocated command_queue. The call to get on the command queue
|
||||
blocks until it receives an item.
|
||||
'''
|
||||
def process_work_queue(self, channel) -> None:
|
||||
while self.state.bot_connected:
|
||||
try:
|
||||
'''
|
||||
This is a blocking call, if the queue is empty it waits until
|
||||
the first event is available before succeeding, good for
|
||||
this kind of a loop
|
||||
'''
|
||||
event = self.state.command_queue[channel.channel_name()].get()
|
||||
|
||||
if event.is_fantasy_command():
|
||||
handled = False
|
||||
|
||||
for module_instance, method_prefix, call_wrapper in self.executables:
|
||||
try:
|
||||
self.l.debug("{instance} {prefix} {wrapper} ".format(
|
||||
instance = module_instance,
|
||||
prefix = method_prefix,
|
||||
wrapper = call_wrapper ) )
|
||||
|
||||
UTIL.executor(
|
||||
self,
|
||||
module_instance,
|
||||
method_prefix,
|
||||
call_wrapper,
|
||||
event)
|
||||
|
||||
handled = True
|
||||
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
if not handled:
|
||||
IRC_TXT.color_say(
|
||||
self,
|
||||
IRC_TXT.fallback,
|
||||
channel.channel_name(),
|
||||
ITER.cycle(IRC_TXT.GREEN) )
|
||||
|
||||
elif event.arguments_contain_urls():
|
||||
HTTP.cmd_display_www_page_title(event)
|
||||
|
||||
except Exception as ex:
|
||||
if not self.bot_connected:
|
||||
self.l.debug("self.bot_connected is False, aborting thread")
|
||||
return
|
||||
|
||||
IRC_TXT.color_say(
|
||||
self,
|
||||
IRC_TXT.fallback,
|
||||
channel.channel_name(),
|
||||
ITER.cycle(IRC_TXT.RED) )
|
||||
|
||||
self.l.exception(ex)
|
||||
|
||||
finally:
|
||||
if self.state.bot_connected:
|
||||
TIME.sleep(channel.command_delay() )
|
||||
self.state.command_queue[ channel.channel_name() ].task_done()
|
||||
|
||||
'''
|
||||
Main bot loop, do not use super().start,
|
||||
this is abortable whereas super().start
|
||||
uses a "process_forever" that doesn't
|
||||
have an abort condition for it's thread
|
||||
'''
|
||||
def start(self):
|
||||
while not self.state.closing:
|
||||
if not self.state.bot_connected:
|
||||
self.state.on_disconnect()
|
||||
|
||||
if (self.state.current_retry_count + 1
|
||||
> self.state.current_server.retry_connect_max_tries() ):
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
self.reactor.process_once()
|
||||
|
||||
TIME.sleep(0.2)
|
||||
|
||||
'''
|
||||
Connect factory
|
||||
'''
|
||||
def connect(self):
|
||||
try:
|
||||
self.state.current_server = self.conf.next_server()
|
||||
|
||||
super().connect(
|
||||
server = self.state.current_server.address(),
|
||||
port = self.state.current_server.port(),
|
||||
nickname = self.state.current_server.parent().nickname(),
|
||||
password = self.state.current_server.parent().password(),
|
||||
username = self.state.current_server.parent().username() )
|
||||
|
||||
self.state.bot_connected = True
|
||||
|
||||
except Exception as ex:
|
||||
self.l.debug("connection failed {} ".format(ex))
|
||||
self.state.bot_connected = False
|
||||
|
||||
finally:
|
||||
return(self)
|
||||
|
||||
'''
|
||||
Disconnects the IRC connection and shuts down all of the bot threads
|
||||
'''
|
||||
def disconnect(self, closing = None):
|
||||
try:
|
||||
if self.state.closing != None:
|
||||
self.state.closing = True
|
||||
|
||||
if self.state.bot_connected:
|
||||
self.state.bot_connected = False
|
||||
|
||||
# process_work_queue blocks on a call to queue get, this will unblock it
|
||||
# and the loop will continue, causing the condition to be re-checked,
|
||||
# which will cause the loop to break and the thread will abort
|
||||
[ self.state.command_queue.get(queue).put(None) for queue in self.state.command_queue.keys() ]
|
||||
|
||||
self.connection.quit()
|
||||
|
||||
except Exception as ex:
|
||||
self.l.exception(ex)
|
||||
|
||||
finally:
|
||||
return(self)
|
||||
|
||||
'''
|
||||
'''
|
||||
@staticmethod
|
||||
def start_bots():
|
||||
bots = []
|
||||
|
||||
bots = [ ( bot, ( lambda t: ( t, t.start() )
|
||||
)( T.Thread(target = bot.start) )[ 0 ]
|
||||
) for bot in [ mcmxi(config).connect(
|
||||
) for config in bot_config().networks() ] ]
|
||||
|
||||
return(bots)
|
||||
|
||||
'''
|
||||
'''
|
||||
@staticmethod
|
||||
def stop_bots(bots):
|
||||
[ bot.disconnect(closing = True) for bot, _ in bots ]
|
||||
|
||||
if __name__ == "__main__":
|
||||
l = UTIL.setup_logging(bot_config())
|
||||
|
||||
bots = mcmxi.start_bots()
|
||||
|
||||
l.info("bot is initialized, press C-d at any point for a smooth and easy exit, starting REPL...")
|
||||
|
||||
IPYSH()
|
||||
|
||||
mcmxi.stop_bots(bots)
|
||||
S.exit()
|
18
mcmxi/objects/__init__.py
Normal file
18
mcmxi/objects/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
#
|
||||
from mcmxi.objects.bot_event import bot_event
|
||||
from mcmxi.objects.bot_config import bot_config
|
||||
from mcmxi.objects.channel import channel
|
||||
from mcmxi.objects.network import network
|
||||
from mcmxi.objects.owner import owner
|
||||
from mcmxi.objects.server import server
|
||||
from mcmxi.objects.user import user
|
||||
from mcmxi.objects.bot_state import bot_state
|
||||
|
||||
__all__ = [ "bot_state",
|
||||
"bot_event",
|
||||
"bot_config",
|
||||
"channel",
|
||||
"network",
|
||||
"owner",
|
||||
"server",
|
||||
"user" ]
|
41
mcmxi/objects/bot_config.py
Normal file
41
mcmxi/objects/bot_config.py
Normal file
@ -0,0 +1,41 @@
|
||||
import yaml as Y
|
||||
from mcmxi.objects.network import network
|
||||
|
||||
'''
|
||||
'''
|
||||
class bot_config():
|
||||
'''
|
||||
'''
|
||||
def __init__(self,
|
||||
conf = Y.load(
|
||||
open('conf.yml'),
|
||||
Loader = Y.FullLoader
|
||||
).get('bot') ):
|
||||
self.conf = conf
|
||||
self._networks = [ network(list(zip(*x.items() )
|
||||
), self) for x in self.conf.get('networks') ]
|
||||
|
||||
'''
|
||||
'''
|
||||
def name_servers(self):
|
||||
return(self.conf.get('dns_servers') )
|
||||
|
||||
'''
|
||||
'''
|
||||
def bot_name(self):
|
||||
return(self.conf.get('name') )
|
||||
|
||||
'''
|
||||
'''
|
||||
def jsonbin_config(self):
|
||||
return(self.conf.get('jsonbin') )
|
||||
|
||||
'''
|
||||
'''
|
||||
def networks(self):
|
||||
return(self._networks)
|
||||
|
||||
'''
|
||||
'''
|
||||
def log_level(self):
|
||||
return(self.conf.get('log_level'))
|
1384
mcmxi/objects/bot_event.py
Normal file
1384
mcmxi/objects/bot_event.py
Normal file
File diff suppressed because it is too large
Load Diff
101
mcmxi/objects/bot_state.py
Normal file
101
mcmxi/objects/bot_state.py
Normal file
@ -0,0 +1,101 @@
|
||||
import collections as COL
|
||||
import logging as L
|
||||
import time as TIME
|
||||
from mcmxi.objects.channel import channel
|
||||
|
||||
class bot_state():
|
||||
command_queue = {}
|
||||
channel_threads = {}
|
||||
targets = []
|
||||
event_history = COL.deque(maxlen = 1000)
|
||||
bot_connected = False
|
||||
closing = False
|
||||
current_retry_count = 0
|
||||
l = L.getLogger(__name__)
|
||||
|
||||
'''
|
||||
returns true if command queue has unfinished tasks or false if not
|
||||
'''
|
||||
def command_queue_has_unfinished_tasks(self, event):
|
||||
return(self.command_queue[ event.target() ].unfinished_tasks > 0)
|
||||
|
||||
'''
|
||||
'''
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
'''
|
||||
channel joined event
|
||||
'''
|
||||
def on_join(self, event):
|
||||
if (event.source_nickname() == event.bot().connection.get_nickname()
|
||||
and len([x for x in self.targets if x == event.target ]) == 0):
|
||||
|
||||
c = [x for x in event.bot().conf.channels() if x.channel_name() == event.target()]
|
||||
|
||||
self.targets.append(c != None
|
||||
and next(enumerate(c))
|
||||
or channel(None, None, target = event.target() ) )
|
||||
|
||||
if self.command_queue.get(event.target()) == None:
|
||||
self.command_queue[event.target()] = Q.Queue(1)
|
||||
|
||||
'''
|
||||
start channel worker thread
|
||||
'''
|
||||
if self.channel_threads.get(event.target()) == None:
|
||||
chan = T.Thread(target = lambda: event.bot().process_work_queue(event.target()) )
|
||||
|
||||
chan.start()
|
||||
|
||||
self.channel_threads[event.target()] = chan
|
||||
|
||||
'''
|
||||
Disconnected event
|
||||
'''
|
||||
def on_disconnect(self, event = None) -> None:
|
||||
if self.bot_connected:
|
||||
self.bot_connected = False
|
||||
|
||||
while not self.closing and not self.bot_connected:
|
||||
if self.bot.connect().state.bot_connected:
|
||||
self.l.debug("connected")
|
||||
self.current_retry_count = 0
|
||||
break
|
||||
|
||||
if self.current_retry_count + 1 > self.current_server.retry_connect_max_tries():
|
||||
self.l.debug("max retry count exceeded, giving up")
|
||||
break
|
||||
|
||||
self.current_retry_count = self.current_retry_count + 1
|
||||
|
||||
self.l.debug("reconnect failed try {} of {} will retry in {} seconds".format(
|
||||
self.current_retry_count,
|
||||
self.current_server.retry_connect_max_tries(),
|
||||
self.current_server.retry_connect_delay() ) )
|
||||
|
||||
TIME.sleep(self.current_server.retry_connect_delay() )
|
||||
|
||||
'''
|
||||
NAMES list (response) for a channel event
|
||||
'''
|
||||
def on_namreply(self, event) -> None:
|
||||
ch_type, channel, nick_list = event.inner().arguments
|
||||
|
||||
if channel == '*':
|
||||
# User is not in any visible channel
|
||||
# http://tools.ietf.org/html/rfc2812#section-3.2.5
|
||||
self.l.debug("user is not in any visible channel")
|
||||
return
|
||||
|
||||
c = next(enumerate([x for x in event.bot().conf.channels() if x.channel_name() == event.target()] ) )
|
||||
|
||||
for nick in nick_list.split():
|
||||
nick_modes = []
|
||||
|
||||
if nick[ 0 ] in event.bot().connection.features.prefix:
|
||||
nick_modes.append(self.connection.features.prefix[nick[ 0 ] ] )
|
||||
nick = nick[ 1: ]
|
||||
|
||||
for mode in nick_modes:
|
||||
pass
|
40
mcmxi/objects/channel.py
Normal file
40
mcmxi/objects/channel.py
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
class channel():
|
||||
def __init__(self, chan, parent_config, target=None):
|
||||
if chan != None:
|
||||
self._channel_name = chan[0][0]
|
||||
self.conf = chan[1][0]
|
||||
self._parent = parent_config
|
||||
elif target != None:
|
||||
self._channel_name = target
|
||||
|
||||
|
||||
self.mode_mask = 0
|
||||
|
||||
def parent(self):
|
||||
return(self._parent)
|
||||
|
||||
def channel_name(self):
|
||||
return(self._channel_name)
|
||||
|
||||
def modes(self):
|
||||
return(self.conf.get("modes") )
|
||||
|
||||
def command_prefix(self):
|
||||
return(self.conf.get("command_prefix") )
|
||||
|
||||
def url_preview(self):
|
||||
return(self.conf.get("preview_url") )
|
||||
|
||||
def protect_channel_topic(self):
|
||||
return(self.conf.get("protect_topic") )
|
||||
|
||||
def ignore_list(self):
|
||||
return(self.conf.get("ignore") )
|
||||
|
||||
def is_source_ignored(self, source):
|
||||
return(len([index for index in self.conf.get("ignore") or [] if index == source]) > 0)
|
||||
|
||||
def command_delay(self):
|
||||
return(float(self.conf.get("command_delay") or 0.0 ) )
|
||||
|
8
mcmxi/objects/hostmask.py
Normal file
8
mcmxi/objects/hostmask.py
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
'''
|
||||
nick!username@hostname
|
||||
*!username@hostname
|
||||
'''
|
||||
class host_mask():
|
||||
def __init__(self):
|
||||
pass
|
44
mcmxi/objects/network.py
Normal file
44
mcmxi/objects/network.py
Normal file
@ -0,0 +1,44 @@
|
||||
import itertools as ITER
|
||||
|
||||
from mcmxi.objects.server import server
|
||||
from mcmxi.objects.owner import owner
|
||||
from mcmxi.objects.user import user
|
||||
from mcmxi.objects.channel import channel
|
||||
|
||||
class network():
|
||||
def __init__(self, network, parent_config):
|
||||
self.network_name = network[0][0]
|
||||
self.conf = network[1][0]
|
||||
self._servers = ITER.cycle( [server(list(zip(*x.items() ) ), self)
|
||||
for x in self.conf.get("servers") ] )
|
||||
self._owners = [owner(list(zip(*x.items() ) ), self)
|
||||
for x in self.conf.get("owners") ]
|
||||
self._users = [user(list(zip(*x.items() ) ), self)
|
||||
for x in self.conf.get("users") ]
|
||||
self._channels = [channel(list(zip(*x.items() ) ), self)
|
||||
for x in self.conf.get("channels") ]
|
||||
self._parent = parent_config
|
||||
|
||||
def channels(self):
|
||||
return(self._channels)
|
||||
|
||||
def username(self):
|
||||
return(self.conf.get("username"))
|
||||
|
||||
def nickname(self):
|
||||
return(self.conf.get("nickname") )
|
||||
|
||||
def alt_nickname(self):
|
||||
return(self.conf.get("alt_nickname") )
|
||||
|
||||
def next_server(self):
|
||||
return(next(self._servers) )
|
||||
|
||||
def password(self):
|
||||
return(self.conf.get("password") )
|
||||
|
||||
def owners(self):
|
||||
return(self._owners)
|
||||
|
||||
def parent(self):
|
||||
return(self._parent)
|
17
mcmxi/objects/owner.py
Normal file
17
mcmxi/objects/owner.py
Normal file
@ -0,0 +1,17 @@
|
||||
class owner():
|
||||
def __init__(self, owner, parent_config):
|
||||
self._owner_name = owner[0][0]
|
||||
self.conf = owner[1][0]
|
||||
self._parent = parent_config
|
||||
|
||||
def hostmask(self):
|
||||
return(self.conf.get('hostmask') )
|
||||
|
||||
def owner_name(self):
|
||||
return(self._owner_name)
|
||||
|
||||
def owner_password(self):
|
||||
return(self.conf.get('password') )
|
||||
|
||||
def parent(self):
|
||||
return(self._parent)
|
23
mcmxi/objects/server.py
Normal file
23
mcmxi/objects/server.py
Normal file
@ -0,0 +1,23 @@
|
||||
class server():
|
||||
def __init__(self, server, parent_config):
|
||||
self.server_name = server[0][0]
|
||||
self.conf = server[1][0]
|
||||
self._parent = parent_config
|
||||
|
||||
def address(self):
|
||||
return(self.conf.get('address') )
|
||||
|
||||
def port(self):
|
||||
return(self.conf.get('port') )
|
||||
|
||||
def parent(self):
|
||||
return(self._parent)
|
||||
|
||||
def retry_connect(self):
|
||||
return(self.conf.get('retry_connect') or False)
|
||||
|
||||
def retry_connect_delay(self):
|
||||
return(self.conf.get('retry_connect_delay') or 1)
|
||||
|
||||
def retry_connect_max_tries(self):
|
||||
return(self.conf.get('retry_connect_max_tries') or 1)
|
17
mcmxi/objects/user.py
Normal file
17
mcmxi/objects/user.py
Normal file
@ -0,0 +1,17 @@
|
||||
class user():
|
||||
def __init__(self, user, parent_config):
|
||||
self._user_name = user[0][0]
|
||||
self.conf = user[1][0]
|
||||
self._parent = parent_config
|
||||
|
||||
def parent(self):
|
||||
return(self._parent)
|
||||
|
||||
def user_name(self):
|
||||
return(self._user_name)
|
||||
|
||||
def hostmask(self):
|
||||
return(self.conf.get('hostmask') )
|
||||
|
||||
def modes(self):
|
||||
return(self.conf.get('modes'))
|
43
mcmxi/term_bin.py
Normal file
43
mcmxi/term_bin.py
Normal file
@ -0,0 +1,43 @@
|
||||
import socket as SOCK
|
||||
import logging as L
|
||||
|
||||
'''
|
||||
'''
|
||||
class term_bin():
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.l = L.getLogger(__name__)
|
||||
|
||||
'''
|
||||
Posts data to TermBin and returns a URL to the term-paste
|
||||
'''
|
||||
def term_bin(self, data) -> "":
|
||||
self.l.debug(data)
|
||||
|
||||
sock = SOCK.socket(
|
||||
SOCK.AF_INET,
|
||||
SOCK.SOCK_STREAM)
|
||||
|
||||
server_address = (
|
||||
'termbin.com',
|
||||
9999)
|
||||
|
||||
sock.connect(server_address)
|
||||
|
||||
sock.sendall(bytes(
|
||||
"{}".format(data),
|
||||
"UTF-8") )
|
||||
|
||||
sock.sendall(bytes(
|
||||
"\0",
|
||||
"UTF-8") )
|
||||
|
||||
return(sock.recv(
|
||||
4096
|
||||
).decode(
|
||||
).replace(
|
||||
'\r\n',
|
||||
'\n') )
|
||||
|
||||
TERM_BIN = term_bin()
|
242
mcmxi/utilities.py
Normal file
242
mcmxi/utilities.py
Normal file
@ -0,0 +1,242 @@
|
||||
import itertools as ITER
|
||||
import sys as S
|
||||
import logging as L
|
||||
import urllib.parse as URL
|
||||
import requests as WWW
|
||||
from pyroute2.iproute import IPRoute as IP
|
||||
from ipaddress import IPv4Network as N4, IPv6Network as N6
|
||||
from mcmxi.irc_text_formatting import IRC_TXT
|
||||
from dns import resolver as DNS
|
||||
'''
|
||||
'''
|
||||
class utilities():
|
||||
DNS = DNS.Resolver()
|
||||
|
||||
'''
|
||||
Determine whether type of f is one of the many function or method types
|
||||
'''
|
||||
is_func_or_meth = lambda f: (
|
||||
TYPES.FunctionType
|
||||
== type(f)
|
||||
or TYPES.BuiltinFunctionType
|
||||
== type(f)
|
||||
or TYPES.BuiltinMethodType
|
||||
== type(f)
|
||||
or TYPES.MethodType
|
||||
== type(f)
|
||||
or TYPES.LambdaType
|
||||
== type(f)
|
||||
) and True or False
|
||||
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.l = L.getLogger(__name__)
|
||||
|
||||
# Executes a method call for a given command, for any
|
||||
# modules whitelisted in self.executables
|
||||
self.executor = (
|
||||
lambda bot, obj, method_prefix, call_wrapper, event: call_wrapper(
|
||||
event,
|
||||
obj.__getattribute__(next(filter(lambda t: (
|
||||
method_prefix != ""
|
||||
and "{prefix}_{command}"
|
||||
or "{prefix}{command}").format(
|
||||
prefix = method_prefix,
|
||||
command = t[ 0 ]
|
||||
) == t[ 1 ] and t[ 1 ], zip(ITER.cycle([event.data(
|
||||
).arguments[ 0 ].split(" "
|
||||
)[ 0 ].strip(next(filter(lambda x: x.channel_name(
|
||||
).lower(
|
||||
) == event.target().lower(
|
||||
), bot.conf.channels() )
|
||||
).command_prefix() ) ]
|
||||
), dir(obj) ) )
|
||||
)[ 1 ] ) ) )
|
||||
|
||||
'''
|
||||
grabs an arbitrary HTTP/s response
|
||||
'''
|
||||
def grab(self, url, event = None, depth = 0) -> "":
|
||||
self.l.debug(url)
|
||||
|
||||
req = WWW.get(
|
||||
url,
|
||||
stream = True,
|
||||
allow_redirects = False)
|
||||
|
||||
self.l.debug(req)
|
||||
|
||||
if req.is_redirect:
|
||||
if self.verify_address(req.headers.get("Location"
|
||||
) or req.headers.get("location")
|
||||
) and depth <= 4:
|
||||
|
||||
return(self.grab(req.headers.get("Location"
|
||||
) or req.headers.get("location"), event, depth + 1) )
|
||||
|
||||
else:
|
||||
raise Exception("no redirect location specified or too many redirects")
|
||||
|
||||
ret = []
|
||||
|
||||
try:
|
||||
[ ret.append(index) for index in req.iter_content(
|
||||
chunk_size = 8192,
|
||||
decode_unicode = True)
|
||||
if ( len(bytes("".join(ret), "UTF-8") ) < 500000 * 4 )
|
||||
or ( _ for _ in () ).throw(StopIteration) ]
|
||||
|
||||
except StopIteration:
|
||||
self.l.debug("max size read")
|
||||
|
||||
return("".join(ret) )
|
||||
|
||||
'''
|
||||
Verify that the destination URL doesn't resolve to something
|
||||
unreasonable, and that it doesn't resolve to any addresses
|
||||
assigned to the server running the bot
|
||||
'''
|
||||
def verify_address(self, url) -> bool:
|
||||
addr = None
|
||||
try:
|
||||
addr = N6("{}/128".format(self.DNS.resolve(URL.urlparse(url
|
||||
).netloc, rdtype = "AAAA"
|
||||
).response.answer.pop(
|
||||
).to_text(
|
||||
).split(
|
||||
).pop() ) )
|
||||
|
||||
except:
|
||||
try:
|
||||
addr = N4("{}/32".format(self.DNS.resolve(URL.urlparse(url
|
||||
).netloc, rdtype = "A"
|
||||
).response.answer.pop(
|
||||
).to_text(
|
||||
).split(
|
||||
).pop() ) )
|
||||
|
||||
except Exception as ex:
|
||||
self.l.exception(ex)
|
||||
|
||||
finally:
|
||||
return(addr != None
|
||||
and addr.is_global
|
||||
and not addr.is_reserved
|
||||
and not addr.is_multicast
|
||||
and not addr.is_link_local
|
||||
and not addr.is_private
|
||||
and not addr.is_unspecified
|
||||
and len([x.get("attrs"
|
||||
)[ 0 ][ 1 ] for x in IP(
|
||||
).get_addr() if x.get("attrs")[ 0 ][ 1 ] == addr.network_address]) == 0)
|
||||
|
||||
'''
|
||||
Guesses the value type of a parameter (parameterization of calls to method calls from IRC via commands)
|
||||
'''
|
||||
def guess_type(self, s) -> type:
|
||||
try:
|
||||
value = AST.literal_eval(s)
|
||||
|
||||
except ValueError or SyntaxError:
|
||||
self.l.debug("computed type {}".format(str) )
|
||||
return str
|
||||
|
||||
else:
|
||||
self.l.debug("computed type {}".format(type(value) ) )
|
||||
return type(value)
|
||||
|
||||
'''
|
||||
Sets up logging formatter and handler
|
||||
'''
|
||||
@staticmethod
|
||||
def setup_logging(conf):
|
||||
formatter = L.Formatter(
|
||||
fmt = "%(levelname)s|%(module)s->%(funcName)s %(message)s" )
|
||||
|
||||
handler = L.StreamHandler(S.stderr)
|
||||
|
||||
handler.setFormatter(
|
||||
formatter )
|
||||
|
||||
logger = L.getLogger()
|
||||
|
||||
if conf.log_level() == "debug":
|
||||
logger.setLevel(L.DEBUG)
|
||||
elif conf.log_level() == "info":
|
||||
logger.setLevel(L.INFO)
|
||||
logger.addHandler(handler)
|
||||
|
||||
return logger
|
||||
|
||||
'''
|
||||
Gives information about an expanded CIDR prefix
|
||||
'''
|
||||
def cmd_cidr(self, event) -> None:
|
||||
[ event.bot().connection.privmsg(event.target(), x) for x in map(lambda c: '''
|
||||
{prefix}
|
||||
{_s_} {_s_} {bold} size: {reset}
|
||||
{_s_}{underline} {num_addresses} {reset} {_s_} {bold} start: {reset}
|
||||
{_s_}{underline} {start} {reset} {_s_} {bold} end: {reset}
|
||||
{_s_}{underline} {end} {reset} {_s_} {bold} netmask: {reset}
|
||||
{_s_}{underline} {netmask} {reset} {_s_} {bold} global: {reset}
|
||||
{_s_}{underline} {_global} {reset} {_s_} {bold} private: {reset}
|
||||
{_s_}{underline} {private} {reset} {_s_} {bold} link-local: {reset}
|
||||
{_s_}{underline} {link_local} {reset} {_s_} {bold} multicast: {reset}
|
||||
{_s_}{underline} {multicast} {reset} {_s_} {bold} unspecified: {reset}
|
||||
{_s_}{underline} {unspecified} {reset} {_s_} {bold} supernet: {reset}
|
||||
{_s_}{underline} {supernet} {reset}
|
||||
'''.replace(
|
||||
"\n",
|
||||
""
|
||||
).replace(
|
||||
" ",
|
||||
""
|
||||
).strip(
|
||||
" "
|
||||
).format(
|
||||
_s_ = " ",
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
underline = IRC_TXT.IRC_FMT_UNDERLINE,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
prefix = IRC_TXT.meander,
|
||||
num_addresses = c.num_addresses,
|
||||
start = c.network_address.exploded,
|
||||
end = c.broadcast_address,
|
||||
netmask = c.with_netmask,
|
||||
_global = c.is_global,
|
||||
private = c.is_private,
|
||||
link_local = c.is_link_local,
|
||||
multicast = c.is_multicast,
|
||||
unspecified = c.is_unspecified,
|
||||
supernet = c.supernet()
|
||||
), [ x.find(":") != -1
|
||||
and N6(x)
|
||||
or N4(x)
|
||||
for x in event.event_arguments() ] ) ]
|
||||
|
||||
'''
|
||||
Resolve DNS for a hostname
|
||||
'''
|
||||
def cmd_dns(self, event) -> None:
|
||||
self.l.debug(event)
|
||||
|
||||
args = event.event_arguments()
|
||||
rdtype = (len(args) > 1) and args[ 0 ] or "A"
|
||||
Q = (len(args) > 1) and args[ 1 ] or args[ 0 ]
|
||||
|
||||
answer = self.DNS.resolve(
|
||||
rdtype = rdtype,
|
||||
qname = Q)
|
||||
|
||||
event.bot().connection.privmsg(
|
||||
event.target(),
|
||||
"{prefix} {bold}{c_name}{reset} @{name_server} rrset: {rr_set} ".format(
|
||||
prefix = IRC_TXT.meander,
|
||||
bold = IRC_TXT.IRC_FMT_BOLD,
|
||||
reset = IRC_TXT.IRC_FMT_RESET,
|
||||
c_name = answer.canonical_name,
|
||||
name_server = answer.nameserver,
|
||||
rr_set = answer.rrset.to_text() ) )
|
||||
|
||||
UTIL = utilities()
|
Loading…
Reference in New Issue
Block a user