pgoapi icon indicating copy to clipboard operation
pgoapi copied to clipboard

Botting

Open Nostrademous opened this issue 8 years ago • 49 comments

I cloned a while ago from @tejado and have been working on implementing features... Got to say things are going well and I'm willing to answer questions people have, but not willing to copy/paste source code since it would lead to more questions and at this point I'm divergent from the current code base by several revisions (some of them structural).

I do not claim to have the best or most advanced bot, but it does what I want it to do... it just leveled a brand new test account from lvl 1 -> 20 in under a day (servers were stable).

Here is what I have figured out thus far:

  • looping a specified set of waypoints OR a random-walk based on seeded start location
  • use all pokestops we encounter
  • catching all pokemon that are map visible as we walk ** catching uses curve balls and probably a "too" high chance of excellent throws ** catching uses appropriate level of pokeball based on difficulty of pokemon ** catching uses razz berries as necessary based of difficulty of pokemon
  • inventory knowledge - I know how much I have of each item (except evolution candies for now....)
  • pokemon inventory control (aka we transfer pokemons to the professor for candy when overly full, config options to keep what you want by type/nickname/cp-level)
  • ability to scan our Pokedex to know what pokemons we have seen or not seen yet
  • bag item control (delete items we don't care about if we run out of space... also cap our pokeballs so we don't keep too many)
  • egg hatching detection
  • automatic egg incubation
  • detection of bad network conditions or session-key changes by server with auto-reconnect after timeout
  • code for using Lucky Eggs (currently not automated as when to do it is debatable .. )
  • code for automatic evolution of pokemons (although untested still)

Feel free to ask questions, but like I said, I won't be releasing code or copy/pasting solutions, but can help with proto formats.

Nostrademous avatar Jul 22 '16 18:07 Nostrademous

detection of bad network conditions or session-key changes by server with auto-reconnect after timeout -> miss Tell me more about this, i don't know too much about sessions

ztukaz avatar Jul 22 '16 19:07 ztukaz

I believe your evolution candies are listed in your inventory as well in the dictionary of the pokemon.

{
            "inventory_item_data": {
              "pokemon_family": {
                "family_id": 124, 
                "candy": 20
              }
            }, 
            "modified_timestamp_ms": 1469131340176
          }

Could you help me out how to trigger egg incubation ? In the inventory I can find if I have a available incubator and its id, how can to get my eggs id How do I trigger it ?

dnsBlah avatar Jul 22 '16 19:07 dnsBlah

@dnsBlah they are listed in dictionary of pokemons but they all are 0 apparently when you walk it... thanks for the pokemon_family data... that will work

Egg Incubation - the proto is slightly wrong as checked (at least time last time I checked). Notice I have "repeated" whereas what's checked in is "optional". @tejado doesn't provide the .proto files here (not sure why) so it won't be an easy fix for you guys - but that fix is only necessary if you want to incubate multiple eggs at the same time.

message EggIncubators {
  repeated EggIncubator egg_incubator = 1;
}

Below should explain how to use incubators (the code is using my structure, but should give you an idea of how to use it).

def useIncubator(api):
    egg_ids = []
    inc_ids = []
    inv = getInventory(api)
    if inv:
        for items in inv.inventory_delta.inventory_items:
            pk = items.inventory_item_data.pokemon
            if pk and pk is not None:
                if pk.is_egg and not pk.egg_km_walked_start:
                    egg_ids.append(pk.id)
            ics = items.inventory_item_data.egg_incubators
            for ic in ics.egg_incubator:  # <-- requires the fixed proto I mentioned, othewise can't loop
                if ic and ic is not None:
                    if ic.item_id:
                        inc_ids.append(ic.item_id)
    log.debug('Available Eggs for Incubation: %s', egg_ids)
    log.debug('Available Incubators: %s', inc_ids)

    for i in range(0, min(len(egg_ids),len(inc_ids))):
        api.use_item_egg_incubator(item_id=inc_ids[i], pokemon_id=egg_ids[i])
        inc_use = api.call(False)
        log.info('Using Egg Incubator "%s" on "%d"...' % (inc_ids[i], egg_ids[i]))
        inc_pay = getPayload(inc_use)
        if inc_pay:
            inc_resp = rsub.UseItemEggIncubatorResponse()
            inc_resp.ParseFromString(inc_pay)
            print(inc_resp)
        else:
            log.error('Bad Payload - Using Egg Incubator')

