diff --git a/tbot/twitch_bot/functions/quotes.py b/tbot/twitch_bot/functions/quotes.py index cecaf48..0dc2107 100644 --- a/tbot/twitch_bot/functions/quotes.py +++ b/tbot/twitch_bot/functions/quotes.py @@ -1,5 +1,4 @@ from tbot.twitch_bot.var_filler import fills_vars, Send_error -from tbot import config from datetime import datetime @fills_vars('quote.add') @@ -40,7 +39,7 @@ async def quote_edit(bot, channel_id, cmd, args, **kwargs): 'WHERE channel_id=%s and number=%s', (' '.join(args), datetime.utcnow(), channel_id, n) ) - raise Send_error(f'Quote updated') + raise Send_error('Quote updated') @fills_vars('quote.delete') async def quote_delete(bot, channel_id, cmd, args, **kwargs): diff --git a/tbot/web/app.py b/tbot/web/app.py index 8733653..eb5da83 100644 --- a/tbot/web/app.py +++ b/tbot/web/app.py @@ -41,6 +41,8 @@ def App(): (r'/api/twitch/channels/([0-9]+)/commands/([0-9]+)', handlers.api.twitch.commands.Handler), (r'/api/twitch/channels/([0-9]+)/commands-public', handlers.api.twitch.commands.Public_collection), (r'/api/twitch/template-commands', handlers.api.twitch.commands.Template_collection), + (r'/api/twitch/channels/([0-9]+)/quotes', handlers.api.twitch.quotes.Collection_handler), + (r'/api/twitch/channels/([0-9]+)/quotes/([0-9]+)', handlers.api.twitch.quotes.Handler), (r'/api/twitch/channels/([0-9]+)/admins', handlers.api.twitch.admin_of.Channel_admins), (r'/api/twitch/channels/([0-9]+)/admins/([0-9]+)', handlers.api.twitch.admin_of.Channel_admins), (r'/api/twitch/channels/([0-9]+)/filters', handlers.api.twitch.filters.Filters), diff --git a/tbot/web/handlers/api/twitch/quotes.py b/tbot/web/handlers/api/twitch/quotes.py new file mode 100644 index 0000000..9a38b46 --- /dev/null +++ b/tbot/web/handlers/api/twitch/quotes.py @@ -0,0 +1,89 @@ +import good +from datetime import datetime, time +from ..base import Api_handler, Level + +_schema = { + 'message': good.All(str, good.Length(min=1, max=400)), + 'date': good.Date('%Y-%m-%d'), +} + +class Collection_handler(Api_handler): + + __schema__ = good.Schema(_schema, default_keys=good.Required) + + @Level(1) + async def get(self, channel_id): + cmds = await self.db.fetchall(''' + SELECT * + FROM twitch_quotes + WHERE channel_id=%s AND enabled=1 + ORDER BY number DESC + ''', (channel_id,)) + self.write_object(cmds) + + @Level(1) + async def post(self, channel_id): + data = self.validate() + data['channel_id'] = channel_id + data['created_at'] = datetime.combine(data['date'], time(0, 0, 0)) + data['updated_at'] = datetime.utcnow() + raise Exception('Not implemented') + # TODO: get user name + #date['created_by'] = self.current_user. + fields = ','.join([f for f in data]) + vfields = ','.join(['%s' for _ in data]) + values = list(data.values()) + c = await self.db.execute( + 'INSERT INTO twitch_quotes ({}) VALUES ({})'.format(fields, vfields), + values + ) + cmd = await get_quote(self, channel_id, c.lastrowid) + self.set_status(201) + self.write_object(cmd) + +class Handler(Api_handler): + + __schema__ = good.Schema(_schema, default_keys=good.Optional) + + @Level(1) + async def get(self, channel_id, number): + cmd = await get_quote(self, channel_id, number) + if not cmd: + self.set_status(404) + self.write_object({'error': 'Unknown quote'}) + else: + self.write_object(cmd) + + @Level(1) + async def put(self, channel_id, number): + data = self.validate() + data['updated_at'] = datetime.utcnow() + if 'date' in data: + if data['date']: + data['created_at'] = datetime.combine(data['date'], time(0, 0, 0)) + del data['date'] + fields = ','.join(['{}=%s'.format(k) for k in data]) + values = list(data.values()) + values.append(channel_id) + values.append(number) + await self.db.execute( + 'UPDATE twitch_quotes SET {} WHERE channel_id=%s AND number=%s'.format(fields), + values + ) + cmd = await get_quote(self, channel_id, number) + self.write_object(cmd) + + @Level(1) + async def delete(self, channel_id, number): + await self.db.execute(''' + UPDATE twitch_quotes SET enabled=0 WHERE channel_id=%s and number=%s + ''', (channel_id, number,)) + self.set_status(204) + +async def get_quote(self, channel_id, number): + cmd = await self.db.fetchone(''' + SELECT * + FROM twitch_quotes + WHERE channel_id=%s and number=%s + ''', (channel_id, number,)) + return cmd \ No newline at end of file diff --git a/tbot/web/ui/twitch/dashboard/components/sidebar.jsx b/tbot/web/ui/twitch/dashboard/components/sidebar.jsx index bc6374d..8874693 100644 --- a/tbot/web/ui/twitch/dashboard/components/sidebar.jsx +++ b/tbot/web/ui/twitch/dashboard/components/sidebar.jsx @@ -67,6 +67,7 @@ class Sidebar extends React.Component { Filters Banned words Chat alerts + Quotes {managedUser.level >= 3 ? Admins diff --git a/tbot/web/ui/twitch/dashboard/index.jsx b/tbot/web/ui/twitch/dashboard/index.jsx index cede524..36b111c 100644 --- a/tbot/web/ui/twitch/dashboard/index.jsx +++ b/tbot/web/ui/twitch/dashboard/index.jsx @@ -8,6 +8,8 @@ import Sidebar from './components/sidebar' import Topbar from './components/topbar' import Dashboard from './dashboard' import Commands from './commands' +import Quotes from './quotes' +import Quote from './quote' import Command from './command' import Spotify from './spotify' import Discord from './discord' @@ -52,6 +54,8 @@ class Main extends React.Component { + + diff --git a/tbot/web/ui/twitch/dashboard/quote.jsx b/tbot/web/ui/twitch/dashboard/quote.jsx new file mode 100644 index 0000000..af495cc --- /dev/null +++ b/tbot/web/ui/twitch/dashboard/quote.jsx @@ -0,0 +1,134 @@ +import React from 'react' +import {Redirect} from 'react-router' +import api from 'tbot/twitch/api' +import Loading from 'tbot/components/loading' +import {setHeader, renderError} from 'tbot/utils' +import {userLevelName, enabledWhenName} from 'tbot/twitch/utils' + +class Quote extends React.Component { + + constructor(props) { + super(props) + this.state = { + cmd: { + message: '', + date: '', + }, + loading: true, + error: null, + saving: false, + success: false, + } + } + + componentDidMount() { + if (this.props.match.params.number) { + setHeader('Edit quote') + this.getQuote() + } else { + setHeader('New quote') + this.setState({loading: false}) + } + } + + getQuote() { + const number = this.props.match.params.number + api.get(`/api/twitch/channels/${managedUser.id}/quotes/${number}`).then(r => { + for (let key in r.data) { + if (!(key in this.state.cmd)) + delete r.data[key] + } + this.setState({ + quote: r.data, + loading: false + }) + setHeader(`Edit quote`) + }) + } + + handleEvent = (e) => { + let val = e.target.type === 'checkbox' ? e.target.checked : e.target.value + this.state.quote[e.target.name] = val + this.setState({quote: this.state.quote}) + } + + handleSubmit = (e) => { + e.preventDefault() + this.setState({saving: true, error: null}) + const number = this.props.match.params.number + if (number) + api.put(`/api/twitch/channels/${managedUser.id}/quotes/${number}`, this.state.quote).then(r => { + this.setState({success: true}) + }).catch(e => { + this.setState({error: e.response.data, saving: false}) + }) + else + api.post(`/api/twitch/channels/${managedUser.id}/quotes`, this.state.quote).then(r => { + this.setState({success: true}) + }).catch(e => { + this.setState({error: e.response.data, saving: false}) + }) + } + + handleDelete = () => { + if (!confirm(`Delete quote?`)) + return + this.setState({deleting: true, error: null}) + let number = this.props.match.params.number + api.delete(`/api/twitch/channels/${managedUser.id}/quotes/${number}`).then(r => { + this.setState({ + cmd: r.data, + success: true, + }) + }).catch(e => { + this.setState({error: e.response.data, deleting: false}) + }) + } + + renderButton() { + if (this.state.saving) + return + return + } + + renderDeleteButton() { + if (!this.props.match.params.number) + return null + if (this.state.deleting) + return + return + } + + render() { + if (this.state.loading) + return + if (this.state.success) + return + + return
+
+ +
+ +
+ + {renderError(this.state.error)} + {this.renderButton()} + {this.renderDeleteButton()} +
+
+ } +} + +export default Quote \ No newline at end of file diff --git a/tbot/web/ui/twitch/dashboard/quotes.jsx b/tbot/web/ui/twitch/dashboard/quotes.jsx new file mode 100644 index 0000000..9a84e43 --- /dev/null +++ b/tbot/web/ui/twitch/dashboard/quotes.jsx @@ -0,0 +1,56 @@ +import React from 'react' +import {Link} from 'react-router-dom' +import api from 'tbot/twitch/api' +import Loading from 'tbot/components/loading' +import {setHeader} from 'tbot/utils' + +class Quotes extends React.Component { + + constructor(props) { + super(props) + this.state = { + quotes: [], + loading: true, + } + } + + componentDidMount() { + setHeader('Quotes') + this.getQuotes() + } + + getQuotes() { + api.get(`/api/twitch/channels/${managedUser.id}/quotes`).then(r => { + this.setState({ + quotes: r.data, + loading: false + }) + }) + } + + render() { + if (this.state.loading) + return + return
+ + + + + + + + + {this.state.quotes.length>0?this.state.quotes.map(quote => + + + + + + ): } + +
NumberQuote
{quote.number}{quote.message}Edit
No quotes.
+
+ } +} + +export default Quotes \ No newline at end of file