forked from ppfun/pixelplanet
add ui for iid banning
removed ip banning add eslint option for react-key
This commit is contained in:
parent
53b8168f24
commit
a41c286372
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react",
|
"react",
|
||||||
|
"react-hooks",
|
||||||
"jsx-a11y",
|
"jsx-a11y",
|
||||||
"import"
|
"import"
|
||||||
],
|
],
|
||||||
|
@ -32,7 +33,9 @@
|
||||||
"no-mixed-operators":"off",
|
"no-mixed-operators":"off",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react/jsx-one-expression-per-line": "off",
|
"react/jsx-one-expression-per-line": "off",
|
||||||
"react/jsx-closing-tag-location":"off",
|
"react/jsx-closing-tag-location": "off",
|
||||||
|
"react/jsx-key": "warn",
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"jsx-a11y/click-events-have-key-events":"off",
|
"jsx-a11y/click-events-have-key-events":"off",
|
||||||
"jsx-a11y/no-static-element-interactions":"off",
|
"jsx-a11y/no-static-element-interactions":"off",
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
|
|
3
package-lock.json
generated
3
package-lock.json
generated
|
@ -80,6 +80,7 @@
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||||
"eslint-plugin-react": "^7.30.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"generate-package-json-webpack-plugin": "^2.6.0",
|
"generate-package-json-webpack-plugin": "^2.6.0",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"ttag-cli": "^1.9.3",
|
"ttag-cli": "^1.9.3",
|
||||||
|
@ -5064,7 +5065,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
|
||||||
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
|
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
|
@ -15151,7 +15151,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
|
||||||
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
|
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
|
|
|
@ -94,6 +94,7 @@
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||||
"eslint-plugin-react": "^7.30.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"generate-package-json-webpack-plugin": "^2.6.0",
|
"generate-package-json-webpack-plugin": "^2.6.0",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"ttag-cli": "^1.9.3",
|
"ttag-cli": "^1.9.3",
|
||||||
|
|
|
@ -6,17 +6,18 @@ import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import GlobalCaptcha from './GlobalCaptcha';
|
import GlobalCaptcha from './GlobalCaptcha';
|
||||||
|
import BanInfo from './BanInfo';
|
||||||
import { closeAlert } from '../store/actions';
|
import { closeAlert } from '../store/actions';
|
||||||
|
|
||||||
const Alert = () => {
|
const Alert = () => {
|
||||||
const [render, setRender] = useState(false);
|
const [render, setRender] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
alertOpen,
|
open,
|
||||||
alertType,
|
alertType,
|
||||||
alertTitle,
|
title,
|
||||||
alertMessage,
|
message,
|
||||||
alertBtn,
|
btn,
|
||||||
} = useSelector((state) => state.alert);
|
} = useSelector((state) => state.alert);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -25,20 +26,32 @@ const Alert = () => {
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const onTransitionEnd = () => {
|
const onTransitionEnd = () => {
|
||||||
if (!alertOpen) setRender(false);
|
if (!open) setRender(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (alertOpen) setRender(true);
|
if (open) setRender(true);
|
||||||
}, 10);
|
}, 10);
|
||||||
}, [alertOpen]);
|
}, [open]);
|
||||||
|
|
||||||
|
let Content = null;
|
||||||
|
switch (alertType) {
|
||||||
|
case 'captcha':
|
||||||
|
Content = GlobalCaptcha;
|
||||||
|
break;
|
||||||
|
case 'ban':
|
||||||
|
Content = BanInfo;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(render || alertOpen) && (
|
(render || open) && (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className={(alertOpen && render)
|
className={(open && render)
|
||||||
? 'OverlayAlert show'
|
? 'OverlayAlert show'
|
||||||
: 'OverlayAlert'}
|
: 'OverlayAlert'}
|
||||||
onTransitionEnd={onTransitionEnd}
|
onTransitionEnd={onTransitionEnd}
|
||||||
|
@ -46,23 +59,21 @@ const Alert = () => {
|
||||||
onClick={close}
|
onClick={close}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={(alertOpen && render) ? 'Alert show' : 'Alert'}
|
className={(open && render) ? 'Alert show' : 'Alert'}
|
||||||
>
|
>
|
||||||
<h2>{alertTitle}</h2>
|
<h2>{title}</h2>
|
||||||
<p className="modaltext">
|
<p className="modaltext">
|
||||||
{alertMessage}
|
{message}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
{(alertType === 'captcha')
|
{(Content) ? (
|
||||||
? <GlobalCaptcha close={close} />
|
<Content close={close} />
|
||||||
: (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={close}
|
onClick={close}
|
||||||
>
|
>{btn}</button>
|
||||||
{alertBtn}
|
)}
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
100
src/components/BanInfo.jsx
Normal file
100
src/components/BanInfo.jsx
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* get information about ban
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { t } from 'ttag';
|
||||||
|
|
||||||
|
import useInterval from './hooks/interval';
|
||||||
|
import {
|
||||||
|
largeDurationToString,
|
||||||
|
} from '../core/utils';
|
||||||
|
import { requestBanInfo } from '../store/actions/fetch';
|
||||||
|
|
||||||
|
|
||||||
|
const BanInfo = ({ close }) => {
|
||||||
|
const [errors, setErrors] = useState([]);
|
||||||
|
const [reason, setReason] = useState('');
|
||||||
|
const [expireTs, setExpireTs] = useState(0);
|
||||||
|
const [expire, setExpire] = useState(null);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSubmitting(true);
|
||||||
|
const info = await requestBanInfo();
|
||||||
|
setSubmitting(false);
|
||||||
|
if (info.errors) {
|
||||||
|
setErrors(info.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
ts,
|
||||||
|
reason: newReason,
|
||||||
|
} = info;
|
||||||
|
setExpireTs(ts);
|
||||||
|
const tsDate = new Date(ts);
|
||||||
|
setExpire(tsDate.toLocaleString);
|
||||||
|
setReason(newReason);
|
||||||
|
};
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
console.log('do');
|
||||||
|
if (expireTs) {
|
||||||
|
setExpireTs(expireTs - 1);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{errors.map((error) => (
|
||||||
|
<p key={error} className="errormessage">
|
||||||
|
<span>{t`Error`}</span>: {error}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
{(reason) && (
|
||||||
|
<>
|
||||||
|
<h3 className="modaltitle">{t`Reason`}:</h3>
|
||||||
|
<p className="modaltext">{reason}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(expireTs) && (
|
||||||
|
<>
|
||||||
|
<h3 className="modaltitle">{t`Duration`}:</h3>
|
||||||
|
<p className="modaltext">
|
||||||
|
{t`Your ban expires at `}
|
||||||
|
<span style={{ fontWeight: 'bold' }}>{expire}</span>
|
||||||
|
{t` which is in `}
|
||||||
|
<span
|
||||||
|
style={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
{largeDurationToString(expireTs)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
style={{ fontSize: 16 }}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{(submitting) ? '...' : t`Why?`}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
style={{ fontSize: 16 }}
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
{t`OK`}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(BanInfo);
|
|
@ -19,10 +19,6 @@ function ChatMessage({
|
||||||
msg,
|
msg,
|
||||||
ts,
|
ts,
|
||||||
}) {
|
}) {
|
||||||
if (!name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isDarkMode = useSelector(
|
const isDarkMode = useSelector(
|
||||||
(state) => state.gui.style.indexOf('dark') !== -1,
|
(state) => state.gui.style.indexOf('dark') !== -1,
|
||||||
|
|
|
@ -11,10 +11,6 @@ import { t } from 'ttag';
|
||||||
import { MONTH } from '../core/constants';
|
import { MONTH } from '../core/constants';
|
||||||
|
|
||||||
function LanguageSelect() {
|
function LanguageSelect() {
|
||||||
if (!navigator.cookieEnabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { lang, langs } = window.ssv;
|
const { lang, langs } = window.ssv;
|
||||||
|
|
||||||
const [langSel, setLangSel] = useState(lang);
|
const [langSel, setLangSel] = useState(lang);
|
||||||
|
@ -30,6 +26,10 @@ function LanguageSelect() {
|
||||||
}
|
}
|
||||||
}, [langSel]);
|
}, [langSel]);
|
||||||
|
|
||||||
|
if (!navigator.cookieEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'right' }}>
|
<div style={{ textAlign: 'right' }}>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -240,7 +240,7 @@ function ModCanvastools() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="modalcotext">Choose Canvas:
|
<p className="modalcotext">{t`Choose Canvas`}:
|
||||||
<select
|
<select
|
||||||
value={selectedCanvas}
|
value={selectedCanvas}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -248,19 +248,14 @@ function ModCanvastools() {
|
||||||
selectCanvas(sel.options[sel.selectedIndex].value);
|
selectCanvas(sel.options[sel.selectedIndex].value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{Object.keys(canvases).filter((c) => !canvases[c].v).map((canvas) => (
|
||||||
Object.keys(canvases).map((canvas) => ((canvases[canvas].v)
|
<option
|
||||||
? null
|
key={canvas}
|
||||||
: (
|
value={canvas}
|
||||||
<option
|
>
|
||||||
value={canvas}
|
{canvases[canvas].title}
|
||||||
>
|
</option>
|
||||||
{
|
))}
|
||||||
canvases[canvas].title
|
|
||||||
}
|
|
||||||
</option>
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
<div className="modaldivider" />
|
<div className="modaldivider" />
|
||||||
|
|
|
@ -5,13 +5,20 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
|
import { parseInterval } from '../core/utils';
|
||||||
|
|
||||||
async function submitIIDAction(
|
async function submitIIDAction(
|
||||||
action,
|
action,
|
||||||
iid,
|
iid,
|
||||||
|
reason,
|
||||||
|
duration,
|
||||||
callback,
|
callback,
|
||||||
) {
|
) {
|
||||||
|
const time = Date.now() + parseInterval(duration);
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('iidaction', action);
|
data.append('iidaction', action);
|
||||||
|
data.append('reason', reason);
|
||||||
|
data.append('time', time);
|
||||||
data.append('iid', iid);
|
data.append('iid', iid);
|
||||||
const resp = await fetch('./api/modtools', {
|
const resp = await fetch('./api/modtools', {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
@ -24,6 +31,8 @@ async function submitIIDAction(
|
||||||
function ModIIDtools() {
|
function ModIIDtools() {
|
||||||
const [iIDAction, selectIIDAction] = useState('givecaptcha');
|
const [iIDAction, selectIIDAction] = useState('givecaptcha');
|
||||||
const [iid, selectIid] = useState('');
|
const [iid, selectIid] = useState('');
|
||||||
|
const [reason, setReason] = useState('');
|
||||||
|
const [duration, setDuration] = useState('1d');
|
||||||
const [resp, setResp] = useState('');
|
const [resp, setResp] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
@ -37,15 +46,47 @@ function ModIIDtools() {
|
||||||
selectIIDAction(sel.options[sel.selectedIndex].value);
|
selectIIDAction(sel.options[sel.selectedIndex].value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{['givecaptcha']
|
{['givecaptcha', 'ban', 'unban', 'whitelist', 'unwhitelist']
|
||||||
.map((opt) => (
|
.map((opt) => (
|
||||||
<option
|
<option
|
||||||
|
key={opt}
|
||||||
value={opt}
|
value={opt}
|
||||||
>
|
>
|
||||||
{opt}
|
{opt}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
{(iIDAction === 'ban') && (
|
||||||
|
<>
|
||||||
|
<p>{t`Reason`}</p>
|
||||||
|
<input
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
value={reason}
|
||||||
|
placeholder={t`Enter Reason`}
|
||||||
|
onChange={(evt) => {
|
||||||
|
setReason(evt.target.value.trim());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{`${t`Duration`}: `}
|
||||||
|
<input
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '7em',
|
||||||
|
}}
|
||||||
|
value={duration}
|
||||||
|
placeholder="1d"
|
||||||
|
onChange={(evt) => {
|
||||||
|
setDuration(evt.target.value.trim());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t`(0 = infinite)`}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<p className="modalcotext">
|
<p className="modalcotext">
|
||||||
{' IID: '}
|
{' IID: '}
|
||||||
<input
|
<input
|
||||||
|
@ -58,8 +99,7 @@ function ModIIDtools() {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="xxxx-xxxxx-xxxx"
|
placeholder="xxxx-xxxxx-xxxx"
|
||||||
onChange={(evt) => {
|
onChange={(evt) => {
|
||||||
const newIid = evt.target.value.trim();
|
selectIid(evt.target.value.trim());
|
||||||
selectIid(newIid);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
@ -72,6 +112,8 @@ function ModIIDtools() {
|
||||||
submitIIDAction(
|
submitIIDAction(
|
||||||
iIDAction,
|
iIDAction,
|
||||||
iid,
|
iid,
|
||||||
|
reason,
|
||||||
|
duration,
|
||||||
(ret) => {
|
(ret) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
setResp(ret);
|
setResp(ret);
|
||||||
|
@ -87,7 +129,6 @@ function ModIIDtools() {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
rows={(resp) ? resp.split('\n').length : 10}
|
rows={(resp) ? resp.split('\n').length : 10}
|
||||||
id="iparea"
|
|
||||||
value={resp}
|
value={resp}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -7,6 +7,8 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { useSelector, shallowEqual } from 'react-redux';
|
import { useSelector, shallowEqual } from 'react-redux';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
|
import { parseInterval } from '../core/utils';
|
||||||
|
|
||||||
const keepState = {
|
const keepState = {
|
||||||
tlcoords: '',
|
tlcoords: '',
|
||||||
brcoords: '',
|
brcoords: '',
|
||||||
|
@ -14,28 +16,6 @@ const keepState = {
|
||||||
iid: '',
|
iid: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* parse interval in s/m/h to timestamp
|
|
||||||
*/
|
|
||||||
function parseInterval(interval) {
|
|
||||||
if (!interval) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const lastChar = interval.slice(-1).toLowerCase();
|
|
||||||
const num = parseInt(interval.slice(0, -1), 10);
|
|
||||||
if (Number.isNaN(num) || num <= 0 || num > 600
|
|
||||||
|| !['s', 'm', 'h'].includes(lastChar)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let factor = 1000;
|
|
||||||
if (lastChar === 'm') {
|
|
||||||
factor *= 60;
|
|
||||||
} else if (lastChar === 'h') {
|
|
||||||
factor *= 3600;
|
|
||||||
}
|
|
||||||
return Date.now() - (num * factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* sorting function for array sort
|
* sorting function for array sort
|
||||||
*/
|
*/
|
||||||
|
@ -59,11 +39,12 @@ async function submitWatchAction(
|
||||||
iid,
|
iid,
|
||||||
callback,
|
callback,
|
||||||
) {
|
) {
|
||||||
const time = parseInterval(interval);
|
let time = parseInterval(interval);
|
||||||
if (!time) {
|
if (!time) {
|
||||||
callback({ info: t`Interval is invalid` });
|
callback({ info: t`Interval is invalid` });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
time = Date.now() - time;
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('watchaction', action);
|
data.append('watchaction', action);
|
||||||
data.append('canvasid', canvas);
|
data.append('canvasid', canvas);
|
||||||
|
@ -147,19 +128,14 @@ function ModWatchtools() {
|
||||||
selectCanvas(sel.options[sel.selectedIndex].value);
|
selectCanvas(sel.options[sel.selectedIndex].value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{Object.keys(canvases).filter((c) => !canvases[c].v).map((canvas) => (
|
||||||
Object.keys(canvases).map((canvas) => ((canvases[canvas].v)
|
<option
|
||||||
? null
|
key={canvas}
|
||||||
: (
|
value={canvas}
|
||||||
<option
|
>
|
||||||
value={canvas}
|
{canvases[canvas].title}
|
||||||
>
|
</option>
|
||||||
{
|
))}
|
||||||
canvases[canvas].title
|
|
||||||
}
|
|
||||||
</option>
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
{` ${t`Interval`}: `}
|
{` ${t`Interval`}: `}
|
||||||
<input
|
<input
|
||||||
|
@ -306,6 +282,7 @@ function ModWatchtools() {
|
||||||
<tr>
|
<tr>
|
||||||
{columns.slice(1).map((col, ind) => (
|
{columns.slice(1).map((col, ind) => (
|
||||||
<th
|
<th
|
||||||
|
key={col}
|
||||||
style={
|
style={
|
||||||
(sortBy - 1 === ind)
|
(sortBy - 1 === ind)
|
||||||
? { fontWeight: 'normal' }
|
? { fontWeight: 'normal' }
|
||||||
|
|
|
@ -18,8 +18,8 @@ import ChannelContextMenu from './contextmenus/ChannelContextMenu';
|
||||||
|
|
||||||
|
|
||||||
const CONTEXT_MENUS = {
|
const CONTEXT_MENUS = {
|
||||||
USER: <UserContextMenu />,
|
USER: UserContextMenu,
|
||||||
CHANNEL: <ChannelContextMenu />,
|
CHANNEL: ChannelContextMenu,
|
||||||
/* other context menus */
|
/* other context menus */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,24 +38,26 @@ const UI = () => {
|
||||||
state.contextMenu.menuType,
|
state.contextMenu.menuType,
|
||||||
], shallowEqual);
|
], shallowEqual);
|
||||||
|
|
||||||
const contextMenu = (menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null;
|
const ContextMenu = menuOpen && menuType && CONTEXT_MENUS[menuType];
|
||||||
|
|
||||||
if (isHistoricalView) {
|
return (
|
||||||
return [
|
<>
|
||||||
<HistorySelect />,
|
<Alert />
|
||||||
contextMenu,
|
{(isHistoricalView) ? (
|
||||||
];
|
<HistorySelect />
|
||||||
}
|
) : (
|
||||||
return [
|
<>
|
||||||
<Alert />,
|
<PalselButton />
|
||||||
<PalselButton />,
|
<Palette />
|
||||||
<Palette />,
|
{(!is3D) && <GlobeButton />}
|
||||||
(!is3D) && <GlobeButton />,
|
{(is3D && isOnMobile) && <Mobile3DControls />}
|
||||||
(is3D && isOnMobile) && <Mobile3DControls />,
|
<CoolDownBox />
|
||||||
<CoolDownBox />,
|
</>
|
||||||
<NotifyBox />,
|
)}
|
||||||
contextMenu,
|
<NotifyBox />
|
||||||
];
|
{ContextMenu && <ContextMenu />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(UI);
|
export default React.memo(UI);
|
||||||
|
|
|
@ -186,6 +186,7 @@ const ChannelDropDown = ({
|
||||||
const [cid, unreadCh, name] = ch;
|
const [cid, unreadCh, name] = ch;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
key={cid}
|
||||||
onClick={() => setChatChannel(cid)}
|
onClick={() => setChatChannel(cid)}
|
||||||
className={
|
className={
|
||||||
`chn${
|
`chn${
|
||||||
|
|
|
@ -64,6 +64,7 @@ const SettingsItemSelect = ({
|
||||||
{
|
{
|
||||||
values.map((value) => (
|
values.map((value) => (
|
||||||
<option
|
<option
|
||||||
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { findIdByNameOrId } from '../data/sql/RegUser';
|
import { findIdByNameOrId } from '../data/sql/RegUser';
|
||||||
import ChatMessageBuffer from './ChatMessageBuffer';
|
import ChatMessageBuffer from './ChatMessageBuffer';
|
||||||
import socketEvents from '../socket/SocketEvents';
|
import socketEvents from '../socket/SocketEvents';
|
||||||
import cheapDetector from './isProxy';
|
import checkIPAllowed from './isAllowed';
|
||||||
import { DailyCron } from '../utils/cron';
|
import { DailyCron } from '../utils/cron';
|
||||||
import { escapeMd } from './utils';
|
import { escapeMd } from './utils';
|
||||||
import ttags from './ttag';
|
import ttags from './ttag';
|
||||||
|
@ -380,11 +380,21 @@ export class ChatProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await cheapDetector(user.ip)) {
|
const allowed = await checkIPAllowed(user.ip);
|
||||||
|
if (!allowed.allowed) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`${name} / ${user.ip} tried to send chat message with proxy`,
|
`${name} / ${user.ip} tried to send chat message but is not allowed`,
|
||||||
);
|
);
|
||||||
return t`You can not send chat messages with proxy`;
|
switch (allowed.status) {
|
||||||
|
case 1:
|
||||||
|
return t`You can not send chat messages with proxy`;
|
||||||
|
case 2:
|
||||||
|
return t`You are banned`;
|
||||||
|
case 3:
|
||||||
|
return t`Your Internet Provider is banned`;
|
||||||
|
default:
|
||||||
|
return t`You are not allowed to use chat`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.charAt(0) === '/' && user.userlvl) {
|
if (message.charAt(0) === '/' && user.userlvl) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import redis from '../data/redis/client';
|
||||||
import { getIPv6Subnet } from '../utils/ip';
|
import { getIPv6Subnet } from '../utils/ip';
|
||||||
import { validateCoorRange } from '../utils/validation';
|
import { validateCoorRange } from '../utils/validation';
|
||||||
import CanvasCleaner from './CanvasCleaner';
|
import CanvasCleaner from './CanvasCleaner';
|
||||||
import { Blacklist, Whitelist, RegUser } from '../data/sql';
|
import { Whitelist, RegUser } from '../data/sql';
|
||||||
import { getIPofIID } from '../data/sql/IPInfo';
|
import { getIPofIID } from '../data/sql/IPInfo';
|
||||||
import { forceCaptcha } from '../data/redis/captcha';
|
import { forceCaptcha } from '../data/redis/captcha';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
@ -61,20 +61,6 @@ export async function executeIPAction(action, ips, logger = null) {
|
||||||
|
|
||||||
if (logger) logger(`${action} ${ip}`);
|
if (logger) logger(`${action} ${ip}`);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'ban':
|
|
||||||
await Blacklist.findOrCreate({
|
|
||||||
where: { ip: ipKey },
|
|
||||||
});
|
|
||||||
await redis.set(key, 'y', {
|
|
||||||
EX: 24 * 3600,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'unban':
|
|
||||||
await Blacklist.destroy({
|
|
||||||
where: { ip: ipKey },
|
|
||||||
});
|
|
||||||
await redis.del(key);
|
|
||||||
break;
|
|
||||||
case 'whitelist':
|
case 'whitelist':
|
||||||
await Whitelist.findOrCreate({
|
await Whitelist.findOrCreate({
|
||||||
where: { ip: ipKey },
|
where: { ip: ipKey },
|
||||||
|
|
|
@ -130,6 +130,7 @@ export async function drawByOffsets(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canvas.req === 'top' && !rankings.prevTop.includes(user.id)) {
|
if (canvas.req === 'top' && !rankings.prevTop.includes(user.id)) {
|
||||||
|
// not in top ten
|
||||||
throw new Error(12);
|
throw new Error(12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
|
/*
|
||||||
|
* decide if IP is allowed
|
||||||
|
* does proxycheck and check bans and whitelists
|
||||||
|
*/
|
||||||
import fetch from '../utils/proxiedFetch';
|
import fetch from '../utils/proxiedFetch';
|
||||||
|
|
||||||
import redis from '../data/redis/client';
|
|
||||||
import { getIPv6Subnet } from '../utils/ip';
|
import { getIPv6Subnet } from '../utils/ip';
|
||||||
import whois from '../utils/whois';
|
import whois from '../utils/whois';
|
||||||
import { Blacklist, Whitelist, IPInfo } from '../data/sql';
|
import { Whitelist, IPInfo } from '../data/sql';
|
||||||
|
import { isIPBanned } from '../data/sql/Ban';
|
||||||
|
import {
|
||||||
|
cacheAllowed,
|
||||||
|
getCacheAllowed,
|
||||||
|
} from '../data/redis/isAllowedCache';
|
||||||
import { proxyLogger as logger } from './logger';
|
import { proxyLogger as logger } from './logger';
|
||||||
|
|
||||||
import { USE_PROXYCHECK } from './config';
|
import { USE_PROXYCHECK } from './config';
|
||||||
|
@ -78,21 +86,6 @@ async function getProxyCheck(ip) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* check MYSQL Blacklist table
|
|
||||||
* @param ip IP to check
|
|
||||||
* @return true if blacklisted
|
|
||||||
*/
|
|
||||||
async function isBlacklisted(ip) {
|
|
||||||
const count = await Blacklist
|
|
||||||
.count({
|
|
||||||
where: {
|
|
||||||
ip,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return count !== 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check MYSQL Whitelist table
|
* check MYSQL Whitelist table
|
||||||
* @param ip IP to check
|
* @param ip IP to check
|
||||||
|
@ -135,21 +128,27 @@ async function saveIPInfo(ip, isProxy, info) {
|
||||||
* @return true if proxy or blacklisted, false if not or whitelisted
|
* @return true if proxy or blacklisted, false if not or whitelisted
|
||||||
*/
|
*/
|
||||||
async function withoutCache(f, ip) {
|
async function withoutCache(f, ip) {
|
||||||
if (!ip) return true;
|
|
||||||
const ipKey = getIPv6Subnet(ip);
|
const ipKey = getIPv6Subnet(ip);
|
||||||
let result;
|
let allowed;
|
||||||
let info;
|
let status;
|
||||||
|
let pcInfo;
|
||||||
if (await isWhitelisted(ipKey)) {
|
if (await isWhitelisted(ipKey)) {
|
||||||
result = false;
|
allowed = false;
|
||||||
info = 'wl';
|
pcInfo = 'wl';
|
||||||
} else if (await isBlacklisted(ipKey)) {
|
status = -1;
|
||||||
result = true;
|
} else if (await isIPBanned(ipKey)) {
|
||||||
info = 'bl';
|
allowed = true;
|
||||||
|
pcInfo = 'bl';
|
||||||
|
status = 2;
|
||||||
} else {
|
} else {
|
||||||
[result, info] = await f(ip);
|
[allowed, pcInfo] = await f(ip);
|
||||||
|
status = (allowed) ? 1 : 0;
|
||||||
}
|
}
|
||||||
saveIPInfo(ipKey, result, info);
|
saveIPInfo(ipKey, allowed, pcInfo);
|
||||||
return result;
|
return {
|
||||||
|
allowed,
|
||||||
|
status,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -162,13 +161,17 @@ async function withoutCache(f, ip) {
|
||||||
let lock = 4;
|
let lock = 4;
|
||||||
const checking = [];
|
const checking = [];
|
||||||
async function withCache(f, ip) {
|
async function withCache(f, ip) {
|
||||||
if (!ip || ip === '0.0.0.1') return true;
|
if (!ip || ip === '0.0.0.1') {
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
status: 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
// get from cache, if there
|
// get from cache, if there
|
||||||
const ipKey = getIPv6Subnet(ip);
|
const ipKey = getIPv6Subnet(ip);
|
||||||
const key = `isprox:${ipKey}`;
|
const cache = await getCacheAllowed(ipKey);
|
||||||
const cache = await redis.get(key);
|
|
||||||
if (cache) {
|
if (cache) {
|
||||||
return cache === 'y';
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// else make asynchronous ipcheck and assume no proxy in the meantime
|
// else make asynchronous ipcheck and assume no proxy in the meantime
|
||||||
|
@ -179,28 +182,42 @@ async function withCache(f, ip) {
|
||||||
checking.push(ipKey);
|
checking.push(ipKey);
|
||||||
try {
|
try {
|
||||||
const result = await withoutCache(f, ip);
|
const result = await withoutCache(f, ip);
|
||||||
const value = result ? 'y' : 'n';
|
cacheAllowed(ip, result);
|
||||||
redis.set(key, value, {
|
|
||||||
EX: 3 * 24 * 3600,
|
|
||||||
}); // cache for three days
|
|
||||||
const pos = checking.indexOf(ipKey);
|
|
||||||
if (~pos) checking.splice(pos, 1);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error %s', error.message || error);
|
logger.error('Error %s', error.message || error);
|
||||||
|
} finally {
|
||||||
const pos = checking.indexOf(ipKey);
|
const pos = checking.indexOf(ipKey);
|
||||||
if (~pos) checking.splice(pos, 1);
|
if (~pos) checking.splice(pos, 1);
|
||||||
} finally {
|
|
||||||
lock += 1;
|
lock += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return {
|
||||||
|
allowed: true,
|
||||||
|
status: -2,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function cheapDetector(ip) {
|
/*
|
||||||
if (USE_PROXYCHECK) {
|
* check if ip is allowed
|
||||||
return withCache(getProxyCheck, ip);
|
* @param ip IP
|
||||||
|
* @param disableCache if we fetch result from cache
|
||||||
|
* @return {
|
||||||
|
* allowed: boolean if allowed to use site
|
||||||
|
* , status: -2: not yet checked
|
||||||
|
* -1: whitelisted
|
||||||
|
* 0: allowed, no proxy
|
||||||
|
* 1 is proxy
|
||||||
|
* 2: is banned
|
||||||
|
* 3: is rangebanned
|
||||||
|
* 4: invalid ip
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function checkIfAllowed(ip, disableCache = false) {
|
||||||
|
const checker = (USE_PROXYCHECK) ? getProxyCheck : dummy;
|
||||||
|
if (disableCache) {
|
||||||
|
return withoutCache(checker, ip);
|
||||||
}
|
}
|
||||||
return withCache(dummy, ip);
|
return withCache(checker, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default cheapDetector;
|
export default checkIfAllowed;
|
|
@ -223,6 +223,10 @@ export function worldToScreen(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parses duration to string
|
||||||
|
* in xx:xx format with min:sec
|
||||||
|
*/
|
||||||
export function durationToString(
|
export function durationToString(
|
||||||
ms,
|
ms,
|
||||||
smallest = false,
|
smallest = false,
|
||||||
|
@ -238,6 +242,38 @@ export function durationToString(
|
||||||
return timestring;
|
return timestring;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parses a large duration to
|
||||||
|
* [x]h [y]min [z]sec format
|
||||||
|
*/
|
||||||
|
export function largeDurationToString(
|
||||||
|
ts,
|
||||||
|
) {
|
||||||
|
let restA = Math.round(ts / 1000);
|
||||||
|
let restB = restA % (3600 * 24);
|
||||||
|
const days = restA - restB;
|
||||||
|
restA = restB % 3600;
|
||||||
|
const hours = restB - restA;
|
||||||
|
restB = restA % 60;
|
||||||
|
const minutes = restA - restB;
|
||||||
|
restA = restB % 60;
|
||||||
|
const seconds = restB - restA;
|
||||||
|
let out = '';
|
||||||
|
if (days) {
|
||||||
|
out += ` ${days}d`;
|
||||||
|
}
|
||||||
|
if (hours) {
|
||||||
|
out += ` ${hours}h`;
|
||||||
|
}
|
||||||
|
if (minutes) {
|
||||||
|
out += ` ${minutes}min`;
|
||||||
|
}
|
||||||
|
if (seconds) {
|
||||||
|
out += ` ${seconds}s`;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
const postfix = ['k', 'M', 'B'];
|
const postfix = ['k', 'M', 'B'];
|
||||||
export function numberToString(num) {
|
export function numberToString(num) {
|
||||||
if (!num) {
|
if (!num) {
|
||||||
|
@ -433,3 +469,31 @@ export function getDateTimeString(timestamp) {
|
||||||
}
|
}
|
||||||
return date.toLocaleTimeString();
|
return date.toLocaleTimeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse interval in s/m/h form to timestamp
|
||||||
|
* @param interval string like "2d"
|
||||||
|
* @return timestamp of now - interval
|
||||||
|
*/
|
||||||
|
export function parseInterval(interval) {
|
||||||
|
if (!interval) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const lastChar = interval.slice(-1).toLowerCase();
|
||||||
|
const num = parseInt(interval.slice(0, -1), 10);
|
||||||
|
if (Number.isNaN(num) || num <= 0 || num > 600
|
||||||
|
|| !['s', 'm', 'h', 'd'].includes(lastChar)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let factor = 1000;
|
||||||
|
if (lastChar === 'm') {
|
||||||
|
factor *= 60;
|
||||||
|
} else if (lastChar === 'h') {
|
||||||
|
factor *= 3600;
|
||||||
|
} else if (lastChar === 'd') {
|
||||||
|
factor *= 3600 * 24;
|
||||||
|
}
|
||||||
|
return (num * factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import logger from '../../core/logger';
|
import logger from '../../core/logger';
|
||||||
import redis from './client';
|
import client from './client';
|
||||||
import { getIPv6Subnet } from '../../utils/ip';
|
import { getIPv6Subnet } from '../../utils/ip';
|
||||||
import {
|
import {
|
||||||
CAPTCHA_TIME,
|
CAPTCHA_TIME,
|
||||||
|
@ -79,7 +79,7 @@ export async function setCaptchaSolution(
|
||||||
key += `:${captchaid}`;
|
key += `:${captchaid}`;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await redis.set(key, text, {
|
await client.set(key, text, {
|
||||||
EX: CAPTCHA_TIMEOUT,
|
EX: CAPTCHA_TIMEOUT,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -112,7 +112,7 @@ export async function checkCaptchaSolution(
|
||||||
if (captchaid) {
|
if (captchaid) {
|
||||||
key += `:${captchaid}`;
|
key += `:${captchaid}`;
|
||||||
}
|
}
|
||||||
const solution = await redis.get(key);
|
const solution = await client.get(key);
|
||||||
if (solution) {
|
if (solution) {
|
||||||
if (evaluateResult(solution, text)) {
|
if (evaluateResult(solution, text)) {
|
||||||
if (Math.random() < 0.1) {
|
if (Math.random() < 0.1) {
|
||||||
|
@ -120,7 +120,7 @@ export async function checkCaptchaSolution(
|
||||||
}
|
}
|
||||||
if (!onetime) {
|
if (!onetime) {
|
||||||
const solvkey = `human:${ipn}`;
|
const solvkey = `human:${ipn}`;
|
||||||
await redis.set(solvkey, '', {
|
await client.set(solvkey, '', {
|
||||||
EX: TTL_CACHE,
|
EX: TTL_CACHE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ export async function needCaptcha(ip) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const key = `human:${getIPv6Subnet(ip)}`;
|
const key = `human:${getIPv6Subnet(ip)}`;
|
||||||
const ttl = await redis.ttl(key);
|
const ttl = await client.ttl(key);
|
||||||
if (ttl > 0) {
|
if (ttl > 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -168,6 +168,6 @@ export async function forceCaptcha(ip) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const key = `human:${getIPv6Subnet(ip)}`;
|
const key = `human:${getIPv6Subnet(ip)}`;
|
||||||
const ret = await redis.del(key);
|
const ret = await client.del(key);
|
||||||
return (ret > 0);
|
return (ret > 0);
|
||||||
}
|
}
|
||||||
|
|
29
src/data/redis/isAllowedCache.js
Normal file
29
src/data/redis/isAllowedCache.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* cache allowed ips
|
||||||
|
* used for proxychecker and banlist
|
||||||
|
*/
|
||||||
|
|
||||||
|
import client from './client';
|
||||||
|
|
||||||
|
const PREFIX = 'isal:';
|
||||||
|
const CACHE_DURATION = 3 * 24 * 3600;
|
||||||
|
|
||||||
|
export function cacheAllowed(ip, allowed) {
|
||||||
|
const key = `${PREFIX}:${ip}`;
|
||||||
|
return client.set(key, allowed.status, {
|
||||||
|
EX: CACHE_DURATION,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCacheAllowed(ip) {
|
||||||
|
const key = `${PREFIX}:${ip}`;
|
||||||
|
let cache = await client.get(key);
|
||||||
|
if (!cache) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
cache = parseInt(cache, 10);
|
||||||
|
return {
|
||||||
|
allowed: (cache <= 0),
|
||||||
|
status: cache,
|
||||||
|
};
|
||||||
|
}
|
|
@ -33,4 +33,12 @@ const Ban = sequelize.define('Blacklist', {
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export async function isIPBanned(ip) {
|
||||||
|
const count = await Ban
|
||||||
|
.count({
|
||||||
|
where: { ip },
|
||||||
|
});
|
||||||
|
return count !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
export default Ban;
|
export default Ban;
|
||||||
|
|
|
@ -35,6 +35,12 @@ const Message = sequelize.define('Message', {
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
|
|
||||||
|
setterMethods: {
|
||||||
|
message(value) {
|
||||||
|
this.setDataValue('message', value.slice(0, 200));
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Message.belongsTo(Channel, {
|
Message.belongsTo(Channel, {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Blacklist from './Blacklist';
|
|
||||||
import Whitelist from './Whitelist';
|
import Whitelist from './Whitelist';
|
||||||
import RegUser from './RegUser';
|
import RegUser from './RegUser';
|
||||||
import Channel from './Channel';
|
import Channel from './Channel';
|
||||||
|
@ -38,7 +37,6 @@ RegUser.belongsToMany(RegUser, {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Whitelist,
|
Whitelist,
|
||||||
Blacklist,
|
|
||||||
RegUser,
|
RegUser,
|
||||||
Channel,
|
Channel,
|
||||||
UserChannel,
|
UserChannel,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import getMe from '../../core/me';
|
||||||
import {
|
import {
|
||||||
USE_PROXYCHECK,
|
USE_PROXYCHECK,
|
||||||
} from '../../core/config';
|
} from '../../core/config';
|
||||||
import cheapDetector from '../../core/isProxy';
|
import checkIPAllowed from '../../core/isAllowed';
|
||||||
|
|
||||||
|
|
||||||
export default async (req, res, next) => {
|
export default async (req, res, next) => {
|
||||||
|
@ -17,10 +17,10 @@ export default async (req, res, next) => {
|
||||||
user.updateLogInTimestamp();
|
user.updateLogInTimestamp();
|
||||||
|
|
||||||
const { trueIp: ip } = req;
|
const { trueIp: ip } = req;
|
||||||
if (USE_PROXYCHECK && ip !== '0.0.0.1') {
|
if (USE_PROXYCHECK) {
|
||||||
// pre-fire cheap Detector to give it time to get a real result
|
// pre-fire ip check to give it time to get a real result
|
||||||
// once api_pixel needs it
|
// once api_pixel needs it
|
||||||
cheapDetector(ip);
|
checkIPAllowed(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/49547/how-to-control-web-page-caching-across-all-browsers
|
// https://stackoverflow.com/questions/49547/how-to-control-web-page-caching-across-all-browsers
|
||||||
|
|
|
@ -25,7 +25,7 @@ import chatProvider, { ChatProvider } from '../core/ChatProvider';
|
||||||
import authenticateClient from './authenticateClient';
|
import authenticateClient from './authenticateClient';
|
||||||
import { drawByOffsets } from '../core/draw';
|
import { drawByOffsets } from '../core/draw';
|
||||||
import { needCaptcha } from '../data/redis/captcha';
|
import { needCaptcha } from '../data/redis/captcha';
|
||||||
import cheapDetector from '../core/isProxy';
|
import isIPAllowed from '../core/isAllowed';
|
||||||
|
|
||||||
|
|
||||||
const ipCounter = new Counter();
|
const ipCounter = new Counter();
|
||||||
|
@ -95,7 +95,7 @@ class SocketServer {
|
||||||
ws.name = user.getName();
|
ws.name = user.getName();
|
||||||
|
|
||||||
const { ip } = user;
|
const { ip } = user;
|
||||||
cheapDetector(ip);
|
isIPAllowed(ip);
|
||||||
|
|
||||||
ws.send(OnlineCounter.dehydrate(socketEvents.onlineCounter));
|
ws.send(OnlineCounter.dehydrate(socketEvents.onlineCounter));
|
||||||
|
|
||||||
|
@ -493,8 +493,18 @@ class SocketServer {
|
||||||
failureRet = PixelReturn.dehydrate(10, 0, 0);
|
failureRet = PixelReturn.dehydrate(10, 0, 0);
|
||||||
}
|
}
|
||||||
// (re)check for Proxy
|
// (re)check for Proxy
|
||||||
if (await cheapDetector(ip)) {
|
const allowed = await isIPAllowed(ip);
|
||||||
failureRet = PixelReturn.dehydrate(11, 0, 0);
|
if (!allowed.allowed) {
|
||||||
|
// proxy
|
||||||
|
let failureStatus = 11;
|
||||||
|
if (allowed.status === 2) {
|
||||||
|
// banned
|
||||||
|
failureStatus = 14;
|
||||||
|
} else if (allowed.status === 3) {
|
||||||
|
// range banned
|
||||||
|
failureStatus = 15;
|
||||||
|
}
|
||||||
|
failureRet = PixelReturn.dehydrate(failureStatus, 0, 0);
|
||||||
}
|
}
|
||||||
if (failureRet !== null) {
|
if (failureRet !== null) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
|
@ -7,9 +7,9 @@ export type Action =
|
||||||
{ type: 'LOGGED_OUT' }
|
{ type: 'LOGGED_OUT' }
|
||||||
| { type: 'ALERT',
|
| { type: 'ALERT',
|
||||||
title: string,
|
title: string,
|
||||||
text: string,
|
message: string,
|
||||||
icon: string,
|
alertType: string,
|
||||||
confirmButtonText: string,
|
btn: string,
|
||||||
}
|
}
|
||||||
| { type: 'CLOSE_ALERT' }
|
| { type: 'CLOSE_ALERT' }
|
||||||
| { type: 'TOGGLE_GRID' }
|
| { type: 'TOGGLE_GRID' }
|
||||||
|
|
|
@ -282,6 +282,12 @@ export function requestRankings() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function requestBanInfo() {
|
||||||
|
return makeAPIGETRequest(
|
||||||
|
'api/baninfo',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function requestMe() {
|
export function requestMe() {
|
||||||
return makeAPIGETRequest(
|
return makeAPIGETRequest(
|
||||||
'api/me',
|
'api/me',
|
||||||
|
|
|
@ -9,18 +9,18 @@ import {
|
||||||
requestMe,
|
requestMe,
|
||||||
} from './fetch';
|
} from './fetch';
|
||||||
|
|
||||||
export function sweetAlert(
|
export function pAlert(
|
||||||
title,
|
title,
|
||||||
text,
|
message,
|
||||||
icon,
|
alertType,
|
||||||
confirmButtonText,
|
btn = t`OK`,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
type: 'ALERT',
|
type: 'ALERT',
|
||||||
title,
|
title,
|
||||||
text,
|
message,
|
||||||
icon,
|
alertType,
|
||||||
confirmButtonText,
|
btn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,7 +871,7 @@ export function startDm(windowId, query) {
|
||||||
dispatch(setApiFetching(true));
|
dispatch(setApiFetching(true));
|
||||||
const res = await requestStartDm(query);
|
const res = await requestStartDm(query);
|
||||||
if (typeof res === 'string') {
|
if (typeof res === 'string') {
|
||||||
dispatch(sweetAlert(
|
dispatch(pAlert(
|
||||||
'Direct Message Error',
|
'Direct Message Error',
|
||||||
res,
|
res,
|
||||||
'error',
|
'error',
|
||||||
|
@ -902,7 +902,7 @@ export function setUserBlock(
|
||||||
dispatch(setApiFetching(true));
|
dispatch(setApiFetching(true));
|
||||||
const res = await requestBlock(userId, block);
|
const res = await requestBlock(userId, block);
|
||||||
if (res) {
|
if (res) {
|
||||||
dispatch(sweetAlert(
|
dispatch(pAlert(
|
||||||
'User Block Error',
|
'User Block Error',
|
||||||
res,
|
res,
|
||||||
'error',
|
'error',
|
||||||
|
@ -924,7 +924,7 @@ export function setBlockingDm(
|
||||||
dispatch(setApiFetching(true));
|
dispatch(setApiFetching(true));
|
||||||
const res = await requestBlockDm(block);
|
const res = await requestBlockDm(block);
|
||||||
if (res) {
|
if (res) {
|
||||||
dispatch(sweetAlert(
|
dispatch(pAlert(
|
||||||
'Blocking DMs Error',
|
'Blocking DMs Error',
|
||||||
res,
|
res,
|
||||||
'error',
|
'error',
|
||||||
|
@ -944,7 +944,7 @@ export function setLeaveChannel(
|
||||||
dispatch(setApiFetching(true));
|
dispatch(setApiFetching(true));
|
||||||
const res = await requestLeaveChan(cid);
|
const res = await requestLeaveChan(cid);
|
||||||
if (res) {
|
if (res) {
|
||||||
dispatch(sweetAlert(
|
dispatch(pAlert(
|
||||||
'Leaving Channel Error',
|
'Leaving Channel Error',
|
||||||
res,
|
res,
|
||||||
'error',
|
'error',
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
const initialState = {
|
const initialState = {
|
||||||
alertOpen: false,
|
open: false,
|
||||||
alertType: null,
|
alertType: null,
|
||||||
alertTitle: null,
|
title: null,
|
||||||
alertMessage: null,
|
message: null,
|
||||||
alertBtn: null,
|
btn: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function alert(
|
export default function alert(
|
||||||
|
@ -13,23 +13,23 @@ export default function alert(
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ALERT': {
|
case 'ALERT': {
|
||||||
const {
|
const {
|
||||||
title, text, icon, confirmButtonText,
|
title, message, alertType, btn,
|
||||||
} = action;
|
} = action;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
alertOpen: true,
|
open: true,
|
||||||
alertTitle: title,
|
title,
|
||||||
alertMessage: text,
|
message,
|
||||||
alertType: icon,
|
alertType,
|
||||||
alertBtn: confirmButtonText,
|
btn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'CLOSE_ALERT': {
|
case 'CLOSE_ALERT': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
alertOpen: false,
|
open: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { t } from 'ttag';
|
||||||
import {
|
import {
|
||||||
notify,
|
notify,
|
||||||
setRequestingPixel,
|
setRequestingPixel,
|
||||||
sweetAlert,
|
pAlert,
|
||||||
gotCoolDownDelta,
|
gotCoolDownDelta,
|
||||||
pixelFailure,
|
pixelFailure,
|
||||||
setWait,
|
setWait,
|
||||||
|
@ -48,11 +48,10 @@ export function requestFromQueue(store) {
|
||||||
pixelQueue = [];
|
pixelQueue = [];
|
||||||
pixelTimeout = null;
|
pixelTimeout = null;
|
||||||
store.dispatch(setRequestingPixel(true));
|
store.dispatch(setRequestingPixel(true));
|
||||||
store.dispatch(sweetAlert(
|
store.dispatch(pAlert(
|
||||||
t`Error :(`,
|
t`Error :(`,
|
||||||
t`Didn't get an answer from pixelplanet. Maybe try to refresh?`,
|
t`Didn't get an answer from pixelplanet. Maybe try to refresh?`,
|
||||||
'error',
|
'error',
|
||||||
t`OK`,
|
|
||||||
));
|
));
|
||||||
}, 15000);
|
}, 15000);
|
||||||
|
|
||||||
|
@ -229,11 +228,10 @@ export function receivePixelReturn(
|
||||||
store.dispatch(pixelWait());
|
store.dispatch(pixelWait());
|
||||||
break;
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
store.dispatch(sweetAlert(
|
store.dispatch(pAlert(
|
||||||
'Captcha',
|
'Captcha',
|
||||||
t`Please prove that you are human`,
|
t`Please prove that you are human`,
|
||||||
'captcha',
|
'captcha',
|
||||||
t`OK`,
|
|
||||||
));
|
));
|
||||||
store.dispatch(setRequestingPixel(true));
|
store.dispatch(setRequestingPixel(true));
|
||||||
return;
|
return;
|
||||||
|
@ -250,6 +248,18 @@ export function receivePixelReturn(
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
msg = t`Server got confused by your pixels. Are you playing on multiple devices?`;
|
msg = t`Server got confused by your pixels. Are you playing on multiple devices?`;
|
||||||
break;
|
break;
|
||||||
|
case 14:
|
||||||
|
store.dispatch(pAlert(
|
||||||
|
'Banned',
|
||||||
|
t`You are banned.`,
|
||||||
|
'ban',
|
||||||
|
));
|
||||||
|
store.dispatch(setRequestingPixel(true));
|
||||||
|
return;
|
||||||
|
case 15:
|
||||||
|
errorTitle = t`Range Banned`;
|
||||||
|
msg = t`Your Internet Provider is banned from playing this game`;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
errorTitle = t`Weird`;
|
errorTitle = t`Weird`;
|
||||||
msg = t`Couldn't set Pixel`;
|
msg = t`Couldn't set Pixel`;
|
||||||
|
@ -257,11 +267,10 @@ export function receivePixelReturn(
|
||||||
|
|
||||||
if (msg) {
|
if (msg) {
|
||||||
store.dispatch(pixelFailure());
|
store.dispatch(pixelFailure());
|
||||||
store.dispatch(sweetAlert(
|
store.dispatch(pAlert(
|
||||||
(errorTitle || t`Error ${retCode}`),
|
(errorTitle || t`Error ${retCode}`),
|
||||||
msg,
|
msg,
|
||||||
'error',
|
'error',
|
||||||
t`OK`,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
import Renderer2D from './Renderer2D';
|
import Renderer2D from './Renderer2D';
|
||||||
import { sweetAlert } from '../store/actions';
|
import { pAlert } from '../store/actions';
|
||||||
import { isWebGL2Available } from '../core/utils';
|
import { isWebGL2Available } from '../core/utils';
|
||||||
|
|
||||||
const dummyRenderer = {
|
const dummyRenderer = {
|
||||||
|
@ -31,7 +31,7 @@ export async function initRenderer(store, is3D) {
|
||||||
renderer.destructor();
|
renderer.destructor();
|
||||||
if (is3D) {
|
if (is3D) {
|
||||||
if (!isWebGL2Available()) {
|
if (!isWebGL2Available()) {
|
||||||
store.dispatch(sweetAlert(
|
store.dispatch(pAlert(
|
||||||
t`Canvas Error`,
|
t`Canvas Error`,
|
||||||
t`Can't render 3D canvas, do you have WebGL2 disabled?`,
|
t`Can't render 3D canvas, do you have WebGL2 disabled?`,
|
||||||
'error',
|
'error',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user