Skip to content

Commit

Permalink
Edit quotes interface
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaserlang committed Aug 7, 2024
1 parent 0874c06 commit d9382fe
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 2 deletions.
3 changes: 1 addition & 2 deletions tbot/twitch_bot/functions/quotes.py
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions tbot/web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
89 changes: 89 additions & 0 deletions tbot/web/handlers/api/twitch/quotes.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tbot/web/ui/twitch/dashboard/components/sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Sidebar extends React.Component {
<NavLink to={`/twitch/${managedUser.name}/filters`} activeClassName="active">Filters</NavLink>
<NavLink to={`/twitch/${managedUser.name}/banned-words`} activeClassName="active">Banned words</NavLink>
<NavLink to={`/twitch/${managedUser.name}/chat-alerts`} activeClassName="active">Chat alerts</NavLink>
<NavLink to={`/twitch/${managedUser.name}/quotes`} activeClassName="active">Quotes</NavLink>

{managedUser.level >= 3 ?
<NavLink to={`/twitch/${managedUser.name}/admins`} activeClassName="active">Admins</NavLink>
Expand Down
4 changes: 4 additions & 0 deletions tbot/web/ui/twitch/dashboard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -52,6 +54,8 @@ class Main extends React.Component {
<Route exact path='/twitch/:channel/commands' component={Commands}/>
<Route exact path='/twitch/:channel/commands/edit/:id' component={Command}/>
<Route exact path='/twitch/:channel/commands/new' component={Command}/>
<Route exact path='/twitch/:channel/quotes' component={Quotes}/>
<Route exact path='/twitch/:channel/quotes/edit/:number' component={Quote}/>
<Route exact path='/twitch/:channel/spotify' component={Spotify}/>
<Route exact path='/twitch/:channel/discord' component={Discord}/>
<Route exact path='/twitch/:channel/admins' component={Admins}/>
Expand Down
134 changes: 134 additions & 0 deletions tbot/web/ui/twitch/dashboard/quote.jsx
Original file line number Diff line number Diff line change
@@ -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 <button className="btn btn-primary" type="button" disabled>
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Saving...
</button>
return <button type="submit" className="btn btn-primary">Save quote</button>
}

renderDeleteButton() {
if (!this.props.match.params.number)
return null
if (this.state.deleting)
return <button className="ml-2 btn btn-danger" type="button" disabled>
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Deleting...
</button>
return <button type="button" onClick={this.handleDelete} className="ml-2 btn btn-danger">Delete quote</button>
}

render() {
if (this.state.loading)
return <Loading />
if (this.state.success)
return <Redirect to={`/twitch/${this.props.match.params.channel}/quotes`} />

return <div style={{maxWidth:'700px'}}>
<form onSubmit={this.handleSubmit}>

<div className="form-group">
<input
className="form-control"
id="message"
name="message"
value={this.state.quote.message}
onChange={this.handleEvent}
required
/>
</div>

{renderError(this.state.error)}
{this.renderButton()}
{this.renderDeleteButton()}
</form>
</div>
}
}

export default Quote
56 changes: 56 additions & 0 deletions tbot/web/ui/twitch/dashboard/quotes.jsx
Original file line number Diff line number Diff line change
@@ -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 <Loading />
return <div>
<table className="table table-dark table-hover">
<thead>
<tr>
<th width="5px">Number</th>
<th>Quote</th>
</tr>
</thead>
<tbody>
{this.state.quotes.length>0?this.state.quotes.map(quote =>
<tr key={quote.number}>
<td>{quote.number}</td>
<td>{quote.message}</td>
<td className="text-right"><Link to={`quotes/edit/${quote.id}`}>Edit</Link></td>
</tr>
): <tr><td colSpan="8" className="text-center">No quotes.</td></tr>}
</tbody>
</table>
</div>
}
}

export default Quotes

0 comments on commit d9382fe

Please sign in to comment.