Files
scripts/Perl OTRS/Kernel/cpan-lib/Sisimai/Message/JSON.pm
2024-10-14 00:08:40 +02:00

320 lines
9.5 KiB
Perl

package Sisimai::Message::JSON;
use parent 'Sisimai::Message';
use feature ':5.10';
use strict;
use warnings;
use Sisimai::Order::JSON;
use JSON;
my $ToBeLoaded = [];
my $TryOnFirst = [];
my $DefaultSet = Sisimai::Order::JSON->default;
my $ObjectKeys = Sisimai::Order::JSON->by('keyname');
sub make {
# Make data structure from decoded JSON object
# @param [Hash] argvs Bounce object
# @options argvs [Hash] data Decoded JSON
# @options argvs [Array] load User defined MTA module list
# @options argvs [Array] order The order of MTA modules
# @options argvs [Code] hook Reference to callback method
# @return [Hash] Resolved data structure
my $class = shift;
my $argvs = { @_ };
my $methodargv = {};
my $hookmethod = $argvs->{'hook'} || undef;
my $bouncedata = undef;
my $processing = {
'from' => '', # From_ line
'header' => {}, # Email header
'rfc822' => '', # Original message part
'ds' => [], # Parsed data, Delivery Status
'catch' => undef, # Data parsed by callback method
};
$methodargv = {
'load' => $argvs->{'load'} || [],
'order' => $argvs->{'order'} || [],
};
$ToBeLoaded = __PACKAGE__->load(%$methodargv);
$TryOnFirst = __PACKAGE__->makeorder($argvs->{'data'});
# Rewrite message body for detecting the bounce reason
$methodargv = { 'hook' => $hookmethod, 'json' => $argvs->{'data'} };
$bouncedata = __PACKAGE__->parse(%$methodargv);
return undef unless $bouncedata;
return undef unless keys %$bouncedata;
$processing->{'ds'} = $bouncedata->{'ds'};
$processing->{'catch'} = $bouncedata->{'catch'};
$processing->{'rfc822'} = $bouncedata->{'rfc822'} || {};
return $processing;
}
sub load {
# Load MTA modules which specified at 'order' and 'load' in the argument
# @param [Hash] argvs Module information to be loaded
# @options argvs [Array] load User defined MTA module list
# @options argvs [Array] order The order of MTA modules
# @return [Array] Module list
# @since v4.20.0
my $class = shift;
my $argvs = { @_ };
my @modulelist = ();
my $tobeloaded = [];
my $modulepath = '';
for my $e ('load', 'order') {
# The order of MTA modules specified by user
next unless exists $argvs->{ $e };
next unless ref $argvs->{ $e } eq 'ARRAY';
next unless scalar @{ $argvs->{ $e } };
push @modulelist, @{ $argvs->{'order'} } if $e eq 'order';
next unless $e eq 'load';
# Load user defined MTA module
for my $v ( @{ $argvs->{'load'} } ) {
# Load user defined MTA module
eval {
($modulepath = $v) =~ s|::|/|g;
require $modulepath.'.pm';
};
next if $@;
push @$tobeloaded, $v;
}
}
for my $e ( @modulelist ) {
# Append the custom order of MTA modules
next if grep { $e eq $_ } @$tobeloaded;
push @$tobeloaded, $e;
}
return $tobeloaded;
}
sub makeorder {
# Check the decoded JSON strucutre for detecting MTA modules and returns the
# order of modules to be called.
# @param [Hash] argvs Decoded JSON object
# @return [Array] Order of MTA modules
my $class = shift;
my $argvs = shift || return [];
my $order = [];
return [] unless ref $argvs eq 'HASH';
return [] unless scalar keys %$argvs;
# Seek some key names from given argument
for my $e ( keys %$ObjectKeys ) {
# Get MTA module list matched with a specified key
next unless exists $argvs->{ $e };
# Matched and push it into the order list
push @$order, @{ $ObjectKeys->{ $e } };
last;
}
return $order;
}
sub parse {
# Parse bounce object with each MTA module
# @param [Hash] argvs Processing message entity.
# @param options argvs [Hash] json Decoded bounce object
# @param options argvs [Code] hook Hook method to be called
# @return [Hash] Parsed and structured bounce mails
my $class = shift;
my $argvs = { @_ };
my $bouncedata = $argvs->{'json'} || {};
my $hookmethod = $argvs->{'hook'} || undef;
my $havecaught = undef;
my $haveloaded = {};
my $hasadapted = undef;
my $modulepath = undef;
if( ref $hookmethod eq 'CODE' ) {
# Call hook method
my $p = {
'datasrc' => 'json',
'headers' => undef,
'message' => undef,
'bounces' => $argvs->{'json'},
};
eval { $havecaught = $hookmethod->($p) };
warn sprintf(" ***warning: Something is wrong in hook method:%s", $@) if $@;
}
ADAPTOR: while(1) {
# 1. User-Defined Module
# 2. MTA Module Candidates to be tried on first
# 3. Sisimai::Bite::JSON::*
#
USER_DEFINED: for my $r ( @$ToBeLoaded ) {
# Call user defined MTA modules
next if exists $haveloaded->{ $r };
eval {
($modulepath = $r) =~ s|::|/|g;
require $modulepath.'.pm';
};
if( $@ ) {
warn sprintf(" ***warning: Failed to load %s: %s", $r, $@);
next;
}
$hasadapted = $r->adapt($bouncedata);
$haveloaded->{ $r } = 1;
last(ADAPTOR) if $hasadapted;
}
TRY_ON_FIRST: while( my $r = shift @$TryOnFirst ) {
# Try MTA module candidates which are detected from object key names
next if exists $haveloaded->{ $r };
($modulepath = $r) =~ s|::|/|g;
require $modulepath.'.pm';
next if $@;
$hasadapted = $r->adapt($bouncedata);
$haveloaded->{ $r } = 1;
last(ADAPTOR) if $hasadapted;
}
DEFAULT_LIST: for my $r ( @$DefaultSet ) {
# Default order of MTA modules
next if exists $haveloaded->{ $r };
($modulepath = $r) =~ s|::|/|g;
require $modulepath.'.pm';
next if $@;
$hasadapted = $r->adapt($bouncedata);
$haveloaded->{ $r } = 1;
last(ADAPTOR) if $hasadapted;
}
last; # as of now, we have no sample json data for coding this block
} # End of while(ADAPTOR)
$hasadapted->{'catch'} = $havecaught if $hasadapted;
return $hasadapted;
}
1;
__END__
=encoding utf-8
=head1 NAME
Sisimai::Message::JSON - Convert bounce object (decoded JSON) to data structure.
=head1 SYNOPSIS
use JSON;
use Sisimai::Message;
my $jsonparser = JSON->new;
my $jsonobject = $jsonparser->decode($jsonstring);
my $messageobj = Sisimai::Message->new('data' => $jsonobject, 'input' => 'json');
=head1 DESCRIPTION
Sisimai::Message::JSON convert bounce object (decode JSON) to data structure.
When the email given as a argument of "new()" method is not a decoded JSON, the
method returns "undef".
=head1 CLASS METHODS
=head2 C<B<new(I<Hash reference>)>>
C<new()> is a constructor of Sisimai::Message
my $jsonparser = JSON->new;
my $jsonstring = '{"neko":2, "nyaan": "meow", ...}';
my $jsonobject = $jsonparser->decode($jsonstring);
my $messageobj = Sisimai::Message->new('data' => $jsonobject, 'input' => 'json');
If you have implemented a custom MTA module and use it, set the value of "load"
in the argument of this method as an array reference like following code:
my $messageobj = Sisimai::Message->new(
'data' => $jsonobject,
'load' => ['Your::Custom::MTA::Module']
'input' => 'json',
);
Beginning from v4.19.0, `hook` argument is available to callback user defined
method like the following codes:
my $callbackto = sub {
my $argv = shift;
my $data = { 'feedback-id' => '', 'account-id' => '' };
my $mesg = $argv->{'message'} || {};
if( exists $mesg->{'feedbackId'} ) {
$data->{'feedback-id'} = $mesg->{'feedback-Id'};
}
if( exists $mesg->{'sendingAccountId'} ) {
$data->{'account-id'} = $mesg->{'sendingAccountId'};
}
return $data;
};
my $messageobj = Sisimai::Message->new(
'data' => $jsonobject,
'hook' => $callbackto,
'input' => 'json' );
print $message->catch->{'feedback-id'}; # 01010157e48fa03f-c7e948fe-...
=head1 INSTANCE METHODS
=head2 C<B<(from)>>
C<from()> returns empty string
print $message->from; # ''
=head2 C<B<header()>>
C<header()> returns empty Hash
print $message->header; # {}
=head2 C<B<ds()>>
C<ds()> returns an array reference which include contents of delivery status.
for my $e ( @{ $message->ds } ) {
print $e->{'status'}; # 5.1.1
print $e->{'recipient'};# neko@example.jp
}
=head2 C<B<rfc822()>>
C<rfc822()> returns a hash reference which include the header part of the original
message.
print $message->rfc822->{'from'}; # cat@example.com
print $message->rfc822->{'to'}; # neko@example.jp
=head2 C<B<catch()>>
C<catch()> returns any data generated by user-defined method passed at the `hook`
argument of new() constructor.
=head1 AUTHOR
azumakuniyuki
=head1 COPYRIGHT
Copyright (C) 2014-2018 azumakuniyuki, All rights reserved.
=head1 LICENSE
This software is distributed under The BSD 2-Clause License.
=cut