Neue Erweiterung: PN-Exporter von mmA

Informationen der Administration oder Moderation
Antworten
Benutzeravatar
TheSilence
Kommt an keinem Thema vorbei
Beiträge: 278
Registriert: Dienstag 15. August 2023, 12:00
Geschlecht: männlich
AB Status: AB 30+
Wohnort: Nordwestdeutschland

Neue Erweiterung: PN-Exporter von mmA

Beitrag von TheSilence »

Hallo Zusammen,

wie ihr der Diskussion in diesem Thread entnehmen könnt und vielleicht auch schon selbst bemerkt hat, ist die Möglichkeit, PNs zu exportieren, standardmässig etwas beschränkt und unkomfortabel. Mit müden Augen hat sich daher freundlicherweise dran gesetzt und eine kleine Erweiterung programmiert, mit der man alle persönlichen Nachrichten in einem Rutsch und mit allen wesentlichen Informationen als XML exportieren kann. Nach ein paar Tests habe ich diese Erweiterung nun im Forum installiert.

Wie wird die Erweiterung benutzt?
Ihr findet im Bereich Private Nachrichten nun in der linken Spalte ganz unten einen neuen Button PN-Export (mmA). Wenn ihr dort draufklickt, öffnet sich im rechten Bereich eine neue Seite mit einem kurzen Erklärungstext und dem Button Exportieren. Dort geklickt werden eure PNs dann als XML-Datei heruntergeladen.

Wenn ihr noch Fragen dazu habt, könnt ihr diese wohl am Besten direkt hier stellen. Ich denke, mmA wird sie euch dann gerne beantworten (ich aber auch, soweit ich kann).

TheSilence
Mit müden Augen
Begeisterter Schreiberling
Beiträge: 1654
Registriert: Dienstag 15. August 2023, 12:00
Geschlecht: männlich
AB Status: Hardcore AB
Ich bin ...: nur an Frauen interessiert.

Re: Neue Erweiterung: PN-Exporter von mmA

Beitrag von Mit müden Augen »

Vielen Dank an dich! :hut:

Ein Hinweis: Wenn man statt seiner PN die Meldung "Das Formular war ungültig" (oä) erhält einfach nochmal probieren aber etwas (1-2 Sekunden) warten bevor man auf den Knopf klickt. Bei meinen Tests (in einem Testforum) hatte ich dieses Problem öfters wenn ich zu schnell war.

Eine Ankündigung: Ich habe ein Perlscript (was über die Kommandozeile bedient wird und ob es unter Windows läuft ist mir nicht bekannt, grundsätzlich ist Perl auch für Windows verfügbar) welches aus dem exportierten XML vernünftig lesbares HTML macht (oder TXT). Das werde ich zeitnah veröffentlichen, TheSilence muss mir nur sagen in welchem Thread/Unterforum am besten.
Nehmt Abschied, Brüder, ungewiss
ist alle Wiederkehr
Die Zukunft liegt in Finsternis


Das ist doch kein Leben. :cry:
Mit müden Augen
Begeisterter Schreiberling
Beiträge: 1654
Registriert: Dienstag 15. August 2023, 12:00
Geschlecht: männlich
AB Status: Hardcore AB
Ich bin ...: nur an Frauen interessiert.

Re: Neue Erweiterung: PN-Exporter von mmA

Beitrag von Mit müden Augen »

Wie versprochen hier mein "PM-Cruncher" :tippen: .

Erstmal der rechtliche Krempel:
LIZENZ
-Exklusiv für das ABF / die von dort exportierten Daten.
-Verwendung und unveränderte Weitergabe innerhalb des ABF frei. Externe Weitergabe verboten.
-Keinerlei Garantie usw.
-Modifizierung für private Zwecke erlaubt, aber keine Weitergabe/Veröffentlichung der modifizierten Version.

Selbstverständlich gehören die XML-Dateien mit euren PN euch und ihr könnt mit diesen machen was ihr wollt. Das genaue Format ist nicht dokumentiert aber (hoffentlich) einigermaßen selbsterklärend für Leute die ihren eigenen Code darauf loslassen wollen. Im Zweifel hier fragen (ich habe keine Lust eine Spezifikation zu schreiben die dann eh niemand liest...).


GRUNDSÄTZLICHES
Mein Skript ist in Perl 5 geschrieben. Es wird über die Kommandozeile bedient und ob es unter Windows läuft ist mir nicht bekannt, ich habe Linux. Grundsätzlich ist Perl auch für Windows verfügbar. Mit einem Programm mit graphischer Oberfläche ("GUI") kann ich leider nicht dienen. Vielleicht programmiert ja jemand anderes sowas.

Das Skript benutzt folgende Perl-Module die eventuell vorher installiert werden müssen:

Code: Alles auswählen

Getopt::Long
XML::LibXML
HTML::Entities
Wie genau das funktioniert ist vom Betriebssystem abhängig. Im Zweifel bitte zuerst eine Suchmaschine und dann ggf hier fragen.

Das Skript erzeugt eine (unter Umständen sehr große) HTML-Datei, also "Webseite". Dies ermöglicht es, im Gegensatz zum TXT-Format (einfacher Text), Smilies und Formatierungen ("BBCode") richtig darzustellen, annähernd wie hier im Forum. Geöffnet wird diese HTML-Datei im Browser der Wahl (normalerweise einfach per Doppelklick wiede jede andere Datei), die Formatierungen usw sind allerdings nur mit Firefox 128 getestet worden.

Die Smilies sowie PN-Symbole ("Icons") müssen lokal auf dem Computer vorhanden sein. Es werden beim Öffnen der HTML-Datei keinerlei Daten aus dem Internet geladen oder ins Internet übertragen, die Anzeige erfolgt komplett "offline".

