Develop and Download Open Source Software

Browse Subversion Repository

Contents of /LivetubeCommentContainer.cs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 11 - (show annotations) (download)
Mon Mar 28 01:33:42 2011 UTC (13 years, 1 month ago) by aqua877
File size: 19198 byte(s)
・URL短縮機能の追加(bit.ly j.mp is.gd ux.nu p.tl)
・コメントを朗読する際に、同じコメントが読まれたり、読まれる順序が不順だったのを修正
・バグ修正等
1 using System;
2 using System.Collections.Generic;
3 using System.Collections.Specialized;
4 using System.Drawing;
5 using System.Linq;
6 using System.Net;
7 using System.Text;
8 using System.Text.RegularExpressions;
9 using System.Web;
10 using System.Reflection;
11 using System.Concurrency;
12
13 namespace Aqua877.WinApp.IronLivetube
14 {
15 public class LivetubeCommentData
16 {
17 public Image UserThumbnail { get; set; }
18 public int Number { get; set; }
19 public bool ContributorCanBroadcasting { get; set; }
20 public string Name { get; set; }
21 public string ID { get; set; }
22 public string Text { get; set; }
23 public DateTime PostedDate { get; set; }
24 public string Host { get; set; }
25 public bool IsBanned { get; set; }
26
27 public override bool Equals(object obj)
28 {
29 if (!(obj is LivetubeCommentData))
30 {
31 return false;
32 }
33 var compare = (obj as LivetubeCommentData);
34 return ((compare.Number == this.Number) && (compare.Name == this.Name) && (compare.Text == this.Text));
35 }
36
37 public override int GetHashCode()
38 {
39 return base.GetHashCode();
40 }
41 }
42
43 public class LivetubeCommentReader
44 {
45 public LivetubeInformation LiveInfo;
46 public List<LivetubeCommentData> CommentContainer { get; set; }
47 public bool IsReading = false;
48 public bool IsTracingIp = false;
49 public event Action<bool> OnLoaded;
50 public event Action<IEnumerable<LivetubeCommentData>> OnCaughtComment;
51 public event Action<bool, Exception> OnPostCommentFinished;
52 public event Action<int, bool, Exception> OnBanCommentFinished;
53 public event Action<int, bool, Exception> OnUnBanCommentFinished;
54 public event Action<string> OnStatusTextChanged;
55 public event Action<Exception, bool> OnExceptionThrown;
56 public event Action<bool, Exception> OnLoginFinished;
57 public event Action<IEnumerable<int>, IEnumerable<int>> OnBannedCommentsListRefreshed;
58 public event Action<int, string> OnGetHostFinished;
59
60 private string StreamId;
61 private WebClient CommentReader;
62
63 public void LoadAsync(Uri url)
64 {
65 this.NotifyStatusTextChanged("StreamID取得開始");
66
67 using (var streamIdGetter = new WebClient { Encoding = Encoding.UTF8 })
68 {
69 streamIdGetter.DownloadStringCompleted += this.OnGetStreamIdFinished;
70 streamIdGetter.DownloadStringAsync(url);
71 }
72 }
73
74 private void OnGetStreamIdFinished(object arg, DownloadStringCompletedEventArgs e)
75 {
76 if (e == null) throw new ArgumentNullException("e");
77 if (e.Error != null)
78 {
79 this.NotifyLoaded(false);
80 this.NotifyStatusTextChanged("StreamID取得失敗");
81 this.NotifyExceptionThrown(e.Error, true);
82
83 return;
84 }
85
86 this.NotifyStatusTextChanged("StreamID取得成功");
87
88 string data = e.Result;
89 string streamID = Regex.Match(data, GlobalValues.GetStreamIDPattern).Groups[1].Value;
90
91 if (streamID == "")
92 {
93 this.NotifyLoaded(false);
94 return;
95 }
96
97 this.NotifyStatusTextChanged("コメント取得準備中...");
98 this.LoadAsync(streamID);
99 }
100
101 public void LoadAsync(string streamId)
102 {
103 this.IsReading = true;
104 this.StreamId = streamId;
105 this.CommentContainer = new List<LivetubeCommentData>();
106 this.CommentReader = new WebClient { Encoding = Encoding.UTF8 };
107
108 this.CommentReader.DownloadStringCompleted += this.ParseComment;
109 this.CommentReader.DownloadProgressChanged += this.CommentReaderProgressChanged;
110
111 string url = String.Format("http://livetube.cc/stream/{0}.comments", streamId);
112 if (GlobalValues.Setting.IsSelectServer)
113 {
114 switch (GlobalValues.Setting.SelectedServer)
115 {
116 case LivetubeServers.Default: url.Insert(0, ""); break;
117 case LivetubeServers.H: url.Insert(0, "h."); break;
118 case LivetubeServers.Large03: url.Insert(0, "large03."); break;
119 case LivetubeServers.Large04: url.Insert(0, "large04."); break;
120 case LivetubeServers.Large05: url.Insert(0, "large05."); break;
121 case LivetubeServers.Tes01: url.Insert(0, "tes01."); break;
122 default: break;
123 }
124 }
125 else
126 {
127 //自動的なサーバー選択
128 }
129
130 this.CommentReader.DownloadStringAsync(new Uri(url));
131 }
132
133 private void CommentReaderProgressChanged(object sender, DownloadProgressChangedEventArgs e)
134 {
135 this.NotifyStatusTextChanged(String.Format("取得中...({0}%:{1}KB/{2}KB)", e.ProgressPercentage, e.BytesReceived, (e.TotalBytesToReceive == -1) ? "?" : e.TotalBytesToReceive.ToString()));
136 }
137
138 private void ParseComment(object sender, DownloadStringCompletedEventArgs e)
139 {
140 if (e.Error != null)
141 {
142 //エラー処理
143 if (e.Error is WebException)
144 {
145 if ((e.Error as WebException).Status == WebExceptionStatus.ProtocolError)
146 {
147 var response = (e.Error as WebException).Response as HttpWebResponse;
148
149 switch (response.StatusCode)
150 {
151 //続行不可能なエラーの場合はエラーを通知してコメントのロードを中止
152 case HttpStatusCode.NotFound:
153 case HttpStatusCode.Forbidden:
154 {
155 if (this.CommentContainer.Count == 0)
156 {
157 this.NotifyLoaded(false);
158 }
159 this.NotifyExceptionThrown(e.Error, true);
160 this.StopLoad();
161 return;
162 }
163
164 default:
165 {
166 this.NotifyExceptionThrown(e.Error, false);
167 break;
168 }
169 }
170 }
171 }
172 else
173 {
174 this.NotifyExceptionThrown(e.Error, false);
175 }
176 }
177 else
178 {
179 if (this.CommentContainer.Count == 0)
180 {
181 this.NotifyLoaded(true);
182 }
183
184 //コメントの追加処理
185 var extractCommentPattern = new Regex(GlobalValues.ExtractCommentDataPattern);
186 var extractedComments = extractCommentPattern.Matches(e.Result);
187
188 //正規表現で抜き出したコメントをもとにデータを生成
189 var additionalComments = extractedComments.Cast<Match>().Select(
190 extractedText =>
191 {
192 var newComment = new LivetubeCommentData
193 {
194 Number = int.Parse(extractedText.Groups[7].Value),
195 Name =
196 HttpUtility.HtmlDecode(
197 string.IsNullOrEmpty(extractedText.Groups[13].Value)
198 ? (string.IsNullOrEmpty(extractedText.Groups[10].Value))
199 ? ""
200 : extractedText.Groups[10].Value
201 : extractedText.Groups[13].Value
202 ),
203 //改行をスペースに
204 Text = HttpUtility.HtmlDecode(extractedText.Groups[15].Value.Replace("<br/>", " ")),
205 PostedDate = DateTime.ParseExact(extractedText.Groups[14].Value, "M/d H:m:s", null),
206 ID = extractedText.Groups[2].Value,
207 ContributorCanBroadcasting = extractedText.Groups[9].Value.Length != 0
208 };
209
210 //URLをエスケープ
211 newComment.Text = Regex.Replace(newComment.Text, @"<a href=""(.*?)"" target=""_blank"">(.*?)</a>", @"$1");
212 return newComment;
213 }
214 );
215
216 //重複削除
217 additionalComments = additionalComments.Distinct(c => c.Number);
218
219 if (additionalComments.Any())
220 {
221 if (additionalComments.Count() == 1 && this.IsReading)
222 {
223 this.NotifyStatusTextChanged(String.Format("コメント抽出完了({0})", this.CommentContainer.Count + additionalComments.Count()));
224 }
225 else
226 {
227 this.NotifyStatusTextChanged(String.Format("コメント抽出完了({0}~{1})", this.CommentContainer.Count + 1, this.CommentContainer.Count + additionalComments.Count()));
228 }
229
230 this.CommentContainer.AddRange(additionalComments);
231 this.NotifyCaughtComment(additionalComments);
232
233 //IP tracing
234 this.TraceIpAddressAsync(additionalComments.Select(d => d.Number));
235 }
236
237 //BANされたコメントの一覧を更新
238 var webRes = this.CommentReader.GetType().InvokeMember("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, this.CommentReader, null) as HttpWebResponse;
239 string responseJson = webRes.Headers["X-JSON"];
240
241 Codeplex.Data.DynamicJson banList = Codeplex.Data.DynamicJson.Parse(responseJson).cc;
242 this.CommentContainer
243 .Where(s => s.IsBanned)
244 .Select(s => s.Number)
245 .Let(recentBannedComments =>
246 {
247 var currentBannedComments = banList.Deserialize<List<int>>();
248
249 //BAN解除されたコメント
250 var unBannedComments = recentBannedComments.Except(currentBannedComments).Do(
251 i => this.CommentContainer[i].IsBanned = false
252 );
253
254 //新規BANされたコメント
255 var bannedComments = currentBannedComments.Except(recentBannedComments).Do(
256 i => this.CommentContainer[i].IsBanned = true
257 );
258
259 this.NotifyBannedCommentsListFinished(unBannedComments, bannedComments);
260 return recentBannedComments;
261 });
262 }
263
264 //再びコネクションを張る
265 if (this.IsReading)
266 {
267 this.CommentReader.DownloadStringAsync(new Uri(String.Format("http://livetube.cc/stream/{0}.comments.{1}", this.StreamId, this.CommentContainer.Count)));
268 }
269 }
270
271 public void RefreshConnection()
272 {
273 if (this.CommentReader.IsBusy)
274 {
275 this.CommentReader.CancelAsync();
276 }
277
278 this.CommentReader.DownloadStringAsync(new Uri(String.Format("http://livetube.cc/stream/{0}.comments.{1}", this.StreamId, this.CommentContainer.Count)));
279 }
280
281 //コメント読み込み停止
282 public void StopLoad()
283 {
284 if (this.IsReading)
285 {
286 //後片付け
287 this.IsReading = false;
288 this.StreamId = "";
289
290 if (this.CommentReader.IsBusy)
291 {
292 this.CommentReader.CancelAsync();
293 }
294
295 this.CommentReader.Dispose();
296 this.CommentContainer.Clear();
297 }
298 }
299
300 public bool BanCommentAsync(IEnumerable<int> id)
301 {
302 if (GlobalValues.Setting.CredentialData.Length == 0)
303 {
304 return false;
305 }
306
307 if (!(id.Count() > 0))
308 {
309 return false;
310 }
311
312 var client = new WebClient() { Encoding = Encoding.UTF8 };
313
314 client.DownloadStringCompleted += this.BanCommentCallBack;
315 client.Headers[HttpRequestHeader.Cookie] = string.Join(";", GlobalValues.Setting.CredentialData);
316
317 id.ForEach(
318 i => client.DownloadStringAsync(new Uri(String.Format("http://livetube.cc/stream/{0}.moder.{1}.-1", this.StreamId, id)), new { i, client })
319 );
320 return true;
321 }
322
323 private void BanCommentCallBack(object sender, DownloadStringCompletedEventArgs e)
324 {
325 dynamic arg = e.UserState;
326
327 if (e.Error != null)
328 {
329 this.NotifyBanCommentFinished(arg.i, false, e.Error);
330 }
331 else
332 {
333 string result = e.Result;
334
335 if (result == "alert( 'マークしました(3)');")
336 {
337 this.CommentContainer[arg.i].IsBanned = true;
338 this.NotifyBanCommentFinished(arg.i, true, null);
339 }
340 else
341 {
342 this.NotifyBanCommentFinished(arg.i, false, new Exception("コメントのBANに失敗しました(ID : " + arg.i));
343 }
344 }
345
346 arg.client.Dispose();
347 }
348
349 public bool UnBanCommentAsync(IEnumerable<int> id)
350 {
351 if (GlobalValues.Setting.CredentialData.Length == 0)
352 {
353 return false;
354 }
355
356 if (!(id.Count() > 0))
357 {
358 return false;
359 }
360
361 var client = new WebClient { Encoding = Encoding.UTF8 };
362
363 client.DownloadStringCompleted += this.BanCommentCallBack;
364 client.Headers[HttpRequestHeader.Cookie] = string.Join(";", GlobalValues.Setting.CredentialData);
365
366 id.ForEach(
367 i => client.DownloadStringAsync(new Uri(String.Format("http://livetube.cc/stream/{0}.moder.{1}.1", this.StreamId, id)), new { i, client })
368 );
369 return true;
370 }
371
372 private void UnBanCommentCallBack(object sender, DownloadStringCompletedEventArgs e)
373 {
374 dynamic arg = e.UserState;
375
376 if (e.Error != null)
377 {
378 this.NotifyBanCommentFinished(arg.i, false, e.Error);
379 }
380 else
381 {
382 string result = e.Result;
383
384 if (result == "alert( 'マークしました(0)');")
385 {
386
387 this.CommentContainer[arg.i].IsBanned = false;
388 this.NotifyBanCommentFinished(arg.i, true, null);
389 }
390 else
391 {
392 this.NotifyBanCommentFinished(arg.i, false, new Exception("コメントのBAN解除に失敗しました(ID : " + arg.i + ")"));
393 }
394 }
395
396 arg.client.Dispose();
397 }
398
399 public void LoginAsync(string userId, string password)
400 {
401 //認証済みなら再度認証は行わない
402 if (GlobalValues.Setting.CredentialData.Length != 0)
403 {
404 return;
405 }
406
407 var client = new WebClient { Encoding = Encoding.UTF8 };
408
409 client.UploadValuesCompleted += this.LoginCallBack;
410 client.UploadValuesAsync(new Uri("http://livetube.cc/login"), "POST", new NameValueCollection
411 {
412 { "user", userId },
413 { "password", password }
414 }
415 , new { userID = userId, client });
416 }
417
418 private void LoginCallBack(object sender, UploadValuesCompletedEventArgs e)
419 {
420 dynamic arg = e.UserState;
421
422 if (e.Error != null)
423 {
424 this.NotifyLoginFinished(false, e.Error);
425 }
426 else
427 {
428 string result = Encoding.UTF8.GetString(e.Result);
429
430
431 if (result.Contains("パスワードが違うようです。再度の入力をお願いします"))
432 {
433 this.NotifyLoginFinished(false, new Exception("ユーザーIDとパスワードが一致しません"));
434 }
435 else if (result.Contains("ユーザが見つかりません。"))
436 {
437 this.NotifyLoginFinished(false, new Exception("ユーザーIDが存在しません"));
438 }
439 else
440 {
441 if (Regex.Match(result, "<h5><span id=\"u\">(.*?)</span></h5>").Groups[1].Value != arg.userID)
442 {
443 this.NotifyLoginFinished(false, new Exception("ログインに失敗しました"));
444 }
445 else
446 {
447 string setCookie = arg.client.ResponseHeaders[HttpResponseHeader.SetCookie];
448 GlobalValues.Setting.CredentialData = Regex.Split(setCookie, "(?<!expires=.{3}),")
449 .Select(s => s.Split(';').First().Split('='))
450 .Select(xs => new { Name = xs.First(), Value = string.Join("=", xs.Skip(1).ToArray()) })
451 .Select(a => a.Name + "=" + a.Value)
452 .ToArray();
453
454 this.NotifyLoginFinished(true, null);
455 }
456 }
457 }
458
459 arg.client.Dispose();
460 }
461
462 public void Logout()
463 {
464 GlobalValues.Setting.LivetubeUserID = "";
465 GlobalValues.Setting.LivetubePassword = "";
466 GlobalValues.Setting.CredentialData = new string[] { };
467 }
468
469 public void PostCommentAsync(string text, string name)
470 {
471 var client = new WebClient { Encoding = Encoding.UTF8 };
472 client.Headers[HttpRequestHeader.Cookie] = string.Join(";", GlobalValues.Setting.CredentialData);
473 client.UploadValuesCompleted += this.PostCommentCallBack;
474 client.UploadValuesAsync(new Uri(String.Format("http://livetube.cc/stream/{0}.comments", this.StreamId)), "POST", new NameValueCollection
475 {
476 { "name", name },
477 { "c", text }
478 }
479 ,client);
480 }
481
482 private void PostCommentCallBack(object sender, UploadValuesCompletedEventArgs e)
483 {
484 //403 Forbidden Invokes.
485 if (e.Error != null)
486 {
487 this.NotifyPostCommentFinished(false, e.Error);
488 }
489
490 string result = Encoding.UTF8.GetString(e.Result);
491
492 if (!Regex.IsMatch(result, GlobalValues.ExtractCommentDataPattern))
493 {
494 if (result == "alert( '現在コメントを書き込むことができません。');")
495 {
496 this.NotifyPostCommentFinished(false, new Exception("配信者にBANされているためコメント出来ません。"));
497 }
498 else
499 {
500 //this.NotifyPostCommentFinished(false, new Exception("コメント投稿に失敗した可能性があります"));
501 this.NotifyPostCommentFinished(true, null);
502 }
503 }
504 else
505 {
506 this.NotifyPostCommentFinished(true, null);
507 }
508
509 (e.UserState as WebClient).Dispose();
510 }
511
512 private void TraceIpAddressAsync(IEnumerable<int> id)
513 {
514 this.NotifyStatusTextChanged(String.Format("書き込み元のトレース中...({0}~{1})", id.First() + 1, id.Last() + 1));
515
516 var order = new Queue<int>(id);
517 Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(3))
518 .TakeWhile(_ => order.Any())
519 .Select(_ => order.Dequeue())
520 .ObserveOnDispatcher()
521 .Subscribe(s => this.TraceIpAddressAsync(s));
522 }
523
524 private void TraceIpAddressAsync(int id)
525 {
526 if (this.LiveInfo.IsLoading || this.LiveInfo.Tags.Contains("traceip"))
527 {
528 var connector = new WebClient() { Encoding = Encoding.UTF8 };
529 connector.DownloadStringCompleted += (s, e) =>
530 {
531 if (e.Error != null)
532 {
533 return;
534 }
535
536 if (Regex.IsMatch(e.Result, "alert\\( '(.*?):\\\\r\\\\n記録されていません\\(not recorded\\)'\\);"))
537 {
538 return;
539 }
540
541 var host = Regex.Match(e.Result, "alert\\( '(?:.*?):\\\\r\\\\nIPアドレス : (.*?)\\((.*?)\\)'\\);").Groups[1].Value;
542 this.CommentContainer[id].Host = host;
543 this.NotifyStatusTextChanged(String.Format("書き込み元のトレース完了({0})", id + 1));
544 this.NotifyGetHostFinished(id, host);
545 };
546
547 connector.DownloadStringAsync(new Uri("http://livetube.cc/stream/" + this.StreamId + ".traceaddr." + id));
548 }
549 }
550
551 protected virtual void NotifyLoaded(bool success)
552 {
553 if (this.OnLoaded != null)
554 {
555 this.OnLoaded(success);
556 }
557 }
558
559 protected virtual void NotifyCaughtComment(IEnumerable<LivetubeCommentData> additionalComments)
560 {
561 if (this.OnCaughtComment != null)
562 {
563 this.OnCaughtComment(additionalComments);
564 }
565 }
566
567 protected virtual void NotifyBanCommentFinished(int id, bool success, Exception error)
568 {
569 if (this.OnBanCommentFinished != null)
570 {
571 this.OnBanCommentFinished(id, success, error);
572 }
573 }
574
575 protected virtual void NotifyUnBanCommentFinished(int id, bool success, Exception error)
576 {
577 if (this.OnUnBanCommentFinished != null)
578 {
579 this.OnUnBanCommentFinished(id, success, error);
580 }
581 }
582
583 protected virtual void NotifyStatusTextChanged(string text)
584 {
585 if (this.OnStatusTextChanged != null)
586 {
587 this.OnStatusTextChanged(text);
588 }
589 }
590
591 protected virtual void NotifyExceptionThrown(Exception exception, bool isCritical)
592 {
593 if (this.OnExceptionThrown != null)
594 {
595 this.OnExceptionThrown(exception, isCritical);
596 }
597 }
598
599 protected virtual void NotifyLoginFinished(bool success, Exception reasonOfError)
600 {
601 if (this.OnLoginFinished != null)
602 {
603 this.OnLoginFinished(success, reasonOfError);
604 }
605 }
606
607 protected virtual void NotifyPostCommentFinished(bool success, Exception reasonOfError)
608 {
609 if (this.OnPostCommentFinished != null)
610 {
611 this.OnPostCommentFinished(success, reasonOfError);
612 }
613 }
614
615 protected virtual void NotifyBannedCommentsListFinished(IEnumerable<int> unBannedComments, IEnumerable<int> bannedComments)
616 {
617 if (this.OnBannedCommentsListRefreshed != null)
618 {
619 this.OnBannedCommentsListRefreshed(unBannedComments, bannedComments);
620 }
621 }
622
623 protected virtual void NotifyGetHostFinished(int id, string host)
624 {
625 if (this.OnGetHostFinished != null)
626 {
627 this.OnGetHostFinished(id, host);
628 }
629 }
630 }
631 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26