2013-06-25 20:58:52 +02:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
|
|
|
|
ENV["RAILS_ENV"] ||= "production"
|
|
|
|
|
|
|
|
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
|
|
|
require File.expand_path('../../config/boot', __FILE__)
|
|
|
|
require APP_PATH
|
|
|
|
Rails.application.require_environment!
|
|
|
|
|
|
|
|
class String
|
|
|
|
def force_to_ascii
|
2013-07-01 21:28:39 +02:00
|
|
|
# fixup some "smart" quotes and things instead of forcing them to "?" chars
|
|
|
|
gsub("\u201C", '"').
|
|
|
|
gsub("\u201D", '"').
|
|
|
|
gsub("\u2018", "'").
|
|
|
|
gsub("\u2019", "'").
|
|
|
|
gsub("\u2013", "-").
|
|
|
|
gsub("\u2014", "--").
|
|
|
|
gsub("\u2026", "...").
|
2013-06-25 20:58:52 +02:00
|
|
|
encode("us-ascii", :invalid => :replace, :undef => :replace,
|
|
|
|
:replace => "?")
|
|
|
|
end
|
|
|
|
|
|
|
|
# like ActionView::Helpers::TextHelper but preserve > and indentation when
|
|
|
|
# wrapping lines
|
|
|
|
def word_wrap(len)
|
|
|
|
split("\n").collect do |line|
|
|
|
|
if line.length <= len
|
|
|
|
line
|
|
|
|
elsif m = line.match(/^(> ?| +)(.*)/)
|
|
|
|
ind = m[1]
|
|
|
|
if len - ind.length < 0
|
|
|
|
ind = " "
|
|
|
|
end
|
|
|
|
m[2].gsub(/(.{1,#{len - ind.length}})(\s+|$)/, "#{ind}\\1\n").strip
|
|
|
|
else
|
|
|
|
line.gsub(/(.{1,#{len}})(\s+|$)/, "\\1\n").strip
|
|
|
|
end
|
|
|
|
end * "\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
EMAIL_WIDTH = 72
|
|
|
|
LAST_STORY_KEY = "mailing:last_story_id"
|
|
|
|
LAST_COMMENT_KEY = "mailing:last_comment_id"
|
|
|
|
|
2014-02-19 19:49:12 +01:00
|
|
|
mailing_list_users = User.where("mailing_list_mode > 0").select{|u|
|
2014-01-13 17:21:49 +01:00
|
|
|
u.is_active? }
|
2013-06-25 20:58:52 +02:00
|
|
|
|
|
|
|
last_story_id = (Keystore.value_for(LAST_STORY_KEY) || Story.last.id).to_i
|
|
|
|
|
2013-07-04 05:40:59 +02:00
|
|
|
Story.where("id > ? AND is_expired = ?", last_story_id, false).order(:id).each do |s|
|
2013-06-25 20:58:52 +02:00
|
|
|
s.fetch_story_cache!
|
2013-07-04 05:11:12 +02:00
|
|
|
|
|
|
|
if s.story_cache.blank?
|
|
|
|
s.fetch_story_cache!
|
|
|
|
end
|
|
|
|
|
2013-06-25 20:58:52 +02:00
|
|
|
s.save
|
2013-06-30 08:29:51 +02:00
|
|
|
|
2013-06-25 20:58:52 +02:00
|
|
|
mailing_list_users.each do |u|
|
|
|
|
if (s.tags.map{|t| t.id } & u.tag_filters.map{|t| t.tag_id }).any?
|
2014-03-24 16:24:55 +01:00
|
|
|
# story has tags this user has filtered out
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if Vote.where(:user_id => u.id, :story_id => s.id, :comment_id => nil,
|
|
|
|
:vote => 0).first
|
|
|
|
# user has hidden this story
|
2013-06-25 20:58:52 +02:00
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2013-06-30 08:29:51 +02:00
|
|
|
domain = Rails.application.domain
|
|
|
|
list = "#{Rails.application.shortname}-#{u.mailing_list_token}@" <<
|
|
|
|
Rails.application.domain
|
|
|
|
|
|
|
|
IO.popen([ {}, "/usr/sbin/sendmail", "-i", "-f",
|
|
|
|
"nobody@#{Rails.application.domain}", u.email ], "w") do |mail|
|
|
|
|
mail.puts "From: #{s.user.username} <#{s.user.username}@" <<
|
|
|
|
"#{Rails.application.domain}>"
|
|
|
|
mail.puts "Reply-To: #{list}"
|
|
|
|
mail.puts "To: #{list}"
|
|
|
|
mail.puts "X-BeenThere: #{list}"
|
|
|
|
mail.puts "List-Id: #{Rails.application.name} <#{list}>"
|
|
|
|
mail.puts "List-Unsubscribe: <" <<
|
2015-01-03 00:47:26 +01:00
|
|
|
"#{Rails.application.root_url}settings>"
|
2013-06-25 20:58:52 +02:00
|
|
|
mail.puts "Precedence: list"
|
|
|
|
mail.puts "Content-Type: text/plain; charset=\"us-ascii\""
|
|
|
|
mail.puts "Message-ID: <#{s.mailing_list_message_id}>"
|
|
|
|
mail.puts "Date: " << s.created_at.strftime("%a, %d %b %Y %H:%M:%S %z")
|
|
|
|
mail.puts "Subject: #{s.title.force_to_ascii}" <<
|
|
|
|
s.tags.sort_by{|t| t.tag }.map{|t| " [#{t.tag}]" }.join
|
|
|
|
|
|
|
|
mail.puts ""
|
|
|
|
|
|
|
|
if s.description.present?
|
|
|
|
mail.puts s.description.to_s.force_to_ascii.word_wrap(EMAIL_WIDTH)
|
|
|
|
end
|
|
|
|
|
|
|
|
if s.url.present?
|
|
|
|
if s.description.present?
|
|
|
|
mail.puts ""
|
|
|
|
end
|
|
|
|
|
|
|
|
mail.puts "Via: #{s.url}"
|
|
|
|
|
|
|
|
if s.story_cache.present?
|
|
|
|
mail.puts ""
|
|
|
|
mail.puts s.story_cache.to_s.force_to_ascii.word_wrap(EMAIL_WIDTH)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
mail.puts ""
|
|
|
|
mail.puts "-- "
|
|
|
|
mail.puts "Vote: #{s.short_id_url}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
last_story_id = s.id
|
|
|
|
end
|
|
|
|
|
|
|
|
Keystore.put(LAST_STORY_KEY, last_story_id)
|
|
|
|
|
|
|
|
# repeat for comments
|
|
|
|
|
|
|
|
last_comment_id = (Keystore.value_for(LAST_COMMENT_KEY) ||
|
|
|
|
Comment.last.id).to_i
|
|
|
|
|
2013-07-04 05:40:59 +02:00
|
|
|
Comment.where("id > ? AND (is_deleted = ? AND is_moderated = ?)",
|
|
|
|
last_comment_id, false, false).order(:id).each do |c|
|
2013-08-24 04:53:48 +02:00
|
|
|
# allow some time for newer comments to be edited before sending them out
|
|
|
|
if (Time.now - c.created_at) < 2.minutes
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
2013-06-25 20:58:52 +02:00
|
|
|
mailing_list_users.each do |u|
|
2014-02-19 19:49:12 +01:00
|
|
|
if u.mailing_list_mode == 2
|
|
|
|
# stories only
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2013-06-25 20:58:52 +02:00
|
|
|
if (c.story.tags.map{|t| t.id } & u.tag_filters.map{|t| t.tag_id }).any?
|
2014-03-24 16:24:55 +01:00
|
|
|
# story has tags this user has filtered out
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if Vote.where(:user_id => u.id, :story_id => c.story_id,
|
|
|
|
:comment_id => nil, :vote => 0).first
|
|
|
|
# user has hidden this story
|
2013-06-25 20:58:52 +02:00
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2013-06-30 08:29:51 +02:00
|
|
|
domain = Rails.application.domain
|
|
|
|
list = "#{Rails.application.shortname}-#{u.mailing_list_token}@" <<
|
|
|
|
Rails.application.domain
|
|
|
|
|
|
|
|
IO.popen([ {}, "/usr/sbin/sendmail", "-i", "-f",
|
|
|
|
"nobody@#{domain}", u.email ], "w") do |mail|
|
|
|
|
mail.puts "From: #{c.user.username} <#{c.user.username}@#{domain}>"
|
|
|
|
mail.puts "Reply-To: #{list}"
|
|
|
|
mail.puts "To: #{list}"
|
|
|
|
mail.puts "List-Id: #{Rails.application.name} <#{list}>"
|
|
|
|
mail.puts "List-Unsubscribe: <" <<
|
2015-01-03 00:47:26 +01:00
|
|
|
"#{Rails.application.root_url}settings>"
|
2013-06-25 20:58:52 +02:00
|
|
|
mail.puts "Precedence: list"
|
|
|
|
mail.puts "Content-Type: text/plain; charset=\"us-ascii\""
|
|
|
|
mail.puts "Message-ID: <#{c.mailing_list_message_id}>"
|
|
|
|
|
|
|
|
refs = [ "<#{c.story.mailing_list_message_id}>" ]
|
|
|
|
|
|
|
|
if c.parent_comment_id
|
|
|
|
mail.puts "In-Reply-To: <#{c.parent_comment.mailing_list_message_id}>"
|
|
|
|
|
|
|
|
thread = []
|
|
|
|
indent_level = 0
|
2014-01-07 23:58:22 +01:00
|
|
|
Comment.where(:thread_id => c.thread_id).arrange_for_user(
|
2013-06-25 20:58:52 +02:00
|
|
|
nil).reverse.each do |cc|
|
|
|
|
if indent_level > 0 && cc.indent_level < indent_level
|
|
|
|
thread.unshift cc
|
|
|
|
indent_level = cc.indent_level
|
|
|
|
elsif cc.id == c.id
|
|
|
|
indent_level = cc.indent_level
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
thread.each do |cc|
|
|
|
|
refs.push "<#{cc.mailing_list_message_id}>"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
mail.puts "In-Reply-To: <#{c.story.mailing_list_message_id}>"
|
|
|
|
end
|
|
|
|
|
|
|
|
mail.print "References:"
|
|
|
|
refs.each do |ref|
|
|
|
|
mail.puts " #{ref}"
|
|
|
|
end
|
|
|
|
|
2015-01-03 06:59:59 +01:00
|
|
|
if c.hat
|
|
|
|
mail.puts "X-Hat: #{c.hat.hat}"
|
|
|
|
end
|
|
|
|
|
2013-06-25 20:58:52 +02:00
|
|
|
mail.puts "Date: " << c.created_at.strftime("%a, %d %b %Y %H:%M:%S %z")
|
|
|
|
mail.puts "Subject: Re: #{c.story.title.force_to_ascii}" <<
|
|
|
|
c.story.tags.sort_by{|t| t.tag }.map{|t| " [#{t.tag}]" }.join
|
|
|
|
|
|
|
|
mail.puts ""
|
|
|
|
|
2015-01-03 06:59:59 +01:00
|
|
|
if c.hat
|
|
|
|
mail.puts "[ Posted while wearing #{c.hat.hat} hat ]"
|
|
|
|
mail.puts ""
|
|
|
|
end
|
|
|
|
|
2013-06-27 21:29:06 +02:00
|
|
|
# if the comment has hard line breaks at <80, it likely came from an
|
|
|
|
# email, so don't re-wrap it at something shorter
|
|
|
|
com = c.comment.to_s.force_to_ascii
|
|
|
|
com_lines = com.split("\n")
|
|
|
|
if com_lines.length > 1 && com_lines.first.length < 80
|
|
|
|
mail.puts com.word_wrap(80)
|
|
|
|
else
|
|
|
|
mail.puts com.word_wrap(EMAIL_WIDTH)
|
|
|
|
end
|
2013-06-25 20:58:52 +02:00
|
|
|
|
|
|
|
mail.puts ""
|
|
|
|
mail.puts "-- "
|
|
|
|
mail.puts "Vote: #{c.short_id_url}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
last_comment_id = c.id
|
|
|
|
end
|
|
|
|
|
|
|
|
Keystore.put(LAST_COMMENT_KEY, last_comment_id)
|