Include bearer token in twitter account (#1881)

* Got twitter forwarder working with bearer token in twitter accounts table

* Update frontend to use bearer token in twitter account

* Update frontend build
This commit is contained in:
Jonathan Zernik 2021-12-22 16:10:03 -08:00 committed by GitHub
parent ae88dcc75f
commit 9f0d5e4097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 164 additions and 462 deletions

View file

@ -29,11 +29,13 @@ export default function AddTwitterAccountDialog({
const classes = useStyles();
const [twitterHandle, setTwitterHandle] = useState('');
const [bearerToken, setBearerToken] = useState('');
const [profileId, setProfileId] = useState(-1);
const [signingProfiles, setSigningProfiles] = useState([]);
const resetFields = () => {
setTwitterHandle('');
setBearerToken('');
setProfileId(-1);
};
@ -41,6 +43,10 @@ export default function AddTwitterAccountDialog({
setTwitterHandle(event.target.value);
};
const handleChangeBearerToken = (event) => {
setBearerToken(event.target.value);
};
const handleChangeProfileId = (event) => {
setProfileId(event.target.value);
};
@ -53,8 +59,8 @@ export default function AddTwitterAccountDialog({
alert(`Error adding twitter account: ${err}`);
};
const addTwitterAccount = (twitterHandle, profileId) => {
addTwitterAccountRequest(twitterHandle, profileId, handleResponse, handleErr);
const addTwitterAccount = (twitterHandle, profileId, bearerToken) => {
addTwitterAccountRequest(twitterHandle, profileId, bearerToken, handleResponse, handleErr);
};
const loadSigningProfiles = () => {
@ -73,7 +79,11 @@ export default function AddTwitterAccountDialog({
alert('twitterHandle cannot be empty.');
return;
}
addTwitterAccount(twitterHandle, profileId);
if (!bearerToken) {
alert('bearerToken cannot be empty.');
return;
}
addTwitterAccount(twitterHandle, profileId, bearerToken);
handleClose();
}
@ -107,6 +117,22 @@ export default function AddTwitterAccountDialog({
);
}
function BearerTokenInput() {
return (
<TextField
id="standard-textarea-bearer-token"
label="Bearer Token"
variant="outlined"
margin="normal"
required
value={bearerToken}
onChange={handleChangeBearerToken}
fullWidth
inputProps={{ maxLength: 128 }}
/>
);
}
function SelectSigningProfile() {
return (
<FormControl className={classes.formControl} required style={{ minWidth: 120 }}>
@ -156,6 +182,7 @@ export default function AddTwitterAccountDialog({
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
<DialogContent>
{AccountHandleInput()}
{BearerTokenInput()}
{SelectSigningProfile()}
</DialogContent>
<DialogActions>

View file

@ -1,121 +0,0 @@
import React, { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
TextField,
DialogActions,
Button,
} from '@material-ui/core';
// styles
import useStyles from './styles';
import {
setTwitterBearerTokenRequest,
} from '../../squeakclient/requests';
export default function SetBearerTokenDialog({
open,
handleClose,
reloadBearerTokenFn,
...props
}) {
const classes = useStyles();
const [bearerToken, setBearerToken] = useState('');
const resetFields = () => {
setBearerToken('');
};
const handleChangeBearerToken = (event) => {
setBearerToken(event.target.value);
};
const handleResponse = (response) => {
// goToProfilePage(history, response.getProfileId());
// TODO
if (reloadBearerTokenFn) {
reloadBearerTokenFn();
}
};
const handleErr = (err) => {
alert(`Error setting bearer token: ${err}`);
};
const setBearerTokenRequest = (bearerToken) => {
setTwitterBearerTokenRequest(bearerToken, handleResponse, handleErr);
};
function handleSubmit(event) {
event.preventDefault();
console.log('bearerToken:', bearerToken);
if (!bearerToken) {
alert('Bearer Token cannot be empty.');
return;
}
setBearerTokenRequest(bearerToken);
handleClose();
}
function SetBearerTokenInput() {
return (
<TextField
id="standard-textarea"
label="Bearer Token"
variant="outlined"
margin="normal"
required
autoFocus
value={bearerToken}
onChange={handleChangeBearerToken}
fullWidth
inputProps={{ maxLength: 256 }}
/>
);
}
function CancelButton() {
return (
<Button
onClick={handleClose}
variant="contained"
color="secondary"
>
Cancel
</Button>
);
}
function SetBearerTokenButton() {
return (
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
Set Bearer Token
</Button>
);
}
return (
<Dialog open={open} onEnter={resetFields} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Set Bearer Token</DialogTitle>
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
<DialogContent>
{SetBearerTokenInput()}
</DialogContent>
<DialogActions>
{CancelButton()}
{SetBearerTokenButton()}
</DialogActions>
</form>
</Dialog>
);
}

View file

@ -1,6 +0,0 @@
{
"name": "SetBearerTokenDialog",
"version": "0.0.0",
"private": true,
"main": "SetBearerTokenDialog.js"
}

View file

@ -1,43 +0,0 @@
import { makeStyles } from '@material-ui/styles';
export default makeStyles((theme) => ({
widgetWrapper: {
display: 'flex',
minHeight: '100%',
},
widgetHeader: {
padding: theme.spacing(3),
paddingBottom: theme.spacing(1),
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
widgetRoot: {
boxShadow: theme.customShadows.widget,
},
widgetBody: {
paddingBottom: theme.spacing(3),
paddingRight: theme.spacing(3),
paddingLeft: theme.spacing(3),
},
noPadding: {
padding: 0,
},
paper: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
overflow: 'hidden',
},
moreButton: {
margin: -theme.spacing(1),
padding: 0,
width: 40,
height: 40,
color: theme.palette.text.hint,
'&:hover': {
backgroundColor: theme.palette.primary.main,
color: 'rgba(255, 255, 255, 0.35)',
},
},
}));

View file

@ -7,35 +7,25 @@ import {
AppBar,
Box,
CircularProgress,
Typography,
} from '@material-ui/core';
// styles
// styles
import useStyles from './styles';
// components
import Widget from '../../components/Widget';
import SetBearerTokenDialog from '../../components/SetBearerTokenDialog';
import AddTwitterAccountDialog from '../../components/AddTwitterAccountDialog';
import TwitterAccountListItem from '../../components/TwitterAccountListItem';
import CheckIcon from '@mui/icons-material/Check';
import CloudOffIcon from '@mui/icons-material/CloudOff';
import {
getTwitterAccountsRequest,
getTwitterStreamStatusRequest,
} from '../../squeakclient/requests';
export default function Twitter() {
const classes = useStyles();
const [accounts, setAccounts] = useState([]);
const [streamStatus, setStreamStatus] = useState(null);
const [waitingForAccounts, setWaitingForAccounts] = useState(false);
const [setBearerTokenDialogOpen, setSetBearerTokenDialogOpen] = useState(false);
const [addAccountDialogOpen, setAddAccountDialogOpen] = useState(false);
@ -54,20 +44,6 @@ export default function Twitter() {
});
};
const getStreamStatus = () => {
getTwitterStreamStatusRequest((resp) => {
setStreamStatus(resp);
});
};
const handleClickOpenSetBearerTokenDialog = () => {
setSetBearerTokenDialogOpen(true);
};
const handleCloseSetBearerTokenDialog = () => {
setSetBearerTokenDialogOpen(false);
};
const handleClickOpenAddAccountDialog = () => {
setAddAccountDialogOpen(true);
};
@ -79,9 +55,7 @@ export default function Twitter() {
useEffect(() => {
getAccounts();
}, []);
useEffect(() => {
getStreamStatus();
}, []);
function TabPanel(props) {
const {
@ -103,43 +77,6 @@ export default function Twitter() {
);
}
function StreamStatusSummary() {
const isStreamActive = (streamStatus ? streamStatus.getIsStreamActive() : false);
return (
<Grid item xs={12}>
<Box
p={1}
>
{isStreamActive
? StreamActiveDisplay()
: StreamNotActiveDisplay()
}
</Box>
</Grid>
);
}
function StreamActiveDisplay() {
return (
<>
<Typography variant="h5" component="h5">
Twitter mirroring successfully
</Typography>
<CheckIcon fontSize="large" style={{ fill: 'green' }} />
</>
);
}
function StreamNotActiveDisplay() {
return (
<>
<Typography variant="h5" component="h5">
Twitter not connected
</Typography>
<CloudOffIcon fontSize="large" style={{ fill: 'red' }} />
</>
);
}
function AccountsGridItem(accounts) {
return (
@ -173,25 +110,6 @@ export default function Twitter() {
);
}
function SetBearerTokenButton() {
return (
<>
<Grid item xs={12}>
<div className={classes.root}>
<Button
variant="contained"
onClick={() => {
handleClickOpenSetBearerTokenDialog();
}}
>
Set Bearer Token
</Button>
</div>
</Grid>
</>
);
}
function AddAccountButton() {
return (
<>
@ -223,9 +141,7 @@ export default function Twitter() {
<Grid container spacing={4}>
<Grid item xs={12}>
<Widget disableWidgetMenu>
{SetBearerTokenButton()}
{AddAccountButton()}
{StreamStatusSummary()}
{AccountsContent()}
</Widget>
</Grid>
@ -251,17 +167,6 @@ export default function Twitter() {
);
}
function SetBearerTokenDialogContent() {
return (
<>
<SetBearerTokenDialog
open={setBearerTokenDialogOpen}
handleClose={handleCloseSetBearerTokenDialog}
/>
</>
);
}
function AddAccountDialogContent() {
return (
<>
@ -288,7 +193,6 @@ export default function Twitter() {
return (
<>
{GridContent()}
{SetBearerTokenDialogContent()}
{AddAccountDialogContent()}
< />
);

View file

@ -125,10 +125,6 @@ import {
GetPeerByAddressReply,
GetDefaultPeerPortRequest,
GetDefaultPeerPortReply,
GetTwitterBearerTokenRequest,
GetTwitterBearerTokenReply,
SetTwitterBearerTokenRequest,
SetTwitterBearerTokenReply,
GetTwitterAccountsRequest,
GetTwitterAccountsReply,
AddTwitterAccountRequest,
@ -1160,29 +1156,6 @@ export function getDefaultPeerPortRequest(handleResponse) {
);
}
export function setTwitterBearerTokenRequest(bearerToken, handleResponse) {
const request = new SetTwitterBearerTokenRequest();
request.setBearerToken(bearerToken);
makeRequest(
'settwitterbearertoken',
request,
SetTwitterBearerTokenReply.deserializeBinary,
handleResponse,
);
}
export function getTwitterBearerTokenRequest(handleResponse) {
const request = new GetTwitterBearerTokenRequest();
makeRequest(
'gettwitterbearertoken',
request,
GetTwitterBearerTokenReply.deserializeBinary,
(response) => {
handleResponse(response.getBearerToken());
},
);
}
export function getTwitterAccountsRequest(handleResponse) {
const request = new GetTwitterAccountsRequest();
makeRequest(
@ -1195,10 +1168,11 @@ export function getTwitterAccountsRequest(handleResponse) {
);
}
export function addTwitterAccountRequest(twitterHandle, profileId, handleResponse) {
export function addTwitterAccountRequest(twitterHandle, profileId, bearerToken, handleResponse) {
const request = new AddTwitterAccountRequest();
request.setHandle(twitterHandle);
request.setProfileId(profileId);
request.setBearerToken(bearerToken);
makeRequest(
'addtwitteraccount',
request,

View file

@ -336,14 +336,6 @@ service SqueakAdmin {
*/
rpc GetSellPrice (GetSellPriceRequest) returns (GetSellPriceReply) {}
/** sqkadmin: `settwitterbearertoken`
*/
rpc SetTwitterBearerToken (SetTwitterBearerTokenRequest) returns (SetTwitterBearerTokenReply) {}
/** sqkadmin: `gettwitterbearertoken`
*/
rpc GetTwitterBearerToken (GetTwitterBearerTokenRequest) returns (GetTwitterBearerTokenReply) {}
/** sqkadmin: `addtwitteraccount`
*/
rpc AddTwitterAccount (AddTwitterAccountRequest) returns (AddTwitterAccountReply) {}
@ -1273,22 +1265,6 @@ message GetSellPriceReply {
int64 default_price_msat = 3;
}
message SetTwitterBearerTokenRequest {
/// The bearer token string.
string bearer_token = 1;
}
message SetTwitterBearerTokenReply {
}
message GetTwitterBearerTokenRequest {
}
message GetTwitterBearerTokenReply {
/// The bearer token string.
string bearer_token = 1;
}
message TwitterAccount {
/// The twitter account id
int32 twitter_account_id = 1;
@ -1312,6 +1288,9 @@ message AddTwitterAccountRequest {
/// The profile id
int32 profile_id = 2;
/// The bearer token string.
string bearer_token = 3;
}
message AddTwitterAccountReply {

View file

@ -1071,31 +1071,19 @@ class SqueakAdminServerHandler(object):
default_price_msat=default_sell_price_msat,
)
def handle_set_twitter_bearer_token(self, request):
twitter_bearer_token = request.bearer_token
logger.info("Handle set twitter bearer token with value: {}".format(
twitter_bearer_token,
))
self.squeak_controller.set_twitter_bearer_token(twitter_bearer_token)
return squeak_admin_pb2.SetTwitterBearerTokenReply()
def handle_get_twitter_bearer_token(self, request):
logger.info("Handle get twitter bearer token")
twitter_bearer_token = self.squeak_controller.get_twitter_bearer_token()
return squeak_admin_pb2.GetTwitterBearerTokenReply(
bearer_token=twitter_bearer_token,
)
def handle_add_twitter_account(self, request):
handle = request.handle
profile_id = request.profile_id
logger.info("Handle add twitter account with handle: {} and profile_id: {}".format(
bearer_token = request.bearer_token
logger.info("Handle add twitter account with handle: {}, profile_id: {}, bearer token: {}".format(
handle,
profile_id,
bearer_token,
))
twitter_account_id = self.squeak_controller.add_twitter_account(
handle,
profile_id,
bearer_token,
)
return squeak_admin_pb2.AddTwitterAccountReply(
twitter_account_id=twitter_account_id,

View file

@ -529,18 +529,6 @@ def create_app(handler, username, password):
def getsellprice(msg):
return handler.handle_get_sell_price(msg)
@app.route("/settwitterbearertoken", methods=["POST"])
@login_required
@protobuf_serialized(squeak_admin_pb2.SetTwitterBearerTokenRequest())
def settwitterbearertoken(msg):
return handler.handle_set_twitter_bearer_token(msg)
@app.route("/gettwitterbearertoken", methods=["POST"])
@login_required
@protobuf_serialized(squeak_admin_pb2.GetTwitterBearerTokenRequest())
def gettwitterbearertoken(msg):
return handler.handle_get_twitter_bearer_token(msg)
@app.route("/addtwitteraccount", methods=["POST"])
@login_required
@protobuf_serialized(squeak_admin_pb2.AddTwitterAccountRequest())

View file

@ -1,15 +1,15 @@
{
"files": {
"main.js": "/static/js/main.01fc1efa.chunk.js",
"main.js.map": "/static/js/main.01fc1efa.chunk.js.map",
"main.js": "/static/js/main.170c2962.chunk.js",
"main.js.map": "/static/js/main.170c2962.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.48a1d1b9.js",
"runtime-main.js.map": "/static/js/runtime-main.48a1d1b9.js.map",
"static/css/2.f68b60d4.chunk.css": "/static/css/2.f68b60d4.chunk.css",
"static/js/2.cc4c0ab4.chunk.js": "/static/js/2.cc4c0ab4.chunk.js",
"static/js/2.cc4c0ab4.chunk.js.map": "/static/js/2.cc4c0ab4.chunk.js.map",
"static/js/2.033499f7.chunk.js": "/static/js/2.033499f7.chunk.js",
"static/js/2.033499f7.chunk.js.map": "/static/js/2.033499f7.chunk.js.map",
"index.html": "/index.html",
"static/css/2.f68b60d4.chunk.css.map": "/static/css/2.f68b60d4.chunk.css.map",
"static/js/2.cc4c0ab4.chunk.js.LICENSE.txt": "/static/js/2.cc4c0ab4.chunk.js.LICENSE.txt",
"static/js/2.033499f7.chunk.js.LICENSE.txt": "/static/js/2.033499f7.chunk.js.LICENSE.txt",
"static/media/font-awesome.min.css": "/static/media/fontawesome-webfont.f691f37e.woff",
"static/media/google.a4fa9b6f.svg": "/static/media/google.a4fa9b6f.svg",
"static/media/logo.31df0ce8.svg": "/static/media/logo.31df0ce8.svg"
@ -17,7 +17,7 @@
"entrypoints": [
"static/js/runtime-main.48a1d1b9.js",
"static/css/2.f68b60d4.chunk.css",
"static/js/2.cc4c0ab4.chunk.js",
"static/js/main.01fc1efa.chunk.js"
"static/js/2.033499f7.chunk.js",
"static/js/main.170c2962.chunk.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><meta name="description" content="Squeaknode is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.f68b60d4.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.cc4c0ab4.chunk.js"></script><script src="/static/js/main.01fc1efa.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><meta name="description" content="Squeaknode is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.f68b60d4.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.033499f7.chunk.js"></script><script src="/static/js/main.170c2962.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -28,3 +28,4 @@ class TwitterAccount(NamedTuple):
twitter_account_id: Optional[int]
handle: str
profile_id: int
bearer_token: str

View file

@ -30,4 +30,5 @@ class TwitterAccountEntry(NamedTuple):
twitter_account_id: Optional[int]
handle: str
profile_id: int
bearer_token: str
profile: Optional[SqueakProfile]

View file

@ -0,0 +1,55 @@
# MIT License
#
# Copyright (c) 2020 Jonathan Zernik
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Include bearer token in twitter account.
Revision ID: b78f8169d063
Revises: b6cf462aaf7c
Create Date: 2021-12-22 13:49:11.692654
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = 'b78f8169d063'
down_revision = 'b6cf462aaf7c'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('config', schema=None) as batch_op:
batch_op.drop_column('twitter_bearer_token')
with op.batch_alter_table('twitter_account', schema=None) as batch_op:
batch_op.add_column(sa.Column('bearer_token', sa.String(
), nullable=False, server_default=sa.text("''")))
def downgrade():
with op.batch_alter_table('twitter_account', schema=None) as batch_op:
batch_op.drop_column('bearer_token')
with op.batch_alter_table('config', schema=None) as batch_op:
batch_op.add_column(
sa.Column('twitter_bearer_token', sa.VARCHAR(), nullable=True))

View file

@ -193,7 +193,6 @@ class Models:
"config",
self.metadata,
Column("username", String, primary_key=True),
Column("twitter_bearer_token", String, nullable=True),
Column("sell_price_msat", Integer, nullable=True),
)
@ -203,4 +202,5 @@ class Models:
Column("twitter_account_id", Integer, primary_key=True),
Column("handle", String, unique=True, nullable=False),
Column("profile_id", Integer, nullable=False),
Column("bearer_token", String, nullable=False),
)

View file

@ -1357,7 +1357,6 @@ class SqueakDb:
"""
ins = self.configs.insert().values(
username=user_config.username,
twitter_bearer_token=user_config.twitter_bearer_token,
)
with self.get_connection() as connection:
try:
@ -1378,16 +1377,6 @@ class SqueakDb:
return None
return self._parse_user_config(row)
def set_config_twitter_bearer_token(self, username: str, twitter_bearer_token: str) -> None:
""" Set a config twitter bearer token. """
stmt = (
self.configs.update()
.where(self.configs.c.username == username)
.values(twitter_bearer_token=twitter_bearer_token)
)
with self.get_connection() as connection:
connection.execute(stmt)
def set_config_sell_price_msat(self, username: str, sell_price_msat: int) -> None:
""" Set a config sell price msat. """
stmt = (
@ -1417,6 +1406,7 @@ class SqueakDb:
ins = self.twitter_accounts.insert().values(
handle=twitter_account.handle,
profile_id=twitter_account.profile_id,
bearer_token=twitter_account.bearer_token,
)
with self.get_connection() as connection:
try:
@ -1600,7 +1590,6 @@ class SqueakDb:
def _parse_user_config(self, row) -> UserConfig:
return UserConfig(
username=row["username"],
twitter_bearer_token=row["twitter_bearer_token"],
sell_price_msat=row["sell_price_msat"],
)
@ -1610,5 +1599,6 @@ class SqueakDb:
twitter_account_id=row["twitter_account_id"],
handle=row["handle"],
profile_id=row["profile_id"],
bearer_token=row["bearer_token"],
profile=profile,
)

View file

@ -893,30 +893,15 @@ class SqueakController:
def get_default_sell_price_msat(self) -> int:
return self.config.node.price_msat
def set_twitter_bearer_token(self, twitter_bearer_token: str) -> None:
self.insert_user_config()
self.squeak_db.set_config_twitter_bearer_token(
username=self.config.webadmin.username,
twitter_bearer_token=twitter_bearer_token,
)
self.create_update_twitter_stream_event()
def get_twitter_bearer_token(self) -> Optional[str]:
user_config = self.squeak_db.get_config(
username=self.config.webadmin.username,
)
if user_config is None:
return None
return user_config.twitter_bearer_token
def get_twitter_stream_status(self) -> bool:
return self.tweet_forwarder.is_processing()
def add_twitter_account(self, handle: str, profile_id: int) -> Optional[int]:
def add_twitter_account(self, handle: str, profile_id: int, bearer_token: str) -> Optional[int]:
twitter_account = TwitterAccount(
twitter_account_id=None,
handle=handle,
profile_id=profile_id,
bearer_token=bearer_token,
)
account_id = self.squeak_db.insert_twitter_account(twitter_account)
self.create_update_twitter_stream_event()

View file

@ -21,8 +21,7 @@
# SOFTWARE.
import logging
import threading
from typing import List
from typing import Optional
from typing import Dict
from squeaknode.core.twitter_account_entry import TwitterAccountEntry
from squeaknode.node.squeak_controller import SqueakController
@ -40,27 +39,37 @@ class TwitterForwarder:
):
self.retry_s = retry_s
self.lock = threading.Lock()
self.current_task: Optional[TwitterForwarderTask] = None
self.current_tasks: Dict[str, TwitterForwarderTask] = {}
def start_processing(self, squeak_controller: SqueakController):
with self.lock:
if self.current_task is not None:
self.current_task.stop_processing()
self.current_task = TwitterForwarderTask(
squeak_controller,
self.retry_s,
)
self.current_task.start_processing()
# Stop existing running tasks.
for handle, task in list(self.current_tasks.items()):
task.stop_processing()
del self.current_tasks[handle]
# Start new tasks.
for account in squeak_controller.get_twitter_accounts():
task = TwitterForwarderTask(
squeak_controller,
account,
self.retry_s,
)
task.start_processing()
self.current_tasks[account.handle] = task
def stop_processing(self):
with self.lock:
if self.current_task is not None:
self.current_task.stop_processing()
for handle, task in list(self.current_tasks.items()):
task.stop_processing()
del self.current_tasks[handle]
def is_processing(self) -> bool:
with self.lock:
if self.current_task is not None:
return self.current_task.is_processing()
# if self.current_task is not None:
# return self.current_task.is_processing()
# return False
# TODO:
return False
@ -69,9 +78,11 @@ class TwitterForwarderTask:
def __init__(
self,
squeak_controller: SqueakController,
twitter_account: TwitterAccountEntry,
retry_s: int,
):
self.squeak_controller = squeak_controller
self.twitter_account = twitter_account
self.retry_s = retry_s
self.stopped = threading.Event()
self.tweet_stream = None
@ -84,15 +95,18 @@ class TwitterForwarderTask:
daemon=True,
).start()
def setup_stream(self, bearer_token, handles):
logger.info("Starting Twitter stream with bearer token: {} and twitter handles: {}".format(
bearer_token,
handles,
def setup_stream(self):
logger.info("Starting Twitter stream with bearer token: {} and twitter handle: {}".format(
self.twitter_account.bearer_token,
self.twitter_account.handle,
))
with self.lock:
if self.stopped.is_set():
return
twitter_stream = TwitterStream(bearer_token, handles)
twitter_stream = TwitterStream(
self.twitter_account.bearer_token,
[self.twitter_account.handle],
)
self.tweet_stream = twitter_stream.get_tweets()
def stop_processing(self):
@ -111,13 +125,7 @@ class TwitterForwarderTask:
while not self.stopped.is_set():
try:
bearer_token = self.get_bearer_token()
handles = self.get_twitter_handles()
if not bearer_token:
return
if not handles:
return
self.setup_stream(bearer_token, handles)
self.setup_stream()
for tweet in self.tweet_stream.result_stream:
self.handle_tweet(tweet)
# TODO: use more specific error.
@ -131,23 +139,20 @@ class TwitterForwarderTask:
self.stopped.wait(wait_s)
wait_s *= 2
def get_bearer_token(self) -> str:
return self.squeak_controller.get_twitter_bearer_token() or ''
# def get_twitter_handles(self) -> List[str]:
# twitter_accounts = self.squeak_controller.get_twitter_accounts()
# handles = [account.handle for account in twitter_accounts]
# return handles
def get_twitter_handles(self) -> List[str]:
twitter_accounts = self.squeak_controller.get_twitter_accounts()
handles = [account.handle for account in twitter_accounts]
return handles
def is_tweet_a_match(self, tweet: dict, account: TwitterAccountEntry) -> bool:
def is_tweet_a_match(self, tweet: dict) -> bool:
for rule in tweet['matching_rules']:
if rule['tag'] == account.handle:
if rule['tag'] == self.twitter_account.handle:
return True
return False
def forward_tweet(self, tweet: dict, account: TwitterAccountEntry) -> None:
def forward_tweet(self, tweet: dict) -> None:
self.squeak_controller.make_squeak(
profile_id=account.profile_id,
profile_id=self.twitter_account.profile_id,
content_str=tweet['data']['text'],
replyto_hash=None,
)
@ -155,7 +160,5 @@ class TwitterForwarderTask:
def handle_tweet(self, tweet: dict):
logger.info(
"Got tweet: {}".format(tweet))
twitter_accounts = self.squeak_controller.get_twitter_accounts()
for account in twitter_accounts:
if self.is_tweet_a_match(tweet, account):
self.forward_tweet(tweet, account)
if self.is_tweet_a_match(tweet):
self.forward_tweet(tweet)

View file

@ -386,20 +386,6 @@ def duplicate_inserted_user_config_username(squeak_db, user_config, inserted_use
yield squeak_db.insert_config(user_config)
@pytest.fixture
def user_config_with_twitter_bearer_token_username(
squeak_db,
user_config,
inserted_user_config_username,
twitter_bearer_token,
):
squeak_db.set_config_twitter_bearer_token(
inserted_user_config_username,
twitter_bearer_token,
)
yield inserted_user_config_username
@pytest.fixture
def user_config_with_sell_price_msat_username(
squeak_db,
@ -415,11 +401,12 @@ def user_config_with_sell_price_msat_username(
@pytest.fixture
def twitter_account(inserted_signing_profile_id):
def twitter_account(inserted_signing_profile_id, twitter_bearer_token):
yield TwitterAccount(
twitter_account_id=None,
handle="fake_twitter_handle",
profile_id=inserted_signing_profile_id,
bearer_token=twitter_bearer_token,
)
@ -1577,17 +1564,6 @@ def test_get_config_missing(squeak_db):
assert retrieved_config is None
def test_set_twitter_bearer_token(
squeak_db,
user_config_with_twitter_bearer_token_username,
twitter_bearer_token,
):
retrieved_config = squeak_db.get_config(
user_config_with_twitter_bearer_token_username)
assert retrieved_config.twitter_bearer_token == twitter_bearer_token
def test_set_sell_price_msat(
squeak_db,
user_config_with_sell_price_msat_username,
@ -1609,6 +1585,7 @@ def test_get_twitter_account(
assert retrieved_twitter_accounts[0].handle == twitter_account.handle
assert retrieved_twitter_accounts[0].profile_id == twitter_account.profile_id
assert retrieved_twitter_accounts[0].bearer_token == twitter_account.bearer_token
assert retrieved_twitter_accounts[0].profile._replace(profile_id=None) == \
signing_profile