-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiicmd.py
executable file
·218 lines (182 loc) · 6.51 KB
/
iicmd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python3
# Workaround https://github.com/psf/black/issues/4175
"""Python implementation of commands for iibot.
2024/Mar/14 @ Zdenek Styblik <[email protected]>
"""
import argparse
import json
import logging
import os
import re
import shutil
import subprocess
import sys
import time
import traceback
import requests
# List of supported commands and whether command requires user input or not.
COMMANDS = {
"calc": True,
"echo": True,
"fortune": False,
"list": False,
"ping": False,
"slap": False,
"url": True,
"whereami": False,
}
HTTP_MAX_REDIRECTS = 2
HTTP_TIMEOUT = 30 # seconds
def cmd_fortune():
"""Try to get a fortune cookie."""
fortune_fpath = shutil.which("fortune", mode=os.F_OK | os.X_OK)
if not fortune_fpath:
print("Damn, I'm out of fortune cookies! :(")
return
with subprocess.Popen(
[fortune_fpath, "-osea"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
) as fortune_proc:
fortune_out, fortune_err = fortune_proc.communicate()
if fortune_proc.returncode != 0:
logging.error("fortune RC: %s", fortune_proc.returncode)
logging.error("fortune STDOUT: '%s'", fortune_out)
logging.error("fortune STDERR: '%s'", fortune_err)
print("Oh no, I've dropped my fortune cookie! :(")
return
print("{:s}".format(fortune_out.decode("utf-8").rstrip("\n")))
def get_url_short(url, bitly_gid, bitly_token):
"""Convert URL to a shorter one through bit.ly.
See https://dev.bitly.com/ for API documentation.
"""
short_url = url
try:
user_agent = "iicmd_{:d}".format(int(time.time()))
headers = {
"Authorization": "Bearer {:s}".format(bitly_token),
"Content-Type": "application/json",
"User-Agent": user_agent,
}
data = {
"long_url": url,
"domain": "bit.ly",
"group_guid": bitly_gid,
}
rsp_short = requests.post(
"https://api-ssl.bitly.com/v4/shorten",
headers=headers,
data=json.dumps(data),
timeout=HTTP_TIMEOUT,
)
rsp_short.raise_for_status()
if "link" not in rsp_short.json():
raise KeyError("Expected key 'link' not found in rsp from bit.ly")
short_url = rsp_short.json()["link"]
except Exception:
# NOTE: this isn't exactly great, but it simplifies the code.
logging.error(
"Failed to get short URL of '%s' due to: %s",
url,
traceback.format_exc(),
)
return short_url
def get_url_title(url):
"""Try to get and return title of given URL."""
url_title = "No title"
try:
session = requests.Session()
session.max_redirects = HTTP_MAX_REDIRECTS
user_agent = "iicmd_{:d}".format(int(time.time()))
headers = {"User-Agent": user_agent}
rsp_title = session.get(url, headers=headers, timeout=HTTP_TIMEOUT)
rsp_title.raise_for_status()
match = re.search(r"<title>(?P<title>[^<]*)<\/title>", rsp_title.text)
if match:
url_title = match.group("title")
else:
logging.debug("No title for '{:s}'".format(url))
except Exception:
# NOTE: this isn't exactly great, but it simplifies the code.
# No title then.
logging.error(
"HTTP req to '%s' has failed: %s", url, traceback.format_exc()
)
return url_title
def cmd_url(extra):
"""Process URL and print-out the result."""
match = re.search(r".*(?P<url>http[^ ]*).*", extra)
if not match:
logging.debug("No URL detected in '%s'", extra)
return
url = match.group("url")
# Convert YouTube URLs
url = re.sub(
r".*youtube\..*v=([^&]+).*", r"http://youtube.com/embed/\1", url
)
url = re.sub(r".*youtu\.be/(.+)", r"http://youtube.com/embed/\1", url)
# Try to get URL's title
url_title = get_url_title(url)
bitly_gid = os.getenv("IICMD_BITLY_GROUP_ID", None)
bitly_token = os.getenv("IICMD_BITLY_API_TOKEN", None)
if len(url) > 80 and bitly_gid and bitly_token:
url = get_url_short(url, bitly_gid, bitly_token)
print("Title for {:s} - {:s}".format(url, url_title))
def main():
"""Run iibot command."""
logging.basicConfig(stream=sys.stderr, encoding="utf-8")
args = parse_args()
cmd = args.message.split(" ")[0]
extra = " ".join(args.message.split(" ")[1:])
# Strip leading/trailing whitespace and check, if we have any "extra" left
# TODO: what about newlines?
extra = extra.strip(" ")
if not extra and cmd in COMMANDS and COMMANDS[cmd] is True:
cmd = "invalid"
if cmd == "list":
print(
"{:s}: supported commands are - {:s}".format(
args.nick, ", ".join(sorted(list(COMMANDS.keys())))
)
)
elif cmd == "calc":
# TODO: this will be big pain and huge amount of LOC to implement
# See https://stackoverflow.com/a/11952343
print("{:s}: my ALU is b0rked - does not compute.".format(args.nick))
elif cmd == "echo":
print("{:s}".format(extra.lstrip("/")))
elif cmd == "fortune":
cmd_fortune()
elif cmd == "ping":
print("{:s}: pong! Ping-pong, get it?".format(args.nick))
elif cmd == "slap":
print("{:s}: I'll slap your butt!".format(args.nick))
elif cmd == "url":
cmd_url(extra)
elif cmd == "whereami":
print("{:s}: this! is!! {:s}!!!".format(args.nick, args.channel))
else:
print(
"{:s}: what are you on about? Me not understanding.".format(
args.nick
)
)
def parse_args():
"""Return parsed CLI args."""
parser = argparse.ArgumentParser()
parser.add_argument("--nick", type=str, required=True)
parser.add_argument("--message", type=str, required=True)
parser.add_argument("--ircd", type=str, required=True)
parser.add_argument("--network", type=str, required=True)
parser.add_argument("--channel", type=str, required=True)
parser.add_argument("--self", type=str, required=True)
args = parser.parse_args()
if not args.nick:
args.nick = "unknown.stranger"
if not args.ircd:
raise argparse.ArgumentError("Argument 'ircd' must not be empty")
if not args.network:
raise argparse.ArgumentError("Argument 'network' must not be empty")
if not args.channel:
raise argparse.ArgumentError("Argument 'channel' must not be empty")
return args
if __name__ == "__main__":
main()