def check_file(args): file_path = args['<file>'] if os.path.isfile(file_path): return file_path Logger.error("File does not exist: %s" % file_path, sysexit=True)
def authenticate(self, login, password): """ Performs authentication :param login: BGG login :param password: BGG password """ Logger.info("Authenticating...", break_line=False) self.driver.get("%s/login" % BGG_BASE_URL) # When user is already authenticated, just skip this task # TODO Handle case where another user is logged in if self.is_authenticated(login): Logger.info(" (already logged) [done]", append=True) return True self.update_text(self.driver.find_element_by_id("login_username"), login) self.update_text(self.driver.find_element_by_id("login_password"), password) self.driver.find_element_by_xpath("//div[@class='menu_login']//input[@type='submit']")\ .click() if self.is_authenticated(login): Logger.info(" [done]", append=True) return True Logger.info(" [error]", append=True) Logger.error("Authentication failed, check your credentials!") return False
def delete(self, game_attrs): """ Delete a game in the collection :param game_attrs: Game attributes as a dictionary. Only the id will be used """ self.goto(game_attrs) #<button type="button" uib-tooltip="More options" tooltip-popup-delay="500" tooltip-append-to-body="true" tooltip-placement="left" class="btn btn-empty text-muted dropdown-toggle" uib-dropdown-toggle="" aria-haspopup="true" aria-expanded="false"> <span class="glyphicon glyphicon-option-vertical"></span> </button> try: if self.notincollection(): Logger.info(" (not in collection)", append=True, break_line=False) return # Not in collection self.openeditform() more = self.driver.find_element_by_xpath( "//button[@uib-tooltip='More options']'") more.click() #<button type="button" class="btn btn-empty" ng-click="editctrl.deleteItem(editctrl.editdata.item)"> Delete from Collection </button> del_button = self.driver.find_element_by_xpath( '//button[ng-click="editctrl.deleteItem(editctrl.editdata.item)"]' ) del_button.click() except NoSuchElementException: Logger.info(" Failed: can't find delete button!", append=True, break_line=False) return
def create_ci_driver(self): # See http://docs.travis-ci.com/user/gui-and-headless-browsers/ capabilities = DesiredCapabilities.FIREFOX.copy() if os.environ.get('TRAVIS') == 'true': Logger.info("Configure Travis for Sauce Labs") capabilities.update( { 'tunnel-identifier': os.environ["TRAVIS_JOB_NUMBER"], 'build': os.environ["TRAVIS_JOB_NUMBER"], 'tags': [os.environ["TRAVIS_PYTHON_VERSION"], "CI"] } ) sauce_url = "http://%s:%s@localhost:4445/wd/hub" %\ ((os.environ["SAUCE_USERNAME"]), (os.environ["SAUCE_ACCESS_KEY"])) else: Logger.info("Configure direct usage of Sauce Labs") sauce_url = "http://%s:%[email protected]:80/wd/hub" %\ ((os.environ["SAUCE_USERNAME"]), (os.environ["SAUCE_ACCESS_KEY"])) capabilities.update( { 'name': 'bggcli-%s' % self.name } ) result = webdriver.Remote(desired_capabilities=capabilities, command_executor=sauce_url) result.implicitly_wait(20) return result
def show_duration(timer_start): m, s = divmod(time.time() - timer_start, 60) h, m = divmod(m, 60) if h > 0: Logger.info("(took %d:%02d:%02d)" % (h, m, s)) else: Logger.info("(took %02d:%02d)" % (m, s))
def create_ci_driver(self): # See http://docs.travis-ci.com/user/gui-and-headless-browsers/ capabilities = DesiredCapabilities.FIREFOX.copy() if os.environ.get('TRAVIS') == 'true': Logger.info("Configure Travis for Sauce Labs") capabilities.update({ 'tunnel-identifier': os.environ["TRAVIS_JOB_NUMBER"], 'build': os.environ["TRAVIS_JOB_NUMBER"], 'tags': [os.environ["TRAVIS_PYTHON_VERSION"], "CI"] }) sauce_url = "http://%s:%s@localhost:4445/wd/hub" %\ ((os.environ["SAUCE_USERNAME"]), (os.environ["SAUCE_ACCESS_KEY"])) else: Logger.info("Configure direct usage of Sauce Labs") sauce_url = "http://%s:%[email protected]:80/wd/hub" %\ ((os.environ["SAUCE_USERNAME"]), (os.environ["SAUCE_ACCESS_KEY"])) capabilities.update({'name': 'bggcli-%s' % self.name}) result = webdriver.Remote(desired_capabilities=capabilities, command_executor=sauce_url) result.implicitly_wait(20) return result
def default_export(req): response = urllib2.urlopen(req) if response.code == 202: Logger.info('Export is queued, will retry in %ss' % EXPORT_QUERY_INTERVAL) time.sleep(EXPORT_QUERY_INTERVAL) return default_export(req) if response.code == 200: return response # Write response in a text file otherwise try: with open(ERROR_FILE_PATH, "wb") as error_file: error_file.write(response.read()) Logger.error("Unexpected response, content has been written in %s" % ERROR_FILE_PATH) except Exception as e: raise Exception( 'Unexpected HTTP response for export request, and cannot write ' 'response content in %s: %s' % (ERROR_FILE_PATH, e)) raise Exception( 'Unexpected HTTP response for export request, response content written in ' '%s' % ERROR_FILE_PATH)
def check_file(args): file_path = args["<file>"] if os.path.isfile(file_path): return file_path Logger.error("File does not exist: %s" % file_path, sysexit=True)
def update_collid(opener, collid, fieldname, values): values.update({'ajax': 1, 'action': 'savedata', 'collid': collid, 'fieldname': fieldname}) values = { k:unicode(v).encode('utf-8') for k,v in values.iteritems() } response = opener.open(BGG_BASE_URL + '/geekcollection.php', urlencode(values)) if response.code != 200: Logger.error("Failed to update 'collid'=%s!" % collid, sysexit=True)
def parse_commad_args(command_module, argv): result = docopt(command_module.__doc__, argv, version='bggcli %s' % VERSION, options_first=False) try: return result, explode_dyn_args(result['-c']) except StandardError: Logger.info('Invalid syntax for -c option, should be "-c key=value"!') return None
def __exit__(self, type, value, traceback): if not self.browser_keep: try: self.driver.quit() Logger.verbose("Close webdriver '%s'" % self.name) except WebDriverException as e: Logger.info("Encountered error when closing webdriver '%s' (will be skipped): %s" % (self.name, repr(e))) return type is None
def check(self): if 'objectid' not in self.reader.fieldnames: Logger.error("Cannot process the CSV file, it should contain at least a column named " "'objectid'! Provided columns: %s" % self.reader.fieldnames, sysexit=True) return unknown_fields = set(self.reader.fieldnames) - set(BGG_SUPPORTED_FIELDS) if unknown_fields: Logger.info('Some fields are not supported in your CSV file, they will be skipped: %s' % unknown_fields)
def show_help(command_args): if len(command_args) > 0: help_command = command_args[0] try: command_module = import_command_module(help_command) Logger.info(docopt(command_module.__doc__, ['-h'])) except ImportError: exit_unknown_command(help_command) else: Logger.info(docopt(__doc__, argv=['-h']))
def __exit__(self, type, value, traceback): if not self.browser_keep: try: self.driver.quit() Logger.verbose("Close webdriver '%s'" % self.name) except WebDriverException as e: Logger.info( "Encountered error when closing webdriver '%s' (will be skipped): %s" % (self.name, repr(e))) return type is None
def notincollection(self): """Either returns button or False, suitable for if statement""" try: Logger.info("edit button? ", append=True, break_line=False) button1 = self.driver.find_element_by_xpath( "(//button[contains(@ng-click, 'colltoolbarctrl.editItem')])[last()]" ) return button1 except NoSuchElementException: return False
def update(self, game_attrs): """ Update game details :param game_attrs: Game attributes as a dictionary """ self.goto(game_attrs) try: self.itemEl = self.driver.find_element_by_xpath( "//table[@class='collectionmodule_table']") Logger.info(" (already in collection)", append=True, break_line=False) except NoSuchElementException: self.driver.find_element_by_xpath( "(//a[contains(@onclick, 'CE_ModuleAddItem')])[last()]").click( ) self.itemEl = self.wait.until( EC.element_to_be_clickable( (By.XPATH, "//table[@class='collectionmodule_table']"))) # Fill all provided values using dynamic invocations 'fill_[fieldname]' for key in game_attrs: if key in BGG_SUPPORTED_FIELDS: value = game_attrs[key] if value is not None: getattr(self, "fill_%s" % key)(value) # Save "Private Info" popup if opened try: self.privateInfoPopupEl.find_element_by_xpath( ".//input[@type='submit']").click() except WebDriverException: pass else: self.wait.until( EC.element_to_be_clickable( (By.XPATH, ".//td[@class='collection_ownershipmod editfield']"))) # Save "Version" popup if opened try: self.versionPopupEl.find_element_by_xpath( ".//input[@type='submit']").click() except WebDriverException: pass else: self.wait.until( EC.element_to_be_clickable( (By.XPATH, ".//td[@class='collection_versionmod editfield']")))
def execute(args, options): login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() game_count = csv_reader.rowCount if not args['--force']: sys.stdout.write( "You are about to delete %s games in you collection (%s), " "please enter the number of games displayed here to confirm you want to continue: " % (game_count, login)) if raw_input() != game_count.__str__(): Logger.error('Operation canceled, number does not match (should be %s).' % game_count, sysexit=True) return Logger.info("Deleting games for '%s' account..." % login) with WebDriver('collection-delete', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate(login, args['--password']): sys.exit(1) Logger.info("Deleting %s games..." % game_count) game_page = GamePage(web_driver.driver) csv_reader.iterate(lambda row: game_page.delete(row)) Logger.info("Deletion has finished.")
def execute(args, options): login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() game_count = csv_reader.rowCount if not args['--force']: sys.stdout.write( "You are about to delete %s games in you collection (%s), " "please enter the number of games displayed here to confirm you want to continue: " % (game_count, login)) if raw_input() != game_count.__str__(): Logger.error( 'Operation canceled, number does not match (should be %s).' % game_count, sysexit=True) return Logger.info("Deleting games for '%s' account..." % login) with WebDriver('collection-delete', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate( login, args['--password']): sys.exit(1) Logger.info("Deleting %s games..." % game_count) game_page = GamePage(web_driver.driver) csv_reader.iterate(lambda row: game_page.delete(row)) Logger.info("Deletion has finished.")
def check(self): if 'objectid' not in self.reader.fieldnames: Logger.error( "Cannot process the CSV file, it should contain at least a column named " "'objectid'! Provided columns: %s" % self.reader.fieldnames, sysexit=True) return unknown_fields = set( self.reader.fieldnames) - set(BGG_SUPPORTED_FIELDS) if unknown_fields: Logger.info( 'Some fields are not supported in your CSV file, they will be skipped: %s' % unknown_fields)
def game_deleter(opener, row): collid = row['collid'] if not collid: return response = opener.open( BGG_BASE_URL + '/geekcollection.php', urlencode({ 'ajax': 1, 'action': 'delete', 'collid': collid })) if response.code != 200: Logger.error("Failed to delete 'collid'=%s!" % collid, sysexit=True)
def game_importer(opener, row, force_new=False): collid = row['collid'] if force_new or not collid: objectid = row['objectid'] if not objectid.isdigit(): Logger.error("Invalid 'objectid'=%s!" % objectid, sysexit=True) collid = create_collid(opener, objectid) values = { k:v for k,v in row.iteritems() if k in [ 'own', 'prevowned', 'fortrade', 'want', 'wanttobuy', 'wishlist', 'wishlistpriority', 'wanttoplay', 'preordered'] } if len(values): update_collid(opener, collid, 'status', values) values = { k:v for k,v in row.iteritems() if k in [ 'pp_currency', 'pricepaid', 'cv_currency', 'currvalue', 'quantity', 'acquisitiondate', 'acquiredfrom', 'privatecomment', 'invdate', 'invlocation'] } if len(values): update_collid(opener, collid, 'ownership', values) if '_versionid' in row.keys() and row['_versionid'].isdigit(): update_collid(opener, collid, 'version', {'geekitem_version': 1, 'objectid': row['_versionid']}) elif 'geekitem_version' in row.keys() and 'objectid' in row.keys() and \ row['geekitem_version'].isdigit() and int(row['geekitem_version']) == 1: update_collid(opener, collid, 'version', {'geekitem_version': 1, 'objectid': row['objectid']}) else: values = { k:v for k,v in row.iteritems() if k in [ 'imageid', 'publisherid', 'languageid', 'year', 'other', 'barcode'] } if len(values): update_collid(opener, collid, 'version', values) if 'objectname' in row.keys(): update_collid(opener, collid, 'objectname', {'value': row['objectname']}) if 'rating' in row.keys(): update_collid(opener, collid, 'rating', {'rating': row['rating']}) if 'weight' in row.keys(): update_collid(opener, collid, 'weight', {'weight': row['weight']}) if 'comment' in row.keys(): update_collid(opener, collid, 'comment', {'value': row['comment']}) if 'conditiontext' in row.keys(): update_collid(opener, collid, 'conditiontext', {'value': row['conditiontext']}) if 'wantpartslist' in row.keys(): update_collid(opener, collid, 'wantpartslist', {'value': row['wantpartslist']}) if 'haspartslist' in row.keys(): update_collid(opener, collid, 'haspartslist', {'value': row['haspartslist']}) if 'wishlistcomment' in row.keys(): update_collid(opener, collid, 'wishlistcomment', {'value': row['wishlistcomment']})
def update_simple(self, game_attrs): """ Added by bbever (07/27/15) Update game details using simple mode. Wont update details, simply adds item to collection. :param game_attrs: Game attributes as a dictionary """ self.goto(game_attrs) try: self.itemEl = self.driver.find_element_by_xpath( "//table[@class='collectionmodule_table']") Logger.info(" (already in collection)", append=True, break_line=False) except NoSuchElementException: self.driver.get("javascript:void(CE_ModuleAddItem({objecttype:'thing',objectid:'%s',addowned:'true',instanceid:'21'}))" % (game_attrs['objectid']))
def execute_command(command, argv): timer_start = time.time() try: command_module = import_command_module(command) command_args, command_args_options = parse_commad_args(command_module, argv) if command_args: command_module.execute(command_args, command_args_options) show_duration(timer_start) except ImportError: exit_unknown_command(command) except WebDriverException as e: Logger.error(UI_ERROR_MSG, e) except Exception as e: Logger.error("Encountered an unexpected error, please report the issue to the author", e)
def create_collid(opener, objectid): response = opener.open(BGG_BASE_URL + '/geekcollection.php', urlencode({ 'ajax': 1, 'action': 'additem', 'force': 'true', 'objecttype': 'thing', 'objectid': objectid})) if response.code != 200: Logger.error("Failed to create item of 'objectid'=%s!" % objectid, sysexit=True) # There seems to be no straightforward way to get the collid of the item just created. # To work around this we fetch a list of all items of this objectid and scrape it to # find the largest collid. This might fail if the collection is concurrently modified. response = opener.open(BGG_BASE_URL + '/geekcollection.php?' + urlencode({ 'ajax': 1, 'action': 'module', 'objecttype': 'thing', 'objectid': objectid})) return max(int(m.group(1)) for m in re.finditer( r"(?i)<input\s+type='hidden'\s+name='collid'\s+value='(\d+)'[^>]*>", response.read()))
def parse_commad_args(command_module, argv): #print('dir',dir(command_module)) #print(command_module.__doc__,argv,VERSION) #try: result = docopt(command_module.__doc__, argv, version='bggcli %s' % VERSION, options_first=False) # except DocoptExit: # traceback.print_exc() # #print('hi!') # raise #print('result=',result) try: return result, explode_dyn_args(result['-c']) except StandardError: Logger.info('Invalid syntax for -c option, should be "-c key=value"!') return None
def delete(self, game_attrs): """ Delete a game in the collection :param game_attrs: Game attributes as a dictionary. Only the id will be used """ self.goto(game_attrs) try: del_button = self.driver.find_element_by_xpath("//a[contains(@onclick, " "'CE_ModuleDeleteItem')]") except NoSuchElementException: Logger.info(" (not in collection)", append=True, break_line=False) return del_button.click() # Confirm alert message self.wait_and_accept_alert()
def delete(self, game_attrs): """ Delete a game in the collection :param game_attrs: Game attributes as a dictionary. Only the id will be used """ self.goto(game_attrs) try: del_button = self.driver.find_element_by_xpath( "//a[contains(@onclick, " "'CE_ModuleDeleteItem')]") except NoSuchElementException: Logger.info(" (not in collection)", append=True, break_line=False) return del_button.click() # Confirm alert message self.wait_and_accept_alert()
def execute_command(command, argv): timer_start = time.time() try: command_module = import_command_module(command) ## command_args, command_args_options = parse_commad_args(command_module, argv) command_args = parse_commad_args(command_module, argv) if command_args: ## command_module.execute(command_args, command_args_options) command_module.execute(command_args) show_duration(timer_start) except ImportError: exit_unknown_command(command) ## except WebDriverException as e: except URLError as e: Logger.error(UI_ERROR_MSG, e) except Exception as e: Logger.error("Encountered an unexpected error, please report the issue to the author", e)
def update(self, game_attrs): """ Update game details :param game_attrs: Game attributes as a dictionary """ self.goto(game_attrs) try: self.itemEl = self.driver.find_element_by_xpath( "//table[@class='collectionmodule_table']") Logger.info(" (already in collection)", append=True, break_line=False) except NoSuchElementException: self.driver.find_element_by_xpath( "(//a[contains(@onclick, 'CE_ModuleAddItem')])[last()]").click() self.itemEl = self.wait.until(EC.element_to_be_clickable( (By.XPATH, "//table[@class='collectionmodule_table']"))) # Fill all provided values using dynamic invocations 'fill_[fieldname]' for key in game_attrs: if key in BGG_SUPPORTED_FIELDS: value = game_attrs[key] if value is not None: getattr(self, "fill_%s" % key)(value) # Save "Private Info" popup if opened try: self.privateInfoPopupEl.find_element_by_xpath(".//input[@type='submit']").click() except WebDriverException: pass else: self.wait.until(EC.element_to_be_clickable( (By.XPATH, ".//td[@class='collection_ownershipmod editfield']"))) # Save "Version" popup if opened try: self.versionPopupEl.find_element_by_xpath(".//input[@type='submit']").click() except WebDriverException: pass else: self.wait.until(EC.element_to_be_clickable( (By.XPATH, ".//td[@class='collection_versionmod editfield']")))
def default_export(req): response = urllib2.urlopen(req) if response.code == 202: Logger.info('Export is queued, will retry in %ss' % EXPORT_QUERY_INTERVAL) time.sleep(EXPORT_QUERY_INTERVAL) return default_export(req) if response.code == 200: return response # Write response in a text file otherwise try: with open(ERROR_FILE_PATH, "wb") as error_file: error_file.write(response.read()) Logger.error("Unexpected response, content has been written in %s" % ERROR_FILE_PATH) except Exception as e: raise Exception('Unexpected HTTP response for export request, and cannot write ' 'response content in %s: %s' % (ERROR_FILE_PATH, e)) raise Exception('Unexpected HTTP response for export request, response content written in ' '%s' % ERROR_FILE_PATH)
def execute(args, options): login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() Logger.info("Importing games for '%s' account..." % login) with WebDriver('collection-import', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate(login, args['--password']): sys.exit(1) Logger.info("Importing %s games..." % csv_reader.rowCount) game_page = GamePage(web_driver.driver) csv_reader.iterate(lambda row: game_page.update(row)) Logger.info("Import has finished.")
def execute(args, options): login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() Logger.info("Importing games for '%s' account..." % login) with WebDriver('collection-import', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate( login, args['--password']): sys.exit(1) Logger.info("Importing %s games..." % csv_reader.rowCount) game_page = GamePage(web_driver.driver) csv_reader.iterate(lambda row: game_page.update(row)) Logger.info("Import has finished.")
def exit_error(msg, args): Logger.error(msg) Logger.error(docopt(__doc__, args)) exit(1)
def execute(args, options): print('Executing!') login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() rows = [] # try: Logger.info("Parsing input file '{}'...".format(file_path)) csv_reader.iterate(lambda row: rows.append(row)) #Logger.info("Found %s games to put in collection..." % csv_reader.rowCount) rows.reverse() firstrow = rows[0] loop = 0 Logger.info("Importing {} games to collection of '{}' ...".format( csv_reader.rowCount, login)) while rows: try: with WebDriver('collection-import', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate( login, args['--password']): sys.exit(1) #input("Kill Firefox, then Press Enter to continue...") game_page = GamePage(web_driver.driver) while rows: row = rows.pop() if firstrow is None or firstrow == row: loop += 1 if loop >= LOOPLIMIT: Logger.info( "Loop limit of {} reached.".format(loop)) return Logger.info('Loop {} (maximum {})'.format( loop, LOOPLIMIT)) if rows: firstrow = rows[0] Logger.info('First assigned {}'.format( firstrow['objectname'])) else: firstrow = None Logger.info('First assigned None') Logger.info('(BGGID {}) Name: {} ({} game left)'.format( row['objectid'], row['objectname'], len(rows) + 1)) try: val = game_page.update(row) Logger.info('update returned {}'.format(val)) if val: #Logger.info('Updated (BGGID {0}) "{1}"'.format(row['objectid'],row['objectname'])) Logger.info('(BGGID {}) Name: {} UPDATED!'.format( row['objectid'], row['objectname'], len(rows))) # ({} game left) else: rows.insert(0, row) Logger.info( 'returned False??, back in queue.'.format( len(rows))) # ({} game left) except WebDriverException: rows.insert(0, row) Logger.info( 'Exception occurred, back in queue.'.format( len(rows))) # ({} left) Logger.info( 'WebDriverException occurred, restarting browser.') raise except Exception as e: traceback.print_exc(limit=2, file=sys.stdout) rows.insert(0, row) Logger.info( 'Exception occurred, back in queue.'.format( len(rows))) # ({} left) #badrows.append(row) # for row in rows: # try: # game_page.update(row) # except: # badrows.append(row) # print except WebDriverException: pass Logger.info("Import has finished.")
def execute(args): login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() game_count = csv_reader.rowCount if not args['--force']: sys.stdout.write( "You are about to delete %s games in you collection (%s), " "please enter the number of games displayed here to confirm you want to continue: " % (game_count, login)) if raw_input() != game_count.__str__(): Logger.error( 'Operation canceled, number does not match (should be %s).' % game_count, sysexit=True) return Logger.info("Deleting games for '%s' account..." % login) cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) Logger.info("Authenticating...", break_line=False) opener.open( BGG_BASE_URL + '/login', urlencode({ 'action': 'login', 'username': login, 'password': args['--password'] })) if not any(cookie.name == "bggusername" for cookie in cj): Logger.info(" [error]", append=True) Logger.error("Authentication failed for user '%s'!" % login, sysexit=True) Logger.info(" [done]", append=True) Logger.info("Deleting %s games..." % game_count) csv_reader.iterate(lambda row: game_deleter(opener, row)) Logger.info("Deletion has finished.")
def updateid(self, game_attrs): """ Update game details :param game_attrs: Game attributes as a dictionary """ # General use of Selenium is # A) goto page, # B) find element using xpath expression, then # C) take action related to the element. # # Flow of this function is: # 1) Go to game page # 2) Try to click "Add to Collection" button. # 3) If it didn't exist, # a) click the "In Collection" button. # b) click the "Edit" button # 4) Open the additional two form dialogs # (Show Advanced, Show Custom) # 5) fill out items on form, dependencies show which values must # exist before the indicated item. # For example, 'wishlistpriority':{'wishlist':1}, # means that to set wishlistpriority, then # wishlist must equal 1. # 6) Finally, find the form element and submit it. #Logger.info("updateid()", append=True, break_line=True) #Logger.info("{} {}, ".format(game_attrs.get('objectid',''),game_attrs.get('objectname','')), append=True, break_line=True) self.goto(game_attrs) Logger.info("page, ", append=False, break_line=False) self.openeditform() # Open advanced data entry panel. #<a class="toggler-caret" ng-href="" ng-click="editctrl.showvars.showAdvanced = !editctrl.showvars.showAdvanced" ng-class="{ 'is-active': editctrl.showvars.showAdvanced }"> <span class="glyphicon glyphicon-menu-right"></span> <strong>Advanced</strong> (private info, parts exchange) </a> Logger.info("Advanced button...", append=True, break_line=False) self.wait.until( EC.element_to_be_clickable(( By.XPATH, "//a[starts-with(@class,'toggler-caret') and starts-with(@ng-click,'editctrl.showvars.showAdvanced')]" ))) try: Logger.info("finding advanced dropdown...", append=True, break_line=False) b = self.itemEl.find_element_by_xpath( ".//a[@class='toggler-caret' and starts-with(@ng-click,'editctrl.showvars.showAdvanced')]" ) Logger.info("Click. ", append=True, break_line=False) b.click() except: Logger.info("Failed.", append=True, break_line=False) pass # Open Customize Game Info data entry panel. #<a class="toggler-caret" ng-href="" ng-click="editctrl.showvars.showCustom = !editctrl.showvars.showCustom" ng-class="{ 'is-active': editctrl.showvars.showCustom }"> <span class="glyphicon glyphicon-menu-right"></span> <strong>Customize Game Info</strong> (title, image) </a> Logger.info("Custom button...", append=True, break_line=False) self.wait.until( EC.element_to_be_clickable(( By.XPATH, "//a[starts-with(@class,'toggler-caret') and starts-with(@ng-click,'editctrl.showvars.showCustom')]" ))) try: self.itemEl.find_element_by_xpath( ".//a[@class='toggler-caret' and starts-with(@ng-click,'editctrl.showvars.showCustom')]" ).click() except: Logger.info("Failed. ", append=True, break_line=False) # custom = self.wait.until(EC.element_to_be_clickable( # (By.XPATH, ".//a[starts-with(@ng-click,'editctrl.showvars.showCustom')]"))) # custom.click() #<input ng-model="editctrl.editdata.item.textfield.customname.value" class="form-control ng-pristine ng-valid ng-empty ng-touched" type="text" placeholder="Gloomhaven Nickname" style=""> Logger.info("Name field...", append=True, break_line=False) nickname = self.wait.until( EC.element_to_be_clickable(( By.XPATH, './/input[@ng-model="editctrl.editdata.item.textfield.customname.value"]' ))) # self.itemEl = self.wait.until(EC.element_to_be_clickable( # (By.XPATH, "//form[@name='collectioneditorform']"))) # Fill all provided values using dynamic invocations 'fill_[fieldname]' dependencies = { 'wishlistpriority': { 'wishlist': 1 }, 'wishlistcomment': { 'wishlist': 1 }, 'conditiontext': { 'fortrade': 1 } } # 'fortrade', 'conditiontext', # these must be in this order # 'wishlist', 'wishlistpriority', 'wishlistcomment', # these must be in this order Logger.info("Updating fields: ", append=True, break_line=False) try: for key in BGG_SUPPORTED_FIELDS: dontdo = 0 if key in game_attrs: value = game_attrs[key] if value is not None: #print '{}'.format(key), #time.sleep(2) dep = dependencies.get(key, None) if dep: #print 'depends on: ', for k, v in dep.items( ): # for python 2: iteritems() #print '{}={}?'.format(k,v), if str(game_attrs[k]) != str(v): dontdo = 1 if dontdo: continue getattr(self, "fill_%s" % key)(value) except: Logger.info("\nEXCEPTION.", append=True, break_line=True) traceback.print_exc() return False # <button class="visible-xs-inline btn btn-primary" ng-disabled="editctrl.saving" type="submit"> <span>Save</span> </button> #savebutton = self.itemEl.find_element_by_xpath(".//button[@ng-disabled='editctrl.saving']") Logger.info("Form? ", append=True, break_line=False) form = self.itemEl.find_element_by_xpath( ".//form[@name='collectioneditorform']") # action=selenium.interactions.Actions(driver); # import selenium.webdriver.common.actions.pointer_actions # selenium.webdriver.common.actions.pointer_actions().click(savebutton) #Logger.info("submitting, ", append=True, break_line=False) form.submit() Logger.info("submitted. ", append=True, break_line=False) time.sleep(0.1) # action.moveToElement(savebutton).click().perform(); return True # Save "Private Info" popup if opened try: self.privateInfoPopupEl.find_element_by_xpath( ".//input[@type='submit']").click() except WebDriverException: pass else: self.wait.until( EC.element_to_be_clickable( (By.XPATH, ".//td[@class='collection_ownershipmod editfield']"))) # Save "Version" popup if opened try: self.versionPopupEl.find_element_by_xpath( ".//input[@type='submit']").click() except WebDriverException: pass else: self.wait.until( EC.element_to_be_clickable( (By.XPATH, ".//td[@class='collection_versionmod editfield']"))) return True
def iterate(self, callback): try: index = 1 for row in self.reader: objectid = row.get('objectid') if objectid is None or not objectid.isdigit(): Logger.error("No valid 'objectid' at line %s!" % index, None, sysexit=True) return # Encode in UTF-8 for key in row: value = row[key] if value is not None: row[key] = unicode(value, 'utf-8') objectname = row['objectname'] if objectname is None or objectname == "": objectname = "(name not available for objectid=%s)" % objectid Logger.info("[%s/%s] %s... " % (index, self.rowCount, objectname), break_line=False) try: callback(row) except WebDriverException as e: Logger.error(UI_ERROR_MSG, e, sysexit=True) return except Exception as e: Logger.info("", append=True) Logger.error("Unexpected error while processing row %s" % index, e, sysexit=True) return Logger.info(" [done]", append=True) index += 1 except csv.Error as e: Logger.error('Error while reading file %s at line %d: %s' % (file, self.reader.line_num, e), sysexit=True)
def openeditform(self): button = self.notincollection() if button: button.click() else: Logger.info(" not found. ", append=True, break_line=False) Logger.info("(i.e. game in col'n)...", append=True, break_line=False) # div = self.driver.find_element_by_xpath( # "(//div[@class, 'toolbar-actions'])[last()]") Logger.info("'In Col'n' button? ", append=True, break_line=False) button = self.driver.find_element_by_xpath( '(' '//' 'button[@id="button-collection" and descendant::' 'span[starts-with(@ng-show,"colltoolbarctrl.collection.items.length") and ' 'contains(text(),"In Collection")]' ']' ')[last()]') Logger.info("Click. ", append=True, break_line=False) button.click() clickable = self.driver.find_element_by_xpath( '//span[@class="collection-dropdown-item-edit" and //button[contains(text(),"Edit")]]' ) Logger.info("Click col'n dropdown. ", append=True, break_line=False) clickable.click() Logger.info("form...", append=True, break_line=False) self.itemEl = self.wait.until( EC.element_to_be_clickable( (By.XPATH, "//div[@class='modal-content']")))
if 'wantpartslist' in row.keys(): update_collid(opener, collid, 'wantpartslist', {'value': row['wantpartslist']}) if 'haspartslist' in row.keys(): update_collid(opener, collid, 'haspartslist', {'value': row['haspartslist']}) if 'wishlistcomment' in row.keys(): update_collid(opener, collid, 'wishlistcomment', {'value': row['wishlistcomment']}) def execute(args): login = args['--login'] file_path = check_file(args) csv_reader = CsvReader(file_path) csv_reader.open() Logger.info("Importing games for '%s' account..." % login) cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) Logger.info("Authenticating...", break_line=False) opener.open(BGG_BASE_URL + '/login', urlencode({ 'action': 'login', 'username': login, 'password': args['--password']})) if not any(cookie.name == "bggusername" for cookie in cj): Logger.info(" [error]", append=True) Logger.error("Authentication failed for user '%s'!" % login, sysexit=True) Logger.info(" [done]", append=True) Logger.info("Importing %s games..." % csv_reader.rowCount) csv_reader.iterate(lambda row: game_importer(opener, row, args['--force-new'])) Logger.info("Import has finished.")
def execute(args, options): login = args['--login'] dest_path = args['<file>'] Logger.info("Exporting collection for '%s' account..." % login) # 1. Authentication with WebDriver('collection-export', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate( login, args['--password']): sys.exit(1) auth_cookie = web_driver.driver.get_cookie( BGG_SESSION_COOKIE_NAME)['value'] # 2. Export # Easier to rely on a client HTTP call rather than Selenium to download a file # Just need to pass the session cookie to get the full export with private information # Use XML2 API, see https://www.boardgamegeek.com/wiki/page/BGG_XML_API2#Collection # Default CSV export doesn't provide version info! url = '%s/xmlapi2/collection?username=%s&version=1&showprivate=1&stats=1' \ % (BGG_BASE_URL, login) req = urllib2.Request( url, None, {'Cookie': '%s=%s' % (BGG_SESSION_COOKIE_NAME, auth_cookie)}) # Get a BadStatusLine error most of times without this delay! # Related to Selenium, but in some conditions that I have not identified time.sleep(8) try: Logger.info('Launching export...') response = default_export(req) except Exception as e: Logger.error('Error while fetching export file!', e, sysexit=True) return # 3. Store XML file if requested xml_file = options.get('save-xml-file') if xml_file == 'true': xml_file_path = write_xml_file(response, dest_path) Logger.info("XML file save as %s" % xml_file_path) source = open(xml_file_path, 'rU') else: source = response # 4. Write CSV file try: write_csv(source, dest_path) except Exception as e: Logger.error('Error while writing export file in file system!', e, sysexit=True) return finally: source.close() # End Logger.info("Collection has been exported as %s" % dest_path)
def execute(args): login = args['--login'] dest_path = args['<file>'] Logger.info("Exporting collection for '%s' account..." % login) # 1. Authentication cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) Logger.info("Authenticating...", break_line=False) opener.open(BGG_BASE_URL + '/login', urlencode({ 'action': 'login', 'username': login, 'password': args['--password']})) if not any(cookie.name == "bggusername" for cookie in cj): Logger.info(" [error]", append=True) Logger.error("Authentication failed for user '%s'!" % login, sysexit=True) Logger.info(" [done]", append=True) # 2. Export # Easier to rely on a client HTTP call rather than Selenium to download a file # Just need to pass the session cookie to get the full export with private information # Use XML2 API, see https://www.boardgamegeek.com/wiki/page/BGG_XML_API2#Collection # Default CSV export doesn't provide version info! url = BGG_BASE_URL + '/xmlapi2/collection?' + urlencode({ 'username': login, 'version': 1, 'showprivate': 1, 'stats': 1}) req = urllib2.Request(url) # Get a BadStatusLine error most of times without this delay! # Related to Selenium, but in some conditions that I have not identified time.sleep(8) try: Logger.info('Launching export...') response = default_export(opener, req) except Exception as e: Logger.error('Error while fetching export file!', e, sysexit=True) return # 3. Store XML file if requested if args['--save-xml-file']: xml_file_path = write_xml_file(response, dest_path) Logger.info("XML file save as %s" % xml_file_path) source = open(xml_file_path, 'rU') else: source = response # 4. Write CSV file try: write_csv(source, dest_path) except Exception as e: Logger.error('Error while writing export file in file system!', e, sysexit=True) return finally: source.close() # End Logger.info("Collection has been exported as %s" % dest_path)
def execute(args, options): login = args['--login'] dest_path = args['<file>'] Logger.info("Exporting collection for '%s' account..." % login) # 1. Authentication with WebDriver('collection-export', args, options) as web_driver: if not LoginPage(web_driver.driver).authenticate(login, args['--password']): sys.exit(1) auth_cookie = web_driver.driver.get_cookie(BGG_SESSION_COOKIE_NAME)['value'] # 2. Export # Easier to rely on a client HTTP call rather than Selenium to download a file # Just need to pass the session cookie to get the full export with private information # Use XML2 API, see https://www.boardgamegeek.com/wiki/page/BGG_XML_API2#Collection # Default CSV export doesn't provide version info! url = '%s/xmlapi2/collection?username=%s&version=1&showprivate=1&stats=1' \ % (BGG_BASE_URL, login) req = urllib2.Request(url, None, {'Cookie': '%s=%s' % (BGG_SESSION_COOKIE_NAME, auth_cookie)}) # Get a BadStatusLine error most of times without this delay! # Related to Selenium, but in some conditions that I have not identified time.sleep(8) try: Logger.info('Launching export...') response = default_export(req) except Exception as e: Logger.error('Error while fetching export file!', e, sysexit=True) return # 3. Store XML file if requested xml_file = options.get('save-xml-file') if xml_file == 'true': xml_file_path = write_xml_file(response, dest_path) Logger.info("XML file save as %s" % xml_file_path) source = open(xml_file_path, 'rU') else: source = response # 4. Write CSV file try: write_csv(source, dest_path) except Exception as e: Logger.error('Error while writing export file in file system!', e, sysexit=True) return finally: source.close() # End Logger.info("Collection has been exported as %s" % dest_path)