예제 #1
0
파일: bot.py 프로젝트: tivisse/yogame
class Bot(object):

    BASE_URL = 'https://en.ogame.gameforge.com/'
    LOGIN_URL = 'https://en.ogame.gameforge.com/main/login'
    HEADERS = [('User-agent', 'Mozilla/5.0 (Windows NT 6.2; WOW64)\
     AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15')]
    RE_BUILD_REQUEST = re.compile(r"sendBuildRequest\(\'(.*)\', null, 1\)")
    RE_RESEARCH_REQUEST = re.compile(r"sendBuildRequest\(\'(.*)\'")
    RE_SERVER_TIME = re.compile(
        r"var serverTime=new Date\((.*)\);var localTime")

    #ship -> ship id on the page
    SHIPS = {
        'lm': '204',
        'hm': '205',
        'cr': '206',
        'ow': '207',
        'pn': '215',
        'bb': '211',
        'ns': '213',
        'gs': '214',
        'lt': '202',
        'dt': '203',
        'cs': '208',
        'rc': '209',
        'ss': '210'
    }

    # mission ids
    MISSIONS = {
        'attack': '1',
        'transport': '3',
        'station': '4',
        'expedition': '15',
        'collect': '8'
    }

    TARGETS = {'planet': '1', 'moon': '3', 'debris': '2'}

    SPEEDS = {
        100: '10',
        90: '9',
        80: '8',
        70: '7',
        60: '6',
        50: '5',
        40: '4',
        30: '3',
        20: '2',
        10: '1'
    }

    def __init__(self, username=None, password=None, uni='69'):
        self.uni = uni
        self.username = username
        self.password = password
        self.logged_in = False

        self._prepare_logger()
        self._prepare_browser()
        farms = options['farming']['farms']
        self.farm_no = randint(0, len(farms) - 1) if farms else 0

        self.MAIN_URL = 'https://s%s-en.ogame.gameforge.com/game/index.php' % self.uni
        self.PAGES = {
            'main': self.MAIN_URL + '?page=overview',
            'resources': self.MAIN_URL + '?page=resources',
            'station': self.MAIN_URL + '?page=station',
            'research': self.MAIN_URL + '?page=research',
            'shipyard': self.MAIN_URL + '?page=shipyard',
            'defense': self.MAIN_URL + '?page=defense',
            'fleet': self.MAIN_URL + '?page=fleet1',
            'galaxy': self.MAIN_URL + '?page=galaxy',
            'galaxyCnt': self.MAIN_URL + '?page=galaxyContent',
            'events': self.MAIN_URL + '?page=eventList',
        }
        self.planets = []
        self.moons = []
        self.active_attacks = []

        self.fleet_slots = 0
        self.active_fleets = 0

        self.server_time = self.local_time = datetime.now()
        self.time_diff = 0
        self.emergency_sms_sent = False
        self.transport_manager = TransportManager()
        self.sim = Sim()

    def _get_url(self, page, planet=None):
        url = self.PAGES[page]
        if planet is not None:
            url += '&cp=%s' % planet.id
        return url

    def _prepare_logger(self):
        self.logger = logging.getLogger("mechanize")
        fh = RotatingFileHandler('bot.log', maxBytes=100000, backupCount=5)
        sh = logging.StreamHandler()
        fmt = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
                                datefmt='%m-%d, %H:%M:%S')
        fh.setFormatter(fmt)
        sh.setFormatter(fmt)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(fh)
        self.logger.addHandler(sh)

    def _prepare_browser(self):
        self.br = mechanize.Browser()
        self.br.set_handle_equiv(True)
        self.br.set_handle_redirect(True)
        self.br.set_handle_referer(True)
        self.br.set_handle_robots(False)
        self.br.addheaders = self.HEADERS

    def _parse_build_url(self, js):
        """
        convert: `sendBuildRequest('url', null, 1)`; into: `url`
        """
        return self.RE_BUILD_REQUEST.findall(js)[0]

    def _parse_research_url(self, js):
        """
        convert: `sendBuildRequest('url', null, false)`; into: `url`
        """
        return self.RE_RESEARCH_REQUEST.findall(js)[0]

    def _parse_server_time(self, content):
        return self.RE_SERVER_TIME.findall(content)[0]

    def get_mother(self):
        for p in self.planets:
            if p.mother:
                return p
        return p[0] if self.planets else None

    def get_closest_planet(self, p):
        def min_dist(p, d):
            return d

        _, d, _ = p.split(":")
        return sorted([(planet, planet.get_distance(p))
                       for planet in self.planets],
                      key=lambda x: x[1])[0][0]

    def find_planet(self, name=None, coords=None, id=None, is_moon=None):
        if is_moon:
            planets = self.moons
        else:
            planets = self.planets
        for p in planets:
            if name == p.name or coords == p.coords or id == p.id:
                return p

    def get_safe_planet(self, planet):
        '''
        Get first planet which is not under attack and isn't `planet`
        '''
        unsafe_planets = [a.planet for a in self.active_attacks]
        for p in self.planets:
            if not p in unsafe_planets and p != planet:
                return p
        # no safe planets! go to mother
        return self.planets[0]

    def login(self, username=None, password=None):
        username = username or self.username
        password = password or self.password

        try:
            resp = self.br.open(self.MAIN_URL, timeout=10)
            soup = BeautifulSoup(resp)
        except:
            return False

        alert = soup.find(id='attack_alert')

        # no redirect on main page == user logged in
        if resp.geturl() != self.BASE_URL and alert:
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True

        self.logger.info('Logging in..')
        self.br.select_form(name='loginForm')
        # self.br.form['uni'] = ['s%s-en.ogame.gameforge.com' % self.uni]
        # self.br.form['login'] = username
        # self.br.form['pass'] = password
        self.br.form.set_value_by_label(['Hyperion'], 'uni')
        self.br.form.set_value(username, 'login')
        self.br.form.set_value(password, 'pass')
        self.br.submit()

        if self.br.geturl().startswith(self.MAIN_URL):
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True
        else:
            self.logged_in = False
            self.logger.error('Login failed!')
            return False

    def calc_time(self, resp):
        try:
            y, mo, d, h, mi, sec = map(
                int,
                self._parse_server_time(resp).split(','))
        except:
            self.logger.error('Exception while calculating time')
        else:
            self.local_time = n = datetime.now()
            self.server_time = datetime(n.year, n.month, n.day, h, mi, sec)
            self.time_diff = self.server_time - self.local_time

            self.logger.info('Server time: %s, local time: %s' % \
                (self.server_time, self.local_time))

    def fetch_planets(self):
        self.logger.info('Fetching planets..')
        resp = self.br.open(self.PAGES['main']).read()

        self.calc_time(resp)

        soup = BeautifulSoup(resp)
        self.planets = []
        self.moons = []

        try:
            for i, c in enumerate(soup.findAll('a', 'planetlink')):
                name = c.find('span', 'planet-name').text
                coords = c.find('span', 'planet-koords').text[1:-1]
                url = c.get('href')
                p_id = int(c.parent.get('id').split('-')[1])
                construct_mode = len(c.parent.findAll('a',
                                                      'constructionIcon')) != 0
                p = Planet(p_id, name, coords, url, construct_mode)
                if i == 0:
                    p.mother = True
                self.planets.append(p)

                #check if planet has moon
                moon = c.parent.find('a', 'moonlink')
                if moon and 'moonlink' in moon['class']:
                    url = moon.get('href')
                    m_id = url.split('cp=')[1]
                    m = Moon(m_id, coords, url)
                    self.moons.append(m)
        except:
            self.logger.exception('Exception while fetching planets')
        else:
            self.check_attacks(soup)

    def handle_planets(self):
        self.fetch_planets()

        for p in iter(self.planets):
            self.update_planet_info(p)
            self.update_planet_research(p)
            self.update_planet_facilities(p)
            self.update_planet_fleet(p)
        for m in iter(self.moons):
            self.update_planet_info(m)
            self.update_planet_fleet(m)

    def update_planet_fleet(self, planet):
        resp = self.br.open(self._get_url('fleet', planet))
        soup = BeautifulSoup(resp)
        ships = {}
        for k, v in self.SHIPS.iteritems():
            available = 0
            try:
                s = soup.find(id='button' + v)
                available = int(
                    s.find('span', 'textlabel').nextSibling.replace('.', ''))
            except:
                available = 0
            ships[k] = available

        #self.logger.info('Updating %s fleet' % planet)
        #self.logger.info('%s' % fleet)
        planet.ships = ships

    def update_planet_info(self, planet):
        in_construction_mode = False
        resp = self.br.open(self._get_url('resources', planet))
        soup = BeautifulSoup(resp)

        try:
            metal = int(soup.find(id='resources_metal').text.replace('.', ''))
            planet.resources['metal'] = metal
            crystal = int(
                soup.find(id='resources_crystal').text.replace('.', ''))
            planet.resources['crystal'] = crystal
            deuterium = int(
                soup.find(id='resources_deuterium').text.replace('.', ''))
            planet.resources['deuterium'] = deuterium
            energy = int(
                soup.find(id='resources_energy').text.replace('.', ''))
            planet.resources['energy'] = energy
        except:
            self.logger.exception('Exception while updating resources info')
        else:
            self.logger.info('Updating resources info for %s:' % planet)
            s = 'metal - %(metal)s, crystal - %(crystal)s, deuterium - %(deuterium)s'
            self.logger.info(s % planet.resources)
        if planet.is_moon():
            return
        try:
            buildingList = soup.find(id='building')
            storageList = soup.find(id='storage')
            buildings = ('metalMine', 'crystalMine', 'deuteriumMine',
                         'solarPlant', 'fusionPlant', 'solarSatellite',
                         'metalStorage', 'crystalStorage', 'deuteriumTank')
            storages = ('metalStorage', 'crystalStorage', 'deuteriumTank')
            allBuildingList = zip(buildings, buildingList.findAll('li')) + zip(
                storages, storageList.findAll('li'))
            for building, b in allBuildingList:
                can_build = 'on' in b.get('class')
                fb = b.find('a', 'fastBuild')
                build_url = fb.get('onclick') if fb else ''
                if build_url:
                    build_url = self._parse_build_url(build_url)
                try:
                    level = int(b.find('span', 'textlabel').nextSibling)
                except AttributeError:
                    try:
                        level = int(b.find('span', 'level').text)
                    except:
                        pass
                suff_energy = planet.resources[
                    'energy'] - self.sim.upgrade_energy_cost(
                        building, level + 1) > 0
                res = dict(level=level,
                           can_build=can_build,
                           build_url=build_url,
                           sufficient_energy=suff_energy)

                planet.buildings[building] = res

            if buildingList.find('div', 'construction'):
                in_construction_mode = True
        except:
            self.logger.exception('Exception while updating buildings info')
            return False
        else:
            self.logger.info('%s buildings were updated' % planet)
        if not in_construction_mode:
            text, url = planet.get_mine_to_upgrade()
            if url:
                self.logger.info('Building upgrade on %s: %s' % (planet, text))
                self.br.open(url)
                planet.in_construction_mode = True
                #let now transport manager to clear building queue
                self.transport_manager.update_building(planet)
        else:
            self.logger.info('Building queue is not empty')
        return True

    def update_planet_research(self, planet):
        resp = self.br.open(self._get_url('research', planet))
        soup = BeautifulSoup(resp)
        try:
            ButtonList = soup.find(id='buttonz')
            AllResearchList = ButtonList.findAll('li')
            for research in AllResearchList:
                if research.get('class') == 'on':
                    fb = research.find('a', 'fastBuild')
                    if fb:
                        build_url = fb.get('onclick') if fb else ''
                        build_url = self._parse_research_url(build_url)
                        self.logger.info('Research launched on %s:%s' %
                                         (planet, fb.get('title')))
                        self.br.open(build_url)
                        break
        except:
            self.logger.exception('Exception while retrieving researches')

    def update_planet_facilities(self, planet):
        resp = self.br.open(self._get_url('station', planet))
        soup = BeautifulSoup(resp)
        try:
            ButtonList = soup.find(id='stationbuilding')
            AllResearchList = ButtonList.findAll('li')
            for research in AllResearchList:
                if research.get('class') == 'on':
                    fb = research.find('a', 'fastBuild')
                    if fb:
                        build_url = fb.get('onclick') if fb else ''
                        build_url = self._parse_research_url(build_url)
                        self.logger.info('Facility upgraded on %s:%s' %
                                         (planet, fb.get('title')))
                        self.br.open(build_url)
                        break
        except:
            self.logger.exception(
                'Exception while retrieving facilities statuses')

        return True

    def transport_resources(self):
        tasks = self.transport_manager.find_dest_planet(self.planets)
        if tasks is None:
            return False
        self.logger.info(self.transport_manager.get_summary())
        for task in iter(tasks):
            self.logger.info('Transport attempt from: %s, to: %s with resources %s' \
                % (task['from'], task['where'], task['resources']))
            result = self.send_fleet(
                task['from'],
                task['where'].coords,
                fleet=task['from'].get_fleet_for_resources(task['resources']),
                resources=task['resources'],
                mission='transport')
            if result:
                self.transport_manager.update_sent_resources(task['resources'])
                self.logger.info('Resources sent: %s, resources needed: %s' \
                    % (task['resources'], self.transport_manager.get_resources_needed()))

        return True

    def build_defense(self, planet):
        """
        Build defense for all resources on the planet
        1. plasma
        2. gauss
        3. heavy cannon
        4. light cannon
        5. rocket launcher
        """
        url = self._get_url('defense', planet)
        resp = self.br.open(url)
        for t in ('406', '404', '403', '402', '401'):
            self.br.select_form(name='form')
            self.br.form.new_control('text', 'menge', {'value': '100'})
            self.br.form.fixup()
            self.br['menge'] = '100'

            self.br.form.new_control('text', 'type', {'value': t})
            self.br.form.fixup()
            self.br['type'] = t

            self.br.form.new_control('text', 'modus', {'value': '1'})
            self.br.form.fixup()
            self.br['modus'] = '1'

            self.br.submit()

    def get_player_status(self, destination, origin_planet=None):
        if not destination:
            return

        status = {}
        origin_planet = origin_planet or self.get_closest_planet(destination)
        galaxy, system, position = destination.split(':')

        url = self._get_url('galaxyCnt', origin_planet)
        data = urlencode({'galaxy': galaxy, 'system': system})
        resp = self.br.open(url, data=data)
        soup = BeautifulSoup(resp)

        soup.find(id='galaxytable')
        planets = soup.findAll('tr', {'class': 'row'})
        target_planet = planets[int(position) - 1]
        name_el = target_planet.find('td', 'playername')
        status['name'] = name_el.find('span').text

        status['inactive'] = 'inactive' in name_el.get('class', '')
        return status

    def find_inactive_nearby(self, planet, radius=15):

        self.logger.info("Searching idlers near %s in radius %s" %
                         (planet, radius))

        nearby_systems = planet.get_nearby_systems(radius)
        idlers = []

        for system in nearby_systems:
            galaxy, system = system.split(":")
            url = self._get_url('galaxyCnt', planet)
            data = urlencode({'galaxy': galaxy, 'system': system})
            resp = self.br.open(url, data=data)
            soup = BeautifulSoup(resp)

            galaxy_el = soup.find(id='galaxytable')
            planets = galaxy_el.findAll('tr', {'class': 'row'})
            for pl in planets:
                name_el = pl.find('td', 'playername')
                debris_el = pl.find('td', 'debris')
                inactive = 'inactive' in name_el.get('class', '')
                debris_not_found = 'js_no_action' in debris_el.get('class', '')
                if not inactive or not debris_not_found:
                    continue
                position = pl.find('td', 'position').text
                coords = "%s:%s:%s" % (galaxy, system, position)
                player_id = name_el.find('a').get('rel')

                player_info = soup.find(id=player_id)
                rank_el = player_info.find('li', 'rank')

                if not rank_el:
                    continue

                rank = int(rank_el.find('a').text)
                if rank > 4000 or rank < 900:
                    continue

                idlers.append(coords)
                time.sleep(2)

        return idlers

    def find_inactives(self):

        inactives = []
        for p in self.planets:
            try:
                idlers = self.find_inactive_nearby(p)
                self.logger.info(" ".join(idlers))
                inactives.extend(idlers)
            except Exception as e:
                self.logger.exception(e)
                continue
            time.sleep(5)

        self.logger.info(" ".join(inactives))
        self.inactives = list(set(inactives))
        self.logger.info(inactives)

    def send_fleet(self,
                   origin_planet,
                   destination,
                   fleet={},
                   resources={},
                   mission='attack',
                   target='planet',
                   speed=None):
        if origin_planet.coords == destination:
            self.logger.error('Cannot send fleet to the same planet')
            return False
        self.logger.info('Sending fleet from %s to %s (%s)' \
            % (origin_planet, destination, mission))
        resp = self.br.open(self._get_url('fleet', origin_planet))
        try:
            try:
                self.br.select_form(name='shipsChosen')
            except mechanize.FormNotFoundError:
                self.logger.info('No available ships on the planet')
                return False

            soup = BeautifulSoup(resp)
            for ship, num in fleet.iteritems():
                s = soup.find(id='button' + self.SHIPS[ship])
                num = int(num)
                try:
                    available = int(
                        s.find('span',
                               'textlabel').nextSibling.replace('.', ''))
                except:
                    available = 0
                if available < num and mission in ('attack', 'expedition'):
                    self.logger.info('No available ships to send')
                    return False
                if num > 0:
                    self.br.form['am' + self.SHIPS[ship]] = str(num)

            self.br.submit()

            try:
                self.br.select_form(name='details')
            except mechanize.FormNotFoundError:
                self.logger.info('No available ships on the planet')
                return False

            galaxy, system, position = destination.split(':')
            self.br['galaxy'] = galaxy
            self.br['system'] = system
            self.br['position'] = position
            self.br.form.find_control("type").readonly = False
            self.br['type'] = self.TARGETS[target]
            self.br.form.find_control("speed").readonly = False
            if speed:
                self.br['speed'] = self.SPEEDS[speed]
            self.br.submit()

            self.br.select_form(name='sendForm')
            self.br.form.find_control("mission").readonly = False
            self.br.form['mission'] = self.MISSIONS[mission]
            if 'metal' in resources:
                self.br.form['metal'] = str(resources['metal'])
            if 'crystal' in resources:
                self.br.form['crystal'] = str(resources['crystal'])
            if 'deuterium' in resources:
                self.br.form['deuterium'] = str(resources['deuterium'])
            self.br.submit()
        except Exception as e:
            self.logger.exception(e)
            return False
        else:
            if mission == 'attack':
                self.farm_no += 1
        return True

    def send_message(self, url, player, subject, message):
        self.logger.info('Sending message to %s: %s' % (player, message))
        self.br.open(url)
        self.br.select_form(nr=0)
        self.br.form['betreff'] = subject
        self.br.form['text'] = message
        self.br.submit()

    def send_sms(self, msg):
        from smsapigateway import SMSAPIGateway
        try:
            SMSAPIGateway().send(msg)
        except Exception as e:
            self.logger.exception(str(e))

    def handle_attacks(self):
        attack_opts = options['attack']
        send_sms = bool(options['sms']['send_sms'])

        for a in self.active_attacks:
            if a.is_dangerous():
                self.logger.info('Handling attack: %s' % a)
                if not a.planet.is_moon():
                    self.build_defense(a.planet)
                if send_sms and not a.sms_sent:
                    self.send_sms(a.get_sms_text())
                    a.sms_sent = True
                if send_sms and not a.message_sent:
                    self.send_message(a.message_url, a.player,
                                      attack_opts['message_topic'],
                                      a.get_random_message())
                    a.message_sent = True
                self.fleet_save(a.planet)

    def check_attacks(self, soup):
        alert = soup.find(id='attack_alert')
        if not alert:
            self.logger.exception('Check attack failed')
            return
        if 'noAttack' in alert.get('class', ''):
            self.logger.info('No attacks')
            self.active_attacks = []
        else:
            self.logger.info('ATTACK!')
            resp = self.br.open(self.PAGES['events'])
            soup = BeautifulSoup(resp)
            hostile = False
            try:
                for tr in soup.findAll('tr'):
                    countDown = tr.find('td', 'countDown')
                    if countDown and 'hostile' in countDown.get('class', ''):
                        hostile = True
                        # First: check if attack was noticed
                        if tr.get('id'):
                            attack_id = tr.get('id').split('-')[1]
                        elif countDown.get('id'):
                            attack_id = countDown.get('id').split('-')[2]
                        if not attack_id or attack_id in [
                                a.id for a in self.active_attacks
                        ]:
                            continue
                        try:
                            # Attack first discovered: save attack info
                            arrivalTime = tr.find(
                                'td', 'arrivalTime').text.split(' ')[0]
                            coordsOrigin = tr.find('td', 'coordsOrigin')
                            if coordsOrigin:
                                if coordsOrigin.find('a'):
                                    coordsOrigin = coordsOrigin.find(
                                        'a').text.strip()[1:-1]
                            destCoords = tr.find('td', 'destCoords')
                            if destCoords:
                                destCoords = destCoords.find(
                                    'a').text.strip()[1:-1]
                            originFleet = tr.find('td', 'originFleet')
                            detailsFleet = int(
                                tr.find('td',
                                        'detailsFleet').span.text.replace(
                                            '.', ''))
                            player_info = originFleet.find('a')
                            message_url = player_info.get('href')
                            player = player_info.get('data-player-name')
                            is_moon = False  # TODO!
                            planet = self.find_planet(coords=destCoords,
                                                      is_moon=is_moon)
                            a = Attack(planet, attack_id, arrivalTime,
                                       coordsOrigin, destCoords, detailsFleet,
                                       player, message_url)

                            self.active_attacks.append(a)
                        except Exception as e:
                            self.logger.exception(e)
                            self.send_sms('ATTACKEROR')
                if not hostile:
                    self.active_attacks = []
            except Exception as e:
                self.logger.exception(e)

    def fleet_save(self, p):
        if not p.has_ships():
            return
        fleet = p.ships
        #recyclers are staying!
        #fleet['rc'] = 0
        self.logger.info('Making fleet save from %s' % p)
        self.send_fleet(p,
                        self.get_safe_planet(p).coords,
                        fleet=fleet,
                        mission='station',
                        speed=10,
                        resources={
                            'metal': p.resources['metal'] + 500,
                            'crystal': p.resources['crystal'] + 500,
                            'deuterium': p.resources['deuterium'] + 500
                        })

    def collect_debris(self, p):
        if not p.has_ships():
            return
        self.logger.info('Collecting debris from %s using %s recyclers' %
                         (p, p.ships['rc']))
        self.send_fleet(p,
                        p.coords,
                        fleet={'rc': p.ships['rc']},
                        mission='collect',
                        target='debris')

    def send_expedition(self):
        expedition = options['expedition']
        planets = expedition['planets'].split(' ')
        random.shuffle(planets)
        for coords in planets[:3]:
            planet = self.find_planet(coords=coords)
            if planet:
                galaxy, system, position = planet.coords.split(':')
                expedition_coords = '%s:%s:16' % (galaxy, system)
                self.send_fleet(planet,
                                expedition_coords,
                                fleet={
                                    expedition['ships_kind']:
                                    expedition['ships_number']
                                },
                                mission='expedition')

    def farm(self):
        farms = options['farming']['farms'].split(' ')
        ships_kind = options['farming']['ships_kind']
        ships_number = options['farming']['ships_number']

        l = len(farms)
        if l == 0 or not farms[0]:
            return
        farm = farms[self.farm_no % l]
        if not self.get_player_status(farm)['inactive']:
            self.farm_no += 1
            self.logger.error('farm %s seems not to be inactive!', farm)
            return
        self.send_fleet(self.get_closest_planet(farm),
                        farm,
                        fleet={ships_kind: ships_number})

    def sleep(self):
        sleep_options = options['general']
        sleep_time = randint(0, int(sleep_options['seed'])) + int(
            sleep_options['check_interval'])
        self.logger.info('Sleeping for %s secs' % sleep_time)
        if self.active_attacks:
            sleep_time = 60
        time.sleep(sleep_time)

    def stop(self):
        self.logger.info('Stopping bot')
        os.unlink(self.pidfile)

    def start(self):
        self.logger.info('Starting bot')
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)

        #main loop
        while True:
            if self.login():
                try:
                    self.handle_planets()
                    #self.find_inactives()
                    if not self.active_attacks:
                        if True or not self.transport_resources():
                            self.send_expedition()
                            self.farm()
                            self.farm()
                    else:
                        self.handle_attacks()

                except Exception as e:
                    self.logger.exception(e)
                    #self.stop()
                    #return
            else:
                self.logger.error('Login failed!')
                #self.stop()
                #return
            self.sleep()
