5 from argparse
import ArgumentParser
8 class OtrContext(potr
.context
.Context
):
10 def __init__(self
, account
, peer
):
11 super(OtrContext
, self
).__init
__(account
, peer
)
13 def getPolicy(self
, key
):
16 def inject(self
, body
, appdata
=None):
17 msg
= appdata
["base_reply"]
18 msg
["body"] = str(body
)
19 appdata
["send_raw_message_fn"](msg
)
22 class BotAccount(potr
.context
.Account
):
24 def __init__(self
, jid
, keyFilePath
):
26 max_message_size
= 10*1024
27 super(BotAccount
, self
).__init
__(jid
, protocol
, max_message_size
)
28 self
.keyFilePath
= keyFilePath
30 def loadPrivkey(self
):
31 with
open(self
.keyFilePath
, 'rb') as keyFile
:
32 return potr
.crypt
.PK
.parsePrivateKey(keyFile
.read())[0]
35 class OtrContextManager
:
37 def __init__(self
, jid
, keyFilePath
):
38 self
.account
= BotAccount(jid
, keyFilePath
)
41 def start_context(self
, other
):
42 if other
not in self
.contexts
:
43 self
.contexts
[other
] = OtrContext(self
.account
, other
)
44 return self
.contexts
[other
]
46 def get_context_for_user(self
, other
):
47 return self
.start_context(other
)
50 class OtrBot(slixmpp
.ClientXMPP
):
52 def __init__(self
, account
, password
, otr_key_path
,
53 rooms
=[], connect_server
=None, log_file
=None):
54 self
.__connect
_server
= connect_server
55 self
.__password
= password
56 self
.__log
_file
= log_file
58 super().__init
__(account
, password
)
59 self
.__otr
_manager
= OtrContextManager(account
, otr_key_path
)
60 self
.send_raw_message_fn
= self
.raw_send
61 self
.__default
_otr
_appdata
= {
62 "send_raw_message_fn": self
.send_raw_message_fn
64 self
.add_event_handler("session_start", self
.start
)
65 self
.add_event_handler("message", self
.handle_message
)
66 self
.register_plugin("xep_0045") # Multi-User Chat
67 self
.register_plugin("xep_0394") # Message Markup
69 def __otr_appdata_for_msg(self
, msg
):
70 appdata
= self
.__default
_otr
_appdata
.copy()
71 appdata
["base_reply"] = msg
76 if self
.__connect
_server
:
77 address
= (self
.__connect
_server
, self
.default_port
)
78 super().connect(address
)
80 async def start(self
, event
):
82 await self
.get_roster()
83 for room
in self
.__rooms
:
86 def join_room(self
, room
):
87 self
.plugin
["xep_0045"].join_muc(room
, self
.boundjid
.user
)
89 def raw_send(self
, msg
):
92 def get_reply(self
, command
):
93 if command
.strip() == "ping":
97 def handle_message(self
, msg
):
98 msg
= self
.decrypt(msg
)
100 if msg
["type"] == "chat":
101 if msg
["html"]["body"].startswith("<p>?OTRv"):
103 reply
= self
.get_reply(msg
["body"])
104 elif msg
["type"] == "groupchat":
106 recipient
, command
= msg
["body"].split(":", 1)
108 recipient
, command
= None, msg
["body"]
109 if msg
["mucnick"] == self
.boundjid
.user
or \
110 recipient
!= self
.boundjid
.user
:
112 response
= self
.get_reply(command
)
114 reply
= "%s: %s" % (msg
["mucnick"], response
)
118 self
.send_message(msg
.reply(reply
))
120 def send_message(self
, msg
):
121 otrctx
= self
.__otr
_manager
.get_context_for_user(msg
["to"])
122 if otrctx
.state
== potr
.context
.STATE_ENCRYPTED
:
123 otrctx
.sendMessage(potr
.context
.FRAGMENT_SEND_ALL
,
124 msg
["body"].encode("utf-8"),
125 appdata
=self
.__otr
_appdata
_for
_msg
(msg
))
129 def decrypt(self
, msg
):
130 if msg
["type"] == "groupchat":
132 otrctx
= self
.__otr
_manager
.get_context_for_user(msg
["from"])
133 if msg
["type"] == "chat":
135 appdata
= self
.__otr
_appdata
_for
_msg
(msg
.reply())
136 plaintext
, tlvs
= otrctx
.receiveMessage(
137 msg
["body"].encode("utf-8"),
141 decrypted_body
= plaintext
.decode("utf-8")
144 otrctx
.processTLVs(tlvs
)
145 except potr
.context
.NotEncryptedError
:
146 otrctx
.authStartV2(appdata
=appdata
)
148 except (potr
.context
.UnencryptedMessage
,
149 potr
.context
.NotOTRMessage
):
150 decrypted_body
= msg
["body"]
152 decrypted_body
= msg
["body"]
153 msg
["body"] = decrypted_body
157 if __name__
== '__main__':
158 parser
= ArgumentParser()
159 parser
.add_argument("account",
160 help="the user account, given as user@domain")
161 parser
.add_argument("password",
162 help="the user account's password")
163 parser
.add_argument("otr_key_path",
164 help="the path to the account's OTR key file")
165 parser
.add_argument("-c", "--connect-server", metavar
='ADDRESS',
166 help="use a Connect Server, given as host[:port] " +
167 "(port defaults to 5222)")
168 parser
.add_argument("-j", "--auto-join", nargs
='+', metavar
='ROOMS',
169 help="auto-join multi-user chatrooms on start",
171 parser
.add_argument("-l", "--log-file", metavar
='LOGFILE',
172 help="Log to file instead of stderr")
173 parser
.add_argument("-d", "--debug",
174 help="enable debug logging",
175 action
="store_const", dest
="loglevel",
176 const
=logging
.DEBUG
, default
=logging
.FATAL
)
177 args
= parser
.parse_args()
178 logging
.basicConfig(level
=args
.loglevel
,
179 format
='%(levelname)-8s %(message)s')
180 otr_bot
= OtrBot(args
.account
,
183 rooms
=args
.auto_join
,
184 connect_server
=args
.connect_server
,
185 log_file
=args
.log_file
)
189 except KeyboardInterrupt: