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);
            }
        }
Beispiel #2
0
 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);
         }
     }
 }
Beispiel #3
0
        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)}.");
        }
Beispiel #4
0
        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));
            }
        }
Beispiel #5
0
        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()));
            }
        }