AddWatchersOnCorrespondDomains

From Request Tracker Wiki
Revision as of 13:35, 7 December 2022 by Robl (talk | contribs) (create)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

AddWatchersOnCorrespondDomains

This RT Scrip is a modified version of AddWatchersOnCorrespond - AddWatchersOnCorrespond simply adds all recipients as watchers.

This Scrip is the same, except it is more restrictive with the requestors added:

  • `AddWatchersOnCorrespondDomains` will add the person making the correspondence as a Watcher to the ticket if they are not already a Watcher.

If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as Watchers, only if:

  • They are not already a Watcher, and:
  • Their email address has the same domain (or subdomain) as another watcher already on the ticket.
  • e.g. New To: and Cc: recipients added by the customer will only be added as watchers if they are from the same domain as a requestor already on the ticket.
  • (Third parties not already on the ticket will not be added.)
  • We prefer to add as Requestors rather than Cc to simplify ticket updates. (All "Replies to Requestors" always go to everyone on the ticket.) If you do not want this behaviour, change the line
    my $type = 'Requestor';
    to
    my $type = 'Cc';
  • This accommodates the majority of our use cases:
  • Internal staff like to follow-up via email, but may not be aware that their replies will not be seen by someone the customer added as a Cc:
  • (After this executes, We also use [RT-Extension-NonWatcherRecipients https://metacpan.org/pod/RT::Extension::NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket. (Our version of AddWatchersOnCorrespond checks if the proposed cc/to to be added is from a domain of a watcher **already** on the ticket and if not, does not add them.))


  • In our RT setup, we have a group named Staff which contains all admins for our site. If the user this Scrip is going to add as a Watcher to the ticket is also a member of the Staff group, then the Scrip will add them as an AdminCC Watcher instead of a CC Watcher. It should be fairly easy for others who do not need this feature to remove or modify this Scrip accordingly.
  • NOTE: If you remove a watcher from a ticket, and a customer replies again with them in a Cc: header, this scrip will probably add them back again. To avoid this, keep them as a watcher, but remove them as an email recipient (Under People -> Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a "Squelched" watcher in RT.)

Changelog

2022-12-07: Created. Update to always populate Real Name field. See this forum post.


Description: AddWatchersOnCorrespondDomains

Condition: On Correspond

Action: User Defined

Template: Global template: Blank

Stage: TransactionBatch

Custom condition:

Custom action preparation code: return 1;

Custom action cleanup code:



# <rob@lonap.net>
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)
# 2. New Cc is from the same domain, or a subdomain.
#
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond
# But does not add just any cc:
#
# Example: From: Fred Bloggs <fred@isp.com>
#          Cc:   Mary Jane <mjane@emea.isp.com>
#
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.
#
# Example: From: Fred Bloggs <fred@isp.com>
#          Cc:   Random Helpdesk <helpdesk@bigcolo.net>
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if "bigcolo.net" is not already
#   a watcher on the ticket.
#

my %People;

# Get some info:
my $scrip = 'Scrip:AddWatchersOnCorrespond';
my $Transaction = $self->TransactionObj;
my $Queue = $self->TicketObj->QueueObj;
my $Ticket = $self->TicketObj;
my $Id = $self->TicketObj->id;

# Load a list of all domains of people on the ticket already:
my @TicketDomains = GetTicketDomains();

# Extract a list of people associated with this transaction:
#  - including the transaction creator, and if it is an email, the sender and recipients of that email

my $CreatorEmailAddr = $self->TransactionObj->CreatorObj->EmailAddress;
my $CreatorRealName  = $self->TransactionObj->CreatorObj->RealName;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);

