Kepler. Documentation · Scripts 325 fonctions disponibles actuellement English

Compte, galaxie, joueurs et messages

Savoir qui vous êtes, regarder ce qu'il y a dans un système, reconnaître les autres joueurs, lire le classement, et écrire à quelqu'un.

Le compte

FonctionRend
GetCachedPlayer()1 : votre fichePlayerID, PlayerName, Points, Rank, Total, HonourPoints.
GetSlots()1 : vos emplacements de flotteInUse, Total, ExpInUse, ExpTotal.
ServerURL()2 : une adresse et une erreurL'adresse de votre serveur, sans barre finale. Un ajout de Kepler.
moi = GetCachedPlayer()          // UNE cible : c'est une structure
Print(moi.PlayerName, moi.Points, "points, rang", moi.Rank, "sur", moi.Total)

slots = GetSlots()               // UNE cible également
if slots.InUse >= slots.Total { Print("plus un emplacement libre") }
if slots.IsAllSlotsInUse(EXPEDITION) { Print("expéditions au complet") }

adresse, err = ServerURL()       // DEUX cibles
Print(adresse)                   // https://s123-fr.ogame.gameforge.com

Rank est votre place au classement, Total le nombre de joueurs classés.

Une fiche de mémoire, pas une lecture. GetCachedPlayer ne demande rien au jeu, et c'est là son intérêt comme sa limite : elle rend ce que le robot a retenu de la dernière page d'accueil qu'il a ouverte, et d'aucune autre. Le pseudo et l'identifiant ne bougent pas, les points et le rang peuvent dater de plusieurs minutes.

Zéro veut dire « la lecture a échoué ». GetSlots interroge le jeu, mais ne rend qu'une valeur : elle n'a aucun moyen de vous signaler un échec. Sans session, ou si le jeu répond de travers, elle rend une structure entièrement à zéro et écrit la raison au journal du robot. C'est vrai aussi de GetCachedPlayer. Zéro emplacement libre sur zéro se lit « complet » par n'importe quel test, IsAllSlotsInUse comprise, si bien qu'aucun script n'ira lancer de flotte là-dessus. C'est voulu.

Relever un système

FonctionRend
GalaxyInfos(galaxie, système)2 : le relevé et une erreurLes quinze positions d'un système.
GalaxyInfosUsing(galaxie, système, céleste)2 : le relevé et une erreurLe même, en se plaçant sur un céleste.
GetPlanetInfo(coordonnée)2 : la position et une erreurUne seule position.
GetPlanetInfoUsing(coordonnée, céleste)2 : la position et une erreurLa même, en se plaçant sur un céleste.
CoordinatesAvailableForDiscoveryFleet(origine, galaxie, système)2 : la liste et une erreurLes positions où une expédition de forme de vie peut partir, places vides comprises. Liste vide quand il n'y en a aucune.
sys, err = GalaxyInfos(1, 224)         // DEUX cibles
if err != nil { LogError(err); return }

for i = 1; i <= 15; i++ {
	p = sys.Position(i)
	if p == nil { continue }           // position entièrement vide
	if p.Player.ID == 0 {
		// personne : champ de débris seul, ou planète détruite
		Print(i, "sans propriétaire", p.Debris.Metal)
		continue
	}
	Print(p.Coordinate, p.Name, p.Player.Name, p.Activity)
}
// [P:1:224:8] Colonie III Xylo 15

Le relevé passe désormais par la porte de la version 13, sous la même forme qu'avant. Le jeu a fermé la page que la bibliothèque interrogeait : elle répondait « error in retrieve galaxy infos », et GalaxyInfos était la dernière lecture du robot restée en arrière. Elle emprunte maintenant le même relevé que les workers, puis le retraduit dans la forme d'origine, champ pour champ. Vos scripts existants n'ont pas une ligne à changer, à ceci près que les champs que la version 13 ne sert plus reviennent vides plutôt qu'en erreur : voyez les pièges ci-dessous.

Ce que porte une position

