Skip to content

Commit 320f11f

Browse files
committed
Use XMPP to search for MUCs via search.jabber.network
Also refactor AutoComplete somewhat to not compute `this._list` too eagerly and to also pass the query string to `this._list`.
1 parent 4237e5b commit 320f11f

File tree

7 files changed

+91
-33
lines changed

7 files changed

+91
-33
lines changed

src/plugins/muc-views/search.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import log from "@converse/headless/log";
2+
import { _converse, api, converse } from "@converse/headless/core";
3+
4+
const { Strophe, $iq, sizzle } = converse.env;
5+
6+
Strophe.addNamespace('MUCSEARCH', 'https://xmlns.zombofant.net/muclumbus/search/1.0');
7+
8+
const rooms_cache = {};
9+
10+
async function searchRooms (query) {
11+
let iq = $iq({
12+
'type': 'get',
13+
'from': _converse.bare_jid,
14+
15+
}).c('search', { 'xmlns': Strophe.NS.MUCSEARCH })
16+
17+
try {
18+
await api.sendIQ(iq);
19+
} catch (e) {
20+
log.error(e);
21+
return [];
22+
}
23+
24+
iq = $iq({
25+
'type': 'get',
26+
'from': _converse.bare_jid,
27+
28+
}).c('search', { 'xmlns': Strophe.NS.MUCSEARCH })
29+
.c('set', { 'xmlns': Strophe.NS.RSM })
30+
.c('max').t(10).up().up()
31+
.c('x', { 'xmlns': Strophe.NS.XFORM, 'type': 'submit' })
32+
.c('field', { 'var': 'FORM_TYPE', 'type': 'hidden' })
33+
.c('value').t('https://xmlns.zombofant.net/muclumbus/search/1.0#params').up().up()
34+
.c('field', { 'var': 'q', 'type': 'text-single' })
35+
.c('value').t(query).up().up()
36+
.c('field', { 'var': 'sinname', 'type': 'boolean' })
37+
.c('value').t('true').up().up()
38+
.c('field', { 'var': 'sindescription', 'type': 'boolean' })
39+
.c('value').t('false').up().up()
40+
.c('field', { 'var': 'sinaddr', 'type': 'boolean' })
41+
.c('value').t('true').up().up()
42+
.c('field', { 'var': 'min_users', 'type': 'text-single' })
43+
.c('value').t('1').up().up()
44+
.c('field', { 'var': 'key', 'type': 'list-single' })
45+
.c('value').t('address').up()
46+
.c('option').c('value').t('nusers').up().up()
47+
.c('option').c('value').t('address')
48+
49+
let iq_result;
50+
try {
51+
iq_result = await api.sendIQ(iq);
52+
} catch (e) {
53+
log.error(e);
54+
return [];
55+
}
56+
const s = `result[xmlns="${Strophe.NS.MUCSEARCH}"] item`;
57+
return sizzle(s, iq_result).map(i => `${i.querySelector('name')?.textContent} (${i.getAttribute('address')})`);
58+
}
59+
60+
export function getAutoCompleteList (query) {
61+
if (!rooms_cache[query]) {
62+
rooms_cache[query] = searchRooms(query);
63+
}
64+
return rooms_cache[query];
65+
}
66+