Die Smilies und Icons werden von TheSilence freundlicherweise direkt zum Download bereitgestellt: smilies_icons.zip

Der Code vom Skript findet sich im nächsten Post.

Die Ausgaben vom Skript sind auf Englisch, das ist Absicht.

Die HTML-Ansicht ist relativ spartanisch, ich habe von Webzeug fast keine Ahnung (aber eine gewisse Abneigung dagegen...) und wollte nicht unnötig Zeit investieren. Siehe auch Punkt 1 der FAQ.


INSTALLATION
Einen Arbeitsordner mit beliebigem Namen anlegen in dem alle Dateien landen sollen, z.B. "PN_ABF".

Den Code vom Skript in eine Textdatei namens "pm_cruncher.pl" kopieren. Es muss eine reine Textdatei sein, also nicht z.B. ODT wie standardmäßig von Libre Office verwendet.

Nur für Linux: Wahlweise die Datei wie üblich als ausführbar markieren.

Das Archiv mit den Smilies und Icons runterladen (Rechtsklick -> "Ziel speichern unter") und in den vorher angelegten Ordner entpacken. Die Unterordner/Struktur muss dabei erhalten bleiben, sonst funktioniert später die Anzeige nicht.


BEDIENUNG
Hier wird es etwas nerdig. :roll: Wie bereits geschrieben wird das Skript per Kommandozeile bedient. Ich versuche die Erklärungen allgemein und einfach zu halten. Im Zweifel bitte auch hier eine Suchmaschine befragen.

Der grundsätzliche Aufruf des Skriptes erfolgt so, unter der Annahme man befindet sich im richtigen Ordner:

Code: Alles auswählen

perl pm_cruncher.pl ARGUMENTE
Das Wort in Großbuchstaben ist ein Platzhalter für die Argumente/Parameter die dem Programm mitgeteilt werden. Zwei Argumente müssen angegeben werden:
--input-file ist der Name der aus dem ABF exportierten XML-Datei.
--mode ist der "Betriebsmodus", siehe weiter unten.

Weitere relevante Argumente:
--output-file ist der Name der erzeugten HTML-Datei. Standardmäßig wird "my_pm.html" verwendet wenn dieses Argument nicht angegeben wurde.
--sort bestimmt die Reihenfolge in der die PN sortiert werden. Zulässig sind "asc" (die neueste PN ganz oben) und "desc" (die älteste PN ganz oben, Standard).

Der Rest der Argumente ist für "0815-Normalnutzer" eher uninteressant. Im Zweifel --help oder --usage benutzen für eine kurze Übersicht.

Betriebsmodus
Dies ist neben dem Namen der XML-Datei das wichtigste Argument und muss angegeben werden. Es gibt (aktuell) drei Möglichkeiten:
--mode all übernimmt sämtliche PN aus der XML in die HTML-Datei.
--mode $id übernimmt nur die PN mit der (Datenbank-internen) ID $id (eine Zahl ungleich 0) sowie alle weiteren PN aus der selben "Unterhaltung" (die nach einem Klick auf "Antworten" verfasst wurden).
--mode $user übernimmt nur die PN von/an Forennutzer $user (ein Name ungleich "all", also z.B. "TheSilence"). Wenn der Name Leerzeichen enthält muss er in Anführungszeichen (je nach Betriebssystem).

Wenn ihr alle eure PN sichern wollt ist "all" richtig.

Minimalbeispiel
Folgender Aufruf übernimmt alle PN aus "export.xml", die älteste wird später ganz oben in "my_pm.html" angezeigt:

Code: Alles auswählen

perl pm_cruncher.pl --input-file export.xml --mode all

"F"AQ

Ich finde das zu kompliziert, warum keine graphische Oberfläche?
Weil ich sowas nicht programmieren kann bzw es wäre deutlich mehr Aufwand. Ursprünglich wollte ich "nur" einen vernünftigen Weg meine eigenen PN zu exportieren (siehe meinen Thread aus 2024), am Ende war es viel mehr Arbeit als geplant. Ich stelle das ABF-intern zur Verfügung falls es jemandem hilft, aber in erster Linie wollte ich ein konkretes Problem für mich lösen.
Falls jemand Lust hat eine GUI auch/nur für Windows zu schreiben, nur zu.

Warum Perl? Warum nicht Python oder so?
Man benutzt die Werkzeuge die man (halbwegs) beherrscht... Ich kann kein Python und mag die Sprache irgendwie nicht.

Wo finde ich die PN-ID?
Aktuell werden die ID nicht in die HTML-Datei übernommen und somit auch nicht angezeigt. Man muss die XML-Datei öffnen um die ID einer PN zu finden. Für mich reicht das, falls allgemeines Interesse besteht kann ich aber den Code anpassen damit die ID auch in die HTML-Datei geschrieben werden.

Ich kriege eine komplizierte Fehlermeldung :verwirrt:
Wenn die Fehlermeldung "output file already existing" sagt einen anderen Dateinamen für die HTML-Datei wählen bzw die schon vorhandene Datei (wenn alt und nicht mehr nötig) löschen.
Wenn die Fehlermeldung vom Skript selber kommt ist entweder irgendwas mit den Argumenten verkehrt oder ich habe einen Käfer übersehen... Im Zweifel siehe eine Frage weiter unten.
Wenn die Fehlermeldung vom Perl-Interpreter kommt fehlt am wahrscheinlichsten das Modul XML::LibXML. Siehe oben.

Ich brauche Hilfe...
Einfach hier fragen. Bitte keine PN schicken.

Sicherheitslücke gefunden!
Das sollte eigentlich nicht passieren. :roll: Reale Sicherheitslücken bitte nur per PN an TheSilence (den Forengründer) und mich melden. Für allgemeinen Support und Probleme hingegen bitte keine PN schicken sondern hier posten.