ChampCe qu'il vaut
.ID, .NameLa planète. Vides sur une position qui ne porte qu'un champ de débris.
.CoordinateS'écrit [P:1:224:8]. Le type est toujours P, même quand la position porte une lune.
.Activity0 : rien à signaler. 15 : actif à l'instant. 16 à 59 : les minutes écoulées, recopiées telles que le jeu les compte.
.Player.ID, .Player.Name, .Player.RankLe propriétaire et sa place au classement.
.Inactive, .Vacation, .Banned, .Newbie, .StrongPlayer, .HonorableTarget, .AdministratorLes états du joueur.
.Player.IsBandit, .Player.IsStarlordLes deux extrémités du rang d'honneur.
.Debris.Metal, .Debris.Crystal, .Debris.Deuterium, .Debris.RecyclersNeededLe champ de débris, avec le nombre de recycleurs que le jeu annonce.
.Moonnil, ou .ID, .Diameter, .Activity. .Moon.Name existe et reste vide.
.Alliancenil, ou .Tag.
.DestroyedExiste, et vaut toujours faux. Voir plus bas.

Les pièges du relevé

nil veut dire « rien du tout », pas « pas de planète ». Une position qui ne porte qu'un champ de débris rend une fiche bien réelle, aux champs de joueur vides. Testez p.Player.ID == 0 pour « personne », p == nil pour « position déserte ». Un cas limite : un champ qui ne contiendrait que du deutérium, sans métal ni cristal, est laissé de côté et sa position revient nil.

Une planète détruite garde un nom, et .Destroyed ne le dit pas. Le jeu la rattache à un compte système au lieu de la retirer. Kepler efface le joueur, le rang, l'alliance et tous les états, mais .Player.Name peut encore porter le nom de remplacement du jeu. Quant à .Destroyed, il existe dans la forme rendue et n'est jamais rempli : il vaut faux sur une planète détruite comme sur les autres. L'identifiant de joueur à zéro est le seul test qui ne trompe pas.

Une activité à zéro n'est pas une absence d'activité. C'est l'absence d'affichage, et le jeu n'affiche rien au-delà d'une heure. Un joueur parti depuis trois jours et un joueur parti depuis soixante et une minutes rendent le même zéro.

L'alliance n'a plus que son tag. La version 13 ne donne plus le nom, le rang ni le nombre de membres sur cette page. .Alliance.Name, .Alliance.ID, .Alliance.Rank et .Alliance.Member existent donc toujours, et rendent la chaîne vide ou zéro au lieu d'une erreur, ce qui est le moins mauvais des deux.

L'inactivité longue n'est pas reportée. Le relevé du jeu distingue l'inactif de l'inactif de longue date, mais la forme rendue à votre script ne porte qu'un .Inactive, recopié du seul drapeau court. Pour faire la différence, lisez le Status que rend PlayersData et cherchez-y le I majuscule.

Le relevé n'expose que trois choses : Position(i), Galaxy() et System(). Les autres membres de la forme d'origine sont hors d'atteinte : sys.ExpeditionDebris s'arrête sur no member named 'ExpeditionDebris' for struct, et sys.Events comme sys.Relocations font de même. C'est une erreur franche, à l'exécution, qui nomme le champ. Les débris d'expédition, qui se posent en position 16, ne sont de toute façon pas dans le relevé.

Un relevé fait la queue derrière les workers. Il passe par la file d'attente du compte, à priorité ordinaire, mais pas par le régulateur de cadence du robot : c'est à votre script d'espacer ses appels s'il balaie cent systèmes.

Se placer sur un céleste, et pourquoi cela compte

c = GetCachedCelestial("1:2:3")
sys, err = GalaxyInfosUsing(4, 116, c.GetID())

Le céleste ne change pas ce que le relevé dit : un système contient les mêmes planètes vu d'où que ce soit. Il compte dès que vous voulez agir depuis ce relevé.

Le jeu rattache l'action à la planète courante, et le jeton qu'il délivre avec le relevé vaut pour elle. Relever depuis l'une et envoyer depuis l'autre se solde par un refus — que rien dans le relevé n'annonce.

Une seule position

p, err = GetPlanetInfo("4:116:6")
if err != nil {
	Print("personne là :", err)      // aucune planète en [P:4:116:6]
} else {
	Print(p.Name, p.Player.Name, p.Activity)
}

GetPlanetInfo relève le système entier et vous en rend une position : elle coûte donc exactement une requête, la même que GalaxyInfos. Si vous en voulez plusieurs dans le même système, appelez GalaxyInfos une fois et parcourez son résultat.

Une position vide rend une erreur, et non une fiche à zéro. C'est délibéré : une fiche vide se lirait comme une planète sans nom appartenant au joueur numéro zéro, et un script qui compare des noms ou des rangs y verrait une vraie cible.

Parcourir plusieurs systèmes

