Maintaining a Telegram channel using GitHub Actions


Probably every developer at least once in their life had the idea to automate something. After all, if there is an opportunity to get rid of the routine, then it is a sin not to use it.


For me, this idea became the basis of many of my own developments, starting with programs for solving Sudoku , counting the time spent at the computer, simulating the work of a PC user using self-written scripts (all this is a long time ago), and ending with more complex projects.


And so, among others, a simple idea was born: "Why not automate the tracking of new releases of IT podcasts using the Telegram bot and GitHub Actions? Just to subscribe to the telegram channel and receive current issues of podcasts as they become available.


Of course, you can download specialized applications, such as "Poket Casts", or subscribe to RSS, but for me personally, using the Telegram channel is the most convenient, simple and familiar.


telegram- @awesome_russian_podcasts, -, . , ( ) .



:


  1. GitHub Actions;
  2. .NET Core Console App;
  3. Luandersonn.iTunesPodcastFinder — iTunes;
  4. Telegram.Bot — API Telegram-;
  5. Git.

2-4 , . , Telegram API HTTP-, API iTunes.


GitHub Actions



GitHub Actions


GitHub Actions , , .


( ) , . .


GitHub Actions :


  • ;
  • "" ;
  • .

, Action-, , , Telegram- Telegram-, .NET Core .



"" (API- Telegram- id Telegram-, ) GitHub Action, :



, id Telegram- Action- , .


.NET Core .


API- Telegram-


Telegram- API-. .


Telegram BotFather — Telegram- :



BotFather /newbot, :



.


BotFather username . , Telegram.


: username bot ( ) ( username , ):



, API- ( API- — ).


P.S. API-, , /mybots BotFather, "API Token". .


Telegram username, "". :



id Telegram-


Telegram- , , id Telegram- .


Telegram- ( ), - , HTTP GET-: https://api.telegram.org/bot<api_key>/getUpdates, <api_key> — API- , :



id chat id Telegram-.


P.S. Telegram, , , HTTP GET- . , Opera , .



Action-.


, . . :


name: Update_Podcasts_Data      #  ,      Actions 

on:                             # ,    
  schedule:                     #   ,    
    - cron: '0 5-20/2 * * *'    # '   2     5  20  UTC+0',    5, 7, 9, 11, 13, 15, 17, 19  UTC+0

jobs:                           #     
  build:

    runs-on: ubuntu-latest      #      ubuntu

    steps:                      # ,    
    - uses: actions/checkout@v2 #    
    - name: Setup .NET Core                                 #  1- 
      uses: actions/setup-dotnet@v1                         #  ,    .NET 
      with:                                                 #  :
        dotnet-version: 3.1.101                             #      .NET Core
    - name: Set chmod to Unchase.HtmlTagsUpdater            #  2- 
      run: chmod 777 ./utils/Unchase.HtmlTagsUpdater        #  ,      .NET Core Console App - "Unchase.HtmlTagsUpdater"
    - name: Run Unchase.HtmlTagsUpdater (with Telegram Bot) #  3- 
      env:                                                  #      
        TG_KEY: ${{ secrets.TgKey }}                        # TG_KEY   ""    "TgKey" -  API-   Telegram- 
        TG_CHANNEL_ID: ${{ secrets.TgChannelId }}           # TG_CHANNEL_ID   ""    "TgChannelId" -  id ,      

      #    .NET Core      :
      # -f -   ("Podcasts.md" -     -        )
      # -t -    (    2 :   - "iTunesPodcast",   YouTube-)
      # -a - API-  Telegram-,      TG_KEY
      # -c - id       TG_CHANNEL_ID
      # -i -   Telegram.Bot  
      #     ,     
      run: ./utils/Unchase.HtmlTagsUpdater -f "Podcasts.md" -t "iTunesPodcast" -a "$TG_KEY" -c "$TG_CHANNEL_ID" -i "90"

    #        telegram-,       "Podcasts.md"   
    - name: Git set author (email)  #  4- 
      run: /usr/bin/git config --global user.name "GitHub Action Unchase"   #   ,     commit

    - name: Git set author (email)  #  5- 
      run: /usr/bin/git config --global user.email "spiritkola@hotmail.com" #  email ,     commit

    - name: Git add                 #  6- 
      run: /usr/bin/git add Podcasts.md #  ()     commit                 

    - name: Git commit              #  7- 
      run: /usr/bin/git commit -m "Update podcasts data"    #  commit

    - name: Git push
      run: /usr/bin/git push origin master  #  push     

, Action- . , , commit, . . .NET Core "Unchase.HtmlTagsUpdater". , .


.NET Core Console App


"Unchase.HtmlTagsUpdater" — .NET Core , . : , , , .


nuget- CommandLineParser. :


using System.Collections.Generic;
using System.IO;
using CommandLine;
using Telegram.Bot.Types;
using File = System.IO.File;

public enum UtilType
{
    YouTube,

    iTunesPodcast
}

public class Options
{
    [Option('f', "file", Required = true)]
    public string InputFile { get; set; }

    [Option('t', "type", Required = true)]
    public UtilType Type { get; set; }

    [Option('a', "tgapi", Required = false)]
    public string TelegramBotApiKey { get; set; }

    [Option('c', "tgchannel", Required = false)]
    public ChatId TelegramChannelId { get; set; }

    [Option('i', "tgtimeout", Required = false)]
    public int TelegramTimeout { get; set; }

    public string ReadAllTextFromInputFile()
    {
        if (!File.Exists(InputFile))
        {
            throw new FileNotFoundException("Input file does not exist!", InputFile);
        }
        return File.ReadAllText(InputFile);
    }