Und nur falls es nicht klar sein sollte: Die XML-Dateien enthalten eure sämtlichen (zum Exportzeitpunkt) PN in unverschlüsselter Form und sind somit genauso vertraulich wie die PN selbst. Passt also auf wo eure Daten/Dateien landen und ladet sie für Supportanfragen o.ä. nicht irgendwo hoch oder postet sie hier!
Nehmt Abschied, Brüder, ungewiss
ist alle Wiederkehr
Die Zukunft liegt in Finsternis


Das ist doch kein Leben. :cry:
Mit müden Augen
Begeisterter Schreiberling
Beiträge: 1654
Registriert: Dienstag 15. August 2023, 12:00
Geschlecht: männlich
AB Status: Hardcore AB
Ich bin ...: nur an Frauen interessiert.

Re: Neue Erweiterung: PN-Exporter von mmA

Beitrag von Mit müden Augen »

Und hier der eigentliche Code Version 0.3.

Code: Alles auswählen

#! /usr/bin/env perl
use strict;
use warnings 'FATAL'=>'all';
use autodie;
use Getopt::Long;
use XML::LibXML;
use HTML::Entities 'encode_entities';
 
use constant SCRIPT_VERSION=>'0.3';

=pod
PM-Cruncher exklusiv für das ABF - (c) 2025 mmA - written from scratch

Dieser Code wandelt die aus dem ABF exportierte XML-Datei mit euren PN in wahlweise TXT oder HTML um. 

BENUTZUNG: "perl pm_cruncher.pl --help" für Hilfe (auf Englisch)

LIZENZ:
-Exklusiv für das ABF / die von dort exportierten Daten.
-Verwendung und unveränderte Weitergabe innerhalb des ABF frei. Externe Weitergabe verboten.
-Keinerlei Garantie usw.
-Modifizierung für private Zwecke erlaubt, aber keine Weitergabe/Veröffentlichung der modifizierten Version.

Bugreports, Support, ... im Rahmen meiner Möglichkeiten im entsprechenden Thread im ABF: https://www.ab-forum.de/viewtopic.php?p=1382495

BEKANNTE FEHLER/LIMITIERUNGEN:
-Spoiler sind immer ausgeklappt/sichtbar, bräuchte Javascript (wontfix)
-Keine Anzeige der internen PN-ID
-Keine Anzeige in welchem (eventuell benutzerdefiniertem) Ordner die PN liegt
-Keine Möglichkeit nur PN aus einem Ordner zu exportieren
-Nicht per BBCode gekennzeichnete Links werden nicht automatisch umgewandelt
-...

STAND: 10.03.25 14:44
=cut

print 'PM-Cruncher for ABF version ',SCRIPT_VERSION,"\n";
print "provided without any warranty\n";
print "(c) 2025 mmA - for licence see top of file\n\n";

my $show_help=0;
my $show_version_only=0;
my $input_file;
my $mode;
my $sort='desc';
my $output_format='html';
my $output_file='my_pm.html';

GetOptions(
	'help|usage' => \$show_help,
	'version' => \$show_version_only,
	'input-file=s' => \$input_file,
	'mode=s' => \$mode,
	'sort=s' => \$sort,
	'output=s' => \$output_format,
	'output-file=s' => \$output_file
) or print_help_and_exit();

exit(0) if($show_version_only);

print_help_and_exit() if($show_help);

die "error: no input file specified, try --help\n" if(!defined($input_file) || !length($input_file));
die "error: no mode specified, try --help\n" if(!defined($mode) || !length($mode));
die "error: invalid sorting specified, try --help\n" if(!defined($sort) || ($sort ne 'asc' && $sort ne 'desc'));
die "error: no or invalid output format specified, try --help\n" if(!defined($output_format) || ($output_format ne 'txt' && $output_format ne 'html'));
die "error: no output file specified, try --help\n" if(!defined($output_file) || !length($output_file));

my $start_time=time();

print "input file:    $input_file\n";
print "output file:   $output_file\n";
print "output format: $output_format\n";
print "parser mode:   $mode\n";
print "sorting mode:  $sort\n";
print 'started at:    ',get_time_str_de($start_time),"\n\n";

die "error: output file \"$output_file\" already existing (and not empty), not overwriting\n" if(-e $output_file && -s $output_file);

die "error: non-existing or empty input file \"$input_file\"\n" if(!-e $input_file || !-s $input_file);

my $parser=XML::LibXML->new(no_network=>1);
my $dom=$parser->load_xml(location=>$input_file);

my @xml_header=$dom->getElementsByTagName('header');
die "incompatible XML file\n", if($xml_header[0]->getChildrenByTagName('exporter_name') ne 'mma/pnexporter');

my $input_time=$xml_header[0]->getChildrenByTagName('time');
print 'Input file exported from ABF on ',get_time_str_de($input_time),"\n";
print 'using mma/pnexporter version '.$xml_header[0]->getChildrenByTagName('exporter_version')."\n\n";

#parse all PM
my @xml_pm=$dom->getElementsByTagName('privmsg');
my @all_pm;

