1
0
Fork 0

End of rework

This commit is contained in:
Ethanell 2019-04-29 15:53:18 +02:00
parent 6beb1bb246
commit 2415834c72
9 changed files with 168 additions and 181 deletions

View file

@ -1,28 +1,48 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms import StringField, PasswordField, BooleanField, SubmitField, RadioField, IntegerField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from app.models import User from app.models import User
import trello, trello_credentials
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()]) username = StringField("Username", validators = [DataRequired()])
password = PasswordField("Password", validators=[DataRequired()]) password = PasswordField("Password", validators = [DataRequired()])
remember_me = BooleanField("Remember Me") remember_me = BooleanField("Remember Me")
submit = SubmitField("Sign In") submit = SubmitField("Sign In")
def validate_username(self, username):
if not User.query.filter_by(username = username.data).first():
raise ValidationError("Invalid username.")
def validate_password(self, password):
user = User.query.filter_by(username = self.username.data).first()
if user and not user.check_password(password.data):
raise ValidationError("Invalid password.")
class RegistrationForm(FlaskForm): class RegistrationForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()]) username = StringField("Username", validators = [DataRequired()])
email = StringField("Email", validators=[DataRequired(), Email()]) email = StringField("Email", validators = [DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()]) password = PasswordField("Password", validators = [DataRequired()])
password2 = PasswordField( password2 = PasswordField(
"Repeat Password", validators=[DataRequired(), EqualTo("password")]) "Repeat Password", validators = [DataRequired(), EqualTo("password")])
submit = SubmitField("Register") submit = SubmitField("Register")
def validate_username(self, username): def validate_username(self, username):
user = User.query.filter_by(username=username.data).first() user = User.query.filter_by(username = username.data).first()
if user is not None: if user is not None:
raise ValidationError("Please use a different username.") raise ValidationError("Please use a different username.")
def validate_email(self, email): def validate_email(self, email):
user = User.query.filter_by(email=email.data).first() user = User.query.filter_by(email = email.data).first()
if user is not None: if user is not None:
raise ValidationError("Please use a different email address.") raise ValidationError("Please use a different email address.")
class TrelloAPIForm(FlaskForm):
token = StringField("Token", validators = [DataRequired()])
submit = SubmitField("Connect")
def validate_token(self, token):
try:
trello.TrelloClient(api_key = trello_credentials.api_key, token = token.data).list_boards()
except:
raise ValidationError("Invalid Trello Token")

View file

@ -2,6 +2,7 @@ from app import db
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin from flask_login import UserMixin
from app import login from app import login
import tweepy, twitter_credentials, trello, trello_credentials
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True) id = db.Column(db.Integer, primary_key=True, unique=True)
@ -9,7 +10,9 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(120), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128)) password_hash = db.Column(db.String(128))
twitter_api = db.relationship("TwitterAPI", backref="user", lazy="dynamic") twitter_api = db.relationship("TwitterAPI", backref="user", lazy="dynamic")
trello_api = db.relationship('TrelloAPI', backref="user", lazy="dynamic") tweets = db.relationship("Tweets", backref="user", lazy="dynamic")
trello_api = db.relationship("TrelloAPI", backref="user", lazy="dynamic")
boards = db.relationship("Boards", backref="user", lazy="dynamic")
def set_password(self, password): def set_password(self, password):
self.password_hash = generate_password_hash(password) self.password_hash = generate_password_hash(password)
@ -26,11 +29,33 @@ class TwitterAPI(db.Model):
access_token = db.Column(db.String(50), unique=True) access_token = db.Column(db.String(50), unique=True)
access_token_secret = db.Column(db.String(45), unique=True) access_token_secret = db.Column(db.String(45), unique=True)
def api_login(self):
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key)
auth.set_access_token(self.access_token, self.access_token_secret)
return tweepy.API(auth)
class Tweets(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
statu_id = db.Column(db.Integer, unique=True)
slots = db.Column(db.Integer)
slots_max = db.Column(db.Integer)
keywords = db.Column(db.String(256))
class TrelloAPI(db.Model): class TrelloAPI(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True) id = db.Column(db.Integer, primary_key=True, unique=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), unique=True) user_id = db.Column(db.Integer, db.ForeignKey("user.id"), unique=True)
token = db.Column(db.String(64), unique=True) token = db.Column(db.String(64), unique=True)
def api_login(self):
return trello.TrelloClient(api_key = trello_credentials.api_key, token = self.token)
class Boards(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), unique=True)
board_id = db.Column(db.String(24))
column_id = db.Column(db.String(24))
@login.user_loader @login.user_loader
def load_user(id): def load_user(id):
return User.query.get(int(id)) return User.query.get(int(id))

