Własna lista dyskusyjna czyli Rails 3 i mailman

15 Sep 2012 | Adam Przymusiała

Tym razem chciałbym podzielić się moimi ostatnimi przygodami z budową listy dyskusyjnej. Jest z tym sporo zabawy, użerania się z kodowaniem maili i zabezpieczeniami ale efekt końcowy jest bardzo przyjemny :).

Do rzeczy: aby postawić własną listę dyskusyjną należy najpierw stworzyć aplikację RoR 3, a następnie w Gemfile dodać gemy: mailman oraz daemons (pierwszy odpowiada za pobieranie maili a drugi za odpalenie listy jako proces w tle. Dokumentacja jest w linkach).

Kolejnym krokiem jest stworzenie skryptu listy dyskusyjnej, polecam np.: lib/mailman_app.rb :

require 'mailman'

# Warto dołączyć środowisko aplikacji aby później pozbyć się konieczności uruchamiania jako rails runner
require File.expand_path(File.join(File.dirname(__FILE__), '../') + 'config/environment.rb')

Mailman.config.logger = Logger.new("#{Rails.root}/log/mailman.log")

# Teraz ważne: Mailman::Application zadziała tylko raz jeśli ustawimy poll_interval na 0.
# W innym przypadku raz odpalony nie będzie mógł być modyfikowany.
# Chodzi o to żeby za każdym razem startował od nowa i pobierał na nowo skład uczestników listy.
Mailman.config.poll_interval = 0

# Przykładowa konfiguracja
Mailman.config.pop3 = {
  :username => 'catch-all@example.pl',
  :password => '***********',
  :server   => 'pop.example.pl',
  :port     => 995,
  :ssl      => true
}
end

Teraz część zasadnicza:

# Uruchamiamy w pętli nieskończonej
  while true
  # Startuje mailman
  Mailman::Application.run do
    # Za każdym wejściem w pętlę ładujemy na nowo członków grup (zakładam że grupy zmieniają co jakiś czas swój skład).
    Group.find(:all, :include => :members).each do |group|
      # Właściwa część skryptu, zakładam że pole email w modelu Group jest nazwą maila grupy
      to("#{group.email}@listy.example.pl") do
        # Tutaj obrabiamy każdego maila, sprawdzamy czy nadawca może pisać na listę itp.
        # W tym bloku następuje wysłanego listu do wszystkich członków grupy.
      end
      # Kasujemy te które nie pasują do grup które mamy.
      default do ; end
    end
  end
  # Możemy dać jakiegoś sleep'a
  sleep(10) if Rails.env == 'production'
  end

Warto pamiętać o tym że:

  • nadawca może siedzieć w message.from lub w message.sender i czasem może być to tablica ;)
  • message.multipart? powie nam jakiego rodzaju jest to wiadomość
  • message.attachments.empty? dowiemy się czy są jakieś załączniki
  • message.subject to oczywiście temat
  • message.parts[0].body to treść wiadomości jeśli jest ona multiart
  • message.parts[0].content_type_parameters może przechować hasha z kodowaniem (klucz to :charset)

Jeśli chcemy obsłużyć załączniki to warto je przechwycić w zasadniczej części skryptu. Można to zrobić np. tak:

unless message.attachments.empty?
  attachments = []
  message.attachments.each do |attachment|
    attachments && [attachment.filename, attachment.decoded]
  end
  group.emails.each { |email| List.message("#{group.email}@listy.example.pl", email, message.subject, message.parts[0].body, sender, group.name, (message.parts[0].content_type_parameters.nil? ? nil : message.parts[0].content_type_parameters), attachments).deliver }
  end

W mailerze List należy ustawić tablicę attachments. Załóżmy że ostatni parametr w metodzie message nazywa się mail_attachments

  unless mail_attachments.nil?
    mail_attachments.each { |m_a| attachments["#{m_a[0]}"] = m_a[1]} unless mail_attachments.empty?
  end
  mail(:from => from, :to => to, :subject => "[Nasza lista | #{group_name}] #{subject}".gsub(/\\[.*\\]/, "[Nasza lista | #{group_name}]"))

Jeśli są problemy z kodowaniem to treść maila warto przekonwetować. Poniższe to wielkie uproszczenie. Należy sprawdzić czy message.content_type_parameters nie jest nil’em. Zmienną body można użyć do zarchiwizowania treści maila w bazie lub do przesłania jej dalej.

body = Iconv.conv('UTF-8', message.content_type_parameters[:charset], message.parts[0].body.decoded)

Ostatnim krokiem jest demonizacja. Proponuję założyć katalog lib/pids i plik lib/mailman_control.rb a w nim: (a w tasku capistrano dodać linkowanie do katalogu tmp/pids)

# coding: UTF-8
    #!/usr/bin/env ruby

require 'rubygems'
require 'daemons'

daemon_options = {
  :app_name   => 'mailman_example',
  :multiple   => false,
  :dir_mode   => :script,
  :dir        => "pids",
  :backtrace  => false,
  :monitor    => true,
  :log_output => false
}
Daemons.run('mailman_app.rb', daemon_options)

Odpalenie listy jest już banalne, wystarczy wykonać komendę:

ruby mailman_control.rb start

I już wszystko śmiga :)


Dodaj komentarz!

Czytaj dalej: