<a href="https://www.explainxkcd.com/wiki/index.php/1783">xkcd Emails</a>

البريد الإلكتروني من أكثر طرق التواصل التي درج الناس على استعمالها وما زالوا. ومن المهم متابعة حالة صندوق الوارد دورياً للتأكد من ورود رسائل جديدة. عندما تعتمد بريداً واحداً لعملك وحياتك فذلك سهل، ولكن المشكلة عندما تكثر هذه العناوين وتكون على مخدمٍ واحد، كـGoogle أو Yahoo. يُقدّم الأخيرين خاصية تسجيل الدخول باسمين منفصلين، ولكن ذلك لا يكفي عندما تكون العناوين أكثر من عشرة عناوين مثلاً. كتبت البرنامج التالي الذي يقوم بسرد عناوين البريد، مهما كان عددها بأسرع سرعة ممكنة (مستخدماً المسارب Threads)، ثم يقوم بكتابة تفاصيل ذلك في ملف (.myemailsunreads) ويعرضها باستخدام التنبيهات عند الانتهاء، أو عند النقر بالفأرة.

البرنامج يستخدم pass مدير كلمات السر في أنظمة unix، لذلك يجب إعداد pass قبل تشغيل البرنامج أو إعداده، وذلك يتطلب قبلاً إنشاء زوج مفاتيح GPG. يمكنك مراجعة مقال GNU Privacy Guard Tutorial كمقدمة سريعة لذلك. ولكن يمكن استخدام أي برنامج آخر مشابه يدعم التصدير، إذ كنت في إصدارٍ سابق (مكتوب بالـPython) أستخدم نصاً على هيئة email:password مشفراً باستخدام cat passwords | gpg --symmetric | base64 لكن الطريقة الأولى أيسر وأدق ولا تحتاج إلى تحديث عند تغيير كلمة السر، عدا تغييرها في pass.

