Pull to refresh

Немножко велосипедостроения под Linux или RSS-агрегатор на коленке своими руками

Reading time9 min
Views8.1K
Навеяно статьей «Google продолжает уничтожать RSS».

Некоторое время назад возникла у меня надобность читать несколько rss-лент. Вопрос для меня был относительно новым, ранее с rss я дело имел весьма эпизодически, так что начал изучать эту тему и выбирать ридер с чистого листа.

Результаты были… скажем так — не радующими.

На облачные сервисы с веб-интерфейсами, у меня хроническая аллергия пополам с паранойей, и последовавшие похороны гугльридера лишний раз подтвердили, что паранойя плохого не посоветует. Да и регулярные изменения привычных интерфейсов у веб-сервисов тоже не радуют.

Среди локально ставящихся ридеров нашлось несколько штук, которыми я попытался пользоваться, но ни один из них так и не был выбран в качестве рабочего. Тут и раздражающие мелочи, вроде плохо настраиваемого интерфейса, убогих инструментов по фильтрации или странная логика сохранения; и более глобальная проблема с тем, что одной из лент, которые хотелось смотреть, были флибустянские отзывы о книгах, где за сутки запросто может обновиться вся лента, и часть постов в итоге будет пропущена; постоянно же держать ридер запущенным — совершенно не вдохновляющий вариант. А кроме того зоопарк из разнородных читалок на машине разводить не хотелось совершенно.

И тогда, после некоторого времени размышлений о несовершенстве этого мира, и формулирования хотелок — в руки привычно был взят напильник и пара дней была посвящена велосипедостроению.



Результат успешно работает уже год с лишним. Возможно, готовый рецепт кому-то сэкономит время и силы, хотя конечно, рецепт может подойти далеко не всем, поскольку основан он был на том, что уже имелся домашний сервер под Centos 6, круглосуточно подключенный к интернету.

Итак, для построения собственного агрегатора из спичек и желудей, кроме собственно сервера, мне понадобились дополнительно пакеты:
rsstool
ssmtp
fdupes
… а также некоторое время и желание повозиться с perl.

В итоге был написан довольно примитивный скрипт, приводить который полностью нет особого смысла, достаточно будет общей логики работы:

После запуска скрипт считывает список лент из текстового конфига в виде:
habrahabr#http://habrahabr.ru/rss — то есть заголовок, который будет использоваться дальше для работы с лентой, разделитель "#" и сам адрес ленты. Все это, плюс сгенерированные по заголовку имена файлов для сохранения промежуточных данных, заносится в несколько массивов. Затем по этим массивам проходимся foreach(), в котором выполняются следующие действия:

1. Через rsstool скрипт скачивает саму ленту в виде csv с разделителями @, iconv конвертирует скачанное в кодировку koi-8, grep отрезает заголовок, и все это сохраняется в файл для свежескачанного.

rsstool --wget --csv=@  @url[$i](то есть http://habrahabr.ru/rss) | iconv -c -f UTF-8 -t KOI8-R| grep SITE\@DATE\@URL\@TITLE\@DESC -v >/path_to_rss2email/feed_new_@rssname[$i].rss (то есть /path_to_rss2email/feed_new_habrahabr.rss)  


2 Натравливаем diff на файл с архивом ленты и файл со свежескачанным. Разница между ними, помеченная ">" и будет требующимися нам новостями, которые появились с момента последнего скачивания. Эта разница записывается в отдельный файл, а также в архив ленты — чтобы при следующем скачивании эти сообщения были отмечены как уже полученные.

diff -iaEbwB --strip-trailing-cr $path_temp/@rssarcfile[$i] (т.е. /path_to_rss2email/habrahabr.rss) $path_temp/feed_new_@rssname[$i].rss (т.е. /path_to_rss2email/feed_new_habrahabr.rss) | grep ^\\>\\  >$path_temp/@rssdiffer[$i] (т.е. /path_to_rss2email/habrahabr.diff)  


3. Теперь можно начинать радоваться — мы получили файл habrahabr.diff с последними сообщениями ленты, и теперь с этим файлом мы можем сделать вообще всё. Что и делаем, построчно считывая файл, а затем, разбирая строчки вида:

«Хабрахабр / Захабренные / Тематические / Посты»@«1399799340»@«habrahabr.ru/post/222391»@«Google продолжает уничтожать RSS»@«На этой неделе, а именно 8 мая, Google отключил RSS-ленту <...cut...>. Читать дальше →»

… создаем в отдельном каталоге файлы согласно формату RFC2822. После этого скрипт продолжает свою работу, обрабатывая следующую ленту в списке, или же засыпает минут на -дцать; а сформированные письма, тем временем, обрабатывает запускающийся по cron'у второй скрипт. Он при помощи fdupes удаляет в каталоге с файлами RFC2822 все одинаковые файлы, кроме одного… (Да, знаю, это костыль. Да, стыдно. Но проблема с тем, что отдельные дублирующиеся строки, несмотря ни на что, всё же не вычищаются diff'ом, всплыла несколько позже написания скрипта, а времени разбираться не нашлось.)… а затем, используя ssmtp, отправляет их на специально заведенный почтовый ящик.