foreach(@xml_pm)
{
	my ($id, $root_id, $time, $icon, $sender, @recipients, $subject, $message);
	
	$id=($_->getChildrenByTagName('id'))[0]->textContent;
	$root_id=($_->getChildrenByTagName('root_id'))[0]->textContent;
	$time=($_->getChildrenByTagName('time'))[0]->textContent;
	$icon=($_->getChildrenByTagName('icon'))[0]->textContent;
	$sender=($_->getChildrenByTagName('sender'))[0]->textContent;
	$subject=($_->getChildrenByTagName('subject'))[0]->textContent;
	$message=($_->getChildrenByTagName('message'))[0]->textContent;
	
	foreach my $r ($_->getChildrenByTagName('recipient'))
	{
		my ($type, $status, $name);
		$type=$r->getAttribute('type');
		$status=$r->getAttribute('status');
		$name=$r->textContent;
		
		push @recipients, {'type'=>$type, 'status'=>$status, 'name'=>$name};
	}
	
	push @all_pm, {'id'=>$id, 'root_id'=>$root_id, 'time'=>$time, 'icon'=>$icon, 'sender'=>$sender, 'recipients'=>[@recipients], 'subject'=>$subject, 'message'=>$message};
}

#filter depending on $mode
my @pm;

if($mode eq 'all')
{
	print "no filtering applied\n\n";
	@pm=@all_pm;
}
elsif($mode=~/^\d+$/)
{
	my $id=$mode;
	print "extracting conversation started with PM-ID $id ...\n\n";
	die "error: invalid ID 0\n" if($id==0);
	
	foreach my $p (@all_pm)
	{
		push @pm, $p if($p->{'id'}==$id || $p->{'root_id'}==$id);
	}
	
	die "error: no PM found\n" if(!scalar(@pm));
}
else
{
	my $user=$mode;
	print "extracting all PM from/to user $user ...\n\n";
	
	foreach my $p (@all_pm)
	{
		push @pm, $p if($p->{'sender'} eq $user || grep ({$_->{'name'} eq $user} @{$p->{'recipients'}}));
	}
	
	die "error: no PM found from/to user $user\n" if(!scalar(@pm));
}

#sort PM
if($sort eq 'desc')
{
	@pm=sort sortfunc_time_desc @pm;
}
elsif($sort eq 'asc')
{
	@pm=sort sortfunc_time_asc @pm;
}
else
{
	die "unknown sort mode $sort\n";
}

#write output
open my $out, '>', $output_file;
binmode $out, ':utf8';

if($output_format eq 'txt')
{
	print "writing TXT file...\n\n";
	do_write_txt(\@pm, $mode, $sort, $start_time, $input_file, $input_time, $output_file);
	print "TXT file written\n";
}
elsif($output_format eq 'html')
{
	print "writing HTML file...\n\n";
	do_write_html(\@pm, $mode, $sort, $start_time, $input_file, $input_time, $output_file);
	print "HTML file written\n";
}

close $out;

print "\n";
print "all done, thank you\n";

############# SUB VARIOUS #############

sub print_help_and_exit
{
	print <<'USAGE';
USAGE: perl pm_cruncher.pl [arguments]

DEPENDENCIES:
  XML::LibXML, on Debian and derived try "sudo apt install libxml-libxml-perl".

ARGUMENTS:
  GENERAL:
    --version
    --help | --usage
  INPUT:
    --input-file $file
  PARSER MODE:
    --mode $mode
      string 'all' -> extract all PM
      numeric $id_start -> conversation starting with PM $id_start (!=0)
      string $name (!='all') -> all PM from/to user $name
  SORTING MODE:
    --sort $mode
      string 'asc' -> sort by time, newest message first
      string 'desc' -> sort by time, oldest message first (default)
  OUTPUT:
    --output-file $file (default: my_pm.html)
    --output txt|html -> output TXT or HTML (default)

OUTPUT:
  TXT is mostly for debugging, no fancy formatting.
  HTML converts BBCode and smilies are supported. You probably want this mode. 
USAGE
	
	exit(0);
}

sub sortfunc_time_desc
{
	return $a->{'time'}<=>$b->{'time'};
}

sub sortfunc_time_asc
{
	return $b->{'time'}<=>$a->{'time'};
}

sub get_time_str_de
{
	my $t=shift;
	
	return '' if(length($t)==0);
	
	my @raw=localtime($t);
	my @day=qw/Mo Di Mi Do Fr Sa So/;
	
	return sprintf("%s %02d.%02d.%02d %02d:%02d:%02d",$day[$raw[6]], $raw[3], $raw[4]+1, $raw[5]%100, $raw[2], $raw[1], $raw[0]);
}

############# SUB TXT #############

sub do_write_txt
{
	my ($pm_ref, $parser_mode, $sort_mode, $start_time, $inp_file, $inp_time, $outp_file)=@_;
	
	print $out 'PM-Cruncher for ABF version ',SCRIPT_VERSION," (c) 2025 mmA\n\n";
	print $out "input file:    $inp_file\n";
	print $out "output file:   $outp_file\n";
	print $out "output format: TXT\n";
	print $out "parser mode:   $parser_mode\n";
	print $out "sort mode:     $sort_mode\n";
	print $out 'started at:    ',get_time_str_de($start_time),"\n";
	print $out "\n";
	
	foreach my $pm (@{$pm_ref})
	{
		print $out 'SUBJECT: ',$pm->{'subject'},"\n";
		print $out 'SENT:    ',scalar(get_time_str_de($pm->{'time'})),"\n";
		print $out 'FROM:    ',$pm->{'sender'},"\n";
		my @to=grep {$_->{'type'} eq 'to';} @{$pm->{'recipients'}};
		print $out 'TO:      ',join(', ', map {$_->{'name'};} @to),"\n" if(scalar(@to));
		my @bcc=grep {$_->{'type'} eq 'bcc';} @{$pm->{'recipients'}};
		print $out 'BCC:     ',join(', ', map {$_->{'name'};} @bcc),"\n" if(scalar(@bcc));
		print $out "\n";
		print $out $pm->{'message'},"\n\n\n";
	}
}

############# SUB HTML #############

