dev
Revision | fb6b145f56bc25197c355fac6551a72178e249ff (tree) |
---|---|
Time | 2013-05-06 20:15:29 |
Author | Kimura Youichi <kim.upsilon@bucy...> |
Commiter | Kimura Youichi |
関連発言の取得で related_result の結果だけでなく in_reply_to も辿るように変更
protected な発言で related_result が機能しない問題への対策
@@ -63,5 +63,30 @@ namespace OpenTween | ||
63 | 63 | return Twitter.ThirdPartyStatusUrlRegex.Matches(url).Cast<Match>() |
64 | 64 | .Select(x => x.Groups["StatusId"].Value).ToArray(); |
65 | 65 | } |
66 | + | |
67 | + [Test] | |
68 | + public void FindTopOfReplyChainTest() | |
69 | + { | |
70 | + var posts = new Dictionary<long, PostClass> | |
71 | + { | |
72 | + {950L, new PostClass { StatusId = 950L, InReplyToStatusId = 0L }}, // このツイートが末端 | |
73 | + {987L, new PostClass { StatusId = 987L, InReplyToStatusId = 950L }}, | |
74 | + {999L, new PostClass { StatusId = 999L, InReplyToStatusId = 987L }}, | |
75 | + {1000L, new PostClass { StatusId = 1000L, InReplyToStatusId = 999L }}, | |
76 | + }; | |
77 | + Assert.That(Twitter.FindTopOfReplyChain(posts, 1000L).StatusId, Is.EqualTo(950L)); | |
78 | + Assert.That(Twitter.FindTopOfReplyChain(posts, 950L).StatusId, Is.EqualTo(950L)); | |
79 | + Assert.That(() => Twitter.FindTopOfReplyChain(posts, 500L), Throws.ArgumentException); | |
80 | + | |
81 | + posts = new Dictionary<long, PostClass> | |
82 | + { | |
83 | + // 1200L は posts の中に存在しない | |
84 | + {1210L, new PostClass { StatusId = 1210L, InReplyToStatusId = 1200L }}, | |
85 | + {1220L, new PostClass { StatusId = 1220L, InReplyToStatusId = 1210L }}, | |
86 | + {1230L, new PostClass { StatusId = 1230L, InReplyToStatusId = 1220L }}, | |
87 | + }; | |
88 | + Assert.That(Twitter.FindTopOfReplyChain(posts, 1230L).StatusId, Is.EqualTo(1210L)); | |
89 | + Assert.That(Twitter.FindTopOfReplyChain(posts, 1210L).StatusId, Is.EqualTo(1210L)); | |
90 | + } | |
66 | 91 | } |
67 | 92 | } |
@@ -6,6 +6,7 @@ | ||
6 | 6 | * NEW: Favstarなどサードパーティ製サービスのパーマリンクURLも関連発言表示の対象に追加 |
7 | 7 | * FIX: スペースが含まれているURLをブラウザで開こうとするとURLが分断されて複数のタブが開いてしまう問題を修正 (thx @5px!) |
8 | 8 | * FIX: 画面更新時にInvalidOperationExceptionのエラーが発生する不具合を修正 |
9 | + * FIX: 関連発言表示が非公開アカウントのツイートに対して機能しない問題を修正 | |
9 | 10 | |
10 | 11 | ==== Ver 1.0.9(2013/04/07) |
11 | 12 | * CHG: APIレートリミット関連の実装を修正 |
@@ -2459,27 +2459,30 @@ namespace OpenTween | ||
2459 | 2459 | return CreatePostsFromJson(content, MyCommon.WORKERTYPE.List, tab, read, count, ref tab.OldestId); |
2460 | 2460 | } |
2461 | 2461 | |
2462 | - | |
2463 | - private PostClass CheckReplyToPost(List<PostClass> relPosts) | |
2462 | + /// <summary> | |
2463 | + /// startStatusId からリプライ先の発言を辿る。発言は posts 以外からは検索しない。 | |
2464 | + /// </summary> | |
2465 | + /// <returns>posts の中から検索されたリプライチェインの末端</returns> | |
2466 | + internal static PostClass FindTopOfReplyChain(IDictionary<Int64, PostClass> posts, Int64 startStatusId) | |
2464 | 2467 | { |
2465 | - var tmpPost = relPosts[0]; | |
2466 | - PostClass lastPost = null; | |
2467 | - while (tmpPost != null) | |
2468 | - { | |
2469 | - if (tmpPost.InReplyToStatusId == 0) return null; | |
2470 | - lastPost = tmpPost; | |
2471 | - var replyToPost = from p in relPosts | |
2472 | - where p.StatusId == tmpPost.InReplyToStatusId | |
2473 | - select p; | |
2474 | - tmpPost = replyToPost.FirstOrDefault(); | |
2475 | - } | |
2476 | - return lastPost; | |
2468 | + if (!posts.ContainsKey(startStatusId)) | |
2469 | + throw new ArgumentException("startStatusId (" + startStatusId + ") が posts の中から見つかりませんでした。"); | |
2470 | + | |
2471 | + var nextPost = posts[startStatusId]; | |
2472 | + while (nextPost.InReplyToStatusId != 0) | |
2473 | + { | |
2474 | + if (!posts.ContainsKey(nextPost.InReplyToStatusId)) | |
2475 | + break; | |
2476 | + nextPost = posts[nextPost.InReplyToStatusId]; | |
2477 | + } | |
2478 | + | |
2479 | + return nextPost; | |
2477 | 2480 | } |
2478 | 2481 | |
2479 | 2482 | public string GetRelatedResult(bool read, TabClass tab) |
2480 | 2483 | { |
2481 | 2484 | var rslt = ""; |
2482 | - var relPosts = new List<PostClass>(); | |
2485 | + var relPosts = new Dictionary<Int64, PostClass>(); | |
2483 | 2486 | if (tab.RelationTargetPost.TextFromApi.Contains("@") && tab.RelationTargetPost.InReplyToStatusId == 0) |
2484 | 2487 | { |
2485 | 2488 | //検索結果対応 |
@@ -2495,23 +2498,89 @@ namespace OpenTween | ||
2495 | 2498 | tab.RelationTargetPost = p; |
2496 | 2499 | } |
2497 | 2500 | } |
2498 | - relPosts.Add(tab.RelationTargetPost.Clone()); | |
2499 | - var tmpPost = relPosts[0]; | |
2501 | + relPosts.Add(tab.RelationTargetPost.StatusId, tab.RelationTargetPost.Clone()); | |
2502 | + | |
2503 | + // 一周目: 非公式な related_results API を使用してリプライチェインを辿る | |
2504 | + var nextPost = relPosts[tab.RelationTargetPost.StatusId]; | |
2505 | + var loopCount = 1; | |
2500 | 2506 | do |
2501 | 2507 | { |
2502 | - rslt = this.GetRelatedResultsApi(read, tmpPost, tab, relPosts); | |
2508 | + rslt = this.GetRelatedResultsApi(nextPost, relPosts); | |
2503 | 2509 | if (!string.IsNullOrEmpty(rslt)) break; |
2504 | - tmpPost = CheckReplyToPost(relPosts); | |
2505 | - } while (tmpPost != null); | |
2510 | + nextPost = FindTopOfReplyChain(relPosts, nextPost.StatusId); | |
2511 | + } while (nextPost.InReplyToStatusId != 0 && loopCount++ <= 5); | |
2512 | + | |
2513 | + // 二周目: in_reply_to_status_id を使用してリプライチェインを辿る | |
2514 | + nextPost = FindTopOfReplyChain(relPosts, tab.RelationTargetPost.StatusId); | |
2515 | + loopCount = 1; | |
2516 | + while (nextPost.InReplyToStatusId != 0 && loopCount++ <= 20) | |
2517 | + { | |
2518 | + var inReplyToId = nextPost.InReplyToStatusId; | |
2519 | + | |
2520 | + var inReplyToPost = TabInformations.GetInstance()[inReplyToId]; | |
2521 | + if (inReplyToPost != null) | |
2522 | + { | |
2523 | + inReplyToPost = inReplyToPost.Clone(); | |
2524 | + } | |
2525 | + else | |
2526 | + { | |
2527 | + var errorText = this.GetStatusApi(read, inReplyToId, ref inReplyToPost); | |
2528 | + if (!string.IsNullOrEmpty(errorText)) | |
2529 | + { | |
2530 | + rslt = errorText; | |
2531 | + break; | |
2532 | + } | |
2533 | + } | |
2534 | + | |
2535 | + relPosts.Add(inReplyToPost.StatusId, inReplyToPost); | |
2536 | + | |
2537 | + nextPost = FindTopOfReplyChain(relPosts, nextPost.StatusId); | |
2538 | + } | |
2539 | + | |
2540 | + //MRTとかに対応のためツイート内にあるツイートを指すURLを取り込む | |
2541 | + var text = tab.RelationTargetPost.Text; | |
2542 | + var ma = Twitter.StatusUrlRegex.Matches(text).Cast<Match>() | |
2543 | + .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>()); | |
2544 | + foreach (var _match in ma) | |
2545 | + { | |
2546 | + Int64 _statusId; | |
2547 | + if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId)) | |
2548 | + { | |
2549 | + if (relPosts.ContainsKey(_statusId)) | |
2550 | + continue; | |
2551 | + | |
2552 | + PostClass p = null; | |
2553 | + var _post = TabInformations.GetInstance()[_statusId]; | |
2554 | + if (_post == null) | |
2555 | + { | |
2556 | + this.GetStatusApi(read, _statusId, ref p); | |
2557 | + } | |
2558 | + else | |
2559 | + { | |
2560 | + p = _post.Clone(); | |
2561 | + } | |
2562 | + | |
2563 | + if (p != null) | |
2564 | + relPosts.Add(p.StatusId, p); | |
2565 | + } | |
2566 | + } | |
2567 | + | |
2568 | + relPosts.Values.ToList().ForEach(p => | |
2569 | + { | |
2570 | + if (p.IsMe && !read && this._readOwnPost) | |
2571 | + p.IsRead = true; | |
2572 | + else | |
2573 | + p.IsRead = read; | |
2574 | + | |
2575 | + p.RelTabName = tab.TabName; | |
2576 | + TabInformations.GetInstance().AddPost(p); | |
2577 | + }); | |
2506 | 2578 | |
2507 | - relPosts.ForEach(p => TabInformations.GetInstance().AddPost(p)); | |
2508 | 2579 | return rslt; |
2509 | 2580 | } |
2510 | 2581 | |
2511 | - private string GetRelatedResultsApi(bool read, | |
2512 | - PostClass post, | |
2513 | - TabClass tab, | |
2514 | - List<PostClass> relatedPosts) | |
2582 | + private string GetRelatedResultsApi(PostClass post, | |
2583 | + IDictionary<Int64, PostClass> relatedPosts) | |
2515 | 2584 | { |
2516 | 2585 | if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid) return ""; |
2517 | 2586 |
@@ -2564,100 +2633,18 @@ namespace OpenTween | ||
2564 | 2633 | return "Invalid Json!"; |
2565 | 2634 | } |
2566 | 2635 | |
2567 | - var targetItem = post; | |
2568 | - if (targetItem == null) | |
2569 | - { | |
2570 | - return ""; | |
2571 | - } | |
2572 | - else | |
2573 | - { | |
2574 | - targetItem = targetItem.Clone(); | |
2575 | - } | |
2576 | - targetItem.RelTabName = tab.TabName; | |
2577 | - TabInformations.GetInstance().AddPost(targetItem); | |
2578 | - | |
2579 | - PostClass replyToItem = null; | |
2580 | - var replyToUserName = targetItem.InReplyToUser; | |
2581 | - if (targetItem.InReplyToStatusId > 0 && TabInformations.GetInstance()[targetItem.InReplyToStatusId] != null) | |
2582 | - { | |
2583 | - replyToItem = TabInformations.GetInstance()[targetItem.InReplyToStatusId].Clone(); | |
2584 | - replyToItem.IsRead = read; | |
2585 | - if (replyToItem.IsMe && !read && _readOwnPost) replyToItem.IsRead = true; | |
2586 | - replyToItem.RelTabName = tab.TabName; | |
2587 | - } | |
2588 | - | |
2589 | - var replyAdded = false; | |
2590 | 2636 | foreach (var relatedData in items) |
2591 | 2637 | { |
2592 | 2638 | foreach (var result in relatedData.Results) |
2593 | 2639 | { |
2594 | 2640 | var item = CreatePostsFromStatusData(result.Status); |
2595 | 2641 | if (item == null) continue; |
2596 | - if (targetItem.InReplyToStatusId == item.StatusId) | |
2597 | - { | |
2598 | - replyToItem = null; | |
2599 | - replyAdded = true; | |
2600 | - } | |
2601 | - item.IsRead = read; | |
2602 | - if (item.IsMe && !read && _readOwnPost) item.IsRead = true; | |
2603 | - if (tab != null) item.RelTabName = tab.TabName; | |
2604 | 2642 | //非同期アイコン取得&StatusDictionaryに追加 |
2605 | - relatedPosts.Add(item); | |
2606 | - } | |
2607 | - } | |
2608 | - if (replyToItem != null) | |
2609 | - { | |
2610 | - relatedPosts.Add(replyToItem); | |
2611 | - } | |
2612 | - else if (targetItem.InReplyToStatusId > 0 && !replyAdded) | |
2613 | - { | |
2614 | - PostClass p = null; | |
2615 | - var rslt = ""; | |
2616 | - rslt = GetStatusApi(read, targetItem.InReplyToStatusId, ref p); | |
2617 | - if (string.IsNullOrEmpty(rslt)) | |
2618 | - { | |
2619 | - p.IsRead = read; | |
2620 | - p.RelTabName = tab.TabName; | |
2621 | - relatedPosts.Add(p); | |
2643 | + if (!relatedPosts.ContainsKey(item.StatusId)) | |
2644 | + relatedPosts.Add(item.StatusId, item); | |
2622 | 2645 | } |
2623 | - return rslt; | |
2624 | 2646 | } |
2625 | 2647 | |
2626 | - //発言者・返信先ユーザーの直近10発言取得 | |
2627 | - //var rslt = this.GetUserTimelineApi(read, 10, "", tab); | |
2628 | - //if (!string.IsNullOrEmpty(rslt)) return rslt; | |
2629 | - //if (!string.IsNullOrEmpty(replyToUserName)) | |
2630 | - // rslt = this.GetUserTimelineApi(read, 10, replyToUserName, tab); | |
2631 | - //} | |
2632 | - //return rslt; | |
2633 | - | |
2634 | - //MRTとかに対応のためツイート内にあるツイートを指すURLを取り込む | |
2635 | - var text = tab.RelationTargetPost.Text; | |
2636 | - var ma = Twitter.StatusUrlRegex.Matches(text).Cast<Match>() | |
2637 | - .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>()); | |
2638 | - foreach (var _match in ma) | |
2639 | - { | |
2640 | - Int64 _statusId; | |
2641 | - if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId)) | |
2642 | - { | |
2643 | - PostClass p = null; | |
2644 | - var _post = TabInformations.GetInstance()[_statusId]; | |
2645 | - if (_post == null) | |
2646 | - { | |
2647 | - var rslt = this.GetStatusApi(read, _statusId, ref p); | |
2648 | - } | |
2649 | - else | |
2650 | - { | |
2651 | - p = _post.Clone(); | |
2652 | - } | |
2653 | - if (p != null) | |
2654 | - { | |
2655 | - p.IsRead = read; | |
2656 | - p.RelTabName = tab.TabName; | |
2657 | - relatedPosts.Add(p); | |
2658 | - } | |
2659 | - } | |
2660 | - } | |
2661 | 2648 | return ""; |
2662 | 2649 | } |
2663 | 2650 |