import "../plugins/autocomplete.js" import log from "@converse/headless/log"; import sizzle from "sizzle"; import { CustomElement } from './element.js'; import { __ } from '../i18n'; import { api, converse } from "@converse/headless/core"; import { html } from "lit-html"; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; const { Strophe, $iq } = converse.env; const u = converse.env.utils; const tpl_command_form = (o, command) => { const i18n_hide = __('Hide'); const i18n_run = __('Execute'); return html`
${ command.alert ? html`` : '' }

${command.instructions}

${ command.fields.map(field => unsafeHTML(field)) }
`; } const tpl_command = (o, command) => html`
  • ${command.name || command.jid}
    ${ command.node === o.showform ? tpl_command_form(o, command) : '' }
  • `; async function getAutoCompleteList () { const models = [...(await api.rooms.get()), ...(await api.contacts.get())]; const jids = [...new Set(models.map(o => Strophe.getDomainFromJid(o.get('jid'))))]; return jids; } const tpl_adhoc = (o) => { const i18n_choose_service = __('On which entity do you want to run commands?'); const i18n_choose_service_instructions = __( 'Certain XMPP services and entities allow privileged users to execute ad-hoc commands on them.'); const i18n_commands_found = __('Commands found'); const i18n_fetch_commands = __('List available commands'); const i18n_jid_placeholder = __('XMPP Address'); const i18n_no_commands_found = __('No commands found'); return html` ${ o.alert ? html`` : '' }
    ${ o.view === 'list-commands' ? html`
    ` : '' }
    `; } async function fetchCommandForm (command) { const node = command.node; const jid = command.jid; const stanza = $iq({ 'type': 'set', 'to': jid }).c('command', { 'xmlns': Strophe.NS.ADHOC, 'node': node, 'action': 'execute' }); try { const iq = await api.sendIQ(stanza); const cmd_el = sizzle(`command[xmlns="${Strophe.NS.ADHOC}"]`, iq).pop(); command.sessionid = cmd_el.getAttribute('sessionid'); command.instructions = sizzle('x[type="form"][xmlns="jabber:x:data"] instructions', cmd_el).pop()?.textContent; command.fields = sizzle('x[type="form"][xmlns="jabber:x:data"] field', cmd_el) .map(f => u.xForm2webForm(f, cmd_el)); } catch (e) { if (e === null) { log.error(`Error: timeout while trying to execute command for ${jid}`); } else { log.error(`Error while trying to execute command for ${jid}`); log.error(e); } command.fields = []; } } export default class AdHocCommands extends CustomElement { static get properties () { return { 'alert': { type: String }, 'alert_type': { type: String }, 'nonce': { type: String }, // Used to force re-rendering 'showform': { type: String }, 'view': { type: String }, } } constructor () { super(); this.view = 'choose-service'; this.showform = ''; this.commands = []; } render () { return tpl_adhoc({ 'alert': this.alert, 'alert_type': this.alert_type, 'commands': this.commands, 'fetchCommands': ev => this.fetchCommands(ev), 'hideCommandForm': ev => this.hideCommandForm(ev), 'runCommand': ev => this.runCommand(ev), 'showform': this.showform, 'toggleCommandForm': ev => this.toggleCommandForm(ev), 'view': this.view, }); } async fetchCommands (ev) { ev.preventDefault(); delete this.alert_type; delete this.alert; const form_data = new FormData(ev.target); const jid = form_data.get('jid').trim(); let supported; try { supported = await api.disco.supports(Strophe.NS.ADHOC, jid) } catch (e) { log.error(e); } if (supported) { try { this.commands = await api.adhoc.getCommands(jid); this.view = 'list-commands'; } catch (e) { log.error(e); this.alert_type = 'danger'; this.alert = __('Sorry, an error occurred while looking for commands on that entity.'); this.commands = []; log.error(e); return; } } else { this.alert_type = 'danger'; this.alert = __("The specified entity doesn't support ad-hoc commands"); } } async toggleCommandForm (ev) { ev.preventDefault(); const node = ev.target.getAttribute('data-command-node'); const cmd = this.commands.filter(c => c.node === node)[0]; this.showform !== node && await fetchCommandForm(cmd); this.showform = node; } hideCommandForm (ev) { ev.preventDefault(); this.showform = '' } async runCommand (ev) { ev.preventDefault(); const form_data = new FormData(ev.target); const jid = form_data.get('command_jid').trim(); const node = form_data.get('command_node').trim(); const cmd = this.commands.filter(c => c.node === node)[0]; const inputs = sizzle(':input:not([type=button]):not([type=submit])', ev.target); const config_array = inputs .filter(i => !['command_jid', 'command_node'].includes(i.getAttribute('name'))) .map(u.webForm2xForm) .filter(n => n); const iq = $iq({to: jid, type: "set"}) .c("command", { 'sessionid': cmd.sessionid, 'node': cmd.node, 'xmlns': Strophe.NS.ADHOC }).c("x", {xmlns: Strophe.NS.XFORM, type: "submit"}); config_array.forEach(node => iq.cnode(node).up()); let result; try { result = await api.sendIQ(iq); } catch (e) { cmd.alert_type = 'danger'; cmd.alert = __('Sorry, an error occurred while trying to execute the command. See the developer console for details'); log.error('Error while trying to execute an ad-hoc command'); log.error(e); } if (result) { cmd.alert = result.querySelector('note')?.textContent; } else { cmd.alert = 'Done'; } cmd.alert_type = 'primary'; this.nonce = u.getUniqueId(); } } api.elements.define('converse-adhoc-commands', AdHocCommands);