Nostrademous avatar Jul 22 '16 20:07 Nostrademous

@ztukaz - regarding sessions, honestly I'm not super familiar with that. @tejado might honestly know better as this would be a API library / networking / connectivity issue. What I know is from here: https://github.com/Mila432/Pokemon_Go_API/blob/master/api.pyy

Nostrademous avatar Jul 22 '16 20:07 Nostrademous

@Nostrademous Thanks for the hints I don't think the statements are right, as a pokemon['is_egg'] == true does not tell me if its active or not. Instead the incubator tells me with start_ and target_km_walked But can't link a incubator to an egg :-/

Or is that the reason why I need a other proto to request the inventory? or just try all 9 eggs until positive result?

dnsBlah avatar Jul 22 '16 20:07 dnsBlah

@dnsBlah pokemon['is_egg'] tells you it's an egg pokemon['egg_km_walked_start'] is only present if it is an active egg (and can only be present if it's an egg) <-- this is why I check that it is NOT present to know it's an available egg

With both those checks you can then record pokemon['id'] as a valid egg for incubation. That is the 'id' you pass as the 2nd argument to api.use_item_egg_incubator()

Also, looks liek POGO fixed the "repeated" 20 hrs ago.... heh, didnt' realize. https://github.com/AeonLucid/POGOProtos/blob/master/src/POGOProtos/Inventory/EggIncubators.proto

Nostrademous avatar Jul 22 '16 20:07 Nostrademous

Hi @Nostrademous Like I mentioned, the egg does not show anything about start...

{
            "inventory_item_data": {
              "pokemon": {
                "creation_time_ms": 1469191037546, 
                "captured_cell_id": 5171996602586365952, 
                "is_egg": true, 
                "id": 11891607393952159418
              }
            }, 
            "modified_timestamp_ms": 1469191037546
          }

(I have 9 eggs *max ofcourse) All 9 look like this, but I'm walking 1.

{
            "inventory_item_data": {
              "egg_incubators": {
                "egg_incubator": {
                  "target_km_walked": 153.98680114746094, 
                  "start_km_walked": 148.98680114746094, 
                  "pokemon_id": -4500287810622364247, 
                  "item_type": 901, 
                  "incubator_type": 1, 
                  "item_id": "EggIncubatorProto-1928901772253991081"
                }
              }
            }, 
            "modified_timestamp_ms": 1469219038432
          }, 

See what I mean :) I understand the call thanks of your hint above. Seems like almost all calls are just 2 parameters?

But, yet, I don't see a link between the incubator and the egg's I tried partials of the incibator item_id, of the pokemon_id None relates to any of the eggs in my inventory

dnsBlah avatar Jul 22 '16 20:07 dnsBlah

@dnsBlah I just verified and I guess it changed, you are right, 'km' stuff no longer listed for active eggs. I guess you loop over the eggs and try each one.

If you try to incubate an egg already in use it will return a RESPONSE with: ERROR_POKEMON_ALREADY_INCUBATING = 6;

If you get that, just try a different egg: Results are:

    enum Result {
        UNSET = 0;
        SUCCESS = 1;
        ERROR_INCUBATOR_NOT_FOUND = 2;
        ERROR_POKEMON_EGG_NOT_FOUND = 3;
        ERROR_POKEMON_ID_NOT_EGG = 4;
        ERROR_INCUBATOR_ALREADY_IN_USE = 5;
        ERROR_POKEMON_ALREADY_INCUBATING = 6;
        ERROR_INCUBATOR_NO_USES_REMAINING = 7;
    }