sub do_write_html
{
	my ($pm_ref, $parser_mode, $sort_mode, $start_time, $inp_file, $inp_time, $outp_file)=@_;
	
	print $out <<'HTML_HEADER';
<!-- AUTOGENERATED FILE - DO NOT EDIT -->

<!DOCTYPE html>
<html lang="de">
<head>
	<meta charset="utf-8">
	<title>PN-Export ABF</title>
	<style>
		body {
		margin-left: auto;
		margin-right: auto;
		width: 80%;
		height: 100%;
		overflow-wrap: anywhere;
	}

	.header {
		display: block;
		min-height: 10%;
		top: 0px;
		position: sticky;
		background-color: white;
		padding-bottom: 3px;
	}

	.header_title {
		text-align: center;
		font-size: 1.5rem;
	}

	.header_details {
		margin-bottom: 10px;
	}

	.messages {
		overflow: auto;
	}

	p.msg {
		width: 100%;
	}

	.msg_subject {
		font-size: 1.2rem;
		margin-bottom: 5px;
	}

	.msg_time, .msg_from, .msg_to, .msg_bcc {
		font-size: 0.8rem;
		margin-top: 0px;
		margin-bottom: 0px;
	}

	div.msg_text {
		margin-top: 5px;
		width: 95%;
		margin-left: auto;
		margin-right: auto;
	}

	blockquote {
		border: 1px solid grey;
		padding: 5px;
	}

	.quote {
		font-size: 0.8rem;
		margin-bottom: 2px;
	}

	.quote_time {
		float: right;
	}

	.code {
		font-family: monospace;
		font-size: 0.8rem;
		border: 1px solid grey;
		margin-left: auto;
		margin-right: auto;
		margin-top: 10px;
		margin-bottom: 10px;
		width: 95%;
		white-space: pre-wrap;
		padding: 5px;
	}

	.code_title {
		font-weight: bold;
	}

	.spoiler {
		border: 1px solid grey;
		margin-left: auto;
		margin-right: auto;
		margin-top: 10px;
		margin-bottom: 10px;
		width: 95%;
	}

	.spoiler_title {
		font-weight: bold;
	}
	</style>
</head>
<body>
	<div class="header">
		<p class="header_title">Exportierte PN aus dem ABF</h1>
HTML_HEADER
	
	print $out "\t\t",'<p class="header_details">erzeugt von PM-Cruncher version ',SCRIPT_VERSION,' von mmA<br>Datei ',$inp_file,' vom ',get_time_str_de($inp_time),'<br>Parser-Modus: ',$parser_mode,'<br>Sortierung: ',$sort_mode,'</p>',"\n";
	
	print $out <<'HTML2';
	</div>
	<div class="messages">
HTML2

	foreach my $pm (@{$pm_ref})
	{
		my $icon=$pm->{'icon'};
		$icon='' if($icon eq '[none]');
		my $subject=$pm->{'subject'};
		my $time_str=get_time_str_de($pm->{'time'});
		my $from=$pm->{'sender'};
		my @to=grep {$_->{'type'} eq 'to';} @{$pm->{'recipients'}};
		my $to_str=join(', ', map {$_->{'name'};} @to);
		my @bcc=grep {$_->{'type'} eq 'bcc';} @{$pm->{'recipients'}};
		my $bcc_str=join(', ', map {$_->{'name'};} @bcc);
		
		my $msg=$pm->{'message'};
		
		print $out "\t\t<p class=\"msg\">\n";
		print $out "\t\t\t<p class=\"msg_subject\">";
		print $out '<img src="./icons/',$icon,'"></img>' if(length($icon));
		print $out $subject,"</p>\n";
		print $out "\t\t\t<p class=\"msg_time\">Datum: ",encode_entities($time_str),"</p>\n";
		print $out "\t\t\t<p class=\"msg_from\">Von: ",encode_entities($from),"</p>\n";
		print $out "\t\t\t<p class=\"msg_to\">An: ",encode_entities($to_str),"</p>\n";
		print $out "\t\t\t<p class=\"msg_bcc\">BCC: ",encode_entities($bcc_str),"</p>\n" if(length($bcc_str));
		print $out "\t\t\t<div class=\"msg_text\">",convert_to_html($msg),"</div>\n";
		print $out "\t\t</p>\n";
	}

	print $out <<'HTML3';
	</div>
</body>
</html>
HTML3
}

sub convert_to_html
{
	my $msg=shift;
	
	#convert newlines to HTML
	$msg=~s/\n/<br>/gm;
	
	#convert BBCode to HTML
	1 while($msg=~s!\[(\w+)(?:\*|([^\]]+?))?\](.+?)\[/(\1)\]!conv_bb($1, $2, $3)!e);
	
	#fix superfluous <br> before or after <blockquote> or <div>
	$msg=~s/(?<=<\/(?:blockquote|div)>)<br>//gm;
	$msg=~s/<br>(?=<(?:blockquote|div)>)//gm;
	
	#convert smilies
	$msg=~s!(:.+?)(?=[^\w\-\?:\|])!check_conv_smilie($1)!egi; #This might need tweaking
	#some smilies do not start with ':' ...
	$msg=~s!8-\)!check_conv_smilie('8-)')!eg;
	$msg=~s!;\)!check_conv_smilie(';)')!eg;
	
	return $msg; 
}