FonctionRend
GetSystemsInRange(origine, rayon)1 : une liste de systèmesDans l'ordre des numéros.
GetSystemsInRangeAsc(origine, rayon)1 : une liste de systèmesDu plus proche au plus loin.
GetSystemsInRangeDesc(origine, rayon)1 : une liste de systèmesDu plus loin au plus proche.
Print(GetSystemsInRange(3, 2))       // [1 2 3 4 5]
Print(GetSystemsInRangeAsc(3, 2))    // [3 2 4 1 5]
Print(GetSystemsInRangeDesc(3, 2))   // [5 1 4 2 3]

for s in GetSystemsInRangeAsc(224, 15) {
	sys, err = GalaxyInfos(1, s)
	if err != nil { LogWarn(s, err); continue }
	SleepRandMs(500, 1200)           // espacez, rien ne le fera pour vous
}

Ces trois fonctions ne demandent rien au jeu : elles calculent. La galaxie boucle, donc un rayon qui déborde revient de l'autre côté, GetSystemsInRange(3, 3) commence par 499. Le nombre de systèmes est celui de votre univers ; faute de session, le calcul retombe sur 499 sans le dire.

Un écart avec Ninja. Notre Desc renverse la liste entière, la sienne ne renverse que les distances. Les deux rendent les mêmes systèmes, du plus loin au plus proche, mais à distance égale l'ordre des deux voisins est inversé : nous rendons [5 1 4 2 3] là où sa documentation annonce [1 5 2 4 3]. Sans conséquence pour un balayage, gênant si vous comparez deux journaux.

Les joueurs

FonctionRend
PlayersData()2 : la liste et une erreurTous les joueurs de l'univers.
PlayerDataByID(id)2 : un joueur et une erreurErreur franche si l'identifiant est inconnu.
PlayerDataByName(pseudo)2 : un joueur et une erreurPseudo entier, la casse est ignorée.

Ce que porte un joueur

ChampCe qu'il vaut
ID, NameL'identifiant et le pseudo, tels que le jeu les publie.
StatusLa suite de lettres du jeu. C'est la seule source complète.
AllianceL'identifiant de son alliance, 0 quand il n'en a pas.
IsInactive()Vraie si le statut porte un i ou un I : l'inactivité courte comme la longue.
IsVacation()Vraie si le statut porte un v.
IsBanned()Vraie si le statut porte un b.
DefensivePoints()Ses points de défense, que le jeu ne publie pas. Fiche riche seulement.
OffensivePoints()Sa flotte, c'est-à-dire le militaire moins la défense. Fiche riche seulement.
joueurs, err = PlayersData()          // DEUX cibles
if err != nil { LogError(err); return }

n = 0
for j in joueurs {
	if j.IsInactive() { n++ }         // les parenthèses comptent, voir plus bas
}
Print(n, "inactifs sur", len(joueurs))

cible, err = PlayerDataByName("Xylo")
if err != nil { Print("inconnu au bataillon") } else { Print(cible.ID) }

Le piège des parenthèses, et il est silencieux. IsInactive, IsVacation et IsBanned sont des fonctions chez nous, des cases à cocher chez Ninja. Écrite sans parenthèses, if j.IsInactive { ... } ne provoque aucune erreur : la condition est simplement toujours fausse, et votre script trouve zéro inactif dans un univers qui en compte deux mille.

if j.IsInactive  { ... }   // ✗ jamais vrai, jamais signalé
if j.IsInactive() { ... }  // ✓

Les autres noms de champs diffèrent aussi. Sa fiche courte porte IsAdmin, IsLongInactive, Vacation et AllianceID ; la nôtre porte Status et Alliance. Un script recopié s'arrête donc sur no member named 'Vacation' for struct, ce qui au moins se voit tout de suite. IsInactive est le seul nom commun aux deux, et c'est justement celui qui ne dit rien quand on l'écrit à sa façon.

La fiche complète, et ce qu'elle coûte. PlayerDataByID et PlayerDataByName rendent la fiche riche, aux noms de Ninja : PointsTotal, PointsEconomy, PointsResearch, PointsMilitary, PointsMilitaryBuilt, PointsMilitaryDestroyed, PointsMilitaryLost, PointsHonor, PointsLifeform, les neuf Position... qui leur répondent, MilitaryShips, Celestials et Alliance. PlayersData, elle, garde la fiche courte.

Alliance vaut nil quand le joueur n'en a pas, et porte sinon ID, Name et Tag. C'est le seul endroit du bot qui joint un identifiant d'alliance à son tag : le relevé de galaxie de la version 13 ne donne que le tag.

