Coverage for amazonorders/cli.py: 81.10%

127 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-07 21:56 +0000

1import datetime 

2import logging 

3from typing import Any, Optional 

4 

5import click 

6from click.core import Context 

7 

8from amazonorders.conf import DEFAULT_OUTPUT_DIR 

9from amazonorders.exception import AmazonOrdersError 

10from amazonorders.orders import AmazonOrders 

11from amazonorders.session import AmazonSession, IODefault 

12 

13__author__ = "Alex Laird" 

14__copyright__ = "Copyright 2024, Alex Laird" 

15__version__ = "1.0.9" 

16 

17logger = logging.getLogger("amazonorders") 

18 

19 

20class IOClick(IODefault): 

21 def echo(self, 

22 msg: str, 

23 fg: Optional[str] = None, 

24 **kwargs: Any): 

25 click.secho(msg, fg=fg) 

26 

27 def prompt(self, 

28 msg: str, 

29 type: str = None, 

30 **kwargs: Any): 

31 return click.prompt("--> {}".format(msg), type=type) 

32 

33 

34@click.group() 

35@click.option('--username', help="An Amazon username.") 

36@click.option('--password', help="An Amazon password.") 

37@click.option('--debug', is_flag=True, default=False, help="Enable debugging and send output to command line.") 

38@click.option('--max-auth-attempts', default=10, 

39 help="Will continue in the login auth loop this many times (successes and failures).") 

40@click.option('--output-dir', default=DEFAULT_OUTPUT_DIR, 

41 help="The directory where any output files should be produced.") 

42@click.pass_context 

43def amazon_orders_cli(ctx: Context, 

44 **kwargs: Any): 

45 """ 

46 amazon-orders is an unofficial library that provides a command line interface alongside a programmatic API that 

47 can be used to interact with Amazon.com's consumer-facing website. 

48 

49 This works by parsing website data from Amazon.com. A nightly build validates functionality to ensure its 

50 stability, but as Amazon provides no official API to use, this package may break at any time. This 

51 package only supports the English version of the website. 

52 

53 Documentation can be found at https://amazon-orders.readthedocs.io. 

54 

55 Session data is persisted between requests and interactions with the CLI, minimizing the need to reauthenticate 

56 after a successful login attempt. 

57 """ 

58 _print_banner() 

59 

60 ctx.ensure_object(dict) 

61 for key, value in kwargs.items(): 

62 if value: 

63 ctx.obj[key] = value 

64 

65 if kwargs["debug"]: 

66 logger.setLevel(logging.DEBUG) 

67 logger.addHandler(logging.StreamHandler()) 

68 

69 username = kwargs.get("username") 

70 password = kwargs.get("password") 

71 

72 amazon_session = AmazonSession(username, 

73 password, 

74 debug=kwargs["debug"], 

75 io=IOClick(), 

76 max_auth_attempts=kwargs["max_auth_attempts"], 

77 output_dir=kwargs["output_dir"]) 

78 

79 ctx.obj["amazon_session"] = amazon_session 

80 

81 

82@amazon_orders_cli.command() 

83@click.pass_context 

84@click.option('--year', default=datetime.date.today().year, 

85 help="The year for which to get order history, defaults to the current year.") 

86@click.option('--start-index', help="Retrieve the single page of history at the given index.") 

87@click.option('--full-details', is_flag=True, default=False, 

88 help="Retrieve the full details for each order in the history.") 

89def history(ctx: Context, 

90 **kwargs: Any): 

91 """ 

92 Retrieve Amazon order history for a given year. 

93 """ 

94 amazon_session = ctx.obj["amazon_session"] 

95 

96 year = kwargs["year"] 

97 start_index = kwargs["start_index"] 

98 full_details = kwargs["full_details"] 

99 

100 click.echo("""----------------------------------------------------------------------- 

101Order History for {}{}{} 

102-----------------------------------------------------------------------\n""".format(year, 

103 ", startIndex={}, one page".format( 

104 start_index) if start_index else ", all pages", 

105 ", with full details" if full_details else "")) 

106 

107 click.echo("Info: This might take a minute ...\n") 

108 

109 try: 

110 _authenticate(ctx, amazon_session) 

111 

112 amazon_orders = AmazonOrders(amazon_session, 

113 debug=amazon_session.debug, 

114 output_dir=ctx.obj["output_dir"]) 

115 

116 orders = amazon_orders.get_order_history(year=kwargs["year"], 

117 start_index=kwargs["start_index"], 

118 full_details=kwargs["full_details"], ) 

119 

120 for order in orders: 

121 click.echo("{}\n".format(_order_output(order))) 

122 except AmazonOrdersError as e: 

123 logger.debug("An error occurred.", exc_info=True) 

124 ctx.fail(str(e)) 

125 

126 

127@amazon_orders_cli.command() 

128@click.pass_context 

129@click.argument("order_id") 

130def order(ctx: Context, 

131 order_id: str): 

132 """ 

133 Retrieve the full details for the given Amazon order ID. 

134 """ 