Nostrademous avatar Jul 22 '16 20:07 Nostrademous

<3 thank you!

dnsBlah avatar Jul 22 '16 20:07 dnsBlah

It also seems like that the incubators are not shown in the inventory list... Just 1 (the unlimited one), and I have 3

dnsBlah avatar Jul 23 '16 13:07 dnsBlah

@dnsBlah are you using ITEM_ID=902 for the "extra incubators"? Will have to check if they show up or not, I don't really care actually as when I use incubators I enumerate them differently.

Nostrademous avatar Jul 23 '16 14:07 Nostrademous

I'm guessing i have it working now.. However I don't get unique id's from my item: 902 But will see tomorrow..

dnsBlah avatar Jul 23 '16 21:07 dnsBlah

@dnsBlah

def useIncubator(api):
    egg_ids = []
    inc_ids = []
    inv = getInventory(api)
    if inv:
        for items in inv.inventory_delta.inventory_items:
            pk = items.inventory_item_data.pokemon
            if pk and pk is not None:
                if pk.is_egg:
                    egg_ids.append(pk.id)
        for items in inv.inventory_delta.inventory_items:
            ics = items.inventory_item_data.egg_incubators
            for ic in ics.egg_incubator:
                if ic and ic is not None:
                    if ic.item_id: # and not ic.target_km_walked:
                        inc_ids.append(ic.item_id)

    log.debug('All Eggs: %s', egg_ids)
    log.debug('All Incubators: %s', inc_ids)

    for inc in inc_ids:
        for egg in egg_ids:
            api.use_item_egg_incubator(item_id=inc, pokemon_id=egg)
            inc_use = api.call(False)
            inc_pay = getPayload(inc_use)
            if inc_pay:
                inc_resp = rsub.UseItemEggIncubatorResponse()
                inc_resp.ParseFromString(inc_pay)
                if inc_resp.result == 1:
                    log.info('Using Egg Incubator "%s" on "%d"...' % (inc, egg))
                    log.info(inc_resp)
                elif inc_resp.result == 6:
                    continue
                elif inc_resp.result == 5:
                    log.info('Incubator "%s" already in use' % inc)
                    break
            else:
                log.error('Bad Payload - Using Egg Incubator')

Nostrademous avatar Jul 24 '16 00:07 Nostrademous

@Nostrademous

@tejado doesn't provide the .proto files here (not sure why) so it won't be an easy fix for you guys - but that fix is only necessary if you want to incubate multiple eggs at the same time.

Did you imported in this project manually?

ztukaz avatar Jul 24 '16 09:07 ztukaz

@Nostrademous With an other proto class its working fine now :) Also finding other incubators :-)

dnsBlah avatar Jul 24 '16 15:07 dnsBlah

@dnsBlah glad you got it working :)

I just recently added mass evolving capability to my bot with the use of a Lucky Egg (if I can do more than 125 evolutions at once). This required tracking proper candy and candy requirements (which I do now).

Also added level-up detection and level-up-prize gathering.

Leveled a brand new account to level 22 in under 12 hours - played by bot from level 1 on (without the use of any evolution or lucky-egg).

Nostrademous avatar Jul 26 '16 00:07 Nostrademous

@Nostrademous How did you approach gym ? It should be something like, StartGymBattle, AttackGym and then Put pokemon right?

With StartGymBattle I get always status = 3 and empty results

gym_id = id of the gym (fort)
attacking_pokemon_ids = [idpok1,idpok2]
defending_pokemon_id=id_first_defender
player_latitude=lat
player_longitude=lng

{'responses': {'START_GYM_BATTLE': {}}, 'status_code': 3, 'auth_ticket': {'expire_timestamp_ms': 1469664544675L, 'start': 'tbXTF4N9/wntFc4f0PV6KlBm7kfwsflM2jW/PTtO2tS8jTG9YYAlBBcFTag9PbRo1TFX9NwYDOAiAmuIEpg6bygUFhoHklL52T4i2OWkOgU=', 'end': 'n5kcn21Ojw+YUJZDUBPwwg=='}, 'request_id': 8145806132888207460L}

