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