mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-11 17:42:48 +00:00
statistics tab for discord chat
This commit is contained in:
@@ -9,6 +9,7 @@ import { NotFound } from './pages/NotFound/NotFound';
|
|||||||
import { Downloader } from './pages/Downloader/Downloader';
|
import { Downloader } from './pages/Downloader/Downloader';
|
||||||
import { Clips } from './pages/Clips';
|
import { Clips } from './pages/Clips';
|
||||||
import { Oauth } from './pages/oauth/oauth';
|
import { Oauth } from './pages/oauth/oauth';
|
||||||
|
import { Stats } from './pages/stats/stats';
|
||||||
import 'babel-polyfill';
|
import 'babel-polyfill';
|
||||||
|
|
||||||
const App: any = (): any => {
|
const App: any = (): any => {
|
||||||
@@ -21,6 +22,7 @@ const App: any = (): any => {
|
|||||||
<Route path="/downloader" component={Downloader} />
|
<Route path="/downloader" component={Downloader} />
|
||||||
<Route path="/clips" component={Clips} />
|
<Route path="/clips" component={Clips} />
|
||||||
<Route path="/oauth" component={Oauth} />
|
<Route path="/oauth" component={Oauth} />
|
||||||
|
<Route path="/stats" component={Stats} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import jwt_decode from 'jwt-decode';
|
import jwt_decode from 'jwt-decode';
|
||||||
|
import { StorageService } from '../../services';
|
||||||
import './Navbar.scss';
|
import './Navbar.scss';
|
||||||
import { storage } from '../../storage';
|
|
||||||
|
|
||||||
let oauthUrl: string;
|
let oauthUrl: string;
|
||||||
|
|
||||||
@@ -31,25 +30,24 @@ export class Navbar extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const token = storage.getJWT();
|
const token = StorageService.getJWT();
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
const claims: any = jwt_decode(token!);
|
const claims: any = jwt_decode(token!);
|
||||||
console.log(claims);
|
|
||||||
const email = claims['email'];
|
const email = claims['email'];
|
||||||
this.setState({ token, email });
|
this.setState({ token, email });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private logout = () => {
|
private logout = () => {
|
||||||
localStorage.clear();
|
StorageService.clear();
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="Navbar">
|
<div className="Navbar">
|
||||||
<div className="Navbar__header">Go Discord Bot</div>
|
<div className="Navbar__header">Cash</div>
|
||||||
<NavLink exact to="/" className="Navbar__item" activeClassName="Navbar__item--active">
|
<NavLink exact to="/" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||||
Home
|
Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@@ -62,6 +60,9 @@ export class Navbar extends React.Component<Props, State> {
|
|||||||
<NavLink to="/clips" className="Navbar__item" activeClassName="Navbar__item--active">
|
<NavLink to="/clips" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||||
Clips
|
Clips
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink to="/stats" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||||
|
Stats
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
{!this.state.token ? (
|
{!this.state.token ? (
|
||||||
<a href={oauthUrl} className="Navbar__item">
|
<a href={oauthUrl} className="Navbar__item">
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ export class SoundList extends React.Component<Props, State> {
|
|||||||
const { soundList, type } = this.props;
|
const { soundList, type } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Card">
|
<div className="card">
|
||||||
<div className="Card__header" style={{ display: 'flex' }}>
|
<div className="card__header" style={{ display: 'flex' }}>
|
||||||
<div>
|
<div>
|
||||||
<span>{type}</span>
|
<span>{type}</span>
|
||||||
<i className="fa fa fa-volume-up" aria-hidden="true" />
|
<i className="fa fa fa-volume-up" aria-hidden="true" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import { axios } from '../../services';
|
||||||
|
|
||||||
import { SoundList, SoundType } from '../../components/SoundList';
|
import { SoundList, SoundType } from '../../components/SoundList';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import { axios } from '../../services';
|
||||||
|
|
||||||
import './Downloader.scss';
|
import './Downloader.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.Home {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ interface State {}
|
|||||||
export class Home extends React.Component<Props, State> {
|
export class Home extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="Home">
|
<div className="content">
|
||||||
<div className="Card">
|
<div className="card">
|
||||||
<div className="Card__header">Go Discord Bot</div>
|
<div className="card__header">Go Discord Bot</div>
|
||||||
|
|
||||||
<h3>04-09-18 Update</h3>
|
<h3>04-09-18 Update</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import { axios } from '../../services';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import './Pubg.scss';
|
import './Pubg.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Dropzone from 'react-dropzone';
|
import Dropzone from 'react-dropzone';
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import { axios } from '../../services';
|
||||||
|
|
||||||
import { SoundList, SoundType } from '../../components/SoundList';
|
import { SoundList, SoundType } from '../../components/SoundList';
|
||||||
|
|
||||||
import './Soundboard.scss';
|
import './Soundboard.scss';
|
||||||
import { storage } from '../../storage';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
let self: any;
|
let self: any;
|
||||||
|
|
||||||
@@ -37,7 +35,6 @@ export class Soundboard extends React.Component<Props, State> {
|
|||||||
this.config = {
|
this.config = {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
Authorization: `Bearer ${storage.getJWT()}`,
|
|
||||||
},
|
},
|
||||||
onUploadProgress: progressEvent => {
|
onUploadProgress: progressEvent => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import axios from 'axios';
|
import { axios, StorageService } from '../../services';
|
||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from 'react-router-dom';
|
||||||
import { storage } from '../../storage';
|
|
||||||
|
|
||||||
interface Props extends RouteComponentProps<any> {}
|
interface Props extends RouteComponentProps<any> {}
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ export class Oauth extends React.Component<Props, State> {
|
|||||||
private async fetchOauth(code: string) {
|
private async fetchOauth(code: string) {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post('/api/oauth', { code });
|
const res = await axios.post('/api/oauth', { code });
|
||||||
storage.setJWT(res.data);
|
StorageService.setJWT(res.data);
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
3
client/app/pages/stats/stats.scss
Normal file
3
client/app/pages/stats/stats.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.Stats {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
70
client/app/pages/stats/stats.tsx
Normal file
70
client/app/pages/stats/stats.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { HorizontalBar } from 'react-chartjs-2';
|
||||||
|
import { chain, map } from 'lodash';
|
||||||
|
import { axios } from '../../services';
|
||||||
|
import './stats.scss';
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
data: {
|
||||||
|
username: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a page to show discord chat statistics
|
||||||
|
* currently keeps track of number messages that contain external links
|
||||||
|
*/
|
||||||
|
export class Stats extends Component<any, IState> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getdata();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getdata() {
|
||||||
|
const messages = await axios.get('/api/logger/linkedmessages');
|
||||||
|
const data: any = chain(messages.data)
|
||||||
|
.map((v, k) => {
|
||||||
|
return { username: k, count: v };
|
||||||
|
})
|
||||||
|
.orderBy(v => v.count, 'desc')
|
||||||
|
.value();
|
||||||
|
|
||||||
|
this.setState({ data });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const data: any = {
|
||||||
|
labels: map(this.state.data, v => v.username),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Count',
|
||||||
|
backgroundColor: 'rgba(114,137,218, 0.4)',
|
||||||
|
borderColor: 'rgba(114,137,218, 0.9)',
|
||||||
|
borderWidth: 1,
|
||||||
|
hoverBackgroundColor: 'rgba(114,137,218, 0.6)',
|
||||||
|
hoverBorderColor: 'rgba(114,137,218, 1)',
|
||||||
|
data: map(this.state.data, v => v.count),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="content">
|
||||||
|
<div className="card" style={{ maxWidth: '1000px' }}>
|
||||||
|
<div className="card__header">Shitposts</div>
|
||||||
|
<HorizontalBar data={data} height={500} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,10 @@ body {
|
|||||||
padding-left: $navbarWidth;
|
padding-left: $navbarWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid $lightGray;
|
border: 1px solid $lightGray;
|
||||||
@@ -56,7 +60,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Card {
|
.card {
|
||||||
background-color: $gray2;
|
background-color: $gray2;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
@@ -65,7 +69,7 @@ body {
|
|||||||
border: 1px solid $gray3;
|
border: 1px solid $gray3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Card__header {
|
.card__header {
|
||||||
margin: -10px -10px 10px -10px;
|
margin: -10px -10px 10px -10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
19
client/app/services/axios.service.ts
Normal file
19
client/app/services/axios.service.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import ax from 'axios';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
|
||||||
|
export const axios = ax.create();
|
||||||
|
|
||||||
|
axios.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
const jwt = StorageService.getJWT();
|
||||||
|
if (jwt) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${jwt}`;
|
||||||
|
}
|
||||||
|
// Do something before request is sent
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
// Do something with request error
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
2
client/app/services/index.ts
Normal file
2
client/app/services/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './axios.service';
|
||||||
|
export * from './storage.service';
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
const clear = () => {
|
||||||
|
localStorage.clear();
|
||||||
|
};
|
||||||
|
|
||||||
const setJWT = (token: string) => {
|
const setJWT = (token: string) => {
|
||||||
localStorage.setItem('jwt', token);
|
localStorage.setItem('jwt', token);
|
||||||
};
|
};
|
||||||
@@ -6,7 +10,8 @@ const getJWT = (): string | null => {
|
|||||||
return localStorage.getItem('jwt');
|
return localStorage.getItem('jwt');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const storage = {
|
export const StorageService = {
|
||||||
|
clear,
|
||||||
getJWT,
|
getJWT,
|
||||||
setJWT,
|
setJWT,
|
||||||
};
|
};
|
||||||
60
client/package-lock.json
generated
60
client/package-lock.json
generated
@@ -9,6 +9,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
|
||||||
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
|
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
|
||||||
},
|
},
|
||||||
|
"@types/chart.js": {
|
||||||
|
"version": "2.7.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.11.tgz",
|
||||||
|
"integrity": "sha512-56jTNUUBLJ7zr+CSN3S3SBBPYPzPOgAtmoBhscANfCQ7F+AUnXbdS2c7RX0KyGyMzJu3LMfVKWU6lS+JDug/Vw=="
|
||||||
|
},
|
||||||
"@types/history": {
|
"@types/history": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.6.2.tgz",
|
||||||
@@ -42,6 +47,14 @@
|
|||||||
"csstype": "2.1.1"
|
"csstype": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-chartjs-2": {
|
||||||
|
"version": "2.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-chartjs-2/-/react-chartjs-2-2.5.7.tgz",
|
||||||
|
"integrity": "sha512-waqYqiNULIVUqaKO7MGUpFmWrVtH7gVPOzqwV4y4zgUyu/JiDwC005PpveO442HKnby9kLgp3t1SB2sld+ACLw==",
|
||||||
|
"requires": {
|
||||||
|
"react-chartjs-2": "2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-dom": {
|
"@types/react-dom": {
|
||||||
"version": "16.0.4",
|
"version": "16.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.4.tgz",
|
||||||
@@ -1761,6 +1774,39 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
|
||||||
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
|
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
|
||||||
},
|
},
|
||||||
|
"chart.js": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-90wl3V9xRZ8tnMvMlpcW+0Yg13BelsGS9P9t0ClaDxv/hdypHDr/YAGf+728m11P5ljwyB0ZHfPKCapZFqSqYA==",
|
||||||
|
"requires": {
|
||||||
|
"chartjs-color": "2.2.0",
|
||||||
|
"moment": "2.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chartjs-color": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
|
||||||
|
"requires": {
|
||||||
|
"chartjs-color-string": "0.5.0",
|
||||||
|
"color-convert": "0.5.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
|
||||||
|
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chartjs-color-string": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cheerio": {
|
"cheerio": {
|
||||||
"version": "0.19.0",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz",
|
||||||
@@ -6799,6 +6845,11 @@
|
|||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.0.tgz",
|
||||||
|
"integrity": "sha512-1muXCh8jb1N/gHRbn9VDUBr0GYb8A/aVcHlII9QSB68a50spqEVLIGN6KVmCOnSvJrUhC0edGgKU5ofnGXdYdg=="
|
||||||
|
},
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@@ -9166,6 +9217,15 @@
|
|||||||
"prop-types": "15.6.1"
|
"prop-types": "15.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-chartjs-2": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-DkT9TVi+/wKaEWL2iI2ka2L3Q0kq+y7TrAqlWNETAqE5CTPphPg1Af3w9X2uQCQ1ZA60Q3base9mK3dRtlb9bQ==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "4.17.5",
|
||||||
|
"prop-types": "15.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"version": "16.3.1",
|
"version": "16.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.1.tgz",
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
"author": "Mitchell Gerber",
|
"author": "Mitchell Gerber",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/chart.js": "^2.7.11",
|
||||||
"@types/jwt-decode": "^2.2.1",
|
"@types/jwt-decode": "^2.2.1",
|
||||||
"@types/lodash": "^4.14.71",
|
"@types/lodash": "^4.14.71",
|
||||||
"@types/node": "^9.4.6",
|
"@types/node": "^9.4.6",
|
||||||
"@types/query-string": "^5.1.0",
|
"@types/query-string": "^5.1.0",
|
||||||
"@types/react": "^16.0.0",
|
"@types/react": "^16.0.0",
|
||||||
|
"@types/react-chartjs-2": "^2.5.7",
|
||||||
"@types/react-dom": "^16.0.4",
|
"@types/react-dom": "^16.0.4",
|
||||||
"@types/react-dropzone": "^4.2.0",
|
"@types/react-dropzone": "^4.2.0",
|
||||||
"@types/react-router-dom": "^4.2.6",
|
"@types/react-router-dom": "^4.2.6",
|
||||||
@@ -27,6 +29,7 @@
|
|||||||
"babel-preset-es2015": "^6.18.0",
|
"babel-preset-es2015": "^6.18.0",
|
||||||
"babel-preset-react": "^6.16.0",
|
"babel-preset-react": "^6.16.0",
|
||||||
"babel-preset-stage-0": "^6.16.0",
|
"babel-preset-stage-0": "^6.16.0",
|
||||||
|
"chart.js": "^2.7.2",
|
||||||
"clean-webpack-plugin": "^0.1.14",
|
"clean-webpack-plugin": "^0.1.14",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^0.28.11",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
@@ -41,6 +44,7 @@
|
|||||||
"postcss-loader": "^2.1.3",
|
"postcss-loader": "^2.1.3",
|
||||||
"query-string": "^6.0.0",
|
"query-string": "^6.0.0",
|
||||||
"react": "^16.3.1",
|
"react": "^16.3.1",
|
||||||
|
"react-chartjs-2": "^2.7.0",
|
||||||
"react-dom": "^16.3.1",
|
"react-dom": "^16.3.1",
|
||||||
"react-dropzone": "^4.2.9",
|
"react-dropzone": "^4.2.9",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
|
|||||||
73
server/logger/operations.go
Normal file
73
server/logger/operations.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mgerb/go-discord-bot/server/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
const urlRegexp = `https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)`
|
||||||
|
|
||||||
|
var linkedPostsCacheTimeout time.Time
|
||||||
|
var linkedPostsCache map[string]int
|
||||||
|
|
||||||
|
// GetMessages - returns all messages - must use paging
|
||||||
|
func GetMessages(page int) ([]Message, error) {
|
||||||
|
messages := []Message{}
|
||||||
|
err := db.Conn.Offset(page*100).Limit(100).Order("timestamp desc", true).Preload("User").Find(&messages).Error
|
||||||
|
return messages, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLinkedMessages - get count of discord comments that contain URL's - per user
|
||||||
|
// cached for 10 minutes because there is a lot of data filtering
|
||||||
|
func GetLinkedMessages() (map[string]int, error) {
|
||||||
|
|
||||||
|
if linkedPostsCacheTimeout.After(time.Now().Add(-10 * time.Minute)) {
|
||||||
|
return linkedPostsCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []map[string]interface{}{}
|
||||||
|
rows, err := db.Conn.Table("messages").
|
||||||
|
Select("users.username, messages.content").
|
||||||
|
Joins("join users on messages.user_id = users.id").
|
||||||
|
Rows()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return map[string]int{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var username, content string
|
||||||
|
rows.Scan(&username, &content)
|
||||||
|
result = append(result, map[string]interface{}{
|
||||||
|
"username": username,
|
||||||
|
"content": content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
linkedPostsCacheTimeout = time.Now()
|
||||||
|
linkedPostsCache = groupPosts(result)
|
||||||
|
|
||||||
|
return linkedPostsCache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// group posts by user and count
|
||||||
|
func groupPosts(posts []map[string]interface{}) map[string]int {
|
||||||
|
|
||||||
|
result := map[string]int{}
|
||||||
|
|
||||||
|
for _, p := range posts {
|
||||||
|
match, _ := regexp.MatchString(urlRegexp, p["content"].(string))
|
||||||
|
|
||||||
|
if match {
|
||||||
|
if _, ok := result[p["username"].(string)]; ok {
|
||||||
|
result[p["username"].(string)]++
|
||||||
|
} else {
|
||||||
|
result[p["username"].(string)] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -4,20 +4,18 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mgerb/go-discord-bot/server/db"
|
|
||||||
"github.com/mgerb/go-discord-bot/server/logger"
|
"github.com/mgerb/go-discord-bot/server/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLogs - get all logs
|
// GetMessages - get all messages
|
||||||
func GetLogs(c *gin.Context) {
|
func GetMessages(c *gin.Context) {
|
||||||
page, err := strconv.Atoi(c.Query("page"))
|
page, err := strconv.Atoi(c.Query("page"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
page = 0
|
page = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := []logger.Message{}
|
messages, err := logger.GetMessages(page)
|
||||||
err = db.Conn.Offset(page*100).Limit(100).Order("timestamp desc", true).Preload("User").Find(&messages).Error
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, err)
|
c.JSON(500, err)
|
||||||
@@ -26,3 +24,15 @@ func GetLogs(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(200, messages)
|
c.JSON(200, messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLinkedMessages -
|
||||||
|
func GetLinkedMessages(c *gin.Context) {
|
||||||
|
posts, err := logger.GetLinkedMessages()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, posts)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ func getRouter() *gin.Engine {
|
|||||||
api.GET("/ytdownloader", handlers.Downloader)
|
api.GET("/ytdownloader", handlers.Downloader)
|
||||||
api.GET("/soundlist", handlers.SoundList)
|
api.GET("/soundlist", handlers.SoundList)
|
||||||
api.GET("/cliplist", handlers.ClipList)
|
api.GET("/cliplist", handlers.ClipList)
|
||||||
api.GET("/logs", handlers.GetLogs)
|
|
||||||
api.POST("/oauth", handlers.Oauth)
|
api.POST("/oauth", handlers.Oauth)
|
||||||
|
api.GET("/logger/messages", handlers.GetMessages)
|
||||||
|
api.GET("/logger/linkedmessages", handlers.GetLinkedMessages)
|
||||||
|
|
||||||
authorizedAPI := router.Group("/api")
|
authorizedAPI := router.Group("/api")
|
||||||
authorizedAPI.Use(middleware.AuthorizedJWT())
|
authorizedAPI.Use(middleware.AuthorizedJWT())
|
||||||
|
|||||||
Reference in New Issue
Block a user