77import json
88import codecs
99import logging
10+ import threading
11+ from queue import Queue
12+ from uuid import uuid4
13+ from functools import partial
1014from logging .config import dictConfig
1115from collections import defaultdict , Counter
12- from configparser import ConfigParser
1316
1417import yaml
1518import click
19+ from flask import Flask , request
20+ from requests_oauthlib import OAuth2Session
21+
1622from inoreader import InoreaderClient
1723from inoreader .filter import get_filter
1824from 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
2130APPID_ENV_NAME = 'INOREADER_APP_ID'
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-
8069def 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 ()
10988def 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