sub conv_bb
{
	my %bb_data=(
		'b'=>{'simple'=>1, 'html_start'=>'<b>', 'html_end'=>'</b>'},
		'i'=>{'simple'=>1, 'html_start'=>'<i>', 'html_end'=>'</i>'},
		'u'=>{'simple'=>1, 'html_start'=>'<u>', 'html_end'=>'</u>'},
		's'=>{'simple'=>1, 'html_start'=>'<s>', 'html_end'=>'</s>'},
		'code'=>{'simple'=>1, 'html_start'=>'<div class="code"><div class="code_title">Code:</div>', 'html_end'=>'</div>'},
		'list'=>{'simple'=>0, 'callback'=>\&handle_list},
		'spoiler'=>{'simple'=>0, 'callback'=>\&handle_spoiler},
		'quote'=>{'simple'=>0, 'callback'=>\&handle_quote},
		'url'=>{'simple'=>0, 'callback'=>\&handle_url},
		'color'=>{'simple'=>0, 'callback'=>\&handle_color},
		'size'=>{'simple'=>0, 'callback'=>\&handle_size},
		'img'=>{'simple'=>0, 'callback'=>\&handle_img}
	);
	my ($bb, $bb_param, $content)=(shift, shift, shift);
	
	die "error: unknown BBCode $bb found\n" if(!defined($bb_data{$bb}));
	
	if($bb_data{$bb}->{'simple'})
	{
		return $bb_data{$bb}->{'html_start'}.$content.$bb_data{$bb}->{'html_end'};		
	}
	else
	{
		return &{$bb_data{$bb}->{'callback'}}($bb, $bb_param, $content);
	}
}

sub handle_list
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	return '<ul>'.join('', map {$_=($_=~s/^\[\*\]//)?'<li>'.encode_entities($_).'</li>':$_; } split(/<br>/, $content)).'</ul>';
}

sub handle_spoiler
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	my $title='spoiler';
	$title=$1 if($bb_param=~/^=\&quot;(.+?)\&quot;/);
	
	return '<div class="spoiler"><div class="spoiler_title">'.$title.':</div>'.$content.'</div>';
}

sub handle_quote
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	my($user, $time)=('?', '');
	
	if(length($bb_param))
	{
		$user=$1 if($bb_param=~/^=\&quot;(.+?)\&quot;/ || $bb_param=~/^=([^\s]+)/);
		$time=$1 if($bb_param=~/time=(\d+)/);
	}
	
	return '<blockquote><div class="quote">'.$user.' schrieb:<span class="quote_time">'.get_time_str_de($time).'</span></div>'.$content.'</blockquote>';
}

sub handle_url
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	return '<a href="'.substr($bb_param, 1).'">'.$content.'</a>';
}

sub handle_color
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	return '<span style="color: '.substr($bb_param, 1).';">'.$content.'</span>';
}

sub handle_size
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	return '<span style="font-size: '.substr($bb_param, 1).'%;">'.$content.'</span>';
}

sub handle_img
{
	my (undef, $bb_param, $content)=(shift, shift, shift);
	
	return '<img src="https://'.$content.'"></img>';
}

