-
Notifications
You must be signed in to change notification settings - Fork 0
/
sMythClient.py
176 lines (147 loc) · 7.53 KB
/
sMythClient.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
#!/usr/bin/env python3
import asyncio
import os
import configparser
from importlib import util
from nio import (AsyncClient, SyncResponse, RoomMessageText)
import sys
import smythbotCommandRunner
class smythClient(object):
response = None
def __init__(self, homeserver, username, password, use_ssl):
self.homeserver = homeserver
self.username = username
self.password = password
self.client = AsyncClient(self.homeserver, self.username, ssl = use_ssl)
#Initialize per room configurator
self.roomConfigsPath = os.path.expanduser("~/.smythbot/rooms.ini")
self.smythbotRoomConfigs = configparser.ConfigParser()
# Declare the nessescary variables:
self.isSynced = False
self.smythbot_handler = "!smythbot"
#Add callbacks
self.client.add_event_callback(self.onNewMatrixEventReccieved, RoomMessageText)
sync_event = asyncio.create_task(self.watch_for_sync(self.client.synced))
return
async def init_login(self):
try:
self.response = await self.client.login(self.password)
if "M_FORBIDDEN" in str(self.response):
print(str(self.response) + "\nExiting")
os._exit(1)
except:
print("There was a problem logging into the specified homeserver.")
LoginError = sys.exc_info()
print("The error was: " + str(LoginError))
print ("Exiting...")
os._exit(1)
return
async def start_client(self):
"""
Name: start_client
Expected input: None
Expected output: None (may change)
Description: This function is meant to be called in the main program loop
It's job is to start all the other functions asscociated with the Matrix client
end of sMythbot.
First it calls the init_login function. If that is successful, it will request
a list of available rooms that we have joined to check for configuration options
(which will be checked via other functons). When that is done, it will start the
synchronization loop in AsyncClient and return
"""
print("Attempting to login to " + self.homeserver)
await self.init_login()
print(self.response)
print("Firing initial sync...")
await self.sync_room_configs()
print ("Starting \"Sync Forever\" loop.\n")
try:
await self.client.sync_forever(timeout=30000, full_state=True)
except KeyboardInterrupt:
print("Caught exit signal. Closing sMythbot.")
self.client.close()
sys.exit(0)
return
async def sync_room_configs(self):
"""
Name: Sync_room_configs
Expected input: None
Expected output: None (may change)
Description: This function reads sMythbot room configurations from the rooms.ini file, or creates them.
Each room that sMythbot is part of can be configured with seperate properties.
These properties will be updated as the bot's settings are changed.
"""
# Fire an initial sync to get a rooms list:
first_sync = await self.client.sync(300000)
# Then we check if roooms.ini exists.
if os.path.exists(self.roomConfigsPath):
self.smythbotRoomConfigs.read(self.roomConfigsPath) #If so, we read the existing values into the configparser
for room_id in first_sync.rooms.join: # Now we check for individual room configurations via the existence of a room ID in the file
# First, the easy part. If a room ID DOES NOT exist, we can safely assume there are no config options for it yet:
newDataWritten = False
if not self.smythbotRoomConfigs.has_section(room_id):
newDataWritten = True
await self.populateRoomConfigs(room_id, False)
if newDataWritten:
await self.writeChangesToDisk()
else:
print ("No new room configurations found")
async def populateRoomConfigs(self, room_id, writeToDisk):
self.smythbotRoomConfigs[room_id] = {}
self.smythbotRoomConfigs[room_id]["MythTv Backend Address"] = "not set"
self.smythbotRoomConfigs[room_id]["MythTv Backend Port"] = "6544"
self.smythbotRoomConfigs[room_id]["Output Type"] = "table"
self.smythbotRoomConfigs[room_id]["Room Notifications"] = "False"
print("Added new room Configuration " + room_id)
if writeToDisk:
await self.writeChangesToDisk()
return
async def writeChangesToDisk(self):
print("Writing New data to file: " + self.roomConfigsPath)
with open(self.roomConfigsPath, "w") as roomsFile:
self.smythbotRoomConfigs.write(roomsFile)
async def watch_for_sync(self, sync_event):
"""
Input: AsyncClient Sync Event
Output: None
Description: When AsyncClient fires a synced event (which only happens during a "sync_forever" loop), this function is called.
"""
while True:
await sync_event.wait()
await self.onIsSyncedCalled()
async def onIsSyncedCalled(self):
"""
Called from the "watch_for_sync" event. This funtion sets the client state as being up to speed with
the current messages.
"""
#print ("We are synced!")
self.isSynced = True
return
async def onNewMatrixEventReccieved(self, room, event):
if self.isSynced and event.body.startswith(self.smythbot_handler):
print("Reccieved sMythbot command from room " + room.room_id + ", sent by " + event.sender)
if not event.body.isprintable():
print ("ERROR: Not sending command. Non-printable characters may cause a security risk")
await self.client.room_send(room.machine_name, "m.room.message", await self.reply("<h1>Command not processed</h1><hr> The command you sent contains at least one non-printable character. As these often pose security risks, sMythbot will not process it."))
return
command_runner = smythbotCommandRunner.smythbot_command(event.body, mythtv_backend=self.smythbotRoomConfigs[room.room_id]["MythTv Backend Address"], mythtv_port=self.smythbotRoomConfigs[room.room_id]["MythTv Backend Port"], formatting=self.smythbotRoomConfigs[room.room_id]["Output Type"])
command_outputs = await command_runner.poulate_command_index() # The various smythbot commands will be processed inside of this function
for item in command_outputs:
for key_item in item.keys():
if key_item == "room settings data":
await self.adjust_room_settings(item["room settings data"], room.room_id)
await self.client.room_send(room.machine_name, "m.room.message", await self.reply(item["command output"]))
else:
return
async def reply(self, reply_body):
reply_content = {}
reply_content["msgtype"] = "m.notice"
reply_content["body"] =""
reply_content["format"] = "org.matrix.custom.html"
reply_content["formatted_body"] = reply_body
return reply_content
async def adjust_room_settings(self, room_settings_dict, room_id):
print("adjusting room setings in room " + room_id)
self.smythbotRoomConfigs[room_id][room_settings_dict["property name"]] = room_settings_dict["property value"]
await self.writeChangesToDisk()
return