View file

@ -1,11 +1,10 @@
from app import app, db from app import app, db
from flask import Flask, request, redirect, session, render_template, flash, url_for from flask import Flask, request, redirect, session, render_template, url_for
from flask_login import current_user, login_user, logout_user, login_required from flask_login import current_user, login_user, logout_user, login_required
from app.forms import LoginForm, RegistrationForm from app.forms import LoginForm, RegistrationForm, TrelloAPIForm
from app.models import User, TwitterAPI, TrelloAPI from app.models import User, TwitterAPI, Tweets, TrelloAPI, Boards
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
import twitter_credentials, tweepy, tw import tweepy, trello, twitter_credentials, trello_credentials
import trello_credentials, trello, tr
@app.route("/login", methods=["GET", "POST"]) @app.route("/login", methods=["GET", "POST"])
def login(): def login():
@ -13,16 +12,13 @@ def login():
return redirect(url_for("home")) return redirect(url_for("home"))
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first() user = User.query.filter_by(username = form.username.data).first()
if user is None or not user.check_password(form.password.data): login_user(user, remember = form.remember_me.data)
flash("Invalid username or password")
return redirect(url_for("login"))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get("next") next_page = request.args.get("next")
if not next_page or url_parse(next_page).netloc != "": if not next_page or url_parse(next_page).netloc != "":
next_page = url_for("home") next_page = url_for("home")
return redirect(next_page) return redirect(next_page)
return render_template("login.html", form=form) return render_template("login.html", form = form)
@app.route("/register", methods=["GET", "POST"]) @app.route("/register", methods=["GET", "POST"])
def register(): def register():
@ -30,11 +26,10 @@ def register():
return redirect(url_for("index")) return redirect(url_for("index"))
form = RegistrationForm() form = RegistrationForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data) u = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data) u.set_password(form.password.data)
db.session.add(user) db.session.add(u)
db.session.commit() db.session.commit()
flash("Congratulations, you are now a registered user!")
return redirect(url_for("login")) return redirect(url_for("login"))
return render_template("register.html", form=form) return render_template("register.html", form=form)
@ -46,6 +41,8 @@ def logout():
@app.route("/twlogin") @app.route("/twlogin")
@login_required @login_required
def twlogin(): def twlogin():
if current_user.twitter_api.first():
return "Already an api connected."
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key, "https://cyberplanificateur.flifloo.fr/twlogin") auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key, "https://cyberplanificateur.flifloo.fr/twlogin")
if request.args.get("oauth_token") and request.args.get("oauth_verifier"): if request.args.get("oauth_token") and request.args.get("oauth_verifier"):
auth.request_token = {"oauth_token" : request.args.get("oauth_token"), "oauth_token_secret" : request.args.get("oauth_verifier")} auth.request_token = {"oauth_token" : request.args.get("oauth_token"), "oauth_token_secret" : request.args.get("oauth_verifier")}
@ -54,8 +51,7 @@ def twlogin():
except: except:
return "Error ! Failed to get access token" return "Error ! Failed to get access token"
else: else:
twapi = TwitterAPI(access_token = auth.access_token, access_token_secret = auth.access_token_secret, user = current_user) db.session.add(TwitterAPI(access_token = auth.access_token, access_token_secret = auth.access_token_secret, user = current_user))
db.session.add(twapi)
db.session.commit() db.session.commit()
elif not TwitterAPI.query.filter_by(user=current_user).first(): elif not TwitterAPI.query.filter_by(user=current_user).first():
return redirect(auth.get_authorization_url()) return redirect(auth.get_authorization_url())
@ -73,6 +69,8 @@ def twlogout():
@app.route("/trlogin") @app.route("/trlogin")
@login_required @login_required
def trlogin(): def trlogin():
if current_user.twitter_api.first():
return "Already an api connected."
return redirect(f"https://trello.com/1/authorize?expiration=never&name=Cyberplanificateur&scope=read,write&response_type=token&key={trello_credentials.api_key}&return_url=https://cyberplanificateur.flifloo.fr/settings") return redirect(f"https://trello.com/1/authorize?expiration=never&name=Cyberplanificateur&scope=read,write&response_type=token&key={trello_credentials.api_key}&return_url=https://cyberplanificateur.flifloo.fr/settings")
@app.route("/trlogout") @app.route("/trlogout")
@ -91,75 +89,81 @@ def home():
@app.route("/settings", methods = ["POST", "GET"]) @app.route("/settings", methods = ["POST", "GET"])
@login_required @login_required
def settings(): def settings():
trloginfail = False form = TrelloAPIForm()
if "trtoken" in request.form: if form.validate_on_submit():
try: if current_user.trello_api.first():
trello.TrelloClient(api_key = trello_credentials.api_key, token = request.form["trtoken"]).list_boards() return "Already an api connected."
except: db.session.add(TrelloAPI(token = form.token.data, user = current_user))
trloginfail = True db.session.commit()
else: return render_template("settings.html", form = form)
trapi = TrelloAPI(token = request.form["trtoken"], user = current_user)
db.session.add(trapi)
db.session.commit()
return render_template("settings.html", twlogin = TwitterAPI.query.filter_by(user=current_user).first(), trlogin = TrelloAPI.query.filter_by(user=current_user).first(), trloginfail = trloginfail)
@app.route("/dashboard", methods = ["POST", "GET"]) @app.route("/dashboard", methods = ["POST", "GET"])
@login_required
def dashboard(): def dashboard():
if not tw.is_login(session): twapi = current_user.twitter_api.first()
return redirect("/") trapi = current_user.trello_api.first()
twapi = tw.api_login(session)
if request.args.get("twrm"):
database.twrm(request.args.get("twrm"))
if request.args.get("delet"):
twapi.destroy_status(request.args.get["twrm"])
elif "tweet" in request.form and "slots" in request.form and "keywords" in request.form:
try:
slots = int(request.form["slots"])
tweet = int(request.form["tweet"])
except:
formerror = True
else:
database.twadd(twapi.me().id, tweet, slots, str(request.form["keywords"].split(",")))
formerror = False
tweets = list() tweets = list()
idtake = list()
for t in database.twlist(twapi.me().id):
tw = twapi.get_status(t["id"])
keywords = "|"
for te in eval(t["keywords"]):
keywords += f" {te} |"
tweets.append({"text": tw.text, "id": tw.id, "slots": f"{t['slots']}/{t['maxslots']}", "keywords": keywords})
idtake.append(tw.id)
timeline = list() timeline = list()
for t in twapi.user_timeline(): boards = list()
if not t.in_reply_to_status_id and not t.retweeted and t.id not in idtake: columns = list()
timeline.append({"text": t.text, "id": t.id})
if tr.is_login(session): if twapi:
trapi = trello.TrelloClient(api_key = trello_credentials.api_key, token = database.trtoken(twapi.me().id)) twapi = twapi.api_login()
boards = list()
if request.args.get("twrm"):
db.session.delete(Tweets.query.filter_by(user = current_user, statu_id = request.args.get("twrm")).first())
db.session.commit()
if request.args.get("delet"):
twapi.destroy_status(request.args.get["twrm"])
elif "tweet" in request.form and "slots" in request.form and "keywords" in request.form:
try:
slots = int(request.form["slots"])
tweet = int(request.form["tweet"])
except:
formerror = True
else:
db.session.add(Tweets(user = current_user, statu_id = tweet, slots = 0, slots_max = slots, keywords = str(request.form["keywords"].split(","))))
db.session.commit()
elif "board" in request.form:
db.session.add(Boards(user = current_user, board_id = request.form["board"]))
db.session.commit()
elif "column" in request.form:
current_user.boards.first().column_id = request.form["column"]
db.session.commit()
for t in Tweets.query.filter_by(user = current_user):
statu = twapi.get_status(t.statu_id)
keywords = "|"
for text in eval(t.keywords):
keywords += f" {text} |"
tweets.append({"text": statu.text, "id": t.statu_id, "slots": f"{t.slots}/{t.slots_max}", "keywords": keywords})
for t in twapi.user_timeline():
if not t.in_reply_to_status_id and not t.retweeted and not Tweets.query.filter_by(user = current_user, statu_id = t.id).first():
timeline.append({"text": t.text, "id": t.id})
if trapi:
trapi = trapi.api_login()
for b in trapi.list_boards(): for b in trapi.list_boards():
boards.append(b.name) select = False
if b.id == current_user.boards.first().board_id:
select = True
boards.append({"text": b.name, "id": b.id, "select": select})
if current_user.boards.first() and current_user.boards.first().board_id:
for c in trapi.get_board(current_user.boards.first().board_id).list_lists():
select = False
if c.id == current_user.boards.first().column_id:
select = True
columns.append({"text": c.name, "id": c.id, "select": select})
return render_template("dashboard.html", login = True, tweets = tweets, timeline = timeline, boards = boards, columns = None) return render_template("dashboard.html", timeline = timeline, tweets = tweets, boards = boards, columns = columns)
@app.route("/twtoken") @app.route("/twtoken")
def twtoken(): def twtoken():
return f"{session['access_token']} -- {session['access_secret_token']}" return f"{session['access_token']} -- {session['access_secret_token']}"
@app.route("/twpost")
def twpost():
if tw.is_login(session):
api = twapi_login(session)
api.update_status("bloup")
return "Send !"
else:
return "Not login !"
@app.route("/test") @app.route("/test")
def test(): def test():
return render_template("elements.html") return render_template("elements.html")

