You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XmppParser.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.parser.xmpp;
  23. import com.dmdirc.parser.common.AwayState;
  24. import com.dmdirc.parser.common.BaseSocketAwareParser;
  25. import com.dmdirc.parser.common.ChannelJoinRequest;
  26. import com.dmdirc.parser.common.ChildImplementations;
  27. import com.dmdirc.parser.common.CompositionState;
  28. import com.dmdirc.parser.common.DefaultStringConverter;
  29. import com.dmdirc.parser.common.ParserError;
  30. import com.dmdirc.parser.common.QueuePriority;
  31. import com.dmdirc.parser.events.AwayStateEvent;
  32. import com.dmdirc.parser.events.ChannelSelfJoinEvent;
  33. import com.dmdirc.parser.events.CompositionStateChangeEvent;
  34. import com.dmdirc.parser.events.ConnectErrorEvent;
  35. import com.dmdirc.parser.events.DataInEvent;
  36. import com.dmdirc.parser.events.DataOutEvent;
  37. import com.dmdirc.parser.events.NumericEvent;
  38. import com.dmdirc.parser.events.OtherAwayStateEvent;
  39. import com.dmdirc.parser.events.PrivateActionEvent;
  40. import com.dmdirc.parser.events.PrivateMessageEvent;
  41. import com.dmdirc.parser.events.ServerReadyEvent;
  42. import com.dmdirc.parser.events.SocketCloseEvent;
  43. import com.dmdirc.parser.interfaces.ChannelInfo;
  44. import com.dmdirc.parser.interfaces.ClientInfo;
  45. import com.dmdirc.parser.interfaces.LocalClientInfo;
  46. import com.dmdirc.parser.interfaces.StringConverter;
  47. import java.net.URI;
  48. import java.time.LocalDateTime;
  49. import java.util.Collection;
  50. import java.util.Collections;
  51. import java.util.HashMap;
  52. import java.util.List;
  53. import java.util.Map;
  54. import java.util.regex.Matcher;
  55. import java.util.regex.Pattern;
  56. import org.jivesoftware.smack.Chat;
  57. import org.jivesoftware.smack.ChatManagerListener;
  58. import org.jivesoftware.smack.ConnectionConfiguration;
  59. import org.jivesoftware.smack.ConnectionListener;
  60. import org.jivesoftware.smack.PacketListener;
  61. import org.jivesoftware.smack.RosterEntry;
  62. import org.jivesoftware.smack.RosterListener;
  63. import org.jivesoftware.smack.XMPPConnection;
  64. import org.jivesoftware.smack.XMPPException;
  65. import org.jivesoftware.smack.filter.PacketFilter;
  66. import org.jivesoftware.smack.packet.Message;
  67. import org.jivesoftware.smack.packet.Packet;
  68. import org.jivesoftware.smack.packet.Presence;
  69. import org.jivesoftware.smackx.ChatState;
  70. import org.jivesoftware.smackx.ChatStateListener;
  71. import org.jivesoftware.smackx.ChatStateManager;
  72. import org.jivesoftware.smackx.muc.MultiUserChat;
  73. import org.slf4j.LoggerFactory;
  74. /**
  75. * A parser which can understand the XMPP protocol.
  76. */
  77. @ChildImplementations({
  78. XmppClientInfo.class, XmppLocalClientInfo.class, XmppFakeChannel.class,
  79. XmppChannelClientInfo.class
  80. })
  81. public class XmppParser extends BaseSocketAwareParser {
  82. private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(XmppParser.class);
  83. /** Pattern to use to extract priority. */
  84. private static final Pattern PRIORITY_PATTERN = Pattern.compile(
  85. "(?i)(?:^|&)priority=([0-9]+)(?:$|&)");
  86. /** The connection to use. */
  87. private XMPPConnection connection;
  88. /** The state manager for the current connection. */
  89. private ChatStateManager stateManager;
  90. /** A cache of known chats. */
  91. private final Map<String, Chat> chats = new HashMap<>();
  92. /** A cache of known clients. */
  93. private final Map<String, XmppClientInfo> contacts = new HashMap<>();
  94. /** Whether or not to use a fake local channel for a buddy list replacement. */
  95. private final boolean useFakeChannel;
  96. /** The priority of this endpoint. */
  97. private final int priority;
  98. /** The fake channel to use is useFakeChannel is enabled. */
  99. private XmppFakeChannel fakeChannel;
  100. /**
  101. * Creates a new XMPP parser for the specified address.
  102. *
  103. * @param address The address to connect to
  104. */
  105. public XmppParser(final URI address) {
  106. super(address);
  107. if (address.getQuery() == null) {
  108. useFakeChannel = false;
  109. priority = 0;
  110. } else {
  111. final Matcher matcher = PRIORITY_PATTERN.matcher(address.getQuery());
  112. useFakeChannel = address.getQuery().matches("(?i).*(^|&)showchannel($|&).*");
  113. priority = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
  114. }
  115. LOG.debug(
  116. "XMPP parser created with query string {}, parsed fake channel = {}, priority = {}",
  117. address.getQuery(), useFakeChannel, priority);
  118. }
  119. @Override
  120. public void disconnect(final String message) {
  121. super.disconnect(message);
  122. // TODO: Pass quit message on as presence?
  123. connection.disconnect();
  124. }
  125. @Override
  126. public void joinChannels(final ChannelJoinRequest... channels) {
  127. for (ChannelJoinRequest request : channels) {
  128. final MultiUserChat muc = new MultiUserChat(connection, request.getName().substring(1));
  129. try {
  130. if (request.getPassword() == null) {
  131. muc.join(getLocalClient().getNickname());
  132. } else {
  133. muc.join(getLocalClient().getNickname(), request.getPassword());
  134. }
  135. // TODO: Send callbacks etc
  136. } catch (XMPPException ex) {
  137. // TODO: handle
  138. }
  139. }
  140. }
  141. @Override
  142. public ChannelInfo getChannel(final String channel) {
  143. // TODO: Implement
  144. throw new UnsupportedOperationException("Not supported yet.");
  145. }
  146. @Override
  147. public Collection<? extends ChannelInfo> getChannels() {
  148. return Collections.<ChannelInfo>emptyList();
  149. }
  150. @Override
  151. public int getMaxLength(final String type, final String target) {
  152. return Integer.MAX_VALUE;
  153. }
  154. @Override
  155. public int getMaxLength() {
  156. return Integer.MAX_VALUE;
  157. }
  158. @Override
  159. public LocalClientInfo getLocalClient() {
  160. final String[] parts = parseHostmask(connection.getUser());
  161. // TODO: Cache this
  162. return new XmppLocalClientInfo(this, parts[0], parts[2], parts[1]);
  163. }
  164. @Override
  165. public XmppClientInfo getClient(final String details) {
  166. final String[] parts = parseHostmask(details);
  167. if (!contacts.containsKey(parts[0])) {
  168. contacts.put(parts[0], new XmppClientInfo(this, parts[0], parts[2], parts[1]));
  169. }
  170. return contacts.get(parts[0]);
  171. }
  172. @Override
  173. public void sendRawMessage(final String message) {
  174. // Urgh, hacky horrible rubbish. These commands should call methods.
  175. if (message.toUpperCase().startsWith("WHOIS ")) {
  176. handleWhois(message.split(" ")[1]);
  177. } else if (!message.isEmpty() && message.charAt(0) == '<') {
  178. // Looks vaguely like XML, let's send it.
  179. connection.sendPacket(new Packet() {
  180. @Override
  181. public String toXML() {
  182. return message;
  183. }
  184. });
  185. }
  186. }
  187. /**
  188. * Handles a whois request for the specified target.
  189. *
  190. * @param target The user being WHOIS'd
  191. */
  192. private void handleWhois(final String target) {
  193. // Urgh, hacky horrible rubbish. This should be abstracted.
  194. if (contacts.containsKey(target)) {
  195. final XmppClientInfo client = contacts.get(target);
  196. final String[] userParts = client.getNickname().split("@", 2);
  197. callNumericCallback(311, target, userParts[0], userParts[1], "*", client.getRealname());
  198. for (Map.Entry<String, XmppEndpoint> endpoint : client.getEndpoints().entrySet()) {
  199. callNumericCallback(399, target, endpoint.getKey(),
  200. "(" + endpoint.getValue().getPresence() + ")", "has endpoint");
  201. }
  202. } else {
  203. callNumericCallback(401, target, "No such contact found");
  204. }
  205. callNumericCallback(318, target, "End of /WHOIS.");
  206. }
  207. private void callNumericCallback(final int numeric, final String... args) {
  208. final String[] newArgs = new String[args.length + 3];
  209. newArgs[0] = ":xmpp.server";
  210. newArgs[1] = (numeric < 100 ? "0" : "") + (numeric < 10 ? "0" : "") + numeric;
  211. newArgs[2] = getLocalClient().getNickname();
  212. System.arraycopy(args, 0, newArgs, 3, args.length);
  213. getCallbackManager().publish(new NumericEvent(this, LocalDateTime.now(), numeric, newArgs));
  214. }
  215. @Override
  216. public void sendRawMessage(final String message, final QueuePriority priority) {
  217. sendRawMessage(message);
  218. }
  219. @Override
  220. public StringConverter getStringConverter() {
  221. return new DefaultStringConverter();
  222. }
  223. @Override
  224. public boolean isValidChannelName(final String name) {
  225. return false; // TODO: Implement
  226. }
  227. @Override
  228. public boolean compareURI(final URI uri) {
  229. throw new UnsupportedOperationException("Not supported yet.");
  230. }
  231. @Override
  232. public Collection<? extends ChannelJoinRequest> extractChannels(final URI uri) {
  233. return Collections.<ChannelJoinRequest>emptyList();
  234. }
  235. @Override
  236. public String getNetworkName() {
  237. return "XMPP"; // TODO
  238. }
  239. @Override
  240. public String getServerSoftware() {
  241. return "Unknown"; // TODO
  242. }
  243. @Override
  244. public String getServerSoftwareType() {
  245. return "XMPP"; // TODO
  246. }
  247. @Override
  248. public List<String> getServerInformationLines() {
  249. return Collections.emptyList(); // TODO
  250. }
  251. @Override
  252. public int getMaxTopicLength() {
  253. return 0; // TODO
  254. }
  255. @Override
  256. public String getBooleanChannelModes() {
  257. return ""; // TODO
  258. }
  259. @Override
  260. public String getListChannelModes() {
  261. return ""; // TODO
  262. }
  263. @Override
  264. public int getMaxListModes(final char mode) {
  265. return 0; // TODO
  266. }
  267. @Override
  268. public boolean isUserSettable(final char mode) {
  269. throw new UnsupportedOperationException("Not supported yet.");
  270. }
  271. @Override
  272. public String getParameterChannelModes() {
  273. return ""; // TODO
  274. }
  275. @Override
  276. public String getDoubleParameterChannelModes() {
  277. return ""; // TODO
  278. }
  279. @Override
  280. public String getUserModes() {
  281. return ""; // TODO
  282. }
  283. @Override
  284. public String getChannelUserModes() {
  285. return ""; // TODO
  286. }
  287. @Override
  288. public String getChannelPrefixes() {
  289. return "#";
  290. }
  291. @Override
  292. public long getServerLatency() {
  293. return 1000L; // TODO
  294. }
  295. @Override
  296. public void sendCTCP(final String target, final String type, final String message) {
  297. throw new UnsupportedOperationException("Not supported yet.");
  298. }
  299. @Override
  300. public void sendCTCPReply(final String target, final String type, final String message) {
  301. throw new UnsupportedOperationException("Not supported yet.");
  302. }
  303. @Override
  304. public void sendMessage(final String target, final String message) {
  305. if (!chats.containsKey(target)) {
  306. LOG.debug("Creating new chat for {}", target);
  307. chats.put(target, connection.getChatManager().createChat(target,
  308. new MessageListenerImpl()));
  309. }
  310. try {
  311. chats.get(target).sendMessage(message);
  312. } catch (XMPPException ex) {
  313. // TODO: Handle this
  314. }
  315. }
  316. @Override
  317. public void sendNotice(final String target, final String message) {
  318. throw new UnsupportedOperationException("Not supported yet.");
  319. }
  320. @Override
  321. public void sendAction(final String target, final String message) {
  322. sendMessage(target, "/me " + message);
  323. }
  324. @Override
  325. public void sendInvite(final String channel, final String user) {
  326. throw new UnsupportedOperationException("Not supported yet.");
  327. }
  328. @Override
  329. public void sendWhois(final String nickname) {
  330. // TODO: Implement this
  331. }
  332. @Override
  333. public String getLastLine() {
  334. return "TODO: Implement me";
  335. }
  336. @Override
  337. public String[] parseHostmask(final String hostmask) {
  338. return new XmppProtocolDescription().parseHostmask(hostmask);
  339. }
  340. @Override
  341. public long getPingTime() {
  342. throw new UnsupportedOperationException("Not supported yet.");
  343. }
  344. @Override
  345. public void run() {
  346. if (getURI().getUserInfo() == null || !getURI().getUserInfo().contains(":")) {
  347. getCallbackManager().publish(new ConnectErrorEvent(this, LocalDateTime.now(),
  348. new ParserError(ParserError.ERROR_USER,
  349. "User name and password must be specified in URI", "")));
  350. return;
  351. }
  352. final String[] userInfoParts = getURI().getUserInfo().split(":", 2);
  353. final String[] userParts = userInfoParts[0].split("@", 2);
  354. final ConnectionConfiguration config = new ConnectionConfiguration(getURI().getHost(),
  355. getURI().getPort(), userParts[0]);
  356. config.setSecurityMode(getURI().getScheme().equalsIgnoreCase("xmpps")
  357. ? ConnectionConfiguration.SecurityMode.required
  358. : ConnectionConfiguration.SecurityMode.disabled);
  359. config.setSASLAuthenticationEnabled(true);
  360. config.setReconnectionAllowed(false);
  361. config.setRosterLoadedAtLogin(true);
  362. config.setSocketFactory(getSocketFactory());
  363. connection = new FixedXmppConnection(config);
  364. try {
  365. connection.connect();
  366. connection.addConnectionListener(new ConnectionListenerImpl());
  367. connection.addPacketListener(new PacketListenerImpl(false),
  368. new AcceptAllPacketFilter());
  369. connection.addPacketSendingListener(new PacketListenerImpl(true),
  370. new AcceptAllPacketFilter());
  371. connection.getChatManager().addChatListener(new ChatManagerListenerImpl());
  372. try {
  373. connection.login(userInfoParts[0], userInfoParts[1], "DMDirc.");
  374. } catch (XMPPException ex) {
  375. getCallbackManager().publish(new ConnectErrorEvent(this, LocalDateTime.now(),
  376. new ParserError(ParserError.ERROR_USER, ex.getMessage(), "")));
  377. return;
  378. }
  379. connection.sendPacket(new Presence(Presence.Type.available, null, priority,
  380. Presence.Mode.available));
  381. connection.getRoster().addRosterListener(new RosterListenerImpl());
  382. stateManager = ChatStateManager.getInstance(connection);
  383. setServerName(connection.getServiceName());
  384. getCallbackManager().publish(new ServerReadyEvent(this, LocalDateTime.now()));
  385. for (RosterEntry contact : connection.getRoster().getEntries()) {
  386. getClient(contact.getUser()).setRosterEntry(contact);
  387. }
  388. if (useFakeChannel) {
  389. fakeChannel = new XmppFakeChannel(this, "&contacts");
  390. getCallbackManager().publish(new ChannelSelfJoinEvent(null, null, fakeChannel));
  391. fakeChannel.updateContacts(contacts.values());
  392. contacts.values().stream().filter(XmppClientInfo::isAway).forEach(client ->
  393. getCallbackManager().publish(
  394. new OtherAwayStateEvent(this, LocalDateTime.now(), client,
  395. AwayState.UNKNOWN, AwayState.AWAY)));
  396. }
  397. } catch (XMPPException ex) {
  398. LOG.debug("Go an XMPP exception", ex);
  399. connection = null;
  400. final ParserError error = new ParserError(ParserError.ERROR_ERROR, "Unable to connect",
  401. "");
  402. if (ex.getWrappedThrowable() instanceof Exception) {
  403. // Pass along the underlying exception instead of an XMPP
  404. // specific one
  405. error.setException((Exception) ex.getWrappedThrowable());
  406. } else {
  407. error.setException(ex);
  408. }
  409. getCallbackManager().publish(new ConnectErrorEvent(this, LocalDateTime.now(), error));
  410. }
  411. }
  412. /**
  413. * Handles a client's away state changing.
  414. *
  415. * @param client The client whose state is changing
  416. * @param isBack True if the client is coming back, false if they're going away
  417. */
  418. public void handleAwayStateChange(final ClientInfo client, final boolean isBack) {
  419. LOG.debug("Handling away state change for {} to {}", client.getNickname(), isBack);
  420. if (useFakeChannel) {
  421. getCallbackManager().publish(new OtherAwayStateEvent(
  422. this, LocalDateTime.now(), client, isBack ? AwayState.AWAY : AwayState.HERE,
  423. isBack ? AwayState.HERE : AwayState.AWAY));
  424. }
  425. }
  426. /**
  427. * Marks the local user as away with the specified reason.
  428. *
  429. * @param reason The away reason
  430. */
  431. public void setAway(final String reason) {
  432. connection.sendPacket(new Presence(Presence.Type.available, reason,
  433. priority, Presence.Mode.away));
  434. getCallbackManager().publish(
  435. new AwayStateEvent(this, LocalDateTime.now(), AwayState.HERE, AwayState.AWAY,
  436. reason));
  437. }
  438. /**
  439. * Marks the local user as back.
  440. */
  441. public void setBack() {
  442. connection.sendPacket(new Presence(Presence.Type.available, null,
  443. priority, Presence.Mode.available));
  444. getCallbackManager().publish(
  445. new AwayStateEvent(this, LocalDateTime.now(), AwayState.AWAY, AwayState.HERE,
  446. null));
  447. }
  448. @Override
  449. public void setCompositionState(final String host, final CompositionState state) {
  450. LOG.debug("Setting composition state for {} to {}", host, state);
  451. final Chat chat = chats.get(parseHostmask(host)[0]);
  452. final ChatState newState;
  453. switch (state) {
  454. case ENTERED_TEXT:
  455. newState = ChatState.paused;
  456. break;
  457. case TYPING:
  458. newState = ChatState.composing;
  459. break;
  460. case IDLE:
  461. default:
  462. newState = ChatState.active;
  463. break;
  464. }
  465. if (chat != null && stateManager != null) {
  466. try {
  467. stateManager.setCurrentState(newState, chat);
  468. } catch (XMPPException ex) {
  469. // Can't set chat state... Oh well?
  470. LOG.info("Couldn't set composition state", ex);
  471. }
  472. }
  473. }
  474. @Override
  475. public void requestGroupList(final String searchTerms) {
  476. // Do nothing
  477. }
  478. private class ConnectionListenerImpl implements ConnectionListener {
  479. @Override
  480. public void connectionClosed() {
  481. getCallbackManager().publish(new SocketCloseEvent(XmppParser.this,
  482. LocalDateTime.now()));
  483. }
  484. @Override
  485. public void connectionClosedOnError(final Exception excptn) {
  486. // TODO: Handle exception
  487. getCallbackManager().publish(new SocketCloseEvent(XmppParser.this,
  488. LocalDateTime.now()));
  489. }
  490. @Override
  491. public void reconnectingIn(final int i) {
  492. throw new UnsupportedOperationException("Not supported yet.");
  493. }
  494. @Override
  495. public void reconnectionSuccessful() {
  496. throw new UnsupportedOperationException("Not supported yet.");
  497. }
  498. @Override
  499. public void reconnectionFailed(final Exception excptn) {
  500. throw new UnsupportedOperationException("Not supported yet.");
  501. }
  502. }
  503. private class RosterListenerImpl implements RosterListener {
  504. @Override
  505. public void entriesAdded(final Collection<String> clctn) {
  506. // Do nothing, yet
  507. }
  508. @Override
  509. public void entriesUpdated(final Collection<String> clctn) {
  510. // Do nothing, yet
  511. }
  512. @Override
  513. public void entriesDeleted(final Collection<String> clctn) {
  514. // Do nothing, yet
  515. }
  516. @Override
  517. public void presenceChanged(final Presence prsnc) {
  518. getClient(prsnc.getFrom()).setPresence(prsnc);
  519. }
  520. }
  521. private class ChatManagerListenerImpl implements ChatManagerListener {
  522. @Override
  523. public void chatCreated(final Chat chat, final boolean bln) {
  524. if (!bln) {
  525. // Only add chats that weren't created locally
  526. chats.put(parseHostmask(chat.getParticipant())[0], chat);
  527. chat.addMessageListener(new MessageListenerImpl());
  528. }
  529. }
  530. }
  531. private class MessageListenerImpl implements ChatStateListener {
  532. @Override
  533. public void processMessage(final Chat chat, final Message msg) {
  534. if (msg.getType() == Message.Type.error) {
  535. getCallbackManager().publish(new NumericEvent(XmppParser.this, LocalDateTime.now(),
  536. 404,
  537. new String[]{":xmpp", "404", getLocalClient().getNickname(), msg.getFrom(),
  538. "Cannot send message: " + msg.getError().toString()}));
  539. return;
  540. }
  541. if (msg.getBody() != null) {
  542. if (msg.getBody().startsWith("/me ")) {
  543. getCallbackManager().publish(new PrivateActionEvent(XmppParser.this,
  544. LocalDateTime.now(),
  545. msg.getBody().substring(4), msg.getFrom()));
  546. } else {
  547. getCallbackManager().publish(
  548. new PrivateMessageEvent(XmppParser.this,
  549. LocalDateTime.now(), msg.getBody(),
  550. msg.getFrom()));
  551. }
  552. }
  553. }
  554. @Override
  555. public void stateChanged(final Chat chat, final ChatState cs) {
  556. final CompositionState state;
  557. switch (cs) {
  558. case paused:
  559. state = CompositionState.ENTERED_TEXT;
  560. break;
  561. case composing:
  562. state = CompositionState.TYPING;
  563. break;
  564. case active:
  565. case gone:
  566. case inactive:
  567. default:
  568. state = CompositionState.IDLE;
  569. break;
  570. }
  571. getCallbackManager().publish(
  572. new CompositionStateChangeEvent(XmppParser.this, LocalDateTime.now(), state,
  573. chat.getParticipant()));
  574. }
  575. }
  576. private class PacketListenerImpl implements PacketListener {
  577. private final boolean dataOut;
  578. public PacketListenerImpl(final boolean dataOut) {
  579. this.dataOut = dataOut;
  580. }
  581. @Override
  582. public void processPacket(final Packet packet) {
  583. if (dataOut) {
  584. getCallbackManager().publish(
  585. new DataOutEvent(XmppParser.this, LocalDateTime.now(), packet.toXML()));
  586. } else {
  587. getCallbackManager().publish(
  588. new DataInEvent(XmppParser.this, LocalDateTime.now(), packet.toXML()));
  589. }
  590. }
  591. }
  592. private static class AcceptAllPacketFilter implements PacketFilter {
  593. @Override
  594. public boolean accept(final Packet packet) {
  595. return true;
  596. }
  597. }
  598. }