1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 // (c) 2008-2011 Moz (@syo68k)
4 // (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 // (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 // (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 // (c) 2011 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
10 // This file is part of OpenTween.
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
28 using System.Collections.Generic;
29 using System.ComponentModel;
34 using System.Threading;
35 using System.Threading.Tasks;
36 using System.Windows.Forms;
37 using System.Text.RegularExpressions;
42 using OpenTween.Connection;
46 public partial class UserInfoDialog : OTBaseForm
48 private TwitterUser _displayUser;
49 private CancellationTokenSource cancellationTokenSource = null;
51 private readonly TweenMain mainForm;
52 private readonly Twitter twitter;
54 public UserInfoDialog(TweenMain mainForm, Twitter twitter)
56 this.mainForm = mainForm;
57 this.twitter = twitter;
59 InitializeComponent();
61 // LabelScreenName のフォントを OTBaseForm.GlobalFont に変更
62 this.LabelScreenName.Font = this.ReplaceToGlobalFont(this.LabelScreenName.Font);
65 private void CancelLoading()
67 var newTokenSource = new CancellationTokenSource();
68 var oldTokenSource = Interlocked.Exchange(ref this.cancellationTokenSource, newTokenSource);
70 if (oldTokenSource != null)
72 oldTokenSource.Cancel();
73 oldTokenSource.Dispose();
77 public async Task ShowUserAsync(TwitterUser user)
79 if (user == null || user == this._displayUser)
84 var cancellationToken = this.cancellationTokenSource.Token;
86 this._displayUser = user;
88 this.LabelId.Text = user.IdStr;
89 this.LabelScreenName.Text = user.ScreenName;
90 this.LabelName.Text = user.Name;
91 this.LabelLocation.Text = user.Location ?? "";
92 this.LabelCreatedAt.Text = MyCommon.DateTimeParse(user.CreatedAt).ToString();
95 this.LabelIsProtected.Text = Properties.Resources.Yes;
97 this.LabelIsProtected.Text = Properties.Resources.No;
100 this.LabelIsVerified.Text = Properties.Resources.Yes;
102 this.LabelIsVerified.Text = Properties.Resources.No;
104 var followingUrl = "https://twitter.com/" + user.ScreenName + "/following";
105 this.LinkLabelFollowing.Text = user.FriendsCount.ToString();
106 this.LinkLabelFollowing.Tag = followingUrl;
107 this.ToolTip1.SetToolTip(this.LinkLabelFollowing, followingUrl);
109 var followersUrl = "https://twitter.com/" + user.ScreenName + "/followers";
110 this.LinkLabelFollowers.Text = user.FollowersCount.ToString();
111 this.LinkLabelFollowers.Tag = followersUrl;
112 this.ToolTip1.SetToolTip(this.LinkLabelFollowers, followersUrl);
114 var favoritesUrl = "https://twitter.com/" + user.ScreenName + "/favorites";
115 this.LinkLabelFav.Text = user.FavouritesCount.ToString();
116 this.LinkLabelFav.Tag = favoritesUrl;
117 this.ToolTip1.SetToolTip(this.LinkLabelFav, favoritesUrl);
119 var profileUrl = "https://twitter.com/" + user.ScreenName;
120 this.LinkLabelTweet.Text = user.StatusesCount.ToString();
121 this.LinkLabelTweet.Tag = profileUrl;
122 this.ToolTip1.SetToolTip(this.LinkLabelTweet, profileUrl);
124 if (this.twitter.UserId == user.Id)
126 this.ButtonEdit.Enabled = true;
127 this.ChangeIconToolStripMenuItem.Enabled = true;
128 this.ButtonBlock.Enabled = false;
129 this.ButtonReportSpam.Enabled = false;
130 this.ButtonBlockDestroy.Enabled = false;
134 this.ButtonEdit.Enabled = false;
135 this.ChangeIconToolStripMenuItem.Enabled = false;
136 this.ButtonBlock.Enabled = true;
137 this.ButtonReportSpam.Enabled = true;
138 this.ButtonBlockDestroy.Enabled = true;
141 await Task.WhenAll(new[]
143 this.SetDescriptionAsync(user.Description, cancellationToken),
144 this.SetRecentStatusAsync(user.Status, cancellationToken),
145 this.SetLinkLabelWebAsync(user.Url, cancellationToken),
146 this.SetUserImageAsync(user.ProfileImageUrlHttps, cancellationToken),
147 this.LoadFriendshipAsync(user.ScreenName, cancellationToken),
151 private async Task SetDescriptionAsync(string descriptionText, CancellationToken cancellationToken)
153 if (descriptionText != null)
155 var atlist = new List<string>();
157 // description に含まれる < > " の記号のみエスケープを一旦解除する
158 var decodedText = descriptionText.Replace("<", "<").Replace(">", ">").Replace(""", "\"");
160 var html = WebUtility.HtmlEncode(decodedText);
161 html = await this.twitter.CreateHtmlAnchorAsync(html, atlist, null);
162 html = this.mainForm.createDetailHtml(html);
164 if (cancellationToken.IsCancellationRequested)
167 this.DescriptionBrowser.DocumentText = html;
171 this.DescriptionBrowser.DocumentText = "";
175 private async Task SetUserImageAsync(string imageUri, CancellationToken cancellationToken)
177 var oldImage = this.UserPicture.Image;
178 if (oldImage != null)
180 this.UserPicture.Image = null;
184 // ProfileImageUrlHttps が null になる場合があるらしい
185 // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=33871
186 if (imageUri == null)
189 await this.UserPicture.SetImageFromTask(async () =>
191 var uri = imageUri.Replace("_normal", "_bigger");
193 using (var imageStream = await Networking.Http.GetStreamAsync(uri).ConfigureAwait(false))
195 var image = await MemoryImage.CopyFromStreamAsync(imageStream)
196 .ConfigureAwait(false);
198 if (cancellationToken.IsCancellationRequested)
201 throw new OperationCanceledException(cancellationToken);
209 private async Task SetLinkLabelWebAsync(string uri, CancellationToken cancellationToken)
213 var expandedUrl = await ShortUrl.Instance.ExpandUrlAsync(uri);
215 if (cancellationToken.IsCancellationRequested)
218 this.LinkLabelWeb.Text = uri;
219 this.LinkLabelWeb.Tag = expandedUrl;
220 this.ToolTip1.SetToolTip(this.LinkLabelWeb, expandedUrl);
224 this.LinkLabelWeb.Text = "";
225 this.LinkLabelWeb.Tag = null;
226 this.ToolTip1.SetToolTip(this.LinkLabelWeb, null);
230 private async Task SetRecentStatusAsync(TwitterStatus status, CancellationToken cancellationToken)
232 var atlist = new List<string>();
236 var html = await this.twitter.CreateHtmlAnchorAsync(status.Text, atlist, status.MergedEntities, null);
237 html = this.mainForm.createDetailHtml(html +
238 " Posted at " + MyCommon.DateTimeParse(status.CreatedAt) +
239 " via " + status.Source);
241 if (cancellationToken.IsCancellationRequested)
244 this.RecentPostBrowser.DocumentText = html;
248 this.RecentPostBrowser.DocumentText = Properties.Resources.ShowUserInfo2;
252 private async Task LoadFriendshipAsync(string screenName, CancellationToken cancellationToken)
254 this.LabelIsFollowing.Text = "";
255 this.LabelIsFollowed.Text = "";
256 this.ButtonFollow.Enabled = false;
257 this.ButtonUnFollow.Enabled = false;
259 if (this.twitter.Username == screenName)
262 var friendship = await Task.Run(() =>
264 var IsFollowing = false;
265 var IsFollowedBy = false;
267 var ret = this.twitter.GetFriendshipInfo(screenName, ref IsFollowing, ref IsFollowedBy);
268 if (!string.IsNullOrEmpty(ret))
271 return new { IsFollowing, IsFollowedBy };
274 if (cancellationToken.IsCancellationRequested)
277 if (friendship == null)
279 LabelIsFollowed.Text = Properties.Resources.GetFriendshipInfo6;
280 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo6;
284 this.LabelIsFollowing.Text = friendship.IsFollowing
285 ? Properties.Resources.GetFriendshipInfo1
286 : Properties.Resources.GetFriendshipInfo2;
288 this.LabelIsFollowed.Text = friendship.IsFollowedBy
289 ? Properties.Resources.GetFriendshipInfo3
290 : Properties.Resources.GetFriendshipInfo4;
292 this.ButtonFollow.Enabled = !friendship.IsFollowing;
293 this.ButtonUnFollow.Enabled = friendship.IsFollowing;
296 private void ShowUserInfo_Load(object sender, EventArgs e)
298 this.TextBoxName.Location = this.LabelName.Location;
299 this.TextBoxName.Height = this.LabelName.Height;
300 this.TextBoxName.Width = this.LabelName.Width;
301 this.TextBoxName.BackColor = this.mainForm.InputBackColor;
302 this.TextBoxName.MaxLength = 20;
304 this.TextBoxLocation.Location = this.LabelLocation.Location;
305 this.TextBoxLocation.Height = this.LabelLocation.Height;
306 this.TextBoxLocation.Width = this.LabelLocation.Width;
307 this.TextBoxLocation.BackColor = this.mainForm.InputBackColor;
308 this.TextBoxLocation.MaxLength = 30;
310 this.TextBoxWeb.Location = this.LinkLabelWeb.Location;
311 this.TextBoxWeb.Height = this.LinkLabelWeb.Height;
312 this.TextBoxWeb.Width = this.LinkLabelWeb.Width;
313 this.TextBoxWeb.BackColor = this.mainForm.InputBackColor;
314 this.TextBoxWeb.MaxLength = 100;
316 this.TextBoxDescription.Location = this.DescriptionBrowser.Location;
317 this.TextBoxDescription.Height = this.DescriptionBrowser.Height;
318 this.TextBoxDescription.Width = this.DescriptionBrowser.Width;
319 this.TextBoxDescription.BackColor = this.mainForm.InputBackColor;
320 this.TextBoxDescription.MaxLength = 160;
321 this.TextBoxDescription.Multiline = true;
322 this.TextBoxDescription.ScrollBars = ScrollBars.Vertical;
325 private async void LinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
327 var linkLabel = (LinkLabel)sender;
329 var linkUrl = (string)linkLabel.Tag;
333 await this.mainForm.OpenUriAsync(linkUrl);
336 private void ButtonFollow_Click(object sender, EventArgs e)
338 string ret = this.twitter.PostFollowCommand(this._displayUser.ScreenName);
339 if (!string.IsNullOrEmpty(ret))
341 MessageBox.Show(Properties.Resources.FRMessage2 + ret);
345 MessageBox.Show(Properties.Resources.FRMessage3);
346 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo1;
347 ButtonFollow.Enabled = false;
348 ButtonUnFollow.Enabled = true;
352 private void ButtonUnFollow_Click(object sender, EventArgs e)
354 if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonUnFollow_ClickText1,
355 Properties.Resources.ButtonUnFollow_ClickText2,
356 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
358 string ret = this.twitter.PostRemoveCommand(this._displayUser.ScreenName);
359 if (!string.IsNullOrEmpty(ret))
361 MessageBox.Show(Properties.Resources.FRMessage2 + ret);
365 MessageBox.Show(Properties.Resources.FRMessage3);
366 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo2;
367 ButtonFollow.Enabled = true;
368 ButtonUnFollow.Enabled = false;
373 private void ShowUserInfo_Activated(object sender, EventArgs e)
375 //画面が他画面の裏に隠れると、アイコン画像が再描画されない問題の対応
376 if (UserPicture.Image != null)
377 UserPicture.Invalidate(false);
380 private void ShowUserInfo_Shown(object sender, EventArgs e)
385 private void WebBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
387 if (e.Url.AbsoluteUri != "about:blank")
391 if (e.Url.AbsoluteUri.StartsWith("http://twitter.com/search?q=%23") ||
392 e.Url.AbsoluteUri.StartsWith("https://twitter.com/search?q=%23"))
395 string urlStr = Uri.UnescapeDataString(e.Url.AbsoluteUri);
396 string hash = urlStr.Substring(urlStr.IndexOf("#"));
397 this.mainForm.HashSupl.AddItem(hash);
398 this.mainForm.HashMgr.AddHashToHistory(hash.Trim(), false);
399 this.mainForm.AddNewTabForSearch(hash);
404 Match m = Regex.Match(e.Url.AbsoluteUri, @"^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)$");
405 if (SettingCommon.Instance.OpenUserTimeline && m.Success && this.mainForm.IsTwitterId(m.Result("${ScreenName}")))
407 this.mainForm.AddNewTabForUserTimeline(m.Result("${ScreenName}"));
411 this.mainForm.OpenUriAsync(e.Url.OriginalString);
417 private void SelectAllToolStripMenuItem_Click(object sender, EventArgs e)
419 var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
420 browser.Document.ExecCommand("SelectAll", false, null);
423 private void SelectionCopyToolStripMenuItem_Click(object sender, EventArgs e)
425 var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
426 var selectedText = this.mainForm.WebBrowser_GetSelectionText(ref browser);
427 if (selectedText != null)
431 Clipboard.SetDataObject(selectedText, false, 5, 100);
435 MessageBox.Show(ex.Message);
440 private void ContextMenuRecentPostBrowser_Opening(object sender, CancelEventArgs e)
442 var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
443 var selectedText = this.mainForm.WebBrowser_GetSelectionText(ref browser);
445 this.SelectionCopyToolStripMenuItem.Enabled = selectedText != null;
448 private void LinkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
450 this.mainForm.OpenUriAsync("https://support.twitter.com/groups/31-twitter-basics/topics/111-features/articles/268350-x8a8d-x8a3c-x6e08-x307f-x30a2-x30ab-x30a6-x30f3-x30c8-x306b-x3064-x3044-x3066");
453 private void LinkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
455 this.mainForm.OpenUriAsync("https://support.twitter.com/groups/31-twitter-basics/topics/107-my-profile-account-settings/articles/243055-x516c-x958b-x3001-x975e-x516c-x958b-x30a2-x30ab-x30a6-x30f3-x30c8-x306b-x3064-x3044-x3066");
458 private void ButtonSearchPosts_Click(object sender, EventArgs e)
460 this.mainForm.AddNewTabForUserTimeline(this._displayUser.ScreenName);
463 private async void UserPicture_Click(object sender, EventArgs e)
465 var imageUrl = this._displayUser.ProfileImageUrlHttps;
466 imageUrl = imageUrl.Remove(imageUrl.LastIndexOf("_normal"), 7);
468 await this.mainForm.OpenUriAsync(imageUrl);
471 private bool IsEditing = false;
472 private string ButtonEditText = "";
474 private async void ButtonEdit_Click(object sender, EventArgs e)
476 // 自分以外のプロフィールは変更できない
477 if (this.twitter.UserId != this._displayUser.Id)
480 this.ButtonEdit.Enabled = false;
484 ButtonEditText = ButtonEdit.Text;
485 ButtonEdit.Text = Properties.Resources.UserInfoButtonEdit_ClickText1;
487 TextBoxName.Text = LabelName.Text;
488 TextBoxName.Enabled = true;
489 TextBoxName.Visible = true;
490 LabelName.Visible = false;
492 TextBoxLocation.Text = LabelLocation.Text;
493 TextBoxLocation.Enabled = true;
494 TextBoxLocation.Visible = true;
495 LabelLocation.Visible = false;
497 TextBoxWeb.Text = this._displayUser.Url;
498 TextBoxWeb.Enabled = true;
499 TextBoxWeb.Visible = true;
500 LinkLabelWeb.Visible = false;
502 TextBoxDescription.Text = this._displayUser.Description;
503 TextBoxDescription.Enabled = true;
504 TextBoxDescription.Visible = true;
505 DescriptionBrowser.Visible = false;
508 TextBoxName.Select(TextBoxName.Text.Length, 0);
514 Task showUserTask = null;
516 if (TextBoxName.Modified ||
517 TextBoxLocation.Modified ||
518 TextBoxWeb.Modified ||
519 TextBoxDescription.Modified)
523 var user = await Task.Run(() =>
524 this.twitter.PostUpdateProfile(
525 this.TextBoxName.Text,
526 this.TextBoxWeb.Text,
527 this.TextBoxLocation.Text,
528 this.TextBoxDescription.Text));
530 showUserTask = this.ShowUserAsync(user);
532 catch (WebApiException ex)
534 MessageBox.Show(ex.Message);
539 TextBoxName.Enabled = false;
540 TextBoxName.Visible = false;
541 LabelName.Visible = true;
543 TextBoxLocation.Enabled = false;
544 TextBoxLocation.Visible = false;
545 LabelLocation.Visible = true;
547 TextBoxWeb.Enabled = false;
548 TextBoxWeb.Visible = false;
549 LinkLabelWeb.Visible = true;
551 TextBoxDescription.Enabled = false;
552 TextBoxDescription.Visible = false;
553 DescriptionBrowser.Visible = true;
555 ButtonEdit.Text = ButtonEditText;
559 if (showUserTask != null)
563 this.ButtonEdit.Enabled = true;
566 private async Task DoChangeIcon(string filename)
568 var ret = await Task.Run(() =>
569 this.twitter.PostUpdateProfileImage(filename));
571 if (!string.IsNullOrEmpty(ret))
573 // "Err:"が付いたエラーメッセージが返ってくる
574 MessageBox.Show(ret + Environment.NewLine + Properties.Resources.ChangeIconToolStripMenuItem_ClickText4);
578 MessageBox.Show(Properties.Resources.ChangeIconToolStripMenuItem_ClickText5);
583 var user = await Task.Run(() =>
585 TwitterUser result = null;
587 var err = this.twitter.GetUserInfo(this._displayUser.ScreenName, ref result);
588 if (!string.IsNullOrEmpty(err))
589 throw new WebApiException(err);
595 await this.ShowUserAsync(user);
597 catch (WebApiException ex)
599 MessageBox.Show(ex.Message);
603 private async void ChangeIconToolStripMenuItem_Click(object sender, EventArgs e)
605 OpenFileDialogIcon.Filter = Properties.Resources.ChangeIconToolStripMenuItem_ClickText1;
606 OpenFileDialogIcon.Title = Properties.Resources.ChangeIconToolStripMenuItem_ClickText2;
607 OpenFileDialogIcon.FileName = "";
609 DialogResult rslt = OpenFileDialogIcon.ShowDialog();
611 if (rslt != DialogResult.OK)
616 string fn = OpenFileDialogIcon.FileName;
617 if (this.IsValidIconFile(new FileInfo(fn)))
619 await this.DoChangeIcon(fn);
623 MessageBox.Show(Properties.Resources.ChangeIconToolStripMenuItem_ClickText6);
627 private void ButtonBlock_Click(object sender, EventArgs e)
629 if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonBlock_ClickText1,
630 Properties.Resources.ButtonBlock_ClickText2,
631 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
633 string res = this.twitter.PostCreateBlock(this._displayUser.ScreenName);
634 if (!string.IsNullOrEmpty(res))
636 MessageBox.Show(res + Environment.NewLine + Properties.Resources.ButtonBlock_ClickText3);
640 MessageBox.Show(Properties.Resources.ButtonBlock_ClickText4);
645 private void ButtonReportSpam_Click(object sender, EventArgs e)
647 if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonReportSpam_ClickText1,
648 Properties.Resources.ButtonReportSpam_ClickText2,
649 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
651 string res = this.twitter.PostReportSpam(this._displayUser.ScreenName);
652 if (!string.IsNullOrEmpty(res))
654 MessageBox.Show(res + Environment.NewLine + Properties.Resources.ButtonReportSpam_ClickText3);
658 MessageBox.Show(Properties.Resources.ButtonReportSpam_ClickText4);
663 private void ButtonBlockDestroy_Click(object sender, EventArgs e)
665 if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonBlockDestroy_ClickText1,
666 Properties.Resources.ButtonBlockDestroy_ClickText2,
667 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
669 string res = this.twitter.PostDestroyBlock(this._displayUser.ScreenName);
670 if (!string.IsNullOrEmpty(res))
672 MessageBox.Show(res + Environment.NewLine + Properties.Resources.ButtonBlockDestroy_ClickText3);
676 MessageBox.Show(Properties.Resources.ButtonBlockDestroy_ClickText4);
681 private bool IsValidExtension(string ext)
685 return ext.Equals(".jpg", StringComparison.Ordinal) ||
686 ext.Equals(".jpeg", StringComparison.Ordinal) ||
687 ext.Equals(".png", StringComparison.Ordinal) ||
688 ext.Equals(".gif", StringComparison.Ordinal);
691 private bool IsValidIconFile(FileInfo info)
693 return this.IsValidExtension(info.Extension) &&
694 info.Length < 700 * 1024 &&
695 !MyCommon.IsAnimatedGif(info.FullName);
698 private void ShowUserInfo_DragOver(object sender, DragEventArgs e)
700 if (e.Data.GetDataPresent(DataFormats.FileDrop))
702 var files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
703 if (files.Length != 1)
706 var finfo = new FileInfo(files[0]);
707 if (this.IsValidIconFile(finfo))
709 e.Effect = DragDropEffects.Copy;
714 private async void ShowUserInfo_DragDrop(object sender, DragEventArgs e)
716 if (e.Data.GetDataPresent(DataFormats.FileDrop))
718 string filename = ((string[])e.Data.GetData(DataFormats.FileDrop, false))[0];
719 await this.DoChangeIcon(filename);
723 protected override void Dispose(bool disposing)
727 var cts = this.cancellationTokenSource;
731 var oldImage = this.UserPicture.Image;
732 if (oldImage != null)
734 this.UserPicture.Image = null;
738 if (this.components != null)
739 this.components.Dispose();
742 base.Dispose(disposing);