예제 #2
0
파일: bot.py 프로젝트: unkvuzutop/ogame-bot
class Bot(object):

    BASE_URL = 'http://pl.ogame.gameforge.com/'
    LOGIN_URL = 'http://pl.ogame.gameforge.com/main/login'
    HEADERS = [('User-agent', 'Mozilla/5.0 (Windows NT 6.2; WOW64)\
     AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15')]
    RE_BUILD_REQUEST = re.compile(r"sendBuildRequest\(\'(.*)\', null, 1\)")
    RE_SERVER_TIME = re.compile(r"var serverTime=new Date\((.*)\);var localTime")

    #ship -> ship id on the page
    SHIPS = {
        'lm': '204',
        'hm': '205',
        'cr': '206',
        'ow': '207',
        'pn': '215',
        'bb': '211',
        'ns': '213',
        'gs': '214',
        'lt': '202',
        'dt': '203',
        'cs': '208',
        'rc': '209',
        'ss': '210'
    }

    # mission ids
    MISSIONS = {
        'attack': '1',
        'transport': '3',
        'station': '4',
        'expedition': '15',
        'collect' : '8'
    }

    TARGETS = {
        'planet' : '1',
        'moon' : '3',
        'debris' : '2'
    }

    SPEEDS = {
        100: '10',
        90: '9',
        80: '8',
        70: '7',
        60: '6',
        50: '5',
        40: '4',
        30: '3',
        20: '2',
        10: '1'
    }
    
    def __init__(self, username=None, password=None, uni='69'):
        self.uni = uni
        self.username = username
        self.password = password
        self.logged_in = False
        
        self._prepare_logger()
        self._prepare_browser()
        farms = options['farming']['farms']
        self.farm_no = randint(0, len(farms)-1) if farms else 0
        
        self.MAIN_URL = 'http://s%s-pl.ogame.gameforge.com/game/index.php' % self.uni
        self.PAGES = {
            'main':        self.MAIN_URL + '?page=overview',
            'resources':   self.MAIN_URL + '?page=resources',
            'station':     self.MAIN_URL + '?page=station',
            'research':    self.MAIN_URL + '?page=research',
            'shipyard':    self.MAIN_URL + '?page=shipyard',
            'defense':     self.MAIN_URL + '?page=defense',
            'fleet':       self.MAIN_URL + '?page=fleet1',
            'galaxy':      self.MAIN_URL + '?page=galaxy',
            'galaxyCnt':   self.MAIN_URL + '?page=galaxyContent',
            'events':      self.MAIN_URL + '?page=eventList',
        }
        self.planets = []
        self.moons = []
        self.active_attacks = []

        self.fleet_slots = 0
        self.active_fleets = 0

        self.server_time = self.local_time = datetime.now()
        self.time_diff = 0
        self.emergency_sms_sent = False
        self.transport_manager = TransportManager()
        self.sim = Sim()

    def _get_url(self, page, planet=None):
        url = self.PAGES[page]
        if planet is not None:
            url += '&cp=%s' % planet.id
        return url
        
    def _prepare_logger(self):
        self.logger = logging.getLogger("mechanize")
        fh = RotatingFileHandler('bot.log', maxBytes=100000, backupCount=5)
        sh = logging.StreamHandler()
        fmt = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
                                datefmt='%m-%d, %H:%M:%S')
        fh.setFormatter(fmt)
        sh.setFormatter(fmt)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(fh)
        self.logger.addHandler(sh)
        
    def _prepare_browser(self):
        self.br = mechanize.Browser()
        self.br.set_handle_equiv(True)
        self.br.set_handle_redirect(True)
        self.br.set_handle_referer(True)
        self.br.set_handle_robots(False)
        self.br.addheaders = self.HEADERS
        
    def _parse_build_url(self, js):
        """
        convert: `sendBuildRequest('url', null, 1)`; into: `url`
        """
        return self.RE_BUILD_REQUEST.findall(js)[0]

    def _parse_server_time(self, content):
        return self.RE_SERVER_TIME.findall(content)[0]
    
    def get_mother(self):
        for p in self.planets:
            if p.mother:
                return p
        return p[0] if self.planets else None

    def get_closest_planet(self, p):
        def min_dist(p, d):
            return d
        _, d, _ = p.split(":")
        return sorted([(planet, planet.get_distance(p)) for planet in self.planets], 
            key=lambda x: x[1])[0][0]


    def find_planet(self, name=None, coords=None, id=None, is_moon=None):
        if is_moon:
            planets = self.moons
        else:
            planets = self.planets
        for p in planets:
            if name == p.name or coords == p.coords or id == p.id:
                return p

    def get_safe_planet(self, planet):
        '''
        Get first planet which is not under attack and isn't `planet`
        '''
        unsafe_planets = [a.planet for a in self.active_attacks]
        for p in self.planets:
            if not p in unsafe_planets and p != planet:
                return p
        # no safe planets! go to mother
        return self.planets[0]

    def login(self, username=None, password=None):
        username = username or self.username
        password = password or self.password
        
        try:
            resp = self.br.open(self.MAIN_URL, timeout=10)
            soup = BeautifulSoup(resp)
        except:
            return False
        
        alert = soup.find(id='attack_alert')

        # no redirect on main page == user logged in
        if resp.geturl() != self.BASE_URL and alert:
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True
        
        self.logger.info('Logging in..')
        self.br.select_form(name='loginForm')
        self.br.form['uni'] = ['s%s-pl.ogame.gameforge.com' % self.uni]
        self.br.form['login'] = username
        self.br.form['pass'] = password
        self.br.submit()

        if self.br.geturl().startswith(self.MAIN_URL):
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True
        else:
            self.logged_in = False
            self.logger.error('Login failed!')
            return False

    def calc_time(self, resp):
        try:
            y, mo, d, h, mi, sec = map(int, self._parse_server_time(resp).split(','))
        except:
            self.logger.error('Exception while calculating time')
        else:
            self.local_time = n = datetime.now()
            self.server_time = datetime(n.year, n.month, n.day, h, mi, sec)
            self.time_diff = self.server_time - self.local_time

            self.logger.info('Server time: %s, local time: %s' % \
                (self.server_time, self.local_time))

    def fetch_planets(self):
        self.logger.info('Fetching planets..')
        resp = self.br.open(self.PAGES['main']).read()

        self.calc_time(resp)

        soup = BeautifulSoup(resp)
        self.planets = []
        self.moons = []

        try:
            for i, c in enumerate(soup.findAll('a', 'planetlink')):
                name = c.find('span', 'planet-name').text
                coords = c.find('span', 'planet-koords').text[1:-1]
                url = c.get('href')
                p_id = int(c.parent.get('id').split('-')[1])
                construct_mode = len(c.parent.findAll('a', 'constructionIcon')) != 0
                p = Planet(p_id, name, coords, url, construct_mode)
                if i == 0:
                    p.mother = True
                self.planets.append(p)

                #check if planet has moon
                moon = c.parent.find('a', 'moonlink')
                if moon and 'moonlink' in moon['class']:
                    url = moon.get('href')
                    m_id = url.split('cp=')[1]
                    m = Moon(m_id, coords, url)
                    self.moons.append(m)
        except:
            self.logger.exception('Exception while fetching planets')
        else:
            self.check_attacks(soup)

    def handle_planets(self):
        self.fetch_planets()

        for p in iter(self.planets):
            self.update_planet_info(p)
            self.update_planet_fleet(p)
        for m in iter(self.moons):
            self.update_planet_info(m)
            self.update_planet_fleet(m)

    def update_planet_fleet(self, planet):
        resp = self.br.open(self._get_url('fleet', planet))
        soup = BeautifulSoup(resp)
        ships = {}
        for k, v in self.SHIPS.iteritems():
            available = 0
            try:
                s = soup.find(id='button' + v)
                available = int(s.find('span', 'textlabel').nextSibling.replace('.', ''))
            except:
                available = 0
            ships[k] = available

        #self.logger.info('Updating %s fleet' % planet)
        #self.logger.info('%s' % fleet)
        planet.ships = ships

    def update_planet_info(self, planet):
        in_construction_mode = False
        resp = self.br.open(self._get_url('resources', planet))
        soup = BeautifulSoup(resp)

        try:
            metal = int(soup.find(id='resources_metal').text.replace('.',''))
            planet.resources['metal'] = metal
            crystal = int(soup.find(id='resources_crystal').text.replace('.',''))
            planet.resources['crystal'] = crystal
            deuterium = int(soup.find(id='resources_deuterium').text.replace('.',''))
            planet.resources['deuterium'] = deuterium
            energy = int(soup.find(id='resources_energy').text.replace('.',''))
            planet.resources['energy'] = energy
        except:
            self.logger.exception('Exception while updating resources info')
        else:
            self.logger.info('Updating resources info for %s:' % planet)
            s = 'metal - %(metal)s, crystal - %(crystal)s, deuterium - %(deuterium)s'
            self.logger.info(s % planet.resources)
        if planet.is_moon():
            return
        try:
            buildingList = soup.find(id='building')
            buildings = ('metalMine', 'crystalMine', 'deuteriumMine', 'solarPlant',
                'fusionPlant', 'solarSatellite'
            )
            for building, b in zip(buildings, buildingList.findAll('li')):
                can_build = 'on' in b.get('class')
                fb = b.find('a', 'fastBuild')
                build_url = fb.get('onclick') if fb else ''
                if build_url:
                    build_url = self._parse_build_url(build_url)
                try:
                    level = int(b.find('span', 'textlabel').nextSibling)
                except AttributeError:
                    try:
                        level = int(b.find('span', 'level').text)
                    except:
                        pass
                suff_energy = planet.resources['energy'] - self.sim.upgrade_energy_cost(building, level+1) > 0
                res = dict(
                    level=level, 
                    can_build=can_build,
                    build_url=build_url,
                    sufficient_energy=suff_energy
                )

                planet.buildings[building] = res

            if buildingList.find('div', 'construction'):
                in_construction_mode = True
        except:
            self.logger.exception('Exception while updating buildings info')
            return False
        else:
            self.logger.info('%s buildings were updated' % planet)
        if not in_construction_mode:
            text, url = planet.get_mine_to_upgrade()
            if url:
                self.logger.info('Building upgrade on %s: %s'% (planet, text))
                self.br.open(url)
                planet.in_construction_mode = True
                #let now transport manager to clear building queue
                self.transport_manager.update_building(planet)
        else:
            self.logger.info('Building queue is not empty')
        return True

    def transport_resources(self):
        tasks = self.transport_manager.find_dest_planet(self.planets)
        if tasks is None:
            return False
        self.logger.info(self.transport_manager.get_summary())
        for task in iter(tasks):
            self.logger.info('Transport attempt from: %s, to: %s with resources %s' \
                % (task['from'], task['where'], task['resources']))
            result = self.send_fleet(
                task['from'], 
                task['where'].coords, 
                fleet=task['from'].get_fleet_for_resources(task['resources']),
                resources=task['resources'],
                mission='transport'
            )
            if result:
                self.transport_manager.update_sent_resources(task['resources'])
                self.logger.info('Resources sent: %s, resources needed: %s' \
                    % (task['resources'], self.transport_manager.get_resources_needed()))

        return True

    def build_defense(self, planet):
        """
        Build defense for all resources on the planet
        1. plasma
        2. gauss
        3. heavy cannon
        4. light cannon
        5. rocket launcher
        """
        url = self._get_url('defense', planet)
        resp = self.br.open(url)
        for t in ('406', '404', '403', '402', '401'):
            self.br.select_form(name='form')
            self.br.form.new_control('text','menge',{'value':'100'})
            self.br.form.fixup()
            self.br['menge'] = '100'

            self.br.form.new_control('text','type',{'value':t})
            self.br.form.fixup()
            self.br['type'] = t

            self.br.form.new_control('text','modus',{'value':'1'})
            self.br.form.fixup()
            self.br['modus'] = '1'

            self.br.submit()

    def get_player_status(self, destination, origin_planet=None):
        if not destination:
            return
            
        status = {}
        origin_planet = origin_planet or self.get_closest_planet(destination)
        galaxy, system, position = destination.split(':')

        url = self._get_url('galaxyCnt', origin_planet)
        data = urlencode({'galaxy': galaxy, 'system': system})
        resp = self.br.open(url, data=data)
        soup = BeautifulSoup(resp)

        soup.find(id='galaxytable')
        planets = soup.findAll('tr', {'class': 'row'})
        target_planet = planets[int(position)-1]
        name_el = target_planet.find('td', 'playername')
        status['name'] = name_el.find('span').text

        status['inactive'] = 'inactive' in name_el.get('class', '')
        return status

    def find_inactive_nearby(self, planet, radius=15):

        self.logger.info("Searching idlers near %s in radius %s" 
            % (planet, radius))

        nearby_systems = planet.get_nearby_systems(radius)
        idlers = []

        for system in nearby_systems:
            galaxy, system = system.split(":")
            url = self._get_url('galaxyCnt', planet)
            data = urlencode({'galaxy': galaxy, 'system': system})
            resp = self.br.open(url, data=data)
            soup = BeautifulSoup(resp)

            galaxy_el = soup.find(id='galaxytable')
            planets = galaxy_el.findAll('tr', {'class': 'row'})
            for pl in planets:
                name_el = pl.find('td', 'playername')
                debris_el = pl.find('td', 'debris')
                inactive = 'inactive' in name_el.get('class', '')
                debris_not_found = 'js_no_action' in debris_el.get('class', '')
                if not inactive or not debris_not_found:
                    continue
                position = pl.find('td', 'position').text
                coords = "%s:%s:%s" % (galaxy, system, position)
                player_id = name_el.find('a').get('rel')

                player_info = soup.find(id=player_id)
                rank_el = player_info.find('li', 'rank')

                if not rank_el:
                    continue

                rank = int(rank_el.find('a').text)
                if rank > 4000 or rank < 900:
                    continue

                idlers.append(coords)
                time.sleep(2)

        return idlers

    def find_inactives(self):

        inactives = []
        for p in self.planets:
            try:
                idlers = self.find_inactive_nearby(p)
                self.logger.info(" ".join(idlers))
                inactives.extend(idlers)
            except Exception as e:
                self.logger.exception(e)
                continue
            time.sleep(5)

        self.logger.info(" ".join(inactives))
        self.inactives = list(set(inactives))
        self.logger.info(inactives)

    def send_fleet(self, origin_planet, destination, fleet={}, resources={},
            mission='attack', target='planet', speed=None):
        if origin_planet.coords == destination:
            self.logger.error('Cannot send fleet to the same planet')
            return False
        self.logger.info('Sending fleet from %s to %s (%s)' \
            % (origin_planet, destination, mission))
        resp = self.br.open(self._get_url('fleet', origin_planet))
        try:
            try:
                self.br.select_form(name='shipsChosen')
            except mechanize.FormNotFoundError:
                self.logger.info('No available ships on the planet')
                return False

            soup = BeautifulSoup(resp)
            for ship, num in fleet.iteritems():
                s = soup.find(id='button' + self.SHIPS[ship])
                num = int(num)
                try:
                    available = int(s.find('span', 'textlabel').nextSibling.replace('.', ''))
                except:
                    available = 0
                if available < num and mission in ('attack', 'expedition'):
                    self.logger.info('No available ships to send')
                    return False
                if num > 0:
                    self.br.form['am' + self.SHIPS[ship]] = str(num)

            self.br.submit()

            try:
                self.br.select_form(name='details')
            except mechanize.FormNotFoundError:
                self.logger.info('No available ships on the planet')
                return False

            galaxy, system, position = destination.split(':')
            self.br['galaxy'] = galaxy
            self.br['system'] = system
            self.br['position'] = position
            self.br.form.find_control("type").readonly = False
            self.br['type'] = self.TARGETS[target]
            self.br.form.find_control("speed").readonly = False
            if speed:
                self.br['speed'] = self.SPEEDS[speed]
            self.br.submit()

            self.br.select_form(name='sendForm')
            self.br.form.find_control("mission").readonly = False
            self.br.form['mission'] = self.MISSIONS[mission]
            if 'metal' in resources:
                self.br.form['metal'] = str(resources['metal'])
            if 'crystal' in resources:
                self.br.form['crystal'] = str(resources['crystal'])
            if 'deuterium' in resources:
                self.br.form['deuterium'] = str(resources['deuterium'])
            self.br.submit()
        except Exception as e:
            self.logger.exception(e)
            return False
        else:
            if mission == 'attack':
                self.farm_no += 1
        return True

    def send_message(self, url, player, subject, message):
        self.logger.info('Sending message to %s: %s' % (player, message))
        self.br.open(url)
        self.br.select_form(nr=0)
        self.br.form['betreff'] = subject
        self.br.form['text'] = message
        self.br.submit()

    def send_sms(self, msg):
        from smsapigateway import SMSAPIGateway
        try:
            SMSAPIGateway().send(msg)
        except Exception as e:
            self.logger.exception(str(e))

    def handle_attacks(self):
        attack_opts = options['attack']
        send_sms = bool(options['sms']['send_sms'])

        for a in self.active_attacks:
            if a.is_dangerous():
                self.logger.info('Handling attack: %s' % a)
                if not a.planet.is_moon():
                    self.build_defense(a.planet)
                if send_sms and not a.sms_sent:
                    self.send_sms(a.get_sms_text())
                    a.sms_sent = True
                if send_sms and not a.message_sent:
                    self.send_message(a.message_url, a.player, attack_opts['message_topic'], 
                        a.get_random_message())
                    a.message_sent = True
                self.fleet_save(a.planet)

    def check_attacks(self, soup):
        alert = soup.find(id='attack_alert')
        if not alert:
            self.logger.exception('Check attack failed')
            return
        if 'noAttack' in alert.get('class', ''):
            self.logger.info('No attacks')
            self.active_attacks = []
        else:
            self.logger.info('ATTACK!')
            resp = self.br.open(self.PAGES['events'])
            soup = BeautifulSoup(resp)
            hostile = False
            try:
                for tr in soup.findAll('tr'):
                    countDown = tr.find('td', 'countDown')
                    if countDown and 'hostile' in countDown.get('class', ''):
                        hostile = True
                        # First: check if attack was noticed
                        if tr.get('id'):
                            attack_id = tr.get('id').split('-')[1]
                        elif countDown.get('id'):
                            attack_id = countDown.get('id').split('-')[2]
                        if not attack_id or attack_id in [a.id for a in self.active_attacks]:
                            continue
                        try:
                            # Attack first discovered: save attack info
                            arrivalTime = tr.find('td', 'arrivalTime').text.split(' ')[0]
                            coordsOrigin = tr.find('td', 'coordsOrigin')
                            if coordsOrigin:
                                if coordsOrigin.find('a'):
                                    coordsOrigin = coordsOrigin.find('a').text.strip()[1:-1]
                            destCoords = tr.find('td', 'destCoords')
                            if destCoords:
                                destCoords = destCoords.find('a').text.strip()[1:-1]
                            originFleet = tr.find('td', 'originFleet')
                            detailsFleet = int(tr.find('td', 'detailsFleet').span.text.replace('.', ''))
                            player_info = originFleet.find('a')
                            message_url = player_info.get('href')
                            player = player_info.get('data-player-name')
                            is_moon = False # TODO!
                            planet = self.find_planet(coords=destCoords, is_moon=is_moon)
                            a = Attack(planet, attack_id, arrivalTime, coordsOrigin, 
                                destCoords, detailsFleet, player, message_url)

                            self.active_attacks.append(a)
                        except Exception as e:
                            self.logger.exception(e)
                            self.send_sms('ATTACKEROR')
                if not hostile:
                    self.active_attacks = []
            except Exception as e:
                self.logger.exception(e)
                
    def fleet_save(self, p):
        if not p.has_ships():
            return
        fleet = p.ships
        #recyclers are staying!
        #fleet['rc'] = 0
        self.logger.info('Making fleet save from %s' % p)
        self.send_fleet(p, 
            self.get_safe_planet(p).coords, 
            fleet=fleet, 
            mission='station',
            speed=10,
            resources={'metal': p.resources['metal']+500,
                       'crystal': p.resources['crystal']+500,
                       'deuterium': p.resources['deuterium']+500})

    def collect_debris(self, p):
        if not p.has_ships():
            return
        self.logger.info('Collecting debris from %s using %s recyclers' % (p, p.ships['rc']))
        self.send_fleet(p, 
            p.coords, 
            fleet={'rc':p.ships['rc']},
            mission='collect',
            target='debris')

    def send_expedition(self):
        expedition = options['expedition']
        planets = expedition['planets'].split(' ')
        random.shuffle(planets)
        for coords in planets[:3]:
            planet = self.find_planet(coords=coords)
            if planet:
                galaxy, system, position = planet.coords.split(':')
                expedition_coords = '%s:%s:16' % (galaxy, system)
                self.send_fleet(planet, expedition_coords,
                    fleet={expedition['ships_kind']:expedition['ships_number']}, 
                    mission='expedition')

    def farm(self):
        farms = options['farming']['farms'].split(' ')
        ships_kind = options['farming']['ships_kind']
        ships_number = options['farming']['ships_number']

        l = len(farms)
        if l == 0 or not farms[0]:
            return
        farm = farms[self.farm_no%l]
        if not self.get_player_status(farm)['inactive']:
            self.farm_no += 1
            self.logger.error('farm %s seems not to be inactive!', farm)
            return
        self.send_fleet(
            self.get_closest_planet(farm),
            farm,
            fleet={ships_kind:ships_number}
        )

    def sleep(self):
        sleep_options = options['general']
        sleep_time = randint(0, int(sleep_options['seed']))+int(sleep_options['check_interval'])
        self.logger.info('Sleeping for %s secs' % sleep_time)
        if self.active_attacks:
            sleep_time = 60
        time.sleep(sleep_time)

    def stop(self):
        self.logger.info('Stopping bot')
        os.unlink(self.pidfile)
        
    def start(self):
        self.logger.info('Starting bot')
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)
        
        #main loop
        while True:
            if self.login():
                try:
                    self.handle_planets()
                    #self.find_inactives()
                    if not self.active_attacks:
                        if True or not self.transport_resources():
                            self.send_expedition()
                            self.farm()
                            self.farm()
                    else:
                        self.handle_attacks()

                except Exception as e:
                    self.logger.exception(e)
                    #self.stop()
                    #return
            else:
                self.logger.error('Login failed!')
                #self.stop()
                #return
            self.sleep()