sub check_conv_smilie
{
	my %smilie_data=(
	'8-)'=>{'url'=>'./smilies/icon_cool.gif', 'title'=>'Cool'},
	':amor:'=>{'url'=>'./smilies/amor.gif', 'title'=>'Amor'},
	':arrow:'=>{'url'=>'./smilies/icon_arrow.gif', 'title'=>'Pfeil'},
	':boxing:'=>{'url'=>'./smilies/boxing.gif', 'title'=>'Boxen'},
	':brille1:'=>{'url'=>'./smilies/brille3.gif', 'title'=>'Brille 1'},
	':brille3:'=>{'url'=>'./smilies/brille7.gif', 'title'=>'Brille 3'},
	':buch:'=>{'url'=>'./smilies/buchlesen.gif', 'title'=>'Buch'},
	':buegeln:'=>{'url'=>'./smilies/buegeln.gif', 'title'=>'Bügeln'},
	':cheerleader:'=>{'url'=>'./smilies/cheerleader.gif', 'title'=>'Cheerleader'},
	':cooler:'=>{'url'=>'./smilies/cooler.gif', 'title'=>'Cool Man'},
	':coolgun:'=>{'url'=>'./smilies/cool3.gif', 'title'=>'Cool Gun'},
	':coolsmoke:'=>{'url'=>'./smilies/cool.gif', 'title'=>'Cool Smoke'},
	':crybaby:'=>{'url'=>'./smilies/crybaby.gif', 'title'=>'Cry Baby'},
	':cry:'=>{'url'=>'./smilies/icon_cry.gif', 'title'=>'Weinen'},
	':daumen:'=>{'url'=>'./smilies/daumen.gif', 'title'=>'Daumen'},
	':dont:'=>{'url'=>'./smilies/dont.gif', 'title'=>'Don\'t'},
	':-D'=>{'url'=>'./smilies/icon_biggrin.gif', 'title'=>'Sehr glücklich'},
	':D'=>{'url'=>'./smilies/icon_biggrin.gif', 'title'=>'Sehr glücklich'},
	':duschen:'=>{'url'=>'./smilies/dusche.gif', 'title'=>'Duschen'},
	':evil:'=>{'url'=>'./smilies/icon_evil.gif', 'title'=>'Böse'},
	':fessel:'=>{'url'=>'./smilies/fessel.gif', 'title'=>'Fessel'},
	':flirten:'=>{'url'=>'./smilies/flirten1.gif', 'title'=>'Flirten'},
	':fluchen2:'=>{'url'=>'./smilies/fluchen1.gif', 'title'=>'Fluchen 2'},
	':fluchen:'=>{'url'=>'./smilies/fluchen.gif', 'title'=>'Fluchen'},
	':foto:'=>{'url'=>'./smilies/foto.gif', 'title'=>'Foto'},
	':frech2:'=>{'url'=>'./smilies/frech3.gif', 'title'=>'Frech 2'},
	':frech:'=>{'url'=>'./smilies/frech.gif', 'title'=>'Frech'},
	':geschirr:'=>{'url'=>'./smilies/geschirr.gif', 'title'=>'Geschirr'},
	':gewicht:'=>{'url'=>'./smilies/gewicht.gif', 'title'=>'Gewicht'},
	':gewinner:'=>{'url'=>'./smilies/winner.gif', 'title'=>'Gewinner'},
	':good:'=>{'url'=>'./smilies/good.gif', 'title'=>'Good'},
	':gruebel:'=>{'url'=>'./smilies/gruebel.gif', 'title'=>'Grübel'},
	':gruppe:'=>{'url'=>'./smilies/gathering.gif', 'title'=>'Gruppe'},
	':hallo:'=>{'url'=>'./smilies/verstecken2.gif', 'title'=>'Hallo'},
	':hammer:'=>{'url'=>'./smilies/hammer.gif', 'title'=>'Hammer'},
	':handkuss:'=>{'url'=>'./smilies/liebe93.gif', 'title'=>'Handkuss'},
	':headbang:'=>{'url'=>'./smilies/headbang.gif', 'title'=>'Headbang'},
	':hierlang:'=>{'url'=>'./smilies/hierlang.gif', 'title'=>'Hier lang'},
	':hochzeit:'=>{'url'=>'./smilies/hochzeit.gif', 'title'=>'Hochzeit'},
	':holy:'=>{'url'=>'./smilies/holy.gif', 'title'=>'Heiliger'},
	':hut:'=>{'url'=>'./smilies/hut.gif', 'title'=>'Hut'},
	':idea:'=>{'url'=>'./smilies/icon_idea.gif', 'title'=>'Idee'},
	':idee:'=>{'url'=>'./smilies/lighten.gif', 'title'=>'Idee'},
	':kehren:'=>{'url'=>'./smilies/putzen.gif', 'title'=>'Kehren'},
	':klassiker2:'=>{'url'=>'./smilies/klassiker1.gif', 'title'=>'Klassiker 2'},
	':klassiker:'=>{'url'=>'./smilies/klassiker4.gif', 'title'=>'Klassiker'},
	':klopfen:'=>{'url'=>'./smilies/klopfen.gif', 'title'=>'Teppich klopfen'},
	':koch:'=>{'url'=>'./smilies/koch.gif', 'title'=>'Koch'},
	':kopfhoerer:'=>{'url'=>'./smilies/kopfhoerer.gif', 'title'=>'Kopfhörer'},
	':kopfstand:'=>{'url'=>'./smilies/kopfstand1.gif', 'title'=>'Kopfstand'},
	':krank2:'=>{'url'=>'./smilies/krank1.gif', 'title'=>'Krank 2'},
	':krank:'=>{'url'=>'./smilies/ill.gif', 'title'=>'Krank'},
	':kuss:'=>{'url'=>'./smilies/kuss2.gif', 'title'=>'Kuss'},
	':lach:'=>{'url'=>'./smilies/lach.gif', 'title'=>'Lachen'},
	':lol:'=>{'url'=>'./smilies/icon_lol.gif', 'title'=>'Lachen'},
	':mail:'=>{'url'=>'./smilies/mail.gif', 'title'=>'Mail'},
	':mosh:'=>{'url'=>'./smilies/mosher.gif', 'title'=>'Mosh'},
	':mrgreen:'=>{'url'=>'./smilies/icon_mrgreen.gif', 'title'=>'Mr. Green'},
	':no:'=>{'url'=>'./smilies/no.gif', 'title'=>'Nein'},
	':nudelholz:'=>{'url'=>'./smilies/wife.gif', 'title'=>'Nudelholz'},
	':ohnmacht:'=>{'url'=>'./smilies/ohnmacht.gif', 'title'=>'Ohnmacht'},
	':oho:'=>{'url'=>'./smilies/oho.gif', 'title'=>'Oho'},
	':oma:'=>{'url'=>'./smilies/oma.gif', 'title'=>'Oma'},
	':omg:'=>{'url'=>'./smilies/omg1.gif', 'title'=>'OMG'},
	':oops:'=>{'url'=>'./smilies/icon_redface.gif', 'title'=>'Verlegen'},
	':opa:'=>{'url'=>'./smilies/wiseopa.gif', 'title'=>'Kluger Opa'},
	':-o'=>{'url'=>'./smilies/icon_surprised.gif', 'title'=>'Überrascht'},
	':o'=>{'url'=>'./smilies/icon_surprised.gif', 'title'=>'Überrascht'},
	':peitsche:'=>{'url'=>'./smilies/peitsche.gif', 'title'=>'Peitsche'},
	':pfarrer:'=>{'url'=>'./smilies/pfarrer.gif', 'title'=>'Pfarrer'},
	':pfeif:'=>{'url'=>'./smilies/pfeif.gif', 'title'=>'Pfeif'},
	':poempel:'=>{'url'=>'./smilies/poempel.gif', 'title'=>'Pömpel'},
	':prost:'=>{'url'=>'./smilies/prost.gif', 'title'=>'Prost'},
	':putzen2:'=>{'url'=>'./smilies/putzenm.gif', 'title'=>'Putzen 2'},
	':regen:'=>{'url'=>'./smilies/regen.gif', 'title'=>'Regen'},
	':reporter:'=>{'url'=>'./smilies/reporter.gif', 'title'=>'Reporter'},
	':roll:'=>{'url'=>'./smilies/icon_rolleyes.gif', 'title'=>'Mit den Augen rollen'},
	':sadman:'=>{'url'=>'./smilies/traurigmann.gif', 'title'=>'Traurig Mann'},
	':sadwoman:'=>{'url'=>'./smilies/traurig.gif', 'title'=>'Traurig Frau'},
	':schlafen:'=>{'url'=>'./smilies/sleeping.gif', 'title'=>'Schlafen'},
	':schreck:'=>{'url'=>'./smilies/schreck.gif', 'title'=>'Schreck'},
	':schrei:'=>{'url'=>'./smilies/schrei1.gif', 'title'=>'Schrei'},
	':schuechtern:'=>{'url'=>'./smilies/schuechtern2.gif', 'title'=>'Schüchtern'},
	':schwarzekatze:'=>{'url'=>'./smilies/schwarzekatze.gif', 'title'=>'Schwarze Katze'},
	':schwitzen:'=>{'url'=>'./smilies/schwitzen.gif', 'title'=>'Schwitzen'},
	':shock:'=>{'url'=>'./smilies/icon_eek.gif', 'title'=>'Geschockt'},
	':shylove:'=>{'url'=>'./smilies/shylove.gif', 'title'=>'Shy Love'},
	':shy:'=>{'url'=>'./smilies/schuechtern3.gif', 'title'=>'Shy'},
	':singen:'=>{'url'=>'./smilies/singen.gif', 'title'=>'Singen'},
	':specht:'=>{'url'=>'./smilies/specht1.gif', 'title'=>'Specht'},
	':spiegel:'=>{'url'=>'./smilies/spiegel.gif', 'title'=>'Spiegel'},
	':stricken:'=>{'url'=>'./smilies/stricken.gif', 'title'=>'Stricken'},
	':surprise:'=>{'url'=>'./smilies/surprise.gif', 'title'=>'Surprise'},
	':tanzen2:'=>{'url'=>'./smilies/tanz2.gif', 'title'=>'Tanzen 2'},
	':tanzen:'=>{'url'=>'./smilies/tanzen.gif', 'title'=>'Tanzen'},
	':tea:'=>{'url'=>'./smilies/tea.gif', 'title'=>'Tee'},
	':telefon:'=>{'url'=>'./smilies/telefon.gif', 'title'=>'Telefon'},
	':tippen:'=>{'url'=>'./smilies/tippen.gif', 'title'=>'Tippen'},
	':traurig2:'=>{'url'=>'./smilies/crying_3.gif', 'title'=>'Traurig Schneuz'},
	':traurig:'=>{'url'=>'./smilies/sad.gif', 'title'=>'Traurig'},
	':twisted:'=>{'url'=>'./smilies/icon_twisted.gif', 'title'=>'Sehr böse'},
	':umarmung2:'=>{'url'=>'./smilies/umarmung4.gif', 'title'=>'Umarmung 2'},
	':umarmungfrech:'=>{'url'=>'./smilies/umarmung3.gif', 'title'=>'Umarmung frech'},
	':umarmung:'=>{'url'=>'./smilies/umarmung1.gif', 'title'=>'Umarmung'},
	':upps:'=>{'url'=>'./smilies/zensur.gif', 'title'=>'Upps'},
	':-?'=>{'url'=>'./smilies/icon_confused.gif', 'title'=>'Geschockt'},
	':?'=>{'url'=>'./smilies/icon_confused.gif', 'title'=>'Geschockt'},
	':!:'=>{'url'=>'./smilies/icon_exclaim.gif', 'title'=>'Ausrufezeichen'},
	':-|'=>{'url'=>'./smilies/icon_neutral.gif', 'title'=>'Neutral'},
	':?:'=>{'url'=>'./smilies/icon_question.gif', 'title'=>'Frage'},
	':('=>{'url'=>'./smilies/icon_sad.gif', 'title'=>'Traurig'},
	':)'=>{'url'=>'./smilies/icon_smile.gif', 'title'=>'Smilie'},
	';)'=>{'url'=>'./smilies/icon_wink.gif', 'title'=>'Winken'},
	':verwirrt:'=>{'url'=>'./smilies/verwirrt.gif', 'title'=>'Verwirrt'},
	':vielglueck:'=>{'url'=>'./smilies/vielglueck.gif', 'title'=>'Viel Glück'},
	':wahrsagerin:'=>{'url'=>'./smilies/wahrsager.gif', 'title'=>'Wahrsagerin'},
	':whiteflag:'=>{'url'=>'./smilies/giveup.gif', 'title'=>'White flag'},
	':winken:'=>{'url'=>'./smilies/hand.gif', 'title'=>'Winken'},
	':wink:'=>{'url'=>'./smilies/icon_wink.gif', 'title'=>'Winken'},
	':wuetend:'=>{'url'=>'./smilies/wuetend2.gif', 'title'=>'Wütend'},
	':yes:'=>{'url'=>'./smilies/yes.gif', 'title'=>'Ja'},
	':zaehneputzen:'=>{'url'=>'./smilies/zaehneputzen.gif', 'title'=>'Zähneputzen'},
	':zugabe:'=>{'url'=>'./smilies/zugabe.gif', 'title'=>'Zugabe'}
	);
	
	my $candidate=shift;
	
	if(defined($smilie_data{$candidate}))
	{
		return '<img src="'.$smilie_data{$candidate}->{'url'}.'" title="'.$smilie_data{$candidate}->{'title'}.'">';
	}
	else
	{
		return $candidate;
	}
}
Nehmt Abschied, Brüder, ungewiss
ist alle Wiederkehr
Die Zukunft liegt in Finsternis


Das ist doch kein Leben. :cry:
Mit müden Augen
Begeisterter Schreiberling
Beiträge: 1654
Registriert: Dienstag 15. August 2023, 12:00
Geschlecht: männlich
AB Status: Hardcore AB
Ich bin ...: nur an Frauen interessiert.

Re: Neue Erweiterung: PN-Exporter von mmA

Beitrag von Mit müden Augen »

Bug: einige Smilies werden nicht erkannt / konvertiert. Da es aber wohl niemanden interessiert lasse ich das erstmal so.
Nehmt Abschied, Brüder, ungewiss
ist alle Wiederkehr
Die Zukunft liegt in Finsternis


Das ist doch kein Leben. :cry:
Antworten