135 amazon_session = ctx.obj["amazon_session"] 

136 

137 try: 

138 _authenticate(ctx, amazon_session) 

139 

140 amazon_orders = AmazonOrders(amazon_session, 

141 debug=amazon_session.debug, 

142 output_dir=ctx.obj["output_dir"]) 

143 

144 order = amazon_orders.get_order(order_id) 

145 

146 click.echo("{}\n".format(_order_output(order))) 

147 except AmazonOrdersError as e: 

148 logger.debug("An error occurred.", exc_info=True) 

149 ctx.fail(str(e)) 

150 

151 

152@amazon_orders_cli.command(short_help="Check if persisted session exists.") 

153@click.pass_context 

154def check_session(ctx: Context): 

155 """ 

156 Check if a persisted session exists, meaning commands can be called without needing to provide credentials. 

157 """ 

158 amazon_session = ctx.obj["amazon_session"] 

159 if amazon_session.auth_cookies_stored(): 

160 click.echo("Info: A persisted session exists.\n") 

161 else: 

162 click.echo("Info: No persisted session exists.\n") 

163 

164 

165@amazon_orders_cli.command() 

166@click.pass_context 

167def logout(ctx: Context): 

168 """ 

169 Logout of existing Amazon sessions and clear cookies. 

170 """ 

171 amazon_session = ctx.obj["amazon_session"] 

172 amazon_session.logout() 

173 

174 click.echo("Info: Successfully logged out of the Amazon session.\n") 

175 

176 

177@amazon_orders_cli.command() 

178def version(): 

179 """ 

180 Get the package version. 

181 """ 

182 click.echo("Version: {}\n".format(__version__)) 

183 

184 

185def _print_banner(): 

186 click.echo(""" 

187======================================================================= 

188 ___ _____ _  

189 / _ \ | _ | | |  

190/ /_\ \_ __ ___ __ _ _______ _ __ | | | |_ __ __| | ___ _ __ ___  

191| _ | '_ ` _ \ / _` |_ / _ \| '_ \ | | | | '__/ _` |/ _ \ '__/ __| 

192| | | | | | | | | (_| |/ / (_) | | | | \ \_/ / | | (_| | __/ | \__ \\ 

193\_| |_/_| |_| |_|\__,_/___\___/|_| |_| \___/|_| \__,_|\___|_| |___/  

194=======================================================================\n""") 

195 

196 

197def _authenticate(ctx: Context, 

198 amazon_session: AmazonSession): 

199 if not amazon_session.username and not amazon_session.password: 

200 click.echo(ctx.get_help()) 

201 

202 ctx.fail("Amazon --username and --password must be provided, since no previous session was found.") 

203 

204 if amazon_session.auth_cookies_stored(): 

205 if amazon_session.username or amazon_session.password: 

206 click.echo("Info: You've provided --username and --password, but because a previous session still exists," 

207 "that is being ignored. If you would like to reauthenticate, call the `logout` command first.\n") 

208 

209 amazon_session.login() 

210 

211 

212def _order_output(order): 

213 order_str = """----------------------------------------------------------------------- 

214Order #{} 

215-----------------------------------------------------------------------""".format(order.order_number) 

216 

217 order_str += "\n Shipments: {}".format(order.shipments) 

218 order_str += "\n Order Details Link: {}".format(order.order_details_link) 

219 order_str += "\n Grand Total: ${:,.2f}".format(order.grand_total) 

220 order_str += "\n Order Placed Date: {}".format(order.order_placed_date) 

221 order_str += "\n {}".format(order.recipient) 

222 if order.payment_method: 

223 order_str += "\n Payment Method: {}".format(order.payment_method) 

224 if order.payment_method_last_4: 

225 order_str += "\n Payment Method Last 4: {}".format(order.payment_method_last_4) 

226 if order.subtotal: 

227 order_str += "\n Subtotal: ${:,.2f}".format(order.subtotal) 

228 if order.shipping_total: 

229 order_str += "\n Shipping Total: ${:,.2f}".format(order.shipping_total) 

230 if order.subscription_discount: 

231 order_str += "\n Subscription Discount: ${:,.2f}".format(order.subscription_discount) 

232 if order.total_before_tax: 

233 order_str += "\n Total Before Tax: ${:,.2f}".format(order.total_before_tax) 

234 if order.estimated_tax: 

235 order_str += "\n Estimated Tax: ${:,.2f}".format(order.estimated_tax) 

236 if order.refund_total: 

237 order_str += "\n Refund Total: ${:,.2f}".format(order.refund_total) 

238 if order.order_shipped_date: 

239 order_str += "\n Order Shipped Date: {}".format(order.order_shipped_date) 

240 if order.refund_completed_date: 

241 order_str += "\n Refund Completed Date: {}".format(order.refund_completed_date) 

242 

243 order_str += "\n-----------------------------------------------------------------------" 

244 

245 return order_str 

246 

247 

248if __name__ == "__main__": 

249 amazon_orders_cli(obj={})