foundryvtt-reve-de-dragon/module/rdd-commands.js

506 lines
22 KiB
JavaScript
Raw Normal View History

2020-12-29 00:11:58 +01:00
/* -------------------------------------------- */
2022-11-04 20:41:16 +01:00
import { DialogChronologie } from "./dialog-chronologie.js";
2021-05-11 21:21:33 +02:00
import { DialogCreateSigneDraconique } from "./dialog-create-signedraconique.js";
import { DialogStress } from "./dialog-stress.js";
import { RdDItemCompetence } from "./item-competence.js";
2020-12-29 00:11:58 +01:00
import { Misc } from "./misc.js";
import { RdDCarac } from "./rdd-carac.js";
2020-12-31 11:53:41 +01:00
import { RdDDice } from "./rdd-dice.js";
2022-06-25 22:18:46 +02:00
import { RdDMeteo } from "./rdd-meteo.js";
2021-02-10 15:25:14 +01:00
import { RdDNameGen } from "./rdd-namegen.js";
2020-12-29 00:11:58 +01:00
import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js";
2020-12-29 00:11:58 +01:00
import { RdDRollTables } from "./rdd-rolltables.js";
import { RdDUtility } from "./rdd-utility.js";
import { CompendiumTableHelpers } from "./settings/system-compendiums.js";
import { TMRUtility } from "./tmr-utility.js";
2020-12-29 00:11:58 +01:00
const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/;
2020-12-29 00:11:58 +01:00
/* -------------------------------------------- */
export class RdDCommands {
static init() {
const rddCommands = new RdDCommands();
Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') {
let regExp = /(\S+)/g;
let commands = content.match(regExp);
if (rddCommands.processChatCommand(commands, content, msg)) {
return false;
}
}
return true;
});
game.system.rdd.commands = rddCommands;
}
constructor() {
this.commandsTable = undefined;
}
_registerCommands() {
this.commandsTable = {}
this.registerCommand({ path: ["/aide"], func: (content, msg, params) => this.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
this.registerCommand({ path: ["/help"], func: (content, msg, params) => this.help(msg), descr: "Affiche l'aide pour toutes les commandes" });
this.registerCommand({ path: ["/liste", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence('liste'), descr: "Affiche la liste des compétences" });
this.registerCommand({ path: ["/table", "queue"], func: (content, msg, params) => RdDRollTables.getQueue('liste'), descr: "Affiche la table des Queues de Dragon" });
this.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre('liste'), descr: "Affiche la table des Ombres de Thanatos" });
this.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR('liste'), descr: "Affiche la table des Têtes de Dragon pour Hauts Revants" });
this.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete('liste'), descr: "Affiche la table des Tête de Dragon pour tous" });
this.registerCommand({ path: ["/table", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle('liste'), descr: "Affiche la table des Souffles de Dragon" });
this.registerCommand({ path: ["/table", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot('liste'), descr: "Affiche la table les cartes du Tarot Draconique" });
this.registerCommand({ path: ["/table", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe('liste'), descr: "Affiche la table des Idées fixes" });
this.registerCommand({ path: ["/table", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant('liste'), descr: "Affiche la table des Désirs Lancinants" });
this.registerCommand({
path: ["/table", "rencontre"], func: (content, msg, params) => this.tableRencontres(msg, params),
descr: `Affiche la table des Rencontres
<br><strong>/table rencontre deso</strong> affiche la table des rencontres en Désolation
<br><strong>/table rencontre mauvaise</strong> affiche la table des mauvaises rencontres`
});
this.registerCommand({ path: ["/table", "milieu"], func: (content, msg, params) => this.tableMilieu(msg, params, 'liste'), descr: "Affiche la table des ressource naturelles pour un milieu donné" });
this.registerCommand({ path: ["/tirer", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence('chat'), descr: "Tire une compétence au hasard" });
this.registerCommand({ path: ["/tirer", "queue"], func: (content, msg, params) => RdDRollTables.getQueue('chat'), descr: "Tire une Queue de Dragon" });
this.registerCommand({ path: ["/tirer", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre('chat'), descr: "Tire une Ombre de Thanatos" });
this.registerCommand({ path: ["/tirer", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR('chat'), descr: "Tire une Tête de Dragon pour Hauts Revants" });
this.registerCommand({ path: ["/tirer", "tete"], func: (content, msg, params) => RdDRollTables.getTete('chat'), descr: "Tire une Tête de Dragon" });
this.registerCommand({ path: ["/tirer", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle('chat'), descr: "Tire un Souffle de Dragon" });
this.registerCommand({ path: ["/tirer", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot('chat'), descr: "Tire une carte du Tarot Draconique" });
this.registerCommand({ path: ["/tirer", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe('chat'), descr: "Tire une Idée fixe" });
this.registerCommand({ path: ["/tirer", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant('chat'), descr: "Tire un Désir Lancinant" });
this.registerCommand({ path: ["/tirer", "rencontre"], func: (content, msg, params) => this.getRencontreTMR(params), descr: `Détermine une rencontre dans les TMR (synonyme de "/tmrr")` });
2022-11-30 02:35:12 +01:00
this.registerCommand({ path: ["/tirer", "milieu"], func: (content, msg, params) => this.tableMilieu(msg, params, 'chat'), descr: "Effectue un tirage dans la table desressource naturelles pour un milieu donné" });
this.registerCommand({ path: ["/meteo"], func: (content, msg, params) => this.getMeteo(msg, params), descr: "Propose une météo marine" });
this.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" });
this.registerCommand({
path: ["/tmr"], func: (content, msg, params) => this.findTMR(msg, params),
descr: `Cherche où se trouve une case des Terres médianes
<br><strong>/tmr sord</strong> indique que la cité Sordide est en D13
<br><strong>/tmr foret</strong> donne la liste des TMR dont le nom contient "foret" (donc, toutes les forêts)`
});
this.registerCommand({
path: ["/tmra"], func: (content, msg, params) => this.getTMRAleatoire(msg, params),
descr: `Tire une case aléatoire des Terres médianes
<br><strong>/tmra forêt</strong> détermine une 'forêt' aléatoire
<br><strong>/tmra</strong> détermine une case aléatoire dans toutes les TMR`
});
this.registerCommand({
path: ["/tmrr"], func: (content, msg, params) => this.getRencontreTMR(params),
descr: `Détermine une rencontre dans les TMR
<br><strong>/tmrr forêt</strong> détermine une rencontre aléatoire en 'forêt'
<br><strong>/tmrr mauvaise</strong> détermine une mauvaise rencontre aléatoire
<br><strong>/tmrr for 47</strong> détermine la rencontre en 'forêt' pour un jet de dé de 47`
});
this.registerCommand({
path: ["/xp", "comp"], func: (content, msg, params) => this.getCoutXpComp(msg, params),
descr: `Détermine le coût d'expérience pour augmenter une compétence. Exemples:
<br>/xp comp -6 1: pour passer de -6 à +1
<br>/xp comp +4: pour atteindre le niveau 4 (depuis +3)`
});
this.registerCommand({
path: ["/xp", "carac"], func: (content, msg, params) => this.getCoutXpCarac(msg, params),
descr: `Détermine le coût d'expérience pour augmenter une caractéristique. Exemples:
<br>/xp carac 15: coût pour atteindre 15 (depuis 14)`
});
2020-12-31 11:53:41 +01:00
this.registerCommand({
path: ["/rdd"], func: (content, msg, params) => this.rollRdd(msg, params),
descr: `Effectue un jet de dés dans la table de résolution. Exemples:
<br><strong>/rdd</strong> ouvre la table de résolution
<br><strong>/rdd 10 3</strong> effectue un jet 10 à +3
<br><strong>/rdd 15 -2</strong> effectue un jet 15 à -2
<br><strong>/rdd 15 0 s</strong> effectue un jet 15 à 0, avec significative requise
<br><strong>/rdd Vue Vigilance -2</strong> effectue un jet de Vue/Vigilance à -2 pour les tokens sélectionnés
<br><strong>/rdd vol déser +2</strong> effectue un jet de Volonté/Survie en désert à +2 pour les tokens sélectionnés
`
});
this.registerCommand({ path: ["/ddr"], func: (content, msg, params) => this.rollDeDraconique(msg), descr: "Lance un Dé Draconique" });
2020-12-29 00:11:58 +01:00
this.registerCommand({
path: ["/payer"], func: (content, msg, params) => RdDUtility.afficherDemandePayer(params[0], params[1]),
descr: `Demande aux joueurs de payer un montant. Exemples:
<br><strong>/payer 5s 10d</strong> permet d'envoyer un message pour payer 5 sols et 10 deniers
<br><strong>/payer 10d</strong> permet d'envoyer un message pour payer 10 deniers`
});
this.registerCommand({
path: ["/astro"], func: (content, msg, params) => RdDUtility.afficherHeuresChanceMalchance(Misc.join(params, ' ')),
descr: `Affiche les heures de chance et de malchance selon l'heure de naissance donnée en argument. Exemples pour l'heure de la Lyre:
<br><strong>/astro 7</strong>
<br><strong>/astro Lyre</strong>
<br><strong>/astro Lyr</strong>`
});
2021-05-11 00:52:25 +02:00
this.registerCommand({
path: ["/signe", "+"], func: (content, msg, params) => this.creerSignesDraconiques(),
descr: "Crée un signe draconique et l'ajoute aux haut-rêvants choisis."
});
2021-05-11 00:52:25 +02:00
this.registerCommand({
path: ["/signe", "-"], func: (content, msg, params) => this.supprimerSignesDraconiquesEphemeres(),
descr: "Supprime les signes draconiques éphémères"
});
2021-05-11 00:52:25 +02:00
this.registerCommand({
path: ["/stress"], func: (content, msg, params) => this.distribuerStress(params),
descr: `Distribue du stress aux personnages. Exemples:
<br><strong>/stress</strong> : Ouvre une fenêtre pour donner du stress ou de l'expérience à un ensemble de personnages
2021-05-19 22:44:14 +02:00
<br><strong>/stress 6</strong> : Distribue 6 points des Stress à tout les personnages joueurs, sans raison renseignée
<br><strong>/stress 6 Tigre</strong> : Distribue 6 points des Stress à tout les personnages joueurs, à cause d'un Tigre (Vert)
<br><strong>/stress 6 Glou Paulo</strong> : Distribue 6 points de Stress au personnage Paulon ou au personnage joueur Paulo, à cause d'un Glou`
});
2021-05-19 22:44:14 +02:00
this.registerCommand({
path: ["/chrono"], func: (content, msg, params) => DialogChronologie.create(),
descr: `Enregistre une entrée de chronologie dans un article de journal`
});
2020-12-29 00:11:58 +01:00
}
/* -------------------------------------------- */
2020-12-29 00:11:58 +01:00
registerCommand(command) {
this._addCommand(this.commandsTable, command.path, '', command);
2020-12-29 00:11:58 +01:00
}
/* -------------------------------------------- */
2020-12-31 11:53:41 +01:00
_addCommand(targetTable, path, fullPath, command) {
2020-12-29 00:11:58 +01:00
if (!this._validateCommand(targetTable, path, command)) {
return;
}
const term = path[0];
2021-01-09 19:33:19 +01:00
fullPath = fullPath + term + ' '
2020-12-29 00:11:58 +01:00
if (path.length == 1) {
2020-12-31 11:53:41 +01:00
command.descr = `<strong>${fullPath}</strong>: ${command.descr}`;
2020-12-29 00:11:58 +01:00
targetTable[term] = command;
}
else {
if (!targetTable[term]) {
targetTable[term] = { subTable: {} };
}
2020-12-31 11:53:41 +01:00
this._addCommand(targetTable[term].subTable, path.slice(1), fullPath, command)
2020-12-29 00:11:58 +01:00
}
}
/* -------------------------------------------- */
2020-12-29 00:11:58 +01:00
_validateCommand(targetTable, path, command) {
if (path.length > 0 && path[0] && command.descr && (path.length != 1 || targetTable[path[0]] == undefined)) {
return true;
}
console.warn("RdDCommands._validateCommand failed ", targetTable, path, command);
return false;
}
/* -------------------------------------------- */
/* Manage chat commands */
processChatCommand(commandLine, content = '', msg = {}) {
2020-12-29 00:11:58 +01:00
// Setup new message's visibility
let rollMode = game.settings.get("core", "rollMode");
if (["gmroll", "blindroll"].includes(rollMode)) {
msg["whisper"] = ChatMessage.getWhisperRecipients("GM");
}
if (rollMode === "blindroll"){
msg["blind"] = true;
}
2020-12-29 00:11:58 +01:00
msg["type"] = 0;
if (!this.commandsTable) {
this._registerCommands();
}
2021-11-27 00:04:34 +01:00
let command = commandLine[0].toLowerCase();
if (this._isCommandHandled(command)) {
let params = commandLine.slice(1);
this._processCommand(this.commandsTable, command, params, content, msg);
return true;
}
return false;
2020-12-29 00:11:58 +01:00
}
_isCommandHandled(command){
return this.commandsTable[command] != undefined;
2020-12-29 00:11:58 +01:00
}
async _processCommand(commandsTable, name, params, content = '', msg = {}, path = "") {
2020-12-29 00:11:58 +01:00
let command = commandsTable[name];
path = path + name + " ";
if (command && command.subTable) {
if (params[0]) {
this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path)
2020-12-29 00:11:58 +01:00
}
else {
this.help(msg, command.subTable);
}
return true;
2020-12-29 00:11:58 +01:00
}
if (command && command.func) {
new Promise(async () => {
const result = await command.func(content, msg, params);
if (result == false) {
RdDCommands._chatAnswer(msg, command.descr);
}
});
2020-12-29 00:11:58 +01:00
return true;
}
return false;
}
/* -------------------------------------------- */
2021-05-01 12:54:45 +02:00
async help(msg) {
this.help(msg, undefined);
}
async help(msg, table) {
let commands = []
this._buildSubTableHelp(commands, table ?? this.commandsTable);
2021-05-01 12:54:45 +02:00
let html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/settings/dialog-aide-commands.html", { commands: commands });
2021-05-01 12:54:45 +02:00
let d = new Dialog(
{
title: "Commandes disponibles dans le tchat",
content: html,
buttons: {},
},
{
width: 600, height: 600,
2021-05-01 12:54:45 +02:00
});
d.render(true);
}
/* -------------------------------------------- */
static _chatAnswer(msg, content) {
2021-03-25 03:18:27 +01:00
msg.whisper = [game.user.id];
msg.content = content;
2020-12-29 00:11:58 +01:00
ChatMessage.create(msg);
}
/* -------------------------------------------- */
2020-12-31 11:53:41 +01:00
_buildSubTableHelp(list, table) {
2020-12-29 00:11:58 +01:00
for (let [name, command] of Object.entries(table)) {
if (command) {
if (command.subTable) {
2020-12-31 11:53:41 +01:00
this._buildSubTableHelp(list, command.subTable);
2020-12-29 00:11:58 +01:00
} else {
2020-12-31 11:53:41 +01:00
list.push(command.descr);
2020-12-29 00:11:58 +01:00
}
}
}
2020-12-31 11:53:41 +01:00
return list.sort();
2020-12-29 00:11:58 +01:00
}
/* -------------------------------------------- */
2021-01-23 00:29:03 +01:00
async getRencontreTMR(params) {
if (params.length == 1 || params.length == 2) {
return game.system.rdd.rencontresTMR.rollRencontre(params[0], params[1])
2020-12-29 00:11:58 +01:00
}
return false;
2020-12-29 00:11:58 +01:00
}
/* -------------------------------------------- */
2020-12-29 00:11:58 +01:00
async rollRdd(msg, params) {
if (params.length == 0) {
RdDRollResolutionTable.open();
2020-12-29 00:11:58 +01:00
}
else {
2021-11-26 23:29:06 +01:00
let flatParams = Misc.join(params, ' ');
2020-12-29 00:11:58 +01:00
const numericParams = flatParams.match(rddRollNumeric);
if (numericParams) {
const carac = Misc.toInt(numericParams[1]);
const diff = Misc.toInt(numericParams[2] || 0);
const significative = numericParams[3] == 's'
await this.rollRdDNumeric(msg, carac, diff, significative);
return;
}
let actors = canvas.tokens.controlled.map(it => it.actor).filter(it => it);
2021-05-01 12:54:45 +02:00
if (actors && actors.length > 0) {
let length = params.length;
2021-05-01 12:54:45 +02:00
let diff = Number(params[length - 1]);
if (Number.isInteger(Number(diff))) {
length--;
}
else {
diff = 0;
}
const caracName = params[0];
let competence = length > 1 ? actors[0].getCompetence(Misc.join(params.slice(1, length), ' ')) : {name:undefined};
if (competence) {
for (let actor of actors) {
await actor.rollCaracCompetence(caracName, competence.name, diff);
}
}
return;
}
2021-05-01 12:54:45 +02:00
else {
ui.notifications.warn("Sélectionnez au moins un personnage pour lancer les dés")
}
2020-12-29 00:11:58 +01:00
}
}
/* -------------------------------------------- */
2020-12-29 00:11:58 +01:00
async rollRdDNumeric(msg, carac, diff, significative = false) {
let rollData = {
caracValue: carac,
finalLevel: diff,
2021-01-22 23:18:19 +01:00
diviseurSignificative: significative ? 2 : 1,
show: { title: "Table de résolution" }
2020-12-29 00:11:58 +01:00
};
await RdDResolutionTable.rollData(rollData);
return RdDCommands._chatAnswer(msg, await RdDResolutionTable.buildRollDataHtml(rollData));
2020-12-29 00:11:58 +01:00
}
2020-12-31 11:53:41 +01:00
/* -------------------------------------------- */
2020-12-31 11:53:41 +01:00
async rollDeDraconique(msg) {
let ddr = await RdDDice.rollTotal("1dr + 7");
return RdDCommands._chatAnswer(msg, `Lancer d'un Dé draconique: ${ddr}`);
2020-12-31 11:53:41 +01:00
}
2021-05-11 21:45:43 +02:00
async getTMRAleatoire(msg, params) {
if (params.length < 2) {
let type = params[0];
2021-05-11 21:45:43 +02:00
const tmr = await TMRUtility.getTMRAleatoire(type ? (it => it.type == type) : (it => true));
return RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`);
}
else {
return false;
}
}
2021-11-26 23:29:06 +01:00
async findTMR(msg, params) {
if (params && params.length > 0) {
const search = Misc.join(params, ' ');
const found = TMRUtility.findTMR(search);
if (found?.length > 0) {
return RdDCommands._chatAnswer(msg, `Les TMRs correspondant à '${search}' sont:` + Misc.join(found.map(it => `<br>${it.coord}: ${it.label}`)));
}
return RdDCommands._chatAnswer(msg, 'Aucune TMR correspondant à ' + search);
2021-11-26 23:29:06 +01:00
}
return false;
2021-11-26 23:29:06 +01:00
}
async tableRencontres(msg, params) {
if (params && params.length > 0) {
const search = Misc.join(params, ' ');
const solvedTerrain = TMRUtility.findTMRLike(search);
if (solvedTerrain == undefined) {
return RdDCommands._chatAnswer(msg, 'Aucune TMR correspondant à ' + search);
}
return await game.system.rdd.rencontresTMR.chatTable(solvedTerrain);
}
return false;
}
async tableMilieu(msg, params, toChat) {
if (params && params.length > 0) {
const search = Misc.join(params, ' ');
const milieux = await game.system.rdd.environnement.findEnvironnementsLike(search);
if (milieux.length == 0) {
const tous = Object.values(await game.system.rdd.environnement.milieux());
return RdDCommands._chatAnswer(msg, `<strong>Aucun milieu correspondant à '${search}'.</strong>
<br>Milieux disponibles:
<br><ul class="chat-list"><li>${tous.reduce(Misc.joining('</li><li>'))}</li></ul>`);
}
if (milieux.length > 1) {
ui.notifications.warn(`<strong>Plusieurs milieux correspondent à '${search}'</strong>:
<br><ul class="chat-list"><li>${milieux.reduce(Misc.joining('</li><li>'))}</li></ul>`);
}
const tableName = `ressources en ${milieux.reduce(Misc.joining(', '))}`;
if (toChat == 'liste') {
return await game.system.rdd.environnement.searchToChatMessage(milieux, tableName);
}
else {
const row = await game.system.rdd.environnement.getRandom(milieux, tableName);
await CompendiumTableHelpers.tableRowToChatMessage(row, 'Item');
return true;
}
}
return false;
}
/* -------------------------------------------- */
getCoutXpComp(msg, params) {
if (params && (params.length == 1 || params.length == 2)) {
let to = params.length == 1 ? Number(params[0]) : Number(params[1]);
let from = params.length == 1 ? to - 1 : Number(params[0]);
return RdDCommands._chatAnswer(msg, `Coût pour passer une compétence de ${from} à ${to}: ${RdDItemCompetence.getDeltaXp(from, to)}`);
}
else {
return false;
}
}
/* -------------------------------------------- */
getCoutXpCarac(msg, params) {
if (params && params.length == 1) {
let to = Number(params[0]);
return RdDCommands._chatAnswer(msg, `Coût pour passer une caractéristique de ${to - 1} à ${to}: ${RdDCarac.getCaracXp(to)}`);
}
else {
return false;
}
}
2021-05-11 00:52:25 +02:00
async creerSignesDraconiques() {
2021-05-11 21:21:33 +02:00
DialogCreateSigneDraconique.createSigneForActors();
2021-05-11 00:52:25 +02:00
return true;
}
async supprimerSignesDraconiquesEphemeres() {
game.actors.forEach(actor => {
const ephemeres = actor.items.filter(item => item.type = 'signedraconique' && item.system.ephemere);
2021-05-11 00:52:25 +02:00
if (ephemeres.length > 0) {
actor.deleteEmbeddedDocuments("Item", ephemeres.map(item => item.id));
2021-05-11 00:52:25 +02:00
}
});
return true;
}
async distribuerStress(params) {
if (!game.user.isGM) {
ui.notifications.warn("Seul le MJ est autorisé à utiliser la commande /stress");
return false;
}
2021-11-26 23:29:06 +01:00
if (params.length < 3) {
DialogStress.distribuerStress();
}
else {
let stress = params[0]
if (stress == undefined) {
ui.notifications.warn("Pas de valeur de stress à distribuer!");
return;
}
2021-11-26 23:29:06 +01:00
let motif = params.slice(1, params.length - 2);
let name = params[params.length - 1];
if (name == undefined) {
for (let actor of game.actors) {
actor.distribuerStress('stress', stress, motif);
}
} else {
//console.log(stressValue, nomJoueur);
let actor = Misc.findActor(name, game.actors.filter(it => it.hasPlayerOwner)) ?? Misc.findPlayer(name)?.character
if (actor) {
actor.distribuerStress('stress', stress, motif);
}
else {
ui.notifications.warn(`Pas de personnage ou de joueur correspondant à ${name}!`);
}
}
}
return true;
}
2022-06-25 22:18:46 +02:00
async getMeteo(msg, params) {
return await RdDMeteo.getMeteo();
}
2022-11-04 20:41:16 +01:00
2020-12-29 00:11:58 +01:00
}