1
0
mirror of https://github.com/mgerb/go-discord-bot synced 2026-01-09 08:32:48 +00:00

UI done for video archiving

This commit is contained in:
2018-08-23 00:07:08 -05:00
parent 5a542e0ffb
commit 94bac26903
30 changed files with 393 additions and 68 deletions

View File

@@ -1,9 +1,9 @@
import 'babel-polyfill';
import './scss/index.scss';
import { Provider } from 'mobx-react';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { Clips, Downloader, NotFound, Oauth, Soundboard, Stats } from './pages';
import { Clips, Downloader, NotFound, Oauth, Soundboard, Stats, VideoArchive } from './pages';
import { rootStoreInstance } from './stores';
import { Wrapper } from './wrapper';
@@ -18,6 +18,7 @@ const App: any = (): any => {
<Route path="/clips" component={Clips} />
<Route path="/oauth" component={Oauth} />
<Route path="/stats" component={Stats} />
<Route path="/video-archive" component={VideoArchive} />
<Route component={NotFound} />
</Switch>
</Wrapper>

View File

@@ -0,0 +1,15 @@
.embedded-youtube {
overflow: hidden;
padding-bottom: 56.25%;
position: relative;
height: 0;
&__iframe {
left: 0;
top: 0;
height: 100%;
width: 100%;
position: absolute;
border: none;
}
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import './embedded-youtube.scss';
interface IProps {
id: string;
className?: string;
}
export const EmbeddedYoutube = ({ id, className }: IProps) => {
const src = `https://www.youtube.com/embed/${id}`;
return (
<div className={`embedded-youtube ${className}`}>
<iframe src={src} className="embedded-youtube__iframe" allowFullScreen />
</div>
);
};

View File

@@ -11,6 +11,7 @@
align-items: center;
justify-content: space-between;
padding-right: 20px;
z-index: 100;
&__title-container {
display: flex;

View File

@@ -1,3 +1,4 @@
export * from './embedded-youtube/embedded-youtube';
export * from './header/header';
export * from './navbar/navbar';
export * from './sound-list/sound-list';

View File

@@ -13,6 +13,7 @@
overflow-y: auto;
padding-bottom: 10px;
transition: 0.2s left ease-in-out;
z-index: 100;
&--open {
left: 0;

View File

@@ -74,6 +74,7 @@ export class Navbar extends React.Component<Props, State> {
return (
<div className={'navbar ' + openClass}>
{this.renderNavLink('Soundboard', '/', { exact: true })}
{this.renderNavLink('Video Archive', '/video-archive')}
{this.renderNavLink('Youtube Downloader', '/downloader')}
{this.renderNavLink('Clips', '/clips')}
{this.renderNavLink('Stats', '/stats')}

View File

@@ -1,2 +1,3 @@
export * from './claims';
export * from './permissions';
export * from './video-archive';

View File

@@ -0,0 +1,13 @@
export interface IVideoArchive {
author: string;
created_at: string;
date_published: string;
description: string;
duration: number;
id: number;
title: string;
updated_at: string;
uploaded_by: string;
url: string;
youtube_id: string;
}

View File

@@ -4,3 +4,4 @@ export * from './not-found/not-found';
export * from './oauth/oauth';
export * from './soundboard/soundboard';
export * from './stats/stats';
export * from './video-archive/video-archive';

View File

@@ -0,0 +1,14 @@
.video-archive {
// custom card
&__card {
border-top-right-radius: 0;
border-top-left-radius: 0;
padding: 0 0 10px;
max-width: initial;
}
&__text-input {
width: 100%;
max-width: 500px;
}
}

View File

@@ -0,0 +1,106 @@
import { DateTime } from 'luxon';
import { inject, observer } from 'mobx-react';
import React from 'react';
import { EmbeddedYoutube } from '../../components';
import { IVideoArchive, Permissions } from '../../model';
import { ArchiveService } from '../../services/archive.service';
import { AppStore } from '../../stores';
import './video-archive.scss';
interface IProps {
appStore: AppStore;
}
interface IState {
archives: IVideoArchive[];
url: string;
error?: string;
}
@inject('appStore')
@observer
export class VideoArchive extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
archives: [],
url: '',
};
}
componentDidMount() {
this.loadArchives();
}
async loadArchives() {
const archives = await ArchiveService.getVideoArchive();
if (archives) {
this.setState({
archives,
});
}
}
onSubmit = async (e: any) => {
e.preventDefault();
const { url } = this.state;
this.setState({ error: undefined });
try {
await ArchiveService.postVideoArchive({ url });
} catch (e) {
this.setState({ error: 'Invalid URL' });
return;
}
this.setState({ url: '' });
this.loadArchives();
};
renderForm() {
const { error, url } = this.state;
return (
<form className="flex flex--v-center" onSubmit={this.onSubmit}>
<input
className="input video-archive__text-input"
placeholder="Enter Youtube URL or ID..."
value={url}
onChange={e => this.setState({ url: e.target.value })}
/>
<input type="submit" className="button button--primary" style={{ marginLeft: '10px' }} />
{error && (
<span className="color__red" style={{ marginLeft: '5px' }}>
{error}
</span>
)}
</form>
);
}
renderArchives() {
return this.state.archives.map((v, k) => (
<div key={k} className="card video-archive__card test">
<EmbeddedYoutube id={v.youtube_id} />
<div style={{ padding: '10px 10px 0' }}>
<h3 style={{ margin: '0 0 5px' }} className="ellipsis" title={v.title}>
{v.title}
</h3>
<div>
<span className="color__red">{v.uploaded_by}</span> -{' '}
<small>{DateTime.fromISO(v.created_at).toLocaleString()}</small>
</div>
</div>
</div>
));
}
render() {
const { claims } = this.props.appStore;
return (
<div className="content">
{claims && claims.permissions > Permissions.User && this.renderForm()}
<div className="archive-grid">{this.renderArchives()}</div>
</div>
);
}
}

13
client/app/scss/grid.scss Normal file
View File

@@ -0,0 +1,13 @@
.archive-grid {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
@include smallScreen() {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
&__item {
height: 300px;
}
}

View File

@@ -1,5 +1,14 @@
@import '~normalize.css/normalize.css';
@import '~font-awesome/css/font-awesome.css';
@import '~nprogress/nprogress.css';
@import './mixins.scss';
@import './variables.scss';
@import './style.scss';
@import './button.scss';
@import './card.scss';
@import './input.scss';
@import './grid.scss';
@import './nprogress.scss';

View File

@@ -0,0 +1,14 @@
.input {
line-height: 38px;
border-radius: 3px;
border: 1px solid $lightGray;
padding-left: 5px;
padding-right: 5px;
height: 38px;
background: $gray5;
color: $white;
&::placeholder {
font-size: 14px;
}
}

View File

@@ -3,3 +3,9 @@
@content;
}
}
@mixin smallScreen {
@media only screen and (max-width: 768px) {
@content;
}
}

View File

@@ -0,0 +1,5 @@
// override nprogress css
#nprogress .bar {
background: $red;
}

View File

@@ -1,6 +1,3 @@
@import './variables.scss';
@import './mixins.scss';
html {
font-family: 'Roboto', sans-serif;
}
@@ -29,13 +26,16 @@ i {
body {
background-color: $gray1;
color: $white;
// padding-left: $navbarWidth;
}
.flex {
display: flex;
}
.flex--v-center {
align-items: center;
}
.content {
padding: 20px;
@include tinyScreen {
@@ -43,16 +43,6 @@ body {
}
}
.input {
border-radius: 3px;
border: 1px solid $lightGray;
padding-left: 5px;
padding-right: 5px;
height: 30px;
background: $gray5;
color: $white;
}
.column {
flex: 1;
@@ -65,3 +55,19 @@ body {
word-break: break-word;
word-wrap: break-word;
}
.color {
&__red {
color: $red;
}
&__primary {
color: $primaryBlue;
}
}
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

View File

@@ -2,6 +2,7 @@ $navbarWidth: 200px;
// colors
$primaryBlue: #7289da;
$red: #dd6e6e;
$white: darken(white, 20%);
$gray1: #2e3136;

View File

@@ -0,0 +1,14 @@
import * as _ from 'lodash';
import { IVideoArchive } from '../model';
import { axios } from './axios.service';
export class ArchiveService {
public static async getVideoArchive(): Promise<IVideoArchive[]> {
const data = (await axios.get('/api/video-archive')).data.data;
return _.orderBy(data, 'created_at', ['desc']);
}
public static postVideoArchive(data: any): Promise<any> {
return axios.post('/api/video-archive', data);
}
}

View File

@@ -1,14 +1,29 @@
import ax from 'axios';
import nprogress from 'nprogress';
nprogress.configure({ showSpinner: false });
export const axios = ax.create();
axios.interceptors.request.use(
config => {
nprogress.start();
// Do something before request is sent
return config;
},
function(error) {
error => {
// Do something with request error
return Promise.reject(error);
},
);
axios.interceptors.response.use(
config => {
nprogress.done();
return config;
},
error => {
nprogress.done();
return Promise.reject(error);
},
);

View File

@@ -8,7 +8,7 @@ const getScreenWidth = () => {
};
const isMobileScreen = () => {
return getScreenWidth() < 520;
return getScreenWidth() < 768;
};
export const Util = {

View File

@@ -2,8 +2,6 @@ import { inject, observer } from 'mobx-react';
import React from 'react';
import { withRouter } from 'react-router';
import { Header, Navbar } from './components';
//styling
import './scss/index.scss';
import { Util } from './util';
import './wrapper.scss';

View File

@@ -1,6 +1,6 @@
{
"name": "go-discord-bot",
"version": "0.6.0",
"version": "0.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -24,11 +24,21 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.110.tgz",
"integrity": "sha512-iXYLa6olt4tnsCA+ZXeP6eEW3tk1SulWeYyP/yooWfAtXjozqXgtX4+XUtMuOCfYjKGz3F34++qUc3Q+TJuIIw=="
},
"@types/luxon": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.2.2.tgz",
"integrity": "sha512-JziyQbl0YIE36lVLDMLhkEhZ1h3Do/+H6x908tXRhzPFrcGAyh7mJ44rhDff+R230RaeIUKeWnhoB8lH5SdsPA=="
},
"@types/node": {
"version": "10.3.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.4.tgz",
"integrity": "sha512-YMLlzdeNnAyLrQew39IFRkMacAR5BqKGIEei9ZjdHsIZtv+ZWKYTu1i7QJhetxQ9ReXx8w5f+cixdHZG3zgMQA=="
},
"@types/nprogress": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.0.29.tgz",
"integrity": "sha1-BgvVEAIqAF8YQCNAMNMTL7kZVHE="
},
"@types/query-string": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/query-string/-/query-string-6.1.0.tgz",
@@ -351,6 +361,11 @@
"uri-js": "3.0.2"
}
},
"ajv-errors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz",
"integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk="
},
"ajv-keywords": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz",
@@ -527,14 +542,6 @@
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
},
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
"integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
"requires": {
"lodash": "4.17.10"
}
},
"async-each": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
@@ -3948,17 +3955,6 @@
}
}
},
"extract-text-webpack-plugin": {
"version": "4.0.0-beta.0",
"resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-4.0.0-beta.0.tgz",
"integrity": "sha512-Hypkn9jUTnFr0DpekNam53X47tXn3ucY08BQumv7kdGgeVUBLq3DJHJTi6HNxv4jl9W+Skxjz9+RnK0sJyqqjA==",
"requires": {
"async": "2.6.0",
"loader-utils": "1.1.0",
"schema-utils": "0.4.5",
"webpack-sources": "1.1.0"
}
},
"extract-zip": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz",
@@ -5696,6 +5692,11 @@
"yallist": "2.1.2"
}
},
"luxon": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.3.3.tgz",
"integrity": "sha512-3CM0jpS3mbHwWoPYprX1/Zsd5esni0LkhMfSiSY6xQ3/M3pnct3OPWbWkQdEEl9MO9593k6PvDn1DhxCkpuZEw=="
},
"macaddress": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz",
@@ -5891,6 +5892,28 @@
"dom-walk": "0.1.1"
}
},
"mini-css-extract-plugin": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz",
"integrity": "sha512-ots7URQH4wccfJq9Ssrzu2+qupbncAce4TmTzunI9CIwlQMp2XI+WNUw6xWF6MMAGAm1cbUVINrSjATaVMyKXg==",
"requires": {
"loader-utils": "1.1.0",
"schema-utils": "1.0.0",
"webpack-sources": "1.1.0"
},
"dependencies": {
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"requires": {
"ajv": "6.4.0",
"ajv-errors": "1.0.0",
"ajv-keywords": "3.1.0"
}
}
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -6292,6 +6315,11 @@
"set-blocking": "2.0.0"
}
},
"nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
"integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E="
},
"nth-check": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "go-discord-bot",
"version": "0.6.0",
"version": "0.7.0",
"description": "Client for go-discord-bot",
"scripts": {
"build": "NODE_ENV=prod webpack -p --progress --colors",
@@ -13,7 +13,9 @@
"@types/chart.js": "^2.7.22",
"@types/jwt-decode": "^2.2.1",
"@types/lodash": "^4.14.110",
"@types/luxon": "^1.2.2",
"@types/node": "^10.3.4",
"@types/nprogress": "0.0.29",
"@types/query-string": "^6.1.0",
"@types/react": "^16.4.1",
"@types/react-chartjs-2": "^2.5.7",
@@ -32,17 +34,19 @@
"chart.js": "^2.7.2",
"clean-webpack-plugin": "^0.1.14",
"css-loader": "^0.28.11",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"favicons-webpack-plugin": "0.0.9",
"file-loader": "^1.1.11",
"font-awesome": "^4.7.0",
"html-webpack-plugin": "^3.2.0",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.10",
"luxon": "^1.3.3",
"mini-css-extract-plugin": "^0.4.2",
"mobx": "^5.0.3",
"mobx-react": "^5.2.5",
"node-sass": "^4.9.0",
"normalize.css": "^8.0.0",
"nprogress": "^0.2.0",
"postcss-loader": "^2.1.5",
"query-string": "^6.1.0",
"react": "^16.4.1",

View File

@@ -1,5 +1,5 @@
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
const path = require('path');
@@ -29,17 +29,29 @@ module.exports = {
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader', 'sass-loader'],
}),
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: './.',
},
},
'css-loader',
// 'postcss-loader',
'sass-loader',
],
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader'],
}),
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: './.',
},
},
'css-loader',
],
},
{
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
@@ -66,10 +78,9 @@ module.exports = {
verbose: true,
allowExternal: true,
}),
new ExtractTextPlugin({
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
disable: false,
allChunks: true,
chunkFilename: '[id].css',
}),
new HtmlWebpackPlugin({
filename: 'index.html',

View File

@@ -3,6 +3,7 @@
"client_id": "",
"client_secret": "",
"redirect_uri": "https://localhost/oauth",
"default_room_id": "",
"bot_prefix": "#",

View File

@@ -7,6 +7,8 @@ import (
log "github.com/sirupsen/logrus"
)
var session *discordgo.Session
// Start the bot
func Start(token string) *discordgo.Session {
// initialize connection
@@ -25,13 +27,34 @@ func Start(token string) *discordgo.Session {
return session
}
// GetSession - get current discord session
func GetSession() *discordgo.Session {
return session
}
// SendEmbeddedNotification - sends notification to default room
func SendEmbeddedNotification(title, description string) {
if session == nil || config.Config.DefaultRoomID == "" {
return
}
embed := &discordgo.MessageEmbed{
Color: 0x42adf4,
Title: title,
Description: description,
}
session.ChannelMessageSendEmbed(config.Config.DefaultRoomID, embed)
}
func addHandler(session *discordgo.Session, handler interface{}) {
session.AddHandler(handler)
}
func connect(token string) *discordgo.Session {
// Create a new Discord session using the provided bot token.
session, err := discordgo.New("Bot " + token)
var err error
session, err = discordgo.New("Bot " + token)
if err != nil {
log.Error(err)

View File

@@ -13,16 +13,17 @@ var (
)
type configType struct {
Token string `json:"token"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURI string `json:"redirect_uri"`
BotPrefix string `json:"bot_prefix"` //prefix to use for bot commands
AdminEmails []string `json:"admin_emails"`
ModEmails []string `json:"mod_emails"`
ServerAddr string `json:"server_addr"`
JWTSecret string `json:"jwt_secret"`
Logger bool `json:"logger"`
Token string `json:"token"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURI string `json:"redirect_uri"`
BotPrefix string `json:"bot_prefix"` //prefix to use for bot commands
AdminEmails []string `json:"admin_emails"`
ModEmails []string `json:"mod_emails"`
ServerAddr string `json:"server_addr"`
JWTSecret string `json:"jwt_secret"`
Logger bool `json:"logger"`
DefaultRoomID string `json:"default_room_id"`
// hard coded folder paths
SoundsPath string

View File

@@ -4,6 +4,7 @@ import (
"errors"
"github.com/gin-gonic/gin"
"github.com/mgerb/go-discord-bot/server/bot"
"github.com/mgerb/go-discord-bot/server/db"
"github.com/mgerb/go-discord-bot/server/webserver/middleware"
"github.com/mgerb/go-discord-bot/server/webserver/model"
@@ -13,11 +14,11 @@ import (
// AddVideoArchiveRoutes -
func AddVideoArchiveRoutes(group *gin.RouterGroup) {
group.GET("/video-archives", listVideoArchivesHandler)
group.GET("/video-archive", listVideoArchivesHandler)
authGroup := group.Group("", middleware.AuthorizedJWT())
authGroup.POST("/video-archives", middleware.AuthPermissions(middleware.PermMod), postVideoArchivesHandler)
authGroup.DELETE("/video-archives/:id", middleware.AuthPermissions(middleware.PermAdmin), deleteVideoArchivesHandler)
authGroup.POST("/video-archive", middleware.AuthPermissions(middleware.PermMod), postVideoArchivesHandler)
authGroup.DELETE("/video-archive/:id", middleware.AuthPermissions(middleware.PermAdmin), deleteVideoArchivesHandler)
}
func listVideoArchivesHandler(c *gin.Context) {
@@ -100,5 +101,9 @@ func postVideoArchivesHandler(c *gin.Context) {
return
}
hostURL := "[Click here to see the full archive!](http://" + c.Request.Host + "/video-archive)"
youtubeURL := "https://youtu.be/" + videoArchive.YoutubeID
bot.SendEmbeddedNotification(videoArchive.Title, "**"+videoArchive.UploadedBy+"** archived a new video:\n"+youtubeURL+"\n\n"+hostURL)
response.Success(c, "saved")
}