Celestials porte les planètes du joueur et leurs lunes, chaque lune juste après la sienne, avec ID, Name et Coordinate. C'est la seule façon de savoir où quelqu'un habite sans balayer des systèmes entiers.

Ces deux fonctions coûtent une requête par joueur, gardée une heure, là où PlayersData n'en coûte aucune. Un script qui parcourt deux mille joueurs doit donc partir de PlayersData et ne demander la fiche complète que de ceux qu'il retient.

DefensivePoints() et OffensivePoints() séparent la défense de la flotte, ce que le jeu ne publie nulle part : son classement militaire mélange les deux.

p, err = PlayerDataByID(id)
Print(p.DefensivePoints())        // ses points de défense
Print(p.OffensivePoints())        // sa flotte, donc ce qui peut voler

Le calcul repose sur une identité du jeu : la défense est la seule chose comptée en entier à la fois dans l'économie et dans le militaire. La somme des catégories dépasse donc le total d'exactement la défense.

défense = économie + recherche + militaire + forme de vie − total flotte = militaire − défense

Ces deux méthodes n'existent pas dans la 1.82.239 ni dans les versions antérieures : un bot qui ne les connaît pas arrête le script dessus.

La formule vient d'un client, le 22/09/2026 ; la référence de Ninja déclare les deux fonctions et n'a jamais donné leur calcul. Vérifiée sur deux comptes indépendamment : chez lui, et ici en chiffrant chaque pièce de défense des vingt célestes d'un compte par son prix. 168 476 points annoncés, 168 476 comptés, à l'unité près.

Les missiles d'interception comptent dans la défense, ce qui n'allait pas de soi : ils ne défendent contre aucune flotte, seulement contre d'autres missiles. La comptabilité du jeu les range pourtant là. Mesuré sur un second compte, qui en porte 490 :

sans les missiles : 2 695 268 → écart de 4 900 avec les missiles : 2 700 168 → écart nul

soit exactement leur prix. Les missiles interplanétaires, eux, n'ont pas pu être mesurés : aucun compte sous la main n'en a un seul. Ils sont sur la même page et dans le même silo, donc ils comptent probablement aussi - mais probablement n'est pas mesuré.

Une réserve qui reste : un joueur dont le fichier date de quelques heures rend les chiffres de ce moment-là, pas de maintenant. Le jeu ne le regénère qu'une fois par jour.

DefensivePoints() ne descend jamais sous zéro : les classements entiers arrondissent, et la soustraction peut tomber à −1. Le fichier de fiches, lui, est fractionnaire et ne déclenche pas ce cas.

Les points militaires ne sont pas dans l'ordre que Ninja déclare. Sa liste donne Built, Destroyed, Lost ; le jeu range les types 4, 5 et 6 dans l'ordre perdu, construit, détruit. Mesuré sur quarante joueurs le 21/09/2026, par une identité qui ne dépend d'aucune documentation : ce qu'un joueur a construit vaut ce qu'il a encore plus ce qu'il a perdu. Les noms sont ceux de Ninja, les valeurs viennent des bons types.

Le fichier est gros, il est donc gardé six heures. Le jeu ne le regénère qu'une fois par jour. Le premier appel télécharge plusieurs centaines de kilo-octets, les suivants ne coûtent rien, et tous les scripts du compte partagent la même copie. Un joueur qui change de pseudo peut donc rester connu sous l'ancien pendant six heures. PlayerDataByID et PlayerDataByName lisent cette même copie ; la seconde compare le pseudo entier, pas un morceau.

Ces lectures passent par la connexion du compte (donc par son proxy), mais pas par sa file d'attente : elles ne retardent aucun worker.

Le classement

FonctionRend
HighscoreData(category, typ)2 : le classement entier et une erreurPar l'API du jeu. Des identifiants, pas des noms.
GetHighscore(category, typ, page)2 : une page et une erreurNe fonctionne plus en version 13.

Employez HighscoreData. GetHighscore lisait la page du classement, et la version 13 l'a réécrite : elle s'arrête maintenant sur failed to find site. Elle reste définie pour qu'un script venu d'ailleurs ne tombe pas sur un symbole inconnu, mais elle rendra une erreur à chaque appel.

HighscoreData interroge l'API que le jeu publie pour être lue. Elle rend le classement entier d'un seul appel, là où la page en donnait cent, et ne se cassera pas à la prochaine version. En échange, elle ne donne que des identifiants : le nom se prend dans PlayersData. Comme elle, elle emprunte la connexion du compte sans passer par sa file d'attente.