يجب أن تكون جميع عناوين البريد على مسار Email/* في شجرة pass. ويجب أن تكون جميع كلمات سر جميع العناوين كلمات سر صحيحة.

يدعم البرنامج تخصيص عنوان بريدٍ واحد على أنه عنوان هام، كبريد العمل مثلاً، وعندما يحتوي هذا العنوان على رسائل جديدة فإنه سوف يعرض التنبيه بأهمية الطوارئ، وتلوين الرمز في شريط i3blocks بالأحمر. وستجد ذلك مؤشراً في البرنامج بعبارة CHANGE THIS في التعليق لتغير القيمة لما يناسب.

إعدادات ملف الـi3blocks config

[email]
command=$HOME/scripts/i3blocks/email
interval=590

البرنامج على هذا الإعداد يقوم بالاتصال والبحث عن كلمات سر جديدة كل 590 ثانية، وذلك لأن جلسة برنامج gpg معرّفة على أن تنتهي كل 600 ثانية افتراضياً، فهذه القيمة الافتراضية حتى لا أعيد كتابة كلمة السر كلما أراد البرنامج إعادة البحث.

إذا أردت زيادة هذه القيمة عن 600، ولم ترد إعادة كلمة السر، يجب زيادة هذه القيمة في ملف ~/.gnupg/gpg-agent.conf:

default-cache-ttl 700
max-cache-ttl 700

الوسيط -CS المُمرر لمفسر لغة Perl لدعم المحارف غير القياسية (العربية هنا بترميز UTF-8).

البرنامج يدعم IMAP كبروتوكول قياسي ولا يستخدم POP3.

البرنامج:

#!/bin/perl -CS

use strict;
use warnings;
no warnings 'experimental';
use utf8;
use v5.10;

use threads;
use File::Basename;
use File::Slurper qw(read_text write_text read_lines);
use Net::IMAP::Simple;

my $urgency_email = 'me@work.co'; # CHANGE THIS
my %emails;
my %unreads;
my $total_unread = 0;
my $color        = "#FFFFFF";
my @threads;

my %imap_servers = ( 'google'  => 'imap.googlemail.com',
                     'hotmail' => 'imap-mail.outlook.com',
                     'yahoo'   => 'imap.mail.yahoo.com',
                     'work'    => 'mail.work.co', # CHANGE THIS
                     'aol'     => 'imap.aol.com',
                     'icq'     => 'imap.mail.ru',
                     'yandex'  => 'imap.yandex.com', );

sub notify {
  my @text = read_lines("$ENV{HOME}/.myemailsunreads");
  chomp $_ for @text;
  my $urgency = 0;
  for (@text) {
    $urgency = 1 if ( $_ =~ m/\Q$urgency_email\E.*$/ );
  }

  my $text = join "\\n", @text[ 1 .. $#text ];
  if ($urgency) {
    $color = "#EE0000";
    system "notify-send -u critical \"$text\"";
  } else {
    $color = "#FFFFFF";
    system "notify-send \"$text\"";
  }
}

sub get_imap_unread {
  my ( $imap_username, $imap_password, $imap_server,
       $full_email_string ) = @_;

  return
    if not $imap_username
    or not $imap_password
    or not $imap_server
    or not $full_email_string;

  my $imap = Net::IMAP::Simple->new( $imap_server, use_ssl => 1 )
    || die
    "$full_email_string: Unable to connect to IMAP server [$imap_server]: $Net::IMAP::Simple::errstr\n";

  if ( !$imap->login( $imap_username, $imap_password ) ) {
    print STDERR "$full_email_string: Login failed: "
      . $imap->errstr . "\n";
    exit(64);
  }

  $imap->select('INBOX');

  my $imap_unread = $imap->unseen;

  $imap->close();

  return { $full_email_string => $imap_unread };
}                               ## end sub get_imap_unread

sub main {

  for ( glob("~/.password-store/Email/*") ) {
    my $email    = basename($_);
    my $username = ( split /@/, $email )[0];
    my $pass     = `pass "Email/$email/$username"`;
    $pass = ( split /\n/, $pass )[0];
    $emails{$email} = $pass;
  }

  die "Error in decrypting password.\n" if not %emails;

  for ( keys %emails ) {
    my $username = $_;
    my $server   = ( split /@/, $_ )[1];
    my $password = $emails{$_};

    for ($server) {
      when ('gmail.com') { $server = $imap_servers{google} }
      when (/(hotmail\.(com|fr))|outlook\.com|live\.com/) {
        $server = $imap_servers{hotmail}
      }
      when ('work.co')   { $server = $imap_servers{work} } # CHANGE THIS
      default { $server =~ s/(.+)\..+?$/$1/;
                $server = $imap_servers{$server};
              }
    }

    if ( $server and $username and $password ) {
      push @threads,
        threads->new( \&get_imap_unread, $username, $password, $server,
                      $_ );
    }
  }

  for (@threads) {
    my $r = $_->join();
    my $k = ( keys %{$r} )[0];
    $unreads{$k} = $r->{$k};
  }

  for ( values %unreads ) {
    $total_unread += $_;
  }
  say " ($total_unread)" if $total_unread;
  say " ($total_unread)" if $total_unread;

  if ( $unreads{$urgency_email} ) {
    $color = "#EE0000";
  } else {
    $color = "#FFFFFF";
  }
  say $color if $total_unread;

  my $text = "$total_unread\n";
  for ( keys %unreads ) {
    $text .= "$_ ($unreads{$_})\n" if $unreads{$_};
  }
  write_text( "$ENV{HOME}/.myemailsunreads", $text );

  notify();
}                               ## end sub main

if ( $ENV{'BLOCK_BUTTON'} and $ENV{'BLOCK_BUTTON'} == 1 ) {
  my @out = read_lines("$ENV{HOME}/.myemailsunreads");
  say " ($out[0])";
  say " ($out[0])";

  notify();
  say $color;
  exit 0;
} elsif ( $ENV{'BLOCK_BUTTON'} and $ENV{'BLOCK_BUTTON'} == 3 ) {
  main();
}

main();

تنبيه عادي

تنبيه هام