1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
\r
3 // Licensed under the Apache License, Version 2.0 (the "License");
\r
4 // you may not use this file except in compliance with the License.
\r
5 // You may obtain a copy of the License at
\r
7 // http://www.apache.org/licenses/LICENSE-2.0
\r
9 // Unless required by applicable law or agreed to in writing, software
\r
10 // distributed under the License is distributed on an "AS IS" BASIS,
\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
12 // See the License for the specific language governing permissions and
\r
13 // limitations under the License.
\r
16 using System.Collections.Generic;
\r
17 using System.Globalization;
\r
20 using System.Net.Sockets;
\r
22 using System.Text.RegularExpressions;
\r
24 namespace KancolleSniffer
\r
26 public class LogServer
\r
28 private static readonly string IndexDir = AppDomain.CurrentDomain.BaseDirectory;
\r
29 private static string _outputDir = AppDomain.CurrentDomain.BaseDirectory;
\r
31 public static string OutputDir
\r
33 set => _outputDir = value;
\r
36 public static MaterialCount[] MaterialHistory { private get; set; }
\r
38 public static void Process(Socket client, string requestLine)
\r
40 var from = DateTime.MinValue;
\r
41 var to = DateTime.MaxValue;
\r
42 var timestamp = false;
\r
44 var request = requestLine.Split(' ');
\r
45 if (request.Length != 3)
\r
47 SendError(client, "400 Bad Request");
\r
50 if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))
\r
52 SendError(client, "501 Not Implemented");
\r
55 var tmp = request[1].Split('?');
\r
56 var path = HttpUtility.UrlDecode(tmp[0]);
\r
57 if (path == null || !path.StartsWith("/"))
\r
59 SendError(client, "400 Bad Request");
\r
62 if (tmp.Length == 2)
\r
64 var query = HttpUtility.ParseQueryString(tmp[1]);
\r
65 if (query["from"] != null)
\r
67 double.TryParse(query["from"], out var tick);
\r
68 from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);
\r
70 if (query["to"] != null)
\r
72 double.TryParse(query["to"], out var tick);
\r
73 to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);
\r
75 if (query["number"] != null)
\r
76 timestamp = query["number"] == "true";
\r
79 path = path == "/" ? "index.html" : path.Substring(1);
\r
80 var full = Path.Combine(IndexDir, path);
\r
81 var csv = Path.Combine(_outputDir, path);
\r
82 if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))
\r
84 SendFile(client, full, "text/html");
\r
87 if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))
\r
89 SendFile(client, csv, "text/csv; charset=Shift_JIS");
\r
92 if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
\r
94 SendJsonData(client, csv, from, to, timestamp);
\r
97 if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))
\r
99 SendFile(client, full, "application/javascript");
\r
102 if (path.EndsWith("proxy.pac"))
\r
104 SendProxyPac(client, HttpProxy.LocalPort);
\r
107 SendError(client, "404 Not Found");
\r
110 private static void SendError(Socket client, string error)
\r
112 using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
114 writer.Write("HTTP/1.1 {0}\r\n", error);
\r
115 writer.Write("Server: KancolleSniffer\r\n");
\r
116 writer.Write("Date: {0:R}\r\n", DateTime.Now);
\r
117 writer.Write("Connection: close\r\n\r\n");
\r
118 writer.Write("<html><head><title>{0}</title></head>\r\n", error);
\r
119 writer.Write("<body><h4>{0}</h4></body></html>\r\n\r\n", error);
\r
121 client.Send(((MemoryStream)writer.BaseStream).ToArray());
\r
125 private static void SendJsonData(Socket client, string path, DateTime from, DateTime to, bool number)
\r
127 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
129 header.Write("HTTP/1.1 200 OK\r\n");
\r
130 header.Write("Server: KancolleSniffer\r\n");
\r
131 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
132 header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");
\r
133 header.Write("Connection: close\r\n\r\n");
\r
135 client.Send(((MemoryStream)header.BaseStream).ToArray());
\r
137 var csv = path.Replace(".json", ".csv");
\r
138 var encoding = Encoding.GetEncoding("Shift_JIS");
\r
139 client.Send(encoding.GetBytes("{ \"data\": [\n"));
\r
140 var battle = false;
\r
141 var material = false;
\r
144 if (!File.Exists(csv))
\r
147 if (path.EndsWith("遠征報告書.json"))
\r
151 else if (path.EndsWith("改修報告書.json"))
\r
155 else if (path.EndsWith("海戦・ドロップ報告書.json"))
\r
160 else if (path.EndsWith("開発報告書.json"))
\r
164 else if (path.EndsWith("建造報告書.json"))
\r
168 else if (path.EndsWith("資材ログ.json"))
\r
173 else if (path.EndsWith("戦果.json"))
\r
177 var delimiter = "";
\r
178 foreach (var line in File.ReadLines(csv, encoding).Skip(1))
\r
180 var data = line.Split(',');
\r
181 if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,
\r
182 DateTimeStyles.AssumeLocal, out DateTime date))
\r
184 if (DateTime.TryParse(data[0], CultureInfo.CurrentCulture,
\r
185 DateTimeStyles.AssumeLocal, out date))
\r
187 data[0] = date.ToString(Logger.DateTimeFormat);
\r
194 if (date < from || to < date)
\r
196 IEnumerable<string> entries = data;
\r
198 entries = data.Take(9);
\r
200 entries = BattleLogProcessor.Process(data);
\r
201 if (entries.Count() != records)
\r
205 var stamp = ((date.ToUniversalTime().Ticks -
\r
206 new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks) /
\r
207 TimeSpan.TicksPerMillisecond).ToString();
\r
208 client.Send(encoding.GetBytes(delimiter + "[" + stamp + "," +
\r
209 string.Join(",", entries.Skip(1)) + "]"));
\r
213 client.Send(encoding.GetBytes(delimiter + "[\"" +
\r
214 string.Join("\",\"", entries) + "\"]"));
\r
218 if (material && !number)
\r
220 client.Send(encoding.GetBytes(delimiter + "[\"" +
\r
221 string.Join("\",\"", GetCurrentMaterialRecord()) + "\"]"));
\r
226 client.Send(encoding.GetBytes("]}\n"));
\r
230 private static IEnumerable<string> GetCurrentMaterialRecord()
\r
232 return new[] {DateTime.Now.ToString(Logger.DateTimeFormat)}.
\r
233 Concat(MaterialHistory.Select(c => c.Now.ToString()));
\r
236 private static void SendFile(Socket client, string path, string mime)
\r
238 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
240 header.Write("HTTP/1.1 200 OK\r\n");
\r
241 header.Write("Server: KancolleSniffer\r\n");
\r
242 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
243 header.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);
\r
244 header.Write("Content-Type: {0}\r\n", mime);
\r
245 header.Write("Connection: close\r\n\r\n");
\r
247 client.SendFile(path, ((MemoryStream)header.BaseStream).ToArray(), null,
\r
248 TransmitFileOptions.UseDefaultWorkerThread);
\r
252 private static void SendProxyPac(Socket client, int port)
\r
254 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
256 header.Write("HTTP/1.1 200 OK\r\n");
\r
257 header.Write("Server: KancolleSniffer\r\n");
\r
258 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
259 header.Write("Content-Type: application/x-ns-proxy-autoconfig\r\n");
\r
260 header.Write("Connection: close\r\n\r\n");
\r
262 client.Send(((MemoryStream)header.BaseStream).ToArray());
\r
265 function FindProxyForURL(url, host) {
\r
266 if(isInNet(host, ""203.104.209.71"", ""255.255.255.255"") ||
\r
267 isInNet(host, ""203.104.209.87"", ""255.255.255.255"") ||
\r
268 isInNet(host, ""125.6.184.16"", ""255.255.255.255"") ||
\r
269 isInNet(host, ""125.6.187.205"", ""255.255.255.255"") ||
\r
270 isInNet(host, ""125.6.187.229"", ""255.255.255.255"") ||
\r
271 isInNet(host, ""203.104.209.134"", ""255.255.255.255"") ||
\r
272 isInNet(host, ""203.104.209.167"", ""255.255.255.255"") ||
\r
273 isInNet(host, ""203.104.248.135"", ""255.255.255.255"") ||
\r
274 isInNet(host, ""125.6.189.7"", ""255.255.255.255"") ||
\r
275 isInNet(host, ""125.6.189.39"", ""255.255.255.255"") ||
\r
276 isInNet(host, ""125.6.189.71"", ""255.255.255.255"") ||
\r
277 isInNet(host, ""125.6.189.103"", ""255.255.255.255"") ||
\r
278 isInNet(host, ""125.6.189.135"", ""255.255.255.255"") ||
\r
279 isInNet(host, ""125.6.189.167"", ""255.255.255.255"") ||
\r
280 isInNet(host, ""125.6.189.215"", ""255.255.255.255"") ||
\r
281 isInNet(host, ""125.6.189.247"", ""255.255.255.255"") ||
\r
282 isInNet(host, ""203.104.209.23"", ""255.255.255.255"") ||
\r
283 isInNet(host, ""203.104.209.39"", ""255.255.255.255"") ||
\r
284 isInNet(host, ""203.104.209.55"", ""255.255.255.255"") ||
\r
285 isInNet(host, ""203.104.209.102"", ""255.255.255.255"")) {
\r
286 return ""PROXY 127.0.0.1:8080"";
\r
291 }".Replace("8080", port.ToString());
\r
292 client.Send(Encoding.ASCII.GetBytes(pacFile));
\r
296 public static class BattleLogProcessor
\r
298 public static IEnumerable<string> Process(string[] data)
\r
300 if (data.Length == 35)
\r
301 data = data.Concat(Enumerable.Repeat("", 3)).ToArray();
\r
302 if (data.Length == 40)
\r
304 data = data.Take(21).Concat(new[] {data[21] + "・" + data[23], data[22] + "・" + data[24]})
\r
305 .Concat(data.Skip(25)).ToArray();
\r
307 else if (data.Length != 38)
\r
311 if (data[5] == "T字戦(有利)")
\r
313 if (data[5] == "T字戦(不利)")
\r
315 if (data[6].EndsWith("航行序列"))
\r
316 data[6] = data[6].Substring(0, 4);
\r
317 if (data[7].EndsWith("航行序列"))
\r
318 data[7] = data[7].Substring(0, 4);
\r
319 data[37] = ShortenAirBattleResult(data[37]);
\r
320 return AddDamagedShip(data);
\r
323 private static string ShortenAirBattleResult(string result)
\r
342 private static IEnumerable<string> AddDamagedShip(string[] data)
\r
344 var damaged = new List<string>();
\r
345 for (var i = 11; i < 11 + 12; i += 2)
\r
349 var ship = data[i] = StripKana(data[i]);
\r
350 var hp = data[i + 1];
\r
353 if (ship.Contains("・"))
\r
355 var ships = ship.Split('・');
\r
356 var hps = hp.Split('・');
\r
357 var nowMax = hps[0].Split('/').Select(int.Parse).ToArray();
\r
358 if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)
\r
359 damaged.Add(ships[0]);
\r
360 nowMax = hps[1].Split('/').Select(int.Parse).ToArray();
\r
361 if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)
\r
362 damaged.Add(ships[1]);
\r
366 var nowMax = hp.Split('/').Select(int.Parse).ToArray();
\r
367 if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)
\r
371 catch (FormatException)
\r
376 return data.Take(23).Concat(new[] { string.Join("・", damaged) }).Concat(data.Skip(23));
\r
379 private static readonly Regex Kana = new Regex(@"\([^)]+\)\(", RegexOptions.Compiled);
\r
381 private static string StripKana(string name)
\r
383 return Kana.Replace(name, "(");
\r