예제 #3
0
파일: bot.py 프로젝트: HePax/ogame-bot
class Bot(object):

    BASE_URL = 'http://labdcc.fceia.unr.edu.ar/~jgalat/'
    LOGIN_URL = 'http://labdcc.fceia.unr.edu.ar/~jgalat/'
    HEADERS = [('User-agent', 'Mozilla/5.0 (Windows NT 6.2; WOW64)\
     AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15')]
    RE_BUILD_REQUEST = re.compile(r"sendBuildRequest\(\'(.*)\', null, 1\)")
    RE_SERVER_TIME = re.compile(r"<th>Server time </th>\s*<th.*>(.*)</th>")
    RE_RESOURCE = re.compile(r"(?:<font >)?(\d+)(?:/\d*)?(?:</font>)?")
    RE_BUILD_LVL = re.compile(r"(?: \(level (\d*)\))|<br />")
    RE_IN_CONSTRUCTION = re.compile(r"\d+.: (.*) (\d+)")
    RE_SHIP_COST = re.compile(
        r"(\w*): <b style=\"color:\w*;\">(?: <t title=\"-(?:\d|\.)+\"><span class=\"noresources\">)?((?:\d|\.)+)"
    )

    # ship -> ship id on the page
    SHIPS = {
        'lm': '204',
        'hm': '205',
        'cr': '206',
        'ow': '207',
        'pn': '215',
        'bb': '211',
        'ns': '213',
        'gs': '214',
        'lt': '202',
        'dt': '203',
        'cs': '208',
        'rc': '209',
        'ss': '210'
    }

    # mission ids
    MISSIONS = {
        'Attack': '1',
        'Transport': '3',
        'Hold Position': '5',
        'Expedition': '15',
        'Collect': '8'
    }

    TARGETS = {'Planet': '1', 'Moon': '3', 'Debris': '2'}

    def __init__(self, username, password, server):
        self.username = username
        self.password = password
        self.logged_in = False

        self._prepare_logger()
        self._prepare_browser()
        farms = options['farming']['farms']
        self.farm_no = randint(0, len(farms) - 1) if farms else 0

        self.MAIN_URL = 'http://labdcc.fceia.unr.edu.ar/~jgalat/game.php'
        server = server.replace('http://', '')
        if server[-1] == '/':
            server = server[:-1]
        self.MAIN_URL = 'http://' + server + '/game.php'
        self.PAGES = {
            'main': self.MAIN_URL + '?page=overview',
            'buildings': self.MAIN_URL + '?page=buildings',
            'station': self.MAIN_URL + '?page=station',
            'research': self.MAIN_URL + '?page=buildings&mode=research',
            'shipyard': self.MAIN_URL + '?page=buildings&mode=fleet',
            'defense': self.MAIN_URL + '?page=defense',
            'fleet': self.MAIN_URL + '?page=fleet',
            'galaxy': self.MAIN_URL + '?page=galaxy',
            'galaxyCnt': self.MAIN_URL + '?page=galaxyContent',
            'events': self.MAIN_URL + '?page=eventList',
        }
        self.planets = []
        self.moons = []
        self.active_attacks = []

        self.fleet_slots = 0
        self.active_fleets = 0

        self.server_time = self.local_time = datetime.now()
        self.time_diff = 0
        self.emergency_sms_sent = False
        self.transport_manager = TransportManager()
        self.sim = Sim()

    def _get_url(self, page, planet=None):
        url = self.PAGES[page]
        if planet is not None:
            url += '&cp=%s' % planet.id
        return url

    def _prepare_logger(self):
        self.logger = logging.getLogger("mechanize")
        fh = RotatingFileHandler('bot.log', maxBytes=100000, backupCount=5)
        sh = logging.StreamHandler()
        fmt = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
                                datefmt='%m-%d, %H:%M:%S')
        fh.setFormatter(fmt)
        sh.setFormatter(fmt)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(fh)
        self.logger.addHandler(sh)
        self.logger.propagate = False

    def _prepare_browser(self):
        self.br = mechanize.Browser()
        self.br.set_handle_equiv(True)
        self.br.set_handle_redirect(True)
        self.br.set_handle_referer(True)
        self.br.set_handle_robots(False)
        self.br.addheaders = self.HEADERS

    def _parse_build_url(self, js):
        """
        convert: `sendBuildRequest('url', null, 1)`; into: `url`
        """
        return self.RE_BUILD_REQUEST.findall(js)[0]

    def _parse_server_time(self, content):
        return self.RE_SERVER_TIME.findall(content)[0]

    def get_mother(self):
        for p in self.planets:
            if p.mother:
                return p
        return p[0] if self.planets else None

    def get_closest_planet(self, p):
        def min_dist(p, d):
            return d

        _, d, _ = p.split(":")
        return sorted([(planet, planet.get_distance(p))
                       for planet in self.planets],
                      key=lambda x: x[1])[0][0]

    def find_planet(self, name=None, coords=None, id=None, is_moon=None):
        if is_moon:
            planets = self.moons
        else:
            planets = self.planets
        for p in planets:
            if name == p.name or coords == p.coords or id == p.id:
                return p

    def get_safe_planet(self, planet):
        '''
        Get first planet which is not under attack and isn't `planet`
        '''
        unsafe_planets = [a.planet for a in self.active_attacks]
        for p in self.planets:
            if not p in unsafe_planets and p != planet:
                return p
        # no safe planets! go to mother
        return self.planets[0]

    def login(self, username=None, password=None):
        username = username or self.username
        password = password or self.password

        try:
            resp = self.br.open(self.MAIN_URL, timeout=10)
            soup = BeautifulSoup(resp)
        except:
            return False

        # no redirect on main page == user logged in
        if resp.geturl() == self.MAIN_URL:
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True

        self.logger.info('Logging in..')
        self.br.select_form(nr=0)
        self.br.form['username'] = username
        self.br.form['password'] = password
        self.br.submit()
        if self.br.geturl().startswith(self.MAIN_URL):
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True
        else:
            self.logged_in = False
            self.logger.error('Login failed!')
            return False

    def calc_time(self, resp):

        try:
            self.server_time = datetime.strptime(
                str(date.today().year) + ' ' + self._parse_server_time(resp),
                "%Y %a %b %d %H:%M:%S")
        except:
            self.logger.error('Exception while calculating time')
        else:
            self.local_time = n = datetime.now()
            self.time_diff = self.server_time - self.local_time

            self.logger.info('Server time: %s, local time: %s' %
                             (self.server_time, self.local_time))

    def fetch_planets(self):
        self.logger.info('Fetching planets..')
        resp = self.br.open(self.PAGES['main']).read()
        self.calc_time(resp)
        soup = BeautifulSoup(resp)
        self.planets = []
        self.moons = []
        try:
            for i, c in enumerate(soup.find('select').findAll('option')):
                url = c['value']
                name, coords = c.contents[0].split('&nbsp;')[0:2]
                p = Planet('1', name, coords[1:-1], url, False)
                if i == 0:
                    p.mother = True
                self.planets.append(p)
                #p_id = int(c.parent.get('id').split('-')[1])
                # construct_mode = len(
                #    c.parent.findAll(
                #        'a',
                #        'constructionIcon')) != 0

                # check if planet has moon
            # moon = c.parent.find('a', 'moonlink')
            # if moon and 'moonlink' in moon['class']:
            #     url = moon.get('href')
            #     m_id = url.split('cp=')[1]
            #     m = Moon(m_id, coords, url)
            #     self.moons.append(m)
        except:
            self.logger.exception('Exception while fetching planets')
        # else:
        #    self.check_attacks(soup)

    def handle_planets(self):
        for p in iter(self.planets):
            self.upgrade_planet(p)
        self.farm()

    def load_planets(self):
        self.fetch_planets()
        for p in iter(self.planets):
            self.update_planet_shipyard(p)
            self.update_planet_info(p)
            self.update_planet_research(p)
            self.update_planet_fleet(p)
        for m in iter(self.moons):
            self.update_planet_info(m)
            self.update_planet_fleet(m)

    def upgrade_planet(self, planet):
        upds = planet.get_upgrades()
        if 'buildings' in upds:
            for name in upds['buildings']:
                self.logger.info('Building upgrade on %s: %s' % (planet, name))
                self.br.open(planet.buildings[name]['link'])
                # let now transport manager to clear building queue
                # self.transport_manager.update_building(planet)
        if 'researches' in upds:
            for name in upds['researches']:
                self.logger.info('Building research on %s: %s' %
                                 (planet, name))
                self.br.open(planet.researches[name]['link'])

        if 'fleets' in upds:
            resp = self.br.open(self._get_url('shipyard', planet))
            soup = BeautifulSoup(resp)
            planet.ships = {}
            formdata = {}
            for c in soup.findAll('td', {'class': 'l'}):
                name = c.find('a').contents[0]
                intxt = c.findNext('th')
                if not intxt:
                    continue
                intxt = intxt.find('input')
                if not intxt:
                    continue
                if name in upds['fleets']:
                    formdata[intxt['name']] = str(upds['fleets'][name])
                    self.logger.info('Building %d %s on %s' %
                                     (upds['fleets'][name], name, planet))
                else:
                    formdata[intxt['name']] = "0"
            self.br.select_form(nr=0)
            for name, value in formdata.iteritems():
                self.br.form[name] = value
            self.br.submit()

        if not upds:
            self.logger.info('Nothing to upgrade or build on %s' % planet)
        return True

    def update_planet_fleet(self, planet):
        planet.ships = {}
        try:
            resp = self.br.open(self._get_url('fleet', planet))
            soup = BeautifulSoup(resp)
            for c in soup.find('form', {
                    'action': 'game.php?page=fleet1'
            }).findAll('tr')[2:-2]:
                name = c.find('a').contents[0].strip()
                cant = int(c.find('th').findNext('th').contents[0].strip())
                planet.ships[name] = cant
        except:
            self.logger.exception('Exception while updating fleets info')
        s = ', '.join(["%s: %d" % (n, c) for n, c in planet.ships.iteritems()])
        self.logger.info('Ships on %s-> %s' % (planet, s))
        return True

    def update_planet_shipyard(self, planet):
        resp = self.br.open(self._get_url('shipyard', planet))
        soup = BeautifulSoup(resp)
        planet.buyable_fleets = {}
        for c in soup.findAll('td', {'class': 'l'}):
            name = c.find('a').contents[0]
            intxt = c.findNext('th')
            if not intxt:
                continue
            res = self.RE_SHIP_COST.findall("".join(map(str, c.contents)))
            res = {x: int(y.replace('.', '')) for x, y in res}
            planet.buyable_fleets[name] = res
        return True

    def update_planet_info(self, planet):
        resp = self.br.open(self._get_url('buildings', planet))
        soup = BeautifulSoup(resp)
        names = ['Metal', 'Crystal', 'Deuterium', 'Energy']
        try:
            for name, c in zip(names,
                               soup.find(id='resources').findAll(width='90')):
                matched = self.RE_RESOURCE.findall(
                    str(c.contents[0]).replace('.', ''))[0]
                planet.resources[name] = int(matched)
        except:
            self.logger.exception('Exception while updating resources info')
        else:
            self.logger.info('Resources in %s:' % planet)
            s = 'Metal - %(Metal)s, Crystal - %(Crystal)s, Deuterium - %(Deuterium)s'
            self.logger.info(s % planet.resources)

        if planet.is_moon():
            return
        try:
            planet.buildings = {}
            for c in soup.find('table', {
                    'width': '530'
            }).findAll('td', {'class': 'l'}):
                if len(c.attrs) == 1:
                    childs = c.findChildren()
                    if len(childs) > 2:
                        name = c.find('a').contents[0]
                        exp = self.RE_BUILD_LVL.findall(str(c.contents[2]))
                        if not exp or not exp[0]:
                            lvl = 0
                        else:
                            lvl = int(exp[0])
                        suff_energy = planet.resources[
                            'energy'] - self.sim.upgrade_energy_cost(
                                name, lvl + 1) > 0
                        canbuild = c.find('b', {'style': "color:red;"}) is None
                        c2 = c.findNext('td').find('a')
                        if c2 and canbuild:
                            link = c2['href']
                        else:
                            link = None
                        planet.buildings[name] = {
                            'link': link,
                            'level': lvl,
                            'sufficient_energy': suff_energy,
                            'in_construction': False
                        }
            for c in soup.find('table', {
                    'width': '530'
            }).findAll('td', {'class': 'l'}):
                if len(c.attrs) > 1:
                    name, lvl = self.RE_IN_CONSTRUCTION.findall(
                        str(c.contents[0]))[0]
                    planet.buildings[name]['link'] = None
                    planet.buildings[name]['level'] = max(
                        planet.buildings[name]['level'], int(lvl))
                    planet.buildings[name]['in_construction'] = True
        except:
            self.logger.exception('Exception while reloading buildings info')
            return False
        else:
            self.logger.info('%s buildings were reloaded' % planet)
        return True

    def update_planet_research(self, planet):
        resp = self.br.open(self._get_url('research', planet))
        soup = BeautifulSoup(resp)
        if planet.is_moon():
            return
        try:
            planet.researches = {}
            tabla = soup.find('table', {'width': '530'})
            if tabla is None:
                return True
            for c in tabla.findAll('td', {'class': 'l'}):
                if len(c.attrs) == 1:
                    childs = c.findChildren()
                    if len(childs) > 2:
                        name = c.find('a').contents[0]
                        exp = self.RE_BUILD_LVL.findall(str(c.contents[2]))
                        if not exp or not exp[0]:
                            lvl = 0
                        else:
                            lvl = int(exp[0])
                        canbuild = c.find('b', {'style': "color:red;"}) is None
                        c2 = c.findNext('th', {'class': 'l'}).find('a')
                        if c2 and canbuild:
                            link = c2['href']
                        else:
                            link = None
                        planet.researches[name] = {
                            'link': link,
                            'level': lvl,
                            'in_construction': False
                        }
        except:
            self.logger.exception('Exception while reloading researches info')
            return False
        else:
            self.logger.info('%s researches were reloaded' % planet)
        return True

    def transport_resources(self):
        tasks = self.transport_manager.find_dest_planet(self.planets)
        if tasks is None:
            return False
        self.logger.info(self.transport_manager.get_summary())
        for task in iter(tasks):
            self.logger.info(
                'Transport attempt from: %s, to: %s with resources %s' %
                (task['from'], task['where'], task['resources']))
            result = self.send_fleet(
                task['from'],
                task['where'].coords,
                fleet=task['from'].get_fleet_for_resources(task['resources']),
                resources=task['resources'],
                mission='transport')
            if result:
                self.transport_manager.update_sent_resources(task['resources'])
                self.logger.info(
                    'Resources sent: %s, resources needed: %s' %
                    (task['resources'],
                     self.transport_manager.get_resources_needed()))

        return True

    def build_defense(self, planet):
        """
        Build defense for all resources on the planet
        1. plasma
        2. gauss
        3. heavy cannon
        4. light cannon
        5. rocket launcher
        """
        url = self._get_url('defense', planet)
        resp = self.br.open(url)
        for t in ('406', '404', '403', '402', '401'):
            self.br.select_form(name='form')
            self.br.form.new_control('text', 'menge', {'value': '100'})
            self.br.form.fixup()
            self.br['menge'] = '100'

            self.br.form.new_control('text', 'type', {'value': t})
            self.br.form.fixup()
            self.br['type'] = t

            self.br.form.new_control('text', 'modus', {'value': '1'})
            self.br.form.fixup()
            self.br['modus'] = '1'

            self.br.submit()

    def get_player_status(self, destination, origin_planet=None):
        if not destination:
            return

        status = {}
        origin_planet = origin_planet or self.get_closest_planet(destination)
        galaxy, system, position = destination.split(':')

        url = self._get_url('galaxyCnt', origin_planet)
        data = urlencode({'galaxy': galaxy, 'system': system})
        resp = self.br.open(url, data=data)
        soup = BeautifulSoup(resp)

        soup.find(id='galaxytable')
        planets = soup.findAll('tr', {'class': 'row'})
        target_planet = planets[int(position) - 1]
        name_el = target_planet.find('td', 'playername')
        status['name'] = name_el.find('span').text

        status['inactive'] = 'inactive' in name_el.get('class', '')
        return status

    def find_inactive_nearby(self, planet, radius=15):

        self.logger.info("Searching idlers near %s in radius %s" %
                         (planet, radius))

        nearby_systems = planet.get_nearby_systems(radius)
        idlers = []

        for system in nearby_systems:
            galaxy, system = system.split(":")
            url = self._get_url('galaxyCnt', planet)
            data = urlencode({'galaxy': galaxy, 'system': system})
            resp = self.br.open(url, data=data)
            soup = BeautifulSoup(resp)

            galaxy_el = soup.find(id='galaxytable')
            planets = galaxy_el.findAll('tr', {'class': 'row'})
            for pl in planets:
                name_el = pl.find('td', 'playername')
                debris_el = pl.find('td', 'debris')
                inactive = 'inactive' in name_el.get('class', '')
                debris_not_found = 'js_no_action' in debris_el.get('class', '')
                if not inactive or not debris_not_found:
                    continue
                position = pl.find('td', 'position').text
                coords = "%s:%s:%s" % (galaxy, system, position)
                player_id = name_el.find('a').get('rel')

                player_info = soup.find(id=player_id)
                rank_el = player_info.find('li', 'rank')

                if not rank_el:
                    continue

                rank = int(rank_el.find('a').text)
                if rank > 4000 or rank < 900:
                    continue

                idlers.append(coords)
                time.sleep(2)

        return idlers

    def find_inactives(self):

        inactives = []
        for p in self.planets:
            try:
                idlers = self.find_inactive_nearby(p)
                self.logger.info(" ".join(idlers))
                inactives.extend(idlers)
            except Exception as e:
                self.logger.exception(e)
                continue
            time.sleep(5)

        self.logger.info(" ".join(inactives))
        self.inactives = list(set(inactives))
        self.logger.info(inactives)

    def send_fleet(self,
                   origin_planet,
                   destination,
                   fleet={},
                   resources={},
                   mission='Attack',
                   target='Planet',
                   speed=None,
                   holdingtime=None):
        if origin_planet.coords == destination:
            self.logger.error('Cannot send fleet to the same planet')
            return False
        self.logger.info('Sending fleet from %s to %s (%s)' %
                         (origin_planet, destination, mission))
        try:
            resp = self.br.open(self._get_url('fleet', origin_planet))
            try:
                self.br.select_form(
                    predicate=lambda f: 'action' in f.attrs and f.attrs[
                        'action'] == 'game.php?page=fleet1')
            except mechanize.FormNotFoundError:
                self.logger.info('No available ships on the planet')
                return False
            #resp=open("samples/fleet.html", "r").read()
            soup = BeautifulSoup(resp)
            sended = set()
            for c in soup.find('form', {
                    'action': 'game.php?page=fleet1'
            }).findAll('tr')[2:-2]:
                name = c.find('a').contents[0].strip()
                if name in fleet:
                    inp = c.find('input')['name'].strip()
                    sended.add(name)
                    self.br.form[inp] = str(fleet[name])

            for name in fleet.iterkeys():
                if name not in sended:
                    self.logger.info("Couldn't send all ships to mission")
                    return False
            self.br.submit()
            try:
                self.br.select_form(
                    predicate=lambda f: 'action' in f.attrs and f.attrs[
                        'action'] == 'game.php?page=fleet2')
            except mechanize.FormNotFoundError:
                self.logger.info('Error while sending ships, fleet2')
                return False

            galaxy, system, position = destination.split(':')
            self.br.form['galaxy'] = galaxy
            self.br.form['system'] = system
            self.br.form['planet'] = position
            self.br.form['planettype'] = [self.TARGETS[target]]
            if speed:
                self.br.form['speed'][0] = str(floor(int(speed) / 10))
            self.br.submit()

            try:
                self.br.select_form(
                    predicate=lambda f: 'action' in f.attrs and f.attrs[
                        'action'] == 'game.php?page=fleet3')
            except mechanize.FormNotFoundError:
                self.logger.info('Error while sendind ships, fleet 3')
                return False
            self.br.form['mission'] = [self.MISSIONS[mission]]
            if holdingtime:
                self.br.form['holdingtime'] = [str(holdingtime)]
            if 'Metal' in resources:
                self.br.form['resource1'] = str(resources['Metal'])
            if 'Crystal' in resources:
                self.br.form['resource2'] = str(resources['Crystal'])
            if 'Deuterium' in resources:
                self.br.form['resource3'] = str(resources['Deuterium'])
            self.br.submit()
            last_response = self.br.response(
            )  # This is returned by br.open(...) too
            print last_response.geturl()
            print last_response.info()
        except Exception as e:
            self.logger.exception(e)
            return False
        return True

    def send_message(self, url, player, subject, message):
        self.logger.info('Sending message to %s: %s' % (player, message))
        self.br.open(url)
        self.br.select_form(nr=0)
        self.br.form['betreff'] = subject
        self.br.form['text'] = message
        self.br.submit()

    def send_sms(self, msg):
        from smsapigateway import SMSAPIGateway
        try:
            SMSAPIGateway().send(msg)
        except Exception as e:
            self.logger.exception(str(e))

    def handle_attacks(self):
        attack_opts = options['attack']
        send_sms = bool(options['sms']['send_sms'])

        for a in self.active_attacks:
            if a.is_dangerous():
                self.logger.info('Handling attack: %s' % a)
                if not a.planet.is_moon():
                    self.build_defense(a.planet)
                if send_sms and not a.sms_sent:
                    self.send_sms(a.get_sms_text())
                    a.sms_sent = True
                if send_sms and not a.message_sent:
                    self.send_message(a.message_url, a.player,
                                      attack_opts['message_topic'],
                                      a.get_random_message())
                    a.message_sent = True
                self.fleet_save(a.planet)

    def check_attacks(self, soup):
        alert = soup.find(id='attack_alert')
        if not alert:
            self.logger.exception('Check attack failed')
            return
        if 'noAttack' in alert.get('class', ''):
            self.logger.info('No attacks')
            self.active_attacks = []
        else:
            self.logger.info('ATTACK!')
            resp = self.br.open(self.PAGES['events'])
            soup = BeautifulSoup(resp)
            hostile = False
            try:
                for tr in soup.findAll('tr'):
                    countDown = tr.find('td', 'countDown')
                    if countDown and 'hostile' in countDown.get('class', ''):
                        hostile = True
                        # First: check if attack was noticed
                        if tr.get('id'):
                            attack_id = tr.get('id').split('-')[1]
                        elif countDown.get('id'):
                            attack_id = countDown.get('id').split('-')[2]
                        if not attack_id or attack_id in [
                                a.id for a in self.active_attacks
                        ]:
                            continue
                        try:
                            # Attack first discovered: save attack info
                            arrivalTime = tr.find(
                                'td', 'arrivalTime').text.split(' ')[0]
                            coordsOrigin = tr.find('td', 'coordsOrigin')
                            if coordsOrigin:
                                if coordsOrigin.find('a'):
                                    coordsOrigin = coordsOrigin.find(
                                        'a').text.strip()[1:-1]
                            destCoords = tr.find('td', 'destCoords')
                            if destCoords:
                                destCoords = destCoords.find(
                                    'a').text.strip()[1:-1]
                            originFleet = tr.find('td', 'originFleet')
                            detailsFleet = int(
                                tr.find('td',
                                        'detailsFleet').span.text.replace(
                                            '.', ''))
                            player_info = originFleet.find('a')
                            message_url = player_info.get('href')
                            player = player_info.get('data-player-name')
                            is_moon = False  # TODO!
                            planet = self.find_planet(coords=destCoords,
                                                      is_moon=is_moon)
                            a = Attack(planet, attack_id, arrivalTime,
                                       coordsOrigin, destCoords, detailsFleet,
                                       player, message_url)

                            self.active_attacks.append(a)
                        except Exception as e:
                            self.logger.exception(e)
                            self.send_sms('ATTACKEROR')
                if not hostile:
                    self.active_attacks = []
            except Exception as e:
                self.logger.exception(e)

    def fleet_save(self, p):
        if not p.has_ships():
            return
        fleet = p.ships
        # recyclers are staying!
        #fleet['rc'] = 0
        self.logger.info('Making fleet save from %s' % p)
        self.send_fleet(p,
                        self.get_safe_planet(p).coords,
                        fleet=fleet,
                        mission='Hold Position',
                        speed=10,
                        resources={
                            'metal': p.resources['metal'] + 500,
                            'crystal': p.resources['crystal'] + 500,
                            'deuterium': p.resources['deuterium'] + 500
                        })

    def collect_debris(self, p):
        if not p.has_ships():
            return
        self.logger.info('Collecting debris from %s using %s recyclers' %
                         (p, p.ships['rc']))
        self.send_fleet(p,
                        p.coords,
                        fleet={'rc': p.ships['rc']},
                        mission='collect',
                        target='debris')

    def send_expedition(self):
        expedition = options['expedition']
        planets = expedition['planets'].split(' ')
        random.shuffle(planets)
        for coords in planets[:3]:
            planet = self.find_planet(coords=coords)
            if planet:
                galaxy, system, position = planet.coords.split(':')
                expedition_coords = '%s:%s:16' % (galaxy, system)
                self.send_fleet(planet,
                                expedition_coords,
                                fleet={
                                    expedition['ships_kind']:
                                    expedition['ships_number']
                                },
                                mission='expedition')

    def farm(self):
        if options['farming'].has_key('enabled') and not strtobool(
                options['farming']['enabled']):
            return
        farms = [
            s.strip().split(' ')[0]
            for s in options['farming']['farms'].split(',')
        ]
        if not farms or not farms[0]:
            return
        ships_kind = options['farming']['ships_kind']
        ships_number = options['farming']['ships_number']
        next_farm = int(options['farming']['next_farm']) % len(farms)

        farm = farms[next_farm]
        #if not self.get_player_status(farm)['inactive']:
        #    self.logger.error('farm %s seems not to be inactive!', farm)
        #    return
        if self.send_fleet(self.get_closest_planet(farm),
                           farm,
                           fleet={ships_kind: ships_number}):
            next_farm = (next_farm + 1) % len(farms)
            options.change_item('farming', 'next_farm', str(next_farm))

    def sleep(self):
        sleep_options = options['general']
        sleep_time = randint(0, int(sleep_options['seed'])) + int(
            sleep_options['check_interval'])
        self.logger.info('Sleeping for %s secs' % sleep_time)
        if self.active_attacks:
            sleep_time = 60
        time.sleep(sleep_time)

    def stop(self):
        self.logger.info('Stopping bot')
        os.unlink(self.pidfile)

    def interactive(self):
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)
        if not self.login():
            self.logger.error('Login failed!')
        self.load_planets()

    def start(self):
        self.logger.info('Starting bot')
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)

        # main loop
        while True:
            if self.login():
                try:
                    self.load_planets()
                    self.handle_planets()
                    # self.find_inactives()
                    # if not self.active_attacks:
                    #    if True or not self.transport_resources():
                    #        self.send_expedition()
                    #        self.farm()
                    #        self.farm()
                    # else:
                    #    self.handle_attacks()

                except Exception as e:
                    self.logger.exception(e)
                    # self.stop()
                    # return
            else:
                self.logger.error('Login failed!')
                # self.stop()
                # return
            self.sleep()
