Export as Trainertext

Add an export as trainertext button to pokepastes

// ==UserScript==
// @name         Export as Trainertext
// @namespace    https://pokepast.es/
// @version      2025-08-28
// @license      MIT
// @description  Add an export as trainertext button to pokepastes
// @author       wiresegal
// @match        https://pokepast.es/*
// @grant        GM_addElement
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// ==/UserScript==



(function() {
    'use strict';
    // THIS IS https://unpkg.com/[email protected]/dist/index.mjs, without the export message!
    // CSP issues meant this didn't work on chrome with a dynamic import, so here we are!

    // src/Pokemon.ts
    var POKEMON_STAT_NAMES = ["HP", "Atk", "Def", "SpA", "SpD", "Spe"];
    var Pokemon = class {
        constructor() {
            this.moves = [];
        }
        static fromObject(obj) {
            const p = new Pokemon();
            p.name = obj.name;
            p.nickname = obj.nickname;
            p.gender = obj.gender;
            p.item = obj.item;
            p.ability = obj.ability;
            p.level = obj.level;
            p.shiny = obj.shiny;
            p.happiness = obj.happiness;
            p.nature = obj.nature;
            p.evs = obj.evs;
            p.ivs = obj.ivs;
            p.teraType = obj.teraType;
            p.dynamaxLevel = obj.dynamaxLevel;
            p.gigantamax = obj.gigantamax;
            p.pokeball = obj.pokeball;
            p.moves = Array.isArray(obj.moves) ? obj.moves : [];
            return p;
        }
        toJson(indentation = 2) {
            return JSON.stringify(this, null, indentation);
        }
        toShowdown() {
            let str = "";
            if (this.nickname) {
                str += `${this.nickname} (${this.name})`;
            } else {
                str += `${this.name}`;
            }
            if (this.gender && this.gender.match(/^[MF]$/i)) {
                str += ` (${this.gender.toUpperCase()})`;
            }
            if (this.item) {
                str += ` @ ${this.item}`;
            }
            str += "\n";
            if (this.ability) {
                str += `Ability: ${this.ability}
`;
            }
            if (!Number.isNaN(this.level)) {
                str += `Level: ${this.level}
`;
            }
            if (this.shiny === true) {
                str += `Shiny: Yes
`;
            }
            if (!Number.isNaN(this.happiness)) {
                str += `Happiness: ${this.happiness}
`;
            }
            if (this.pokeball) {
                str += `Pokeball: ${this.pokeball}
`;
            }
            if (!Number.isNaN(this.dynamaxLevel)) {
                str += `Dynamax Level: ${this.dynamaxLevel}
`;
            }
            if (this.gigantamax === true) {
                str += `Gigantamax: Yes
`;
            }
            if (this.teraType) {
                str += `Tera Type: ${this.teraType}
`;
            }
            if (this.evs) {
                const evs = this.evs;
                str += `EVs: ` + POKEMON_STAT_NAMES.filter(function(prop) {
                    return !isNaN(evs[prop.toLowerCase()]);
                }).map(function(prop) {
                    const val = evs[prop.toLowerCase()];
                    return `${val} ${prop}`;
                }).join(" / ") + "\n";
            }
            if (this.nature) {
                str += `${this.nature} Nature
`;
            }
            if (this.ivs) {
                const ivs = this.ivs;
                str += `IVs: ` + POKEMON_STAT_NAMES.filter(function(prop) {
                    return !isNaN(ivs[prop.toLowerCase()]);
                }).map(function(prop) {
                    const val = ivs[prop.toLowerCase()];
                    return `${val} ${prop}`;
                }).join(" / ") + "\n";
            }
            if (this.moves) {
                str += this.moves.map(function(move) {
                    return `- ${move}`;
                }).join("\n") + "\n";
            }
            return str.trim();
        }
        toString() {
            return this.toShowdown();
        }
    };

    // src/PokemonTeam.ts
    var PokemonTeam = class {
        constructor(format = "gen9", name = "Untitled", folder = void 0) {
            this.pokemon = [];
            this.name = name;
            this.format = format;
            this.folder = folder;
        }
        static fromObject(obj) {
            const team = new PokemonTeam();
            team.name = obj.name;
            team.format = obj.format;
            team.folder = obj.folder;
            team.pokemon = obj.pokemon ? obj.pokemon.map(function(pokemon) {
                return Pokemon.fromObject(pokemon);
            }) : [];
            return team;
        }
        toJson(indentation = 2) {
            return JSON.stringify(this, null, indentation);
        }
        toShowdown() {
            const name = this.folder ? `${this.folder}/${this.name}` : this.name;
            let str = `=== [${this.format}] ${name} ===

`;
            str += this.pokemon.map(function(p) {
                return p.toString();
            }).join("\n\n");
            return str.trim();
        }
        toString() {
            return this.toShowdown();
        }
    };

    // src/PokemonTeamSet.ts
    var PokemonTeamSet = class {
        constructor(teams = []) {
            this.teams = teams;
        }
        static fromObject(obj) {
            const teamSet = new PokemonTeamSet();
            teamSet.teams = obj.teams ? obj.teams.map(function(team) {
                return PokemonTeam.fromObject(team);
            }) : [];
            return teamSet;
        }
        toJson(indentation = 2) {
            return JSON.stringify(this, null, indentation);
        }
        toShowdown() {
            return this.teams.map(function(p) {
                return p.toString();
            }).join("\n\n").trim();
        }
        toString() {
            return this.toShowdown();
        }
    };

    // src/ShowdownParser.ts
    var clamp = (num, min, max) => {
        if (Number.isNaN(num)) {
            return min;
        }
        return Math.min(Math.max(num, min), max);
    };
    var _ShowdownParser = class {
        constructor(code) {
            this.code = code.toString().trim();
        }
        parse() {
            const regexes = _ShowdownParser.regexes;
            const teams = [];
            const current = {
                team: null,
                pokemon: null
            };
            const lines = this.code.trim().split("\n").map(function(line) {
                return line.trim();
            });
            lines.forEach((line) => {
                if (line.match(regexes.team)) {
                    current.team = new PokemonTeam();
                    this._parseTeam(line, current.team);
                    teams.push(current.team);
                    return;
                }
                if (line === "" || line.match(/^[- ]+$/)) {
                    this._saveCurrent(teams, current);
                    return;
                }
                if (!current.pokemon) {
                    current.pokemon = new Pokemon();
                    this._parseNameLine(line, current.pokemon);
                    return;
                }
                if (this._parseKeyValuePairs(line, current.pokemon)) {
                    return;
                }
                if (this._parseEvsIvs(line, current.pokemon)) {
                    return;
                }
                if (current.pokemon.moves.length < 4 && line.match(regexes.move)) {
                    const moveMatches = regexes.move.exec(line);
                    if (moveMatches !== null) {
                        current.pokemon.moves.push(moveMatches[1].trim());
                    }
                }
            });
            this._saveCurrent(teams, current);
            return new PokemonTeamSet(teams);
        }
        _parseTeam(line, team) {
            const rg = _ShowdownParser.regexes;
            const teamDataMatches = rg.team.exec(line);
            if (teamDataMatches && teamDataMatches.length >= 2) {
                const teamNames = teamDataMatches[2].split("/");
                let teamName, teamFolder;
                if (teamNames.length > 1) {
                    teamFolder = teamNames.shift();
                    teamName = teamNames.join("/");
                } else {
                    teamName = teamDataMatches[2];
                }
                team.format = teamDataMatches[1].trim();
                team.name = teamName.trim();
                team.folder = teamFolder ? teamFolder.trim() : void 0;
            }
        }
        _parseNameLine(line, pokemon) {
            const rg = _ShowdownParser.regexes;
            if (line.match(rg.nickname_name)) {
                const nameMatches = rg.nickname_name.exec(line);
                if (nameMatches) {
                    pokemon.nickname = nameMatches[1].trim();
                    pokemon.name = nameMatches[2].trim();
                }
            } else if (line.match(rg.name)) {
                const nameMatches = rg.name.exec(line);
                if (nameMatches) {
                    pokemon.name = nameMatches[1].trim();
                }
            }
            if (line.match(rg.gender)) {
                const genderMatches = rg.gender.exec(line);
                if (genderMatches) {
                    pokemon.gender = genderMatches[1].toUpperCase().trim();
                }
            }
            if (line.match(rg.item)) {
                const itemMatches = rg.item.exec(line);
                if (itemMatches) {
                    pokemon.item = itemMatches[1].trim();
                }
            }
        }
        _parseEvsIvs(line, pokemon) {
            const rg = _ShowdownParser.regexes;
            if (line.match(rg.eivs)) {
                const data = rg.eivs.exec(line);
                if (data === null) {
                    return false;
                }
                const prop = data[1].toLowerCase();
                const values = data[2].split("/");
                const limit = prop === "evs" ? 255 : 31;
                values.forEach(function(stat) {
                    const statData = rg.eivs_value.exec(stat.trim().toLowerCase());
                    if (!statData) {
                        console.error("Invalid syntax for " + prop + ": " + stat);
                        return;
                    }
                    if (!pokemon[prop]) {
                        pokemon[prop] = {};
                    }
                    pokemon[prop][statData[2]] = clamp(parseInt(statData[1]), 0, limit);
                });
                return true;
            }
            return false;
        }
        _parseKeyValuePairs(line, pokemon) {
            const propNames = [
                "nature",
                "ability",
                "level",
                "shiny",
                "happiness",
                "pokeball",
                "dynamaxLevel",
                "gigantamax",
                "teraType"
            ];
            return propNames.some(function(key) {
                const matches = _ShowdownParser.regexes[key].exec(line);
                if (matches === null) {
                    return false;
                }
                let value = matches[1].trim();
                if (key === "happiness") {
                    value = clamp(parseInt(value), 0, 255);
                } else if (key === "level") {
                    value = clamp(parseInt(value), 1, 100);
                } else if (key === "dynamaxLevel") {
                    value = clamp(parseInt(value), 0, 10);
                } else if (key.match(/^(shiny|gigantamax)$/i)) {
                    value = value.match(/yes/i) !== null ? true : void 0;
                }
                pokemon[key] = value;
                return true;
            });
        }
        _saveCurrent(teams, current) {
            if (!current.team) {
                current.team = new PokemonTeam();
                teams.push(current.team);
            }
            if (current.pokemon) {
                current.team.pokemon.push(current.pokemon);
                current.pokemon = null;
            }
            return this;
        }
        format() {
            this.code = this.parse().toString();
            return this;
        }
        toString() {
            return this.code;
        }
    };
    var ShowdownParser = _ShowdownParser;
    ShowdownParser.regexes = {
        team: /^===\s+\[(.*)\]\s+(.*)\s+===$/,
        nickname_name: /^([^()=@]*)\s+\(([^()=@]{2,})\)/i,
        name: /^([^()=@]{2,})/i,
        gender: /\((F|M)\)/i,
        item: /@\s?(.*)$/i,
        eivs: /^([EI]Vs):\s?(.*)$/i,
        eivs_value: /^([0-9]+)\s+(hp|atk|def|spa|spd|spe)$/i,
        move: /^[-~]\s?(.*)$/i,
        nature: /^(.*)\s+Nature$/,
        ability: /^(?:Ability|Trait):\s?(.*)$/i,
        level: /^Level:\s?([0-9]{1,3})$/i,
        shiny: /^Shiny:\s?(Yes|No)$/i,
        happiness: /^(?:Happiness|Friendship):\s?([0-9]{1,3})$/i,
        pokeball: /^(?:Pokeball|Ball):\s?(.*)$/i,
        dynamaxLevel: /^Dynamax Level:\s?([0-9]{1,2})$/i,
        gigantamax: /^Gigantamax:\s?(Yes|No)$/i,
        teraType: /^Tera Type:\s?(.*)$/i
    };

    // src/Koffing.ts
    var Koffing = class {
        static parse(data) {
            if (data instanceof PokemonTeamSet || data instanceof PokemonTeam || data instanceof Pokemon) {
                return data;
            }
            if (data instanceof ShowdownParser) {
                return data.parse();
            }
            return new ShowdownParser(data).parse();
        }
        static format(data) {
            return this.parse(data).toShowdown();
        }
        static toJson(data) {
            return this.parse(data).toJson();
        }
        static toShowdown(data) {
            if (data instanceof PokemonTeamSet || data instanceof PokemonTeam || data instanceof Pokemon) {
                return data.toShowdown();
            }
            if (data instanceof ShowdownParser) {
                return data.parse().toShowdown();
            }
            if (typeof data === "string") {
                data = JSON.parse(data);
            }
            return PokemonTeamSet.fromObject(data).toShowdown();
        }
    };

    // Begin actual plugin code

    GM_addElement(GM_addElement(document.getElementsByTagName('aside')[0], 'p'), 'button', {
        textContent: "Export to Trainertext"
    }).addEventListener("click", function (ev) {
        GM_xmlhttpRequest({
            url: window.location + '/raw',
            onload: async res => {
                const koff = Koffing.parse(res.responseText);
                const teams = [];
                koff.teams.forEach(teamraw => {
                    let ttx = '{\n' +
                        ':teamid => ["Dummy",:TCLASS,id],\n' +
                        ':defeat => "Dummy text",\n' +
                        ':mons => [';
                    const mons = [];
                    teamraw.pokemon.forEach(monraw => {
                        let form = 0;
                        let formmatch = monraw.name.match(/(?<species>.*)-(Aevium|Alola|Paldea|Galar|Hisui)$/);
                        if (formmatch) {
                            form = 1;
                            monraw.name = formmatch.groups.species;
                        }
                        let mtx = '{\n' +
                            '    :species => :' + monraw.name.replaceAll(/\W/g, '').toUpperCase() + ',\n';
                        if (monraw.nickname) {
                            '    :name => ' + JSON.stringify(monraw.nickname) + ',\n';
                        }
                        mtx += '    :level => ' + monraw.level + ',\n';
                        if (monraw.item) {
                            mtx += '    :item => :' + monraw.item.replaceAll(/\W/g, '').toUpperCase() + ',\n';
                        }
                        let moves = []
                        monraw.moves.forEach(moveraw => {
                            moves.push(':' + moveraw.replaceAll(/\W/g, '').toUpperCase());
                        });
                        mtx += '    :moves => [' + moves.join(',') + '],\n';
                        mtx += '    :ability => :' + monraw.ability.replaceAll(/\W/g, '').toUpperCase() + ',\n';
                        if (monraw.gender) {
                            mtx += '    :gender => "' + monraw.gender + '",\n';
                        }
                        if (monraw.shiny) {
                            mtx += '    :shiny => true,\n';
                        }
                        if (form > 0) {
                            mtx += '    :form => ' + form + ',\n';
                        }
                        mtx += '    :nature => :' + monraw.nature.toUpperCase() + ',\n';
                        if (monraw.ivs && monraw.ivs.spe === 0) {
                            mtx += '    :iv => 32,\n';
                        } else {
                            mtx += '    :iv => 31,\n';
                        }
                        let evs = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'].map(key => {
                            if (monraw.evs && monraw.evs[key]) {
                                return monraw.evs[key]
                            } else {
                                return 0
                            }
                        });
                        mtx += '    :ev => [' + evs.join(', ') + ']}';
                        mons.push(mtx);
                    });
                    ttx += mons.join(',\n') + ']},\n';
                    teams.push(ttx);
                });
                GM_setClipboard(teams.join(''), 'text')
            }
        });
    });
})();