View file

@ -12,6 +12,7 @@
<h3>Current automatized tweets</h3> <h3>Current automatized tweets</h3>
<hr /> <hr />
{% if current_user.twitter_api.first() %}
<table> <table>
<thead> <thead>
<tr> <tr>
@ -33,10 +34,14 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %}
<h4>Twitter API not connected</h4>
{% endif %}
<hr /> <hr />
<h3>Add an automatized tweet</h3> <h3>Add an automatized tweet</h3>
<hr /> <hr />
{% if current_user.twitter_api.first() %}
<form action = "https://cyberplanificateur.flifloo.fr/dashboard" method = "POST"> <form action = "https://cyberplanificateur.flifloo.fr/dashboard" method = "POST">
<section> <section>
<div class="row"> <div class="row">
@ -54,12 +59,16 @@
</div> </div>
</div> </div>
</section> </section>
<input type="submit" value="Connect" class="button primary"> <input type="submit" value="Select" class="button primary">
</form> </form>
{% else %}
<h4>Twitter API not connected</h4>
{% endif %}
<hr /> <hr />
<h3>Trello board</h3> <h3>Trello board</h3>
<hr /> <hr />
{% if current_user.trello_api.first() %}
<section> <section>
<div class="row"> <div class="row">
<div class="col-6 col-12-medium"> <div class="col-6 col-12-medium">
@ -67,7 +76,7 @@
<form action="https://cyberplanificateur.flifloo.fr/dashboard" method="POST"> <form action="https://cyberplanificateur.flifloo.fr/dashboard" method="POST">
{% for b in boards %} {% for b in boards %}
<div class="col-4 col-12-small"> <div class="col-4 col-12-small">
<input type="radio" id="{{ b['id'] }}" name="board" value="{{ b['id'] }}"> <input type="radio" id="{{ b['id'] }}" name="board" value="{{ b['id'] }}" {% if b['select'] %} checked="checked" {% endif %}>
<label for="{{ b['id'] }}">{{ b['text'] }}</label> <label for="{{ b['id'] }}">{{ b['text'] }}</label>
</div> </div>
{% endfor %} {% endfor %}
@ -76,18 +85,25 @@
</div> </div>
<div class="col-6 col-12-medium"> <div class="col-6 col-12-medium">
<h4>Choose a column</h4> <h4>Choose a column</h4>
{% if columns %}
<form action="https://cyberplanificateur.flifloo.fr/dashboard" method="POST"> <form action="https://cyberplanificateur.flifloo.fr/dashboard" method="POST">
{% for c in columns %} {% for c in columns %}
<div class="col-4 col-12-small"> <div class="col-4 col-12-small">
<input type="radio" id="{{ c['id'] }}" name="column" value="{{ c['id'] }}"> <input type="radio" id="{{ c['id'] }}" name="column" value="{{ c['id'] }}" {% if c['select'] %} checked="checked" {% endif %}>
<label for="{{ c['id'] }}">{{ c['text'] }}</label> <label for="{{ c['id'] }}">{{ c['text'] }}</label>
</div> </div>
{% endfor %} {% endfor %}
<input type="submit" value="Select" class="button primary"> <input type="submit" value="Select" class="button primary">
</form> </form>
{% else %}
<p>No board set yet</p>
{% endif %}
</div> </div>
</div> </div>
</section> </section>
{% else %}
<h4>Trello API not connected</h4>
{% endif %}
<hr /> <hr />
</div> </div>
</section> </section>

