Compare commits

..

5 Commits

3 changed files with 111 additions and 19 deletions

56
README.md Normal file
View File

@@ -0,0 +1,56 @@
maidbot
====
A bot program for fediverse.
## CONFIGURATION
Create `config.json` like below.
```json
{
"logging" : "DEBUG",
"instance": "falsy.cat",
"username": "maidbot",
"password": "helloworld",
"master" : "user ID of the master (not username)",
"interval": "interval seconds of a single cycle",
"mods": {
"MODNAME": {
"key": "param"
}
}
}
```
You can enable a mod by inserting new item to the `mods`.
## MODS
I'll add new one when I need.
### elicense
**THIS MOD MAY CAUSE YOU A CANCELLALATION FEE BY UNEXPECTED BEHAVIOUR BUT I DON'T TAKE ANY RESPONSIBILITY**
This mod can make a reservation through elicense.jp.
To enable this mod, insert the below object into the `mods`.
Now you can say `@maidbot watch 19900101 1 2 3 4` to make a reservation on one of periods from 1 to 4 on 1990/01/01.
```json
"elicense": {
"logging" : "INFO",
"school" : "school ID",
"username": "username",
"password": "password"
}
```
## LICENSE
WTFPLv2

31
main.py
View File

@@ -44,16 +44,17 @@ class App:
def __init__(self):
logging.basicConfig()
self.logging = logging.getLogger("app")
self.logging.setLevel(logging.DEBUG)
with open("config.json", "r") as f:
self.CONFIG = json.load(f)
self.logging.setLevel(self.CONFIG["logging"])
self.logging.info("configuration loaded")
self.M = self.authenticate()
self.logging.info("authentication succeeded")
self.mods = {}
self.install("elicense", mods.elicense)
self.alive = True
signal.signal(signal.SIGTERM, lambda a,b: self.exit())
@@ -90,13 +91,18 @@ class App:
return M
def install(self, name, mod):
config = self.CONFIG["mods"].get(name)
if config is None:
return
logger = logging.getLogger("mods."+name)
if "logging" in config:
logger.setLevel(config["logging"])
self.mods[name] = mod.init(config, logger)
self.logging.info("mod installed, "+name)
logger = logging.getLogger("mod."+name)
self.mods[name] = mod.init(self.CONFIG["mods"][name], logger)
def run(self):
self.install("elicense", mods.elicense)
while self.alive:
try:
self.cycle()
@@ -104,13 +110,12 @@ class App:
except Exception as e:
self.logging.warning("cycle aborted:", e)
for mod in self.mods:
self.mods[mod].cycle()
for i in range(self.CONFIG["interval"]):
if not self.alive: break
time.sleep(1)
del self.mods
def cycle(self):
items = self.M.notifications(
account_id = self.CONFIG["master"],
@@ -123,7 +128,8 @@ class App:
self.reply(st, "handling aborted: {}".format(e))
self.M.notifications_dismiss(id = item["id"])
# TODO: call mod cycles
for mod in self.mods:
self.mods[mod].cycle()
def exit(self):
self.alive = False
@@ -131,10 +137,11 @@ class App:
def reply(self, st, msg):
try:
self.M.status_post(
status = "@{} {}".format(st["account"]["acct"], msg),
in_reply_to_id = st["id"],
visibility = "direct")
self.logging.debug("reply:", msg)
#self.M.status_post(
# status = "@{} {}".format(st["account"]["acct"], msg),
# in_reply_to_id = st["id"])
except Exception as e:
self.logging.error("reply failure:", msg, "(", e, ")")

View File

@@ -7,7 +7,6 @@ import requests
def init(CONFIG, logger):
logger.setLevel(logging.DEBUG)
return Mod(CONFIG, logger)
@@ -22,6 +21,18 @@ class Mod:
self.ss = self.authenticate()
def __del__(self):
res = self.ss.get(
url = self.URL+"logout.action",
params = {
"b.schoolCd": self.CONFIG["school"],
"senisakiCd": 4,
})
res.encoding = "shift_jis"
self.calender = Calender(res.text)
self.logging.info("logout succeeded")
def cycle(self):
if len(self.targets) == 0:
return
@@ -30,7 +41,7 @@ class Mod:
url = self.URL+"p04a.action",
data = {
"b.processCd": "A",
"b.kamokuCd" : 0,
"b.kamokuCd" : self.calender.params["kamokuCd"],
"b.page" : 1,
"b.schoolCd" : self.CONFIG["school"],
})
@@ -62,12 +73,12 @@ class Mod:
data = {
"b.schoolCd" : self.CONFIG["school"],
"b.processCd" : "V",
"b.kamokuCd" : 0,
"b.instructorTypeCd" : 0,
"b.kamokuCd" : self.calender.params["kamokuCd"],
"b.instructorTypeCd" : self.calender.params["instructorTypeCd"],
"b.dateInformationType": date,
"b.infoPeriodNumber" : slot,
"b.carModelCd" : self.calender.carModel,
"b.instructorCd" : 0,
"b.carModelCd" : self.calender.params["carModelCd"],
"b.instructorCd" : self.calender.params["instructorCd"],
"b.page" : 1,
"b.groupCd" : self.calender.group,
})
@@ -107,6 +118,13 @@ class Calender:
raise Exception(m[1])
self.avails = re.findall(RE_AVAIL_SLOT, text)
print(self.avails)
self.params = {}
for name in RE_PARAMS:
m = re.search(RE_PARAMS[name], text)
if m is not None:
self.params[name] = m[1]
def checkAvailable(self, date, slot):
for avail in self.avails:
@@ -130,4 +148,15 @@ TIMETABLE = {
RE_ALERT = re.compile(r"window\.alert\('(.*)'\)")
RE_ERROR = re.compile(r"<.*? class=\"errorTitle\">メッセージ<\/.*?>\s*<.*? class=\"errorDisp\">(.*?)<\/.*?>", re.MULTILINE)
RE_AVAIL_SLOT = re.compile(r"^\s*<a href=\"#\" onclick=\"sendContent\('(\d+)','(\d+)','V',document\.getElementById\('formId'\)\)\">$")
RE_AVAIL_SLOT = re.compile(r"<a href=\"#\" onclick=\"sendContent\('(\d+)','(\d+)','V',document\.getElementById\('formId'\)\)\">")
RE_PARAMS = {
"kamokuCd" : None,
"instructorTypeCd": None,
"carModelCd" : None,
"instructorCd" : None,
"groupCd" : None,
}
for name in RE_PARAMS:
RE_PARAMS[name] = re.compile(
r"<input type=\"hidden\" name=\"b.{0}\" value=\"(.*?)\" id=\"".format(name))