]>
code.delx.au - pymsnt/blob - src/misciq.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
5 from twisted
.internet
import reactor
, task
, protocol
, error
6 from twisted
.words
.xish
.domish
import Element
7 from twisted
.words
.protocols
.jabber
.jid
import internJID
8 from debug
import LogEvent
, INFO
, WARN
, ERROR
21 def __init__(self
, pytrans
):
22 self
.pytrans
= pytrans
23 self
.pytrans
.adHocCommands
.addCommand("connectusers", self
.incomingIq
, "command_ConnectUsers")
26 for jid
in self
.pytrans
.xdb
.files():
27 jabw
.sendPresence(self
.pytrans
, jid
, config
.jid
, ptype
="probe")
29 def incomingIq(self
, el
):
30 to
= el
.getAttribute("from")
31 ID
= el
.getAttribute("id")
32 ulang
= utils
.getLang(el
)
34 if config
.admins
.count(internJID(to
).userhost()) == 0:
35 self
.pytrans
.discovery
.sendIqError(to
=to
, fro
=config
.jid
, ID
=ID
, xmlns
=disco
.COMMANDS
, etype
="cancel", condition
="not-authorized")
41 iq
= Element((None, "iq"))
42 iq
.attributes
["to"] = to
43 iq
.attributes
["from"] = config
.jid
45 iq
.attributes
["id"] = ID
46 iq
.attributes
["type"] = "result"
48 command
= iq
.addElement("command")
49 command
.attributes
["sessionid"] = self
.pytrans
.makeMessageID()
50 command
.attributes
["xmlns"] = disco
.COMMANDS
51 command
.attributes
["status"] = "completed"
53 x
= command
.addElement("x")
54 x
.attributes
["xmlns"] = disco
.XDATA
55 x
.attributes
["type"] = "result"
57 title
= x
.addElement("title")
58 title
.addContent(lang
.get(ulang
).command_ConnectUsers
)
60 field
= x
.addElement("field")
61 field
.attributes
["type"] = "fixed"
62 field
.addElement("value").addContent(lang
.get(ulang
).command_Done
)
68 def __init__(self
, pytrans
):
69 self
.pytrans
= pytrans
70 self
.pytrans
.adHocCommands
.addCommand("stats", self
.incomingIq
, "command_Statistics")
72 # self.stats is indexed by a unique ID, with value being the value for that statistic
74 self
.stats
["Uptime"] = 0
75 self
.stats
["OnlineUsers"] = 0
76 self
.stats
["TotalUsers"] = 0
78 legacy
.startStats(self
)
80 def incomingIq(self
, el
):
81 to
= el
.getAttribute("from")
82 ID
= el
.getAttribute("id")
83 ulang
= utils
.getLang(el
)
85 iq
= Element((None, "iq"))
86 iq
.attributes
["to"] = to
87 iq
.attributes
["from"] = config
.jid
89 iq
.attributes
["id"] = ID
90 iq
.attributes
["type"] = "result"
92 command
= iq
.addElement("command")
93 command
.attributes
["sessionid"] = self
.pytrans
.makeMessageID()
94 command
.attributes
["xmlns"] = disco
.COMMANDS
95 command
.attributes
["status"] = "completed"
97 x
= command
.addElement("x")
98 x
.attributes
["xmlns"] = disco
.XDATA
99 x
.attributes
["type"] = "result"
101 title
= x
.addElement("title")
102 title
.addContent(lang
.get(ulang
).command_Statistics
)
104 for key
in self
.stats
:
105 label
= getattr(lang
.get(ulang
), "command_%s" % key
)
106 description
= getattr(lang
.get(ulang
), "command_%s_Desc" % key
)
107 field
= x
.addElement("field")
108 field
.attributes
["var"] = key
109 field
.attributes
["label"] = label
110 field
.attributes
["type"] = "text-single"
111 field
.addElement("value").addContent(str(self
.stats
[key
]))
112 field
.addElement("desc").addContent(description
)
114 self
.pytrans
.send(iq
)
119 def __init__(self
, pytrans
):
120 self
.pytrans
= pytrans
121 self
.pytrans
.discovery
.addFeature(disco
.COMMANDS
, self
.incomingIq
, config
.jid
)
122 self
.pytrans
.discovery
.addNode(disco
.COMMANDS
, self
.sendCommandList
, "command_CommandList", config
.jid
, True)
124 self
.commands
= {} # Dict of handlers indexed by node
125 self
.commandNames
= {} # Dict of names indexed by node
127 def addCommand(self
, command
, handler
, name
):
128 self
.commands
[command
] = handler
129 self
.commandNames
[command
] = name
130 self
.pytrans
.discovery
.addNode(command
, self
.incomingIq
, name
, config
.jid
, False)
132 def incomingIq(self
, el
):
133 itype
= el
.getAttribute("type")
134 fro
= el
.getAttribute("from")
135 froj
= internJID(fro
)
136 to
= el
.getAttribute("to")
137 ID
= el
.getAttribute("id")
139 LogEvent(INFO
, "", "Looking for handler")
142 for child
in el
.elements():
144 node
= child
.getAttribute("node")
147 if(child
.name
== "query" and xmlns
== disco
.DISCO_INFO
):
148 if(node
and self
.commands
.has_key(node
) and (itype
== "get")):
149 self
.sendCommandInfoResponse(to
=fro
, ID
=ID
)
151 elif(child
.name
== "query" and xmlns
== disco
.DISCO_ITEMS
):
152 if(node
and self
.commands
.has_key(node
) and (itype
== "get")):
153 self
.sendCommandItemsResponse(to
=fro
, ID
=ID
)
155 elif(child
.name
== "command" and xmlns
== disco
.COMMANDS
):
156 if((node
and self
.commands
.has_key(node
)) and (itype
== "set" or itype
== "error")):
157 self
.commands
[node
](el
)
160 LogEvent(WARN
, "", "Unknown Ad-Hoc command received.")
161 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
=xmlns
, etype
="cancel", condition
="feature-not-implemented")
164 def sendCommandList(self
, el
):
165 to
= el
.getAttribute("from")
166 ID
= el
.getAttribute("id")
167 ulang
= utils
.getLang(el
)
169 iq
= Element((None, "iq"))
170 iq
.attributes
["to"] = to
171 iq
.attributes
["from"] = config
.jid
173 iq
.attributes
["id"] = ID
174 iq
.attributes
["type"] = "result"
176 query
= iq
.addElement("query")
177 query
.attributes
["xmlns"] = disco
.DISCO_ITEMS
178 query
.attributes
["node"] = disco
.COMMANDS
180 for command
in self
.commands
:
181 item
= query
.addElement("item")
182 item
.attributes
["jid"] = config
.jid
183 item
.attributes
["node"] = command
184 item
.attributes
["name"] = getattr(lang
.get(ulang
), self
.commandNames
[command
])
186 self
.pytrans
.send(iq
)
188 def sendCommandInfoResponse(self
, to
, ID
):
189 LogEvent(INFO
, "", "Replying to disco#info")
190 iq
= Element((None, "iq"))
191 iq
.attributes
["type"] = "result"
192 iq
.attributes
["from"] = config
.jid
193 iq
.attributes
["to"] = to
194 if(ID
): iq
.attributes
["id"] = ID
195 query
= iq
.addElement("query")
196 query
.attributes
["xmlns"] = disco
.DISCO_INFO
198 feature
= query
.addElement("feature")
199 feature
.attributes
["var"] = disco
.COMMANDS
200 self
.pytrans
.send(iq
)
202 def sendCommandItemsResponse(self
, to
, ID
):
203 LogEvent(INFO
, "", "Replying to disco#items")
204 iq
= Element((None, "iq"))
205 iq
.attributes
["type"] = "result"
206 iq
.attributes
["from"] = config
.jid
207 iq
.attributes
["to"] = to
208 if(ID
): iq
.attributes
["id"] = ID
209 query
= iq
.addElement("query")
210 query
.attributes
["xmlns"] = disco
.DISCO_ITEMS
211 self
.pytrans
.send(iq
)
215 def __init__(self
, pytrans
):
216 self
.pytrans
= pytrans
217 self
.pytrans
.discovery
.addFeature("vcard-temp", self
.incomingIq
, "USER")
218 self
.pytrans
.discovery
.addFeature("vcard-temp", self
.incomingIq
, config
.jid
)
220 def incomingIq(self
, el
):
221 itype
= el
.getAttribute("type")
222 fro
= el
.getAttribute("from")
223 froj
= internJID(fro
)
224 to
= el
.getAttribute("to")
226 ID
= el
.getAttribute("id")
227 if itype
!= "get" and itype
!= "error":
228 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
="vcard-temp", etype
="cancel", condition
="feature-not-implemented")
231 LogEvent(INFO
, "", "Sending vCard")
233 toGateway
= not (to
.find('@') > 0)
236 if not self
.pytrans
.sessions
.has_key(froj
.userhost()):
237 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
="vcard-temp", etype
="auth", condition
="not-authorized")
239 s
= self
.pytrans
.sessions
[froj
.userhost()]
241 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
="vcard-temp", etype
="auth", condition
="not-authorized")
244 c
= s
.contactList
.findContact(toj
.userhost())
246 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
="vcard-temp", etype
="cancel", condition
="recipient-unavailable")
250 iq
= Element((None, "iq"))
251 iq
.attributes
["to"] = fro
252 iq
.attributes
["from"] = to
254 iq
.attributes
["id"] = ID
255 iq
.attributes
["type"] = "result"
256 vCard
= iq
.addElement("vCard")
257 vCard
.attributes
["xmlns"] = "vcard-temp"
259 FN
= vCard
.addElement("FN")
260 FN
.addContent(config
.discoName
)
261 DESC
= vCard
.addElement("DESC")
262 DESC
.addContent(config
.discoName
)
263 URL
= vCard
.addElement("URL")
264 URL
.addContent(legacy
.url
)
267 NICKNAME
= vCard
.addElement("NICKNAME")
268 NICKNAME
.addContent(c
.nickname
)
270 PHOTO
= c
.avatar
.makePhotoElement()
271 vCard
.addChild(PHOTO
)
273 self
.pytrans
.send(iq
)
275 class IqAvatarFactory
:
276 def __init__(self
, pytrans
):
277 self
.pytrans
= pytrans
278 self
.pytrans
.discovery
.addFeature(disco
.IQAVATAR
, self
.incomingIq
, "USER")
279 self
.pytrans
.discovery
.addFeature(disco
.STORAGEAVATAR
, self
.incomingIq
, "USER")
281 def incomingIq(self
, el
):
282 itype
= el
.getAttribute("type")
283 fro
= el
.getAttribute("from")
284 froj
= internJID(fro
)
285 to
= el
.getAttribute("to")
287 ID
= el
.getAttribute("id")
289 if(itype
!= "get" and itype
!= "error"):
290 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
=disco
.IQAVATAR
, etype
="cancel", condition
="feature-not-implemented")
293 LogEvent(INFO
, "", "Retrieving avatar")
295 if(not self
.pytrans
.sessions
.has_key(froj
.userhost())):
296 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
=disco
.IQAVATAR
, etype
="auth", condition
="not-authorized")
298 s
= self
.pytrans
.sessions
[froj
.userhost()]
300 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
=disco
.IQAVATAR
, etype
="auth", condition
="not-authorized")
303 c
= s
.contactList
.findContact(toj
.userhost())
305 self
.pytrans
.discovery
.sendIqError(to
=fro
, fro
=config
.jid
, ID
=ID
, xmlns
=disco
.IQAVATAR
, etype
="cancel", condition
="recipient-unavailable")
308 iq
= Element((None, "iq"))
309 iq
.attributes
["to"] = fro
310 iq
.attributes
["from"] = to
312 iq
.attributes
["id"] = ID
313 iq
.attributes
["type"] = "result"
314 query
= iq
.addElement("query")
315 query
.attributes
["xmlns"] = disco
.IQAVATAR
317 DATA
= c
.avatar
.makeDataElement()
320 self
.pytrans
.send(iq
)
325 def __init__(self
, pytrans
):
326 self
.pytrans
= pytrans
327 # self.pingCounter = 0
328 # self.pingTask = task.LoopingCall(self.pingCheck)
329 self
.pingTask
= task
.LoopingCall(self
.whitespace
)
330 # reactor.callLater(10.0, self.start)
333 # self.pingTask.start(120.0)
335 def whitespace(self
):
336 self
.pytrans
.send(" ")
338 # def pingCheck(self):
339 # if(self.pingCounter >= 2 and self.pytrans.xmlstream): # Two minutes of no response from the main server
340 # LogEvent(WARN, "", "Disconnecting because the main server has ignored our pings for too long.")
341 # self.pytrans.xmlstream.transport.loseConnection()
342 # elif(config.mainServerJID):
343 # d = self.pytrans.discovery.sendIq(self.makePingPacket())
344 # d.addCallback(self.pongReceived)
345 # d.addErrback(self.pongFailed)
346 # self.pingCounter += 1
348 # def pongReceived(self, el):
349 # self.pingCounter = 0
351 # def pongFailed(self, el):
354 # def makePingPacket(self):
355 # iq = Element((None, "iq"))
356 # iq.attributes["from"] = config.jid
357 # iq.attributes["to"] = config.mainServerJID
358 # iq.attributes["type"] = "get"
359 # query = iq.addElement("query")
360 # query.attributes["xmlns"] = disco.IQVERSION
363 class GatewayTranslator
:
364 def __init__(self
, pytrans
):
365 self
.pytrans
= pytrans
366 self
.pytrans
.discovery
.addFeature(disco
.IQGATEWAY
, self
.incomingIq
, config
.jid
)
368 def incomingIq(self
, el
):
369 fro
= el
.getAttribute("from")
370 ID
= el
.getAttribute("id")
371 itype
= el
.getAttribute("type")
373 self
.sendPrompt(fro
, ID
, utils
.getLang(el
))
374 elif(itype
== "set"):
375 self
.sendTranslation(fro
, ID
, el
)
378 def sendPrompt(self
, to
, ID
, ulang
):
381 iq
= Element((None, "iq"))
383 iq
.attributes
["type"] = "result"
384 iq
.attributes
["from"] = config
.jid
385 iq
.attributes
["to"] = to
387 iq
.attributes
["id"] = ID
388 query
= iq
.addElement("query")
389 query
.attributes
["xmlns"] = disco
.IQGATEWAY
390 desc
= query
.addElement("desc")
391 desc
.addContent(lang
.get(ulang
).gatewayTranslator
)
392 prompt
= query
.addElement("prompt")
394 self
.pytrans
.send(iq
)
396 def sendTranslation(self
, to
, ID
, el
):
399 # Find the user's legacy account
401 for query
in el
.elements():
402 if(query
.name
== "query"):
403 for child
in query
.elements():
404 if(child
.name
== "prompt"):
405 legacyaccount
= str(child
)
410 if(legacyaccount
and len(legacyaccount
) > 0):
411 LogEvent(INFO
, "", "Sending translated account.")
412 iq
= Element((None, "iq"))
413 iq
.attributes
["type"] = "result"
414 iq
.attributes
["from"] = config
.jid
415 iq
.attributes
["to"] = to
417 iq
.attributes
["id"] = ID
418 query
= iq
.addElement("query")
419 query
.attributes
["xmlns"] = disco
.IQGATEWAY
420 prompt
= query
.addElement("prompt")
421 prompt
.addContent(legacy
.translateAccount(legacyaccount
))
423 self
.pytrans
.send(iq
)
426 self
.pytrans
.discovery
.sendIqError(to
, ID
, disco
.IQGATEWAY
)
427 self
.pytrans
.discovery
.sendIqError(to
=to
, fro
=config
.jid
, ID
=ID
, xmlns
=disco
.IQGATEWAY
, etype
="retry", condition
="bad-request")
432 def __init__(self
, pytrans
):
433 self
.pytrans
= pytrans
434 self
.pytrans
.discovery
.addFeature(disco
.IQVERSION
, self
.incomingIq
, config
.jid
)
435 self
.pytrans
.discovery
.addFeature(disco
.IQVERSION
, self
.incomingIq
, "USER")
437 self
.version
= "%s - SVN r%s" % (legacy
.version
, svninfo
.getSVNVersion())
439 self
.version
= legacy
.version
440 self
.os
= "Python" + ".".join([str(x
) for x
in sys
.version_info
[0:3]]) + " - " + sys
.platform
442 def incomingIq(self
, el
):
443 eltype
= el
.getAttribute("type")
444 if(eltype
!= "get"): return # Only answer "get" stanzas
448 def sendVersion(self
, el
):
450 iq
= Element((None, "iq"))
451 iq
.attributes
["type"] = "result"
452 iq
.attributes
["from"] = el
.getAttribute("to")
453 iq
.attributes
["to"] = el
.getAttribute("from")
454 if(el
.getAttribute("id")):
455 iq
.attributes
["id"] = el
.getAttribute("id")
456 query
= iq
.addElement("query")
457 query
.attributes
["xmlns"] = disco
.IQVERSION
458 name
= query
.addElement("name")
459 name
.addContent(config
.discoName
)
460 version
= query
.addElement("version")
461 version
.addContent(self
.version
)
462 os
= query
.addElement("os")
463 os
.addContent(self
.os
)
465 self
.pytrans
.send(iq
)
468 class FileTransferOOBSend
:
469 def __init__(self
, pytrans
):
470 self
.pytrans
= pytrans
471 self
.pytrans
.discovery
.addFeature(disco
.IQOOB
, self
.incomingOOB
, "USER")
473 def incomingOOB(self
, el
):
474 ID
= el
.getAttribute("id")
476 self
.pytrans
.discovery
.sendIqError(to
=el
.getAttribute("from"), fro
=el
.getAttribute("to"), ID
=ID
, xmlns
=disco
.IQOOB
, etype
="cancel", condition
="feature-not-implemented")
478 if el
.attributes
["type"] != "set":
480 for child
in el
.elements():
481 if child
.name
== "query":
486 for child
in query
.elements():
487 if child
.name
== "url":
488 url
= child
.__str
__()
493 froj
= internJID(el
.getAttribute("from"))
494 toj
= internJID(el
.getAttribute("to"))
495 session
= self
.pytrans
.sessions
.get(froj
.userhost(), None)
499 res
= utils
.getURLBits(url
, "http")
502 host
, port
, path
, filename
= res
506 iq
= Element((None, "iq"))
507 iq
.attributes
["to"] = froj
.full()
508 iq
.attributes
["from"] = toj
.full()
509 iq
.attributes
["type"] = "result"
511 iq
.attributes
["id"] = ID
512 iq
.addElement("query").attributes
["xmlns"] = "jabber:iq:oob"
513 self
.pytrans
.send(iq
)
515 def startTransfer(consumer
):
516 factory
= protocol
.ClientFactory()
517 factory
.protocol
= ft
.OOBSendConnector
521 factory
.consumer
= consumer
522 factory
.finished
= sendResult
523 reactor
.connectTCP(host
, port
, factory
)
525 def doSendFile(length
):
526 ft
.FTSend(session
, toj
.userhost(), startTransfer
, errOut
, filename
, length
)
528 # Make a HEAD request to grab the length of data first
529 factory
= protocol
.ClientFactory()
530 factory
.protocol
= ft
.OOBHeaderHelper
534 factory
.gotLength
= doSendFile
535 reactor
.connectTCP(host
, port
, factory
)
539 class Socks5FileTransfer
:
540 def __init__(self
, pytrans
):
541 self
.pytrans
= pytrans
542 self
.pytrans
.discovery
.addFeature(disco
.SI
, self
.incomingSI
, "USER")
543 self
.pytrans
.discovery
.addFeature(disco
.FT
, lambda: None, "USER")
544 self
.pytrans
.discovery
.addFeature(disco
.S5B
, self
.incomingS5B
, "USER")
547 def incomingSI(self
, el
):
548 ID
= el
.getAttribute("id")
550 self
.pytrans
.discovery
.sendIqError(to
=el
.getAttribute("from"), fro
=el
.getAttribute("to"), ID
=ID
, xmlns
=disco
.SI
, etype
="cancel", condition
="bad-request")
552 toj
= internJID(el
.getAttribute("to"))
553 froj
= internJID(el
.getAttribute("from"))
554 session
= self
.pytrans
.sessions
.get(froj
.userhost(), None)
559 if not (si
and si
.getAttribute("profile") == disco
.FT
):
562 if not (file and file.uri
== disco
.FT
):
566 filename
= file["name"]
567 filesize
= int(file["size"])
573 # Check that we can use socks5 bytestreams
575 if not (feature
and feature
.uri
== disco
.FEATURE_NEG
):
578 if not (x
and x
.uri
== disco
.XDATA
):
581 if not (field
and field
.getAttribute("var") == "stream-method"):
583 for option
in field
.elements():
587 value
= value
.__str
__()
588 if value
== disco
.S5B
:
591 return errOut() # Socks5 bytestreams not supported :(
594 def startTransfer(consumer
):
595 iq
= Element((None, "iq"))
596 iq
["type"] = "result"
597 iq
["to"] = froj
.full()
598 iq
["from"] = toj
.full()
600 si
= iq
.addElement("si")
601 si
["xmlns"] = disco
.SI
602 feature
= si
.addElement("feature")
603 feature
["xmlns"] = disco
.FEATURE_NEG
604 x
= feature
.addElement("x")
605 x
["xmlns"] = disco
.XDATA
607 field
= x
.addElement("field")
608 field
["var"] = "stream-method"
609 value
= field
.addElement("value")
610 value
.addContent(disco
.S5B
)
611 self
.pytrans
.send(iq
)
612 self
.sessions
[(froj
.full(), sid
)] = consumer
614 ft
.FTSend(session
, toj
.userhost(), startTransfer
, errOut
, filename
, filesize
)
616 def incomingS5B(self
, el
):
617 ID
= el
.getAttribute("id")
619 self
.pytrans
.discovery
.sendIqError(to
=el
.getAttribute("from"), fro
=el
.getAttribute("to"), ID
=ID
, xmlns
=disco
.S5B
, etype
="cancel", condition
="item-not-found")
621 if el
.getAttribute("type") != "set":
624 toj
= internJID(el
.getAttribute("to"))
625 froj
= internJID(el
.getAttribute("from"))
628 if not (query
and query
.getAttribute("mode", "tcp") == "tcp"):
630 sid
= query
.getAttribute("sid")
631 consumer
= self
.sessions
.pop((froj
.full(), sid
), None)
635 for streamhost
in query
.elements():
636 if streamhost
.name
== "streamhost":
638 JID
= streamhost
["jid"]
639 host
= streamhost
["host"]
640 port
= int(streamhost
["port"])
645 streamhosts
.append((JID
, host
, port
))
648 def gotStreamhost(host
):
649 for streamhost
in streamhosts
:
650 if streamhost
[1] == host
:
657 for connector
in factory
.connectors
:
658 # Stop any other connections
660 connector
.stopConnecting()
661 except error
.NotConnectingError
:
664 if factory
.streamHostTimeout
:
665 factory
.streamHostTimeout
.cancel()
666 factory
.streamHostTimeout
= None
668 iq
= Element((None, "iq"))
669 iq
["type"] = "result"
670 iq
["from"] = toj
.full()
671 iq
["to"] = froj
.full()
673 query
= iq
.addElement("query")
674 query
["xmlns"] = disco
.S5B
675 streamhost
= query
.addElement("streamhost-used")
676 streamhost
["jid"] = jid
677 self
.pytrans
.send(iq
)
680 # Try the streamhosts
682 factory
= protocol
.ClientFactory()
683 factory
.protocol
= ft
.JEP65ConnectionSend
684 factory
.consumer
= consumer
685 factory
.hash = utils
.socks5Hash(sid
, froj
.full(), toj
.full())
686 factory
.madeConnection
= gotStreamhost
687 factory
.connectors
= []
688 factory
.streamHostTimeout
= reactor
.callLater(120, consumer
.error
)
690 for streamhost
in streamhosts
:
691 factory
.connectors
.append(reactor
.connectTCP(streamhost
[1], streamhost
[2], factory
))