public TwitterConfiguration Configuration { get; private set; }
public TwitterTextConfiguration TextConfiguration { get; private set; }
+ public bool GetFollowersSuccess { get; private set; } = false;
+ public bool GetNoRetweetSuccess { get; private set; } = false;
+
delegate void GetIconImageDelegate(PostClass post);
private readonly object LockObj = new object();
private ISet<long> followerId = new HashSet<long>();
- private bool _GetFollowerResult = false;
private long[] noRTId = new long[0];
- private bool _GetNoRetweetResult = false;
//プロパティからアクセスされる共通情報
private string _uname;
-
- private bool _readOwnPost;
private List<string> _hashList = new List<string>();
//max_idで古い発言を取得するために保持(lists分は個別タブで管理)
}
public TwitterApiAccessLevel AccessLevel
- {
- get
- {
- return MyCommon.TwitterApiInfo.AccessLevel;
- }
- }
+ => MyCommon.TwitterApiInfo.AccessLevel;
protected void ResetApiStatus()
- {
- MyCommon.TwitterApiInfo.Reset();
- }
+ => MyCommon.TwitterApiInfo.Reset();
public void ClearAuthInfo()
{
}
this.ResetApiStatus();
this.Api.Initialize(token, tokenSecret, userId, username);
- _uname = username.ToLowerInvariant();
+ _uname = username;
if (SettingManager.Common.UserstreamStartup) this.ReconnectUserStream();
}
- public string PreProcessUrl(string orgData)
+ internal static string PreProcessUrl(string orgData)
{
int posl1;
var posl2 = 0;
post.IsRead = read;
post.IsOwl = false;
- if (_readOwnPost) post.IsRead = true;
+ if (this.ReadOwnPost) post.IsRead = true;
post.IsDm = false;
TabInformations.GetInstance().AddPost(post);
public long UserId
=> this.Api.CurrentUserId;
- private static MyCommon.ACCOUNT_STATE _accountState = MyCommon.ACCOUNT_STATE.Valid;
- public static MyCommon.ACCOUNT_STATE AccountState
- {
- get
- {
- return _accountState;
- }
- set
- {
- _accountState = value;
- }
- }
-
+ public static MyCommon.ACCOUNT_STATE AccountState { get; set; } = MyCommon.ACCOUNT_STATE.Valid;
public bool RestrictFavCheck { get; set; }
-
- public bool ReadOwnPost
- {
- get
- {
- return _readOwnPost;
- }
- set
- {
- _readOwnPost = value;
- }
- }
+ public bool ReadOwnPost { get; set; }
public int FollowersCount { get; private set; }
public int FriendsCount { get; private set; }
/// 渡された取得件数がWORKERTYPEに応じた取得可能範囲に収まっているか検証する
/// </summary>
public static bool VerifyApiResultCount(MyCommon.WORKERTYPE type, int count)
- {
- return count >= 20 && count <= GetMaxApiResultCount(type);
- }
+ => count >= 20 && count <= GetMaxApiResultCount(type);
/// <summary>
/// 渡された取得件数が更新時の取得可能範囲に収まっているか検証する
/// </summary>
public static bool VerifyMoreApiResultCount(int count)
- {
- return count >= 20 && count <= 200;
- }
+ => count >= 20 && count <= 200;
/// <summary>
/// 渡された取得件数が起動時の取得可能範囲に収まっているか検証する
/// </summary>
public static bool VerifyFirstApiResultCount(int count)
- {
- return count >= 20 && count <= 200;
- }
+ => count >= 20 && count <= 200;
/// <summary>
/// WORKERTYPEに応じた取得可能な最大件数を取得する
var item = CreatePostsFromStatusData(status);
item.IsRead = read;
- if (item.IsMe && !read && _readOwnPost) item.IsRead = true;
+ if (item.IsMe && !read && this.ReadOwnPost) item.IsRead = true;
return item;
}
}
private PostClass CreatePostsFromStatusData(TwitterStatus status)
- {
- return CreatePostsFromStatusData(status, false);
- }
+ => this.CreatePostsFromStatusData(status, false);
private PostClass CreatePostsFromStatusData(TwitterStatus status, bool favTweet)
{
{
post.RetweetedBy = status.User.ScreenName;
post.RetweetedByUserId = status.User.Id;
- post.IsMe = post.RetweetedBy.ToLowerInvariant().Equals(_uname);
+ post.IsMe = post.RetweetedBy.Equals(_uname, StringComparison.InvariantCultureIgnoreCase);
}
else
{
post.Nickname = user.Name.Trim();
post.ImageUrl = user.ProfileImageUrlHttps;
post.IsProtect = user.Protected;
- post.IsMe = post.ScreenName.ToLowerInvariant().Equals(_uname);
+ post.IsMe = post.ScreenName.Equals(_uname, StringComparison.InvariantCultureIgnoreCase);
}
else
{
}
//HTMLに整形
string textFromApi = post.TextFromApi;
- post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, entities, post.Media);
+
+ var quotedStatusLink = (status.RetweetedStatus ?? status).QuotedStatusPermalink;
+
+ if (quotedStatusLink != null && entities.Urls.Any(x => x.ExpandedUrl == quotedStatusLink.Expanded))
+ quotedStatusLink = null; // 移行期は entities.urls と quoted_status_permalink の両方に含まれる場合がある
+
+ post.Text = CreateHtmlAnchor(textFromApi, entities, quotedStatusLink);
post.TextFromApi = textFromApi;
- post.TextFromApi = this.ReplaceTextFromApi(post.TextFromApi, entities);
+ post.TextFromApi = this.ReplaceTextFromApi(post.TextFromApi, entities, quotedStatusLink);
post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
- post.AccessibleText = this.CreateAccessibleText(textFromApi, entities, (status.RetweetedStatus ?? status).QuotedStatus);
+ post.AccessibleText = CreateAccessibleText(textFromApi, entities, (status.RetweetedStatus ?? status).QuotedStatus, quotedStatusLink);
post.AccessibleText = WebUtility.HtmlDecode(post.AccessibleText);
post.AccessibleText = post.AccessibleText.Replace("<3", "\u2661");
- post.QuoteStatusIds = GetQuoteTweetStatusIds(entities)
+ this.ExtractEntities(entities, post.ReplyToList, post.Media);
+
+ post.QuoteStatusIds = GetQuoteTweetStatusIds(entities, quotedStatusLink)
.Where(x => x != post.StatusId && x != post.RetweetedId)
.Distinct().ToArray();
/// <summary>
/// ツイートに含まれる引用ツイートのURLからステータスIDを抽出
/// </summary>
- public static IEnumerable<long> GetQuoteTweetStatusIds(IEnumerable<TwitterEntity> entities)
+ public static IEnumerable<long> GetQuoteTweetStatusIds(IEnumerable<TwitterEntity> entities, TwitterQuotedStatusPermalink quotedStatusLink)
{
var urls = entities.OfType<TwitterEntityUrl>().Select(x => x.ExpandedUrl);
+ if (quotedStatusLink != null)
+ urls = urls.Concat(new[] { quotedStatusLink.Expanded });
+
return GetQuoteTweetStatusIds(urls);
}
var post = CreatePostsFromStatusData(status);
post.IsRead = read;
- if (post.IsMe && !read && _readOwnPost) post.IsRead = true;
+ if (post.IsMe && !read && this.ReadOwnPost) post.IsRead = true;
if (tab != null && tab.IsInnerStorageTabType)
tab.AddPostQueue(post);
var post = CreatePostsFromStatusData(status);
post.IsRead = read;
- if ((post.IsMe && !read) && this._readOwnPost) post.IsRead = true;
+ if ((post.IsMe && !read) && this.ReadOwnPost) post.IsRead = true;
tab.AddPostQueue(post);
}
relPosts.Values.ToList().ForEach(p =>
{
- if (p.IsMe && !read && this._readOwnPost)
+ if (p.IsMe && !read && this.ReadOwnPost)
p.IsRead = true;
else
p.IsRead = read;
//本文
var textFromApi = message.Text;
//HTMLに整形
- post.Text = CreateHtmlAnchor(textFromApi, post.ReplyToList, message.Entities, post.Media);
- post.TextFromApi = this.ReplaceTextFromApi(textFromApi, message.Entities);
+ post.Text = CreateHtmlAnchor(textFromApi, message.Entities, quotedStatusLink: null);
+ post.TextFromApi = this.ReplaceTextFromApi(textFromApi, message.Entities, quotedStatusLink: null);
post.TextFromApi = WebUtility.HtmlDecode(post.TextFromApi);
post.TextFromApi = post.TextFromApi.Replace("<3", "\u2661");
- post.AccessibleText = this.CreateAccessibleText(textFromApi, message.Entities, quoteStatus: null);
+ post.AccessibleText = CreateAccessibleText(textFromApi, message.Entities, quotedStatus: null, quotedStatusLink: null);
post.AccessibleText = WebUtility.HtmlDecode(post.AccessibleText);
post.AccessibleText = post.AccessibleText.Replace("<3", "\u2661");
post.IsFav = false;
- post.QuoteStatusIds = GetQuoteTweetStatusIds(message.Entities).Distinct().ToArray();
+ this.ExtractEntities(message.Entities, post.ReplyToList, post.Media);
+
+ post.QuoteStatusIds = GetQuoteTweetStatusIds(message.Entities, quotedStatusLink: null)
+ .Distinct().ToArray();
post.ExpandedUrls = message.Entities.OfType<TwitterEntityUrl>()
.Select(x => new PostClass.ExpandedUrlInfo(x.Url, x.ExpandedUrl))
}
post.IsRead = read;
- if (post.IsMe && !read && _readOwnPost) post.IsRead = true;
+ if (post.IsMe && !read && this.ReadOwnPost) post.IsRead = true;
post.IsReply = false;
post.IsExcludeReply = false;
post.IsDm = true;
tab.OldestId = minimumId.Value;
}
- private string ReplaceTextFromApi(string text, TwitterEntities entities)
+ private string ReplaceTextFromApi(string text, TwitterEntities entities, TwitterQuotedStatusPermalink quotedStatusLink)
{
if (entities != null)
{
}
}
}
+
+ if (quotedStatusLink != null)
+ text += " " + quotedStatusLink.Display;
+
return text;
}
- private string CreateAccessibleText(string text, TwitterEntities entities, TwitterStatus quoteStatus)
+ internal static string CreateAccessibleText(string text, TwitterEntities entities, TwitterStatus quotedStatus, TwitterQuotedStatusPermalink quotedStatusLink)
{
if (entities == null)
return text;
{
foreach (var entity in entities.Urls)
{
- if (quoteStatus != null)
+ if (quotedStatus != null)
{
var matchStatusUrl = Twitter.StatusUrlRegex.Match(entity.ExpandedUrl);
- if (matchStatusUrl.Success && matchStatusUrl.Groups["StatusId"].Value == quoteStatus.IdStr)
+ if (matchStatusUrl.Success && matchStatusUrl.Groups["StatusId"].Value == quotedStatus.IdStr)
{
- var quoteText = this.CreateAccessibleText(quoteStatus.FullText, quoteStatus.MergedEntities, quoteStatus: null);
- text = text.Replace(entity.Url, string.Format(Properties.Resources.QuoteStatus_AccessibleText, quoteStatus.User.ScreenName, quoteText));
+ var quotedText = CreateAccessibleText(quotedStatus.FullText, quotedStatus.MergedEntities, quotedStatus: null, quotedStatusLink: null);
+ text = text.Replace(entity.Url, string.Format(Properties.Resources.QuoteStatus_AccessibleText, quotedStatus.User.ScreenName, quotedText));
+ continue;
}
}
- else if (!string.IsNullOrEmpty(entity.DisplayUrl))
- {
+
+ if (!string.IsNullOrEmpty(entity.DisplayUrl))
text = text.Replace(entity.Url, entity.DisplayUrl);
- }
}
}
}
}
+ if (quotedStatusLink != null)
+ {
+ var quoteText = CreateAccessibleText(quotedStatus.FullText, quotedStatus.MergedEntities, quotedStatus: null, quotedStatusLink: null);
+ text += " " + string.Format(Properties.Resources.QuoteStatus_AccessibleText, quotedStatus.User.ScreenName, quoteText);
+ }
+
return text;
}
this.followerId = newFollowerIds;
TabInformations.GetInstance().RefreshOwl(this.followerId);
- this._GetFollowerResult = true;
- }
-
- public bool GetFollowersSuccess
- {
- get
- {
- return _GetFollowerResult;
- }
+ this.GetFollowersSuccess = true;
}
/// <summary>
this.noRTId = await this.Api.NoRetweetIds()
.ConfigureAwait(false);
- this._GetNoRetweetResult = true;
- }
-
- public bool GetNoRetweetSuccess
- {
- get
- {
- return _GetNoRetweetResult;
- }
+ this.GetNoRetweetSuccess = true;
}
/// <summary>
}
}
- public string CreateHtmlAnchor(string text, List<Tuple<long, string>> AtList, TwitterEntities entities, List<MediaInfo> media)
+ private void ExtractEntities(TwitterEntities entities, List<Tuple<long, string>> AtList, List<MediaInfo> media)
{
if (entities != null)
{
{
foreach (var ent in entities.Media)
{
- if (!media.Any(x => x.Url == ent.MediaUrl))
+ if (!media.Any(x => x.Url == ent.MediaUrlHttps))
{
if (ent.VideoInfo != null &&
ent.Type == "animated_gif" || ent.Type == "video")
// .Where(v => v.ContentType == "video/mp4")
// .OrderByDescending(v => v.Bitrate)
// .Select(v => v.Url).FirstOrDefault();
- media.Add(new MediaInfo(ent.MediaUrl, ent.AltText, ent.ExpandedUrl));
+ media.Add(new MediaInfo(ent.MediaUrlHttps, ent.AltText, ent.ExpandedUrl));
}
else
- media.Add(new MediaInfo(ent.MediaUrl, ent.AltText, videoUrl: null));
+ media.Add(new MediaInfo(ent.MediaUrlHttps, ent.AltText, videoUrl: null));
}
}
}
}
}
+ }
+ internal static string CreateHtmlAnchor(string text, TwitterEntities entities, TwitterQuotedStatusPermalink quotedStatusLink)
+ {
// PostClass.ExpandedUrlInfo を使用して非同期に URL 展開を行うためここでは expanded_url を使用しない
text = TweetFormatter.AutoLinkHtml(text, entities, keepTco: true);
text = Regex.Replace(text, "(^|[^a-zA-Z0-9_/&##@@>=.~])(sm|nm)([0-9]{1,10})", "$1<a href=\"http://www.nicovideo.jp/watch/$2$3\">$2$3</a>");
text = PreProcessUrl(text); //IDN置換
+ if (quotedStatusLink != null)
+ {
+ text += string.Format(" <a href=\"{0}\" title=\"{0}\">{1}</a>",
+ WebUtility.HtmlEncode(quotedStatusLink.Url),
+ WebUtility.HtmlEncode(quotedStatusLink.Display));
+ }
+
return text;
}
#region "UserStream"
- private string trackWord_ = "";
- public string TrackWord
- {
- get
- {
- return trackWord_;
- }
- set
- {
- trackWord_ = value;
- }
- }
- private bool allAtReply_ = false;
- public bool AllAtReply
- {
- get
- {
- return allAtReply_;
- }
- set
- {
- allAtReply_ = value;
- }
- }
+ public string TrackWord { get; set; } = "";
+ public bool AllAtReply { get; set; } = false;
public event EventHandler NewPostFromStream;
public event EventHandler UserStreamStarted;
public event EventHandler UserStreamStopped;
public event EventHandler<PostDeletedEventArgs> PostDeleted;
public event EventHandler<UserStreamEventReceivedEventArgs> UserStreamEventReceived;
- private DateTime _lastUserstreamDataReceived;
+ private DateTimeUtc _lastUserstreamDataReceived;
private TwitterUserstream userStream;
public class FormattedEvent
{
public MyCommon.EVENTTYPE Eventtype { get; set; }
- public DateTime CreatedAt { get; set; }
+ public DateTimeUtc CreatedAt { get; set; }
public string Event { get; set; }
public string Username { get; set; }
public string Target { get; set; }
public bool IsMe { get; set; }
}
- public List<FormattedEvent> storedEvent_ = new List<FormattedEvent>();
- public List<FormattedEvent> StoredEvent
- {
- get
- {
- return storedEvent_;
- }
- set
- {
- storedEvent_ = value;
- }
- }
+ public List<FormattedEvent> StoredEvent { get; } = new List<FormattedEvent>();
private readonly IReadOnlyDictionary<string, MyCommon.EVENTTYPE> eventTable = new Dictionary<string, MyCommon.EVENTTYPE>
{
};
public bool IsUserstreamDataReceived
- {
- get
- {
- return DateTime.Now.Subtract(this._lastUserstreamDataReceived).TotalSeconds < 31;
- }
- }
+ => (DateTimeUtc.Now - this._lastUserstreamDataReceived).TotalSeconds < 31;
private void userStream_StatusArrived(string line)
{
- this._lastUserstreamDataReceived = DateTime.Now;
+ this._lastUserstreamDataReceived = DateTimeUtc.Now;
if (string.IsNullOrEmpty(line)) return;
if (line.First() != '{' || line.Last() != '}')
MyCommon.TraceOut(ex, "Event Exception!" + Environment.NewLine + content);
}
- var evt = new FormattedEvent();
- evt.CreatedAt = MyCommon.DateTimeParse(eventData.CreatedAt);
- evt.Event = eventData.Event;
- evt.Username = eventData.Source.ScreenName;
- evt.IsMe = evt.Username.ToLowerInvariant().Equals(this.Username.ToLowerInvariant());
-
- eventTable.TryGetValue(eventData.Event, out var eventType);
- evt.Eventtype = eventType;
+ var evt = new FormattedEvent
+ {
+ CreatedAt = MyCommon.DateTimeParse(eventData.CreatedAt),
+ Event = eventData.Event,
+ Username = eventData.Source.ScreenName,
+ IsMe = eventData.Source.ScreenName.Equals(this.Username, StringComparison.InvariantCultureIgnoreCase),
+ Eventtype = eventTable.TryGetValue(eventData.Event, out var eventType) ? eventType : MyCommon.EVENTTYPE.None,
+ };
TwitterStreamEvent<TwitterStatusCompat> tweetEvent;
TwitterStatus tweet;
case "user_suspend":
return;
case "follow":
- if (eventData.Target.ScreenName.ToLowerInvariant().Equals(_uname))
+ if (eventData.Target.ScreenName.Equals(_uname, StringComparison.InvariantCultureIgnoreCase))
{
if (!this.followerId.Contains(eventData.Source.Id)) this.followerId.Add(eventData.Source.Id);
}
}
private void userStream_Started()
- {
- this.UserStreamStarted?.Invoke(this, EventArgs.Empty);
- }
+ => this.UserStreamStarted?.Invoke(this, EventArgs.Empty);
private void userStream_Stopped()
- {
- this.UserStreamStopped?.Invoke(this, EventArgs.Empty);
- }
+ => this.UserStreamStopped?.Invoke(this, EventArgs.Empty);
public bool UserStreamActive
=> this.userStream != null && this.userStream.IsStreamActive;
private CancellationTokenSource streamCts;
public TwitterUserstream(TwitterApi twitterApi)
- {
- this.twitterApi = twitterApi;
- }
+ => this.twitterApi = twitterApi;
public void Start(bool allAtReplies, string trackwords)
{
public long StatusId { get; }
public PostDeletedEventArgs(long statusId)
- {
- this.StatusId = statusId;
- }
+ => this.StatusId = statusId;
}
public class UserStreamEventReceivedEventArgs : EventArgs
public Twitter.FormattedEvent EventData { get; }
public UserStreamEventReceivedEventArgs(Twitter.FormattedEvent eventData)
- {
- this.EventData = eventData;
- }
+ => this.EventData = eventData;
}
}