-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from lordyavin/develop
Initial Release
- Loading branch information
Showing
10 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,3 +127,4 @@ dmypy.json | |
|
||
# Pyre type checker | ||
.pyre/ | ||
/runtime/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<projectDescription> | ||
<name>eightanu</name> | ||
<comment></comment> | ||
<projects> | ||
</projects> | ||
<buildSpec> | ||
<buildCommand> | ||
<name>org.python.pydev.PyDevBuilder</name> | ||
<arguments> | ||
</arguments> | ||
</buildCommand> | ||
</buildSpec> | ||
<natures> | ||
<nature>org.python.pydev.pythonNature</nature> | ||
</natures> | ||
</projectDescription> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
<?eclipse-pydev version="1.0"?><pydev_project> | ||
|
||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> | ||
|
||
<path>/${PROJECT_DIR_NAME}/src</path> | ||
|
||
</pydev_pathproperty> | ||
|
||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property> | ||
|
||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">eightanu venv</pydev_property> | ||
|
||
</pydev_project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/org.eclipse.core.resources.prefs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,8 @@ | ||
# eightanu | ||
Python script to export 8a.nu logbook | ||
|
||
# Introduction | ||
Since the restart of 8a.nu you can no longer export your ascents. This project aims to provide a tool to export personal ascents. | ||
|
||
# Usage | ||
more to come... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
selenium==3.141.0 | ||
urllib3==1.25.9 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
#!/usr/local/bin/python3.6 | ||
# encoding: utf-8 | ||
''' | ||
eightanu.cli -- The command line interface to export the new 8a.new ascents data. | ||
eightanu.cli is an exporter that scrap your ascents from the new 8a.nu website. | ||
@author: lordyavin | ||
@copyright: 2020 lordyavin. All rights reserved. | ||
@license: MIT | ||
@contact: github@klesatschke.net | ||
@deffield updated: Updated | ||
''' | ||
from argparse import ArgumentParser | ||
from argparse import RawDescriptionHelpFormatter | ||
import os | ||
import sys | ||
|
||
from eightanu import webdriver | ||
from eightanu.export import export | ||
|
||
__all__ = [] | ||
__version__ = 0.1 | ||
__date__ = '2020-05-27' | ||
__updated__ = '2020-05-28' | ||
|
||
DEBUG = 0 | ||
TESTRUN = 0 | ||
PROFILE = 0 | ||
|
||
|
||
class CLIError(Exception): | ||
'''Generic exception to raise and log different fatal errors.''' | ||
|
||
def __init__(self, msg): | ||
super(CLIError).__init__(type(self)) | ||
self.msg = "E: %s" % msg | ||
|
||
def __str__(self): | ||
return self.msg | ||
|
||
def __unicode__(self): | ||
return self.msg | ||
|
||
|
||
def main(argv=None): # IGNORE:C0111 | ||
'''Command line options.''' | ||
|
||
if argv is None: | ||
argv = sys.argv | ||
else: | ||
sys.argv.extend(argv) | ||
|
||
program_name = os.path.basename(sys.argv[0]) | ||
program_version = "v%s" % __version__ | ||
program_build_date = str(__updated__) | ||
program_version_message = '%%(prog)s %s (%s)' % (program_version, program_build_date) | ||
program_shortdesc = __import__('__main__').__doc__.split("\n")[1] | ||
program_license = '''%s | ||
Created by user_name on %s. | ||
Copyright 2020 organization_name. All rights reserved. | ||
Licensed under the Apache License 2.0 | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Distributed on an "AS IS" basis without warranties | ||
or conditions of any kind, either express or implied. | ||
USAGE | ||
''' % (program_shortdesc, str(__date__)) | ||
|
||
try: | ||
# Setup argument parser | ||
parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter) | ||
parser.add_argument("-b", "--browser", dest="browser", type=str, choices=webdriver.SUPPORTED_BROWSER, required=True) | ||
parser.add_argument("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %(default)s]", default=0) | ||
parser.add_argument('-V', '--version', action='version', version=program_version_message) | ||
|
||
parser.add_argument("username", type=str, help="Your 8a user name") | ||
|
||
# Process arguments | ||
args = parser.parse_args() | ||
|
||
verbose = args.verbose | ||
if verbose > 0: | ||
print("Verbose mode on") | ||
|
||
export(args.browser, args.username, verbose) | ||
|
||
return 0 | ||
except KeyboardInterrupt: | ||
### handle keyboard interrupt ### | ||
return 0 | ||
except Exception as e: | ||
if DEBUG or TESTRUN: | ||
raise e | ||
indent = len(program_name) * " " | ||
sys.stderr.write(program_name + ": " + repr(e) + "\n") | ||
sys.stderr.write(indent + " for help use --help") | ||
return 2 | ||
|
||
|
||
if __name__ == "__main__": | ||
if TESTRUN: | ||
import doctest | ||
doctest.testmod() | ||
if PROFILE: | ||
import cProfile | ||
import pstats | ||
profile_filename = 'eightanu.export_ascents_profile.txt' | ||
cProfile.run('main()', profile_filename) | ||
statsfile = open("profile_stats.txt", "wb") | ||
p = pstats.Stats(profile_filename, stream=statsfile) | ||
stats = p.strip_dirs().sort_stats('cumulative') | ||
stats.print_stats() | ||
statsfile.close() | ||
sys.exit(0) | ||
sys.exit(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
''' | ||
eightanu.export -- The 8a.nu logbook export | ||
@author: lordyavin | ||
@copyright: 2020 lordyavin. All rights reserved. | ||
@license: MIT | ||
@contact: github@klesatschke.net | ||
@deffield updated: Updated | ||
''' | ||
from selenium.webdriver.remote.webelement import WebElement | ||
|
||
from eightanu import webdriver | ||
from selenium.webdriver.remote.webdriver import WebDriver | ||
from eightanu.webdriver import SUPPORTED_BROWSER | ||
|
||
def _select_alltime_ascents(driver): | ||
assert isinstance(driver, WebDriver) | ||
|
||
filters = driver.find_element_by_class_name("ascent-filters") | ||
assert isinstance(filters, WebElement) | ||
|
||
all_options = filters.find_elements_by_tag_name("option") | ||
for option in all_options: | ||
if option.text == "All Time": | ||
option.click() | ||
|
||
def _sort_by_date(driver): | ||
assert isinstance(driver, WebDriver) | ||
|
||
order = driver.find_element_by_class_name("ascent-filters__order") | ||
all_options = order.find_elements_by_tag_name("option") | ||
for option in all_options: | ||
if option.text == "Date": | ||
option.click() | ||
|
||
|
||
def _read_table_headers(table): | ||
assert isinstance(table, WebElement) | ||
|
||
thead = table.find_element_by_tag_name("thead") | ||
table_headers = thead.find_elements_by_tag_name("th")[1:] # skip first empty cell | ||
headers = ["DATE"] + [cell.text.strip() for cell in table_headers] | ||
return headers | ||
|
||
|
||
def _read_ascents(table, headers): | ||
assert isinstance(table, WebElement) | ||
assert isinstance(headers, (list, tuple)) | ||
|
||
ascents = [] | ||
name_idx = headers.index("NAME") | ||
groups = table.find_elements_by_tag_name("tbody") | ||
for group in groups: | ||
date = group.find_element_by_tag_name("th").text | ||
rows = group.find_elements_by_tag_name("tr") | ||
for row in rows: | ||
cells = row.find_elements_by_tag_name("td")[1:] # skip first empty cell | ||
if cells: | ||
ascent = [date] + [cell.text.strip() for cell in cells] | ||
name = ascent[name_idx] | ||
if "\n" in name: | ||
ascent[name_idx] = name[:name.index("\n")] | ||
ascents.append(ascent) | ||
|
||
return ascents | ||
|
||
|
||
def _print_logbook(headers, ascents): | ||
assert isinstance(headers, (list, tuple)) | ||
assert isinstance(ascents, (list, tuple)) | ||
|
||
print(";".join(headers)) | ||
for ascent in ascents: | ||
print(";".join(ascent)) | ||
|
||
def export(browser, username, verbose=0): | ||
assert browser in SUPPORTED_BROWSER | ||
assert isinstance(username, str) | ||
assert isinstance(verbose, int) | ||
|
||
username = username.replace(" ", "-").lower() | ||
url = "https://www.8a.nu/user/{username}/sportclimbing".format(username=username) | ||
|
||
driver = webdriver.get(browser, verbose) | ||
try: | ||
driver.get(url) | ||
_select_alltime_ascents(driver) | ||
_sort_by_date(driver) | ||
|
||
table = driver.find_element_by_class_name("user-ascents") | ||
headers = _read_table_headers(table) | ||
ascents = _read_ascents(table, headers) | ||
_print_logbook(headers, ascents) | ||
finally: | ||
driver.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# encoding: utf-8 | ||
''' | ||
eightanu.webdriver -- Handles the selenium webdrivers. | ||
@author: lordyavin | ||
@copyright: 2020 lordyavin. All rights reserved. | ||
@license: MIT | ||
@contact: github@klesatschke.net | ||
@deffield updated: Updated | ||
''' | ||
import os | ||
from zipfile import ZipFile | ||
|
||
from selenium import webdriver | ||
from selenium.common.exceptions import WebDriverException | ||
import urllib3 | ||
from urllib3.response import HTTPResponse | ||
|
||
FIREFOX_DRIVER_URL = "https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-win64.zip" | ||
CHROME_DRIVER_URL = "https://chromedriver.storage.googleapis.com/83.0.4103.39/chromedriver_win32.zip" | ||
|
||
FIREFOX = "Firefox" | ||
CHROME = "Chrome" | ||
|
||
SUPPORTED_BROWSER = ( | ||
FIREFOX, | ||
CHROME, | ||
) | ||
|
||
DOWNLOAD = { | ||
FIREFOX: FIREFOX_DRIVER_URL, | ||
CHROME: CHROME_DRIVER_URL | ||
} | ||
|
||
DRIVER = { | ||
FIREFOX: webdriver.Firefox, | ||
CHROME: webdriver.Chrome, | ||
} | ||
|
||
|
||
def download(url, verbose=0): | ||
http = urllib3.PoolManager() | ||
|
||
if verbose: | ||
print("Downloading %s" % url) | ||
|
||
response = http.request('GET', url) | ||
assert isinstance(response, HTTPResponse) | ||
zipfilename = os.path.basename(url) | ||
|
||
with open(zipfilename, "wb") as zipfile: | ||
zipfile.write(response.data) | ||
|
||
if verbose: | ||
print("Extracting %s" % zipfilename) | ||
|
||
with ZipFile(zipfilename) as zipfile: | ||
zipfile.extractall(".") | ||
|
||
os.remove(zipfilename) | ||
|
||
|
||
def get(browser, verbose=0): | ||
assert browser in SUPPORTED_BROWSER | ||
try: | ||
Driver = DRIVER[browser] | ||
driver = Driver() | ||
except WebDriverException as e: | ||
if verbose: | ||
print(e.msg) | ||
download(DOWNLOAD[browser], verbose) | ||
driver = Driver() | ||
return driver |