Process packet seenby/path/via lines when saving echomail/netmail
This commit is contained in:
parent
7fedf88d8c
commit
612efda945
@ -11,8 +11,7 @@ use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Validator as ValidatorResult;
|
||||
|
||||
use App\Classes\FTN as FTNBase;
|
||||
use App\Http\Controllers\DomainController;
|
||||
use App\Models\{Address,Domain,System,Zone};
|
||||
use App\Models\{Address,Domain,Zone};
|
||||
use App\Rules\{TwoByteInteger,TwoByteIntegerWithZero};
|
||||
use App\Traits\EncodeUTF8;
|
||||
|
||||
@ -138,10 +137,7 @@ class Message extends FTNBase
|
||||
|
||||
private Collection $rescanned; // Message was created as a result of a rescan
|
||||
private Collection $path; // FTS-0004.001 The message PATH lines
|
||||
private Collection $pathaddress; // Collection of Addresses after parsing seenby
|
||||
private Collection $rogue_seenby; // Collection of FTNs in the Seen-by that are not defined
|
||||
private Collection $seenby; // FTS-0004.001 The message SEEN-BY lines
|
||||
private Collection $seenaddress; // Collection of Addresses after parsing seenby
|
||||
private Collection $via; // The path the message has gone using Via lines (Netmail)
|
||||
private Collection $unknown; // Temporarily hold attributes we have no logic for.
|
||||
|
||||
@ -224,10 +220,7 @@ class Message extends FTNBase
|
||||
$this->kludge = collect();
|
||||
$this->rescanned = collect();
|
||||
$this->path = collect();
|
||||
$this->rogue_seenby = collect();
|
||||
$this->seenby = collect();
|
||||
$this->seenaddress = collect();
|
||||
$this->pathaddress = collect();
|
||||
$this->via = collect();
|
||||
$this->unknown = collect();
|
||||
}
|
||||
@ -364,9 +357,6 @@ class Message extends FTNBase
|
||||
case 'rescanned':
|
||||
case 'path':
|
||||
case 'seenby':
|
||||
case 'pathaddress':
|
||||
case 'rogue_seenby':
|
||||
case 'seenaddress':
|
||||
case 'unknown':
|
||||
case 'via':
|
||||
|
||||
@ -556,6 +546,7 @@ class Message extends FTNBase
|
||||
}
|
||||
|
||||
$ptr = 0;
|
||||
|
||||
// To User
|
||||
$o->user_to = strstr(substr($msg,self::HEADER_LEN+$ptr),"\x00",TRUE);
|
||||
$ptr += strlen($o->user_to)+1;
|
||||
@ -670,77 +661,6 @@ class Message extends FTNBase
|
||||
return ($this->flags & $flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the Seenby/path lines and return a collection of addresses
|
||||
*
|
||||
* @param string $type Type of address, ie: seenby/path
|
||||
* @param Collection $addresses
|
||||
* @param Collection $rogue
|
||||
* @return Collection
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function parseAddresses(string $type,Collection $addresses,Collection &$rogue): Collection
|
||||
{
|
||||
$nodes = collect();
|
||||
|
||||
$net = NULL;
|
||||
foreach ($addresses as $line) {
|
||||
foreach (explode(' ',$line) as $item) {
|
||||
if (($x=strpos($item,'/')) !== FALSE) {
|
||||
$net = (int)substr($item,0,$x);
|
||||
$node = (int)substr($item,$x+1);
|
||||
|
||||
} else {
|
||||
$node = (int)$item;
|
||||
}
|
||||
|
||||
$aoid = NULL;
|
||||
|
||||
// If domain should be flattened, look for node regardless of zone (within the list of zones for the domain)
|
||||
if ($this->fdomain && $this->fdomain->flatten) {
|
||||
$ao = Address::findZone($this->fdomain,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,0);
|
||||
$ftn = sprintf('%d:%d/%d@%s',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,$this->fdomain->name);
|
||||
|
||||
$aoid = $ao?->id;
|
||||
|
||||
} elseif ($this->fdomain) {
|
||||
$ftn = sprintf('%d:%d/%d@%s',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,$this->fdomain->name);
|
||||
|
||||
} else {
|
||||
$ftn = sprintf('%d:%d/%d',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX);
|
||||
}
|
||||
|
||||
if (! $aoid) {
|
||||
if (! ($ao=Address::findFTN($ftn))) {
|
||||
Log::alert(sprintf('%s:! Undefined Node [%s] in [%s] - auto created.',self::LOGKEY,$ftn,$type));
|
||||
|
||||
$ao = Address::createFTN($ftn,System::createUnknownSystem());
|
||||
}
|
||||
|
||||
$aoid = $ao?->id;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'seenby':
|
||||
if (! $ao) {
|
||||
$rogue->push(sprintf('%d:%d/%d',$this->fz,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX));
|
||||
continue 2;
|
||||
}
|
||||
|
||||
case 'path':
|
||||
if (! $aoid)
|
||||
throw new \Exception(sprintf('Didnt get an address for [%s]',$ftn));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$nodes->push($aoid);
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the data after the ORIGIN
|
||||
* There may be kludge lines after the origin - notably SEEN-BY
|
||||
@ -785,48 +705,6 @@ class Message extends FTNBase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the via address and return a collection of addresses
|
||||
*
|
||||
* <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]
|
||||
*
|
||||
* @param Collection $via
|
||||
* @return Collection
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function parseVia(Collection $via): Collection
|
||||
{
|
||||
$nodes = collect();
|
||||
|
||||
foreach ($via as $line) {
|
||||
$m = [];
|
||||
|
||||
if (preg_match('/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/',$line,$m)) {
|
||||
// Address
|
||||
$ao = Address::findFTN($m[1]);
|
||||
|
||||
// Time
|
||||
$t = [];
|
||||
$datetime = '';
|
||||
|
||||
if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
|
||||
Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
|
||||
else
|
||||
$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');
|
||||
|
||||
if (! $ao) {
|
||||
Log::alert(sprintf('%s:! Undefined Node [%s] for Netmail.',self::LOGKEY,$m[1]));
|
||||
//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);
|
||||
|
||||
} else {
|
||||
$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information out of the message text.
|
||||
*
|
||||
@ -1009,19 +887,6 @@ class Message extends FTNBase
|
||||
} elseif ($this->zone) {
|
||||
$this->src = Address::parseFTN(sprintf('%d:%d/%d.%d@%s',$this->zone->zone_id,$this->fn,$this->ff,$this->fp,$this->zone->domain->name));
|
||||
}
|
||||
|
||||
// Parse SEEN-BY
|
||||
if ($this->seenby->count())
|
||||
$this->seenaddress = $this->parseAddresses('seenby',$this->seenby,$this->rogue_seenby);
|
||||
|
||||
$dummy = collect();
|
||||
// Parse PATH
|
||||
if ($this->path->count())
|
||||
$this->pathaddress = $this->parseAddresses('path',$this->path,$dummy);
|
||||
|
||||
// Parse VIA
|
||||
if ($this->via->count())
|
||||
$this->pathaddress = $this->parseVia($this->via);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1061,6 +926,15 @@ class Message extends FTNBase
|
||||
]);
|
||||
|
||||
$validator->after(function($validator) {
|
||||
if ($this->zone->domain->flatten) {
|
||||
if (! $this->zone->domain->zones->pluck('zone_id')->contains($this->fz))
|
||||
$validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match any zone in domain for packet zone [%d].',$this->fz,$this->zone->zone_id));
|
||||
|
||||
} else {
|
||||
if ($this->zone->zone_id !== $this->fz)
|
||||
$validator->errors()->add('invalid-zone',sprintf('Message zone [%d] doesnt match packet zone [%d].',$this->fz,$this->zone->zone_id));
|
||||
}
|
||||
|
||||
if (! $this->fboss_o)
|
||||
$validator->errors()->add('from',sprintf('Undefined Node [%s] sent message.',$this->fboss));
|
||||
if (! $this->tboss_o)
|
||||
|
@ -5,12 +5,13 @@ namespace App\Classes\FTN;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
use App\Classes\FTN as FTNBase;
|
||||
use App\Models\{Address,Software,System,Zone};
|
||||
use App\Models\{Address,Domain,Software,System,Zone};
|
||||
use App\Notifications\Netmails\EchomailBadAddress;
|
||||
|
||||
/**
|
||||
* Represents a Fidonet Packet, that contains an array of messages.
|
||||
@ -172,11 +173,11 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
* @param mixed $f
|
||||
* @param string $name
|
||||
* @param int $size
|
||||
* @param System|null $system
|
||||
* @param Domain|null $domain
|
||||
* @return Packet
|
||||
* @throws InvalidPacketException
|
||||
*/
|
||||
public static function process(mixed $f,string $name,int $size,System $system=NULL): self
|
||||
public static function process(mixed $f,string $name,int $size,Domain $domain=NULL): self
|
||||
{
|
||||
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
|
||||
|
||||
@ -223,11 +224,29 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
else if (! strlen($x))
|
||||
throw new InvalidPacketException('No message in packet: '.bin2hex($x));
|
||||
|
||||
$o->zone = $system?->zones->firstWhere('zone_id',$o->fz);
|
||||
// Work out the packet zone
|
||||
if ($o->fz && ($o->fd || $domain)) {
|
||||
$o->zone = Zone::select('zones.*')
|
||||
->join('domains',['domains.id'=>'zones.domain_id'])
|
||||
->where('zone_id',$o->fz)
|
||||
->where('name',$o->fd ?: $domain->name)
|
||||
->single();
|
||||
|
||||
// If zone is null, we'll take the zone from the packet
|
||||
if (! $o->zone)
|
||||
$o->zone = Zone::where('zone_id',$o->fz)->where('default',TRUE)->single();
|
||||
// We need not knowing the domain, we use the default zone
|
||||
} else {
|
||||
$o->zone = Zone::where('zone_id',$o->fz)
|
||||
->where('default',TRUE)
|
||||
->single();
|
||||
}
|
||||
|
||||
// If zone is not set, then we need to use a default zone - the messages may not be from this zone.
|
||||
if (! $o->zone) {
|
||||
Log::alert(sprintf('%s:! We couldnt work out the packet zone, so we have fallen back to the default for [%d]',self::LOGKEY,$o->fz));
|
||||
|
||||
$o->zone = Zone::where('zone_id',$o->fz)
|
||||
->where('default',TRUE)
|
||||
->singleOrFail();
|
||||
}
|
||||
|
||||
$buf_ptr = 0;
|
||||
$message = '';
|
||||
@ -415,15 +434,27 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
*/
|
||||
private function parseMessage(string $message): void
|
||||
{
|
||||
Log::info(sprintf('%s:+ Processing message [%d] bytes',self::LOGKEY,strlen($message)));
|
||||
Log::info(sprintf('%s:+ Processing packet message [%d] bytes',self::LOGKEY,strlen($message)));
|
||||
|
||||
$msg = Message::parseMessage($message,$this->zone);
|
||||
|
||||
// If the message from domain is different to the packet address domain, we'll skip this message
|
||||
|
||||
// If the message is invalid, we'll ignore it
|
||||
if ($msg->errors) {
|
||||
Log::info(sprintf('%s:- Message [%s] has errors',self::LOGKEY,$msg->msgid));
|
||||
|
||||
// If the from address doenst exist, we'll create a new entry
|
||||
// If the messages is not for the right zone, we'll ignore it
|
||||
if ($msg->errors->messages()->has('invalid-zone')) {
|
||||
Log::alert(sprintf('%s:! Message is from an invalid zone [%s], packet is from [%s] - ignoring it',self::LOGKEY,$msg->fftn,$msg->zone->domain->name));
|
||||
|
||||
if (! $msg->rescanned->count())
|
||||
Notification::route('netmail',$this->fftn_o)->notify(new EchomailBadAddress($msg));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If the to address doenst exist, we'll create a new entry
|
||||
if ($msg->errors->messages()->has('to') && $msg->tzone) {
|
||||
try {
|
||||
// @todo Need to work out the correct region for the host_id
|
||||
@ -450,15 +481,13 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
$ao->role = Address::NODE_UNKNOWN;
|
||||
|
||||
$so = System::createUnknownSystem();
|
||||
// @todo Remove this debugging line
|
||||
if ($so->id !== 443)
|
||||
Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] A',self::LOGKEY,$msg->msgid));
|
||||
|
||||
$so->addresses()->save($ao);
|
||||
|
||||
Log::alert(sprintf('%s:- To FTN is not defined, creating new entry for [%s] (%d)',self::LOGKEY,$msg->tboss,$ao->id));
|
||||
}
|
||||
|
||||
// If the from address doenst exist, we'll create a new entry
|
||||
if ($msg->errors->messages()->has('from') && $msg->tzone) {
|
||||
try {
|
||||
// @todo Need to work out the correct region for the host_id
|
||||
@ -485,9 +514,6 @@ class Packet extends FTNBase implements \Iterator, \Countable
|
||||
$ao->role = Address::NODE_UNKNOWN;
|
||||
|
||||
$so = System::createUnknownSystem();
|
||||
// @todo Remvoe this debugging line
|
||||
if ($so->id !== 443)
|
||||
Log::alert(sprintf('%s:? Just created Discovered System for MSGID [%s] B',self::LOGKEY,$msg->msgid));
|
||||
|
||||
$so->addresses()->save($ao);
|
||||
|
||||
|
@ -6,7 +6,7 @@ use Illuminate\Console\Command;
|
||||
|
||||
use App\Classes\File;
|
||||
use App\Classes\FTN\Packet;
|
||||
use App\Models\System;
|
||||
use App\Models\Address;
|
||||
|
||||
class PacketInfo extends Command
|
||||
{
|
||||
@ -17,7 +17,7 @@ class PacketInfo extends Command
|
||||
*/
|
||||
protected $signature = 'packet:info'
|
||||
.' {file : Packet to process}'
|
||||
.' {system? : System the packet is from}';
|
||||
.' {ftn? : FTN the packet is from}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -35,10 +35,10 @@ class PacketInfo extends Command
|
||||
public function handle()
|
||||
{
|
||||
$f = new File($this->argument('file'));
|
||||
$s = $this->argument('system') ? System::where('name',$this->argument('system'))->singleOrFail() : NULL;
|
||||
$a = $this->argument('ftn') ? Address::findFTN($this->argument('ftn')) : NULL;
|
||||
|
||||
foreach ($f as $packet) {
|
||||
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$s);
|
||||
$pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$a->zone->domain);
|
||||
|
||||
$this->alert(sprintf('File Name: %s',$x));
|
||||
|
||||
|
@ -34,6 +34,7 @@ class PacketProcess extends Command
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \App\Classes\FTN\InvalidPacketException
|
||||
* @todo Should this just call PacketProcess instead?
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
@ -41,14 +42,12 @@ class PacketProcess extends Command
|
||||
$a = Address::findFTN($this->argument('ftn'));
|
||||
|
||||
foreach ($f as $packet) {
|
||||
foreach (Packet::process($packet,$f->itemName(),$f->itemSize(),$a->system) as $msg) {
|
||||
foreach ($pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$a->zone->domain) as $msg) {
|
||||
// @todo Quick check that the packet should be processed by us.
|
||||
// @todo validate that the packet's zone is in the domain.
|
||||
|
||||
$this->info(sprintf('Processing message from [%s] with msgid [%s] in (%s)',$msg->fboss,$msg->msgid,$f->pktName()));
|
||||
|
||||
// Dispatch job.
|
||||
Job::dispatchSync($msg,$f->pktName(),$a,$a,Carbon::now(),$this->option('nobot'));
|
||||
Job::dispatchSync($msg,$f->pktName(),$a,$pkt->fftn_o,Carbon::now(),$this->option('nobot'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,18 +8,20 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
use App\Classes\FTN\Message;
|
||||
use App\Models\{Address,Echoarea,Echomail,Netmail,Setup,User};
|
||||
use App\Notifications\Netmails\{EchoareaNotExist,EchoareaNotSubscribed,EchoareaNoWrite,NetmailForward,Reject};
|
||||
use App\Traits\ParseAddresses;
|
||||
|
||||
class MessageProcess implements ShouldQueue
|
||||
{
|
||||
private const LOGKEY = 'JMP';
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable,InteractsWithQueue,Queueable,SerializesModels,ParseAddresses;
|
||||
|
||||
private Address $sender;
|
||||
private Message $msg;
|
||||
@ -125,7 +127,7 @@ class MessageProcess implements ShouldQueue
|
||||
|
||||
$o->set_pkt = $this->packet;
|
||||
$o->set_sender = $this->sender;
|
||||
$o->set_path = $this->msg->pathaddress;
|
||||
$o->set_path = $this->msg->via;
|
||||
$o->set_recvtime = $this->recvtime;
|
||||
// Strip any local/transit flags
|
||||
$o->flags &= ~(Message::FLAG_LOCAL|Message::FLAG_INTRANSIT);
|
||||
@ -245,11 +247,13 @@ class MessageProcess implements ShouldQueue
|
||||
Log::debug(sprintf('%s:- Processing echomail [%s] in [%s].',self::LOGKEY,$this->msg->msgid,$this->msg->echoarea));
|
||||
|
||||
if (! $this->pktsrc->zone->domain->zones->pluck('zone_id')->contains($this->msg->fboss_o->zone->zone_id)) {
|
||||
Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d]',
|
||||
Log::alert(sprintf('%s:! The message [%s] is from a different zone [%d] than the packet sender [%d] - not importing',
|
||||
self::LOGKEY,
|
||||
$this->msg->msgid,
|
||||
$this->msg->fboss_o->zone->zone_id,
|
||||
$this->pktsrc->zone->zone_id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicate messages
|
||||
@ -257,7 +261,8 @@ class MessageProcess implements ShouldQueue
|
||||
if ($this->msg->msgid) {
|
||||
$o = Echomail::where('msgid',$this->msg->msgid)
|
||||
->where('fftn_id',($x=$this->msg->fboss_o) ? $x->id : NULL)
|
||||
->where('datetime','>',Carbon::now()->subYears(3))
|
||||
->where('datetime','>=',$this->msg->date->subYears(3))
|
||||
->where('datetime','<=',$this->msg->date)
|
||||
->single();
|
||||
|
||||
Log::debug(sprintf('%s:- Checking for duplicate from host id [%d].',self::LOGKEY,($x=$this->msg->fboss_o) ? $x->id : NULL));
|
||||
@ -276,12 +281,32 @@ class MessageProcess implements ShouldQueue
|
||||
|
||||
$o->save();
|
||||
|
||||
// If the path is empty, then its probably because of the previous bug, we'll replace it.
|
||||
// @todo This duplicate message may have gone via a different path, be nice to record it.
|
||||
//$o->path()->sync($o->path->pluck('id')->merge($this->msg->pathaddress)->toArray());
|
||||
// If we didnt get the path on the original message, we'll override it
|
||||
if (! $o->path->count()) {
|
||||
$dummy = collect();
|
||||
$path = $this->parseAddresses('path',$this->msg->path,$this->pktsrc->zone,$dummy);
|
||||
|
||||
$ppoid = NULL;
|
||||
foreach ($path as $aoid) {
|
||||
|
||||
$po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[
|
||||
$o->id,
|
||||
$aoid,
|
||||
$ppoid,
|
||||
]);
|
||||
|
||||
$ppoid = $po[0]->id;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo if we have an export for any of the seenby addresses, remove it
|
||||
// @todo add received packet details
|
||||
$o->seenby()->sync($o->seenby->pluck('id')->merge($this->msg->seenaddress)->filter()->toArray());
|
||||
$seenby = $this->parseAddresses('seenby',$this->msg->seenby,$this->pktsrc->zone,$o->rogue_seenby);
|
||||
$x = $o->seenby()->syncWithoutDetaching($seenby);
|
||||
|
||||
// In case our rogue_seenby changed
|
||||
if ($o->getDirty())
|
||||
$o->save();
|
||||
|
||||
return;
|
||||
}
|
||||
@ -305,7 +330,7 @@ class MessageProcess implements ShouldQueue
|
||||
// @todo Can the sender create it if it doesnt exist?
|
||||
// Can the system send messages to this area?
|
||||
if (! $ea->sec_write || ($this->pktsrc->security < $ea->sec_write)) {
|
||||
Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$this->pktsrc,$this->msg->msgid,$ea->name));
|
||||
Log::alert(sprintf('%s:! FTN [%s] is not allowed to post [%s] to [%s].',self::LOGKEY,$this->pktsrc->ftn,$this->msg->msgid,$ea->name));
|
||||
if (! $this->msg->rescanned->count())
|
||||
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNoWrite($this->msg));
|
||||
|
||||
@ -314,7 +339,7 @@ class MessageProcess implements ShouldQueue
|
||||
|
||||
// If the node is not subscribed
|
||||
if ($this->pktsrc->echoareas->search(function($item) use ($ea) { return $item->id === $ea->id; }) === FALSE) {
|
||||
Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc,$ea->name,$this->msg->msgid));
|
||||
Log::alert(sprintf('%s:! FTN [%s] is not subscribed to [%s] for [%s].',self::LOGKEY,$this->pktsrc->ftn,$ea->name,$this->msg->msgid));
|
||||
|
||||
if (! $this->msg->rescanned->count())
|
||||
Notification::route('netmail',$this->pktsrc)->notify(new EchoareaNotSubscribed($this->msg));
|
||||
@ -338,9 +363,8 @@ class MessageProcess implements ShouldQueue
|
||||
$o->msg = $this->msg->message_src."\r";
|
||||
$o->msg_src = $this->msg->message_src;
|
||||
$o->msg_crc = md5($this->msg->message);
|
||||
$o->rogue_seenby = $this->msg->rogue_seenby;
|
||||
$o->set_path = $this->msg->pathaddress;
|
||||
$o->set_seenby = $this->msg->seenaddress;
|
||||
$o->set_path = $this->msg->path;
|
||||
$o->set_seenby = $this->msg->seenby;
|
||||
$o->set_recvtime = $this->recvtime;
|
||||
// Record receiving packet and sender
|
||||
$o->set_pkt = $this->packet;
|
||||
|
@ -59,7 +59,7 @@ class PacketProcess implements ShouldQueue
|
||||
$processed = FALSE;
|
||||
|
||||
foreach ($f as $packet) {
|
||||
$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system);
|
||||
$pkt = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->zone->domain);
|
||||
|
||||
// Check the messages are from the uplink
|
||||
if ($this->ao->system->addresses->search(function($item) use ($pkt) { return $item->id === $pkt->fftn_o->id; }) === FALSE) {
|
||||
|
@ -13,11 +13,11 @@ use Rennokki\QueryCache\Traits\QueryCacheable;
|
||||
use App\Casts\{CollectionOrNull,CompressedString};
|
||||
use App\Classes\FTN\Message;
|
||||
use App\Interfaces\Packet;
|
||||
use App\Traits\{EncodeUTF8,MsgID};
|
||||
use App\Traits\{EncodeUTF8,MsgID,ParseAddresses};
|
||||
|
||||
final class Echomail extends Model implements Packet
|
||||
{
|
||||
use SoftDeletes,EncodeUTF8,MsgID,QueryCacheable;
|
||||
use SoftDeletes,EncodeUTF8,MsgID,QueryCacheable,ParseAddresses;
|
||||
|
||||
private const LOGKEY = 'ME-';
|
||||
private Collection $set_seenby;
|
||||
@ -68,17 +68,17 @@ final class Echomail extends Model implements Packet
|
||||
|
||||
// @todo if the message is updated with new SEEN-BY's from another route, we'll delete the pending export for systems (if there is one)
|
||||
static::created(function($model) {
|
||||
if (! $model->echoarea_id) {
|
||||
Log::alert(sprintf('%s:- Message has no echoarea, not exporting',self::LOGKEY,$model->id));
|
||||
return;
|
||||
}
|
||||
$rogue = collect();
|
||||
$seenby = NULL;
|
||||
$path = [];
|
||||
|
||||
// Save the seenby
|
||||
$model->seenby()->sync($model->set_seenby);
|
||||
// Parse PATH
|
||||
if ($model->set_path->count())
|
||||
$path = self::parseAddresses('path',$model->set_path,$model->fftn->zone,$rogue);
|
||||
|
||||
// Save the Path
|
||||
$ppoid = NULL;
|
||||
foreach ($model->set_path as $aoid) {
|
||||
foreach ($path as $aoid) {
|
||||
$po = DB::select('INSERT INTO echomail_path (echomail_id,address_id,parent_id) VALUES (?,?,?) RETURNING id',[
|
||||
$model->id,
|
||||
$aoid,
|
||||
@ -88,12 +88,26 @@ final class Echomail extends Model implements Packet
|
||||
$ppoid = $po[0]->id;
|
||||
}
|
||||
|
||||
$rogue = collect();
|
||||
|
||||
// Parse SEEN-BY
|
||||
if ($model->set_seenby->count())
|
||||
$seenby = self::parseAddresses('seenby',$model->set_seenby,$model->fftn->zone,$rogue);
|
||||
|
||||
if (count($rogue)) {
|
||||
$model->rogue_seenby = $rogue;
|
||||
$model->save();
|
||||
}
|
||||
|
||||
if ($seenby)
|
||||
$model->seenby()->sync($seenby);
|
||||
|
||||
// Our last node in the path is our sender
|
||||
if (isset($model->set_pkt) && isset($model->set_recvtime)) {
|
||||
DB::update('UPDATE echomail_path set recv_pkt=?,recv_at=? where address_id=? and echomail_id=?',[
|
||||
$model->set_pkt,
|
||||
$model->set_recvtime,
|
||||
$model->set_path->last(),
|
||||
$path->last(),
|
||||
$model->id,
|
||||
]);
|
||||
}
|
||||
@ -105,7 +119,7 @@ final class Echomail extends Model implements Packet
|
||||
->addresses
|
||||
->filter(function($item) use ($model) { return $item->security >= $model->echoarea->sec_read; }))
|
||||
->pluck('id')
|
||||
->diff($model->set_seenby);
|
||||
->diff($seenby);
|
||||
|
||||
if ($exportto->count()) {
|
||||
if ($model->no_export) {
|
||||
|
@ -15,8 +15,6 @@ use App\Classes\FTN\Message;
|
||||
use App\Interfaces\Packet;
|
||||
use App\Traits\{EncodeUTF8,MsgID};
|
||||
|
||||
// @deprecated recv_pkt now in netmail_path
|
||||
// @deprecated local - use flags
|
||||
final class Netmail extends Model implements Packet
|
||||
{
|
||||
private const LOGKEY = 'MN-';
|
||||
@ -66,37 +64,68 @@ final class Netmail extends Model implements Packet
|
||||
parent::boot();
|
||||
|
||||
static::created(function($model) {
|
||||
$nodes = collect();
|
||||
|
||||
// Parse PATH
|
||||
// <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]
|
||||
if (isset($model->set_path)) {
|
||||
if ($model->set_path->count()) {
|
||||
foreach ($model->set_path as $line) {
|
||||
$m = [];
|
||||
|
||||
if (preg_match('/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/',$line,$m)) {
|
||||
// Address
|
||||
$ao = Address::findFTN($m[1]);
|
||||
|
||||
// Time
|
||||
$t = [];
|
||||
$datetime = '';
|
||||
|
||||
if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
|
||||
Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
|
||||
else
|
||||
$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');
|
||||
|
||||
if (! $ao) {
|
||||
Log::alert(sprintf('%s:! Undefined Node [%s] for Netmail.',self::LOGKEY,$m[1]));
|
||||
//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);
|
||||
|
||||
} else {
|
||||
$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no details (Mystic), we'll create a blank
|
||||
} else {
|
||||
$nodes->push(['node'=>$model->set_sender,'datetime'=>Carbon::now(),'program'=>'Unknown']);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the Path
|
||||
$ppoid = NULL;
|
||||
|
||||
if (isset($model->set_path)) {
|
||||
// If there are no details (Mystic), we'll create a blank
|
||||
if (! $model->set_path->count()) {
|
||||
$model->set_path->push(['node'=>$model->set_sender,'datetime'=>Carbon::now(),'program'=>'Unknown']);
|
||||
}
|
||||
foreach ($nodes as $path) {
|
||||
$po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[
|
||||
$model->id,
|
||||
$path['node']->id,
|
||||
$ppoid,
|
||||
(string)$path['datetime'],
|
||||
$path['program'],
|
||||
]);
|
||||
|
||||
foreach ($model->set_path as $path) {
|
||||
$po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[
|
||||
$model->id,
|
||||
$path['node']->id,
|
||||
$ppoid,
|
||||
(string)$path['datetime'],
|
||||
$path['program'],
|
||||
]);
|
||||
$ppoid = $po[0]->id;
|
||||
}
|
||||
|
||||
$ppoid = $po[0]->id;
|
||||
}
|
||||
|
||||
// Our last node in the path is our sender
|
||||
if (isset($model->set_pkt) && isset($model->set_sender) && isset($model->set_recvtime)) {
|
||||
DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[
|
||||
$model->set_pkt,
|
||||
$model->set_recvtime,
|
||||
$model->set_sender->id,
|
||||
Arr::get($model->set_path->last(),'node')->id,
|
||||
$model->id,
|
||||
]);
|
||||
}
|
||||
// Our last node in the path is our sender
|
||||
if ($nodes->count() && isset($model->set_pkt) && isset($model->set_sender) && isset($model->set_recvtime)) {
|
||||
DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[
|
||||
$model->set_pkt,
|
||||
$model->set_recvtime,
|
||||
$model->set_sender->id,
|
||||
Arr::get($nodes->last(),'node')->id,
|
||||
$model->id,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
75
app/Notifications/Netmails/EchomailBadAddress.php
Normal file
75
app/Notifications/Netmails/EchomailBadAddress.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Netmails;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Classes\FTN\Message;
|
||||
use App\Notifications\Netmails;
|
||||
use App\Models\{Netmail,System};
|
||||
use App\Traits\{MessagePath,PageTemplate};
|
||||
|
||||
class EchomailBadAddress extends Netmails
|
||||
{
|
||||
use MessagePath,PageTemplate;
|
||||
|
||||
private const LOGKEY = 'NBA';
|
||||
|
||||
private Message $mo;
|
||||
|
||||
/**
|
||||
* Send a sysop a message if they give us a message with a bad address in it.
|
||||
*
|
||||
* @param Message $mo
|
||||
*/
|
||||
public function __construct(Message $mo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mo = $mo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param System $so
|
||||
* @param mixed $notifiable
|
||||
* @return Netmail
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toNetmail(System $so,object $notifiable): Netmail
|
||||
{
|
||||
$o = $this->setupNetmail($so,$notifiable);
|
||||
$ao = $notifiable->routeNotificationFor(static::via);
|
||||
|
||||
Log::info(sprintf('%s:+ Creating ECHOMAIL BAD ADDRESS netmail to [%s]',self::LOGKEY,$ao->ftn));
|
||||
|
||||
$o->subject = sprintf('Bad address in echomail [%s]',$this->mo->msgid);
|
||||
|
||||
// Message
|
||||
$msg = $this->page(FALSE,'badmsg');
|
||||
|
||||
$msg->addText(
|
||||
sprintf("Your echomail with ID [%s] to [%s] here was received here on [%s] and it looks like you sent it on [%s].\r\r",
|
||||
$this->mo->msgid,
|
||||
$this->mo->user_to,
|
||||
Carbon::now()->utc()->toDateTimeString(),
|
||||
$this->mo->date->utc()->toDateTimeString(),
|
||||
)
|
||||
);
|
||||
|
||||
$msg->addText(sprintf("The address in this echomail [%s] is the wrong address for this domain [%s].\r\r",$this->mo->fftn,$ao->zone->domain->name));
|
||||
|
||||
$msg->addText("This echomail has been rejected and not stored here - so no downstream nodes will receive it. If you think this is a mistake, please let me know.\r\r");
|
||||
|
||||
$msg->addText($this->message_path($this->mo));
|
||||
|
||||
$o->msg = $msg->render();
|
||||
$o->tagline = 'I enjoyed reading your message, even though nobody else will get it :)';
|
||||
|
||||
$o->save();
|
||||
|
||||
return $o;
|
||||
}
|
||||
}
|
66
app/Traits/ParseAddresses.php
Normal file
66
app/Traits/ParseAddresses.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Http\Controllers\DomainController;
|
||||
use App\Models\{Address,System,Zone};
|
||||
|
||||
trait ParseAddresses
|
||||
{
|
||||
/**
|
||||
* Parse the Seenby/path lines and return a collection of addresses
|
||||
*
|
||||
* @param string $type Type of address, ie: seenby/path
|
||||
* @param Collection $addresses
|
||||
* @param Zone $zone
|
||||
* @param Collection $rogue
|
||||
* @return Collection
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function parseAddresses(string $type,Collection $addresses,Zone $zone,Collection &$rogue): Collection
|
||||
{
|
||||
$nodes = collect();
|
||||
|
||||
$net = NULL;
|
||||
foreach ($addresses as $line) {
|
||||
foreach (explode(' ',$line) as $item) {
|
||||
if (($x=strpos($item,'/')) !== FALSE) {
|
||||
$net = (int)substr($item,0,$x);
|
||||
$node = (int)substr($item,$x+1);
|
||||
|
||||
} else {
|
||||
$node = (int)$item;
|
||||
}
|
||||
|
||||
// If domain should be flattened, look for node regardless of zone (within the list of zones for the domain)
|
||||
$ao = ($zone->domain->flatten)
|
||||
? Address::findZone($zone->domain,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,0)
|
||||
: Address::findFTN(sprintf('%d:%d/%d',$zone->zone_id,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX));
|
||||
|
||||
switch ($type) {
|
||||
case 'seenby':
|
||||
if (! $ao)
|
||||
$rogue->push(sprintf('%d:%d/%d',$zone->domain->flatten ? 0 : $zone->zone_id,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX));
|
||||
else
|
||||
$nodes->push($ao->id);
|
||||
|
||||
break;
|
||||
|
||||
case 'path':
|
||||
if (! $ao) {
|
||||
$ftn = sprintf('%d:%d/%d@%s',$zone->zone_id,$net&DomainController::NUMBER_MAX,$node&DomainController::NUMBER_MAX,$zone->domain->name);
|
||||
$ao = Address::createFTN($ftn,System::createUnknownSystem());
|
||||
}
|
||||
|
||||
$nodes->push($ao->id);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ class PacketTest extends TestCase
|
||||
$this->do = Domain::firstOrCreate(['name'=>'packets','active'=>TRUE]);
|
||||
}
|
||||
|
||||
/*
|
||||
* @todo This packet doesnt have an origin line, need a new one with nomsg and an origin line
|
||||
public function test_nomsgid_origin()
|
||||
{
|
||||
$this->init();
|
||||
@ -50,7 +52,10 @@ class PacketTest extends TestCase
|
||||
$this->assertTrue($messages);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* @todo We are not correctly setup to parse messages without an origin line
|
||||
public function test_nomsgid_noorigin()
|
||||
{
|
||||
$this->init();
|
||||
@ -60,10 +65,10 @@ class PacketTest extends TestCase
|
||||
$zo = Zone::firstOrCreate(['zone_id'=>10,'default'=>TRUE,'active'=>TRUE,'domain_id'=>$this->do->id,'system_id'=>$this->so->id]);
|
||||
$src = Address::firstOrCreate(['zone_id'=>$zo->id,'region_id'=>0,'host_id'=>999,'node_id'=>1,'point_id'=>0,'role'=>Address::NODE_ACTIVE,'active'=>TRUE,'system_id'=>$this->so->id]);
|
||||
|
||||
// This packet has an incorrect zone in the Origin
|
||||
// This packet has no Origin Line
|
||||
$f = new File(__DIR__.'/data/test_nomsgid_noorigin.pkt');
|
||||
foreach ($f as $packet) {
|
||||
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->so);
|
||||
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do);
|
||||
|
||||
$this->assertEquals(1,$pkt->count());
|
||||
|
||||
@ -71,15 +76,14 @@ class PacketTest extends TestCase
|
||||
foreach ($pkt as $msg) {
|
||||
$messages = TRUE;
|
||||
$this->assertNotTrue($msg->isNetmail());
|
||||
|
||||
$this->assertNotFalse($msg->path->search('1/1 999/1'));
|
||||
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertNotFalse($msg->seenby->search('1/1 4 3/0 999/1 999'));
|
||||
}
|
||||
|
||||
$this->assertTrue($messages);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public function test_msgid_origin()
|
||||
{
|
||||
@ -93,7 +97,7 @@ class PacketTest extends TestCase
|
||||
// This packet has an incorrect zone in the Origin
|
||||
$f = new File(__DIR__.'/data/test_msgid_origin.pkt');
|
||||
foreach ($f as $packet) {
|
||||
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->so);
|
||||
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do);
|
||||
|
||||
$this->assertEquals(1,$pkt->count());
|
||||
|
||||
@ -101,9 +105,7 @@ class PacketTest extends TestCase
|
||||
foreach ($pkt as $msg) {
|
||||
$messages = TRUE;
|
||||
$this->assertNotTrue($msg->isNetmail());
|
||||
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertNotFalse($msg->seenaddress->search($src->id));
|
||||
$this->assertNotFalse($msg->seenby->search('1/1 999/1 999'));
|
||||
}
|
||||
|
||||
$this->assertTrue($messages);
|
||||
@ -112,10 +114,14 @@ class PacketTest extends TestCase
|
||||
|
||||
public function test_packet_parse()
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$zo = Zone::firstOrCreate(['zone_id'=>21,'default'=>TRUE,'active'=>TRUE,'domain_id'=>$this->do->id,'system_id'=>$this->so->id]);
|
||||
|
||||
// This packet has a SOH<char>SOH sequence
|
||||
$f = new File(__DIR__.'/data/test_binary_content-2.pkt');
|
||||
foreach ($f as $packet) {
|
||||
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize());
|
||||
$pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$zo->domain);
|
||||
|
||||
$this->assertEquals(1,$pkt->count());
|
||||
|
||||
@ -127,7 +133,6 @@ class PacketTest extends TestCase
|
||||
$this->assertSame('21:1/151 6189F64C',$msg->msgid);
|
||||
$this->assertSame('db727bd3778ddd457784ada4bf016010',md5($msg->message));
|
||||
$this->assertSame('5b627ab5936b0550a97b738f4deff419',md5($msg->message_src));
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertContains('3/2744 4/100 106 5/100',$msg->seenby);
|
||||
$this->assertContains('1/151 100 3/100',$msg->path);
|
||||
$this->assertCount(11,$msg->seenby);
|
||||
@ -152,7 +157,6 @@ class PacketTest extends TestCase
|
||||
$this->assertSame('21:1/126 eec6e958',$msg->msgid);
|
||||
$this->assertSame('5a525cc1c393292dc65160a852d4d615',md5($msg->message));
|
||||
$this->assertSame('a3193edcc68521d4ed07da6db2aeb0b6',md5($msg->message_src));
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertContains('1/995 2/100 116 1202 3/100 105 107 108 109 110 111 112 113 117 119',$msg->seenby);
|
||||
$this->assertContains('1/126 100 3/100',$msg->path);
|
||||
$this->assertCount(10,$msg->seenby);
|
||||
@ -177,8 +181,6 @@ class PacketTest extends TestCase
|
||||
$this->assertSame('10:999/1 612aecda',$msg->msgid);
|
||||
$this->assertSame('61078e680cda04c8b5eba0f712582e70',md5($msg->message));
|
||||
$this->assertSame('b9d65d4f7319ded282f3f1986276ae79',md5($msg->message_src));
|
||||
$this->assertCount(1,$msg->pathaddress);
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertContains('1/1 999/1 999',$msg->seenby);
|
||||
$this->assertContains('999/1',$msg->path);
|
||||
$this->assertCount(1,$msg->seenby);
|
||||
@ -220,7 +222,6 @@ class PacketTest extends TestCase
|
||||
$this->assertSame('3:712/886 220da89f',$msg->msgid);
|
||||
$this->assertSame('9f5544bea46ef57a45f561b9e07dd71e',md5($msg->message));
|
||||
$this->assertSame('9bf4b8c348ac235cc218577abf7140af',md5($msg->message_src));
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertContains('633/0 267 280 281 408 410 412 509 509 640/1384 712/114 550 620 848',$msg->seenby);
|
||||
$this->assertContains('712/886 848 633/280',$msg->path);
|
||||
$this->assertCount(2,$msg->seenby);
|
||||
@ -231,7 +232,6 @@ class PacketTest extends TestCase
|
||||
$this->assertSame('',$msg->msgid);
|
||||
$this->assertSame('b975057002def556c5a9497aacd000fb',md5($msg->message));
|
||||
$this->assertSame('c90dd234d2aa029af22c453a25b79a4e',md5($msg->message_src));
|
||||
$this->assertCount(0,$msg->rogue_seenby);
|
||||
$this->assertContains('633/267 280 281 384 408 410 412 418 420 509 509 712/848 770/1 100 330',$msg->seenby);
|
||||
$this->assertContains('772/210 770/1 633/280',$msg->path);
|
||||
$this->assertCount(2,$msg->seenby);
|
||||
|
Loading…
Reference in New Issue
Block a user