#!/usr/bin/env swipl /* echobot.pl Author: Gimenez, Christian. Copyright (C) 2026 Gimenez, Christian This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . 2026-05-03 */ :- module(echobot, []). /** echobot: Echo bot example. To run this script, set BOTJID and BOTPASS with the JID and password to connect to the server. @author Christian Gimenez @license GPLv3 */ :- license(gplv3). :- use_module(library(xmpp/xmpp)). :- use_module(library(xmpp/xmpp_core)). :- use_module(library(xpath)). :- initialization(main, main). opt_type(d, debug, boolean). opt_help(debug, 'Enable debug mode'). % ---------- % Handlers %! after_connect_handler(+Args: list) % % Handler for after_connect hook. % % This handler sends the chatty presence of the bot. % % @param Args Ignored (no processing to this data). after_connect_handler(_Args) :- send_presence('Echo Bot ready!', [show('chat')]), debug(echobot, 'Echobot connected!', []). %! received_presence_handler(+Args: list) % % Handler for received_presence hook. % % Executed when a new presence update stanza is received. This handler only % prints to standard output the update presence. % % # Example % Presences stanza are XML strings such as follows: % % ``` % % % PDih23uetra123aeoua//1uerta1234taro45096tor= % % % % % b123ebaeba1234be384ab39952138e9281a123f3 % % % Not in my phone % away % % ``` % % The `Args` argument receives a list of the XML string parsed using % load_xml/3. Also, the list contains the current client status. For instance, % the XML string presented before is parsed as the following list: % % ``` % [ % data([element(presence, % ['xml:lang'=en, % to='alice@example.net/echobot', % from='bob@example.net'], % [element(c, % [xmlns='urn:xmpp:caps'], % [element(hash, % [xmlns='urn:xmpp:hashes:2',algo='sha-256'], % ['PDih23uetra123aeoua//1uerta1234taro45096tor=']) % ]), % element(c, % [xmlns='http://jabber.org/protocol/caps', % hash='sha-1', % ver='29g1I++jyf7QyZgmeQdOWtbHTZ0=', % node='https://cheogram.com'], % []), % element(idle,[xmlns='urn:xmpp:idle:1',since='2026-05-03T14:51:48.684Z'],[]), % element(x,[xmlns='vcard-temp:x:update'], % [element(photo,[],[b123ebaeba1234be384ab39952138e9281a123f3]) % ]), % element(delay, % [from='bob@example.net', % stamp='2026-05-03T14:51:49.130928Z', % xmlns='urn:xmpp:delay'], % []), % element(status,[],['Not in my phone']), % element(show,[],[away]) % ]) % ]), % current_state(connected) % ] % ``` received_presence_handler(Args) :- debug(echobot,'Handling presence: ~q', [Args]), member(data(XML), Args), (xpath(XML, //presence/status(text), Text), !; Text = '[No status]'), (xpath(XML, //presence/show(text), Show), !; Show = 'Online'), xpath(XML, //presence(@to), To), xpath(XML, //presence(@from), From), !, format('Presence from ~q to ~q: (~q) ~q', [From, To, Show, Text]), nl, flush. received_presence_handler(Args) :- !, debug(echobot, 'Failed to handle presence: ~q', [Args]). %! received_message_handler(+Args: list) % % Handler for the `received_message` hook. % % This handlers is executed when the bot receives a message stanza. It extracts % the from JID and the text message, and sends it back. % % # Argument example % The `Args` argument is an XML parsed into a Prolog list. The message stanza % is parsed using load_xml/3, and passed to this hook handler. % % The following is an XML string example received by the client and not yet % processed by the xmpp_core library. % % ``` % % % % % Hola % % ``` % % This string is parsed and generates a list of terms `DATA`. Then it is added % to a list as `data(DATA)` with the current XMPP client state. This list is % unified with Args when calling this handler. % % ``` % [ % data([element(message, % ['xml:lang'=en, % to='bob@example.net', % from='alice@example.net/myclient-12345', % type=chat, % id='d30b3d1b-67f3-4f4a-a0e0-c3fdc9d36b93'], % [element(archived, % [by='bob@example.net', % id='1777820434735876', % xmlns='urn:xmpp:mam:tmp'], % []), % element('stanza-id', % [by='bob@example.net', % id='1777820434735876', % xmlns='urn:xmpp:sid:0'], % []), % element(request,[xmlns='urn:xmpp:receipts'],[]), % element(markable,[xmlns='urn:xmpp:chat-markers:0'],[]), % element(body,[],['Hola']) % ]) % ]), % current_state(connected) % ] % ``` % % @param Args a list with `[data(XMLParsed), current_state(ClientState)]`. The % order of data and currest_state is not garanteed. received_message_handler(Args) :- debug(echobot, 'Received: ~q', [Args]), member(data(XML), Args), xpath(XML, //message/body(text), Text), xpath(XML, //message(@from), From), format(atom(NewText), 'Echo: ~q', [Text]), debug(echobot, 'Sending ~q...', [NewText]), send_message(From, NewText), format('Echo to ~q: ~q', [From, NewText]), nl, flush. received_message_handler(Args) :- !, debug(echobot, 'Message handler failed with ~q', [Args]). % ----- enable_debug(Options) :- option(debug(true), Options, false), debug(xmpp), debug(xmpp_auth), debug(xmpp_ping), debug(echobot). get_envdata(JID, Password) :- getenv('BOTJID', JID), getenv('BOTPASS', Password). get_envdata(_, _) :- throw(error(no_envvar_set, context(get_envdata/2, 'Please, set BOTJID and BOTPASS environment variables'))). main(ArgV) :- argv_options(ArgV, _Pos, Options), ignore(enable_debug(Options)), get_envdata(JID, Password), add_hook(after_connect, echobot:after_connect_handler), add_hook(received_message, echobot:received_message_handler), add_hook(received_presence, echobot:received_presence_handler), connect(JID, Password), write('Press ENTER to quit.'), nl, get_char(_), disconnect.