И теперь, получив ленту на почту, мы сможем спокойно, не страдая от очередного приступа креативности дизайнеров и судьбоносных решений менеджеров, читать её везде, в одном и том же привычном и любимом почтовом клиенте (том же самом TheBat!, например), пользуясь при этом всем богатым инструментарием в виде фильтров, сортировок, поиска и прочих полезностей, которые сделают чтение ленты удобнее и эффективнее.

UPD: Немножко причесав и откомментировав выкладываю собственно скрипты. Первый делает собственно скачивание лент и создает файлы с постами для отправки на почту, второй — отправляет их. Оба запускаются по cron. Ну и плюс кусочек конфига для примера.

rsstoemail.pl


#! /usr/bin/perl   
use MIME::Base64;

$path_temp="/home/media/rsstool";
# каталог для временных файлов/архивов


# считываем файл конфигурации и заносим разобранную строчку  в массивы - заголовок ленты, url ленты, файл с архивом ленты 
open(config,"</etc/rss/rsstoemail.conf");
$numb=0;
while (!eof(config))
{
 $strcnf=<config>;
 $strcnf=~tr/\n//d;
 (@rss[$numb],@rssname[$numb],@rssfeeds[$numb])=split "#", $strcnf;
 @rssarcfile[$numb]="@rssname[$numb].rss";
 @rssdiffer[$numb]="@rssname[$numb].diff";
 $numb=$numb+1;
}
close config;
# считываем файл конфигурации и заносим разобранную строчку  в массивы - заголовок ленты, url ленты, файл с архивом ленты 



# если файла для архива ленты нет - создаем его
foreach $srtname1(@rssarcfile)
{
 if (! -e "$path_temp/$srtname1")
  {
   `echo >$path_temp/$srtname1`;
  }
}
# если файла для архива ленты нет - создаем его



# проверим нет ли уже запущенной копии если есть - выходим
$run=`ps -AH|grep -c rsstoemail.pl`;
$run=~tr/\n//d;
if ($run ne "1")
 {
  exit;
 }
# проверим нет ли уже запущенной копии если есть - выходим