예제 #4
0
파일: bot.py 프로젝트: mvpossum/ogame-bot
class Bot(object):

    BASE_URL = 'http://labdcc.fceia.unr.edu.ar/~jgalat/'
    LOGIN_URL = 'http://labdcc.fceia.unr.edu.ar/~jgalat/'
    HEADERS = [('User-agent', 'Mozilla/5.0 (Windows NT 6.2; WOW64)\
     AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15')]
    RE_BUILD_REQUEST = re.compile(r"sendBuildRequest\(\'(.*)\', null, 1\)")
    RE_SERVER_TIME = re.compile(r"<th>Server time </th>\s*<th.*>(.*)</th>")
    RE_RESOURCE = re.compile(r"(?:<font >)?(\d+)(?:/\d*)?(?:</font>)?")
    RE_BUILD_LVL = re.compile(r"(?: \(level (\d*)\))|<br />")
    RE_IN_CONSTRUCTION = re.compile(r"\d+.: (.*) (\d+)")
    RE_SHIP_COST = re.compile(
        r"(\w*): <b style=\"color:\w*;\">(?: <t title=\"-(?:\d|\.)+\"><span class=\"noresources\">)?((?:\d|\.)+)")

    # ship -> ship id on the page
    SHIPS = {
        'lm': '204',
        'hm': '205',
        'cr': '206',
        'ow': '207',
        'pn': '215',
        'bb': '211',
        'ns': '213',
        'gs': '214',
        'lt': '202',
        'dt': '203',
        'cs': '208',
        'rc': '209',
        'ss': '210'
    }

    # mission ids
    MISSIONS = {
        'Attack': '1',
        'Transport': '3',
        'Hold Position': '5',
        'Expedition': '15',
        'Collect': '8'
    }

    TARGETS = {
        'Planet': '1',
        'Moon': '3',
        'Debris': '2'
    }

    def __init__(self, username, password, server):
        self.username = username
        self.password = password
        self.logged_in = False

        self._prepare_logger()
        self._prepare_browser()
        farms = options['farming']['farms']
        self.farm_no = randint(0, len(farms)-1) if farms else 0

        self.MAIN_URL = 'http://labdcc.fceia.unr.edu.ar/~jgalat/game.php'
        server=server.replace('http://','')
        if server[-1]=='/':
            server=server[:-1]
        self.MAIN_URL = 'http://'+server+'/game.php'
        self.PAGES = {
            'main':        self.MAIN_URL + '?page=overview',
            'buildings':   self.MAIN_URL + '?page=buildings',
            'station':     self.MAIN_URL + '?page=station',
            'research':    self.MAIN_URL + '?page=buildings&mode=research',
            'shipyard':    self.MAIN_URL + '?page=buildings&mode=fleet',
            'defense':     self.MAIN_URL + '?page=defense',
            'fleet':       self.MAIN_URL + '?page=fleet',
            'galaxy':      self.MAIN_URL + '?page=galaxy',
            'galaxyCnt':   self.MAIN_URL + '?page=galaxyContent',
            'events':      self.MAIN_URL + '?page=eventList',
        }
        self.planets = []
        self.moons = []
        self.active_attacks = []

        self.fleet_slots = 0
        self.active_fleets = 0

        self.server_time = self.local_time = datetime.now()
        self.time_diff = 0
        self.emergency_sms_sent = False
        self.transport_manager = TransportManager()
        self.sim = Sim()

    def _get_url(self, page, planet=None):
        url = self.PAGES[page]
        if planet is not None:
            url += '&cp=%s' % planet.id
        return url

    def _prepare_logger(self):
        self.logger = logging.getLogger("mechanize")
        fh = RotatingFileHandler('bot.log', maxBytes=100000, backupCount=5)
        sh = logging.StreamHandler()
        fmt = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
                                datefmt='%m-%d, %H:%M:%S')
        fh.setFormatter(fmt)
        sh.setFormatter(fmt)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(fh)
        self.logger.addHandler(sh)
        self.logger.propagate = False

    def _prepare_browser(self):
        self.br = mechanize.Browser()
        self.br.set_handle_equiv(True)
        self.br.set_handle_redirect(True)
        self.br.set_handle_referer(True)
        self.br.set_handle_robots(False)
        self.br.addheaders = self.HEADERS

    def _parse_build_url(self, js):
        """
        convert: `sendBuildRequest('url', null, 1)`; into: `url`
        """
        return self.RE_BUILD_REQUEST.findall(js)[0]

    def _parse_server_time(self, content):
        return self.RE_SERVER_TIME.findall(content)[0]

    def get_mother(self):
        for p in self.planets:
            if p.mother:
                return p
        return p[0] if self.planets else None

    def get_closest_planet(self, p):
        def min_dist(p, d):
            return d
        _, d, _ = p.split(":")
        return sorted([(planet, planet.get_distance(p))
                      for planet in self.planets], key=lambda x: x[1])[0][0]

    def find_planet(self, name=None, coords=None, id=None, is_moon=None):
        if is_moon:
            planets = self.moons
        else:
            planets = self.planets
        for p in planets:
            if name == p.name or coords == p.coords or id == p.id:
                return p

    def get_safe_planet(self, planet):
        '''
        Get first planet which is not under attack and isn't `planet`
        '''
        unsafe_planets = [a.planet for a in self.active_attacks]
        for p in self.planets:
            if not p in unsafe_planets and p != planet:
                return p
        # no safe planets! go to mother
        return self.planets[0]

    def login(self, username=None, password=None):
        username = username or self.username
        password = password or self.password

        try:
            resp = self.br.open(self.MAIN_URL, timeout=10)
            soup = BeautifulSoup(resp)
        except:
            return False

        # no redirect on main page == user logged in
        if resp.geturl() == self.MAIN_URL:
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True

        self.logger.info('Logging in..')
        self.br.select_form(nr=0)
        self.br.form['username'] = username
        self.br.form['password'] = password
        self.br.submit()
        if self.br.geturl().startswith(self.MAIN_URL):
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True
        else:
            self.logged_in = False
            self.logger.error('Login failed!')
            return False

    def calc_time(self, resp):

        try:
            self.server_time = datetime.strptime(
                str(date.today().year) + ' ' + self._parse_server_time(resp),
                "%Y %a %b %d %H:%M:%S")
        except:
            self.logger.error('Exception while calculating time')
        else:
            self.local_time = n = datetime.now()
            self.time_diff = self.server_time - self.local_time

            self.logger.info('Server time: %s, local time: %s' %
                             (self.server_time, self.local_time))

    def fetch_planets(self):
        self.logger.info('Fetching planets..')
        resp = self.br.open(self.PAGES['main']).read()
        self.calc_time(resp)
        soup = BeautifulSoup(resp)
        self.planets = []
        self.moons = []
        try:
            for i, c in enumerate(soup.find('select').findAll('option')):
                url = c['value']
                name, coords = c.contents[0].split('&nbsp;')[0:2]
                p = Planet('1', name, coords[1:-1], url, False)
                if i == 0:
                    p.mother = True
                self.planets.append(p)
                     #p_id = int(c.parent.get('id').split('-')[1])
                     # construct_mode = len(
                     #    c.parent.findAll(
                     #        'a',
                     #        'constructionIcon')) != 0

                # check if planet has moon
               # moon = c.parent.find('a', 'moonlink')
               # if moon and 'moonlink' in moon['class']:
               #     url = moon.get('href')
               #     m_id = url.split('cp=')[1]
               #     m = Moon(m_id, coords, url)
               #     self.moons.append(m)
        except:
            self.logger.exception('Exception while fetching planets')
        # else:
        #    self.check_attacks(soup)

    def handle_planets(self):
        for p in iter(self.planets):
            self.upgrade_planet(p)
        self.farm()

    def load_planets(self):
        self.fetch_planets()
        for p in iter(self.planets):
            self.update_planet_shipyard(p)
            self.update_planet_info(p)
            self.update_planet_research(p)
            self.update_planet_fleet(p)
        for m in iter(self.moons):
            self.update_planet_info(m)
            self.update_planet_fleet(m)

    def upgrade_planet(self, planet):
        upds = planet.get_upgrades()
        if 'buildings' in upds:
            for name in upds['buildings']:
                self.logger.info('Building upgrade on %s: %s' % (planet, name))
                self.br.open(planet.buildings[name]['link'])
                # let now transport manager to clear building queue
                # self.transport_manager.update_building(planet)
        if 'researches' in upds:
            for name in upds['researches']:
                self.logger.info('Building research on %s: %s' % (planet, name))
                self.br.open(planet.researches[name]['link'])

        if 'fleets' in upds:
            resp = self.br.open(self._get_url('shipyard', planet))
            soup = BeautifulSoup(resp)
            planet.ships = {}
            formdata = {}
            for c in soup.findAll('td', {'class': 'l'}):
                name = c.find('a').contents[0]
                intxt = c.findNext('th')
                if not intxt:
                    continue
                intxt = intxt.find('input')
                if not intxt:
                    continue
                if name in upds['fleets']:
                    formdata[intxt['name']] = str(upds['fleets'][name])
                    self.logger.info(
                        'Building %d %s on %s' %
                        (upds['fleets'][name], name, planet))
                else:
                    formdata[intxt['name']] = "0"
            self.br.select_form(nr=0)
            for name, value in formdata.iteritems():
                self.br.form[name] = value
            self.br.submit()

        if not upds:
            self.logger.info('Nothing to upgrade or build on %s' % planet)
        return True

    def update_planet_fleet(self, planet):
        planet.ships = {}
        try:
            resp = self.br.open(self._get_url('fleet', planet))
            soup = BeautifulSoup(resp)
            for c in soup.find('form', {'action': 'game.php?page=fleet1'}).findAll('tr')[2:-2]:
                name = c.find('a').contents[0].strip()
                cant = int(c.find('th').findNext('th').contents[0].strip())
                planet.ships[name] = cant
        except:
            self.logger.exception('Exception while updating fleets info')
        s = ', '.join(["%s: %d" % (n, c) for n, c in planet.ships.iteritems()])
        self.logger.info('Ships on %s-> %s' % (planet, s))
        return True

    def update_planet_shipyard(self, planet):
        resp = self.br.open(self._get_url('shipyard', planet))
        soup = BeautifulSoup(resp)
        planet.buyable_fleets = {}
        for c in soup.findAll('td', {'class': 'l'}):
            name = c.find('a').contents[0]
            intxt = c.findNext('th')
            if not intxt:
                continue
            res = self.RE_SHIP_COST.findall("".join(map(str, c.contents)))
            res = {x: int(y.replace('.', '')) for x, y in res}
            planet.buyable_fleets[name] = res
        return True

    def update_planet_info(self, planet):
        resp = self.br.open(self._get_url('buildings', planet))
        soup = BeautifulSoup(resp)
        names = ['Metal', 'Crystal', 'Deuterium', 'Energy']
        try:
            for name, c in zip(names, soup.find(id='resources').findAll(width='90')):
                matched = self.RE_RESOURCE.findall(
                    str(c.contents[0]).replace('.', ''))[0]
                planet.resources[name] = int(matched)
        except:
            self.logger.exception('Exception while updating resources info')
        else:
            self.logger.info('Resources in %s:' % planet)
            s = 'Metal - %(Metal)s, Crystal - %(Crystal)s, Deuterium - %(Deuterium)s'
            self.logger.info(s % planet.resources)

        if planet.is_moon():
            return
        try:
            planet.buildings = {}
            for c in soup.find('table', {'width': '530'}).findAll('td', {'class': 'l'}):
                if len(c.attrs) == 1:
                    childs = c.findChildren()
                    if len(childs) > 2:
                        name = c.find('a').contents[0]
                        exp = self.RE_BUILD_LVL.findall(str(c.contents[2]))
                        if not exp or not exp[0]:
                            lvl = 0
                        else:
                            lvl = int(exp[0])
                        suff_energy = planet.resources[
                            'energy'] - self.sim.upgrade_energy_cost(name, lvl+1) > 0
                        canbuild = c.find('b', {'style': "color:red;"}) is None
                        c2 = c.findNext('td').find('a')
                        if c2 and canbuild:
                            link = c2['href']
                        else:
                            link = None
                        planet.buildings[name] = {
                            'link': link,
                            'level': lvl,
                            'sufficient_energy': suff_energy,
                            'in_construction': False}
            for c in soup.find('table', {'width': '530'}).findAll('td', {'class': 'l'}):
                if len(c.attrs) > 1:
                    name, lvl = self.RE_IN_CONSTRUCTION.findall(
                        str(c.contents[0]))[0]
                    planet.buildings[name]['link']=None
                    planet.buildings[name]['level'] = max(
                        planet.buildings[name]['level'],
                        int(lvl))
                    planet.buildings[name]['in_construction'] = True
        except:
            self.logger.exception('Exception while reloading buildings info')
            return False
        else:
            self.logger.info('%s buildings were reloaded' % planet)
        return True

    def update_planet_research(self, planet):
        resp = self.br.open(self._get_url('research', planet))
        soup = BeautifulSoup(resp)
        if planet.is_moon():
            return
        try:
            planet.researches = {}
            tabla=soup.find('table', {'width': '530'})
            if tabla is None:
                return True
            for c in tabla.findAll('td', {'class': 'l'}):
                if len(c.attrs) == 1:
                    childs = c.findChildren()
                    if len(childs) > 2:
                        name = c.find('a').contents[0]
                        exp = self.RE_BUILD_LVL.findall(str(c.contents[2]))
                        if not exp or not exp[0]:
                            lvl = 0
                        else:
                            lvl = int(exp[0])
                        canbuild = c.find('b', {'style': "color:red;"}) is None
                        c2 = c.findNext('th', {'class': 'l'}).find('a')
                        if c2 and canbuild:
                            link = c2['href']
                        else:
                            link = None
                        planet.researches[name] = {
                            'link': link,
                            'level': lvl,
                            'in_construction': False}
        except:
            self.logger.exception('Exception while reloading researches info')
            return False
        else:
            self.logger.info('%s researches were reloaded' % planet)
        return True

    def transport_resources(self):
        tasks = self.transport_manager.find_dest_planet(self.planets)
        if tasks is None:
            return False
        self.logger.info(self.transport_manager.get_summary())
        for task in iter(tasks):
            self.logger.info(
                'Transport attempt from: %s, to: %s with resources %s' %
                (task['from'], task['where'], task['resources']))
            result = self.send_fleet(
                task['from'],
                task['where'].coords,
                fleet=task['from'].get_fleet_for_resources(task['resources']),
                resources=task['resources'],
                mission='transport'
            )
            if result:
                self.transport_manager.update_sent_resources(task['resources'])
                self.logger.info(
                    'Resources sent: %s, resources needed: %s' %
                    (task['resources'], self.transport_manager.get_resources_needed()))

        return True

    def build_defense(self, planet):
        """
        Build defense for all resources on the planet
        1. plasma
        2. gauss
        3. heavy cannon
        4. light cannon
        5. rocket launcher
        """
        url = self._get_url('defense', planet)
        resp = self.br.open(url)
        for t in ('406', '404', '403', '402', '401'):
            self.br.select_form(name='form')
            self.br.form.new_control('text', 'menge', {'value': '100'})
            self.br.form.fixup()
            self.br['menge'] = '100'

            self.br.form.new_control('text', 'type', {'value': t})
            self.br.form.fixup()
            self.br['type'] = t

            self.br.form.new_control('text', 'modus', {'value': '1'})
            self.br.form.fixup()
            self.br['modus'] = '1'

            self.br.submit()

    def get_player_status(self, destination, origin_planet=None):
        if not destination:
            return

        status = {}
        origin_planet = origin_planet or self.get_closest_planet(destination)
        galaxy, system, position = destination.split(':')

        url = self._get_url('galaxyCnt', origin_planet)
        data = urlencode({'galaxy': galaxy, 'system': system})
        resp = self.br.open(url, data=data)
        soup = BeautifulSoup(resp)

        soup.find(id='galaxytable')
        planets = soup.findAll('tr', {'class': 'row'})
        target_planet = planets[int(position)-1]
        name_el = target_planet.find('td', 'playername')
        status['name'] = name_el.find('span').text

        status['inactive'] = 'inactive' in name_el.get('class', '')
        return status

    def find_inactive_nearby(self, planet, radius=15):

        self.logger.info("Searching idlers near %s in radius %s"
                         % (planet, radius))

        nearby_systems = planet.get_nearby_systems(radius)
        idlers = []

        for system in nearby_systems:
            galaxy, system = system.split(":")
            url = self._get_url('galaxyCnt', planet)
            data = urlencode({'galaxy': galaxy, 'system': system})
            resp = self.br.open(url, data=data)
            soup = BeautifulSoup(resp)

            galaxy_el = soup.find(id='galaxytable')
            planets = galaxy_el.findAll('tr', {'class': 'row'})
            for pl in planets:
                name_el = pl.find('td', 'playername')
                debris_el = pl.find('td', 'debris')
                inactive = 'inactive' in name_el.get('class', '')
                debris_not_found = 'js_no_action' in debris_el.get('class', '')
                if not inactive or not debris_not_found:
                    continue
                position = pl.find('td', 'position').text
                coords = "%s:%s:%s" % (galaxy, system, position)
                player_id = name_el.find('a').get('rel')

                player_info = soup.find(id=player_id)
                rank_el = player_info.find('li', 'rank')

                if not rank_el:
                    continue

                rank = int(rank_el.find('a').text)
                if rank > 4000 or rank < 900:
                    continue

                idlers.append(coords)
                time.sleep(2)

        return idlers

    def find_inactives(self):

        inactives = []
        for p in self.planets:
            try:
                idlers = self.find_inactive_nearby(p)
                self.logger.info(" ".join(idlers))
                inactives.extend(idlers)
            except Exception as e:
                self.logger.exception(e)
                continue
            time.sleep(5)

        self.logger.info(" ".join(inactives))
        self.inactives = list(set(inactives))
        self.logger.info(inactives)

    def send_fleet(self, origin_planet, destination, fleet={}, resources={},
                   mission='Attack', target='Planet', speed=None, holdingtime=None):
        if origin_planet.coords == destination:
            self.logger.error('Cannot send fleet to the same planet')
            return False
        self.logger.info('Sending fleet from %s to %s (%s)'
                         % (origin_planet, destination, mission))
        try:
            resp = self.br.open(self._get_url('fleet', origin_planet))
            try:
                self.br.select_form(
                    predicate=lambda f: 'action' in f.attrs and f.attrs
                    ['action'] == 'game.php?page=fleet1')
            except mechanize.FormNotFoundError:
                self.logger.info('No available ships on the planet')
                return False
            #resp=open("samples/fleet.html", "r").read()
            soup = BeautifulSoup(resp)
            sended = set()
            for c in soup.find('form', {'action': 'game.php?page=fleet1'}).findAll('tr')[2:-2]:
                name = c.find('a').contents[0].strip()
                if name in fleet:
                    inp = c.find('input')['name'].strip()
                    sended.add(name)
                    self.br.form[inp] = str(fleet[name])

            for name in fleet.iterkeys():
                if name not in sended:
                    self.logger.info("Couldn't send all ships to mission")
                    return False
            self.br.submit()
            try:
                self.br.select_form(
                    predicate=lambda f: 'action' in f.attrs and f.attrs
                    ['action'] == 'game.php?page=fleet2')
            except mechanize.FormNotFoundError:
                self.logger.info('Error while sending ships, fleet2')
                return False

            galaxy, system, position = destination.split(':')
            self.br.form['galaxy'] = galaxy
            self.br.form['system'] = system
            self.br.form['planet'] = position
            self.br.form['planettype'] = [self.TARGETS[target]]
            if speed:
                self.br.form['speed'][0] = str(floor(int(speed)/10))
            self.br.submit()

            try:
                self.br.select_form(
                    predicate=lambda f: 'action' in f.attrs and f.attrs
                    ['action'] == 'game.php?page=fleet3')
            except mechanize.FormNotFoundError:
                self.logger.info('Error while sendind ships, fleet 3')
                return False
            self.br.form['mission'] = [self.MISSIONS[mission]]
            if holdingtime:
                self.br.form['holdingtime'] = [str(holdingtime)]
            if 'Metal' in resources:
                self.br.form['resource1'] = str(resources['Metal'])
            if 'Crystal' in resources:
                self.br.form['resource2'] = str(resources['Crystal'])
            if 'Deuterium' in resources:
                self.br.form['resource3'] = str(resources['Deuterium'])
            self.br.submit()
            last_response = self.br.response() # This is returned by br.open(...) too
            print last_response.geturl()
            print last_response.info()
        except Exception as e:
            self.logger.exception(e)
            return False
        return True

    def send_message(self, url, player, subject, message):
        self.logger.info('Sending message to %s: %s' % (player, message))
        self.br.open(url)
        self.br.select_form(nr=0)
        self.br.form['betreff'] = subject
        self.br.form['text'] = message
        self.br.submit()

    def send_sms(self, msg):
        from smsapigateway import SMSAPIGateway
        try:
            SMSAPIGateway().send(msg)
        except Exception as e:
            self.logger.exception(str(e))

    def handle_attacks(self):
        attack_opts = options['attack']
        send_sms = bool(options['sms']['send_sms'])

        for a in self.active_attacks:
            if a.is_dangerous():
                self.logger.info('Handling attack: %s' % a)
                if not a.planet.is_moon():
                    self.build_defense(a.planet)
                if send_sms and not a.sms_sent:
                    self.send_sms(a.get_sms_text())
                    a.sms_sent = True
                if send_sms and not a.message_sent:
                    self.send_message(
                        a.message_url,
                        a.player,
                        attack_opts['message_topic'],
                        a.get_random_message())
                    a.message_sent = True
                self.fleet_save(a.planet)

    def check_attacks(self, soup):
        alert = soup.find(id='attack_alert')
        if not alert:
            self.logger.exception('Check attack failed')
            return
        if 'noAttack' in alert.get('class', ''):
            self.logger.info('No attacks')
            self.active_attacks = []
        else:
            self.logger.info('ATTACK!')
            resp = self.br.open(self.PAGES['events'])
            soup = BeautifulSoup(resp)
            hostile = False
            try:
                for tr in soup.findAll('tr'):
                    countDown = tr.find('td', 'countDown')
                    if countDown and 'hostile' in countDown.get('class', ''):
                        hostile = True
                        # First: check if attack was noticed
                        if tr.get('id'):
                            attack_id = tr.get('id').split('-')[1]
                        elif countDown.get('id'):
                            attack_id = countDown.get('id').split('-')[2]
                        if not attack_id or attack_id in [a.id for a in self.active_attacks]:
                            continue
                        try:
                            # Attack first discovered: save attack info
                            arrivalTime = tr.find(
                                'td',
                                'arrivalTime').text.split(' ')[0]
                            coordsOrigin = tr.find('td', 'coordsOrigin')
                            if coordsOrigin:
                                if coordsOrigin.find('a'):
                                    coordsOrigin = coordsOrigin.find(
                                        'a').text.strip()[1:-1]
                            destCoords = tr.find('td', 'destCoords')
                            if destCoords:
                                destCoords = destCoords.find(
                                    'a').text.strip()[1:-1]
                            originFleet = tr.find('td', 'originFleet')
                            detailsFleet = int(
                                tr.find(
                                    'td',
                                    'detailsFleet').span.text.replace(
                                    '.',
                                    ''))
                            player_info = originFleet.find('a')
                            message_url = player_info.get('href')
                            player = player_info.get('data-player-name')
                            is_moon = False  # TODO!
                            planet = self.find_planet(
                                coords=destCoords,
                                is_moon=is_moon)
                            a = Attack(
                                planet,
                                attack_id,
                                arrivalTime,
                                coordsOrigin,
                                destCoords,
                                detailsFleet,
                                player,
                                message_url)

                            self.active_attacks.append(a)
                        except Exception as e:
                            self.logger.exception(e)
                            self.send_sms('ATTACKEROR')
                if not hostile:
                    self.active_attacks = []
            except Exception as e:
                self.logger.exception(e)

    def fleet_save(self, p):
        if not p.has_ships():
            return
        fleet = p.ships
        # recyclers are staying!
        #fleet['rc'] = 0
        self.logger.info('Making fleet save from %s' % p)
        self.send_fleet(p,
                        self.get_safe_planet(p).coords,
                        fleet=fleet,
                        mission='Hold Position',
                        speed=10,
                        resources={'metal': p.resources['metal']+500,
                                   'crystal': p.resources['crystal']+500,
                                   'deuterium': p.resources['deuterium']+500})

    def collect_debris(self, p):
        if not p.has_ships():
            return
        self.logger.info(
            'Collecting debris from %s using %s recyclers' %
            (p, p.ships['rc']))
        self.send_fleet(p,
                        p.coords,
                        fleet={'rc': p.ships['rc']},
                        mission='collect',
                        target='debris')

    def send_expedition(self):
        expedition = options['expedition']
        planets = expedition['planets'].split(' ')
        random.shuffle(planets)
        for coords in planets[:3]:
            planet = self.find_planet(coords=coords)
            if planet:
                galaxy, system, position = planet.coords.split(':')
                expedition_coords = '%s:%s:16' % (galaxy, system)
                self.send_fleet(
                    planet,
                    expedition_coords,
                    fleet={
                        expedition['ships_kind']: expedition['ships_number']},
                    mission='expedition')

    
    def farm(self):
        if options['farming'].has_key('enabled') and not strtobool(options['farming']['enabled']):
            return
        farms =[s.strip().split(' ')[0] for s in  options['farming']['farms'].split(',')]
        if not farms or not farms[0]:
            return
        ships_kind = options['farming']['ships_kind']
        ships_number = options['farming']['ships_number']
        next_farm = int(options['farming']['next_farm'])  % len(farms)

        farm = farms[next_farm]
        #if not self.get_player_status(farm)['inactive']:
        #    self.logger.error('farm %s seems not to be inactive!', farm)
        #    return
        if self.send_fleet(
            self.get_closest_planet(farm),
            farm,
            fleet={ships_kind: ships_number}
        ):
            next_farm = (next_farm+1) % len(farms)
            options.change_item('farming', 'next_farm', str(next_farm))


    def sleep(self):
        sleep_options = options['general']
        sleep_time = randint(
            0, int(sleep_options['seed']))+int(sleep_options['check_interval'])
        self.logger.info('Sleeping for %s secs' % sleep_time)
        if self.active_attacks:
            sleep_time = 60
        time.sleep(sleep_time)

    def stop(self):
        self.logger.info('Stopping bot')
        os.unlink(self.pidfile)

    def interactive(self):
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)
        if not self.login():
            self.logger.error('Login failed!')
        self.load_planets()

    def start(self):
        self.logger.info('Starting bot')
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)

        # main loop
        while True:
            if self.login():
                try:
                    self.load_planets()
                    self.handle_planets()
                    # self.find_inactives()
                    # if not self.active_attacks:
                    #    if True or not self.transport_resources():
                    #        self.send_expedition()
                    #        self.farm()
                    #        self.farm()
                    # else:
                    #    self.handle_attacks()

                except Exception as e:
                    self.logger.exception(e)
                    # self.stop()
                    # return
            else:
                self.logger.error('Login failed!')
                # self.stop()
                # return
            self.sleep()