rangs, err = HighscoreData(1, 0)      // joueurs, classement général
if err != nil { LogError(err); return }

joueurs, err = PlayersData()
if err != nil { LogError(err); return }

noms = {}
for j in joueurs { noms[j.ID] = j.Name }

for r in rangs {
	if r.Position <= 10 { Print(r.Position, noms[r.ID], r.Score) }
}

Chaque entrée porte ID, Position, Score et Ships, ce dernier n'étant rempli que par les classements militaires.

category vaut 1 pour les joueurs et 2 pour les alliances. typ choisit le classement : 0 le total, 1 l'économie, 2 la recherche, 3 le militaire, puis 4, 5 et 6 pour les pertes, les constructions et les destructions. Au-delà, la documentation de Ninja se contredit elle-même : sa liste nomme 9 « économie » quand son propre exemple donne ce nom à 1. Kepler ne les interprète pas, il les recopie tels quels dans l'adresse de l'API : un appel d'essai tranche en une seconde, un pari se paie en heures.

Un classement d'alliances rendra des identifiants d'alliance, que PlayersData ne sait pas nommer : elle ne connaît que les joueurs.

Chaque couple catégorie/type est gardé une heure de son côté, parce que le jeu ne recalcule les rangs qu'une fois par heure. Comparer l'économie au militaire ne fait donc pas retélécharger l'un par l'autre.

Lire une page du jeu telle quelle

Deux ajouts qui n'existent pas chez Ninja. Ils servent à voir ce que le jeu envoie vraiment, quand une erreur d'analyse ne dit pas en quoi la page a changé.

FonctionRend
PageContent(requête)2 : le texte et une erreurUne page du jeu, par la session du compte.
FetchURL(adresse)2 : le texte et une erreurN'importe quelle adresse, par la même session.
HttpRequest(méthode, adresse, en-têtes, corps)3 : le texte, le code et une erreurUne requête complète, par la même session.
html, err = PageContent("page=ingame&component=overview")
if err != nil { LogError(err); return }
Print(len(html), "octets")

regexp = import("regexp")
r = regexp.MustCompile(`var site = (\d+)`)
Print(r.FindStringSubmatch(html))

PageContent prend une suite de paramètres, pas une adresse : ce qui suit le ? de index.php. La requête part par la session du compte, donc par son proxy, ses cookies et son empreinte, et par sa file d'attente.

Un nom de page inexact retient le compte plusieurs minutes. Le jeu répond « An error has occured! », que la bibliothèque prend pour une déconnexion : elle se reconnecte et réessaie, dix fois, en doublant l'attente jusqu'à une minute, et pendant ce temps ni les workers ni l'écran n'obtiennent la main. Une graphie est corrigée d'office pour vous, resourceSettings devenu resourcesettings en version 13, et la correction se lit au journal. Les autres sont à vous.

FetchURL télécharge une adresse complète. ServerURL() vous donne la racine. FetchURL est bornée à 4 Mo, ce qui suffit largement pour un fichier d'interface. Deux choses à savoir : elle ne regarde pas le code de réponse, si bien qu'une page d'erreur du serveur vous revient comme un texte ordinaire, sans erreur ; et une longue lecture s'interrompt si vous arrêtez le script.

HttpRequest est la même chose avec les trois pièces qui manquent à FetchURL : la méthode, les en-têtes et le code de réponse.

entêtes = {}
entêtes["Authorization"] = "Bearer " + jeton
entêtes["Content-Type"] = "application/json"

charge, err = JsonEncode({"celeste": 33628068})
if err != nil { LogError(err); return }

corps, code, err = HttpRequest("POST", "https://exemple.net/api", entêtes, charge)
if err != nil { LogError(err); return }       // ← TROIS cibles

if code == 429 {
    Sleep(60)                                 // trop vite : on réessaiera
    return
}
if code == 401 {
    LogError("jeton refusé")                  // inutile de réessayer
    return
}
Print(code, len(corps))

C'est le code qui vous manquait pour une échelle de reprise. Un 429 se réessaie après une pause, un 401 ne se réessaiera jamais avec succès, et sans le code les deux se ressemblent : FetchURL vous rendrait la page d'erreur comme un texte ordinaire.

Les en-têtes s'écrivent en tableau ordinaire, nil si vous n'en avez pas. Un corps vide ne part pas. La méthode est libre - GET, POST, PUT, DELETE, PATCH - et une méthode vide vaut GET.

