protected async Task AuthenticateAsync(QBittorrentClient client) { if (!string.IsNullOrEmpty(UserName)) { if (Password == null && AskForPassword) { Password = Prompt.GetPassword("Please, enter your password: "); } await client.LoginAsync(UserName, Password); } }
public async Task EnsureLoggedInAsync() { try { await _client.GetApiVersionAsync(); } catch (QBittorrentClientRequestException e) { if (e.StatusCode == HttpStatusCode.Forbidden) { _logger.LogWarning("Qbittorrent logged out, logging in Now"); await _client.LoginAsync(_qbittorrentSetting.Username, _qbittorrentSetting.Password); } } }
private static async Task RunOptions(Options opts) { if (opts.DryRun) { Console.WriteLine($"Dry run!"); } var deleteCount = 0; long spaceSaved = 0; var client = new QBittorrentClient(new Uri(opts.Url)); await client.LoginAsync(opts.Username, opts.Password); var torrents = await client.GetTorrentListAsync(); foreach (var directory in Directory.GetDirectories(opts.Path).OrderBy(x => x)) { var dirName = Path.GetFileName(directory); if (!torrents.Any(x => x.Name == dirName)) { var dirSize = DirSize(new DirectoryInfo(directory)); Console.WriteLine($"Deleting: ({BytesToString(dirSize)}) {directory}"); if (!opts.DryRun) { Directory.Delete(directory, true); } deleteCount++; spaceSaved += dirSize; } } Console.WriteLine($"Deleted {deleteCount} of left behind torrents which saves {BytesToString(spaceSaved)}."); }
static void Main(string[] args) { List <string> removeHashes = new List <string>(); Dictionary <string, MetricsTorrent> torrentMetrics = new Dictionary <string, MetricsTorrent>(); CLOptions options = null; Parser.Default.ParseArguments <CLOptions>(args) .WithParsed <CLOptions>(o => { options = o; }); Console.WriteLine($"Starting Prometheus scrape listener on {options.PrometheusAddress}:{options.PrometheusPort}"); var promServer = new MetricServer(options.PrometheusAddress, options.PrometheusPort); promServer.Start(); Console.WriteLine($"Creating qbittorrent WebAPI client on {options.QbittorrentIpAddress}:{options.QbittorrentPort}"); QBittorrentClient client = new QBittorrentClient(new Uri($"http://{options.QbittorrentIpAddress}:{options.QbittorrentPort}")); bool reconnectRequired = true; for (; ;) { try { if (reconnectRequired) { try { var logoutTask = client.LogoutAsync(); logoutTask.Wait(); } catch { } var loginTask = client.LoginAsync(options.QbittorrentUsername, options.QbittorrentPassword); loginTask.Wait(); } reconnectRequired = false; if (options.Verbose) { Console.WriteLine($"Processing Qbittorrent Poll at {DateTime.Now}"); } var getTorrentListTask = client.GetTorrentListAsync(); getTorrentListTask.Wait(); var response = getTorrentListTask.Result; if (response != null) { foreach (var trackedTorrent in torrentMetrics) { trackedTorrent.Value.BeginPoll(); } // summarize all sub-torrent data foreach (var torrent in response) { if (torrentMetrics.ContainsKey(torrent.Hash)) { torrentMetrics[torrent.Hash].UpdateStatus(torrent); } else { var newTorrent = new MetricsTorrent(torrent, options); torrentMetrics.Add(torrent.Hash, newTorrent); newTorrent.UpdateStatus(torrent); } } foreach (var trackedTorrent in torrentMetrics) { trackedTorrent.Value.EndPoll(); if (trackedTorrent.Value.TorrentDeleted) { removeHashes.Add(trackedTorrent.Value.Hash); } } foreach (var item in removeHashes) { torrentMetrics.Remove(item); } removeHashes.Clear(); } } catch (Exception ex) { Console.WriteLine($"Fatal Error: {ex.Message}"); reconnectRequired = true; } System.Threading.Thread.Sleep(TimeSpan.FromSeconds(options.QbitorrentPollSeconds)); } }
private static async void Bot_OnMessage(object sender, MessageEventArgs e) { string messageText = e.Message.Text; ChatId chatId = e.Message.Chat.Id; try { if (chatId.Identifier == _chatId.Identifier) { if (messageText.ToLower() == "/status") { var startTimeInEt = TimeZoneInfo.ConvertTime(_startDateTime, GetEasternTimeZone()); await _botClient.SendTextMessageAsync( chatId : _chatId, text : string.Join(Environment.NewLine, $"Status is good. Running on container '{Environment.MachineName}'. Platform is {GetOsPlatform()}.", $"Bot uptime is {DateTimeOffset.UtcNow - _startDateTime} (since {startTimeInEt})."), parseMode : ParseMode.Html ); } else if (messageText.ToLower() == "/qbittorrentstatus" && string.IsNullOrEmpty(_qBittorrentServer) == false) { // Instantiate the client QBittorrentClient qBittorrentClient = new QBittorrentClient(new Uri($"http://{_qBittorrentServer}:8080")); ApiVersion apiVersion = default; try { await qBittorrentClient.LoginAsync(_qBittorrentUsername, _qBittorrentPassword); apiVersion = await qBittorrentClient.GetApiVersionAsync(); } catch (HttpRequestException ex) when(ex.InnerException is SocketException || ex is QBittorrentClientRequestException) { if ((ex.InnerException as SocketException)?.ErrorCode == 111) { // This means the login failed, which we will handle below. await _botClient.SendTextMessageAsync( chatId : _chatId, text : "There was an error communicating with the qBittorrent server. It may be offline."); return; } if ((ex as QBittorrentClientRequestException)?.StatusCode == HttpStatusCode.Forbidden) { // This means the login failed, which we will handle below. await _botClient.SendTextMessageAsync( chatId : _chatId, text : "There was an error communicating with the qBittorrent server. You may have the wrong username/password."); return; } throw; } // If we get here the server is online. await _botClient.SendTextMessageAsync( chatId : _chatId, text : $"The qBittorrent server is online and running API version {apiVersion}."); } else { foreach (string messageLine in messageText.Split(Environment.NewLine)) { if (Uri.TryCreate(messageLine, UriKind.Absolute, out Uri uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) { // Check if our data directory exists. If not, we can't download anything. if (Directory.Exists("/data/") == false) { await _botClient.SendTextMessageAsync( chatId : _chatId, text : "Data directory mapping does not exist. Cannot process video download request."); break; } // We got a valid URL, now check if it's a YouTube URL // (This handles youtube.com and youtu.be) if (messageLine.Contains("youtu", StringComparison.OrdinalIgnoreCase)) { string processName = "yt-dlp"; string args = $"-f bestvideo+bestaudio -o \"%(uploader)s [%(channel_id)s]/%(upload_date)s [%(id)s].%(ext)s\" --sponsorblock-mark all --sponsorblock-chapter-title \"%(category_names)l\" {messageLine}"; Console.WriteLine($"Got a YouTube URL: {uri}. Executing command '{processName} {args}'"); Process process = Process.Start(new ProcessStartInfo { FileName = processName, Arguments = args, UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true, WorkingDirectory = "/data/" }); // This is important to cause OutputDataReceived to be fired process.BeginOutputReadLine(); Message outputMessage = default; List <string> output = new List <string>(); string lastLine = string.Empty; DateTimeOffset lastMessageSent = DateTimeOffset.MinValue; process.OutputDataReceived += async(_, dataReceivedEventArgs) => { string newLine = dataReceivedEventArgs.Data; // Don't print blank lines if (string.IsNullOrWhiteSpace(newLine)) { return; } Console.WriteLine(newLine); // If the last line contains a % and this line contains a %, replace the last line with this one. if (lastLine.Contains('%') && newLine.Contains('%')) { output.Remove(lastLine); } output.Add(newLine); lastLine = newLine; // Don't send messages more often than 1/s // Do this skip after we've updated the output so that we get all of it by the end, even if we don't send it. if (DateTimeOffset.Now - lastMessageSent < TimeSpan.FromSeconds(1)) { return; } lastMessageSent = DateTimeOffset.Now; if (outputMessage is null) { outputMessage = await _botClient.SendTextMessageAsync( chatId : _chatId, text : string.Join(Environment.NewLine, output)); } // Only edit the message if the text has changed else { try { await _botClient.EditMessageTextAsync( chatId : _chatId, messageId : outputMessage.MessageId, text : string.Join(Environment.NewLine, output)); } catch { // Don't let this any exceptions kill us. // We could get MessageIsNotModifiedException if we try to edit with the same text. // We could get HttpRequestException if we try to edit in too rapid succession. } } }; process.WaitForExit(); // After the process exits, make sure we actually wrote the whole output while (true) { try { await _botClient.EditMessageTextAsync( chatId : _chatId, messageId : outputMessage.MessageId, text : string.Join(Environment.NewLine, output)); break; } catch (HttpRequestException) { } catch (MessageIsNotModifiedException) { // This means we were able to successfully write the final message. break; } } Console.WriteLine($"yt-dlp return value is {process.ExitCode}"); if (process.ExitCode == 0) { outputMessage = await _botClient.SendTextMessageAsync( chatId : _chatId, text : "Video downloaded successfully!"); } else { outputMessage = await _botClient.SendTextMessageAsync( chatId : _chatId, text : "Video download failed."); } } } } } } } catch (Exception ex) { await _botClient.SendTextMessageAsync( chatId : _chatId, text : "There was an error processing your request."); Console.WriteLine(string.Join(Environment.NewLine, "Processing Error.", $"Message: {messageText}", $"Sender name / ID: {e.Message.Chat.Username} / {chatId}", "", ex.ToString())); } }