├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── data ├── config.sample.yaml ├── irc │ └── irc.sample.yaml └── matrix │ └── matrix.sample.yaml ├── python ├── client.py ├── common.py ├── config.sample.py ├── docker │ └── Dockerfile ├── main.py ├── modules │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ └── bot.py │ ├── commands │ │ ├── __init__.py │ │ ├── acg.py │ │ ├── api.py │ │ ├── arch.py │ │ ├── blug.py │ │ ├── common.py │ │ ├── handy.py │ │ ├── ime.py │ │ ├── lang.py │ │ ├── multiline.py │ │ ├── simple.py │ │ ├── tool.py │ │ └── util.py │ └── timeoutdict.py ├── toxclient.py └── toxmain.py └── src ├── base ├── command.pest ├── command.rs ├── context.rs ├── message.rs └── mod.rs ├── bin ├── irc.rs └── matrix.rs ├── client ├── irc.pest ├── irc.rs ├── matrix.pest ├── matrix.rs └── mod.rs ├── command ├── api.rs ├── config.rs ├── input.rs ├── language.rs ├── mod.rs ├── music.rs ├── random.rs ├── scheme.rs └── utility.rs └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | 3 | __pycache__/ 4 | 5 | config.py 6 | 7 | 8 | # rust 9 | 10 | debug/ 11 | target/ 12 | 13 | Cargo.lock 14 | 15 | config.yaml 16 | irc.yaml 17 | matrix.yaml 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ircbot" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | aes = "0.8" 10 | anyhow = "1.0" 11 | async-trait = "0.1" 12 | base64 = "0.21" 13 | cbc = "0.1" 14 | chrono = { version = "0.4", features = ["serde"] } 15 | futures = { version = "0.3", default-features = false } 16 | #futures-channel = { version = "0.3", default-features = false } 17 | fuzzy-matcher = "0.3" 18 | infer = "0.15" 19 | #irc = "0.15" 20 | irc = { git = "https://github.com/aatxe/irc", branch = "develop", default-features = false, features = [ 21 | "ctcp", 22 | "tls-native", 23 | "yaml_config", 24 | ] } 25 | #matrix-sdk = "0.6" 26 | matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "main" } 27 | mime = "0.3" 28 | num-bigint-dig = "0.8" 29 | percent-encoding = "2.3" 30 | pest = "2.7" 31 | pest_derive = "2.7" 32 | quick-xml = { version = "0.30", features = ["serialize"] } 33 | rand = "0.8" 34 | regex = "1.9" 35 | reqwest = { version = "0.11", features = ["cookies", "json"] } 36 | serde = { version = "1.0", features = ["derive"] } 37 | serde_json = { version = "1.0", features = ["raw_value"] } 38 | serde_yaml = "0.9" 39 | tempfile = "3.8" 40 | thiserror = "1.0" 41 | tokio = { version = "1.32", features = [ 42 | "macros", 43 | "process", 44 | "rt-multi-thread", 45 | "sync", 46 | ] } 47 | tokio-stream = "0.1" 48 | tracing = "0.1" 49 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 50 | url = "2.4" 51 | wana_kana = "3.0" 52 | 53 | #[profile.release] 54 | #lto = false 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ircbot 2 | -------------------------------------------------------------------------------- /data/config.sample.yaml: -------------------------------------------------------------------------------- 1 | command: {} 2 | 3 | interpreter: {} 4 | 5 | router: {} 6 | -------------------------------------------------------------------------------- /data/irc/irc.sample.yaml: -------------------------------------------------------------------------------- 1 | owners: [''] 2 | nickname: '' 3 | realname: '' 4 | 5 | username: '' 6 | password: '' 7 | 8 | server: '' 9 | port: 0 10 | -------------------------------------------------------------------------------- /data/matrix/matrix.sample.yaml: -------------------------------------------------------------------------------- 1 | user_id: '' 2 | password: '' 3 | -------------------------------------------------------------------------------- /python/client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import collections 3 | import importlib 4 | 5 | import bottom 6 | 7 | from common import dePrefix, Normalize, splitmessage 8 | 9 | 10 | class Client(bottom.Client): 11 | 12 | def __init__(self, config): 13 | self.config = importlib.import_module(config) 14 | super().__init__(self.config.host, self.config.port, **self.config.option) 15 | # https://github.com/numberoverzero/bottom/issues/60 16 | self._events = collections.defaultdict(lambda: asyncio.Event()) 17 | 18 | self.admin = self.config.admin 19 | self.nick = self.config.nick 20 | self.login = self.config.login 21 | self.password = self.config.password 22 | self.channel = self.config.channel 23 | self.key = self.config.key 24 | 25 | # (512 - 2) / 3 = 170 26 | # 430 bytes should be safe 27 | # not really, for #archlinux-cn-offtopic 28 | self.msglimit = 420 29 | 30 | self.deprefix = dePrefix() 31 | self.normalize = Normalize() 32 | self.modules = importlib.import_module('modules') 33 | 34 | ## fully async 35 | #async def trigger(self, event, **kwargs): 36 | # partials = self.__partials__[event] 37 | # tasks = [func(**kwargs) for func in partials] 38 | # if not tasks: 39 | # return 40 | # asyncio.async(asyncio.wait(tasks)) 41 | 42 | def reload(self): 43 | self.modules = importlib.reload(self.modules) 44 | self.config = importlib.reload(self.config) 45 | self.key = self.config.key 46 | 47 | def sendm(self, target, message, *, command='PRIVMSG', to='', raw=False, mlimit=0, color=None, **kw): 48 | prefix = (to + ': ') if to else '' 49 | message = ('' if raw else prefix) + self.normalize(message, **kw) 50 | print('send: {}'.format(repr(message))) 51 | for (i, m) in enumerate(splitmessage(message, self.msglimit)): 52 | if mlimit > 0 and i >= mlimit: 53 | self.send(command, target=target, message=prefix + '太多了啦...') 54 | break 55 | self.send(command, target=target, message=m) 56 | 57 | #def sendl(self, target, line, n, *, llimit=0, loffset=0, **kw): 58 | # sent = False 59 | 60 | # if loffset > 0: 61 | # pass 62 | 63 | # for (i, m) in enumerate(line): 64 | # if n > 0 and i >= n: 65 | # break 66 | # if llimit > 0 and i >= llimit: 67 | # #d = {k: kw[k] for k in ['command', 'to'] if k in kw} 68 | # #self.sendm(target, '太长了啦...', **d) 69 | # command = kw.get('command', 'PRIVMSG') 70 | # to = kw.get('to', '') 71 | # prefix = (to + ': ') if to else '' 72 | # self.send(command, target=target, message=prefix + '太长了啦...') 73 | # break 74 | # self.sendm(target, m, **kw) 75 | # sent = True 76 | # if not sent: 77 | # raise Exception() 78 | 79 | #def sender(self, target, content, *, n=-1, llimit=-1, loffest=-1, **kw): 80 | # if n < 0: 81 | # self.sendm(target, content, **kw) 82 | # else: 83 | # d = {} 84 | # if llimit >= 0: 85 | # d['llimit'] = llimit 86 | # if loffset >=0: 87 | # d['loffset'] = loffset 88 | # self.sendl(target, content, n, **d, **kw) 89 | # #if llimit < 0: 90 | # # self.sendl(target, content, n, **kw) 91 | # #else: 92 | # # self.sendl(target, content, n, llimit=llimit, **kw) 93 | 94 | def sendl(self, target, line, n, *, llimit=0, **kw): 95 | sent = False 96 | for (i, m) in enumerate(line): 97 | if llimit > 0 and i >= llimit: 98 | #d = {k: kw[k] for k in ['command', 'to'] if k in kw} 99 | #self.sendm(target, '太长了啦...', **d) 100 | command = kw.get('command', 'PRIVMSG') 101 | to = kw.get('to', '') 102 | prefix = (to + ': ') if to else '' 103 | self.send(command, target=target, message=prefix + '太长了啦...') 104 | break 105 | self.sendm(target, m, **kw) 106 | sent = True 107 | if n > 0 and i >= (n - 1): 108 | break 109 | if not sent: 110 | raise Exception() 111 | 112 | def sender(self, target, content, *, n=-1, llimit=-1, **kw): 113 | if n < 0: 114 | self.sendm(target, content, **kw) 115 | else: 116 | if llimit < 0: 117 | self.sendl(target, content, n, **kw) 118 | else: 119 | self.sendl(target, content, n, llimit=llimit, **kw) 120 | -------------------------------------------------------------------------------- /python/common.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # use tex line breaking algorithm? 4 | # send to pastebin when message is too long? 5 | 6 | 7 | class dePrefix: 8 | 9 | def __init__(self): 10 | #self.r = re.compile(r'(?:(\[)?(?P.+?)(?(1)\]|:) )?(?P.*)') 11 | #self.r = re.compile(r'(\[(?P.+?)\] )?((?P[^\s\']+?): )?(?P.*)') 12 | #self.r = re.compile(r'(\[(?P.+?)\] )?((?P[^\'"]+?)[:,] )?(?P.*)') 13 | #self.r = re.compile(r'((?:(?P\[)|(?P\())(?P.+?)(?(s)\])(?(r)\)) )?((?P[^\'"]+?)[:,] )?(?P.*)', re.DOTALL) 14 | #self.r = re.compile(r'((?:(?P\[)|(?P\())(?P.+?)(?(s)\])(?(r)\)) )?((?P[^\'"].*?)[:,] )?(?P.*)', re.DOTALL) 15 | self.r = re.compile(r'((?:(?P\[)|(?P\())(?P.+?)(?(s)\])(?(r)\)) )?((?P[^\'"].*?)(?:[:,] | さん、))?(?P.*)', re.DOTALL) 16 | self.esc = re.compile(r'(\x03\d{1,2}(,\d{1,2})?|\x02|\x03|\x04|\x06|\x07|\x0f|\x16|\x1b|\x1d|\x1f)') 17 | #self.orz = re.compile(r'((?:(?P\[))(?P[\x00-\x1f].+?[\x00-\x1f])(?(s)\]) )?((?P[^\'"]+?)[:,] )?(?P.*)', re.DOTALL) 18 | self.orz = re.compile(r'((?:(?P\[))(?P[\x00-\x1f].+?[\x00-\x1f])(?(s)\]) )?((?P[^\'"].*?)[:,] )?(?P.*)', re.DOTALL) 19 | 20 | def __call__(self, n, m): 21 | # orizon 22 | r = self.orz.fullmatch(m).groupdict() 23 | if r['nick']: 24 | r['nick'] = self.esc.sub('', r['nick']) 25 | else: 26 | r = self.r.fullmatch(self.esc.sub('', m)).groupdict() 27 | #return (r['to'].strip() if r['to'] else r['nick'].strip() if r['nick'] else n, r['message']) 28 | return (r['to'] or r['nick'] or n, r['message']) 29 | 30 | 31 | class Normalize: 32 | 33 | def __init__(self, stripspace=True, stripline=True, newline='\\x0304\\n\\x0f ', convert=True, escape=True): 34 | self.stripspace = stripspace 35 | self.stripline = stripline 36 | self.newline = newline 37 | self.convert = convert 38 | self.escape = escape 39 | 40 | self.alias = str.maketrans({ 41 | ' ': ' ', 42 | ',': ', ', 43 | '。': '. ', 44 | '!': '! ', 45 | '?': '? ', 46 | ':': ': ', 47 | ';': '; ', 48 | '(': ' (', 49 | ')': ') ', 50 | }) 51 | self.esc = [ 52 | # irssi 53 | # https://github.com/irssi/irssi/blob/master/src/fe-common/core/formats.c#L1086 54 | # IS_COLOR_CODE(...) 55 | # https://github.com/irssi/irssi/blob/master/src/fe-common/core/formats.c#L1254 56 | # format_send_to_gui(...) 57 | (r'\x02', '\x02'), 58 | (r'\x03', '\x03'), 59 | (r'\x04', '\x04'), 60 | (r'\x06', '\x06'), 61 | (r'\x07', '\x07'), 62 | (r'\x0f', '\x0f'), 63 | (r'\x16', '\x16'), 64 | (r'\x1b', '\x1b'), 65 | (r'\x1d', '\x1d'), 66 | (r'\x1f', '\x1f'), 67 | ] 68 | self.colorreg = re.compile(r'(\\x03\d{1,2}(,\d{1,2})?)') 69 | 70 | def __call__(self, message, *, stripspace=None, stripline=None, newline=None, convert=None, escape=None): 71 | if stripspace == None: stripspace = self.stripspace 72 | if stripline == None: stripline = self.stripline 73 | if newline == None: newline = self.newline 74 | if convert == None: convert = self.convert 75 | if escape == None: escape = self.escape 76 | 77 | #lines = str(message).splitlines() if stripline else [str(message)] 78 | l = str(message).translate(self.alias) if convert else str(message) 79 | lines = l.splitlines() if stripline else [l] 80 | if stripspace: 81 | lines = map(lambda l: ' '.join(l.split()), lines) 82 | if stripline: 83 | lines = filter(lambda l: l, lines) 84 | line = newline.join(lines) 85 | #if convert: 86 | # line = line.translate(self.alias) 87 | if escape: 88 | for (s, e) in self.esc: 89 | line = line.replace(s, e) 90 | else: 91 | line = self.colorreg.sub('', line) 92 | for (s, e) in self.esc: 93 | line = line.replace(s, '') 94 | return line 95 | 96 | 97 | def splitmessage(s, n): 98 | print('split message: {} {}'.format(n, repr(s))) 99 | # rules 100 | starting = '' 101 | ending = '' 102 | 103 | # zh 104 | starting += '、。〃〆〕〗〞﹚﹜!"%'),.:;?!]}~' 105 | ending += '〈《「『【〔〖〝﹙﹛$(.[{£¥' 106 | 107 | # ja 108 | starting += '}〕〉》」』】〙〗〟⦆' + 'ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻' + '゠〜・、。' 109 | ending += '{〔〈《「『【〘〖〝⦅' 110 | 111 | # en 112 | ending += 'abcdefghijklmnopqrstuvwxyz' 113 | ending += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 114 | starting += ')]}>' 115 | ending += '([{<' 116 | 117 | # num 118 | ending += '0123456789' 119 | 120 | # esc 121 | esc = ['\x02', '\x03', '\x04', '\x06', '\x07', '\x0f', '\x16', '\x1b', '\x1d', '\x1f'] 122 | 123 | if n < 4: 124 | return 125 | 126 | bs = s.encode('utf-8') 127 | 128 | # need splitting 129 | # n should be large enough for a single character 130 | while len(bs) > n: 131 | i = n + 1 132 | 133 | # find good ending with one extra character 134 | while i < len(bs) and (bs[i] & 0xc0) == 0x80: 135 | i = i + 1 136 | 137 | # result candidate 138 | str = bs[:i].decode('utf-8') 139 | 140 | i = len(str) 141 | j = i - 1 142 | j0 = j 143 | 144 | #print('bs = {}'.format(repr(bs))) 145 | #print('j = {}'.format(j)) 146 | 147 | # naive coloring 148 | # too naive, assuming following operations don't violate coloring 149 | if sum((1 if x in esc else 0) for x in str[:j] if x in esc) % 2 != 0: 150 | while str[j - 1] not in esc: 151 | j = j - 1 152 | j = j - 1 153 | i = j + 1 154 | j1 = j 155 | 156 | # check first character in next line and last character in this line 157 | while (0 <= j and str[j] in starting) or (1 <= j and str[j - 1] in ending): 158 | # if too short or cannot find good line 159 | # according to arXiv:1208.6109, average word length is about 5 160 | # for poisson distribution, 5 \sigma is [0, 30] 161 | if j <= 0 or 30 <= (i - 1) - j: 162 | j = i - 1 163 | break 164 | # otherwise 165 | j = j - 1 166 | 167 | #print('j = {}'.format(j)) 168 | 169 | if j == 0: 170 | if j1 == 0: 171 | j = j0 172 | else: 173 | j = j1 174 | 175 | str = str[:j] 176 | 177 | yield str 178 | 179 | bs = bs[len(str.encode('utf-8')):] 180 | 181 | yield bs.decode('utf-8') 182 | -------------------------------------------------------------------------------- /python/config.sample.py: -------------------------------------------------------------------------------- 1 | 2 | host = 'chat.freenode.net' 3 | port = 6667 4 | 5 | option = {} 6 | 7 | admin = 'nick' 8 | nick = 'nick' 9 | login = 'nick' 10 | password = '' 11 | channel = '#chan' 12 | 13 | key = { 14 | 'baidu' : '', 15 | 'baiduocr' : '', 16 | 'microsoft' : '', 17 | 'translator' : ('', ''), 18 | 'google' : '', 19 | 'googleseid' : '', 20 | 'collins' : '', 21 | 'mashape' : '', 22 | 'breezo' : '', 23 | 'jsonwhois' : '', 24 | 'wolfram' : '', 25 | 'pm25' : '', 26 | 'howtospeak' : '', 27 | 'hackerearth' : '', 28 | } 29 | -------------------------------------------------------------------------------- /python/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Dockerfile for ircbot 3 | # 4 | 5 | FROM alpine 6 | MAINTAINER user 7 | 8 | RUN set -ex && \ 9 | apk add --no-cache --virtual .build-deps \ 10 | build-base \ 11 | libxml2-dev \ 12 | libxslt-dev \ 13 | python3-dev && \ 14 | apk add --no-cache --virtual .run-deps \ 15 | ca-certificates \ 16 | libxml2 \ 17 | libxslt \ 18 | python3 \ 19 | py3-pip && \ 20 | update-ca-certificates && \ 21 | pip3 install aiohttp \ 22 | bottom \ 23 | demjson3 \ 24 | dicttoxml \ 25 | html5lib \ 26 | lxml \ 27 | pycryptodome \ 28 | romkan && \ 29 | apk del .build-deps && \ 30 | rm -rf /tmp/* && \ 31 | 32 | mkdir /data && \ 33 | chown -R nobody:nobody /data 34 | 35 | USER nobody 36 | 37 | VOLUME /data 38 | 39 | CMD python3 -u /data/ircbot/python/main.py 40 | -------------------------------------------------------------------------------- /python/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import tracemalloc 3 | 4 | import client 5 | 6 | 7 | tracemalloc.start() 8 | 9 | bot = client.Client('config') 10 | 11 | 12 | @bot.on('CLIENT_CONNECT') 13 | async def connect(**kwargs): 14 | bot.send('NICK', nick=bot.login) 15 | bot.send('PASS', password=bot.password) 16 | bot.send('USER', user=bot.login, realname='Bot using bottom.py') 17 | 18 | 19 | @bot.on('PING') 20 | async def keepalive(message, **kwargs): 21 | bot.send('PONG', message=message) 22 | 23 | 24 | @bot.on('PRIVMSG') 25 | async def privmsg_commands(nick, target, message, **kwargs): 26 | #if nick == bot.nick: 27 | # return 28 | #if 'condy' in nick.lower() or 'flandre' in nick.lower() or 'youmu' in nick.lower(): 29 | # return 30 | #if '#linux-cn' == target: 31 | # return 32 | if any(n in nick.lower() for n in ['labots']): 33 | return 34 | 35 | (nick, message) = bot.deprefix(nick, message) 36 | if target == bot.nick: 37 | sender = lambda m, **kw: bot.sender(nick, m, **kw) 38 | else: 39 | sender = lambda m, **kw: bot.sender(target, m, to=nick, **kw) 40 | 41 | coros = [f(bot, nick, message, sender) for f in bot.modules.commands.privmsg] 42 | 43 | await asyncio.wait([asyncio.create_task(c) for c in coros]) 44 | 45 | 46 | @bot.on('PRIVMSG') 47 | async def privmsg_admin(nick, target, message, **kwargs): 48 | if target == bot.nick: 49 | sender = lambda m, **kw: bot.sender(nick, m, **kw) 50 | else: 51 | sender = lambda m, **kw: bot.sender(target, m, to=nick, **kw) 52 | 53 | coros = [f(bot, nick, message, sender) for f in bot.modules.admin.privmsg] 54 | 55 | await asyncio.wait([asyncio.create_task(c) for c in coros]) 56 | 57 | 58 | bot.loop.create_task(bot.connect()) 59 | bot.loop.run_forever() 60 | -------------------------------------------------------------------------------- /python/modules/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | path = 'modules.' 4 | files = ['timeoutdict', 'commands', 'admin'] 5 | modules = [importlib.reload(importlib.import_module(path + f)) for f in files] 6 | table = dict(zip(files, modules)) 7 | 8 | privmsg = sum((getattr(m, 'privmsg', []) for m in modules), []) 9 | -------------------------------------------------------------------------------- /python/modules/admin/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | 4 | path = 'modules.admin.' 5 | files = ['bot'] 6 | modules = [importlib.reload(importlib.import_module(path + f)) for f in files] 7 | 8 | 9 | privmsg = [ 10 | modules[0].reload, 11 | modules[0].status, 12 | ] 13 | -------------------------------------------------------------------------------- /python/modules/admin/bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import itertools 3 | import traceback 4 | import tracemalloc 5 | 6 | 7 | async def reload(bot, nick, message, send): 8 | if nick != bot.admin or message != "'!reload": 9 | return 10 | 11 | try: 12 | bot.reload() 13 | send('reloaded') 14 | except: 15 | trace = traceback.format_exc() 16 | print(trace) 17 | trace = trace.splitlines() 18 | send('error: traceback {} lines'.format(len(trace))) 19 | if len(trace) > 10: 20 | send('...') 21 | send(trace[-10:], n=0, stripspace=False) 22 | 23 | 24 | async def status(bot, nick, message, send): 25 | if nick != bot.admin or message != "'!status": 26 | return 27 | 28 | tasks = asyncio.Task.all_tasks() 29 | running = [t for t in tasks if not t.done()] 30 | done = [t for t in tasks if t.done()] 31 | send('tasks: total {}, running {}'.format(len(tasks), len(running))) 32 | send(itertools.chain( 33 | ('+ {}'.format(t) for t in running), 34 | ('- {}'.format(t) for t in done) 35 | ), n=10) 36 | 37 | snapshot = tracemalloc.take_snapshot() 38 | top = snapshot.statistics('lineno') 39 | send('memeory: total {} KiB'.format(sum(stat.size for stat in top) / 1024)) 40 | send(top, n=10) 41 | 42 | 43 | #async def dump(loop): 44 | # while True: 45 | # #print('dump lines') 46 | # #print(bot.lines) 47 | # print('----- dump -----') 48 | # all = asyncio.Task.all_tasks() 49 | # not_done = [t for t in all if not t.done()] 50 | # print('all: {0}, not done: {1}'.format(len(all), len(not_done))) 51 | # for t in all: 52 | # print('task:', t, gc.get_referrers(t)) 53 | # print('gc:', gc.garbage, gc.get_stats()) 54 | # try: 55 | # snapshot = tracemalloc.take_snapshot() 56 | # top = snapshot.statistics('lineno') 57 | # for stat in top[:10]: 58 | # print(stat) 59 | # total = sum(stat.size for stat in top) 60 | # print("Total allocated size: %.1f KiB" % (total / 1024)) 61 | # except Exception as e: 62 | # print(e) 63 | # await asyncio.sleep(10) 64 | -------------------------------------------------------------------------------- /python/modules/commands/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import importlib 3 | import inspect 4 | import os 5 | import re 6 | import time 7 | import traceback 8 | 9 | from ..timeoutdict import TimeoutDict 10 | 11 | 12 | path = 'modules.commands.' 13 | files = ['common', 'simple', 'util', 'tool', 'lang', 'api', 'ime', 'acg', 'handy', 'multiline', 'blug', 'arch'] 14 | modules = [importlib.reload(importlib.import_module(path + f)) for f in files] 15 | table = dict(zip(files, modules)) 16 | 17 | Get = table['common'].Get 18 | fetcher = table['multiline'].fetcher 19 | cmdsub = table['util'].cmdsub 20 | 21 | help = dict(sum((getattr(m, 'help', []) for m in modules), [])) 22 | 23 | 24 | async def helper(arg, send): 25 | c = arg['command'] 26 | if c: 27 | h = help[c] 28 | send('<...> is mandatory, [...] is optional, (...) also accepts multiline input') 29 | #send('\\x0300{0}:\\x0f {1}'.format(c, h)) 30 | send('\\x02{0}:\\x0f {1}'.format(c, h)) 31 | else: 32 | #send('\\x0300help:\\x0f help [command] -- "{0} 可是 14 岁的\\x0304萌妹子\\x0f哦" by anonymous'.format(arg['meta']['bot'].nick)) 33 | send('\\x02help:\\x0f help [command] -- "{0} 可是 14 岁的\\x0304萌妹子\\x0f哦" by anonymous'.format(arg['meta']['bot'].nick)) 34 | #send('(づ ̄ω ̄)づ -->> ' + ' '.join(sorted(help.keys()))) 35 | send('(っ‾ω‾)っ -->> ' + ' '.join(sorted(help.keys()))) 36 | #send('try "\\x0300help \\x1fcommand\\x1f\\x0f" to find out more~') 37 | 38 | 39 | def command(f, r): 40 | func = f if inspect.signature(f).parameters.get('lines') else (lambda arg, lines, send: f(arg, send)) 41 | reg = re.compile(r, re.IGNORECASE | re.DOTALL) 42 | 43 | async def wrap(message, lines, send, meta): 44 | arg = reg.fullmatch(message) 45 | if arg: 46 | try: 47 | name = f.__qualname__ 48 | except AttributeError: 49 | name = f.__class__.__qualname__ 50 | print('{0}: {1}'.format(name, arg.groupdict())) 51 | try: 52 | t = time.time() 53 | d = arg.groupdict() 54 | d.update(meta) 55 | await func(d, lines, send) 56 | print('time: {} s'.format(time.time() - t)) 57 | except Exception as e: 58 | err = ' sad story... ' + str(e) if str(e) else '' 59 | send('╮( ̄▽ ̄)╭' + err) 60 | traceback.print_exc() 61 | return False 62 | return True 63 | return False 64 | 65 | return wrap 66 | 67 | func = [command(f[0], f[1]) for f in sum((getattr(m, 'func', []) for m in modules), [(helper, r"help(\s+(?P\S+))?")])] 68 | 69 | 70 | class Line(TimeoutDict): 71 | 72 | def append(self, nick, l): 73 | self.__setitem__(nick, self.get(nick, []) + l) 74 | 75 | def __cleanup__(self, key): 76 | self.pop(key, None) 77 | 78 | line = Line() 79 | 80 | 81 | # queue will wait self.timeout starting from the last put before calling __cleanup__ 82 | class Queue(TimeoutDict): 83 | 84 | async def put(self, key, value, func): 85 | try: 86 | queue = self.__getitem__(key) 87 | except KeyError: 88 | queue = [asyncio.Queue(), asyncio.ensure_future(func(key))] 89 | await queue[0].put(value) 90 | self.__setitem__(key, queue) 91 | 92 | async def get(self, key, default=None): 93 | try: 94 | return (await self.__getitem__(key)[0].get()) 95 | except KeyError: 96 | return default 97 | 98 | def __delitem__(self, key): 99 | item = self.d.pop(key) 100 | item[0][1].cancel() 101 | item[1].cancel() 102 | 103 | # not safe to cleanup 104 | # if some command runs longer than timeout 105 | def __cleanup__(self, key): 106 | self.pop(key, None) 107 | print('queue clean up: {}'.format(key)) 108 | 109 | queue = Queue() 110 | 111 | 112 | async def execute(msg, lines, send, meta): 113 | msg = await cmdsub(meta['meta']['command'], msg) 114 | print('execute: {}'.format(repr(msg))) 115 | coros = [f(msg, lines, send, meta) for f in func] 116 | 117 | status = await asyncio.gather(*coros) 118 | return any(status) 119 | 120 | 121 | async def reply(bot, nick, message, send): 122 | # prefix 123 | if message[0] == "'": 124 | if message[:3] in ["'..", "'::"]: 125 | return 126 | sender = send 127 | elif message[0] == '"': 128 | sender = Get(lambda l: line.append(nick, l)) 129 | else: 130 | return 131 | 132 | msg = message[1:].rstrip() 133 | lines = line.pop(nick, []) 134 | meta = {'meta': { 135 | 'bot': bot, 136 | 'nick': nick, 137 | 'send': send, 138 | 'save': Get(lambda l: line.append(nick, l)), 139 | 'command': lambda msg, lines, send: execute(msg, lines, send, meta), 140 | }} 141 | print('reply: {} {}'.format(nick, repr(msg))) 142 | 143 | success = await execute(msg, lines, sender, meta) 144 | print('success?', success) 145 | 146 | if not success and lines: 147 | line.append(nick, lines) 148 | 149 | 150 | async def multiline(bot, nick, message, send): 151 | if message[:4] == "'.. " or message == "'..": 152 | msg = message[4:].rstrip() 153 | print('multiline: {}'.format(repr(msg))) 154 | l = [msg] 155 | line.append(nick, l) 156 | 157 | 158 | async def fetchline(bot, nick, message, send): 159 | if message[:4] == "':: ": 160 | msg = message[4:].rstrip() 161 | print('fetchline: {}'.format(repr(msg))) 162 | try: 163 | l = await fetcher(msg) 164 | line.append(nick, l) 165 | except: 166 | send('出错了啦...') 167 | traceback.print_exc() 168 | 169 | 170 | async def process(nick): 171 | while True: 172 | item = await queue.get(nick) 173 | 174 | if item == None: 175 | print('process return') 176 | return 177 | 178 | coros = [f(item[0], nick, item[1], item[2]) for f in [reply, multiline, fetchline]] 179 | # gather() cancels coros when process() is cancelled 180 | await asyncio.gather(*coros) 181 | 182 | 183 | async def dispatch(bot, nick, message, send): 184 | if message[:1] not in ["'", '"']: 185 | return 186 | if message[:2] == "'!": 187 | return 188 | 189 | await queue.put(nick, (bot, message, send), process) 190 | 191 | 192 | privmsg = [ 193 | #reply, 194 | #multiline, 195 | #fetchline, 196 | dispatch, 197 | ] 198 | -------------------------------------------------------------------------------- /python/modules/commands/arch.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone, timedelta 2 | 3 | from .tool import xml 4 | 5 | 6 | async def arch(arg, send): 7 | table = { 8 | 'gpg': 'rm -rf /etc/pacman.d/gnupg && pacman-key --init && pacman-key --populate' 9 | } 10 | 11 | send(table.get(arg['key'], 'Σ(っ °Д °;)っ 怎么什么都没有呀')) 12 | 13 | 14 | help = [ 15 | ] 16 | 17 | func = [ 18 | (arch , r"arch\s+(?P.+)"), 19 | ] 20 | -------------------------------------------------------------------------------- /python/modules/commands/blug.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone, timedelta 2 | 3 | from .tool import xml 4 | 5 | 6 | async def event(arg, send): 7 | n = int(arg['n'] or '1') 8 | arg.update({ 9 | 'n': str(n * 2 if arg['detail'] else n), 10 | 'url': 'https://beijinglug.club/?plugin=all-in-one-event-calendar&controller=ai1ec_exporter_controller&action=export_events&xml=true', 11 | 'xpath': '//vevent', 12 | }) 13 | def format(self, es): 14 | def parsedate(s): 15 | try: 16 | return datetime.strptime(s, '%Y%m%dT%H%M%S') 17 | except: 18 | return datetime.strptime(s, '%Y%m%d') 19 | def future(e): 20 | t = parsedate(e.xpath('./dtstart')[0].xpath('string()')) 21 | t = t.replace(tzinfo=timezone(timedelta(hours=8))) 22 | # being late by one hour is ok 23 | return datetime.now(timezone(timedelta(hours=8))) < t + timedelta(hours=1) 24 | def key(e): 25 | return parsedate(e.xpath('./dtstart')[0].xpath('string()')) 26 | def formatter(time, summary, description, location, coordinate, url): 27 | if arg['detail']: 28 | return [ 29 | '\\x02{0:%Y %b %d %a %H:%M}\\x0f {1} @ {2} [\\x0302 {3} \\x0f]'.format( 30 | time, 31 | summary, 32 | '{} ({})'.format(location, coordinate) if coordinate else location, 33 | url), 34 | description, 35 | ] 36 | else: 37 | return [ 38 | '\\x02{0:%b %d %a %H:%M}\\x0f {1} @ {2} [\\x0302 {3} \\x0f]'.format(time, summary, location.split(' @ ')[0], url), 39 | ] 40 | field = [ 41 | ('./dtstart', 'text', lambda x: parsedate(self.iter_first(x))), 42 | ('./summary', 'text', lambda x: self.iter_first(x).strip()), 43 | ('./description', 'text', lambda x: ' '.join(self.iter_first(x).strip().split())), 44 | ('./location', 'text', lambda x: self.iter_first(x).strip()), 45 | ('./geo', 'text', self.iter_first), 46 | ('./url/@uri', '', self.iter_first), 47 | ] 48 | 49 | # workaround for dtstart format 50 | new = [e for e in es[1:] if future(e)] 51 | if not new: 52 | raise Exception('we need more parties \o/') 53 | return (formatter(*self.get_fields(self.get, e, field)) for e in sorted(new, key=key)) 54 | 55 | return (await xml(arg, [], send, format_new=format)) 56 | 57 | 58 | help = [ 59 | ('blug' , 'blug (command is one of the following: event; see \'help blug- for detail)'), 60 | ('blug-event' , 'blug event [detail] [#max number][+offset]'), 61 | ] 62 | 63 | func = [ 64 | (event , r"blug\s+event(?:\s+(?Pdetail))?(\s+(#(?P\d+))?(\+(?P\d+))?)?"), 65 | ] 66 | -------------------------------------------------------------------------------- /python/modules/commands/common.py: -------------------------------------------------------------------------------- 1 | 2 | class Get: 3 | 4 | def __init__(self, add=None): 5 | self.line = [] 6 | self.add = add or self.line.extend 7 | 8 | def __call__(self, l, n=-1, **kw): 9 | if n < 0: 10 | self.add(l.splitlines() or ['']) 11 | else: 12 | for (i, m) in enumerate(l): 13 | self.add(m.splitlines() or ['']) 14 | if n > 0 and i >= (n - 1): 15 | break 16 | 17 | def str(self, sep='\n'): 18 | return sep.join(self.line) 19 | 20 | class GetRaw: 21 | 22 | def __init__(self): 23 | self.result = [] 24 | 25 | def __call__(self, l, n=-1, **kw): 26 | if n < 0: 27 | self.result.append(list(l)) 28 | else: 29 | for (i, m) in enumerate(l): 30 | self.result.append(list(m)) 31 | if n > 0 and i >= (n - 1): 32 | break 33 | if not self.result: 34 | raise Exception() 35 | -------------------------------------------------------------------------------- /python/modules/commands/ime.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import re 3 | from urllib.parse import quote 4 | 5 | # no kwa wha etc. 6 | # find an alternative? 7 | import romkan 8 | from aiohttp.client import ClientRequest 9 | 10 | from .tool import jsonxml 11 | 12 | # add fcitx? fim 13 | 14 | 15 | async def kana(arg, send): 16 | send(romkan.to_hiragana(arg['romaji'])) 17 | 18 | 19 | async def romaji(arg, send): 20 | send(romkan.to_roma(arg['kana'])) 21 | 22 | 23 | class IMGetter: 24 | 25 | def __init__(self): 26 | self.l = '' 27 | self.len = 0 28 | 29 | def __call__(self, l, n=-1, **kw): 30 | if n < 0: 31 | self.l += list(l) 32 | else: 33 | l = list(list(l)[0]) 34 | print('IMGetter: {}'.format(repr(l))) 35 | self.l += l[0] 36 | self.len = int(l[1] or 0) 37 | 38 | 39 | class IM: 40 | 41 | def __init__(self, Getter=None): 42 | self.prefix = "'" 43 | self.sep = re.compile(r"([^a-z']+)") 44 | self.valid = re.compile(r"[a-z']") 45 | self.letter = re.compile(r"[^']") 46 | self.comment = re.compile(r"(?:(?<=[^a-z'])|^)''(.*?)''(?:(?=[^a-z'])|$)") 47 | self.Get = Getter or IMGetter 48 | 49 | def special(self, e): 50 | return e[1:] 51 | 52 | async def request(self, e, get): 53 | pass 54 | 55 | def getpos(self, e, l): 56 | pass 57 | 58 | async def process(self, e): 59 | get = self.Get() 60 | while len(e) > 0: 61 | #print(e) 62 | await self.request(e, get) 63 | pos = self.getpos(e, get.len) 64 | e = e[pos:] 65 | return get.l 66 | 67 | async def getitem(self, e): 68 | if not self.valid.match(e): 69 | return e 70 | if e[0] == self.prefix: 71 | return self.special(e) 72 | 73 | return (await self.process(e)) 74 | 75 | async def __call__(self, input, send): 76 | print('im') 77 | 78 | l = [] 79 | pos = 0 80 | for m in self.comment.finditer(input): 81 | l.extend(self.sep.split(input[pos:m.start()])) 82 | #l.append("'" + m.group()[2:-2]) 83 | l.append(m.group()[1:-2]) 84 | pos = m.end() 85 | l.extend(self.sep.split(input[pos:])) 86 | #l = self.sep.split(input) 87 | print(l) 88 | 89 | coros = [self.getitem(e) for e in l] 90 | lines = await asyncio.gather(*coros) 91 | line = ''.join(lines) if lines else 'Σ(っ °Д °;)っ 怎么什么都没有呀' 92 | 93 | return send(line) 94 | 95 | 96 | class BIM(IM): 97 | 98 | def __init__(self): 99 | IM.__init__(self) 100 | self.arg = { 101 | 'n': '1', 102 | 'url': 'http://olime.baidu.com/py', 103 | 'xpath': '//result/item[1]/item[child::item]', 104 | } 105 | self.params = { 106 | 'inputtype': 'py', 107 | 'bg': '0', 108 | 'ed': '5', 109 | 'result': 'hanzi', 110 | 'resultcoding': 'unicode', 111 | 'ch_en': '0', 112 | 'clientinfo': 'web', 113 | 'version': '1', 114 | 'input': '', 115 | } 116 | self.format = lambda s, es: ([s.get_fields(s.get, e, [ 117 | ('./item[1]', 'text', s.iter_first), 118 | ('./item[2]', 'text', s.iter_first), 119 | ])] for e in es) 120 | 121 | async def request(self, e, get): 122 | params = self.params.copy() 123 | params['input'] = e 124 | await jsonxml(self.arg, [], get, params=params, format_new=self.format) 125 | 126 | def getpos(self, e, l): 127 | if not (0 < l and l < len(e)): 128 | return len(e) 129 | for (i, c) in enumerate(self.letter.finditer(e)): 130 | if i == l: 131 | return c.start() 132 | return len(e) 133 | 134 | async def __call__(self, arg, send): 135 | await IM.__call__(self, arg['pinyin'], send) 136 | 137 | bim = BIM() 138 | 139 | 140 | class IMNEW: 141 | 142 | def __init__(self, Getter=None): 143 | self.Get = Getter or IMGetter 144 | 145 | async def request(self, e, get): 146 | pass 147 | 148 | def getpos(self, e, l): 149 | pass 150 | 151 | async def process(self, e): 152 | get = self.Get() 153 | while len(e) > 0: 154 | #print(e) 155 | await self.request(e, get) 156 | pos = self.getpos(e, get.len) 157 | e = e[pos:] 158 | return get.l 159 | 160 | async def getitem(self, e): 161 | if e[0]: 162 | return (await self.process(e[1])) 163 | else: 164 | return e[1] 165 | 166 | async def __call__(self, input, send): 167 | print('im') 168 | 169 | coros = [self.getitem(e) for e in input] 170 | lines = await asyncio.gather(*coros) 171 | line = ''.join(lines) if lines else 'Σ(っ °Д °;)っ 怎么什么都没有呀' 172 | print(line) 173 | 174 | return send(line) 175 | 176 | 177 | class GIMNEW(IMNEW): 178 | 179 | # our url does not conform with url standard 180 | class RequestGoogle(ClientRequest): 181 | 182 | def __init__(self, method, url, *, params=None, **kw): 183 | ClientRequest.__init__(self, method, url, params=params, **kw) 184 | # note the extra quote for ',' 185 | if params: 186 | #p = '&'.join('='.join((i[0], i[1].replace(',', quote(quote(','))))) for i in params.items()) 187 | #self.url._val = self.url._val._replace(query=p) 188 | p = [(i[0], i[1].replace(',', quote(quote(',')))) for i in params.items()] 189 | self.url = url.with_query(p).with_fragment(None) 190 | 191 | def __init__(self, itc): 192 | IMNEW.__init__(self) 193 | self.arg = { 194 | 'n': '1', 195 | 'url': 'https://inputtools.google.com/request', 196 | # is always well formed? 197 | 'xpath': '/root/item[2]/item[1]', 198 | } 199 | self.params = { 200 | 'itc': itc, 201 | 'num': '1', 202 | 'cp': '0', 203 | 'cs': '0', 204 | 'ie': 'utf-8', 205 | 'oe': 'utf-8', 206 | 'app': 'demopage', 207 | 'text': '', 208 | } 209 | self.format = lambda s, es: ([s.get_fields(s.get, e, [ 210 | ('./item[2]/item[1]', 'text', s.iter_first), 211 | ('./item[3]/item[1]', 'text', s.iter_first), 212 | ])] for e in es) 213 | 214 | async def request(self, e, get): 215 | params = self.params.copy() 216 | params['text'] = e 217 | await jsonxml(self.arg, [], get, method='POST', params=params, request_class=GIMNEW.RequestGoogle, format_new=self.format) 218 | 219 | def getpos(self, e, l): 220 | print('getpos: {0} {1}'.format(e, l)) 221 | if not (0 < l and l < len(e)): 222 | return len(e) 223 | return l 224 | 225 | async def __call__(self, input, send): 226 | await IMNEW.__call__(self, input, send) 227 | 228 | 229 | async def gimnew(arg, send): 230 | table = { 231 | # pinyin 232 | 'pinyins': 'zh-t-i0-pinyin', 233 | 'pinyint': 'zh-hant-t-i0-pinyin', 234 | # wubi 235 | 'wubi': 'zh-t-i0-wubi-1986', 236 | # shuangpin 237 | # TODO emacs ZIRANMA.el where ' is used 238 | 'shuangpinabc': 'zh-t-i0-pinyin-x0-shuangpin-abc', 239 | 'shuangpinms': 'zh-t-i0-pinyin-x0-shuangpin-ms', 240 | 'shuangpinflypy': 'zh-t-i0-pinyin-x0-shuangpin-flypy', 241 | 'shuangpinjiajia': 'zh-t-i0-pinyin-x0-shuangpin-jiajia', 242 | 'shuangpinziguang': 'zh-t-i0-pinyin-x0-shuangpin-ziguang', 243 | 'shuangpinziranma': 'zh-t-i0-pinyin-x0-shuangpin-ziranma', 244 | # zhuyin 245 | 'zhuyin': 'zh-hant-t-i0-und', 246 | # for blackberry layout 247 | 'zhuyinbb': 'zh-hant-t-i0-und', 248 | # cangjie 249 | 'cangjie': 'zh-hant-t-i0-cangjie-1982', 250 | # yue 251 | 'yue': 'yue-hant-t-i0-und', 252 | # ja 253 | 'ja': 'ja-t-ja-hira-i0-und', 254 | } 255 | alias = { 256 | # default 257 | 'chs': 'pinyins', 258 | 'cht': 'pinyint', 259 | 'pinyin': 'pinyins', 260 | 'shuangpin': 'shuangpinflypy', 261 | 'udpn': 'shuangpinziranma', 262 | # less is more 263 | 'py': 'pinyins', 264 | 'wb': 'wubi', 265 | 'sp': 'shuangpinflypy', 266 | 'zrm': 'shuangpinziranma', 267 | 'zy': 'zhuyin', 268 | 'cj': 'cangjie', 269 | # alias 270 | 'ggtt': 'wubi', 271 | 'vtpc': 'shuangpinabc', 272 | 'udpnms': 'shuangpinms', 273 | 'ulpb': 'shuangpinflypy', 274 | 'ihpl': 'shuangpinjiajia', 275 | 'igpy': 'shuangpinziguang', 276 | 'udpnzrm': 'shuangpinziranma', 277 | '5j4up=': 'zhuyin', 278 | 'rhnyoo$': 'zhuyinbb', 279 | 'oiargrmbc': 'cangjie', 280 | 'yut': 'yue', 281 | # alt 282 | 'jp': 'ja', 283 | } 284 | 285 | def parse(reg, text, f, g): 286 | line = [] 287 | pos = 0 288 | for m in reg.finditer(text): 289 | #print('parse: {}'.format(repr(text[pos:m.start()]))) 290 | line.extend(f(text[pos:m.start()])) 291 | line.extend(g(m.group())) 292 | pos = m.end() 293 | line.extend(f(text[pos:])) 294 | 295 | return line 296 | 297 | def replace(text, rule): 298 | if not rule: 299 | return text 300 | (f, t) = rule[0] 301 | parts = text.split(f) 302 | return t.join(replace(part, rule[1:]) for part in parts) 303 | 304 | try: 305 | lang = arg['lang'] or 'chs' 306 | lang = alias.get(lang, lang) 307 | itc = table[lang] 308 | except: 309 | #raise Exception("this method is not supported yet...") 310 | raise Exception("Do you REALLY need this input method?") 311 | 312 | if lang == 'zhuyin': 313 | sep = re.compile(r"([^a-z'0-9\-;,./=]+)") 314 | comment = re.compile(r"(?:(?<=[^a-z'0-9\-;,./=])|^)''(.*?)''(?:(?=[^a-z'0-9\-;,./=])|$)") 315 | elif lang == 'zhuyinbb': 316 | sep = re.compile(r"([^a-z'0$]+)") 317 | comment = re.compile(r"(?:(?<=[^a-z'0$])|^)''(.*?)''(?:(?=[^a-z'0$])|$)") 318 | elif lang == 'ja': 319 | sep = re.compile(r"([^a-z'\-]+)") 320 | comment = re.compile(r"(?:(?<=[^a-z'\-])|^)''(.*?)''(?:(?=[^a-z'\-])|$)") 321 | elif lang == 'shuangpinabc' or lang == 'shuangpinms' or lang == 'shuangpinflypy' or lang == 'shuangpinjiajia' or lang == 'shuangpinziguang' or lang == 'shuangpinziranma': 322 | sep = re.compile(r"([^a-z;]+)") 323 | comment = re.compile(r"(?:(?<=[^a-z;])|^)''(.*?)''(?:(?=[^a-z;])|$)") 324 | else: 325 | sep = re.compile(r"([^a-z']+)") 326 | comment = re.compile(r"(?:(?<=[^a-z'])|^)''(.*?)''(?:(?=[^a-z'])|$)") 327 | 328 | text = arg['text'] 329 | 330 | line = parse(comment, text, 331 | lambda t: parse(sep, t, 332 | #lambda x: [(True, e) for e in x.split("'")] if x != '' and x[0].islower() else [(False, x)], 333 | # for zhuyin 334 | lambda x: [(True, e) for e in x.split("'")] if x != '' else [(False, x)], 335 | lambda x: [(False, x)] 336 | ), 337 | lambda t: [(False, t[2:-2])] 338 | ) 339 | 340 | if lang == 'ja': 341 | tmp = [] 342 | for e in line: 343 | if e[0]: 344 | tmp.append((e[0], romkan.to_hiragana(e[1]))) 345 | else: 346 | tmp.append(e) 347 | line = tmp 348 | elif lang == 'zhuyinbb': 349 | tmp = [] 350 | for e in line: 351 | if e[0]: 352 | t = [ 353 | ('aa', 'z'), 354 | ('dd', 'f'), 355 | ('ee', 'r'), 356 | ('ii', 'o'), 357 | ('jj', ','), 358 | ('kk', '.'), 359 | ('ll', '/'), 360 | ('oo', 'p'), 361 | ('qq', 'q'), 362 | ('rr', 't'), 363 | ('ss', 'x'), 364 | ('uu', 'i'), 365 | ('ww', 'w'), 366 | ('xx', 'v'), 367 | ( 'a', 'a'), 368 | ( 'b', 'm'), 369 | ( 'c', 'b'), 370 | ( 'd', 'd'), 371 | ( 'e', 'e'), 372 | ( 'f', 'g'), 373 | ( 'g', 'h'), 374 | ( 'h', 'j'), 375 | ( 'i', '9'), 376 | ( 'j', 'k'), 377 | ( 'k', 'l'), 378 | ( 'l', ';'), 379 | ( 'm', '7'), 380 | ( 'n', '4'), 381 | ( 'o', '0'), 382 | ( 'p', '-'), 383 | ( 'q', '1'), 384 | ( 'r', '5'), 385 | ( 's', 's'), 386 | ( 't', 'y'), 387 | ( 'u', '8'), 388 | ( 'v', 'n'), 389 | ( 'w', '2'), 390 | ( 'x', 'c'), 391 | ( 'y', 'u'), 392 | ( 'z', '6'), 393 | ( '0', '3'), 394 | ( '$', '='), 395 | ] 396 | tmp.append((e[0], replace(e[1], t))) 397 | else: 398 | tmp.append(e) 399 | line = tmp 400 | print(line) 401 | 402 | im = GIMNEW(itc) 403 | await im(line, send) 404 | 405 | 406 | class BIMNEW(IMNEW): 407 | 408 | def __init__(self): 409 | IMNEW.__init__(self) 410 | self.arg = { 411 | 'n': '1', 412 | 'url': 'http://olime.baidu.com/py', 413 | 'xpath': '//result/item[1]/item[child::item]', 414 | } 415 | self.params = { 416 | 'inputtype': 'py', 417 | 'bg': '0', 418 | 'ed': '5', 419 | 'result': 'hanzi', 420 | 'resultcoding': 'unicode', 421 | 'ch_en': '0', 422 | 'clientinfo': 'web', 423 | 'version': '1', 424 | 'input': '', 425 | } 426 | self.letter = re.compile(r"[^']") 427 | self.format = lambda s, es: ([s.get_fields(s.get, e, [ 428 | ('./item[1]', 'text', s.iter_first), 429 | ('./item[2]', 'text', s.iter_first), 430 | ])] for e in es) 431 | 432 | async def request(self, e, get): 433 | params = self.params.copy() 434 | params['input'] = e 435 | await jsonxml(self.arg, [], get, params=params, format_new=self.format) 436 | 437 | def getpos(self, e, l): 438 | if not (0 < l and l < len(e)): 439 | return len(e) 440 | for (i, c) in enumerate(self.letter.finditer(e)): 441 | if i == l: 442 | return c.start() 443 | return len(e) 444 | 445 | async def __call__(self, input, send): 446 | await IMNEW.__call__(self, input, send) 447 | 448 | 449 | async def bimnew(arg, send): 450 | def parse(reg, text, f, g): 451 | line = [] 452 | pos = 0 453 | for m in reg.finditer(text): 454 | line.extend(f(text[pos:m.start()])) 455 | line.extend(g(m.group())) 456 | pos = m.end() 457 | line.extend(f(text[pos:])) 458 | 459 | return line 460 | 461 | def replace(text, rule): 462 | if not rule: 463 | return text 464 | (f, t) = rule[0] 465 | parts = text.split(f) 466 | return t.join(replace(part, rule[1:]) for part in parts) 467 | 468 | sep = re.compile(r"([^a-z']+)") 469 | comment = re.compile(r"(?:(?<=[^a-z'])|^)''(.*?)''(?:(?=[^a-z'])|$)") 470 | 471 | text = arg['text'] 472 | 473 | line = parse(comment, text, 474 | lambda t: parse(sep, t, 475 | lambda x: [(True, e) for e in x.split("'")] if x != '' else [(False, x)], 476 | lambda x: [(False, x)] 477 | ), 478 | lambda t: [(False, t[2:-2])] 479 | ) 480 | print(line) 481 | 482 | im = BIMNEW() 483 | await im(line, send) 484 | 485 | 486 | help = [ 487 | ('bim' , 'bim (a valid pinyin starts with a lower case letter, followed by lower case letters or \'; use \'\' in pair for comment)'), 488 | ('gim' , 'gim[:lang] (a valid text consists some lower case letters and other symbols; use \' for word breaking; use \'\' in pair for comment)'), 489 | #('kana' , 'kana '), 490 | #('romaji' , 'romaji '), 491 | ] 492 | 493 | func = [ 494 | #(bim , r"bim\s+(?P.+)"), 495 | #(bimnew , r"bim\s+(?P.+)"), 496 | #(gimnew , r"gim(?::(?P\S+))?\s+(?P.+)"), 497 | #(kana , r"kana\s+(?P.+)"), 498 | #(romaji , r"romaji\s+(?P.+)"), 499 | ] 500 | -------------------------------------------------------------------------------- /python/modules/commands/multiline.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from urllib.parse import urlsplit 4 | 5 | from .common import Get 6 | from .tool import fetch, html, regex 7 | 8 | 9 | async def getcode(url): 10 | site = { 11 | # raw 12 | 'cfp.vim-cn.com': '.', 13 | 'p.vim-cn.com': '.', 14 | 'ix.io': '.', 15 | 'sprunge.us': '.', 16 | 'ptpb.pw': '.', 17 | 'fars.ee': '.', 18 | # parse 19 | 'paste.ubuntu.com': '//*[@id="contentColumn"]/div/div/div/table/tbody/tr/td[2]/div/pre', 20 | 'pastebin.ubuntu.com': '//*[@id="contentColumn"]/div/div/div/table/tbody/tr/td[2]/div/pre', 21 | 'paste.kde.org': '//*[@id="show"]/div[1]/div/div[2]/div', 22 | 'paste.opensuse.org': '//*[@id="content"]/div[2]/div[2]/div', 23 | 'susepaste.org': '//*[@id="content"]/div[2]/div[2]/div', 24 | 'paste.fedoraproject.org': '//*[@id="paste_form"]/div[1]/div/div[3]', 25 | 'www.fpaste.org': '//*[@id="paste_form"]/div[1]/div/div[3]', 26 | 'codepad.org': '/html/body/div/table/tbody/tr/td/div[1]/table/tbody/tr/td[2]/div/pre', 27 | 'bpaste.net': '//*[@id="paste"]/div/table/tbody/tr/td[2]/div', 28 | 'pastebin.com': '//*[@id="paste_code"]', 29 | #'pastebin.com': '//*[@id="selectable"]/div', 30 | 'code.bulix.org': '//*[@id="contents"]/pre', 31 | 'dpaste.com': '//*[@id="content"]/table/tbody/tr/td[2]/div/pre', 32 | 'paste.debian.net': '//*[@id="content"]/table/tbody/tr/td[2]/div/pre', 33 | 'www.refheap.com': '//*[@id="paste"]/table/tbody/tr/td[2]/div/pre', 34 | 'ideone.com': '//*[@id="source"]/pre/ol/li/div', 35 | 'gist.github.com': '//*[@class="file"]/div[2]/table/tbody/tr/td[contains(@class, "blob-code")]', 36 | 'lpaste.net': '//*[@id="paste"]/div/div[3]/table/tbody/tr/td[2]/pre', 37 | 'paste.xinu.at': '//*[@id="wrap"]/div[3]/div[2]/div/pre/div/span', 38 | #'notepad.cc': '//*[@id="contents"]', 39 | 'paste.pound-python.org': '//*[@id="paste"]/div/ol/li/span', 40 | 'justpaste.it': '//*[@id="articleContent"]/div/p', 41 | #'thepasteb.in' 42 | # what is this? it should be raw 43 | #'ptpb.pw': '/html/body/div/table/tbody/tr/td[2]/div/pre', 44 | 'dpaste.de': '/html/body/div/div[3]/ol/li', 45 | #'paste.wentropy.com' 46 | 'paste.codedump.ch': '/html/body/div[1]/section[2]/div/div/blockquote/div/ol/li', 47 | 'pastebin.mozilla.org': '//*[@id="content"]/div/div/ol/li', 48 | #'pasted.co': '//*[@id="thepaste"]', 49 | 'pastebin.ca': '//*[@id="source"]/ol/li/div', 50 | 'pasteall.org': '//*[@id="originalcode"]', 51 | 'paste2.org': '//*[@id="content"]/div[1]/ol/li/div', 52 | #'pastebin.anthonos.org': '/html/body/div[2]/div/div/div/ol/li/div', 53 | #'nopaste.me' 54 | 'paste.gnome.org': '//*[@id="show"]/div/div/div[2]/div/ol/li/div', 55 | 'paste.ee': '//*[@id="section0"]/div/div[2]/div[2]/pre/code', 56 | 'paste.mheaven.ch': '/html/body/table/tbody/tr/td[2]/div/pre', 57 | #'hastebin.com' 58 | #'paste.ttlab.de' 59 | #'paste.arza.us' 60 | #'www.irccloud.com': '/html/body/div/div[2]/div/div/div[2]/div[2]/div/div[3]/div', 61 | #'zerobin.net'? 62 | #'0bin.net': '//*[@id="paste-content"]', 63 | # https://paste.metalgamer.eu/ 64 | } 65 | 66 | get = Get() 67 | u = urlsplit(url) 68 | xpath = site[u[1]] 69 | if xpath == '.': 70 | arg = {'url': url, 'regex': r'(.*)(?:\n|$)', 'n': '0'} 71 | await regex(arg, [], get) 72 | else: 73 | arg = {'url': url, 'xpath': xpath, 'n': '0'} 74 | await html(arg, [], get) 75 | 76 | return get.line 77 | 78 | 79 | async def geturl(msg): 80 | #reg = re.compile(r"(?PGET|POST)\s+(?Phttp\S+)(?:\s+(?P\{.+?\}))?(?:\s+:(?P\w+))?", re.IGNORECASE) 81 | reg = re.compile(r"(?PGET|POST)\s+(?Phttp\S+)(?:\s+p(?P\{.+?\}))?(?:\s+h(?P\{.+?\}))?(?:\s+:(?P\w+))?", re.IGNORECASE) 82 | arg = reg.fullmatch(msg) 83 | if arg: 84 | d = arg.groupdict() 85 | print(d) 86 | params = json.loads(d.get('params') or '{}') 87 | headers = json.loads(d.get('headers') or '{}') 88 | content = d.get('content') 89 | if content: 90 | r = await fetch(d['method'], d['url'], params=params, headers=headers, content='raw') 91 | #text = str(getattr(r, content.lower()) or '') 92 | text = str(getattr(r, content)) 93 | else: 94 | text = await fetch(d['method'], d['url'], params=params, headers=headers, content='text') 95 | else: 96 | raise Exception() 97 | 98 | return [text] 99 | 100 | 101 | async def fetcher(msg): 102 | try: 103 | return (await getcode(msg)) 104 | except: 105 | print('not paste bin') 106 | return (await geturl(msg)) 107 | 108 | # util 109 | 110 | 111 | async def clear(arg, lines, send): 112 | print('clear') 113 | 114 | 115 | async def undo(arg, lines, send): 116 | print('undo') 117 | 118 | n = int(arg['n'] or 1) 119 | 120 | if n < len(lines): 121 | for i in range(n): 122 | lines.pop() 123 | 124 | arg['meta']['bot'].addlines(arg['meta']['nick'], lines) 125 | 126 | help = [ 127 | ('clear' , 'clear'), 128 | ('undo' , 'undo [number]'), 129 | ] 130 | 131 | func = [ 132 | (clear , r"clear"), 133 | (undo , r"undo(?:\s+(?P\d+))?"), 134 | ] 135 | -------------------------------------------------------------------------------- /python/modules/commands/simple.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | import math 4 | import unicodedata 5 | import re 6 | import datetime 7 | 8 | 9 | async def say(arg, send): 10 | #send(arg['content']) 11 | send('[{0}] '.format(arg['meta']['nick']) + arg['content'], raw=True) 12 | 13 | 14 | async def ping(arg, send): 15 | send("ping!") 16 | #send("\\x0305ping!\\x0f") 17 | 18 | 19 | async def pong(arg, send): 20 | send("pong!") 21 | 22 | 23 | class ping2c(): 24 | 25 | def __init__(self): 26 | self.i = 3 27 | self.tt = time.time() 28 | 29 | async def __call__(self, arg, send): 30 | print(self.i) 31 | t = time.time() 32 | if t - self.tt > 20: 33 | self.tt = t 34 | self.i = 3 35 | elif self.i <= 0: 36 | send('好累啊...') 37 | if self.i == 1: 38 | send("\\x0304ping!!!!!!!!!\\x0f") 39 | self.i = self.i - 1 40 | elif self.i > 0: 41 | send("\\x0304ping!\\x0f") 42 | self.i = self.i - 1 43 | 44 | ping2 = ping2c() 45 | 46 | 47 | class unicodenormalize: 48 | 49 | def __init__(self): 50 | #self.reg = re.compile(r"(?:[\u0300-\u036F]|[\u1AB0–\u1AFF]|[\u1DC0–\u1DFF]|[\u20D0–\u20FF]|[\uFE20–\uFE2F])+") 51 | # add Cyrillic combining characters 52 | self.reg = re.compile(r"(?:[\u0300-\u036F]|[\u1AB0–\u1AFF]|[\u1DC0–\u1DFF]|[\u20D0–\u20FF]|[\uFE20–\uFE2F]|[\u0483-\u0489])+") 53 | self.trans = str.maketrans( 54 | #'абвгдеёзийклмнопрстуфхъыьэАБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЬЭ', 55 | #'abvgdeezijklmnoprstufh y eABVGDEEZIJKLMNOPRSTUFH Y E', 56 | #'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя', 57 | #'ABVGDEZZIJKLMNOPRSTUFHCCSS Y EUAabvgdezzijklnmoprstufhccss y eua', 58 | 'еђгєѕіјљњћкиуџабвджзлмнопрстфхцчшщъыьэюяѡѣѥѧѩѫѭѯѱѳѵѹѻѽѿҁ҂ҋҍҏґғҕҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӏӄӆӈӊӌӎӕәӡөӷӻӽӿ', 59 | 'ehgesijbbhknyyabbaxeamhonpctpxyywwbbbeorwbeaaaaeptvyoowcxnbpgfhxekkkkhhhqctyyxyyyheelkahhymaeetgfxx', 60 | '\u200b\u200c\u200d\u200e\u200f\ufeff', 61 | ) 62 | 63 | def __call__(self, s): 64 | return self.reg.sub('', unicodedata.normalize('NFKD', s.lower())).translate(self.trans) 65 | unormalize = unicodenormalize() 66 | 67 | async def pia(arg, send): 68 | content = arg['content'] or '' 69 | 70 | fullface = [ 71 | '°Д°', 72 | '・ω・', 73 | '・∀・', 74 | '‵-′', 75 | ' ̄▽ ̄', 76 | '・_・', 77 | '>∧<', 78 | '´∀`', 79 | '°_°', 80 | ' ̄皿 ̄', 81 | ' ̄ω ̄', 82 | '° △ °', 83 | '°ー°', 84 | '@_@', 85 | 'T﹏T', 86 | '>﹏<', 87 | '┬_┬', 88 | ' ̄︿ ̄', 89 | '╥﹏╥', 90 | ] 91 | face = [ 92 | '°{}°', 93 | '・{}・', 94 | '‵{}′', 95 | ' ̄{} ̄', 96 | '>{}<', 97 | '´{}`', 98 | '@{}@', 99 | 'T{}T', 100 | '•̀{}•́', 101 | '艹{}艹', 102 | '^{}^', 103 | 'X{}X', 104 | #'┬{}┬', 105 | #'╥{}╥', 106 | ] 107 | mouth = [ 108 | 'Д', 109 | 'ω', 110 | '∀', 111 | '-', 112 | '▽', 113 | '_', 114 | '∧', 115 | '_>', 116 | '皿', 117 | '△', 118 | 'ー', 119 | '﹏', 120 | '_', 121 | '︿', 122 | ] 123 | face.extend([ 124 | # csslayer 125 | 'ˊ_>ˋ', 126 | # felixonmars 127 | '=﹁"﹁=', 128 | # frantic 129 | '>_<', 130 | # cuihao 131 | '=3=', 132 | ]) 133 | icon = '(╯{0})╯ ┻━┻ '.format(random.choice(face).format(random.choice(mouth))) 134 | #if arg['meta']['bot'].nick not in content: 135 | # send(icon + content) 136 | #else: 137 | # send(icon + '不要 pia 我!') 138 | if 'orznzbot' in arg['meta']['nick']: 139 | return send(icon + '你才是!') 140 | if arg['meta']['bot'].nick.lower() in unormalize(content): 141 | send(icon + '不要 pia 我!') 142 | else: 143 | send(icon + content) 144 | 145 | 146 | async def mua(arg, send): 147 | content = arg['content'] or '' 148 | 149 | #if arg['meta']['bot'].nick not in content: 150 | # send('o(* ̄3 ̄)o ' + content) 151 | #else: 152 | # send('o(* ̄3 ̄)o ' + '谢谢啦~') 153 | if arg['meta']['bot'].nick.lower() in unormalize(content): 154 | send('o(* ̄3 ̄)o ' + '谢谢啦~') 155 | else: 156 | send('o(* ̄3 ̄)o ' + content) 157 | 158 | 159 | async def prpr(arg, send): 160 | content = arg['content'] or '' 161 | 162 | if arg['meta']['bot'].nick.lower() in unormalize(content): 163 | send('咦!') 164 | else: 165 | send('哧溜! ' + content) 166 | 167 | 168 | async def hug(arg, send): 169 | content = arg['content'] or '' 170 | 171 | #if arg['meta']['bot'].nick not in content: 172 | # send('(つ°ω°)つ ' + content) 173 | #else: 174 | # send('(つ°ω°)つ ' + '谢谢啦~') 175 | if arg['meta']['bot'].nick.lower() in unormalize(content): 176 | send('(つ°ω°)つ ' + '谢谢啦~') 177 | else: 178 | send('(つ°ω°)つ ' + content) 179 | 180 | 181 | async def color(arg, send): 182 | #c = [ 183 | # ('00', 'white'), 184 | # ('01', 'black'), 185 | # ('02', 'blue'), 186 | # ('03', 'green'), 187 | # ('04', 'red'), 188 | # ('05', 'brown'), 189 | # ('06', 'purple'), 190 | # ('07', 'orange'), 191 | # ('08', 'yellow'), 192 | # ('09', 'light green'), 193 | # ('10', 'teal'), 194 | # ('11', 'light cyan'), 195 | # ('12', 'light blue'), 196 | # ('13', 'pink'), 197 | # ('14', 'grey'), 198 | # ('15', 'light grey'), 199 | #] 200 | c = [ 201 | ('00', 'white'), 202 | ('01', 'black'), 203 | ('02', 'blue'), 204 | ('03', 'green'), 205 | ('04', 'light red'), 206 | ('05', 'red'), 207 | ('06', 'magenta'), 208 | ('07', 'orange'), 209 | ('08', 'yellow'), 210 | ('09', 'light green'), 211 | ('10', 'cyan'), 212 | ('11', 'light cyan'), 213 | ('12', 'light blue'), 214 | ('13', 'light magenta'), 215 | ('14', 'grey'), 216 | ('15', 'light grey'), 217 | ] 218 | if arg['all']: 219 | send(' '.join(['\\x03{0:0>#02}{0:0>#02}\\x0f'.format(x) for x in range(0, 50)])) 220 | send(' '.join(['\\x03{0:0>#02}{0:0>#02}\\x0f'.format(x) for x in range(50, 100)])) 221 | else: 222 | send('\\x02bold\\x02 \\x1ditalic\\x1d \\x1funderline\\x1f \\x06blink\\x06 \\x16reverse\\x16') 223 | #send('\\x07bell\\x07 \\x1bansi color\\x1b') 224 | send(' '.join(map(lambda x: '\\x03{0}{0} {1}\\x0f'.format(*x), c[:8]))) 225 | send(' '.join(map(lambda x: '\\x03{0}{0} {1}\\x0f'.format(*x), c[8:]))) 226 | 227 | 228 | # provide a search? 229 | async def mode(arg, send): 230 | u = [ 231 | ('g', 'caller-id'), 232 | ('i', 'invisible'), 233 | ('Q', 'disable forwarding'), 234 | ('R', 'block unidentified'), 235 | ('w', 'see wallops'), 236 | ('Z', 'connected securely'), 237 | ] 238 | c = [ 239 | ('b', 'channel ban'), 240 | ('c', 'colour filter'), 241 | ('C', 'block CTCPs'), 242 | ('e', 'ban exemption'), 243 | ('f', 'forward'), 244 | ('F', 'enable forwarding'), 245 | ('g', 'free invite'), 246 | ('i', 'invite only'), 247 | ('I', 'invite exemption'), 248 | ('j', 'join throttle'), 249 | ('k', 'password'), 250 | ('l', 'join limit'), 251 | ('m', 'moderated'), 252 | ('n', 'prevent external send'), 253 | ('p', 'private'), 254 | ('q', 'quiet'), 255 | ('Q', 'block forwarded users'), 256 | ('r', 'block unidentified'), 257 | ('s', 'secret'), 258 | ('S', 'SSL-only'), 259 | ('t', 'ops topic'), 260 | ('z', 'reduced moderation'), 261 | ] 262 | send('\\x0304user\\x0f ' + ' '.join(map(lambda e: '\\x0300{0}\\x0f/{1}'.format(*e), u)) + ' (see [\\x0302 https://freenode.net/kb/answer/usermodes \\x0f])') 263 | send('\\x0304channel\\x0f ' + ' '.join(map(lambda e: '\\x0300{0}\\x0f/{1}'.format(*e), c[:20]))) 264 | send('\\x0304cont.\\x0f ' + ' '.join(map(lambda e: '\\x0300{0}\\x0f/{1}'.format(*e), c[20:])) + ' (see [\\x0302 https://freenode.net/kb/answer/channelmodes \\x0f])') 265 | 266 | 267 | def getrandom(show): 268 | # http://en.wikipedia.org/wiki/Mathematical_constant 269 | # http://pdg.lbl.gov/2014/reviews/rpp2014-rev-phys-constants.pdf 270 | const = [ 271 | # math 272 | (0, 'zero'), 273 | (1, 'unity'), 274 | ('i', 'imaginary unit'), 275 | (3.1415926535, 'pi'), 276 | (2.7182818284, 'e'), 277 | (1.4142135623, 'Pythagoras constant'), 278 | (0.5772156649, 'Euler-Mascheroni constant'), 279 | (1.6180339887, 'golden ratio'), 280 | # physics 281 | (299792458, 'speed of light in vacuum'), 282 | (6.62606957, 'Planck constant'), 283 | (1.054571726, 'Planck constant, reduced'), 284 | (6.58211928, 'Planck constant, reduced'), 285 | (1.602176565, 'electron charge magnitude'), 286 | (0.510998928, 'electron mass'), 287 | (9.10938291, 'electron mass'), 288 | (938.272046, 'proton mass'), 289 | (1.672621777, 'proton mass'), 290 | (1836.15267245, 'proton mass'), 291 | (8.854187817, 'permittivity of free space'), 292 | (12.566370614, 'permeability of free space'), 293 | (7.2973525698, 'fine-structure constant'), 294 | (137.035999074, 'fine-structure constant'), 295 | (2.8179403267, 'classical electron radius'), 296 | (3.8615926800, 'electron Compton wavelength, reduced'), 297 | (6.67384, 'gravitational constant'), 298 | (6.70837, 'gravitational constant'), 299 | (6.02214129, 'Avogadro constant'), 300 | (1.3806488, 'Boltzmann constant'), 301 | (8.6173324, 'Boltzmann constant'), 302 | (1.1663787, 'Fermi coupling constant'), 303 | (0.23126, 'weak-mixing angle'), 304 | (80.385, 'W boson mass'), 305 | (91.1876, 'Z boson mass'), 306 | (0.1185, 'strong coupling constant'), 307 | # other 308 | (9, 'Cirno'), 309 | (1024, 'caoliu'), 310 | (1984, 'Orwell'), 311 | (10086, 'China Mobile'), 312 | (233, 'LOL'), 313 | (2333, 'LOL'), 314 | (23333, 'LOL'), 315 | ] 316 | rand = [ 317 | # random 318 | (random.randint(0, 9), 'random number'), 319 | (random.randint(10, 99), 'random number'), 320 | (random.randint(100, 999), 'random number'), 321 | (random.randint(1000, 9999), 'random number'), 322 | (random.randint(10000, 99999), 'random number'), 323 | #math.sqrt(random.randint(0, 100000)), 324 | ] 325 | 326 | if show: 327 | get = lambda e: '{0} -- {1}'.format(str(e[0]), e[1]) 328 | else: 329 | get = lambda e: str(e[0]) 330 | l = random.choice([const, rand]) 331 | return get(random.choice(l)) 332 | 333 | 334 | async def up(arg, send): 335 | send('+' + getrandom(arg['show'])) 336 | 337 | 338 | async def down(arg, send): 339 | send('-' + getrandom(arg['show'])) 340 | 341 | 342 | async def utc(arg, send): 343 | 344 | tz = arg['zone'] or '0' 345 | send(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=int(tz))))) 346 | 347 | 348 | async def horo(arg, send): 349 | l = [ 350 | '咱可是贤狼啊。起码还是知道这世界上有很多东西是咱所不了解的。', 351 | '人呐……在这种时候似乎会说『最近的年轻人……』呗。', 352 | '嗯。这么一想,或许好雄性的表现和好大人的表现互不相容。好雄性显得孩子气,而好大人显得窝囊。', 353 | '咱希望汝不要说得那么羡慕的样子。', 354 | '咱也一样……不,咱懂汝的心情。就是因为咱懂……所以咱更不想说出口……咱想,在旁人的眼中看起来,咱们俩也是一副蠢样子。', 355 | '不过,汝在那个瞬间带着那些麦子,站在那座村落的那个位置上,也算是偶然。世上没有什么事情比必然与偶然更难懂了,就像要懂得木头人的爱恋之情一样困难。', 356 | '这种话就算不是咱的耳朵,也听得出来是谎言。', 357 | '神、神总是这样。总是……总是这么不讲理?', 358 | '咱不想再碰到醒过来时,都见不到人的状况了……咱受够孤独了。孤独好冷。孤独……好让人寂寞。', 359 | '人们就是清醒时,也可能走路踩到别人的脚,更何况是睡着的时候。可是呐,这尾巴是咱的骄傲,也是证明咱就是咱的唯一铁证。', 360 | '草原上的狼。比人类好应付,至少能沟通。', 361 | '咱可是约伊兹的贤狼赫萝。', 362 | '狼群只会在森林里生活,而狗儿曾经被人类饲养过。这就是狼跟狗攻击性不同的地方。狼只知道人类会狩猎,人类是恐怖的存在。所以咱们狼时时刻刻都在思考,当人类进入森林时,咱们要采取什么样的行动。', 363 | '你说的是结果吧。要不是在卡梅尔森赚到了钱,我的财产早就真的被你吃光了。', 364 | '哼。俗话说一不做二不休,到时候咱也会很快地把汝吃进肚子里。', 365 | '当然,汝这个人非但没有求我教导,反而是个拼命想要抓住咱的缰绳的罕见大笨驴啊。虽然在成功率上没什么希望,不过汝肯定是想站在跟咱相同的高度上呗。咱一直都是独自呆在山上,我已经对从下面看着咱的目光感到厌倦了。', 366 | '如果你是贤狼,就应该战胜诱惑啊。', 367 | '虽然纵欲会失去很多东西,可是禁欲也不会有任何建设。', 368 | '人们就是清醒时,也可能走路踩到别人的脚,更何况是睡着的时候。可是呐,这尾巴是咱的骄傲,也是证明咱就是咱的唯一铁证。', 369 | '旅行唯有出发前最愉快;狗儿唯有叫声最吓人;女人唯有背影最美丽。随随便便地抛头露面,会坏了人家的美梦。这种事情咱做可不来。', 370 | '接下来要商谈,所以,您大可以朝对您有利的方向尽情地撒谎。不过,这只狼绝对比我聪明得多,它可以看出您的话背后再背后的意思。如果您说了欠缺考量的话,身高可是会缩短一大截的。您明白了吧?', 371 | '咱可是唯一的贤狼赫萝。人们畏惧咱、服侍咱,但害怕咱可就不像话了。', 372 | '咱可没有建立新故乡的想法。故乡就是故乡,重要的不是谁在那里,而是土地在哪里。而且,汝所担心的,就是咱不这样说吧?咱的故乡是能重新建立的吗?', 373 | '猪如果被奉承,连树都爬得上去;但如果奉承雄性,只会被爬到头顶上。', 374 | '咱会受不了。如果尾巴被卖了,咱会受不了。汝脸上怎么好像写着『只要是能卖的东西,我什么都卖』呐?', 375 | '虽然咱长久以来被尊为神,且被束缚在这块土地上,但咱根本不是什么伟大的神。咱就是咱。咱是赫萝。', 376 | #「嗯。那儿夏季短暂,冬季漫长,是个银白色的世界呢。」 377 | #「汝曾去过吗?」 378 | #「我去过最北端的地方是亚罗西史托,那是全年吹着暴风雪的恐怖地方。」 379 | #「喔,咱没听过。」 380 | #「那你曾去过哪里呢?」 381 | #「咱去过约伊兹,怎么着?」 382 | #罗伦斯回答一声「没事」后,硬是掩饰住内心的动摇。罗伦斯曾听过约伊兹这个地名。不过那是在北方大地的旅馆听来的古老传说里,所出现的地名。 383 | #「你是在那里出生的吗?」 384 | #「没错。不知道约伊兹现在变成什么样了,大伙儿过得好不好呢?」 385 | # unofficial 386 | '基本上,得记住的文字种类太多了。还有呐,莫名其妙的组合也太多了。虽然人类会说只要照着说话规则写字就好,但是这显然是骗人的呗。', 387 | '如果我也能够变身成女孩子就好了。', 388 | '如果您会变身成女孩子,八成会被赫萝吃掉。', 389 | '呵。毕竟咱这么可爱,人类的雄性也会为咱倾倒。不过呐,人类当中没有够资格当咱对象的雄性。如果有人敢碰咱,咱只要警告他『小心命根子』,任谁都会吓得脸色惨白。呵呵呵。', 390 | '……抱歉。', 391 | '汝和咱活着的世界可是大不相同啊。', 392 | ] 393 | send('「{}」'.format(random.choice(l))) 394 | 395 | 396 | async def bidi(arg, send): 397 | if arg['content']: 398 | return send(arg['content'] + ' bidi') 399 | 400 | normal = [ 401 | 'bidi', 402 | 'bidi' + '!' * random.randint(1, 5), 403 | 'bidi' + '?' * random.randint(1, 5), 404 | ] 405 | short = [ 406 | 'bi', 407 | 'bi' + '!' * random.randint(1, 5), 408 | 'bi' + '?' * random.randint(1, 5), 409 | 'di', 410 | 'di' + '!' * random.randint(1, 5), 411 | 'di' + '?' * random.randint(1, 5), 412 | ] 413 | audio = [ 414 | 'bidibidibi!', 415 | 'bidi' * random.randint(1, 15), 416 | 'bidi' * random.randint(1, 15) + '!' * random.randint(1, 10), 417 | 'bidi' * random.randint(1, 15) + '?' * random.randint(1, 10), 418 | 'bidi' * random.randint(1, 30), 419 | ] 420 | 421 | if random.randint(1, 100) <= 10: 422 | lang = random.choice(['en', 'zh', 'ja', 'eo', 'id', 'la', 'no', 'vi']) 423 | line = random.choice(audio) 424 | await arg['meta']['command']('gtran {}:audio {}'.format(lang, line), [], send) 425 | else: 426 | send(random.choice(random.choice([normal, normal, normal, normal, short, audio]))) 427 | 428 | 429 | async def latex(arg, send): 430 | symbol = [ 431 | (r'\alpha', '\U0001d6fc'), 432 | (r'\pi', '\U0001d6d1'), 433 | (r'\Alpha', '\u0391'), 434 | (r'\Beta', '\u0392'), 435 | (r'\Gamma', '\u0393'), 436 | (r'\Delta', '\u0394'), 437 | (r'\Epsilon', '\u0395'), 438 | (r'\Zeta', '\u0396'), 439 | (r'\Eta', '\u0397'), 440 | (r'\Theta', '\u0398'), 441 | (r'\Iota', '\u0399'), 442 | (r'\Kappa', '\u039a'), 443 | (r'\Lambda', '\u039b'), 444 | (r'\Mu', '\u039c'), 445 | (r'\Nu', '\u039d'), 446 | (r'\Xi', '\u039e'), 447 | (r'\Omicron', '\u039f'), 448 | (r'\Pi', '\u03a0'), 449 | (r'\Rho', '\u03a1'), 450 | (r'\Sigma', '\u03a3'), 451 | (r'\Tau', '\u03a4'), 452 | (r'\Upsilon', '\u03a5'), 453 | (r'\Phi', '\u03a6'), 454 | (r'\Chi', '\u03a7'), 455 | (r'\Psi', '\u03a8'), 456 | (r'\Omega', '\u03a9'), 457 | (r'\alpha', '\u03b1'), 458 | (r'\beta', '\u03b2'), 459 | (r'\gamma', '\u03b3'), 460 | (r'\delta', '\u03b4'), 461 | (r'\epsilon', '\u03b5'), 462 | (r'\zeta', '\u03b6'), 463 | (r'\eta', '\u03b7'), 464 | (r'\theta', '\u03b8'), 465 | (r'\iota', '\u03b9'), 466 | (r'\kappa', '\u03ba'), 467 | (r'\lambda', '\u03bb'), 468 | (r'\mu', '\u03bc'), 469 | (r'\nu', '\u03bd'), 470 | (r'\xi', '\u03be'), 471 | (r'\omicron', '\u03bf'), 472 | (r'\pi', '\u03c0'), 473 | (r'\rho', '\u03c1'), 474 | (r'\sigma', '\u03c3'), 475 | (r'\tau', '\u03c4'), 476 | (r'\upsilon', '\u03c5'), 477 | (r'\phi', '\u03c6'), 478 | (r'\chi', '\u03c7'), 479 | (r'\psi', '\u03c8'), 480 | (r'\omega', '\u03c9'), 481 | ] 482 | 483 | m = arg['content'] 484 | for (t, s) in symbol: 485 | m = m.replace(t, s) 486 | send(m) 487 | 488 | help = [ 489 | ('say' , 'say '), 490 | ('ping!' , 'ping!'), 491 | ('pong!' , 'pong!'), 492 | ('pia' , 'pia [content] -- Каждая несчастливая семья несчастлива по-своему'), 493 | ('mua' , 'mua [content] -- Все счастливые семьи похожи друг на друга'), 494 | ('color' , 'color [all] -- let\'s puke \\x0304r\\x0307a\\x0308i\\x0303n\\x0310b\\x0302o\\x0306w\\x0fs!'), 495 | ('mode' , 'mode -- \\x0300free\\x0f\\x0303node\\x0f is awesome!'), 496 | ('up' , 'up [show] -- nice boat!'), 497 | ('down' , 'down [show]'), 498 | ('utc' , 'utc [+/- zone offset]'), 499 | ('bidi' , 'bidi [content] -- bidibidibi!'), 500 | ] 501 | 502 | func = [ 503 | (say , r"say (?P.+)"), 504 | (pong , r"ping!"), 505 | (ping , r"pong!"), 506 | #(ping2 , r"(?:.*): pong!"), 507 | (color , r"color(?:\s+(?Pall))?"), 508 | (mode , r"mode"), 509 | (up , r"up(?:\s+(?Pshow))?"), 510 | (down , r"down(?:\s+(?Pshow))?"), 511 | (pia , r"pia( (?P.+))?"), 512 | (mua , r"mua( (?P.+))?"), 513 | (hug , r"hug( (?P.+))?"), 514 | (prpr , r"prpr( (?P.+))?"), 515 | #(utc , r"utc(?:\s(?P([-+])?[0-9]+))?"), 516 | (horo , r"horo"), 517 | (horo , r"horohoro"), 518 | (bidi , r"bidi(?:bidi)*( (?P.+))?"), 519 | (latex , r"latex\s+(?P.+)"), 520 | ] 521 | -------------------------------------------------------------------------------- /python/modules/commands/tool.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import itertools 3 | import json 4 | import re 5 | from urllib.parse import urldefrag 6 | 7 | import demjson3 as demjson 8 | import html5lib 9 | import lxml.html 10 | from aiohttp import ClientSession, ClientRequest 11 | from aiohttp.helpers import parse_mimetype 12 | from dicttoxml import dicttoxml 13 | from lxml import etree 14 | 15 | 16 | demjson.undefined = None 17 | 18 | # bad encoding 19 | # https://item.m.jd.com 20 | 21 | def drop(l, offset): 22 | return itertools.islice(l, offset, None) 23 | 24 | 25 | def charset(r): 26 | ctype = r.headers.get('content-type', '').lower() 27 | #_, _, _, params = parse_mimetype(ctype) 28 | #return params.get('charset') 29 | return parse_mimetype(ctype).parameters.get('chardet') 30 | 31 | 32 | # here we handle timeout differently 33 | async def fetch(method, url, content='text', timeout=10, **kw): 34 | url_defrag = urldefrag(url)[0] 35 | # workaround 36 | req_class = kw.get('request_class', ClientRequest) 37 | req_kw = kw 38 | try: 39 | del req_kw['request_class'] 40 | except KeyError: 41 | pass 42 | 43 | async with ClientSession(request_class=req_class) as session: 44 | r = await asyncio.wait_for(session.request(method, url_defrag, **req_kw), timeout) 45 | print('fetching from {}'.format(r.url)) 46 | 47 | if content == 'raw': 48 | return r 49 | elif content == 'byte': 50 | return ((await asyncio.wait_for(r.read(), timeout)), charset(r)) 51 | elif content == 'text': 52 | # we skip chardet in r.text() 53 | # as sometimes it yields wrong result 54 | encoding = charset(r) or 'utf-8' 55 | text = (await asyncio.wait_for(r.read(), timeout)).decode(encoding, 'replace') 56 | #try: 57 | # text = await asyncio.wait_for(r.text(), timeout) 58 | #except: 59 | # print('bad encoding') 60 | # text = (await asyncio.wait_for(r.read(), timeout)).decode('utf-8', 'replace') 61 | return text 62 | 63 | return None 64 | 65 | 66 | class Request: 67 | 68 | def __init__(self): 69 | # no # in xpath 70 | self.rfield = re.compile(r"\s*(?P[^#]+)?#(?P[^\s']+)?(?:'(?P[^']+)')?") 71 | # illegal char in xml 72 | illegal = [ 73 | b"[\x00-\x08]", 74 | b"[\x0B-\x0C]", 75 | b"[\x0E-\x1F]", 76 | b"[\x7F-\x84]", 77 | b"[\x86-\x9F]", 78 | b"\xFD[\xD0-\xDF]", 79 | b"\xFF[\xFE-\xFF]", 80 | b"\x01\xFF[\xFE-\xFF]", 81 | b"\x02\xFF[\xFE-\xFF]", 82 | b"\x03\xFF[\xFE-\xFF]", 83 | b"\x04\xFF[\xFE-\xFF]", 84 | b"\x05\xFF[\xFE-\xFF]", 85 | b"\x06\xFF[\xFE-\xFF]", 86 | b"\x07\xFF[\xFE-\xFF]", 87 | b"\x08\xFF[\xFE-\xFF]", 88 | b"\x09\xFF[\xFE-\xFF]", 89 | b"\x0A\xFF[\xFE-\xFF]", 90 | b"\x0B\xFF[\xFE-\xFF]", 91 | b"\x0C\xFF[\xFE-\xFF]", 92 | b"\x0D\xFF[\xFE-\xFF]", 93 | b"\x0E\xFF[\xFE-\xFF]", 94 | b"\x0F\xFF[\xFE-\xFF]", 95 | b"\x10\xFF[\xFE-\xFF]", 96 | ] 97 | self.rvalid = re.compile(b"(?:" + b"|".join(illegal) + b")+") 98 | self.ns = {'re': 'http://exslt.org/regular-expressions'} 99 | # handy 100 | self.islice = itertools.islice 101 | self.iter_first = lambda i: ''.join(itertools.islice(i, 1)) 102 | self.iter_list = lambda i: ', '.join(i) 103 | 104 | 105 | def parsefield(self, field): 106 | if field: 107 | def getitem(e): 108 | d = e.groupdict() 109 | return (d['xpath'] or '.', d['field'] or '', d['format'] if d['format'] else '{}') 110 | return [getitem(e) for e in self.rfield.finditer(field)] 111 | else: 112 | return [('.', '', '{}')] 113 | 114 | def getfield(self, get, ns, field): 115 | def getf(e, f): 116 | #def gete(e): 117 | # #item = get(e, f[1]) 118 | # #return str(item).strip() if item else '' 119 | # return str(get(e, f[1]) or '') 120 | # check if e is node 121 | #l = [y for y in [gete(x) for x in e.xpath(f[0], namespaces=ns)] if y.strip()] if hasattr(e, 'xpath') else [e] 122 | l = [y for y in [str(get(x, f[1]) or '') for x in e.xpath(f[0], namespaces=ns)] if y.strip()] if hasattr(e, 'xpath') else [e] 123 | print('getfield {}'.format(l)) 124 | return f[2].format(', '.join(l)) if l else '' 125 | return (lambda e: [getf(e, f) for f in field]) 126 | 127 | def parse(self, text, encoding): 128 | pass 129 | 130 | def init_ns(self, t): 131 | self.ns = {'re': 'http://exslt.org/regular-expressions'} 132 | 133 | def get(self, e, f): 134 | pass 135 | 136 | def format(self, l): 137 | return map(lambda e: ' '.join(e), l) 138 | 139 | def get_xpath(self, element, xpath): 140 | return element.xpath(xpath, namespaces=self.ns) 141 | 142 | def get_field(self, get, element, field): 143 | #print(field) 144 | fs = self.get_xpath(element, field[0]) 145 | fs = (get(f, field[1]) for f in fs) 146 | fs = (f for f in fs if f.strip()) 147 | return field[2](fs) 148 | 149 | def get_fields(self, get, element, fields): 150 | #print(fields) 151 | return (self.get_field(get, element, field) for field in fields) 152 | 153 | async def fetch(self, method, url, **kw): 154 | return (await fetch(method, url, content='byte', **kw)) 155 | 156 | async def __call__(self, arg, lines, send, *, method='GET', field=None, transform=None, get=None, preget=None, format=None, format_new=None, **kw): 157 | n = int(arg.get('n') or 3) 158 | offset = int(arg.get('offset') or 0) 159 | try: 160 | xpath = arg['xpath'] 161 | except KeyError: 162 | raise Exception('xpath is missing') 163 | 164 | # fetch text 165 | if lines: 166 | text = '\n'.join(lines) 167 | encoding = None 168 | else: 169 | try: 170 | url = arg['url'] 171 | except KeyError: 172 | raise Exception('url is missing') 173 | (text, encoding) = await self.fetch(method, url, **kw) 174 | # parse text into xml 175 | # text -> tree 176 | try: 177 | tree = self.parse(text, encoding) 178 | except ValueError: 179 | # handle bad char 180 | # actually only works with utf-8 encoding 181 | # https://bpaste.net/show/438a3ef4f0b7 182 | tree = self.parse(self.rvalid.sub(b'', text), encoding) 183 | # add xml namespaces to ns 184 | self.init_ns(tree) 185 | # find elements 186 | # tree -> elements 187 | elements = tree.xpath(xpath, namespaces=self.ns) 188 | 189 | field = field or self.parsefield(arg.get('field')) 190 | # map elements to elements 191 | transformer = transform or (lambda es: es) 192 | # map element to string 193 | getter_old = get or self.get 194 | if preget: 195 | getter = lambda e, f: getter_old(preget(e), f) 196 | else: 197 | getter = getter_old 198 | # map strings to strings 199 | formatter_old = format or ((lambda l: map(lambda e: arg['format'].format(*e), l)) if arg.get('format') else self.format) 200 | formatter = (lambda es: format_new(self, es)) if format_new else (lambda es: ([line] for line in formatter_old(filter(lambda e: any(e), map(self.getfield(getter, self.ns, field), transformer(es)))))) 201 | ## transform elements 202 | #elements = transformer(elements) 203 | ## get 204 | #lines = filter(lambda e: any(e), map(self.getfield(getter, ns, field), elements)) 205 | ## format lines 206 | #lines = formatter_old(lines) 207 | 208 | # format lines 209 | # elements -> lines 210 | lines = formatter(elements) 211 | #print('hello', [list(line) for line in formatter(elements)]) 212 | lines = itertools.chain.from_iterable(itertools.islice(lines, offset, None)) 213 | # send lines 214 | send(lines, n=n, llimit=10) 215 | 216 | 217 | def addstyle(e): 218 | # br to newline 219 | #print(etree.tostring(e)) 220 | for br in e.xpath('.//br'): 221 | br.tail = '\n' + (br.tail or '') 222 | for b in e.xpath('.//b'): 223 | b.text = '\\x02' + (b.text or '') 224 | b.tail = '\\x02' + (b.tail or '') 225 | for i in e.xpath('.//i'): 226 | i.text = '\\x1d' + (i.text or '') 227 | i.tail = '\\x1d' + (i.tail or '') 228 | for u in e.xpath('.//u'): 229 | u.text = '\\x1f' + (u.text or '') 230 | u.tail = '\\x1f' + (u.tail or '') 231 | return e 232 | 233 | 234 | # use html5lib for standard compliance 235 | def htmlparse(t, encoding=None): 236 | return html5lib.parse(t, treebuilder='lxml', namespaceHTMLElements=False) 237 | #try: 238 | # return html5lib.parse(t, treebuilder='lxml', namespaceHTMLElements=False, transport_encoding=encoding) 239 | #except TypeError: 240 | # return html5lib.parse(t, treebuilder='lxml', namespaceHTMLElements=False) 241 | def htmlparsefast(t, *, parser=None): 242 | return lxml.html.fromstring(t, parser=parser) 243 | 244 | 245 | def htmltostr(t): 246 | return addstyle(htmlparse(t)).xpath('string()') 247 | 248 | 249 | def htmlget(e, f): 250 | if hasattr(e, 'xpath'): 251 | if not f: 252 | return addstyle(e).xpath('string()') 253 | elif hasattr(e, f): 254 | return getattr(e, f, '') 255 | else: 256 | return e.attrib.get(f, '') 257 | else: 258 | return str(e) 259 | 260 | 261 | class HTMLRequest(Request): 262 | 263 | def parse(self, text, encoding): 264 | return htmlparse(text, encoding=encoding) 265 | 266 | def get(self, e, f): 267 | return htmlget(e, f) 268 | 269 | html = HTMLRequest() 270 | 271 | 272 | def xmlparse(t, encoding=None): 273 | parser = etree.XMLParser(recover=True, encoding=encoding) 274 | try: 275 | return etree.XML(t, parser) 276 | except: 277 | return etree.XML(t.encode('utf-8'), parser) 278 | 279 | 280 | def xmlget(e, f): 281 | if hasattr(e, 'xpath'): 282 | if not f: 283 | return htmltostr(e.text) 284 | elif hasattr(e, f): 285 | return getattr(e, f, '') 286 | else: 287 | return e.attrib.get(f, '') 288 | else: 289 | return str(e) 290 | 291 | 292 | class XMLRequest(Request): 293 | 294 | def parse(self, text, encoding): 295 | return xmlparse(text, encoding=encoding) 296 | 297 | def init_ns(self, t): 298 | self.ns = {'re': 'http://exslt.org/regular-expressions'} 299 | self.ns.update(t.nsmap) 300 | xmlns = self.ns.pop(None, None) 301 | if xmlns: 302 | self.ns['ns'] = xmlns 303 | 304 | def get(self, e, f): 305 | return xmlget(e, f) 306 | 307 | xml = XMLRequest() 308 | 309 | 310 | def jsonparse(t, encoding=None): 311 | try: 312 | try: 313 | return json.loads(t) 314 | except TypeError: 315 | return json.loads(t.decode(encoding or 'utf-8', 'replace')) 316 | except: 317 | return demjson.decode(t, encoding=encoding) 318 | 319 | 320 | class JSONRequest(Request): 321 | 322 | def parse(self, text, encoding): 323 | #print(text) 324 | j = jsonparse(text, encoding=encoding) 325 | b = dicttoxml(j, attr_type=False) 326 | #print(j) 327 | #print(b) 328 | return xmlparse(b) 329 | 330 | def get(self, e, f): 331 | return e.text 332 | 333 | jsonxml = JSONRequest() 334 | 335 | 336 | async def regex(arg, lines, send, **kw): 337 | n = int(arg.get('n') or 5) 338 | url = arg.get('url') 339 | 340 | if arg.get('multi'): 341 | reg = re.compile(arg['regex'], re.MULTILINE) 342 | else: 343 | reg = re.compile(arg['regex']) 344 | 345 | text = '\n'.join(lines) if lines else (await fetch('GET', url, **kw)) 346 | line = map(lambda e: ', '.join(e.groups()), reg.finditer(text)) 347 | send(line, n=n, llimit=10) 348 | 349 | 350 | help = [ 351 | #('html' , 'html (url) [output fields (e.g. {[xpath (no # allowed)]#[attrib][\'format\']})] [#max number][+offset]'), 352 | #('xml' , 'xml (url) [output fields (e.g. {[xpath (no # allowed)]#[attrib][\'format\']})] [#max number][+offset]'), 353 | #('json' , 'json (url) [output fields (e.g. {[xpath (no # allowed)]#[attrib][\'format\']})] [#max number][+offset]'), 354 | #('html' , 'html (url) [output fields (e.g. {[xpath]#[attrib][\'format\']})] [#max number][+offset]'), 355 | #('xml' , 'xml (url) [output fields (e.g. {[xpath]#[attrib][\'format\']})] [#max number][+offset]'), 356 | #('json' , 'json (url) [output fields (e.g. {[xpath]#[attrib][\'format\']})] [#max number][+offset]'), 357 | #('regex' , 'regex (url) [#max number][+offset]'), 358 | ] 359 | 360 | func = [ 361 | # no { in xpath 362 | (html , r"html(?:\s+(?Phttp\S+))?\s+(?P[^{]+?)(\s+{(?P.+)})?(\s+'(?P[^']+)')?(\s+(#(?P\d+))?(\+(?P\d+))?)?"), 363 | (xml , r"xml(?:\s+(?Phttp\S+))?\s+(?P[^{]+?)(\s+{(?P.+)})?(\s+'(?P[^']+)')?(\s+(#(?P\d+))?(\+(?P\d+))?)?"), 364 | (jsonxml , r"json(?:\s+(?Phttp\S+))?\s+(?P[^{]+?)(\s+{(?P.+)})?(\s+'(?P[^']+)')?(\s+(#(?P\d+))?(\+(?P\d+))?)?"), 365 | (regex , r"regex(:(?Pmulti)?)?(?:\s+(?Phttp\S+))?\s+(?P.+?)(\s+(#(?P\d+))?(\+(?P\d+))?)?"), 366 | ] 367 | -------------------------------------------------------------------------------- /python/modules/commands/util.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import re 3 | import base64 4 | import random 5 | from .common import Get 6 | 7 | 8 | def lsend(l, send, **kw): 9 | #send(l, n=len(l), llimit=10) 10 | send(l, n=0, llimit=10, **kw) 11 | 12 | 13 | async def cmdsub(cmd, string): 14 | print('cmdsub') 15 | 16 | #esc = [ 17 | # # irssi 18 | # # https://github.com/irssi/irssi/blob/master/src/fe-common/core/formats.c#L1086 19 | # # IS_COLOR_CODE(...) 20 | # # https://github.com/irssi/irssi/blob/master/src/fe-common/core/formats.c#L1254 21 | # # format_send_to_gui(...) 22 | # (r'\x02', '\x02'), 23 | # (r'\x03', '\x03'), 24 | # (r'\x04', '\x04'), 25 | # (r'\x06', '\x06'), 26 | # (r'\x07', '\x07'), 27 | # (r'\x0f', '\x0f'), 28 | # (r'\x16', '\x16'), 29 | # (r'\x1b', '\x1b'), 30 | # (r'\x1d', '\x1d'), 31 | # (r'\x1f', '\x1f'), 32 | #] 33 | 34 | async def identity(x): 35 | return x 36 | 37 | # how about normalizing the message? 38 | async def command(x): 39 | get = Get() 40 | status = await cmd(x, [], get) 41 | if status: 42 | #msg = get.str(sep=' ') 43 | #for (s, e) in esc: 44 | # msg = msg.replace(s, e) 45 | #return msg 46 | #return get.str(sep=' ') 47 | return get.str() 48 | else: 49 | return '({0})'.format(x) 50 | 51 | def splitter(s): 52 | i = 0 53 | j = 0 54 | n = 0 55 | while i < len(s): 56 | if s[i] == '(': 57 | n = n + 1 58 | if n == 1: 59 | yield s[j:i] 60 | j = i + 1 61 | elif s[i] == ')': 62 | n = n - 1 63 | if n == 0: 64 | yield s[j:i] 65 | j = i + 1 66 | i = i + 1 67 | if n != 0: 68 | raise Exception() 69 | else: 70 | yield s[j:i] 71 | 72 | 73 | #reg = re.compile(r"''(.*?)''") 74 | #reg = re.compile(r"\((.*?)\)") 75 | #s = reg.split(string) 76 | try: 77 | s = list(splitter(string)) 78 | except: 79 | print('unmatched parentheses: {0}'.format(repr(string))) 80 | return string 81 | 82 | coros = [command(x) if i % 2 == 1 else identity(x) for (i, x) in enumerate(s)] 83 | s = await asyncio.gather(*coros) 84 | print('cmdsub: {0}'.format(s)) 85 | 86 | return ''.join(s) 87 | 88 | 89 | async def lower(arg, send): 90 | send(arg['content'].lower()) 91 | 92 | 93 | async def upper(arg, send): 94 | send(arg['content'].upper()) 95 | 96 | 97 | async def newline(arg, send): 98 | send('\n\n') 99 | 100 | 101 | # coreutils 102 | 103 | 104 | async def echo(arg, send): 105 | send(arg['content'], raw=True) 106 | 107 | 108 | async def cat(arg, lines, send): 109 | lsend(lines, send, raw=bool(arg['raw'])) 110 | 111 | 112 | async def tac(arg, lines, send): 113 | lsend(list(reversed(lines)), send) 114 | 115 | 116 | async def tee(arg, lines, send): 117 | lsend(lines, send) 118 | # do not tee again? 119 | if arg['output'] == '\'': 120 | await arg['meta']['command'](arg['command'], lines, arg['meta']['send']) 121 | if arg['output'] == '"': 122 | await arg['meta']['command'](arg['command'], lines, arg['meta']['save']) 123 | 124 | 125 | async def head(arg, lines, send): 126 | l = int(arg['line'] or 10) 127 | lsend(lines[:l], send) 128 | 129 | 130 | async def tail(arg, lines, send): 131 | l = int(arg['line'] or 10) 132 | lsend(lines[(-l):], send) 133 | 134 | 135 | async def sort(arg, lines, send): 136 | lsend(sorted(lines), send) 137 | 138 | 139 | async def uniq(arg, lines, send): 140 | l = lines[:1] 141 | for e in lines: 142 | if e != l[-1]: 143 | l.append(e) 144 | lsend(l, send) 145 | 146 | 147 | async def b64(arg, lines, send): 148 | decode = arg['decode'] 149 | content = '\n'.join(lines) or arg['content'] or '' 150 | 151 | if not content: 152 | raise Exception() 153 | 154 | if decode: 155 | lsend(base64.b64decode(content).decode('utf-8', 'replace').splitlines(), send) 156 | else: 157 | send(base64.b64encode(content.encode('utf-8')).decode('utf-8', 'replace')) 158 | 159 | 160 | async def sleep(arg, lines, send): 161 | await asyncio.sleep(int(arg['time'])) 162 | send('wake up') 163 | 164 | 165 | async def wc(arg, lines, send): 166 | content = ('\n'.join(lines) or arg['content'] or '') + '\n' 167 | 168 | l = len(content.splitlines()) 169 | w = len(content.split()) 170 | b = len(content.encode()) 171 | send('{0} {1} {2}'.format(l, w, b)) 172 | 173 | 174 | async def shuf(arg, lines, send): 175 | random.shuffle(lines) 176 | lsend(lines, send) 177 | 178 | 179 | async def nl(arg, lines, send): 180 | for i in range(len(lines)): 181 | lines[i] = '{0} {1}'.format(i + 1, lines[i]) 182 | lsend(lines, send) 183 | 184 | 185 | async def paste(arg, lines, send): 186 | lsend([(arg['delimiter'] or '\n').join(lines)], send) 187 | 188 | 189 | async def tr(arg, lines, send): 190 | line = [arg['content']] if arg['content'] else lines 191 | lsend([l.replace(arg['old'], arg['new']) for l in line], send) 192 | 193 | # other 194 | 195 | 196 | class Sed: 197 | 198 | def __init__(self): 199 | #self.ra = r'(?:{0}|(?P\d+))'.format(r'(?:\\(?P[^\\/])|/)(?P.+?)(?(da)(?P=da)|/)') 200 | #self.rb = r'(?:{0}|(?P\d+))'.format(r'(?:\\(?P[^\\/])|/)(?P.+?)(?(db)(?P=db)|/)') 201 | self.ra = r'(?:(?P\d+)|{0})'.format(r'(?P\\)?(?P(?(a)[^\\]|/))(?P.+?)(?\d+)|{0})'.format(r'(?P\\)?(?P(?(b)[^\\]|/))(?P.+?)(?[^\\])(?P.*?)(?.*?)(?.*)' 205 | self.reg = re.compile(r'(?:{0}(?:\s*,\s*{1})?\s*)?'.format(self.ra, self.rb) + self.rs + self.rf) 206 | self.addr = re.compile(r'^(?:{0}(?:\s*,\s*{1})?\s*)?'.format(self.ra, self.rb)) 207 | self.s = re.compile(self.rs + self.rf) 208 | 209 | def getf(self, d): 210 | delimiter = d['d'] 211 | flag = d['flag'] 212 | rf = re.compile(d['from'].replace('\\' + delimiter, delimiter)) 213 | rt = d['to'].replace('\\' + delimiter, delimiter) 214 | 215 | if flag == '': 216 | return lambda l: rf.sub(rt, l, count=1) 217 | elif flag == 'g': 218 | return lambda l: rf.sub(rt, l) 219 | else: 220 | raise Exception() 221 | 222 | def getl(self, d, lines): 223 | na = int(d['na']) if d['na'] else -1 224 | da = d['da'] 225 | ra = re.compile(d['ra'].replace('\\' + da, da)) if d['ra'] else None 226 | nb = int(d['nb']) if d['nb'] else -1 227 | db = d['db'] 228 | rb = re.compile(d['rb'].replace('\\' + db, db)) if d['rb'] else None 229 | 230 | # only a 231 | 232 | if not rb and nb < 0: 233 | if na == 0: 234 | return [0] 235 | if na > 0: 236 | return [(na - 1)] 237 | if ra: 238 | line = [] 239 | for (i, l) in enumerate(lines): 240 | if ra.search(l): 241 | line.append(i) 242 | return line 243 | return list(range(len(lines))) 244 | 245 | # a and b 246 | 247 | if na == 0: 248 | na = 1 249 | if nb == 0: 250 | nb = 1 251 | 252 | if na > 0 and nb > 0: 253 | if (na - 1) < nb: 254 | return list(range((na - 1), nb)) 255 | else: 256 | return [(na - 1)] 257 | if na > 0 and rb: 258 | for (i, l) in enumerate(lines): 259 | if rb.search(l): 260 | break 261 | if (na - 1) < (i + 1): 262 | return list(range((na - 1), (i + 1))) 263 | else: 264 | return [(na - 1)] 265 | if ra and nb > 0: 266 | for (i, l) in enumerate(lines): 267 | if ra.search(l): 268 | break 269 | if i < nb: 270 | return list(range(i, nb)) 271 | else: 272 | return [i] 273 | if ra and rb: 274 | line = [] 275 | inrange = False 276 | for (i, l) in enumerate(lines): 277 | if not inrange: 278 | if ra.search(l): 279 | inrange = True 280 | line.append(i) 281 | else: 282 | line.append(i) 283 | if rb.search(l): 284 | inrange = False 285 | return line 286 | 287 | return [] 288 | 289 | async def __call__(self, arg, lines, send): 290 | if not lines: 291 | raise Exception() 292 | 293 | script = arg['script'] 294 | 295 | addr = self.addr.match(script) 296 | c = script[addr.end():] if addr else script 297 | comm = self.s.fullmatch(c) 298 | 299 | if not comm: 300 | raise Exception() 301 | 302 | da = addr.groupdict() 303 | dc = comm.groupdict() 304 | #send(da) 305 | #send(dc) 306 | 307 | f = self.getf(dc) 308 | tmp = lines 309 | for i in self.getl(da, tmp): 310 | tmp[i] = f(tmp[i]) 311 | line = [l for l in tmp if l] 312 | 313 | lsend(line, send) 314 | 315 | sed = Sed() 316 | 317 | 318 | help = [ 319 | ('echo' , 'echo -- 我才不会自问自答呢!'), 320 | ('cat' , 'cat [raw] -- meow~'), 321 | ('base64' , 'base64[:decode] (content)'), 322 | ] 323 | 324 | func = [ 325 | #(echo , r"echo (?P.+)"), 326 | (cat , r"cat(?:\s+(?Praw))?"), 327 | (tac , r"tac"), 328 | (tee , r"tee(?:\s+(?P['\"])(?P.+))?"), 329 | (head , r"head(?:\s+(?P\d+))?"), 330 | (tail , r"tail(?:\s+(?P\d+))?"), 331 | (sort , r"sort"), 332 | (uniq , r"uniq"), 333 | #(sed , r"sed\s(?P['\"])(?P