Le code de réponse n'est jamais une erreur : un 500 vous revient avec err à nil et code à 500. err ne se remplit que si la requête n'a pas pu partir ou pas pu revenir. Testez les deux.

Comme FetchURL, elle passe par la session du compte - même proxy, mêmes cookies, même empreinte - ce qui lui permet de poster sur les points d'entrée du jeu lui-même, qu'un GET nu n'atteint pas. Parler à un service tiers n'emporte rien de votre session : le bocal à cookies ne sert que le domaine visé. La réponse est bornée à 4 Mo, comme FetchURL.

Écrire à un joueur

FonctionRend
SendMessage(playerID, message)1 : une erreurUn message privé, par le chat du jeu.
cible, err = PlayerDataByName("Xylo")
if err != nil { LogError(err); return }

err = SendMessage(cible.ID, "Bonjour, je viens en paix.")
if err != nil { LogError("message refusé :", err) }

Il faut un identifiant, pas un pseudo : passez par PlayerDataByName ou par .Player.ID d'un relevé de galaxie.

L'envoi emprunte le formulaire de la version 13, celui-là même que le robot utilise pour ses propres alertes. Le jeton de discussion est gardé d'un message au suivant, et retrouvé tout seul s'il est refusé : une campagne de trois cents messages ne recharge pas trois cents pages. Les erreurs, elles, sont franches : elles disent sous quelle forme le jeton a été lu, et le début de ce que le jeu a répondu.

SendMessageAlliance n'existe pas encore.

Les rapports et la boîte à messages

C'est la matière première d'un script de pillage écrit à la main : sans elle, un script voit les cibles par la galaxie mais jamais ce qu'elles portent.

FonctionRend
GetEspionageReportMessages()2 : la liste et une erreurLe résumé de chaque rapport de la première page.
GetEspionageReportMessagesPages(pages)2 : la liste et une erreurLa même chose, sur plusieurs pages.
GetEspionageReport(idMessage)2 : le rapport et une erreurLe rapport détaillé d'un message.
GetEspionageReportFor(coordonnée)2 : le rapport et une erreurLe dernier rapport visant cette position.
GetCombatReportSummaryFor(coordonnée)2 : le résumé et une erreurLe dernier combat à cette position.
DeleteMessage(idMessage)1 : une erreurEfface un message.
DeleteAllMessagesFromTab(onglet)1 : une erreurVide un onglet entier.
resumes, err = GetEspionageReportMessages()
if err != nil { LogError("boite :", err) }
for m in resumes {
  Print(m.ID, m.Target, "butin", m.LootPercentage)
}

rapport, err = GetEspionageReportFor("1:2:3")
if err == nil {
  Print(rapport.Username, "inactif :", rapport.IsInactive)
  Print("metal :", rapport.Metal, "cristal :", rapport.Crystal)
}

detail, err = GetEspionageReport(resumes[0].ID)
Print(detail.Username, err)

combat, err = GetCombatReportSummaryFor("1:2:3")
if err == nil { Print("butin :", combat.Loot, combat.AttackerName) }

anciens, err = GetEspionageReportMessagesPages(3)
Print("sur trois pages :", len(anciens))

DeleteMessage(resumes[0].ID)
DeleteAllMessagesFromTab(20)

Le résumé porte ID, Target, Type, From et LootPercentage — pas les ressources : celles-ci sont dans le rapport détaillé, que GetEspionageReport ou GetEspionageReportFor rendent. Ce dernier porte aussi Username, IsInactive, LastActivity, et les niveaux de bâtiments quand vous avez envoyé assez de sondes pour les voir.

Les numéros d'onglet sont ceux du jeu : 20 espionnage, 21 rapports de combat, 22 expéditions, 23 transports et unions, 24 le reste. Un autre numéro est refusé ici plutôt que passé au jeu, qui répondrait par sa page d'accueil, c'est-à-dire par rien.

Une seule page par défaut. GetEspionageReportMessages lit ce que voit un joueur qui ouvre sa boîte. Parcourir tout l'historique coûterait une requête par page sans que le script l'ait demandé : c'est à quoi sert la variante qui prend un nombre de pages.

Compter ses colonies

FonctionRend
CountColonies()2 : le nombre et le maximumCe que vous avez, ce que vous pouvez avoir.
combien, possible = CountColonies()
Print(combien, "colonies sur", possible)

Aucune erreur en retour : le compte se lit dans la mémoire du robot, il ne coûte pas de requête. Sans session ouverte, deux zéros et une ligne au journal.