# обрабатываем список лент
foreach $i(@rss)      
      {
           # если в строчке из списка что-то есть
           if (@rss[$i] ne "")
             {
        
              $chkdiff="";

              # обнуляем дифф-файл с новыми постами
              `echo >$path_temp/@rssdiffer[$i]`;
              printf "run rsstool\n";

              # скачиваем ленту, конвертируем текст в koi-8, вырезаем заголовок и сбрасываем во временный файл
             `/sbin/myscript/rsstool --wget --csv=@  @rssfeeds[$i] | iconv -c -f UTF-8 -t KOI8-R| grep SITE\@DATE\@URL\@TITLE\@DESC -v >$path_temp/feed_new_@rssname[$i].rss`;

              # сравниваем временный файл с архивом ленты, результаты записываем в дифф
             `diff -iaEbwB --strip-trailing-cr $path_temp/@rssarcfile[$i] $path_temp/feed_new_@rssname[$i].rss | grep ^\\>\\  >$path_temp/@rssdiffer[$i]`;

             # проверяем сколько в получившемся диффе строчек
             $chkdiff=`grep \@ $path_temp/@rssdiffer[$i] -c`;
             $chkdiff=~tr/\n//d;

             # если в нем что-то есть
             if ($chkdiff ne "0")
               {
                my @toemail = ();
                my @original = ();

                # открываем дифф, считываем построчно, из строчек вырезаем > оставшийся после diff 
                # результат заносим в массив для обработки и массив с исходником
                open(diffopen,"<$path_temp/@rssdiffer[$i]"); 
                while (!eof(diffopen))
                    {
                     $string = <diffopen>;    
                     $string=~s/^(\>\ )//;
                     $string=~tr/\n//d;    
                     push (@toemail, $string);
                     push (@original, $string);
                    }
                close diffopen;
                # открываем дифф, считываем построчно, из строчек вырезаем > оставшийся после diff 
                # результат заносим в массив для обработки и массив с исходником

         # отправка на почту
               
                # создаем на основе даты и времени уникальную строку и сообщаем когда и сколько было новых постов - для отладки
                $dirr=`date '+%Y-%m-%d-%H_%M_%S'`; 
                $newm=$#toemail+1;
                printf "$dirr new messages $newm\n";
                # создаем на основе даты и времени уникальную строку и сообщаем когда и сколько было новых постов - для отладки


                # проходим по массиву с новыми постами
                foreach $difs(@toemail)
                      {
                       # обрабатываем строчку перед созданием письма 
                       # убираем символы "
                       $difs=~tr/\"//d;
                       # убираем конечный пробел 
                       $difs=~s/\ $//;     

                       $nmf="";
                       $date="";
                       $url="";
                       $bookname="";
                       $body="";
                       $resser="";
                       $ssurl="";
                       $sndusr="";

                       # разбираем строку в переменные
                       ($nmf,$date,$url,$bookname,$body)=split '@', $difs;



                   # дополнительная обработка текста, отправляемого на почту

                       # в тексте для ленты отзывов флибусты и либрусека вырезаем и заносим в переменную ник оставившего отзыв
                       if (@rssname[$i] eq "flibusta" or @rssname[$i] eq "librusec")
                         {
                          $body=~m/(.+?)(\ про\ )/;      
                          $sndusr=$1;
                          $sndusr=~tr/\n//d;
                         }
                       # в тексте для ленты отзывов флибусты и либрусека вырезаем и заносим в переменную ник оставившего отзыв


                       # в ленте отзывов и новых поступлений флибусты url меняем на единообразный (поскольку там может быть
                       #  несколько вариантов proxy.flibusta.net, flibusta.net, www.flibusta.net ) и делаем сразу url для скачивания
                       if (@rssname[$i] eq "flibusta" or @rssname[$i] eq "flibustanewbooks")
                          {
                           ($resser,$ssurl)=split "\/b\/",$url;
                           $resser=~m/(flibusta.net)/;    
                           if ($1 eq "flibusta.net")
                              {
                               $surl="http://flibusta.net/b/$ssurl/download\n";
                              }
                           else
                              {
                               $surl=$url;
                              }
                          }
                       else
                          {
                           $surl=$url;
                          }
                       # в ленте отзывов и новых поступлений флибусты url меняем на единообразный (поскольку там может быть
                       #  несколько вариантов proxy.flibusta.net, flibusta.net, www.flibusta.net ) и делаем сразу url для скачивания



                       # в ленте новых поступлений флибусты изменяем строку в формат название книги - автор - жанр
                       if (@rssname[$i] eq "flibustanewbooks")
                          {
                           ($book_auth,$book_name,$book_genre)=split "- ",$bookname;
                            $bookname="$book_name - $book_auth - $book_genre";
                          }
                       # в ленте новых поступлений флибусты изменяем строку в формат название книги - автор - жанр


                 # дополнительная обработка текста, отправляемого на почту



                     # строку заголовка кодируем в base64 и добавляем строку с кодировкой для поля SUBJ:
                     $booknameenc=encode_base64("$bookname my_@rssname[$i]_rss");
                     $booknameenc=~tr/\n//d;
                     $booknameenc="\=\?KOI8\-R\?B\?$booknameenc\?\=";
                     # строку заголовка кодируем в base64 и добавляем строку с кодировкой для поля SUBJ:

                    

                    # спим секунду и генерируем уникальную строку на основе времени
                    sleep 1;
                    $dirr=`date '+%Y-%m-%d-%H_%M_%S'`; 
                    # спим секунду и генерируем уникальную строку на основе времени
                    

                    # создаем файл с письмом в каталоге для отправки почты с именем вида msgrss_2014-05-20-19_26_09 и начинаем записывать туда текст письма
                    open(fopen,">>$path_temp/mail/msgrss_$dirr");
                    print fopen "From\:\ fromemail\@domen\.ru\n";
                    print fopen "To\:\ toemail\@gmail\.com\n";
                    print fopen "Subject\: $booknameenc\n";
                    print fopen "MIME-Version: 1.0\n";
                    print fopen "Content-Type: multipart/mixed;\n";
                    print fopen " boundary=\"----------12012917B16D15D68\"\n";
                    print fopen "------------12012917B16D15D68\n";               
                    print fopen "Content-Type: text/plain; charset=koi8-r\n";    
                    print fopen "Content-Transfer-Encoding: 8bit\n";             
                    print fopen "\n";
                    print fopen "$nmf\n";
                    print fopen "$surl\n";
                    print fopen "$bookname\n";
                    print fopen "\n";
                    if ($sndusr ne "")
                      {
                       print fopen "sendbyuser:$sndusr\n";
                      }
                    print fopen "\n";
                    print fopen "$body\n";
                    print fopen "\n";
                    close fopen;
                    # создаем файл с письмом в каталоге для отправки почты с именем вида msgrss_2014-05-20-09_26_09 и начинаем записывать туда текст письма
                  }

             # после завершения обработки одной ленты из списка

              # копируем файл с новыми постами в каталог архива с уникальным именем - включено для отладки
              `cp $path_temp/@rssdiffer[$i] $path_temp/arhiv/diffnew\_$dirr`;
              # добавляем пустую строку в файл с архивом лент - включено для отладки
              `echo >>$path_temp/@rssarcfile[$i]`;
             

              # в файл архива ленты загоняем исходные новые посты
              open(arcrss,">>$path_temp/@rssarcfile[$i]"); 
              foreach $difffs(@original)
                     {
                      $difffs=~s/^(\>\ )//; 
                      $difffs=~tr/\n//d;    
                      print arcrss "$difffs\n";                     
                     }
              close(arcrss);
             # в файл архива ленты загоняем исходные новые посты





           }
        }     
     }
     
     
