Skip to content

Commit 6baeef2

Browse files
committed
support OAuth 2.0 in command login
1 parent 2763629 commit 6baeef2

File tree

2 files changed

+81
-59
lines changed

2 files changed

+81
-59
lines changed

inoreader/consts.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
# coding: utf-8
2+
import os
3+
24
BASE_URL = 'https://www.inoreader.com/reader/api/0/'
35
LOGIN_URL = 'https://www.inoreader.com/accounts/ClientLogin'
6+
7+
DEFAULT_APPID = '1000000337'
8+
DEFAULT_APPKEY = 'Bp1UxqT8KbbhNe5lmJUS6bpJ0EKow9Ze'
9+
10+
CONFIG_FILE = os.path.join(os.environ.get('HOME'), '.inoreader')

inoreader/main.py

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,24 @@
77
import json
88
import codecs
99
import logging
10+
import threading
11+
from queue import Queue
12+
from uuid import uuid4
13+
from functools import partial
1014
from logging.config import dictConfig
1115
from collections import defaultdict, Counter
12-
from configparser import ConfigParser
1316

1417
import yaml
1518
import click
19+
from flask import Flask, request
20+
from requests_oauthlib import OAuth2Session
21+
1622
from inoreader import InoreaderClient
1723
from inoreader.filter import get_filter
1824
from inoreader.sim import sim_of, InvIndex
25+
from inoreader.exception import NotLoginError
26+
from inoreader.config import InoreaderConfigManager
27+
from inoreader.consts import DEFAULT_APPID, DEFAULT_APPKEY
1928

2029

2130
APPID_ENV_NAME = 'INOREADER_APP_ID'
@@ -57,46 +66,16 @@
5766
})
5867

5968

60-
def read_config():
61-
config = ConfigParser()
62-
if os.path.exists(CONFIG_FILE):
63-
config.read(CONFIG_FILE)
64-
65-
return config
66-
67-
68-
def get_appid_key(config):
69-
# 先尝试从配置文件中读取 appid 和 appkey
70-
appid = config.get('auth', 'appid') if config.has_section('auth') else None
71-
appkey = config.get('auth', 'appkey') if config.has_section('auth') else None
72-
if not appid:
73-
appid = os.environ.get(APPID_ENV_NAME)
74-
if not appkey:
75-
appkey = os.environ.get(APPKEY_ENV_NAME)
76-
77-
return appid, appkey
78-
79-
8069
def get_client():
81-
config = read_config()
82-
appid, appkey = get_appid_key(config)
83-
if not appid or not appkey:
84-
LOGGER.error("'appid' or 'appkey' is missing")
85-
sys.exit(1)
86-
87-
token = None
88-
if config.has_section('auth'):
89-
token = config.get('auth', 'token')
90-
token = token or os.environ.get(TOKEN_ENV_NAME)
91-
if not token:
70+
config = InoreaderConfigManager(CONFIG_FILE)
71+
if not config.data:
9272
LOGGER.error("Please login first")
9373
sys.exit(1)
9474

95-
userid = None
96-
if config.has_section('user'):
97-
userid = config.get('user', 'id')
98-
99-
client = InoreaderClient(appid, appkey, userid=userid, auth_token=token)
75+
client = InoreaderClient(
76+
config.app_id, config.app_key, config.access_token, config.refresh_token,
77+
config.expires_at, userid=config.user_id, config_manager=config
78+
)
10079
return client
10180

10281

@@ -107,29 +86,65 @@ def main():
10786

10887
@main.command()
10988
def login():
110-
"""Login to your inoreader account"""
111-
client = InoreaderClient(None, None)
112-
113-
username = input("EMAIL: ").strip()
114-
password = input("PASSWORD: ").strip()
115-
status = client.login(username, password)
116-
if status:
117-
LOGGER.info("Login as '%s'", username)
118-
auth_token = client.auth_token
119-
config = read_config()
120-
if 'auth' in config:
121-
config['auth']['token'] = auth_token
122-
else:
123-
config['auth'] = {'token': auth_token}
124-
125-
appid, appkey = get_appid_key(config)
126-
client = InoreaderClient(appid, appkey, auth_token=auth_token)
127-
config['user'] = {'email': username, 'id': client.userinfo()['userId']}
128-
with codecs.open(CONFIG_FILE, mode='w', encoding='utf-8') as fconfig:
129-
config.write(fconfig)
130-
LOGGER.info("save token in config file '%s'", CONFIG_FILE)
89+
"""Login to your inoreader account with OAuth 2.0"""
90+
# run simple daemon http server to handle callback
91+
app = Flask(__name__)
92+
93+
# disable flask output
94+
app.logger.disabled = True
95+
logger = logging.getLogger('werkzeug')
96+
logger.setLevel(logging.ERROR)
97+
logger.disabled = True
98+
sys.modules['flask.cli'].show_server_banner = lambda *x: None
99+
100+
# use queue to pass data between threads
101+
queue = Queue()
102+
103+
config = InoreaderConfigManager(CONFIG_FILE)
104+
app_id = config.app_id or DEFAULT_APPID
105+
app_key = config.app_key or DEFAULT_APPKEY
106+
state = str(uuid4())
107+
oauth = OAuth2Session(app_id,
108+
redirect_uri='http://localhost:8080/oauth/redirect',
109+
scope='read write',
110+
state=state)
111+
112+
@app.route('/oauth/redirect')
113+
def redirect():
114+
token = oauth.fetch_token('https://www.inoreader.com/oauth2/token',
115+
authorization_response=request.url,
116+
client_secret=app_key)
117+
queue.put(token)
118+
queue.task_done()
119+
return 'Done.'
120+
121+
func = partial(app.run, port=8080, debug=False)
122+
threading.Thread(target=func, daemon=True).start()
123+
124+
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
125+
authorization_url, ret_state = oauth.authorization_url('https://www.inoreader.com/oauth2/auth')
126+
if state != ret_state:
127+
LOGGER.error("Server return bad state")
128+
sys.exit(1)
129+
130+
token = None
131+
print('Open the link to authorize access:', authorization_url)
132+
while True:
133+
token = queue.get()
134+
if token:
135+
break
136+
137+
queue.join()
138+
if token:
139+
config.app_id = app_id
140+
config.app_key = app_key
141+
config.access_token = token['access_token']
142+
config.refresh_token = token['refresh_token']
143+
config.expires_at = token['expires_at']
144+
config.save()
145+
print("Login successfully, tokens are saved in config file %s" % config.config_file)
131146
else:
132-
LOGGER.info("Login failed: Wrong username or password")
147+
print("Login failed, please check your environment or try again later.")
133148
sys.exit(1)
134149

135150

0 commit comments

Comments
 (0)