webXMPP

Check-in [bbb9ef0e3a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Tighten up display of nicknames, integrate in destination selection
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256: bbb9ef0e3ac04bd8fa802e1fe43771fa3e550da604ce35cb4fb24921fd9aa520
User & Date: web 2020-05-06 01:11:50
Context
2020-05-18
23:04
Fiddle with some edge cases of trying to do XMPP before the connection is considered up. TBD, reconnect if it goes down. check-in: 1def2b208b user: web tags: master, trunk
2020-05-06
01:11
Tighten up display of nicknames, integrate in destination selection check-in: bbb9ef0e3a user: web tags: master, trunk
2020-05-04
17:35
Add in any XMMP nickname, to make it easier to see who the sender is check-in: 973917fdd9 user: web tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to acct_xmpp.py.

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
...
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
#  together for purposes of viewing.
#
# user - Back-reference to User instance we're serving
# acct/pw - Account name and its password
# conn - XMPP Client instance
# stopping - Bool flag to tell us to wrap up our service loop
# status - String, latest "show" status from peer
# nicks - Map from an ID to any known nickname
class Account(object):
    def __init__(self, user, acct, pw):
	self.user = user
	self.acct = acct
	self.pw = pw
	self.conn = None
	self.stopping = False
	self.nicks = {}

    # Send a message
    # (User exclusion is held.)
    def send(self, towhom, msg):
	self.conn.send( xmpp.Message(towhom, msg) )

    # Callback from XMPP stack when we get a message
................................................................................
	body  = toascii(m)
	sys.stderr.write("Message received from %s for %s\n" %
	    (sender, self.acct))
	sys.stderr.write("  Body: %s\n" % (body,))

	# Clean up sender, note nickname if any
	snd = aname(sender)
	if snd in self.nicks:
	    body = ("(%s) " % (self.nicks[snd],)) + body

	# Add as a new message
	u = self.user
	u.add(True, snd, toascii(body), None, None)

	# Learn into roster?
	if sender not in u.roster:
................................................................................
	conn.sendInitPresence()
	conn.RegisterHandler('message', self.message)
	conn.RegisterHandler('presence', self.presence)

	# Fold in roster
	r = conn.getRoster()
	buds = set()

	for bud in r.getItems():
	    budstr = toascii(bud)
	    if '@' not in budstr:
		# Ignore aliases
		continue
	    if r[bud].get("subscription"):
		buds.add(budstr)

		# Learn any nickname
		n = r.getName(bud)
		if n and (n != bud):
		    sys.stderr.write("Nickname %s -> %s\n" % (bud, n))
		    self.nicks[bud] = n
	self.buddies = frozenset(buds)
	user = self.user
	for bud in buds:
	    user.roster[bud] = self
	del buds

	# And dispatch incoming messages







<







<







 







<
<







 







>












|







17
18
19
20
21
22
23

24
25
26
27
28
29
30

31
32
33
34
35
36
37
..
45
46
47
48
49
50
51


52
53
54
55
56
57
58
...
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
#  together for purposes of viewing.
#
# user - Back-reference to User instance we're serving
# acct/pw - Account name and its password
# conn - XMPP Client instance
# stopping - Bool flag to tell us to wrap up our service loop
# status - String, latest "show" status from peer

class Account(object):
    def __init__(self, user, acct, pw):
	self.user = user
	self.acct = acct
	self.pw = pw
	self.conn = None
	self.stopping = False


    # Send a message
    # (User exclusion is held.)
    def send(self, towhom, msg):
	self.conn.send( xmpp.Message(towhom, msg) )

    # Callback from XMPP stack when we get a message
................................................................................
	body  = toascii(m)
	sys.stderr.write("Message received from %s for %s\n" %
	    (sender, self.acct))
	sys.stderr.write("  Body: %s\n" % (body,))

	# Clean up sender, note nickname if any
	snd = aname(sender)



	# Add as a new message
	u = self.user
	u.add(True, snd, toascii(body), None, None)

	# Learn into roster?
	if sender not in u.roster:
................................................................................
	conn.sendInitPresence()
	conn.RegisterHandler('message', self.message)
	conn.RegisterHandler('presence', self.presence)

	# Fold in roster
	r = conn.getRoster()
	buds = set()
	nicks = self.user.approot.nicks
	for bud in r.getItems():
	    budstr = toascii(bud)
	    if '@' not in budstr:
		# Ignore aliases
		continue
	    if r[bud].get("subscription"):
		buds.add(budstr)

		# Learn any nickname
		n = r.getName(bud)
		if n and (n != bud):
		    sys.stderr.write("Nickname %s -> %s\n" % (bud, n))
		    nicks[bud] = n
	self.buddies = frozenset(buds)
	user = self.user
	for bud in buds:
	    user.roster[bud] = self
	del buds

	# And dispatch incoming messages

Changes to get.py.

16
17
18
19
20
21
22


23

24
25
26
27
28
29
30
31
...
119
120
121
122
123
124
125
126
127

128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
143
144
145
146
147
148
149

150
151
152
153
154
155
156
157
#	server.
import sys, os, threading
import chore

# A little static function to order accounts based on status
# Each argument is a (acct-name, acct-status) tuple
def _order_stat(a, b):


    aname,astat = a

    bname,bstat = b

    # WTF?
    if aname == bname:
	return 0

    # If tie on status, order by name
    if astat == bstat:
................................................................................
	fn = "var/" + phnum + "/" + fn

	# Try and send the media file
	return True,self.send_files(fn)

    # Send what we have, based on generation @gen
    def send_current(self, gen, rgen):
	webxmpp = self.server.approot
	user = webxmpp.users[self.user]

	msgs = []
	for tup in user.msgs:
	    if tup[0] >= gen:
		d = {}
		d["rx"] = tup[1]
		# TBD, more handling of nicknames
		d["them"] = tup[2]
		d["body"] = tup[3]
		d["mtype"] = tup[4]
		d["fname"] = tup[5]
		msgs.append(d)

	# Here's the messages we have right now
	resp = { "serial": user.serial,
................................................................................
	    "msgs": msgs }
	user.serial += 1

	# Add a roster update?
	if rgen != user.rgen:
	    uroster = []
	    for name in user.roster:

		uroster.append( (name, user.status.get(name, "offline")) )
	    uroster.sort(cmp = _order_stat)
	    resp["roster"] = uroster
	    sys.stderr.write("%s: new roster\n" % (user.name,))
	else:
	    resp["roster"] = tuple()

	# Send off our json encoding of the response







>
>
|
>
|







 







|
|
>






|







 







>
|







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
...
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#	server.
import sys, os, threading
import chore

# A little static function to order accounts based on status
# Each argument is a (acct-name, acct-status) tuple
def _order_stat(a, b):
    # Order by name, status
    aname = a[0]
    astat = a[1]
    bname = b[0]
    bstat = b[1]

    # WTF?
    if aname == bname:
	return 0

    # If tie on status, order by name
    if astat == bstat:
................................................................................
	fn = "var/" + phnum + "/" + fn

	# Try and send the media file
	return True,self.send_files(fn)

    # Send what we have, based on generation @gen
    def send_current(self, gen, rgen):
	approot = self.server.approot
	user = approot.users[self.user]
	nicks = approot.nicks
	msgs = []
	for tup in user.msgs:
	    if tup[0] >= gen:
		d = {}
		d["rx"] = tup[1]
		# TBD, more handling of nicknames
		them = d["them"] = tup[2]
		d["body"] = tup[3]
		d["mtype"] = tup[4]
		d["fname"] = tup[5]
		msgs.append(d)

	# Here's the messages we have right now
	resp = { "serial": user.serial,
................................................................................
	    "msgs": msgs }
	user.serial += 1

	# Add a roster update?
	if rgen != user.rgen:
	    uroster = []
	    for name in user.roster:
		uroster.append(
		 (name, user.status.get(name, "offline"), nicks.get(name)) )
	    uroster.sort(cmp = _order_stat)
	    resp["roster"] = uroster
	    sys.stderr.write("%s: new roster\n" % (user.name,))
	else:
	    resp["roster"] = tuple()

	# Send off our json encoding of the response

Changes to js/ui.js.

85
86
87
88
89
90
91



92
93
94
95
96
97
98
...
215
216
217
218
219
220
221


222





223
224
225
226
227
228
229
230
...
350
351
352
353
354
355
356
357
358

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375









376
377
378
379
380
381
382
383
//  won't do if we can't post notifications anyway.
let notifiable = false;

// Matching URL's in texts
const URLpat =
/(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi ;




// Returning online (network change, cell->wifi, etc.)
function goOnline() {
    if (curReq != null) {
	curReq.abort();
	curReq = null;
    }

................................................................................
    for (let i = 0; i < ourMsgs.length; ++i) {
	const m = ourMsgs[i];

	// State who it is if it isn't the same as the
	//  line above.
	// Bold'ed so it's easy to spot
	if (m.them != curThem) {


	    curThem = m.them;





	    const n = document.createTextNode(m.them);
	    const d = document.createElement("div");
	    d.appendChild(n);
	    d.style.fontWeight = "bold";
	    ourTexts.appendChild(d);
	}

	// Message direction
................................................................................
	// Build new
	// First one says "whoever last sent to us", the rest
	//  enumerate buddies in our rosters.
	ourDests[0] = new Option("--", "--", true, false);
	for (let i = 0; i < resp.roster.length; ++i) {
	    // Each tuple is (acct-name, acct-status)
	    const tup = resp.roster[i];
	    const val = tup[0]
	    const st = tup[1]


	    // Label +name for available, -name for away,
	    //  and .name for extended away.  While not an XMPP
	    //  status, "offline" is a leading blank for a roster
	    //  member who is not online.
	    let nm = null;
	    if ((st == "available") || (st == "chat")) {
		nm = "+" + val;
	    } else if (st == "away") {
		nm = "-" + val;
	    } else if (st == "xa") {
		nm = "." + val;
	    } else if (st == "dnd") {
		nm = "*" + val;
	    } else {
		nm = " " + val;
	    }









	    ourDests[i+1] = new Option(nm, val, false, false);
	}
    }

    // No messages
    if (resp.msgs.length == 0) {
	moreMessages();
	return;







>
>
>







 







>
>
|
>
>
>
>
>
|







 







|

>







|

|

|

|

|

>
>
>
>
>
>
>
>
>
|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
...
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
...
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//  won't do if we can't post notifications anyway.
let notifiable = false;

// Matching URL's in texts
const URLpat =
/(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi ;

// Mapping for nicknames
const name_nick = new Map(), nick_name = new Map();

// Returning online (network change, cell->wifi, etc.)
function goOnline() {
    if (curReq != null) {
	curReq.abort();
	curReq = null;
    }

................................................................................
    for (let i = 0; i < ourMsgs.length; ++i) {
	const m = ourMsgs[i];

	// State who it is if it isn't the same as the
	//  line above.
	// Bold'ed so it's easy to spot
	if (m.them != curThem) {
	    const nm = m.them;
	    let lbl = nm;
	    curThem = nm;

	    // Note nickname in the header, if known
	    if (name_nick.has(nm)) {
		lbl = lbl + " (" + name_nick.get(nm) + ")";
	    }
	    const n = document.createTextNode(lbl);
	    const d = document.createElement("div");
	    d.appendChild(n);
	    d.style.fontWeight = "bold";
	    ourTexts.appendChild(d);
	}

	// Message direction
................................................................................
	// Build new
	// First one says "whoever last sent to us", the rest
	//  enumerate buddies in our rosters.
	ourDests[0] = new Option("--", "--", true, false);
	for (let i = 0; i < resp.roster.length; ++i) {
	    // Each tuple is (acct-name, acct-status)
	    const tup = resp.roster[i];
	    const who = tup[0]
	    const st = tup[1]
	    const nick = tup[2]

	    // Label +name for available, -name for away,
	    //  and .name for extended away.  While not an XMPP
	    //  status, "offline" is a leading blank for a roster
	    //  member who is not online.
	    let nm = null;
	    if ((st == "available") || (st == "chat")) {
		nm = "+ " + who;
	    } else if (st == "away") {
		nm = "- " + who;
	    } else if (st == "xa") {
		nm = ". " + who;
	    } else if (st == "dnd") {
		nm = "* " + who;
	    } else {
		nm = "  " + who;
	    }

	    // Add on nickname, if any
	    if (nick) {
		nm = nm + " (" + nick + ")";
		name_nick.set(who, nick);
		nick_name.set(nick, who);
	    }

	    // Another dest we could pick
	    ourDests[i+1] = new Option(nm, who, false, false);
	}
    }

    // No messages
    if (resp.msgs.length == 0) {
	moreMessages();
	return;

Changes to main.py.

148
149
150
151
152
153
154



155
156
157
158
159
160
161
162
163
164

165
166
167
168
169
170
171
	chore.server.Server.__init__(self, cfg, App_Handler)

	# Hook for Google's stuff
	FCM_mixin.__init__(self)

	# Users who have been loaded
	self.users = {}




	# Our timeout service thread
	self.timer = None

	# Callbacks when time expires
	# List of tuples; (expires-tm, call-fn, call-arg)
	self.timeouts = []

	# Possibly restrict REST SMS submission by IP
	# SMS source filters, if any

	self.smsok = []

	# List of good/bad phone #'s (SMS spam) and file which we
	#  refresh from on demand
	self.smslnums = []
	self.smsisblack = None
	self.smslfn = None







>
>
>








<

>







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
	chore.server.Server.__init__(self, cfg, App_Handler)

	# Hook for Google's stuff
	FCM_mixin.__init__(self)

	# Users who have been loaded
	self.users = {}

	# Any nicknames
	self.nicks = {}

	# Our timeout service thread
	self.timer = None

	# Callbacks when time expires
	# List of tuples; (expires-tm, call-fn, call-arg)
	self.timeouts = []


	# SMS source filters, if any
	# (Possibly restrict REST SMS submission by IP)
	self.smsok = []

	# List of good/bad phone #'s (SMS spam) and file which we
	#  refresh from on demand
	self.smslnums = []
	self.smsisblack = None
	self.smslfn = None

Changes to user.py.

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
...
139
140
141
142
143
144
145
146
from chore.utils import Exclusion
import acct_xmpp, acct_sms

# One of these is spun up when a user first authenticates.  It caches
#  the user/pw, and fires up threads to watch XMPP for each configured
#  server.
#
# top - Top-level server state
# name - HTTP authentication username
# accounts[] - List of configured accounts,
#	each a (account-name, password) tuple
# activity - A time value, updated each time we hear (HTTP) from the user
# timeout - The running timer waiting for the HTTP user to be idle
#	long enough to have us close down our XMPP.
# exclusion - Mutex so only one user HTTP session at a time comes
................................................................................
#	 presence messages before the user shows up in the roster)
# serial - Running counter, so clients can detect dup (from cache)
#	completions.
# pushkeys[] - Set of active Google messaging endpoints
# host - Record host at which they reach us
#
class User(object):
    def __init__(self, top, user, accounts):
	self.top = top
	self.name = user
	self.accounts = accounts
	self.activity = None
	self.timeout = None
	self.exclusion = Exclusion()
	self.active = {}
	self.roster = {}
................................................................................
    def await(self, timeout, sema, clientID, handler):
	self.pending.add( (time.time() + timeout, sema, clientID, handler) )

    # Add a new message
    # It's received (i.e., *to* us) if @rx, involving @them
    #  with body of @mBody
    def add(self, rx, them, mBody, mt, fname):
	mmax = self.top.config["nmsg"]
	msgs = self.msgs
	while len(msgs) >= mmax:
	    del msgs[0]
	msgs.append( (self.gen, rx, them, mBody, mt, fname) )
	self.gen += 1

	# Pending Ajax requests wake up now, clearing
................................................................................

	# Pending Google Chrome browsers
	if self.pushkeys:
	    # Grab current list and reset
	    keys = self.pushkeys
	    self.pushkeys = set()
	    for k in keys:
		self.top.fcm(self.host, k, them, mBody)







|







 







|
|







 







|







 







|
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
...
139
140
141
142
143
144
145
146
from chore.utils import Exclusion
import acct_xmpp, acct_sms

# One of these is spun up when a user first authenticates.  It caches
#  the user/pw, and fires up threads to watch XMPP for each configured
#  server.
#
# approot - Top-level server state
# name - HTTP authentication username
# accounts[] - List of configured accounts,
#	each a (account-name, password) tuple
# activity - A time value, updated each time we hear (HTTP) from the user
# timeout - The running timer waiting for the HTTP user to be idle
#	long enough to have us close down our XMPP.
# exclusion - Mutex so only one user HTTP session at a time comes
................................................................................
#	 presence messages before the user shows up in the roster)
# serial - Running counter, so clients can detect dup (from cache)
#	completions.
# pushkeys[] - Set of active Google messaging endpoints
# host - Record host at which they reach us
#
class User(object):
    def __init__(self, approot, user, accounts):
	self.approot = approot
	self.name = user
	self.accounts = accounts
	self.activity = None
	self.timeout = None
	self.exclusion = Exclusion()
	self.active = {}
	self.roster = {}
................................................................................
    def await(self, timeout, sema, clientID, handler):
	self.pending.add( (time.time() + timeout, sema, clientID, handler) )

    # Add a new message
    # It's received (i.e., *to* us) if @rx, involving @them
    #  with body of @mBody
    def add(self, rx, them, mBody, mt, fname):
	mmax = self.approot.config["nmsg"]
	msgs = self.msgs
	while len(msgs) >= mmax:
	    del msgs[0]
	msgs.append( (self.gen, rx, them, mBody, mt, fname) )
	self.gen += 1

	# Pending Ajax requests wake up now, clearing
................................................................................

	# Pending Google Chrome browsers
	if self.pushkeys:
	    # Grab current list and reset
	    keys = self.pushkeys
	    self.pushkeys = set()
	    for k in keys:
		self.approot.fcm(self.host, k, them, mBody)