diff --git a/api.py b/api.py index dab1a64..a90c7d4 100644 --- a/api.py +++ b/api.py @@ -20,15 +20,17 @@ class RESTAPI: class StandardNotesAPI: encryption_helper = EncryptionHelper() - base_url = 'https://sync.standardnotes.org' sync_token = None def getAuthParamsForEmail(self): return self.api.get('/auth/params', dict(email=self.username)) - def signIn(self, password): + def genKeys(self, password): pw_info = self.getAuthParamsForEmail() - self.keys = self.encryption_helper.pure_generatePasswordAndKey(password, pw_info['pw_salt'], pw_info['pw_cost']) + return self.encryption_helper.pure_generatePasswordAndKey(password, pw_info['pw_salt'], pw_info['pw_cost']) + + def signIn(self, keys): + self.keys = keys res = self.api.post('/auth/sign_in', dict(email=self.username, password=self.keys['pw'])) self.api.addHeader(dict(Authorization='Bearer ' + res['token'])) @@ -48,7 +50,6 @@ class StandardNotesAPI: saved_items = self.encryption_helper.decryptResponseItems(response['saved_items'], self.keys) return dict(response_items=response_items, saved_items=saved_items) - def __init__(self, username, password): - self.api = RESTAPI(self.base_url) + def __init__(self, base_url, username): + self.api = RESTAPI(base_url) self.username = username - self.signIn(password) diff --git a/requirements.txt b/requirements.txt index 2e5e635..7ffb45f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +appdirs==1.4.3 cement==2.10.2 certifi==2017.7.27.1 chardet==3.0.4 diff --git a/sn-fs.py b/sn-fuse.py similarity index 100% rename from sn-fs.py rename to sn-fuse.py diff --git a/standardnotes-fs.py b/standardnotes-fs.py new file mode 100644 index 0000000..ed773a1 --- /dev/null +++ b/standardnotes-fs.py @@ -0,0 +1,86 @@ +import appdirs +import argparse +import logging +import os +import pathlib +import sys + +from configparser import ConfigParser +from getpass import getpass + +from api import StandardNotesAPI + +OFFICIAL_SERVER_URL = 'https://sync.standardnotes.org' +APP_NAME = 'standardnotes-fs' +logging.basicConfig(level=logging.DEBUG) + +# path settings +cfg_env = os.environ.get('SN_FS_CONFIG_PATH') +CONFIG_PATH = cfg_env if cfg_env else appdirs.user_config_dir(APP_NAME) +CONFIG_FILE = os.path.join(CONFIG_PATH, APP_NAME + '.conf') +CONFIG_FILE = pathlib.PurePath(CONFIG_PATH, APP_NAME + '.conf') + +def parse_options(): + parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('--username', + help='Standard Notes username to log in with') + parser.add_argument('--password', + help='Standard Notes password to log in with\n' + 'NOTE: It is NOT recommended to use this! The\n' + ' password may be stored in history, so\n' + ' use the password prompt instead.') + parser.add_argument('--sync-url', + help='URL of Standard File sync server. Defaults to:\n' + ''+OFFICIAL_SERVER_URL) + parser.add_argument('--no-config-file', action='store_true', + help='Don\'t load or create a config file') + parser.add_argument('--config', + help='Specify a config file location. Defaults to:\n' + ''+str(CONFIG_FILE)) + return parser.parse_args() + +def main(): + args = parse_options() + config = ConfigParser() + config['DEFAULT'] = {} + keys = {} + + if not args.no_config_file: + config_file = args.config if args.config else CONFIG_FILE + config_file = pathlib.Path(config_file) + + try: + config_file.parent.mkdir(mode=0o0700, parents=True, exist_ok=True) + except OSError: + err_msg = 'Error creating directory "%s".' + logging.critical(err_msg % str(config_file.parent)) + sys.exit(1) + + try: + with config_file.open() as f: + config.read_file(f) + except OSError: + err_msg = 'Config file "%s" not found.' + logging.debug(err_msg % str(config_file)) + + if config.has_option('user', 'sync_url'): + sync_url = config.get('user', 'sync_url') + else: + sync_url = args.sync_url if args.sync_url else OFFICIAL_SERVER_URL + + if config.has_option('user', 'username') and config.has_section('keys'): + username = config.get('user', 'username') + keys = dict(config.items('keys')) + else: + username = args.username if args.username else \ + input('Please enter your Standard Notes username: ') + password = args.password if args.password else \ + getpass('Please enter your password (hidden): ') + + sn_api = StandardNotesAPI(sync_url, username) + if not keys: + keys = sn_api.genKeys(password) + sn_api.signIn(keys) + +if __name__ == '__main__': + main()