    public void WriteAllTextToInputFile(string text)
    {
        if (!File.Exists(InputFile))
        {
            throw new FileNotFoundException("Input file does not exist!", InputFile);
        }
        File.WriteAllText(InputFile, text);
    }
}

Main Parser.Default.ParseArguments:


internal static Options Options { get; private set; }

static void Main(string[] args)
{
    Console.WriteLine("Start!");

    //     ,    Options
    var parseResult = Parser.Default.ParseArguments<Options>(args)
        .WithParsed(o =>
        {
            Options = o;
        });

    //      ,    
    if (Options == null || parseResult.Tag != ParserResultType.Parsed)
    {
        //        GitHub Action
        Console.WriteLine("Error: Options was not parsed!");
        return;
    }

    //...

    //      
    var text = Options.ReadAllTextFromInputFile();

    switch (Options.Type)
    {
        // ...
        case UtilType.iTunesPodcast:
            //   iTunes-
            text = ProcessPodcasts(text);
            break;
    }

    //       
    Options.WriteAllTextToInputFile(text);

    Console.WriteLine("Done!");
}

ProcessPodcasts SendPodcastData:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTunesPodcastFinder;
using iTunesPodcastFinder.Models;
using Telegram.Bot;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;

//     API Telegram-
private static ITelegramBotClient _telegramBotClient;
internal static ITelegramBotClient TelegramBotClient
{
    get
    {
        if (_telegramBotClient != null)
        {
            return _telegramBotClient;
        }

        if (!string.IsNullOrWhiteSpace(Options.TelegramBotApiKey) && !string.IsNullOrWhiteSpace(Options.TelegramChannelId))
        {
            //      API Telegram-
            //     , ,      TelegramBotClient     - 
            // ,       nuget- 'HttpToSocks5Proxy'
            //    GitHub Actions ,  ,  
            _telegramBotClient = new TelegramBotClient(Options.TelegramBotApiKey) { Timeout = new TimeSpan(0, 0, Options.TelegramTimeout) };
        }

        return _telegramBotClient;
    }
}

private static string ProcessPodcasts(string text)
{
    //   span'   ,        
    //      : '<span itunes-id="1120110650" class="episodes" hashtag="_">35 (<font color="red">0</font>)</span>'
    foreach (var span in GetSpans(text, "episodes"))
    {
        //    id   iTunes. , '1120110650'
        var iTunesPodcastId = GetAttributeValue(span, "itunes-id");
        if (string.IsNullOrWhiteSpace(iTunesPodcastId))
            continue;

        try
        {
            //       id  iTunes
            Podcast podcast = PodcastFinder.GetPodcastAsync(iTunesPodcastId).GetAwaiter().GetResult();
            if (podcast == null)
                continue;

            //    (    )  span'
            var newValue = podcast.EpisodesCount.ToString();

            //       Telegram-
            SendPodcastData(podcast, span, newValue);

            // ...

            //      
            //       
            text = text.Replace(span, SetSpanValue(span, newValue));
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}{Environment.NewLine}Podcast id = {iTunesPodcastId}");
        }
    }

    //         
    return text;
}

// ...

private static void SendPodcastData(Podcast podcast, string span, string newValue)
{
    //      
    var currentSpanValue = GetSpanValue(span);
    if (long.TryParse(currentSpanValue, out var currentSpanLongValue) && long.TryParse(newValue, out var newSpanLongValue))
    {
        var diff = newSpanLongValue - currentSpanLongValue;

        //    ...
        if (diff > 0)
        {
            try
            {
                //    
                var episodes = PodcastFinder.GetPodcastEpisodesAsync(podcast.FeedUrl).GetAwaiter().GetResult()
                    ?.Episodes?.OrderByDescending(e => e.PublishedDate)?.ToList();

                if (episodes?.Any() == true && episodes.Count >= diff)
                {
                    for (int i = (int)diff - 1; i >= 0; i--)
                    {
                        //   ,   Telegram-
                        var message = new StringBuilder();

                        // ...    

                        message.AppendLine("@awesome\\_russian\\_podcasts");

                        if (!string.IsNullOrWhiteSpace(Options.TelegramBotApiKey) && TelegramBotClient != null)
                        {
                            //    Telegram-  Telegram-
                            TelegramBotClient.SendPhotoAsync(Options.TelegramChannelId,    // id Telegram-
                                podcast.ArtWork,    //    iTunes
                                $"{message.ToString()}",    //  (  )
                                ParseMode.Markdown, // ,     Markdown
                                true,   //  push-   
                                //    
                                replyMarkup: new InlineKeyboardMarkup(new List<InlineKeyboardButton> {
                                    InlineKeyboardButton.WithUrl(
                                        "iTunes",
                                        podcast.ItunesLink),
                                    InlineKeyboardButton.WithUrl(
                                        "Episode",
                                        episodes[i].FileUrl.ToString()),
                                    InlineKeyboardButton.WithUrl(
                                        "Feed URL",
                                        podcast.FeedUrl)
                                })).GetAwaiter().GetResult();

                            // ...
                        }
                    }
                }
            }
            catch (Exception e)
            {
                var errorMessage = new StringBuilder();
                //        
                // ...

                Console.WriteLine(errorMessage.ToString());
            }
        }
    }
}

, .



GitHub Actions Telegram-: , , … : CI/CD , GitHub Pages, ..


. .


I am convinced that channels should benefit not only its creators, so if you like listening to IT podcasts, or you are looking for new knowledge, join the @awesome_russian_podcasts channel and add your favorite Russian-language IT podcasts to the repository, so that about them other people could hear.


If you are an adherent of youtub and watching a video, then for you there is a similar channel - @awesome_russian_youtube .


Thank you for reaching the end. Be healthy and do not go astray. Good luck


All Articles