printf "start send messages\n";

# ночью производим убивание возможных дубликатов в архиве ленты
if ($hour eq "04" and $min<"30")
  {
   foreach $srtname(@rssarcfile)
          { 
           `uniq -u $path_temp/$srtname >$path_temp/$srtname.tmp`;
           `mv -f $path_temp/$srtname.tmp $path_temp/$srtname`;
          }
   sleep 1000;
  }
# ночью производим убивание возможных дубликатов в архиве ленты



rsssendemail.pl
#! /usr/bin/perl   

# общий каталог временных файлов
$path_all="/home/media/rsstool";

# подкаталоги
$path_mail="$path_all/mail";
$path_mail_arhiv="$path_all/mail_arhiv";
$path_arhiv="$path_all/arhiv";

# получаем список дублицирующихся файлов  без _send в конце
@list_dupes=`/sbin/myscript/fdupes -f $path_mail|grep -v send|grep -v \^\$`;

# проходим по списку и удаляем дубликаты
foreach  $fl(@list_dupes)
       { 
        $fl=~tr/\n//d;
        `rm -f $fl`;
        printf "rm dupe $fl\n";
       }
# проходим по списку и удаляем дубликаты


# получаем список оставшихся файлов без _send на конце
@list_files=`ls $path_mail/ |grep -v send`; 
sleep 5;

# проходим по списку отправляя файлы через ssmtp а затем переименовываем отправленные в *_send
foreach $i(@list_files)
 {
  $i=~tr/\n//d;
  printf "sended $path_mail/$i\n";
  `/sbin/myscript/ssmtp toemail\@gmail.com \<$path_mail/$i`;
  `mv $path_mail/$i $path_mail/$i\_send`;
  sleep 1;
 }
# проходим по списку отправляя файлы через ssmtp а затем переименовываем отправленные в *_send

# перекидываем  в архив все файлы кроме последней тысячи
@all_files=`ls $path_mail/`;         
$cont=$#all_files-1000;               
if ($cont > 0)                       
{                                    
printf "bigger 1000 to $cont\n";                    
 for ($y=0; $y<$cont;$y++)           
  {                                   
   @all_files[$y]=~tr/\n//d;
  `mv $path_mail/@all_files[$y] $path_mail_arhiv`;
   printf "@all_files[$y]\n";         
  }                                   
}                                    
# перекидываем  в архив все файлы кроме последней тысячи




rsstoemail.conf
1#librusec#http://lib.rus.ec/polka/show/all/rss
2#flibusta#http://flibusta.net/polka/show/all/rss
3#nnm#http://nnm.me/rss/
4#habrahabr#http://habrahabr.ru/rss
5#3dnewssoft#http://www.3dnews.ru/software-news/rss/
6#3dnewshard#http://www.3dnews.ru/news/rss/
7#flibustanewbooks#http://flibusta.net/new/rss




Tags:
Hubs:
+17
Comments11

Articles

Change theme settings