View file

@ -14,10 +14,16 @@
<p> <p>
{{ form.username.label }}<br> {{ form.username.label }}<br>
{{ form.username(size=32) }} {{ form.username(size=32) }}
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p> </p>
<p> <p>
{{ form.password.label }}<br> {{ form.password.label }}<br>
{{ form.password(size=32) }} {{ form.password(size=32) }}
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p> </p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p> <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p> <p>{{ form.submit() }}</p>

View file

@ -12,7 +12,7 @@
<h3>Connexions</h3> <h3>Connexions</h3>
<p>Twitter : <p>Twitter :
{% if twlogin %} {% if current_user.twitter_api.first() %}
<font color="green">Connected</font></p> <font color="green">Connected</font></p>
<a href="{{ url_for('twlogout') }}" class="button">Disconnect</a> <a href="{{ url_for('twlogout') }}" class="button">Disconnect</a>
{% else %} {% else %}
@ -20,15 +20,22 @@
<a href="{{ url_for('twlogin') }}" class="button primary">Connect</a> <a href="{{ url_for('twlogin') }}" class="button primary">Connect</a>
{% endif %} {% endif %}
<p>Trello : <p>Trello :
{% if trlogin %} {% if current_user.trello_api.first() %}
<font color="green">Connected</font></p> <font color="green">Connected</font></p>
<a href="{{ url_for('trlogout') }}" class="button">Disconnect</a> <a href="{{ url_for('trlogout') }}" class="button">Disconnect</a>
{% else %} {% else %}
<font color="red">Disconnected</font></p> <font color="red">Disconnected</font></p>
<p><a href="{{ url_for('trlogin') }}">Click here</a> and give the token bellow</p> <p><a href="{{ url_for('trlogin') }}">Click here</a> and give the token bellow</p>
<form action = "https://cyberplanificateur.flifloo.fr/settings" method = "POST"> <form action = "" method = "POST" novalidate>
<input type="text" name="trtoken" placeholder="Trello token" /> {{ form.hidden_tag() }}
<input type="submit" value="Connect" class="button primary"> <p>
{{ form.token.label }}
{{ form.token(size=64) }}
{% for error in form.token.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form> </form>
{% endif %} {% endif %}

View file

@ -1,68 +0,0 @@
import sqlite3
def tradd(id, token):
with sqlite3.connect('database.db') as db:
sucess = True
dbc = db.cursor()
for u in dbc.execute("SELECT * FROM trello WHERE id=?", (id,)):
sucess = False
if sucess:
dbc.execute("INSERT INTO trello (id, token) VALUES (?, ?)", (id, token,))
dbc.close()
db.commit()
return sucess
def trrm(id):
with sqlite3.connect('database.db') as db:
dbc = db.cursor()
dbc.execute("DELETE FROM trello WHERE id=?", (id,))
dbc.close()
db.commit()
def trtoken(id):
with sqlite3.connect('database.db') as db:
token = False
dbc = db.cursor()
for u in dbc.execute("SELECT * FROM trello WHERE id=?;", (id,)):
token = u
dbc.close()
return token
def twadd(user, id, maxslots, keywords):
with sqlite3.connect('database.db') as db:
dbc = db.cursor()
dbc.execute("INSERT INTO tweets (user, id, slots, maxslots, keywords) VALUES (?, ?, ?, ?, ?)", (user, id, 0, maxslots, keywords,))
dbc.close()
db.commit()
def twrm(id):
with sqlite3.connect('database.db') as db:
dbc = db.cursor()
dbc.execute("DELETE FROM tweets WHERE id=?", (id,))
dbc.close()
db.commit()
def twlist(user):
with sqlite3.connect('database.db') as db:
dbc = db.cursor()
tweets = list()
for u in dbc.execute("SELECT * FROM tweets WHERE user=?", (user,)):
tweets.append({"id": u[1], "slots": u[2], "maxslots": u[3], "keywords": u[4]})
dbc.close()
db.commit()
return tweets
with sqlite3.connect('database.db') as db:
Table = False
dbc = db.cursor()
log = False
iid = None
for t in dbc.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='trello';"):
Table = True
if not Table:
dbc.execute('''CREATE TABLE trello (id int, token text)''')
dbc.execute('''CREATE TABLE tweets (user int, id int, slots int, maxslots int, keywords text)''')
dbc.close()
db.commit()
#register("flifloo", "flifloo@gmail.com", "owo")

5
tr.py
View file

@ -1,5 +0,0 @@
import trello
import tw
def is_trlogin(session):
return is_twkeys(session) and database.trtoken(tw.api_login(session).me().id)

18
tw.py
View file

@ -1,18 +0,0 @@
import tweepy, twitter_credentials
def api_login(session):
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key)
auth.set_access_token(session["access_token"], session["access_secret_token"])
return tweepy.API(auth)
def is_keys(session):
try:
session["access_token"]
session["access_secret_token"]
except:
return False
else:
return True
def is_login(session):
return is_keys(session) and api_login(session).verify_credentials()