225 lines
5.4 KiB
Ruby
225 lines
5.4 KiB
Ruby
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
|
|
|
module SignatureDetection
|
|
|
|
=begin
|
|
|
|
try to detect the signature in list of articles for example
|
|
|
|
messages = [
|
|
{
|
|
content: 'some content',
|
|
content_type: 'text/plain',
|
|
},
|
|
]
|
|
|
|
signature = SignatureDetection.find_signature(messages)
|
|
|
|
returns
|
|
|
|
signature = '...signature possible match...'
|
|
|
|
=end
|
|
|
|
def self.find_signature(messages)
|
|
signature_candidates = Hash.new(0) # <potential_signature>: <score>
|
|
messages = messages.map { |m| m[:content_type].match?(%r{text/html}i) ? m[:content].html2text(true) : m[:content] }
|
|
message_pairs = messages.each_cons(2).to_a
|
|
diffs = message_pairs.map { |msg_pair| Diffy::Diff.new(*msg_pair).to_s }
|
|
|
|
# Find the first 5- to 10-line common substring in each diff
|
|
diffs.map { |d| d.split("\n") }.each do |diff_lines|
|
|
# Get line numbers in diff representing changes (those starting with +, -, \)
|
|
delta_indices = diff_lines.map.with_index { |l, i| l.start_with?(' ') ? nil : i }.compact
|
|
|
|
# Add boundaries at start and end
|
|
delta_indices.unshift(-1).push(diff_lines.length)
|
|
|
|
# Find first gap of 5+ lines between deltas (i.e., the common substring's location)
|
|
sig_range = delta_indices.each_cons(2)
|
|
.map { |head, tail| [head + 1, tail - 1] }
|
|
.find { |head, tail| tail > head + 4 }
|
|
|
|
next if sig_range.nil?
|
|
|
|
# Take up to 10 lines from this "gap" (i.e., the common substring)
|
|
match_content = diff_lines[sig_range.first..sig_range.last]
|
|
.map { |l| l.sub(%r{^.}, '') }
|
|
.first(10).join("\n")
|
|
|
|
# Invalid html signature detection for exchange warning boxes #3571
|
|
next if match_content.include?('CAUTION:')
|
|
|
|
# Add this substring to the signature_candidates hash and increment its match score
|
|
signature_candidates[match_content] += 1
|
|
end
|
|
|
|
signature_candidates.max_by { |_, score| score }&.first
|
|
end
|
|
|
|
=begin
|
|
|
|
this function will search for a signature string in a string (e.g. article) and return the line number of the signature start
|
|
|
|
signature_line = SignatureDetection.find_signature_line(signature, message, content_type)
|
|
|
|
returns
|
|
|
|
signature_line = 123
|
|
|
|
or
|
|
|
|
signature_line = nil
|
|
|
|
=end
|
|
|
|
def self.find_signature_line(signature, string, content_type)
|
|
string = string.html2text(true) if content_type.match?(%r{text/html}i)
|
|
|
|
# try to find the char position of the signature
|
|
search_position = string.index(signature)
|
|
|
|
# count new lines up to signature
|
|
string[0..search_position].split("\n").length + 1 if search_position.present?
|
|
end
|
|
|
|
=begin
|
|
|
|
find signature line of message by user and article
|
|
|
|
signature_line = SignatureDetection.find_signature_line_by_article(user, article)
|
|
|
|
returns
|
|
|
|
signature_line = 123
|
|
|
|
or
|
|
|
|
signature_line = nil
|
|
|
|
=end
|
|
|
|
def self.find_signature_line_by_article(user, article)
|
|
return if !user.preferences[:signature_detection]
|
|
|
|
SignatureDetection.find_signature_line(
|
|
user.preferences[:signature_detection],
|
|
article.body,
|
|
article.content_type,
|
|
)
|
|
end
|
|
|
|
=begin
|
|
|
|
this function will search for a signature string in all articles of a given user_id
|
|
|
|
signature = SignatureDetection.by_user_id(user_id)
|
|
|
|
returns
|
|
|
|
signature = '...signature possible match...'
|
|
|
|
=end
|
|
|
|
def self.by_user_id(user_id)
|
|
type = Ticket::Article::Type.lookup(name: 'email')
|
|
sender = Ticket::Article::Sender.lookup(name: 'Customer')
|
|
tickets = Ticket.where(
|
|
created_by_id: user_id,
|
|
create_article_type_id: type.id,
|
|
create_article_sender_id: sender.id
|
|
).limit(5).order(id: :desc)
|
|
article_bodies = []
|
|
tickets.each do |ticket|
|
|
article = ticket.articles.first
|
|
next if !article
|
|
|
|
data = {
|
|
content: article.body,
|
|
content_type: article.content_type,
|
|
}
|
|
article_bodies.push data
|
|
end
|
|
|
|
find_signature(article_bodies)
|
|
end
|
|
|
|
=begin
|
|
|
|
rebuild signature for each user
|
|
|
|
SignatureDetection.rebuild_all_user
|
|
|
|
returns
|
|
|
|
true/false
|
|
|
|
=end
|
|
|
|
def self.rebuild_all_user
|
|
User.select('id').where(active: true).order(id: :desc).each do |local_user|
|
|
rebuild_user(local_user.id)
|
|
end
|
|
true
|
|
end
|
|
|
|
=begin
|
|
|
|
rebuild signature detection for user
|
|
|
|
SignatureDetection.rebuild_user(user_id)
|
|
|
|
returns
|
|
|
|
true/false
|
|
|
|
=end
|
|
|
|
def self.rebuild_user(user_id)
|
|
signature_detection = by_user_id(user_id)
|
|
return if !signature_detection
|
|
|
|
user = User.find(user_id)
|
|
return if user.preferences[:signature_detection] == signature_detection
|
|
|
|
user.preferences[:signature_detection] = signature_detection
|
|
user.save
|
|
|
|
true
|
|
end
|
|
|
|
=begin
|
|
|
|
rebuild signature for all articles
|
|
|
|
SignatureDetection.rebuild_all_articles
|
|
|
|
returns
|
|
|
|
true/false
|
|
|
|
=end
|
|
|
|
def self.rebuild_all_articles
|
|
article_type = Ticket::Article::Type.lookup(name: 'email')
|
|
|
|
Ticket::Article.where(type_id: article_type.id)
|
|
.order(id: :desc)
|
|
.find_each(batch_size: 10) do |article|
|
|
user = User.lookup(id: article.created_by_id)
|
|
next if !user.preferences[:signature_detection]
|
|
|
|
signature_line = find_signature_line(
|
|
user.preferences[:signature_detection],
|
|
article.body,
|
|
article.content_type,
|
|
)
|
|
next if !signature_line
|
|
|
|
article.preferences[:signature_detection] = signature_line
|
|
article.save if article.changed?
|
|
end
|
|
true
|
|
end
|
|
|
|
end
|