1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.plugin.pircbot;
21
22 import java.awt.event.ActionEvent;
23 import java.beans.PropertyChangeListener;
24 import java.beans.PropertyChangeSupport;
25 import java.io.IOException;
26 import java.util.HashSet;
27 import java.util.Hashtable;
28 import java.util.Iterator;
29
30 import javax.swing.Action;
31
32 import org.apache.log4j.Logger;
33 import org.jibble.pircbot.DccFileTransfer;
34 import org.jibble.pircbot.NickAlreadyInUseException;
35 import org.jibble.pircbot.PircBot;
36 import org.jibble.pircbot.User;
37 import org.xnap.XNap;
38 import org.xnap.chat.AbstractChatProvider;
39 import org.xnap.chat.ChannelInfo;
40 import org.xnap.chat.ChatManager;
41 import org.xnap.chat.DefaultChannelInfo;
42 import org.xnap.cmdl.Command;
43 import org.xnap.cmdl.Console;
44 import org.xnap.cmdl.Executer;
45 import org.xnap.cmdl.LocalExecuter;
46 import org.xnap.event.StateListener;
47 import org.xnap.event.StateSupport;
48 import org.xnap.gui.action.AbstractListChannelsAction;
49 import org.xnap.gui.action.JoinChannelAction;
50 import org.xnap.net.NetHelper;
51 import org.xnap.transfer.DccDownload;
52 import org.xnap.transfer.DownloadManager;
53 import org.xnap.util.FiniteStateMachine;
54 import org.xnap.util.IllegalOperationException;
55 import org.xnap.util.Preferences;
56 import org.xnap.util.State;
57 import org.xnap.util.StringHelper;
58
59 public class PircBotServer extends AbstractChatProvider {
60
61
62
63 private static final Hashtable TRANSITION_TABLE;
64 static {
65 State[][] table = new State[][] {
66 { State.DISCONNECTED,
67 State.CONNECTING, },
68 { State.CONNECTING,
69 State.CONNECTED, State.DISCONNECTED, },
70 { State.CONNECTED,
71 State.DISCONNECTED, },
72 };
73
74 TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
75 }
76
77
78
79 private static transient Logger logger
80 = Logger.getLogger(PircBotServer.class);
81
82 private transient PropertyChangeSupport pcs
83 = new PropertyChangeSupport(this);
84
85 private PircBotServerData data;
86 private Server server;
87
88 private StateMachine sm = new StateMachine();
89 private StateSupport listeners = new StateSupport(this);
90
91
92
93 public PircBotServer(PircBotServerData data)
94 {
95 this.data = data;
96 }
97
98 public PircBotServer()
99 {
100 this(new PircBotServerData());
101 }
102
103
104
105 public void addPropertyChangeListener(PropertyChangeListener l)
106 {
107 pcs.addPropertyChangeListener(l);
108 }
109
110 public void removePropertyChangeListener(PropertyChangeListener l)
111 {
112 pcs.removePropertyChangeListener(l);
113 }
114
115 public void addStateListener(StateListener listener)
116 {
117 listeners.addStateListener(listener);
118 }
119
120 public void removeStateListener(StateListener listener)
121 {
122 listeners.removeStateListener(listener);
123 }
124
125 /***
126 * Disconnects the server and removes all channels.
127 */
128 public void close()
129 {
130 if (server != null) {
131 server.removeAllChannels();
132 server.disconnect();
133 }
134 }
135
136 public void connect()
137 {
138 try {
139 setState(State.CONNECTING);
140 }
141 catch (IllegalOperationException e) {
142 logger.debug("unexpected state", e);
143 }
144 }
145
146 public Action[] getActions()
147 {
148 return new Action[] { new JoinChannelAction(this),
149 new ListChannelsAction() };
150 }
151
152 public ChannelInfo[] getChannels()
153 {
154 return getBot().getChannelInfos();
155 }
156
157 public boolean getAutoConnect()
158 {
159 return data.autoConnect;
160 }
161
162 public String getAutoJoinChannels()
163 {
164 return data.autoJoinChannels;
165 }
166
167 public String getConnectCommand()
168 {
169 return data.connectCommand;
170 }
171
172 public String getHost()
173 {
174 return data.host;
175 }
176
177 public String getLogin()
178 {
179 return data.login;
180 }
181
182 public String getName()
183 {
184 return data.name;
185 }
186
187 public String getNetwork()
188 {
189 return data.network;
190 }
191
192 public String getNick()
193 {
194 return data.nick;
195 }
196
197 /***
198 * Returns the local peer.
199 */
200 public PircBotPeer getLocalPeer()
201 {
202 return getBot().getPeerByName(getBot().getNick());
203 }
204
205 public String getPassword()
206 {
207 return data.password;
208 }
209
210 public int getPort()
211 {
212 return data.port;
213 }
214
215 public String getStatus()
216 {
217 return sm.getDescription();
218 }
219
220 public boolean isConnected()
221 {
222 return sm.getState() == State.CONNECTED;
223 }
224
225 public boolean isDisconnected()
226 {
227 return sm.getState() == State.DISCONNECTED;
228 }
229
230 public void join(String name)
231 {
232 PircBotChannel c = getBot().getChannelByName(name);
233 getBot().joinChannel(name);
234 c.setJoined(true, null);
235 }
236
237 public void setAutoConnect(boolean autoConnect)
238 {
239 boolean oldValue = getAutoConnect();
240 data.autoConnect = autoConnect;
241 pcs.firePropertyChange("autoConnect", oldValue, autoConnect);
242 }
243
244 public void setAutoJoinChannels(String autoJoinChannels)
245 {
246 String oldValue = getAutoJoinChannels();
247 data.autoJoinChannels = autoJoinChannels;
248 pcs.firePropertyChange("autoJoinChannels", oldValue,
249 autoJoinChannels);
250 }
251
252 /***
253 * @param string
254 */
255 public void setConnectCommand(String connectCommand)
256 {
257 String oldValue = getConnectCommand();
258 data.connectCommand = connectCommand;
259 pcs.firePropertyChange("connectCommand", oldValue,
260 connectCommand);
261 }
262
263 public void setHost(String host)
264 {
265 String oldValue = getHost();
266 data.host = host;
267 pcs.firePropertyChange("host", oldValue, host);
268 }
269
270 public void setLogin(String login)
271 {
272 String oldValue = getLogin();
273 data.login = login;
274 pcs.firePropertyChange("login", oldValue, login);
275 }
276
277 public void setName(String name)
278 {
279 String oldValue = getName();
280 data.name = name;
281 pcs.firePropertyChange("name", oldValue, name);
282 }
283
284 public void setNetwork(String network)
285 {
286 String oldValue = getNetwork();
287 data.network = network;
288 pcs.firePropertyChange("network", oldValue, network);
289 }
290
291 public void setNick(String nick)
292 {
293 String oldValue = getNick();
294 data.nick = nick;
295 pcs.firePropertyChange("nick", oldValue, nick);
296 }
297
298 public void setPassword(String password)
299 {
300 String oldValue = getPassword();
301 data.password = password;
302 pcs.firePropertyChange("password", oldValue, password);
303 }
304
305 public void setPort(int port)
306 {
307 int oldValue = getPort();
308 data.port = port;
309 pcs.firePropertyChange("port", new Integer(oldValue),
310 new Integer(port));
311 }
312
313 /***
314 * Used by classes in this package to access the PircBot instance
315 * directly.
316 */
317 Server getBot()
318 {
319
320 if (server == null) {
321 server = new Server();
322 }
323 return server;
324 }
325
326 /***
327 * Used by {@link PircBotPanel} for serialization.
328 */
329 PircBotServerData getData()
330 {
331 return data;
332 }
333
334 private void setState(State newState)
335 {
336 sm.setState(newState);
337 listeners.fireStateChanged();
338 }
339
340 private void setState(State newState, String message)
341 {
342 sm.setState(newState, message);
343 listeners.fireStateChanged();
344 }
345
346
347
348 private class StateMachine extends FiniteStateMachine
349 {
350
351
352
353 public StateMachine()
354 {
355 super(State.DISCONNECTED, TRANSITION_TABLE);
356 }
357
358
359
360 protected synchronized void stateChanged(State oldState,
361 State newState)
362 {
363 if (newState == State.CONNECTING) {
364 Runnable runner = new Runnable()
365 {
366 public void run()
367 {
368 connect();
369 }
370 };
371 Thread t
372 = new Thread(runner, "PircBotServer " + getName());
373 t.start();
374 return;
375 }
376 else if (newState == State.CONNECTED) {
377 String connectCommand = PircBotServer.this.getConnectCommand();
378 if (connectCommand != null && connectCommand.length() > 0) {
379 Executer.parse(connectCommand, getBot());
380 }
381
382 String autoJoin = PircBotServer.this.getAutoJoinChannels();
383 if (autoJoin != null && autoJoin.length() > 0) {
384
385 String[] channels = StringHelper.toArray(autoJoin, ";,");
386 for (int i = 0; i < channels.length; i++) {
387 PircBotServer.this.join(channels[i]);
388 }
389 }
390 }
391 }
392
393 private void connect()
394 {
395 try {
396 String login = PircBotServer.this.getLogin();
397 login = (login != null && login.length() > 0)
398 ? login
399 : Preferences.getInstance().getUsername();
400
401 String nick = PircBotServer.this.getNick();
402 nick = (nick != null && nick.length() > 0)
403 ? nick
404 : Preferences.getInstance().getUsername();
405
406 String password = PircBotServer.this.getPassword();
407
408 for (int i = 0; i < 3; i++) {
409 try {
410 getBot().setLogin(login, nick);
411 if (password != null && password.length() > 0) {
412 getBot().connect(PircBotServer.this.getHost(),
413 PircBotServer.this.getPort(),
414 password);
415 }
416 else {
417 getBot().connect(PircBotServer.this.getHost(),
418 PircBotServer.this.getPort());
419 }
420 break;
421 }
422 catch (NickAlreadyInUseException e2) {
423 nick += "_";
424 }
425 }
426 }
427 catch (Exception e) {
428 logger.debug("connect failed", e);
429 String msg = (e instanceof IOException)
430 ? NetHelper.getErrorMessage((IOException)e)
431 : e.getLocalizedMessage();
432 PircBotServer.this.setState
433 (State.DISCONNECTED, XNap.tr("connect failed: {0}", msg));
434 return;
435 }
436 }
437
438 }
439
440 class Server extends PircBot implements Console {
441
442
443
444
445
446 /***
447 * Contains {@link ChannelInfo} objects.
448 */
449 private HashSet channels = new HashSet();
450
451 /***
452 * A list of PircBotChannel objects. Contains the channels that have
453 * been added to the {@link ChatManager}.
454 */
455 private Hashtable channelByName = new Hashtable();
456
457 private Hashtable peerByName = new Hashtable();
458
459 private LocalExecuter executer;
460
461
462
463 public Server()
464 {
465 executer = new LocalExecuter(getCommands());
466 }
467
468
469
470 /***
471 * Closes all channels.
472 */
473 public void removeAllChannels()
474 {
475 for (Iterator i = channelByName.values().iterator(); i.hasNext();) {
476 PircBotChannel c = (PircBotChannel)i.next();
477 partChannel(c.getName());
478 ChatManager.getInstance().remove(c);
479 }
480 channelByName.clear();
481 }
482
483 public PircBotChannel getChannelByName(String name)
484 {
485 PircBotChannel channel = (PircBotChannel)channelByName.get(name);
486 if (channel == null) {
487 channel = new PircBotChannel(PircBotServer.this, name);
488 channelByName.put(name, channel);
489 ChatManager.getInstance().add(channel);
490 }
491 return channel;
492 }
493
494 public PircBotChannel getPrivateChannelByName(String name)
495 {
496 PircBotChannel channel = (PircBotChannel)channelByName.get(name);
497 if (channel == null) {
498 channel = new PircBotChannel(PircBotServer.this, name);
499 channel.add(getPeerByName(this.getNick()));
500 channel.add(getPeerByName(name));
501 channelByName.put(name, channel);
502 ChatManager.getInstance().add(channel);
503 }
504 return channel;
505 }
506
507 public void removeChannel(PircBotChannel channel)
508 {
509 channelByName.remove(channel.getName());
510 }
511
512 public synchronized ChannelInfo[] getChannelInfos()
513 {
514
515 String[] joinedChannels = this.getChannels();
516 for (int i = 0; i < joinedChannels.length; i++) {
517 ChannelInfo ci = new DefaultChannelInfo(joinedChannels[i]);
518 if (!channels.contains(ci)) {
519 channels.add(ci);
520 }
521 }
522
523 return (ChannelInfo[])channels.toArray(new ChannelInfo[0]);
524 }
525
526 public PircBotPeer getPeerByName(String name)
527 {
528 PircBotPeer peer = (PircBotPeer)peerByName.get(name);
529 if (peer == null) {
530 peer = new PircBotPeer(PircBotServer.this, name);
531 peerByName.put(name, peer);
532 }
533 return peer;
534 }
535
536 protected synchronized void onChannelInfo(String channel,
537 int userCount, String topic)
538 {
539 channels.add(new DefaultChannelInfo(channel, userCount, topic));
540 channelsUpdated();
541 }
542
543 protected void onAction(String sender, String login,
544 String hostname, String channel,
545 String message)
546 {
547 PircBotChannel c = getChannelByName(channel);
548 c.actionMessageReceived(getPeerByName(sender), message);
549 }
550
551 protected void onConnect()
552 {
553 PircBotServer.this.setState(State.CONNECTED);
554 logger.debug("connected to " + PircBotServer.this.getName());
555 ChatManager.getInstance().add(PircBotServer.this);
556 }
557
558 protected void onDeop(String channel, String sourceNick,
559 String sourceLogin, String sourceHostname,
560 String recipient)
561 {
562 PircBotPeer p = getPeerByName(recipient);
563 PircBotChannel c = getChannelByName(channel);
564 c.setOp(p, false);
565 }
566
567 protected void onDeVoice(String channel, String sourceNick,
568 String sourceLogin, String sourceHostname,
569 String recipient)
570 {
571
572
573 PircBotPeer p = getPeerByName(recipient);
574 PircBotChannel c = getChannelByName(channel);
575 c.setVoice(p, false);
576 }
577
578 protected void onDisconnect()
579 {
580 for (Iterator i = channelByName.values().iterator(); i.hasNext();) {
581 PircBotChannel c = (PircBotChannel)i.next();
582 c.setJoined(false, XNap.tr("client disconnected"));
583 }
584
585 PircBotServer.this.setState(State.DISCONNECTED);
586 logger.debug("disconnected from " + PircBotServer.this.getName());
587 ChatManager.getInstance().remove(PircBotServer.this);
588 }
589
590 protected void onIncomingFileTransfer(DccFileTransfer transfer)
591 {
592 DccDownload d = new DccDownload
593 (getPeerByName(transfer.getNick()),
594 transfer.getFile().getName(),
595 transfer.getSize(),
596 transfer.getHostname(),
597 transfer.getPort());
598 DownloadManager.getInstance().add(d);
599 }
600
601 protected void onJoin(String channel, String sender, String login,
602 String hostname)
603 {
604 PircBotPeer p = getPeerByName(sender);
605 p.setHost(hostname);
606
607 PircBotChannel c = getChannelByName(channel);
608 c.add(p);
609 }
610
611 protected void onMessage(String channel, String sender, String login,
612 String hostname, String message)
613 {
614 PircBotChannel c = getChannelByName(channel);
615 c.messageReceived(getPeerByName(sender), message);
616 }
617
618 protected void onNickChange(String oldNick, String login,
619 String hostname, String newNick)
620 {
621 PircBotPeer peer = getPeerByName(oldNick);
622 peer.setHost(hostname);
623 peer.setName(newNick);
624 for (Iterator i = channelByName.values().iterator();
625 i.hasNext();) {
626 ((PircBotChannel)i.next()).peerChanged(peer);
627 }
628 }
629
630 protected void onNotice(String sourceNick, String sourceLogin,
631 String sourceHostname, String target,
632 String notice)
633 {
634 logger.debug("notice: " + notice);
635
636
637 int i = notice.indexOf("[#");
638 int j = notice.indexOf("]", i);
639 if (i != -1 && j != -1) {
640 target = notice.substring(i + 1, j);
641 logger.warn("x:" + target);
642 }
643
644 PircBotChannel c = getChannelByName(target);
645 c.infoReceived(notice);
646 }
647
648 protected void onPart(String channel, String sender,
649 String login, String hostname)
650 {
651 PircBotChannel c = (PircBotChannel)channelByName.get(channel);
652 if (c != null) {
653 if (sender.equals(this.getNick())) {
654 c.setJoined(false, null);
655 }
656
657 c.remove(getPeerByName(sender));
658 }
659 }
660
661 protected void onPrivateMessage(String sender, String login,
662 String hostname, String message)
663 {
664 PircBotChannel c = getPrivateChannelByName(sender);
665 c.messageReceived(getPeerByName(sender), message);
666 }
667
668 protected void onOp(String channel, String sourceNick,
669 String sourceLogin, String sourceHostname,
670 String recipient)
671 {
672 PircBotPeer p = getPeerByName(recipient);
673 PircBotChannel c = getChannelByName(channel);
674 c.setOp(p, true);
675 }
676
677 protected void onTopic(String channel, String topic, String setBy, long date, boolean changed)
678 {
679 PircBotChannel c = getChannelByName(channel);
680 c.topicChanged(topic);
681 }
682
683 protected void onServerResponse(int code, String response)
684 {
685 logger.debug(code + ": " + response);
686
687 String nick = this.getNick();
688 if (response.startsWith(nick + " :")) {
689 response = response.substring(nick.length() + 2);
690 }
691
692 if (code >= 400 && code <= 599) {
693
694 messageReceived(response + "\n");
695 return;
696 }
697
698 switch (code) {
699 case 1:
700 case 2:
701 case 3:
702 case 4:
703 case 5:
704 case 251:
705 case 254:
706 case 255:
707 case RPL_WHOISUSER:
708 case RPL_WHOISSERVER:
709 case RPL_WHOISOPERATOR:
710 case RPL_WHOWASUSER:
711 case RPL_ENDOFWHO:
712 case RPL_WHOISIDLE:
713 case RPL_ENDOFWHOIS:
714 case RPL_WHOISCHANNELS:
715 case RPL_MOTD:
716 messageReceived(response + "\n");
717 break;
718 }
719 }
720
721 protected void onUserList(String channel, User[] users)
722 {
723 PircBotChannel c = getChannelByName(channel);
724 for (int i = 0; i < users.length; i++) {
725 PircBotPeer p = getPeerByName(users[i].getNick());
726 c.setOp(p, users[i].isOp());
727 c.add(p);
728 }
729 }
730
731 protected void onVoice(String channel, String sourceNick,
732 String sourceLogin, String sourceHostname,
733 String recipient)
734 {
735 PircBotPeer p = getPeerByName(recipient);
736 PircBotChannel c = getChannelByName(channel);
737 c.setVoice(p, true);
738 }
739
740 public void setLogin(String login, String nick)
741 {
742 super.setFinger("XNap/PircBot");
743 super.setLogin(login);
744 super.setName(nick);
745 }
746
747 public void whois(String nick)
748 {
749 sendRawLine("WHOIS " + nick);
750 }
751
752 /***
753 * @see org.xnap.cmdl.Console#getCommand(java.lang.String)
754 */
755 public Command getCommand(String commandName)
756 {
757 return executer.getCommand(commandName);
758 }
759
760 /***
761 * @see org.xnap.cmdl.Console#getCommands()
762 */
763 public Command[] getCommands()
764 {
765 return new Command[] { new PircBotMessageCommand(PircBotServer.this) };
766 }
767
768 /***
769 * @see org.xnap.cmdl.Console#isEchoing()
770 */
771 public boolean isEchoing()
772 {
773 return false;
774 }
775
776 /***
777 * @see org.xnap.cmdl.Console#readln(java.lang.String)
778 */
779 public String readln(String prompt)
780 {
781 return null;
782 }
783
784 /***
785 * @see org.xnap.cmdl.Console#println(java.lang.String)
786 */
787 public void println(String text)
788 {
789 messageReceived(text);
790 }
791
792 }
793
794 /***
795 * Queries the list of channels from the server.
796 */
797 private class ListChannelsAction extends AbstractListChannelsAction
798 {
799 public void actionPerformed(ActionEvent event)
800 {
801 PircBotServer.this.getBot().listChannels();
802 channelsUpdated();
803 }
804 }
805
806 }