]> Pileus Git - ~andy/spades/blob - src/Message.java
Fix .look bugs
[~andy/spades] / src / Message.java
1 package org.pileus.spades;
2
3 import java.util.ArrayList;
4 import java.util.Date;
5 import java.util.List;
6 import java.util.regex.Pattern;
7 import java.util.regex.Matcher;
8
9 public class Message
10 {
11         /* Enumerations */
12         static enum Type {
13                 OTHER,    // Unknown message type
14                 JOIN,     // Join channel
15                 PART,     // Leave channel
16                 PRIVMSG,  // Private message
17                 TOPIC,    // Display current topic
18                 NAMES,    // Display user names
19                 ERROR,    // Error message from server
20                 CAP,      // Server capabilities
21                 AUTH,     // Authentication message
22                 AUTHOK,   // Authentication succeeded
23                 AUTHFAIL, // Authentication failed
24         };
25
26         static enum How {
27                 OTHER,    // Unknown message type
28                 CHANNEL,  // Normal message to a channel
29                 MENTION,  // User was mentioned in message text
30                 DIRECT,   // Message directed towards user
31                 PRIVMSG,  // Private message to user only
32                 SENT      // Message was sent by the user
33         };
34
35         /* Structures */
36         static class Color {
37                 public int     code;
38                 public String  hex;
39                 public String  name;
40                 public int     color;
41
42                 public Color(int code, String hex, String name)
43                 {
44                         this.code  = code;
45                         this.hex   = hex;
46                         this.name  = name;
47                         this.color = Os.getColor(hex);
48                 }
49         };
50
51         static class Format implements Cloneable {
52                 public boolean bold;
53                 public boolean italic;
54                 public boolean strike;
55                 public boolean underline;
56                 public boolean reverse;
57                 public Color   fg;
58                 public Color   bg;
59                 public String  txt;
60
61                 public Format clone()
62                 {
63                         try {
64                                 return (Format)super.clone();
65                         } catch (Exception e) {
66                                 return null;
67                         }
68                 }
69
70                 public String toString()
71                 {
72                         return (fg!=null  ? fg.hex : "xxxxxx") + ":" +
73                                (bg!=null  ? bg.hex : "xxxxxx") + ":" +
74                                (bold      ? "b"    : "-"     ) +
75                                (italic    ? "i"    : "-"     ) +
76                                (strike    ? "s"    : "-"     ) +
77                                (underline ? "u"    : "-"     ) +
78                                (reverse   ? "r"    : "-"     ) + ":" +
79                                "[" + txt + "]";
80                 }
81         };
82
83         /* Colors */
84         private static final Color colors[] = {
85                 new Color(0x0, "FFFFFF", "White"),
86                 new Color(0x1, "000000", "Black"),
87                 new Color(0x2, "000080", "Navy Blue"),
88                 new Color(0x3, "008000", "Green"),
89                 new Color(0x4, "FF0000", "Red"),
90                 new Color(0x5, "804040", "Brown"),
91                 new Color(0x6, "8000FF", "Purple"),
92                 new Color(0x7, "808000", "Olive"),
93                 new Color(0x8, "FFFF00", "Yellow"),
94                 new Color(0x9, "00FF00", "Lime Green"),
95                 new Color(0xA, "008080", "Teal"),
96                 new Color(0xB, "00FFFF", "Aqua Light"),
97                 new Color(0xC, "0000FF", "Royal Blue"),
98                 new Color(0xD, "FF00FF", "Hot Pink"),
99                 new Color(0xE, "808080", "Dark Gray"),
100                 new Color(0xF, "C0C0C0", "Light Gray"),
101         };
102
103         /* Constants */
104         private static final String  reMsg  = "(:([^ ]+) +)?(([A-Z0-9]+) +)(([^ ]+)[= ]+)?(([^: ]+) *)?(:(.*))?";
105         private static final String  reFrom = "([^! ]+)!.*";
106         private static final String  reTo   = "(([^ :,]*)[:,] *)?(.*)";
107         private static final String  reCmd  = "/([a-z]+)(?: +([^ ]*)(?: +(.*))?)?";
108
109         private static final Pattern ptMsg  = Pattern.compile(reMsg);
110         private static final Pattern ptFrom = Pattern.compile(reFrom);
111         private static final Pattern ptTo   = Pattern.compile(reTo);
112         private static final Pattern ptCmd  = Pattern.compile(reCmd);
113
114         private static final String  cReNum = "1[0-5]|0?[0-9]";
115         private static final String  cReFmt = "[\\002\\011\\017\\023\\025\\026\\037]";
116         private static final String  cReClr = "[\\003\\013]("+cReNum+")?(,("+cReNum+"))?";
117         private static final String  cRegex = cReFmt + "|" + cReClr + "|$";
118         private static final Pattern cPtrn  = Pattern.compile(cRegex);
119
120         /* Public data */
121         public Date    time = null;        // Message delivery time
122
123         public String  line = "";          // Raw IRC line     -- george@~g@example.com PRIVMSG #chat :larry: hello!
124
125         public String  src  = "";          // IRC Source       -- george!~g@example.com
126         public String  cmd  = "";          // IRC Command      -- PRIVMSG
127         public String  dst  = "";          // IRC Destination  -- #chat
128         public String  arg  = "";          // IRC Arguments    -- #chan in topic msg, etc
129         public String  msg  = "";          // IRC Message text -- larry: Hello!
130
131         public Type    type = Type.OTHER;  // Message Type     -- PRIVMSG
132         public How     how  = How.OTHER;   // How msg relates  -- SENT=geroge, DIRECT=larry, CHANNEL=*
133         public String  from = "";          // Nick of sender   -- george
134         public String  to   = "";          // Addressed name   -- larry
135         public String  txt  = "";          // Text of msg      -- Hello!
136
137         public List<Format> parts = new ArrayList<Format>();
138
139         /* Static methods */
140         private static Color getColor(String code)
141         {
142                 if (code == null)
143                         return null;
144                 int i = Integer.parseInt(code);
145                 if (i >= 0 && i < Message.colors.length)
146                         return Message.colors[i];
147                 return null;
148         }
149
150         /* Public Methods */
151         public Message(String dst, String from, String msg)
152         {
153                 this.time = new Date();
154                 this.how  = How.SENT;
155                 this.from = from;
156
157                 if (this.parseSlash(msg))
158                         return;
159
160                 this.type = Type.PRIVMSG;
161                 this.cmd  = "PRIVMSG";
162                 this.dst  = dst;
163                 this.msg  = msg;
164                 this.line = this.cmd + " " + this.dst + " :" + this.msg;
165         }
166
167         public Message(String line, String name)
168         {
169                 this.time = new Date();
170
171                 this.parseText(line);
172                 this.parseTypes(name);
173                 this.parseColors(this.msg);
174         }
175
176         public void debug()
177         {
178                 Os.debug("---------------------");
179                 Os.debug("line = [" + line + "]");
180                 Os.debug("src  = " + this.src);
181                 Os.debug("cmd  = " + this.cmd);
182                 Os.debug("dst  = " + this.dst);
183                 Os.debug("arg  = " + this.arg);
184                 Os.debug("msg  = " + this.msg);
185                 Os.debug("from = " + this.from);
186                 Os.debug("to   = " + this.to);
187                 Os.debug("txt  = " + this.txt);
188                 Os.debug("---------------------");
189         }
190
191         public String toString()
192         {
193                 return this.from + ": " + this.txt;
194         }
195
196         /* Private methods */
197         private String notnull(String string)
198         {
199                 return string == null ? "" : string;
200         }
201
202         private boolean parseSlash(String msg)
203         {
204                 if (msg.charAt(0) != '/')
205                         return false;
206
207                 // Split up line
208                 Matcher mr = ptCmd.matcher(msg);
209                 if (!mr.matches())
210                         return false;
211
212                 String cmd = notnull(mr.group(1));
213                 String arg = notnull(mr.group(2));
214                 String txt = notnull(mr.group(3));
215
216                 // Parse commands
217                 if (cmd.matches("j(oin)?")) {
218                         Os.debug("Message: /join");
219                         this.type = Type.JOIN;
220                         this.cmd  = "JOIN";
221                         this.msg  = arg;
222                         this.line = this.cmd + " :" + arg;
223                 }
224
225                 if (cmd.matches("p(art)?")) {
226                         Os.debug("Message: /part");
227                         this.type = Type.PART;
228                         this.cmd  = "PART";
229                         this.msg  = arg;
230                         this.line = this.cmd + " :" + arg;
231                 }
232
233                 if (cmd.matches("msg") && arg != null) {
234                         Os.debug("Message: /msg");
235                         this.type = Type.PRIVMSG;
236                         this.cmd  = "PRIVMSG";
237                         this.dst  = arg;
238                         this.msg  = txt;
239                         this.line = this.cmd + " " + arg + " :" + txt;
240                 }
241
242                 // Print warning if command is not recognized
243                 if (this.line == null)
244                         Os.debug("Message: unknown command");
245
246                 return true;
247         }
248
249         private void parseText(String line)
250         {
251                 // Cleanup line
252                 line = line.replaceAll("\\s+",  " ");
253                 line = line.replaceAll("^ | $", "");
254                 this.line = line;
255
256                 // Split line into parts
257                 Matcher mrMsg = ptMsg.matcher(line);
258                 if (mrMsg.matches()) {
259                         this.src  = notnull(mrMsg.group(2));
260                         this.cmd  = notnull(mrMsg.group(4));
261                         this.dst  = notnull(mrMsg.group(6));
262                         this.arg  = notnull(mrMsg.group(8));
263                         this.msg  = notnull(mrMsg.group(10));
264                 }
265
266                 // Determine friendly parts
267                 Matcher mrFrom = ptFrom.matcher(this.src);
268                 if (mrFrom.matches())
269                         this.from = notnull(mrFrom.group(1));
270
271                 Matcher mrTo = ptTo.matcher(this.msg);
272                 if (mrTo.matches())
273                         this.to   = notnull(mrTo.group(2));
274
275                 if (this.to.equals(""))
276                         this.txt  = notnull(this.msg);
277                 else
278                         this.txt  = notnull(mrTo.group(3));
279         }
280
281         private void parseTypes(String name)
282         {
283                 // Parse commands names
284                 if      (this.cmd.equals("PRIVMSG"))       this.type = Type.PRIVMSG;
285                 else if (this.cmd.equals("332"))           this.type = Type.TOPIC;
286                 else if (this.cmd.equals("353"))           this.type = Type.NAMES;
287                 else if (this.cmd.equals("ERROR"))         this.type = Type.ERROR;
288                 else if (this.cmd.equals("CAP"))           this.type = Type.CAP;
289                 else if (this.cmd.equals("AUTHENTICATE"))  this.type = Type.AUTH;
290                 else if (this.cmd.equals("903"))           this.type = Type.AUTHOK;
291                 else if (this.cmd.equals("904"))           this.type = Type.AUTHFAIL;
292                 else if (this.cmd.equals("905"))           this.type = Type.AUTHFAIL;
293                 else if (this.cmd.equals("906"))           this.type = Type.AUTHFAIL;
294                 else if (this.cmd.equals("907"))           this.type = Type.AUTHFAIL;
295
296                 // Set directed
297                 if      (this.dst.equals(name))            this.how  = How.PRIVMSG;
298                 else if (this.to.equals(name))             this.how  = How.DIRECT;
299                 else if (this.msg.contains(name))          this.how  = How.MENTION;
300                 else if (this.type == Type.PRIVMSG)        this.how  = How.CHANNEL;
301         }
302
303         private void parseColors(String msg)
304         {
305                 // Setup regex matching
306                 Matcher match = Message.cPtrn.matcher(msg);
307
308                 // Initialize state variables
309                 int    pos = 0;
310                 Format fmt = new Format();
311                 ArrayList<Format> list = new ArrayList<Format>();
312
313                 // Parse the string
314                 while (match.find()) {
315                         // Push current string
316                         fmt.txt = msg.substring(pos, match.start());
317                         if (fmt.txt.length() > 0)
318                                 list.add(fmt.clone());
319                         pos = match.end();
320
321                         // Abort at end of string
322                         if (match.hitEnd())
323                                 break;
324
325                         // Update format for next string
326                         switch (match.group().charAt(0)) {
327                                 // Format attributes
328                                 case 002: fmt.bold      ^= true; break;
329                                 case 011: fmt.italic    ^= true; break;
330                                 case 023: fmt.strike    ^= true; break;
331                                 case 025: fmt.underline ^= true; break;
332                                 case 037: fmt.underline ^= true; break;
333                                 case 026: fmt.reverse   ^= true; break;
334
335                                 // Reset
336                                 case 017:
337                                           fmt = new Format();
338                                           break;
339
340                                 // Colors
341                                 case 003:
342                                           String fg = match.group(1);
343                                           String bg = match.group(3);
344                                           fmt.fg = Message.getColor(fg);
345                                           fmt.bg = Message.getColor(bg);
346                                           break;
347                         }
348                 }
349
350                 // Cleanup extra space
351                 list.trimToSize();
352                 this.parts = list;
353                 this.msg   = this.msg.replaceAll(cRegex, "");
354                 this.to    = this.to.replaceAll(cRegex, "");
355                 this.txt   = this.txt.replaceAll(cRegex, "");
356         }
357 }