-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMainForm.cs
More file actions
360 lines (309 loc) · 15 KB
/
MainForm.cs
File metadata and controls
360 lines (309 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
using CrossPoster.Models;
using CrossPoster.Services;
using System.Security.Policy;
using Tweetinvi;
namespace CrossPoster
{
/// <summary>
/// メインの投稿フォーム。UIの操作と各SNSへの投稿指示を担当します。
/// </summary>
public partial class MainForm : Form
{
#region Fields
/// <summary>
/// 設定ファイルの読み書きを管理するマネージャー。
/// </summary>
private readonly SettingsManager _settingsManager = new SettingsManager();
/// <summary>
/// アプリケーションの設定を保持するオブジェクト。
/// null! (null forgiving operator) を使用し、コンストラクタ内で必ず初期化されることをコンパイラに伝えています。
/// </summary>
private AppSettings _settings = null!;
/// <summary>
/// 添付するメディアファイルのパス。
/// </summary>
private string? _mediaPath = null;
// 各SNSの画像ファイルサイズ上限 (バイト単位)
private const long TWITTER_IMAGE_SIZE_LIMIT = 5 * 1024 * 1024; // 5MB
private const long BLUESKY_IMAGE_SIZE_LIMIT = 1 * 1024 * 1024; // 1MB
private const long MISSKEY_IMAGE_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB (サーバーにより異なる)
// メディアが各SNSの要件を満たしているかの状態を保持
private bool _isMediaValidForTwitter = true;
private bool _isMediaValidForBluesky = true;
private bool _isMediaValidForMisskey = true;
#endregion
#region Form Lifecycle
/// <summary>
/// MainFormのコンストラクタ。
/// </summary>
public MainForm()
{
InitializeComponent();
// フォームのLoadイベントハンドラを登録
this.Load += new System.EventHandler(this.MainForm_Load);
}
/// <summary>
/// フォームが読み込まれたときの処理。
/// </summary>
private async void MainForm_Load(object? sender, EventArgs e) // ★修正点: sender を object? に変更
{
LoadSettingsAndApply();
// アップデートチェックを実行
var updateChecker = new UpdateChecker();
await updateChecker.CheckForUpdateAsync();
}
/// <summary>
/// 設定を読み込み、UIに適用します。
/// </summary>
private void LoadSettingsAndApply()
{
_settings = _settingsManager.Load();
UpdateCheckBoxesState();
// 保存された前回のチェック状態を復元
checkBoxX.Checked = _settings.General.LastUsedTwitter;
checkBoxBluesky.Checked = _settings.General.LastUsedBluesky;
checkBoxMisskey.Checked = _settings.General.LastUsedMisskey;
}
#endregion
#region UI Event Handlers
/// <summary>
/// 送信ボタンがクリックされたときの処理。
/// </summary>
private async void postButton_Click(object sender, EventArgs e)
{
// --- 投稿前バリデーション ---
var validationErrors = new List<string>();
// メディアファイルのサイズチェック
if (!string.IsNullOrEmpty(_mediaPath))
{
if (checkBoxX.Checked && !_isMediaValidForTwitter)
validationErrors.Add("X (Twitter): 画像サイズが5MBを超えています。");
if (checkBoxBluesky.Checked && !_isMediaValidForBluesky)
validationErrors.Add("Bluesky: 画像サイズが1MBを超えています。");
if (checkBoxMisskey.Checked && !_isMediaValidForMisskey)
validationErrors.Add("Misskey: 画像サイズが10MBを超えています。");
}
// 文字数チェック
string textToPost = postTextBox.Text;
if (checkBoxX.Checked && textToPost.Length > 140)
validationErrors.Add("X (Twitter): 140文字を超えています。");
if (checkBoxBluesky.Checked && textToPost.Length > 300)
validationErrors.Add("Bluesky: 300文字を超えています。");
if (checkBoxMisskey.Checked && textToPost.Length > 3000)
validationErrors.Add("Misskey: 3000文字を超えています。");
// 投稿先未選択チェック
if (!checkBoxX.Checked && !checkBoxBluesky.Checked && !checkBoxMisskey.Checked)
{
MessageBox.Show("投稿先のSNSを選択してください。", "情報", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// バリデーションエラーがあればメッセージを表示して処理を中断
if (validationErrors.Any())
{
MessageBox.Show("以下の問題により投稿できませんでした:\n\n" + string.Join("\n", validationErrors),
"投稿エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// --- バリデーションここまで ---
// 連続クリックを防ぐためにボタンを無効化
postButton.Enabled = false;
// 投稿タスクを作成
var postTasks = new List<Tuple<string, Task>>();
if (checkBoxX.Checked)
postTasks.Add(new Tuple<string, Task>("Twitter", PostToTwitterAsync(textToPost, _mediaPath)));
if (checkBoxBluesky.Checked)
postTasks.Add(new Tuple<string, Task>("Bluesky", PostToBlueskyAsync(textToPost, _mediaPath)));
if (checkBoxMisskey.Checked)
postTasks.Add(new Tuple<string, Task>("Misskey", PostToMisskeyAsync(textToPost, _mediaPath)));
// 作成した投稿タスクをすべて非同期で実行
var results = new List<string>();
bool hasSuccess = false; // 少なくとも1つの投稿が成功したか
foreach (var taskInfo in postTasks)
{
try
{
await taskInfo.Item2;
results.Add($"{taskInfo.Item1}: 投稿に成功しました。");
hasSuccess = true;
}
catch (Exception ex)
{
results.Add($"{taskInfo.Item1}: 投稿に失敗しました。\n エラー: {ex.Message}");
}
}
// 少なくとも1つの投稿が成功した場合、チェックボックスの状態を保存
if (hasSuccess)
{
_settings.General.LastUsedTwitter = checkBoxX.Checked;
_settings.General.LastUsedBluesky = checkBoxBluesky.Checked;
_settings.General.LastUsedMisskey = checkBoxMisskey.Checked;
_settingsManager.Save(_settings);
}
// すべての投稿結果をまとめて表示
MessageBox.Show(string.Join("\n\n", results), "投稿結果", MessageBoxButtons.OK, MessageBoxIcon.Information);
// ボタンを再度有効化
postButton.Enabled = true;
// 投稿後、テキストボックスと添付画像をクリアするか確認
var clearResult = MessageBox.Show("入力内容をクリアしますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (clearResult == DialogResult.Yes)
{
postTextBox.Text = string.Empty;
ClearMedia();
}
}
/// <summary>
/// 投稿テキストボックスの文字数が変更されるたびに文字数カウンターを更新する処理。
/// </summary>
private void postTextBox_TextChanged(object sender, EventArgs e)
{
int length = postTextBox.Text.Length;
// Twitterカウンターの更新
twitterCharCountLabel.Text = $"{length} / 140";
twitterCharCountLabel.ForeColor = length > 140 ? Color.Red : SystemColors.ControlText;
// Blueskyカウンターの更新
blueskyCharCountLabel.Text = $"{length} / 300";
blueskyCharCountLabel.ForeColor = length > 300 ? Color.Red : SystemColors.ControlText;
// Misskeyカウンターの更新
misskeyCharCountLabel.Text = $"{length} / 3000";
misskeyCharCountLabel.ForeColor = length > 3000 ? Color.Red : SystemColors.ControlText;
}
/// <summary>
/// メニューの「設定」がクリックされたときの処理。
/// </summary>
private void 設定ToolStripMenuItem_Click(object sender, EventArgs e)
{
// usingステートメントで、フォームが閉じられたときにリソースが自動的に解放されるようにする
using (var form2 = new SettingsForm()) // Form2をSettingsFormに変更
{
form2.ShowDialog();
}
// 設定画面が閉じられた後、変更をUIに反映するために設定を再読み込み
LoadSettingsAndApply();
}
/// <summary>
/// メニューの「終了」がクリックされたときの処理。
/// </summary>
private void 終了ToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
/// <summary>
/// 「メディア選択」ボタンがクリックされたときの処理。
/// </summary>
private void selectMediaButton_Click(object sender, EventArgs e)
{
using (var ofd = new OpenFileDialog())
{
ofd.Filter = "画像ファイル(*.jpg;*.jpeg;*.png;*.gif)|*.jpg;*.jpeg;*.png;*.gif";
if (ofd.ShowDialog() == DialogResult.OK)
{
_mediaPath = ofd.FileName;
pictureBox.ImageLocation = _mediaPath;
ValidateMedia(_mediaPath);
}
}
}
/// <summary>
/// 「クリア」ボタンがクリックされたときの処理。
/// </summary>
private void clearMediaButton_Click(object sender, EventArgs e)
{
ClearMedia();
}
#endregion
#region Private Methods
/// <summary>
/// 添付されたメディアのファイルサイズを検証し、UIに結果を表示します。
/// </summary>
/// <param name="filePath">検証するファイルのパス。</param>
private void ValidateMedia(string? filePath)
{
if (string.IsNullOrEmpty(filePath))
{
// ファイルパスがなければ全検証をリセット
_isMediaValidForTwitter = true;
_isMediaValidForBluesky = true;
_isMediaValidForMisskey = true;
twitterMediaStatusLabel.Text = "";
blueskyMediaStatusLabel.Text = "";
misskeyMediaStatusLabel.Text = "";
return;
}
try
{
var fileInfo = new FileInfo(filePath);
long fileSize = fileInfo.Length;
// Twitterのサイズチェック
_isMediaValidForTwitter = fileSize <= TWITTER_IMAGE_SIZE_LIMIT;
twitterMediaStatusLabel.Text = _isMediaValidForTwitter ? "" : "サイズ超過";
// Blueskyのサイズチェック
_isMediaValidForBluesky = fileSize <= BLUESKY_IMAGE_SIZE_LIMIT;
blueskyMediaStatusLabel.Text = _isMediaValidForBluesky ? "" : "サイズ超過";
// Misskeyのサイズチェック
_isMediaValidForMisskey = fileSize <= MISSKEY_IMAGE_SIZE_LIMIT;
misskeyMediaStatusLabel.Text = _isMediaValidForMisskey ? "" : "サイズ超過";
}
catch (Exception ex)
{
// ファイル情報の取得に失敗した場合
MessageBox.Show($"ファイル情報の取得に失敗しました: {ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
ClearMedia();
}
}
/// <summary>
/// 添付メディアをクリアします。
/// </summary>
private void ClearMedia()
{
_mediaPath = null;
pictureBox.Image = null;
// メディアをクリアしたら検証ステータスもリセット
ValidateMedia(null);
}
/// <summary>
/// 設定に基づいて、各SNSのチェックボックスの有効/無効を切り替えます。
/// </summary>
private void UpdateCheckBoxesState()
{
checkBoxX.Enabled = _settings.Twitter.IsConfigured;
checkBoxBluesky.Enabled = _settings.Bluesky.IsConfigured;
checkBoxMisskey.Enabled = _settings.Misskey.IsConfigured;
// 設定が無効な場合はチェックも外す
if (!checkBoxX.Enabled) checkBoxX.Checked = false;
if (!checkBoxBluesky.Enabled) checkBoxBluesky.Checked = false;
if (!checkBoxMisskey.Enabled) checkBoxMisskey.Checked = false;
}
/// <summary>
/// Twitterへの投稿処理を呼び出すヘルパーメソッド。
/// </summary>
/// <param name="text">投稿するテキスト。</param>
/// <param name="imagePath">添付する画像のパス。</param>
private Task PostToTwitterAsync(string text, string? imagePath)
{
var twitterClient = new TwitterClient(_settings.Twitter.ConsumerKey, _settings.Twitter.ConsumerKeySecret, _settings.Twitter.AccessToken, _settings.Twitter.AccessTokenSecret);
var twitterService = new TwitterService(twitterClient);
return twitterService.PostAsync(text, imagePath);
}
/// <summary>
/// Blueskyへの投稿処理を呼び出すヘルパーメソッド。
/// </summary>
/// <param name="text">投稿するテキスト。</param>
/// <param name="imagePath">添付する画像のパス。</param>
private Task PostToBlueskyAsync(string text, string? imagePath)
{
var blueskyService = new BlueskyService();
return blueskyService.PostAsync(_settings.Bluesky.Account, _settings.Bluesky.Password, text, imagePath);
}
/// <summary>
/// Misskeyへの投稿処理を呼び出すヘルパーメソッド。
/// </summary>
/// <param name="text">投稿するテキスト。</param>
/// <param name="imagePath">添付する画像のパス。</param>
private Task PostToMisskeyAsync(string text, string? imagePath)
{
var misskeyService = new MisskeyService();
return misskeyService.PostAsync(_settings.Misskey.BaseUrl, _settings.Misskey.AccessToken, text, imagePath);
}
#endregion
}
}