ztukaz avatar Jul 26 '16 07:07 ztukaz

@Nostrademous Please share the level-up-prize gathering. :-D However I'm sure that the items are gathered anyways when you reach the next level tho.. Still I think it would be good to simulate as much as possible. Because lots of times I end up with more items than my bag can handle ;-)

dnsBlah avatar Jul 27 '16 07:07 dnsBlah

@ztukaz tackling gyms next - haven't started yet

@dnsBlah - for leveling use the LEVEL_UP_REWARDS call and you must supply your current level (which you can obtain from Inventory ItemData PlayerStats. The response message will have all the info you need, and unlocked new items which you can now start getting at pokestops.

Nostrademous avatar Jul 27 '16 23:07 Nostrademous

@ztukaz isn't status_code 3 stating that it is a NEUTRAL GYM? Meaning you can't attack it as no one owns it?

Nostrademous avatar Jul 28 '16 00:07 Nostrademous

Hi @Nostrademous

That's so cool! I now get AWARED_ALREADY Guess I will have to monitor before I level up, to see what the response might be :)

Any idea when this is called in the real game? In the heartbeat ? Btw you also know how the heartbeat actually looks like? :D

Thanks!

dnsBlah avatar Jul 28 '16 06:07 dnsBlah

@ztukaz @tejado Working on gyms... but apparently I keep failing my request... no idea why: any thoughts?

def battleGym(fort):
    log.info("<>________________________________________________________________________<>")
    log.info("%s GYM NEARBY", TeamColor.DESCRIPTOR.values[fort.owned_by_team].name)
    # get gym details
    gym_proto = getGymDetails(fort)
    if not gym_proto: return 0
    log.info(gym_proto)
    log.info("<>________________________________________________________________________<>")

    ##############################################
    # gather necessary details to start a battle #
    ##############################################

    # check if gym is neutral/unowned - if so deploy to it
    if fort.owned_by_team == 0: # Neutral
        deployPokemonToGym(gym_proto)
        return 0

    # save a boolean checking if we are on the same team
    sameTeam = gl.my_team == fort.owned_by_team

    # find out who is defending
    defending_pokemon = []
    for defender in gym_proto.gym_state.memberships:
        defending_pokemon.append(defender.pokemon_data)

    # determine who we will attack with
    num = 6
    if sameTeam: num = 1
    my_attackers = pickAttackers(num) # this will be a list []

    #log.error('Function not finished, returning out...')
    #return 0

    # start the battle
    log.info('Gym ID: %s' % gym_proto.gym_state.fort_data.id)
    log.info('Attacker list: %s' % str(my_attackers))
    log.info('Defender: %d' % defending_pokemon[0].id)
    gl.api.start_gym_battle(gym_id=gym_proto.gym_state.fort_data.id, attacking_pokemon_ids=my_attackers, defending_pokemon_id=defending_pokemon[0].id, player_latitude=gl.p_lat, player_longitude=gl.p_lng)

    start_reply = gl.api.call(False)
    start_payload = getPayload(start_reply)

Printing the request I see:

 [23:45:59] [ INFO] Gym ID: 555ba115a93546aca7851fed86d3ef6c.16
 [23:45:59] [ INFO] Attacker list: [17xxxxxx list of IDs xxxx 2L]
 [23:45:59] [ INFO] Defender: 568051736162843098
 [23:46:00] [ERROR] startGymBattle() request failed to get payload

and printing the gym_proto result:

 [23:45:59] [ INFO] gym_state {
  fort_data {
    id: "555ba115a93546aca7851fed86d3ef6c.16"
    last_modified_timestamp_ms: 1469762856688
    latitude: 38.854506
    longitude: -77.349488
    owned_by_team: RED
    guard_pokemon_id: FLAREON
    enabled: true
    gym_points: 2000
  }
  memberships {
    pokemon_data {
      id: 568051736162843098
      pokemon_id: FLAREON
      cp: 1002
      stamina: 130
      stamina_max: 130
      move_1: EMBER_FAST
      move_2: FLAMETHROWER
      owner_name: "Firehawk04xx"
      height_m: 0.884167253971
      weight_kg: 9.47041511536
      individual_attack: 15
      individual_defense: 14
      individual_stamina: 1
      cp_multiplier: 0.481684952974
      pokeball: ITEM_GREAT_BALL
      battles_attacked: 10
      battles_defended: 1
      creation_time_ms: 1469753995514
      num_upgrades: 2
      additional_cp_multiplier: 0.0181734859943
      nickname: "Flareon"
    }
    trainer_public_profile {
      name: "Firehawk04xx"
      level: 13
      avatar {
        skin: 2
        hair: 2
        shirt: 1
        hat: 2
        shoes: 1
        backpack: 2
      }
    }
  }
}

Nostrademous avatar Jul 29 '16 03:07 Nostrademous

@Nostrademous I was waiting for you for the same problem, but still didn't get out

ztukaz avatar Jul 29 '16 07:07 ztukaz

@tejado @ztukaz so I get it to work sometimes. The fix is modifying the StartGymBattleMessage() protobuf. the defender_id needs to be fixed64.

However it fails at time when the defender_id happens to be larger than int64 but still smaller than uint64. Python by default treats everything as signed.. but I checked your rpc_api and you use "setattr()" in the _build_sub_requests() function which is pass by reference.. so I am not sure what the issue is. However, there is a pattern for me. For IDs larger than int64 but still 8-bytes I get no reponse payload from server. This also happens for me in Incense-based Pokemon encounters.

Nostrademous avatar Jul 29 '16 14:07 Nostrademous

I'm just getting responses like: Session.py - line 407 - startGymBattle()

status_code: 3
request_id: 951201794807
returns: ""

Which are not good I guess status 3? Or I will need the request_id to perform AttackGymMessage <-> Response for the actual battle I guess Then still I think status 3 is not good

According to the responses it should be, ERROR_GYM_NEUTRAL ? The gym is not neutral! :-/

@Nostradamous where exactly do you change what in the request message ?

dnsBlah avatar Jul 29 '16 14:07 dnsBlah

@dnsBlah if you go into the /pgoapi/protos/POGOProtos_src/Networking/Requests/Messages/StartGymBattleMessage.proto

you need to change the defending_id from uint64 to fixed64

then you need to recompile the proto using "protoc" which you need to have installed of version 3.0.0b4

Nostrademous avatar Jul 29 '16 19:07 Nostrademous

Ahhh I need to do that before compiling it!? Thanks will have a look :)


awesome ;-) but its not always working, however using the same defender, so that part has nothing to do with the fixed64, it's fine I guess I am experiencing some server issues throughout the whole day already tho.

Guess its a matter of a loop now and send back the actions battle_log.battle_actions to the BattleAction proto, together with the battle_id and some BattleAction's... Not sure how to create a BattleAction fo... send an fast-attack for special

dnsBlah avatar Jul 29 '16 20:07 dnsBlah

@Nostrademous can you show some examples of running / reading forts from map_objects (i keep getting a weird error, at work now so can't supply it at this time)?

joshk6656 avatar Aug 01 '16 21:08 joshk6656

You need bigger delay's... You can only call map_objects per 5 seconds. Besides that there are hardly any pokemon shown anymore. Only within a range of 50-70 meters. Dispite the fact your scanning a bigger area

dnsBlah avatar Aug 02 '16 08:08 dnsBlah

@joshk6656 did @dnsBlah comments help you? It's hard to tell what your errors were. If you are not getting good fort data from map_objects it's probably b/c you are soft-banned for teleporting too much. Otherwise it is fairly straight forward.

Nostrademous avatar Aug 02 '16 13:08 Nostrademous