src/plugins/muc-views/templates/ad-hoc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export default (o) => {
2222
<converse-autocomplete
2323
.getAutoCompleteList="${getAutoCompleteList}"
2424
placeholder="${i18n_jid_placeholder}"
25-
name="jid"></converse-autocomplete>
25+
name="jid">
26+
</converse-autocomplete>
2627
</label>
2728
</fieldset>
2829
<fieldset class="form-group">

src/plugins/muc-views/templates/add-muc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { api } from '@converse/headless/core.js';
44
import { html } from "lit";
55
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
66
import { unsafeHTML } from "lit/directives/unsafe-html.js";
7-
import { getAutoCompleteList } from "../utils.js";
7+
import { getAutoCompleteList } from "../search.js";
88

99

1010
const nickname_input = (o) => {
@@ -38,6 +38,7 @@ export default (o) => {
3838
<converse-autocomplete
3939
.getAutoCompleteList="${getAutoCompleteList}"
4040
?autofocus=${true}
41+
min_chars="3"
4142
position="below"
4243
placeholder="${o.chatroom_placeholder}"
4344
class="add-muc-autocomplete"

src/plugins/muc-views/tests/autocomplete.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ describe("The nickname autocomplete feature", function () {
258258
'preventDefault': function preventDefault () {},
259259
'keyCode': 8
260260
}
261-
for (var i=0; i<3; i++) {
261+
for (let i=0; i<3; i++) {
262262
// Press backspace 3 times to remove "som"
263263
message_form.onKeyDown(backspace_event);
264264
textarea.value = textarea.value.slice(0, textarea.value.length-1)

src/plugins/muc-views/utils.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,22 +127,10 @@ export function getAutoCompleteListItem (text, input) {
127127
return element;
128128
}
129129

130-
let fetched_room_jids = [];
131-
let timestamp = null;
132-
133-
async function fetchListOfRooms () {
134-
const response = await fetch('https://search.jabber.network/api/1.0/rooms');
135-
const data = await response.json();
136-
const popular_mucs = data.items.map(item => item.address);
137-
fetched_room_jids = [...new Set(popular_mucs)];
138-
}
139-
140130
export async function getAutoCompleteList () {
141-
if (!timestamp || converse.env.dayjs().isAfter(timestamp, 'day')) {
142-
await fetchListOfRooms();
143-
timestamp = (new Date()).toISOString();
144-
}
145-
return fetched_room_jids;
131+
const models = [...(await api.rooms.get()), ...(await api.contacts.get())];
132+
const jids = [...new Set(models.map(o => Strophe.getDomainFromJid(o.get('jid'))))];
133+
return jids;
146134
}
147135

148136
export async function fetchCommandForm (command) {

src/shared/autocomplete/autocomplete.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class AutoComplete {
6464
"blur": () => this.close({'reason': 'blur'})
6565
}
6666
if (this.auto_evaluate) {
67-
input["input"] = () => this.evaluate();
67+
input["input"] = (e) => this.evaluate(e);
6868
}
6969

7070
this._events = {
@@ -265,25 +265,27 @@ export class AutoComplete {
265265
return;
266266
}
267267

268-
const list = typeof this._list === "function" ? await this._list() : this._list;
269-
if (list.length === 0) {
270-
return;
271-
}
272-
273268
let value = this.match_current_word ? u.getCurrentWord(this.input) : this.input.value;
269+
274270
const contains_trigger = helpers.isMention(value, this.ac_triggers);
275-
if (contains_trigger) {
271+
if (contains_trigger && !this.include_triggers.includes(ev.key)) {
272+
value = u.isMentionBoundary(value[0])
273+
? value.slice('2')
274+
: value.slice('1');
275+
}
276+
277+
const is_long_enough = value.length && value.length >= this.min_chars;
278+
279+
if (contains_trigger || is_long_enough) {
276280
this.auto_completing = true;
277-
if (!this.include_triggers.includes(ev.key)) {
278-
value = u.isMentionBoundary(value[0])
279-
? value.slice('2')
280-
: value.slice('1');
281+
282+
const list = typeof this._list === "function" ? await this._list(value) : this._list;
283+
if (list.length === 0 || !this.auto_completing) {
284+
this.close({'reason': 'nomatches'});
285+
return;
281286
}
282-
}
283287

284-
if ((contains_trigger || value.length) && value.length >= this.min_chars) {
285288
this.index = -1;
286-
// Populate list with options that match
287289
this.ul.innerHTML = "";
288290

289291
this.suggestions = list

src/shared/autocomplete/component.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export default class AutoCompleteComponent extends CustomElement {
100100
'auto_first': this.auto_first,
101101
'filter': this.filter == 'contains' ? FILTER_CONTAINS : FILTER_STARTSWITH,
102102
'include_triggers': [],
103-
'list': () => this.getAutoCompleteList(),
103+
'list': (q) => this.getAutoCompleteList(q),
104104
'match_current_word': true,
105105
'max_items': this.max_items,
106106
'min_chars': this.min_chars,

0 commit comments

Comments
 (0)