예제 #5
0
class Bot(object):
    BASE_URL = 'http://pl.ogame.gameforge.com/'
    LOGIN_URL = 'http://pl.ogame.gameforge.com/main/login'
    HEADERS = [(
        'User-agent',
        'Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 		Firefox/45.0'
    )]
    RE_BUILD_REQUEST = re.compile(r"sendBuildRequest\(\'(.*)\', null, 1\)")
    RE_SERVER_TIME = re.compile(
        r"var serverTime=new Date\((.*)\);var localTime")
    LOGOUT_URL = 'https://s145-pl.ogame.gameforge.com/game/index.php?page=logout'

    def __init__(self, username=None, password=None, uni='145'):
        self.uni = uni
        self.username = username
        self.password = password
        self.logged_in = False

        self._prepare_logger()
        self._prepare_browser()

        self.MAIN_URL = 'https://s%s-pl.ogame.gameforge.com/game/index.php' % self.uni
        self.PAGES = {
            'main': self.MAIN_URL + '?page=overview',
            'resources': self.MAIN_URL + '?page=resources',
            'station': self.MAIN_URL + '?page=station',
            'research': self.MAIN_URL + '?page=research',
            'shipyard': self.MAIN_URL + '?page=shipyard',
            'defense': self.MAIN_URL + '?page=defense',
            'fleet': self.MAIN_URL + '?page=fleet1',
            'galaxy': self.MAIN_URL + '?page=galaxy',
            'galaxyCnt': self.MAIN_URL + '?page=galaxyContent',
            'events': self.MAIN_URL + '?page=eventList',
        }
        self.planets = []
        self.moons = []
        self.active_attacks = []

        self.fleet_slots = 0
        self.active_fleets = 0
        self.transport_manager = TransportManager()
        self.server_time = self.local_time = datetime.now()
        self.time_diff = 0
        self.sim = Sim()

    def _prepare_logger(self):
        self.logger = logging.getLogger("mechanize")
        fh = RotatingFileHandler('bot.log', maxBytes=100000, backupCount=5)
        sh = logging.StreamHandler()
        fmt = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
                                datefmt='%m-%d, %H:%M:%S')
        fh.setFormatter(fmt)
        sh.setFormatter(fmt)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(fh)
        self.logger.addHandler(sh)

    def _prepare_browser(self):
        self.br = mechanize.Browser()
        self.br.set_handle_equiv(True)
        self.br.set_handle_redirect(True)
        self.br.set_handle_referer(True)
        self.br.set_handle_robots(False)
        self.br.addheaders = self.HEADERS

    def login(self, username=None, password=None):
        username = username or self.username
        password = password or self.password

        try:
            resp = self.br.open(self.MAIN_URL, timeout=10)
            soup = BeautifulSoup(resp)
        except:
            return False

        alert = soup.find(id='attack_alert')

        # no redirect on main page == user logged in
        if resp.geturl() != self.BASE_URL and alert:
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True

        self.logger.info('Logging in..')
        self.br.select_form(name='loginForm')
        self.br.form['uni'] = ['s%s-pl.ogame.gameforge.com' % self.uni]
        self.br.form['login'] = username
        self.br.form['pass'] = password
        self.br.submit()
        if self.br.geturl().startswith(self.MAIN_URL):
            self.logged_in = True
            self.logger.info('Logged as: %s' % username)
            return True
        else:
            self.logged_in = False
            self.logger.error('Login failed!')
            return False

    def fetch_planets(self):
        self.logger.info('Fetching planets..')
        resp = self.br.open(self.PAGES['main']).read()

        self.calc_time(resp)

        soup = BeautifulSoup(resp)
        self.planets = []
        self.moons = []

        try:
            for i, c in enumerate(soup.findAll('a', 'planetlink')):
                name = c.find('span', 'planet-name').text
                coords = c.find('span', 'planet-koords').text[1:-1]
                url = c.get('href')
                p_id = int(c.parent.get('id').split('-')[1])
                construct_mode = len(c.parent.findAll('a',
                                                      'constructionIcon')) != 0
                p = Planet(p_id, name, coords, url, construct_mode)
                if i == 0:
                    p.mother = True
                self.planets.append(p)
                time.sleep(3)

                # check if planet has moon
                # moon = c.parent.find('a', 'moonlink')
                # if moon and 'moonlink' in moon['class']:
                #	url = moon.get('href')
                #	m_id = url.split('cp=')[1]
                #	m = Moon(m_id, coords, url)
                #	self.moons.append(m)
        except:
            self.logger.exception('Exception while fetching planets')
        else:
            self.check_attacks(soup)

    def sleep(self):
        sleep_options = options['general']
        sleep_time = randint(0, int(sleep_options['seed'])) + int(
            sleep_options['check_interval'])
        self.logger.info('Sleeping for %s secs' % sleep_time)
        if self.active_attacks:
            sleep_time = 60
        time.sleep(sleep_time)

    def handle_planets(self):
        self.fetch_planets()

        for p in iter(self.planets):
            if p.mother:
                self.update_planet_laboratory(self, p)
                time.sleep(randint(2, 8))
            self.update_planet_info(p)
            time.sleep(randint(2, 8))
            # self.update_planet_fleet(p)
            # for m in iter(self.moons):
            #	self.update_planet_info(m)
            #	self.update_planet_fleet(m)

    def update_planet_fleet(self, planet):
        resp = self.br.open(self._get_url('fleet', planet))
        soup = BeautifulSoup(resp)
        ships = {}
        for k, v in self.SHIPS.iteritems():
            available = 0
            try:
                s = soup.find(id='button' + v)
                available = int(
                    s.find('span', 'textlabel').nextSibling.replace('.', ''))
            except:
                available = 0
            ships[k] = available

        # self.logger.info('Updating %s fleet' % planet)
        # self.logger.info('%s' % fleet)
        planet.ships = ships

    def update_planet_info(self, planet):
        in_construction_mode = False
        resp = self.br.open(self._get_url('resources', planet))
        soup = BeautifulSoup(resp)
        time.sleep(randint(3, 15))

        try:
            metal = int(soup.find(id='resources_metal').text.replace('.', ''))
            planet.resources['metal'] = metal
            crystal = int(
                soup.find(id='resources_crystal').text.replace('.', ''))
            planet.resources['crystal'] = crystal
            deuterium = int(
                soup.find(id='resources_deuterium').text.replace('.', ''))
            planet.resources['deuterium'] = deuterium
            energy = int(
                soup.find(id='resources_energy').text.replace('.', ''))
            planet.resources['energy'] = energy
        except:
            self.logger.exception('Exception while updating resources info')
        else:
            self.logger.info('Updating resources info for %s:' % planet)
            s = 'metal - %(metal)s, crystal - %(crystal)s, deuterium - %(deuterium)s'
            self.logger.info(s % planet.resources)
        if planet.is_moon():
            return
        try:
            buildingList = soup.find(id='building')
            buildings = ('metalMine', 'crystalMine', 'deuteriumMine',
                         'solarPlant', 'fusionPlant', 'solarSatellite')
            for building, b in zip(buildings, buildingList.findAll('li')):
                can_build = 'on' in b.get('class')
                fb = b.find('a', 'fastBuild')
                build_url = fb.get('onclick') if fb else ''
                if build_url:
                    build_url = self._parse_build_url(build_url)
                try:
                    level = int(b.find('span', 'textlabel').nextSibling)
                except AttributeError:
                    try:
                        level = int(b.find('span', 'level').text)
                    except:
                        pass
                suff_energy = planet.resources[
                    'energy'] - self.sim.upgrade_energy_cost(
                        building, level + 1) > 0
                res = dict(level=level,
                           can_build=can_build,
                           build_url=build_url,
                           sufficient_energy=suff_energy)

                planet.buildings[building] = res
            storageList = soup.find(id='storage')
            storages = ('metalStorage', 'crystalStorage', 'deuterStorage')
            for storage, s in zip(storages, storageList.findAll('li')):
                can_build = 'on' in s.get('class')
                fb = s.find('a', 'fastBuild')
                build_url = fb.get('onclick') if fb else ''
                if build_url:
                    build_url = self._parse_build_url(build_url)
                try:
                    level = int(s.find('span', 'textlabel').nextSibling)
                    capacity = int(
                        float(
                            int(12.5 * math.exp(20 * float(level) / 33)) / 5) *
                        5 * 1000)
                    print level
                    print capacity

                except AttributeError:
                    try:
                        level = int(s.find('span', 'level').text)
                    except:
                        pass
                suff_energy = 0
                res = dict(level=level,
                           build_url=build_url,
                           can_build=can_build,
                           capacity=capacity,
                           sufficient_energy=suff_energy)
                planet.storageBuildings[storage] = res

            if buildingList.find('div', 'construction'):
                in_construction_mode = True
            if storageList.find('div', 'construction'):
                in_construction_mode = True
        except:
            self.logger.exception('Exception while updating buildings info')
            return False
        else:
            self.logger.info('%s buildings were updated' % planet)
        if not in_construction_mode:
            text, url = planet.get_mine_to_upgrade()
            if url:
                self.logger.info('Building upgrade on %s: %s' % (planet, text))
                self.br.open(url)
                planet.in_construction_mode = True
                # let now transport manager to clear building queue
                self.transport_manager.update_building(planet)
                time.sleep(randint(5, 15))
            else:
                return False
        else:
            self.logger.info('Building queue is not empty')
        return True

    def update_planet_laboratory(self, planet):
        in_research_mode = False
        resp = self.br.open(self._get_url('research', planet))
        soup = BeautifulSoup(resp)
        time.sleep(randint(3, 15))
        if planet.is_moon():
            return

        try:
            researchList = soup.find(id='base1')
            researches = ('energyTech', 'laserTech', 'ionTech', 'plazmTech',
                          'fuelDrive', 'impulsDrive', 'hyperspaceDrive',
                          'spyTech', 'computerTech', 'astroPhysic',
                          'interstellarWeb', 'hyperspaceTech', 'combatTech',
                          'defenceTech', 'armorTech')
            for research, r in zip(researches, researchList.findAll('li')):
                can_build = 'on' in r.get('class')
                fb = r.find('a', 'fastBuild')
                build_url = fb.get('onclick') if fb else ''
                if build_url:
                    build_url = self._parse_build_url(build_url)
                try:
                    level = int(r.find('span', 'textlabel').nextSibling)
                except AttributeError:
                    try:
                        level = int(r.find('span', 'level').text)
                    except:
                        pass
                res = dict(
                    level=level,
                    can_build=can_build,
                    build_url=build_url,
                )

                planet.reserches[researches] = res
            if researchList.find('div', 'construction'):
                in_research_mode = True
        except:
            self.logger.exception('Exception while updating buildings info')
            return False
        else:
            self.logger.info('%s researches were updated' % planet)
        if not in_research_mode:
            text, url = planet.get_reserch_to_upgrade()
            if url:
                self.logger.info('Research upgrade on %s: %s' % (planet, text))
                self.br.open(url)
                planet.in_research_mode = True
                # let now transport manager to clear building queue
                self.transport_manager.update_research(planet)
                time.sleep(randint(5, 15))
            else:
                return False
        else:
            self.logger.info('Building queue is not empty')
        return True

    def _parse_build_url(self, js):
        """
        convert: `sendBuildRequest('url', null, 1)`; into: `url`
        """
        return self.RE_BUILD_REQUEST.findall(js)[0]

    def calc_time(self, resp):
        try:
            y, mo, d, h, mi, sec = map(
                int,
                self._parse_server_time(resp).split(','))
        except:
            self.logger.error('Exception while calculating time')
        else:
            self.local_time = n = datetime.now()
            self.server_time = datetime(n.year, n.month, n.day, h, mi, sec)
            self.time_diff = self.server_time - self.local_time

            self.logger.info('Server time: %s, local time: %s' % \
                             (self.server_time, self.local_time))

    def check_attacks(self, soup):
        alert = soup.find(id='attack_alert')
        if not alert:
            self.logger.exception('Check attack failed')
            return
        if 'noAttack' in alert.get('class', ''):
            self.logger.info('No attacks')
            self.active_attacks = []
        else:
            self.logger.info('ATTACK!')

    def _get_url(self, page, planet=None):
        url = self.PAGES[page]
        if planet is not None:
            url += '&cp=%s' % planet.id
        return url

    def start(self):
        self.logger.info('Starting bot')
        self.pid = str(os.getpid())
        self.pidfile = 'bot.pid'
        file(self.pidfile, 'w').write(self.pid)
        while True:
            if self.login():
                try:
                    self.handle_planets()
                except Exception as e:
                    self.logger.exception(e)
                    break
            else:
                self.logger.error('Login failed!')
                break
            self.sleep()
        resp = self.br.open(self.MAIN_URL, timeout=10)
        self.logger.info('LOGOUT')
        quit()

    def exit_gracefully(self, signum, frame):
        signal.signal(signal.SIGINT, original_sigint)
        resp = self.br.open(self.MAIN_URL, timeout=10)
        print('LOGOUT with break')
        quit()