foreach my $h (qw(From To Cc)) {
    my $header = $Transaction->Attachments->First->GetHeader($h);
    my @addr = Mail::Address->parse($header);
    foreach my $addrobj (@addr) {
        my $addr = lc $RT::Nobody->UserObj->CanonicalizeEmailAddress($addrobj->address);
        # Ignore the specific addresses for this queue:
        next if lc $Queue->CorrespondAddress eq $addr;
        next if lc $Queue->CommentAddress eq $addr;
        # Ignore any email address that looks like one for ANY of our queues:
        next if RT::EmailParser->IsRTAddress($addr);
        my $fullname = GetFullName($addr,$addrobj->phrase);
        $People{$addr}{RealName} = $fullname;
        $RT::Logger->debug("$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname");
    }
}

# Lookup the 'experts' (staff) group to use below:
my $Experts = RT::Group->new($self->CurrentUser);
$Experts->LoadUserDefinedGroup('Staff');

# Now check if each user is already watching the ticket or queue:

foreach my $addr (sort { $a <=> $b } keys %People) {

    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);

    my $User = RT::User->new( $RT::SystemUser );
    $User->LoadOrCreateByEmail(
        RealName     => $People{$addr}{RealName},
        EmailAddress => $addr,
        Comments     => "Autocreated by $scrip",
    );
    
    my $Name = $User->Name;
    my $Principal = $User->PrincipalId;
    
    if ( not ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or
            $Queue->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or
            $Ticket->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or
            $Ticket->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or
            $Ticket->IsWatcher(Type => 'Requestor', PrincipalId => $Principal) or
            $Ticket->IsOwner($User) )) {
     
     # check if the new cc: person's domain matches a domain already on the ticket.
     # If so, add them as a new watcher:
     if (is_address_authdomain($addr,@TicketDomains)) {
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:
        # my $type = 'Cc';
        my $type = 'Requestor';
        $type = 'AdminCc' if $Experts->HasMember($User->PrincipalObj);
        # Add the new watcher now and check for errors:
        my ($ret, $msg) = $Ticket->AddWatcher(Type  => $type, PrincipalId => $Principal);
        if ($ret) {
            $RT::Logger->info("$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)");
        } else {
            $RT::Logger->error("$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg");
        }
     }

    }
}

sub GetFullName {

    # Get a nicely formatted name for RT RealName Field:

    # If fullname is blank, make something up from the
    # local_part of the email address: "fred.bloggs@..." -> "Fred Bloggs".
    # Do not allow fullname to contain "@".

    my ($addr,$fullname) = @_;
    my ($local_part,$domain) = split('@', $addr);

     if (($fullname eq '') || ($fullname =~ /\@/)) {
      $fullname = $local_part;
      $fullname =~ s/[\._-]/ /g;
      $fullname =~ s/(\w+)/\u$1/g;
     }

     $fullname =~ s/^[\"\']|[\"\']$//g; # strip leading/trailing " or '
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces

    return $fullname;
}

sub is_address_authdomain {

        # Is address in an authorised domain?
        # Also allow subdomain of existing domain

        my $addr = shift;
        my @domains = @_;
        $addr = lc($addr);

        my ($undef,$domain) = split('@',$addr);
        my $is_auth = 0;

        foreach my $authdomain (@domains) {

           # if ($domain eq $authdomain)      { $is_auth=1; last; };
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };

        }

        return($is_auth);

}

sub GetTicketDomains {

    # my $self = shift;
	
    # Get list of existing email addresses from the ticket, and push all the domains.
    # We'll check this later. Any cc: by an existing requestor will be allowed from 
    # the same domain or a subdomain.

    # For each Role, get RoleAddresses: "foo@bar.com, foo@baz.com" 
    # Return a deduped domain list.

    my %allticketdomains;

     foreach my $role (qw(Requestor Cc AdminCc)) {

        my $roleaddresses = lc($self->TicketObj->RoleAddresses($role));
               foreach my $a (split(', ', $roleaddresses)) {
                 my (undef,$domain) = split('@',$a);
                 next if ($allticketdomains{$domain});
                 $allticketdomains{$domain} = $domain;
                }
      }

      my @ticketdomains = sort keys %allticketdomains;

      return (@ticketdomains);

}

return 1;

# vim:ft=perl: