Глупые статусы, окно настроек

This commit is contained in:
2023-03-29 21:13:40 +04:00
parent da66d2855a
commit 8b7d6216a4
25 changed files with 956 additions and 106 deletions
+2
View File
@@ -84,6 +84,8 @@ window.AprilJokes.settings = {
Обратите внимание, что метод `disableAll()` должен быть вызван *до* установки настроек `settings`, иначе заданные значения `enabled` будут сброшены. Обратите внимание, что метод `disableAll()` должен быть вызван *до* установки настроек `settings`, иначе заданные значения `enabled` будут сброшены.
Так же есть метод `window.AprilJokes.enableAll()`, который включает все шутки, в том числе отключённые по умолчанию.
--- ---
### Настройка `shake` (Форумо-трясение) ### Настройка `shake` (Форумо-трясение)
+59 -19
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+176
View File
@@ -26,6 +26,7 @@
"grunt-browserify": "^6.0.0", "grunt-browserify": "^6.0.0",
"grunt-contrib-uglify": "^5.2.2", "grunt-contrib-uglify": "^5.2.2",
"grunt-jsdoc-to-markdown": "^6.0.0", "grunt-jsdoc-to-markdown": "^6.0.0",
"html-loader": "^4.2.0",
"jsdoc-babel": "^0.5.0", "jsdoc-babel": "^0.5.0",
"jsdoc-to-markdown": "^8.0.0", "jsdoc-to-markdown": "^8.0.0",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
@@ -3615,6 +3616,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/camel-case": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
"integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
"dev": true,
"dependencies": {
"pascal-case": "^3.1.2",
"tslib": "^2.0.3"
}
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001470", "version": "1.0.30001470",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz",
@@ -3716,6 +3727,27 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node_modules/clean-css": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.0.tgz",
"integrity": "sha512-2639sWGa43EMmG7fn8mdVuBSs6HuWaSor+ZPoFWzenBc6oN+td8YhTfghWXZ25G1NiiSvz8bOFBS7PdSbTiqEA==",
"dev": true,
"dependencies": {
"source-map": "~0.6.0"
},
"engines": {
"node": ">= 10.0"
}
},
"node_modules/clean-css/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/clean-stack": { "node_modules/clean-stack": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -4436,6 +4468,16 @@
"npm": ">=1.2" "npm": ">=1.2"
} }
}, },
"node_modules/dot-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/duplexer": { "node_modules/duplexer": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -5877,6 +5919,68 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "dev": true
}, },
"node_modules/html-loader": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/html-loader/-/html-loader-4.2.0.tgz",
"integrity": "sha512-OxCHD3yt+qwqng2vvcaPApCEvbx+nXWu+v69TYHx1FO8bffHn/JjHtE3TTQZmHjwvnJe4xxzuecetDVBrQR1Zg==",
"dev": true,
"dependencies": {
"html-minifier-terser": "^7.0.0",
"parse5": "^7.0.0"
},
"engines": {
"node": ">= 14.15.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.0.0"
}
},
"node_modules/html-minifier-terser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.1.0.tgz",
"integrity": "sha512-BvPO2S7Ip0Q5qt+Y8j/27Vclj6uHC6av0TMoDn7/bJPhMWHI2UtR2e/zEgJn3/qYAmxumrGp9q4UHurL6mtW9Q==",
"dev": true,
"dependencies": {
"camel-case": "^4.1.2",
"clean-css": "5.2.0",
"commander": "^9.4.1",
"entities": "^4.4.0",
"param-case": "^3.0.4",
"relateurl": "^0.2.7",
"terser": "^5.15.1"
},
"bin": {
"html-minifier-terser": "cli.js"
},
"engines": {
"node": "^14.13.1 || >=16.0.0"
}
},
"node_modules/html-minifier-terser/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"dev": true,
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/html-minifier-terser/node_modules/entities": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/htmlescape": { "node_modules/htmlescape": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
@@ -7142,6 +7246,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"dev": true,
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -7811,6 +7924,16 @@
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
}, },
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
"dev": true,
"dependencies": {
"lower-case": "^2.0.2",
"tslib": "^2.0.3"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
@@ -8847,6 +8970,16 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true "dev": true
}, },
"node_modules/param-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
"integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
"dev": true,
"dependencies": {
"dot-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/parents": { "node_modules/parents": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
@@ -8918,6 +9051,40 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"dev": true,
"dependencies": {
"entities": "^4.4.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/pascal-case": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/path-browserify": { "node_modules/path-browserify": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -9623,6 +9790,15 @@
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
} }
}, },
"node_modules/relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/remove-trailing-separator": { "node_modules/remove-trailing-separator": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+1
View File
@@ -13,6 +13,7 @@
"grunt-browserify": "^6.0.0", "grunt-browserify": "^6.0.0",
"grunt-contrib-uglify": "^5.2.2", "grunt-contrib-uglify": "^5.2.2",
"grunt-jsdoc-to-markdown": "^6.0.0", "grunt-jsdoc-to-markdown": "^6.0.0",
"html-loader": "^4.2.0",
"jsdoc-babel": "^0.5.0", "jsdoc-babel": "^0.5.0",
"jsdoc-to-markdown": "^8.0.0", "jsdoc-to-markdown": "^8.0.0",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
+31 -1
View File
@@ -16,7 +16,7 @@ window.AprilJokes.settings = {
enabled: false, enabled: false,
chance: 30, chance: 30,
}, },
carnival: { carnival: {
enabled: true, enabled: true,
chance: 30, chance: 30,
@@ -127,4 +127,34 @@ window.AprilJokes.settings = {
}, },
}, },
}, },
stupid_title: {
enabled: true,
chance: 20,
gender_resolver: null,
rude: false,
change_frequency: 5,
schemas: {
'{adj} {noun}{p}': 10,
'{noun} {adj}{p}': 3,
'{adj}, {adj} {noun}{p}': 10,
'{adj} и очень {adj} {noun}{p}': 10,
'{adj} {noun} {verb}{p}': 10,
'{adj} {noun} {adv} {verb}{p}': 5,
'{adj} {noun} {verb} {place}{p}': 5,
'{adj} {noun} {adv} {verb} {place}{p}': 5,
},
variants: {
adj: {
...window.AprilJokes.settings.stupid_title.variants.adj,
...BuildStupdiTitleVariant(true, 'скромный, шутливый, !грубый'),
...BuildStupdiTitleVariant(false, 'скромная, шутливая, !грубая'),
},
noun: { ...window.AprilJokes.settings.stupid_title.variants.noun, },
verb: { ...window.AprilJokes.settings.stupid_title.variants.verb, },
place: { ...window.AprilJokes.settings.stupid_title.variants.place, },
adv: { ...window.AprilJokes.settings.stupid_title.variants.adv, },
p: { ...window.AprilJokes.settings.stupid_title.variants.p, },
},
},
} }
+71 -1
View File
@@ -37,7 +37,7 @@ window.AprilJokes.settings = {
* @author Kozhilya * @author Kozhilya
*/ */
shake: { shake: {
enabled: true, enabled: false,
chance: 1, chance: 1,
/** /**
@@ -379,4 +379,74 @@ window.AprilJokes.settings = {
}, },
}, },
}, },
/**
* **Глупые статусы**
*
* Каждому пользователю даётся новый глупый статус.
*
* @author Kozhilya
*/
stupid_title: {
enabled: true,
chance: 20,
/**
* Определение пола по имени.
* По умолчанию род устанавливается случайным образом
*
* Если установлено значение `null`, система попытается загрузить пол, установленный в настройках аккаунта. Если этот пол определить невозможно, то он будет выбран случайно. Эта настройка позволяет изменить это поведение, если ваш форум обладает ролевой системой, в которой пол определяется иным образом.
*
* @param name Имя персонажа
* @returns true, если мужской род; false, если женский род
*/
gender_resolver: null,
/**
* Разрешить возможно оскорбительные слова
*/
rude: false,
/**
* Частота смены статусов (в минутах)
*/
change_frequency: 5,
/**
* Разрешённые схемы формирования статусов
*
* {adj} - прилагательное
* {noun} - имя существительно
* {verb} - глагол
* {adv} - наречие
* {p} - знак препинания
*/
schemas: {
'{adj} {noun}{p}': 10,
'{noun} {adj}{p}': 3,
'{adj}, {adj} {noun}{p}': 10,
'{adj} и очень {adj} {noun}{p}': 10,
'{adj} {noun} {verb}{p}': 10,
'{adj} {noun} {adv} {verb}{p}': 5,
'{adj} {noun} {verb} {place}{p}': 5,
'{adj} {noun} {adv} {verb} {place}{p}': 5,
},
/**
* Вариант слов.
* Смотрите раздел описания скрипта, что бы настроить это поле
*/
variants: {
adj: {
...window.AprilJokes.settings.stupid_title.variants.adj,
...BuildStupdiTitleVariant(true, 'скромный, шутливый, !грубый'),
...BuildStupdiTitleVariant(false, 'скромная, шутливая, !грубая'),
},
noun: { ...window.AprilJokes.settings.stupid_title.variants.noun, },
verb: { ...window.AprilJokes.settings.stupid_title.variants.verb, },
place: { ...window.AprilJokes.settings.stupid_title.variants.place, },
adv: { ...window.AprilJokes.settings.stupid_title.variants.adv, },
p: { ...window.AprilJokes.settings.stupid_title.variants.p, },
},
},
} }
+4
View File
@@ -0,0 +1,4 @@
declare module "*.html" {
const content: string;
export default content;
}
+1
View File
@@ -0,0 +1 @@
<button class="april-fools__button">🎉</button>
+66
View File
@@ -0,0 +1,66 @@
<style class="april-fools__styles">
.april-fools__button {
position: fixed;
bottom: 0.2rem;
right: 0.2rem;
padding: 0;
font-size: 3rem;
border: 0 solid;
background: transparent none;
opacity: 0.3;
transition: 0.4s all;
cursor: pointer;
}
.april-fools__button:hover {
opacity: 1;
}
.april-fools__joke-row {
border-bottom: 1px solid black;
}
.april-fools__joke-row:first-of-type {
border-top: 1px solid black;
}
.april-fools__joke {
display: flex;
flex-flow: row wrap;
padding: 10px;
opacity: 0.5;
justify-content: space-between;
align-items: center;
gap: 1ch;
cursor: pointer;
}
.april-fools__joke:hover {
text-shadow: 0 0 black;
}
.april-fools__joke:before {
display: block;
content: "";
flex: 0 0 2ch;
text-align: center;
content: "✔";
opacity: 0;
}
input.april-fools__joke-input:checked + .april-fools__joke {
opacity: 1;
}
input.april-fools__joke-input:checked + .april-fools__joke:before {
opacity: 1;
}
.april-fools__joke .april-fools__joke-title {
flex: 1 auto;
font-weight: bold;
}
.april-fools__joke .april-fools__joke-description {
flex: 1 auto;
text-align: right;
}
.april-fools__joke .april-fools__joke-chance {
flex: 0 0 4ch;
text-align: right;
}
.april-fools__content .april-fools__reset-button {
display: block;
margin: 10px auto;
}
</style>
+15
View File
@@ -0,0 +1,15 @@
<div class="april-fools__content">
<div class="april-fools__jokes"></div>
<button class="april-fools__reset-button">Вернуться к настройкам форума (страница будет перезагружена!)</button>
</div>
<template class="april-fools__template" id="april-fools__window-row">
<div class="april-fools__joke-row">
<input type="checkbox" class="april-fools__joke-input" name="april-fools__joke-${joke.id}" id="april-fools__joke-${joke.id}" style="display: none;" />
<label class="april-fools__joke" for="april-fools__joke-${joke.id}">
<p class="april-fools__joke-title">${joke.title}</p>
<p class="april-fools__joke-description">${joke.description}</p>
<p class="april-fools__joke-chance">${joke.settings.chance}%</p>
</label>
</div>
</template>
+10
View File
@@ -22,6 +22,16 @@ export abstract class Joke {
*/ */
abstract id: string; abstract id: string;
/**
* Заголовок шутки
*/
abstract title: string;
/**
* Описание шутки
*/
abstract description: string;
/** /**
* Настройки шутки * Настройки шутки
*/ */
+103 -5
View File
@@ -1,4 +1,6 @@
import { Joke, JokeSettings } from "./joke"; import { stat } from "fs";
import { Joke, JokeSettings, StopableJoke } from "./joke";
import { JokerUI } from "./ui";
/** /**
* Блок обработки перво-апрельских шуток * Блок обработки перво-апрельских шуток
@@ -6,6 +8,8 @@ import { Joke, JokeSettings } from "./joke";
* @author Kozhilya * @author Kozhilya
*/ */
export class JokerClass { export class JokerClass {
ui: JokerUI = new JokerUI(this);
/** /**
* Проверка вероятности * Проверка вероятности
* *
@@ -32,10 +36,14 @@ export class JokerClass {
* @param handler Метод, выполняющий шутку * @param handler Метод, выполняющий шутку
* @param settings Объект настроек шутки * @param settings Объект настроек шутки
*/ */
addCustom(id: string, handler: Function, settings: JokeSettings|null = null) { addCustom(id: string, handler: Function, settings: JokeSettings|null = null): Joke {
const joke = new class extends Joke { const joke = new class extends Joke {
id = id; id = id;
title = 'Пользовательская шутка';
description = 'Для шутки не определено описание';
settings: JokeSettings = { chance: 10, enabled: true }; settings: JokeSettings = { chance: 10, enabled: true };
start() { start() {
@@ -46,6 +54,8 @@ export class JokerClass {
Object.assign(joke.settings, settings ?? {}); Object.assign(joke.settings, settings ?? {});
this.add(joke); this.add(joke);
return joke;
} }
/** /**
@@ -113,6 +123,74 @@ export class JokerClass {
} }
} }
/**
* Переключение шутки
*/
toggleJoke(joke_id: string, state: boolean) {
const joke = this.jokes[joke_id];
joke.settings.enabled = state;
if (joke instanceof StopableJoke && !state) {
joke.stop();
}
this.saveUserStates();
}
/**
* Сохранение пользовательских настроек
*/
saveUserStates() {
const states: any = {};
for (const key of Object.keys(this.jokes)) {
states[key] = this.jokes[key].settings.enabled;
}
fetch('/api.php?' + $.param({
method: 'storage.set',
user_id: (<any> window).UserID,
key: 'april-fools',
token: (<any> window).ForumAPITicket,
value: JSON.stringify(states),
timer: 24 * 60
}));
}
/**
* Загрузка пользовательских настроек
*/
async loadUserStates() {
const response = await fetch('/api.php?' + $.param({
method: 'storage.get',
user_id: (<any> window).UserID,
key: 'april-fools',
}));
const json = await response.json();
if (json.error) {
return;
}
const data = JSON.parse(json.response.storage.data['april-fools']);
for (const key of Object.keys(this.jokes)) {
this.jokes[key].settings.enabled = data[key];
}
}
async clearUserStates() {
await fetch('/api.php?' + $.param({
method: 'storage.delete',
user_id: (<any> window).UserID,
key: 'april-fools',
token: (<any> window).ForumAPITicket,
}));
return;
}
/** /**
* Запуск шутки * Запуск шутки
* *
@@ -141,15 +219,33 @@ export class JokerClass {
} }
} }
regularStart(): void { regularCheck(): boolean {
if (!this.core_settings.enabled) {
return false;
}
if (this.core_settings.testers.indexOf((<any> window).UserID) >= 0) {
return true;
}
const today = new Date(); const today = new Date();
const month = today.getMonth() + 1; // Note that getMonth() returns 0-indexed month, so we need to add 1 const month = today.getMonth() + 1;
const day = today.getDate(); const day = today.getDate();
if (month !== 4 || day !== 1 || !this.core_settings.enabled || !this.check(this.core_settings.chance)) { if (month !== 4 || day !== 1) {
return false;
}
return this.check(this.core_settings.chance);
}
async regularStart() {
if (!this.regularCheck()) {
return; return;
} }
await this.loadUserStates();
this.ui.show();
this.startAll(); this.startAll();
} }
@@ -181,4 +277,6 @@ interface SettingsMap {
class CoreSettings implements JokeSettings { class CoreSettings implements JokeSettings {
chance = 100; chance = 100;
enabled = true; enabled = true;
testers: number[] = [];
} }
+5 -1
View File
@@ -10,7 +10,11 @@ import { JokerClass } from "../joker";
* @author Kozhilya * @author Kozhilya
*/ */
export class CarnivalJoke extends Joke { export class CarnivalJoke extends Joke {
id = 'carnival' id = 'carnival';
title = 'Карнавал';
description = 'Аватарки приобретают интересные цвета';
settings = new CarnivalJokeSettings; settings = new CarnivalJokeSettings;
+4
View File
@@ -11,6 +11,10 @@ import $ from "jquery";
export class CrazyLettersJoke extends Joke { export class CrazyLettersJoke extends Joke {
id = 'crazy_letters'; id = 'crazy_letters';
title = 'Мама, Я сОшЛа С уМа!';
description = 'иМеНа ЗаБоРчИкОм';
settings = new CrazyLettersJokeSettings; settings = new CrazyLettersJokeSettings;
start(): void { start(): void {
-72
View File
@@ -1,72 +0,0 @@
import { Joke, JokeSettings, StopableJoke } from "../joke";
import $ from "jquery";
/**
* **Бешеные запятые**
*
* Запятые после слов
*
* @author Kozhilya
*/
export class CrazyPunctuationJoke extends Joke {
id = 'crazy_punctuation';
settings = new CrazyPunctuationJokeSettings;
punctuationSymbols: string[]|null = null;
private shufflePunctuation(text: string): string {
if (this.punctuationSymbols === null) {
this.punctuationSymbols = this.settings.symbols.split('');
}
const words = text.split(" ");
const shuffledWords = words.map(word => {
const characters = word.split("");
// Filter out any non-punctuation characters
const punctuation = characters.filter(char => this.punctuationSymbols.includes(char));
// Shuffle the punctuation array
const shuffledPunctuation = punctuation.sort(() => Math.random() - 0.5);
// Loop through each character in the word and replace punctuation with shuffled punctuation
let index = 0;
return characters.map(char => {
if (this.punctuationSymbols.includes(char)) {
return shuffledPunctuation[index++];
}
return char;
}).join("");
});
// Join the shuffled words array into a single string with spaces
return shuffledWords.join(" ");
}
start(): void {
const items = $(this.settings.selector).find('*').contents().filter((_, node) => node.nodeType === Node.TEXT_NODE);
items.each((_, node) => {
node.textContent = this.shufflePunctuation(node.textContent);
})
}
}
export class CrazyPunctuationJokeSettings implements JokeSettings {
enabled: boolean = true;
chance: number = 20;
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = '.post-content p';
/**
* Мигрирующие знаки
*/
symbols: string = ',.;:?!-()[]{}\'"';
}
+5 -1
View File
@@ -15,6 +15,10 @@ const cursor_effects = require("cursor-effects");
export class CursorEffectsJoke extends StopableJoke { export class CursorEffectsJoke extends StopableJoke {
id = 'cursor_effects'; id = 'cursor_effects';
title = 'Красивый курсор';
description = 'Эффекты, следующие за курсором';
settings = new CursorEffectsJokeSettings; settings = new CursorEffectsJokeSettings;
active_cursor_effect: any; active_cursor_effect: any;
@@ -43,7 +47,7 @@ export class CursorEffectsJoke extends StopableJoke {
} }
getRandomEffect(): CursorEffectsEnum { getRandomEffect(): CursorEffectsEnum {
const elements = Object.keys(CursorEffectsEnum).map(e => <CursorEffectsEnum>e); const elements = Object.values(CursorEffectsEnum).map(e => <CursorEffectsEnum>e);
const totalWeight = elements.reduce((sum, element) => sum + this.weight(element), 0); const totalWeight = elements.reduce((sum, element) => sum + this.weight(element), 0);
const randomWeight = Math.random() * totalWeight; const randomWeight = Math.random() * totalWeight;
+4
View File
@@ -10,6 +10,10 @@ import $ from "jquery";
*/ */
export class HumanitarianHellJoke extends Joke { export class HumanitarianHellJoke extends Joke {
id = 'humanitarian_hell'; id = 'humanitarian_hell';
title = 'Ад гуманитария';
description = 'Все "тся" заменяются на "ться", а все "ться" на "тся"';
settings = new HumanitarianHellJokeSettings; settings = new HumanitarianHellJokeSettings;
+4
View File
@@ -12,6 +12,10 @@ import { JokerClass } from "../joker";
export class LetterChaosJoke extends Joke { export class LetterChaosJoke extends Joke {
id = 'letter_chaos'; id = 'letter_chaos';
title = 'Буквенный хаос';
description = 'Все буквы в словах, кроме первой и последней, перемешены.';
settings = new LetterChaosJokeSettings; settings = new LetterChaosJokeSettings;
punctuationSymbols: string[] | null = null; punctuationSymbols: string[] | null = null;
+4
View File
@@ -10,6 +10,10 @@ import $ from "jquery";
*/ */
export class MirrorJoke extends Joke { export class MirrorJoke extends Joke {
id = 'mirror'; id = 'mirror';
title = 'Зеркало';
description = 'Элементы форума отражаются (по крайней мере, пытаются)';
settings = new MirrorJokeSettings; settings = new MirrorJokeSettings;
+5 -1
View File
@@ -11,6 +11,10 @@ import $ from "jquery";
export class ShakeJoke extends StopableJoke { export class ShakeJoke extends StopableJoke {
id = 'shake'; id = 'shake';
title = 'Форумо-трясение';
description = 'Страница форума начинает трястись';
settings = new ShakeJokeSettings; settings = new ShakeJokeSettings;
private interval_id: NodeJS.Timer; private interval_id: NodeJS.Timer;
@@ -63,7 +67,7 @@ export class ShakeJoke extends StopableJoke {
} }
export class ShakeJokeSettings implements JokeSettings { export class ShakeJokeSettings implements JokeSettings {
enabled: boolean = true; enabled: boolean = false;
chance: number = 1; chance: number = 1;
+312
View File
@@ -0,0 +1,312 @@
import { Joke, JokeSettings, StopableJoke } from "../joke";
import $ from "jquery";
/**
* **Глупые статусы**
*
* Каждому пользователю даётся новый глупый статус.
*
* @author Kozhilya
*/
export class StupidTitleJoke extends Joke {
id = 'stupid_title';
title = 'Глупые статусы';
description = 'Каждому пользователю даётся новый глупый статус.';
settings = new StupidTitleJokeSettings;
private loadedGenders: any = null;
async loadGenders(): Promise<void> {
const names = $('.pa-author a')
.map((_, elem) => elem.innerText).toArray()
.reduce((arr: string[], name: string) => {
if (arr.indexOf(name) < 0){
arr.push(name);
}
return arr
}, []);
const url = '/api.php?' + $.param({ method: 'users.get', fields: 'username,sex', username: names });
const response = await fetch(url);
const json = await response.json();
this.loadedGenders = {};
if (json.error) {
return;
}
json.response.users.forEach((element: any) => {
if (element.sex === '0') {
return;
}
this.loadedGenders[element.username] = element.sex === '1';
});
}
resolveGender(name: string): boolean {
if (this.settings.gender_resolver !== null) {
return !!this.settings.gender_resolver.call(this, name);
}
if (name in this.loadedGenders) {
return !!this.loadedGenders[name];
}
this.resetSeed();
return this.selectRandom(name, { 'm': 1, 'f': 1 }) === 'm';
}
async start(): Promise<void> {
await this.loadGenders();
const cache: any = {};
$('.post-author').each((_, root) => {
const name = $('.pa-author a', root).text();
if (!name) {
return;
}
if (!(name in cache)) {
this.resetSeed();
cache[name] = this.generatePhrase(name);
}
$('.pa-title', root).text(cache[name]);
});
}
private prng(name: string, seed: number, limit: number): number {
/*
https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
cyrb53 (c) 2018 bryc (github.com/bryc)
A fast and simple hash function with decent collision resistance.
Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
Public domain. Attribution appreciated.
*/
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < name.length; i++) {
ch = name.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
const value = 4294967296 * (2097151 & h2) + (h1>>>0);
return Math.floor(Math.pow(Math.sin(value), 2) * limit) % limit;
}
private seed = 0;
private resetSeed(): void {
const date = new Date();
const time = new Date().getUTCHours() * 60 + new Date().getUTCMinutes();
this.seed = Math.floor(time / this.settings.change_frequency);
console.log('Seed reset:', time, this.seed);
}
private selectRandom(name: string, variants: StupidTitleWeightedVariants): string {
const elements = Object.keys(variants);
const totalWeight = elements.reduce((sum, element) => sum + variants[element], 0);
const randomWeight = this.prng(name, this.seed++, totalWeight);
let currentWeight = 0;
for (const element of elements) {
currentWeight += variants[element];
if (randomWeight <= currentWeight) {
return element;
}
}
throw new Error("Тебя быть тут не должно...");
}
private buildVariants(type: string, gender: boolean): StupidTitleWeightedVariants {
const result: StupidTitleWeightedVariants = {};
const variants: StupidTitleVariant = this.settings.variants[type];
for (const iterator of Object.entries(variants)) {
const word = iterator[0];
const wordSettings = iterator[1];
if (!wordSettings.enabled || (wordSettings.gender === !gender)) {
continue;
}
if (!this.settings.rude && wordSettings.rude) {
continue;
}
result[word] = wordSettings.weight;
}
return result;
}
private capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
generatePhrase(name: string): string {
const gender = this.resolveGender(name);
const prhase = this.selectRandom(name, this.settings.schemas);
return this.capitalizeFirst(prhase.replace(/\{(.+?)\}/g, (_, type: string) => {
const variants = this.buildVariants(type, gender);
return this.selectRandom(name, variants);
}));
}
}
export class StupidTitleJokeSettings implements JokeSettings {
enabled: boolean = true;
chance: number = 100;
/**
* Определение пола по имени.
* По умолчанию род устанавливается случайным образом
*
* Если установлено значение `null`, система попытается загрузить пол, установленный в настройках аккаунта. Если этот пол определить невозможно, то он будет выбран случайно. Эта настройка позволяет изменить это поведение, если ваш форум обладает ролевой системой, в которой пол определяется иным образом.
*
* @param name Имя персонажа
* @returns true, если мужской род; false, если женский род
*/
gender_resolver: Function | null = null;
/**
* Разрешить возможно оскорбительные варианты
*/
rude: boolean = false;
/**
* Частота смены статусов (в минутах)
*/
change_frequency: number = 5;
/**
* Разрешённые схемы формирования статусов
*
* {adj} - прилагательное
* {noun} - имя существительно
* {verb} - глагол
* {adv} - наречие
* {place} - дополнение места
* {p} - знак препинания
*/
schemas: StupidTitleWeightedVariants = {
'{adj} {noun}{p}': 10,
'{noun} {adj}{p}': 3,
'{adj}, {adj} {noun}{p}': 10,
'{adj} и очень {adj} {noun}{p}': 10,
'{adj} {noun} {verb}{p}': 10,
'{adj} {noun} {adv} {verb}{p}': 5,
'{adj} {noun} {verb} {place}{p}': 5,
'{adj} {noun} {adv} {verb} {place}{p}': 5,
};
/**
* Вариант слов
* Смотрите раздел описания скрипта, что бы настроить это поле
*/
variants: StupidTitleVariants = {
adj: {
...buildVariant(true, 'жесткий,^удачливый,низкий,знакомый,откровенный,^мирный,грамотный,вкусный,виртуальный,пышный,провинциальный,$жирный,заинтересованный,объективный,^оригинальный,пожилой,чистый,особенный,выгодный,громадный,обыкновенный,чужой,ударный,^солнечный,истинный,посторонний,южный,седой,$бесполезный,острый,пестрый,формальный,повседневный,невозможный,$жалкий,целый,$глупый,последний,выходной,серьезный,подземный,небесный,любимый,былой,семейный,далекий,северный,суровый,гигантский,иностранный,родной,конструктивный,показательный,нужный,чрезвычайный,гордый,банальный,открытый,независимый,запасной,мстительный,грубый,алчный,мелочный,хладнокровный,спокойный,терпеливый,креативный,активный,адекватный,застенчивый,опрятный,безнадёжный,уверенный в себе,доброжелательный,ласковый,робкий,воспитанный,самовлюблённый,деятельный,двуличный,верный'),
...buildVariant(false, 'жесткая,^удачливая,низкая,знакомая,откровенная,^мирная,грамотная,вкусная,виртуальная,пышная,провинциальная,$жирная,заинтересованная,объективная,^оригинальная,пожилая,чистая,особенная,выгодная,громадная,обыкновенная,чужая,ударная,^солнечная,истинная,посторонняя,южная,седая,$бесполезная,острая,пестрая,формальная,повседневная,невозможная,$жалкая,целая,$глупая,последняя,выходная,серьезная,подземная,небесная,любимая,былая,семейная,далекая,северная,суровая,гигантская,иностранная,родная,конструктивная,показательная,нужная,чрезвычайная,гордая,банальная,открытая,независимая,запасная,мстительная,грубая,алчная,мелочная,хладнокровная,спокаяная,терпеливая,креативная,активная,адекватная,застенчивая,опрятная,безнадёжная,уверенная в себе,доброжелательная,ласковая,робкая,воспитанная,самовлюблённая,деятельная,двуличная,верная'),
},
noun: {
...buildVariant(null, ''),
...buildVariant(true, 'орёл, ниндзя, манекен, пузырь, бродяга, рыцарь, кентавр, зайц, диван, наследник, сын, джентльмен, басурманин, карасик, $злоумышленник, $раб, хоббит, одиночка, отец, кролик, бруталище, дворф, актёр, %дед, спортсмен, $балбес, %мальчег, лев, ёж, организм, монах, хоббит, борец, тигр, монстр, массажист, мачо, эльф, ледник, жук, кот, зомби, проказник, дракон, носорог, волк, лесоруб, кит, %дедушка, динозавр, бард, кулак, принц, %сынок, мальчик, товарищ, ёжик, сурикат'),
...buildVariant(false, 'заноза, волторна, $личинка, детка, %бабушка, простуда, черепашка, мышь, волчица, деревяшка, пиранья, каруселька, красотка, леди, %дочка, орлица, принцесса, $ведьма, травка, актриса, наследница, %деффачка, горгулья, белочка, акула, машинка, конфетка, знаменитость, косточка, спортсменка, панда, мать, львица, эльфийка, $злоумышленница, чертовка, дворфиха, кошка, $рабыня, скамеечка, дочь, монашка, бактерия, рыбка, бусинка, $бабка, девочка, валькирия, килька, бабочка, милашка, крышка, тигрица, рыбка, русалочка'),
},
verb: {
...buildVariant(null, 'встаёт, снимает, приказывает, указывает, собирается, чувствует опасность, орёт, болеет, поздравляет, освобождает, поздравляет, бежит, берёт и делает, молчит, радуется, прощает, читает, снимает, снимается в фильме, что-то скрывает, думает, имеет план и придерживается его, тянет, работает, меняется, ловит, отказаться куда-то идти, заходит, замирает, оформляет, нарушает, отказывается что-то делать, шепчет, снимается в сериале, скрывается, учит, спрашивает, идёт, отказывается вставать, прибывает, отказывается, забывает, получает, плывёт, отвечает, поглядывает, говорит, просыпается, соединяет, оглядывается'),
...buildVariant(true, 'женится, женился, успел, не успел, смог, скрылся, заработался, познал дзен, присел, обиделся, проснулся, взял и сделал, додумался, не спал'),
...buildVariant(false, 'выходит замуж, вышла замуж, успела, не успела, смогла, скрылась, заработалась, познала дзен, присела, обиделась, проснулась, взяла и сделала, додумалась, не спала'),
},
place: {
...buildVariant(null, 'где-то, в пустыне, на пляже, под дождём, %под проливным дождём, в квартире, %в тёплой и уютной квартире, в космосе, в театре, в филармонии, на заснеженной вершине, на конференции, на форуме, в очереди, в лесу, в джунглях, под водой'),
},
adv: {
...buildVariant(null, 'счастливо, уныло, грустно, позже, немедленно, регулярно, вновь, опять, уже, внезапно, предсказуемо, медленно, быстро, %молниеносно, $грубо, безрассудно, осмысленно, расчётливо'),
},
p: {
'': new StupidTitleSetting({ weight: 10 }),
'.': new StupidTitleSetting({ weight: 10 }),
'...': new StupidTitleSetting({ weight: 5 }),
'!': new StupidTitleSetting({ weight: 5 }),
'!!!': new StupidTitleSetting({ weight: 2 }),
'?': new StupidTitleSetting({ weight: 2 }),
'?!': new StupidTitleSetting({ weight: 1 }),
},
};
}
interface StupidTitleVariants {
[key: string]: StupidTitleVariant
}
interface StupidTitleVariant {
[key: string]: StupidTitleSetting
}
interface StupidTitleWeightedVariants {
[key: string]: number
}
class StupidTitleSetting {
weight: number = 10;
enabled: boolean = true;
rude: boolean = false;
gender: boolean|null = null;
constructor(options: any = new Object) {
for (const iterator of Object.entries(options)) {
if (iterator[0] in this) {
(<any> this)[iterator[0]] = iterator[1];
}
}
}
}
function buildVariant(gender: boolean|null, input: string): StupidTitleVariant
{
const result: StupidTitleVariant = {};
const words = input.split(',').map(word => word.trim());
words.forEach(word => {
const clean_word = word.replace(/[^А-ЯЁа-яёA-Za-z\- ]/g, '');
if (clean_word === '') {
return;
}
result[clean_word] = new StupidTitleSetting({
gender,
rude: word.indexOf('$') >= 0,
enabled: word.indexOf('!') < 0,
weight: 10 * ((word.indexOf('$') >= 0) ? 0.5 : 1) * ((word.indexOf('%') >= 0) ? 0.5 : 1) * ((word.indexOf('^') >= 0) ? 2 : 1)
});
});
return result;
}
(window as any).BuildStupdiTitleVariant = buildVariant;
+4 -4
View File
@@ -5,9 +5,9 @@ import { HumanitarianHellJoke } from "./jokes/humanitarian_hell";
import { MirrorJoke } from "./jokes/mirror"; import { MirrorJoke } from "./jokes/mirror";
import { CarnivalJoke } from "./jokes/carnival"; import { CarnivalJoke } from "./jokes/carnival";
import { CrazyLettersJoke } from "./jokes/crazy_letters"; import { CrazyLettersJoke } from "./jokes/crazy_letters";
import { CrazyPunctuationJoke } from "./jokes/crazy_punctuation";
import { LetterChaosJoke } from "./jokes/letter_chaos"; import { LetterChaosJoke } from "./jokes/letter_chaos";
import { CursorEffectsJoke } from "./jokes/cursor" import { CursorEffectsJoke } from "./jokes/cursor";
import { StupidTitleJoke } from "./jokes/stupid_title";
(window as any).AprilJokes = ((jokes: JokeClassList): JokerClass => { (window as any).AprilJokes = ((jokes: JokeClassList): JokerClass => {
const cl = new JokerClass(); const cl = new JokerClass();
@@ -16,7 +16,7 @@ import { CursorEffectsJoke } from "./jokes/cursor"
cl.add(new joke()); cl.add(new joke());
}); });
cl.regularStart(); $(() => cl.regularStart());
return cl; return cl;
})([ })([
@@ -25,9 +25,9 @@ import { CursorEffectsJoke } from "./jokes/cursor"
MirrorJoke, MirrorJoke,
CarnivalJoke, CarnivalJoke,
CrazyLettersJoke, CrazyLettersJoke,
CrazyPunctuationJoke,
LetterChaosJoke, LetterChaosJoke,
CursorEffectsJoke, CursorEffectsJoke,
StupidTitleJoke,
]); ]);
interface JokeClassList extends Array<new () => Joke> {} interface JokeClassList extends Array<new () => Joke> {}
+62
View File
@@ -0,0 +1,62 @@
/// <reference path="html.d.ts"/>
import { JokerClass } from "./joker";
import $ from "jquery";
import button_html from "./html/button.html";
import styles_html from "./html/styles.html";
import window_html from "./html/window.html";
export class JokerUI {
joker: JokerClass;
constructor(joker: JokerClass) {
this.joker = joker;
$('link[rel="stylesheet"]').eq(0).before(styles_html);
const button = this.button;
button.hide();
(<any>button).mybbModal({
title: "Настройки первоапрельских шуток",
content: () => this.render(),
closer: true,
noscroll: true,
escClose: true,
});
}
get button() {
let button = $('.april-fools__button');
if (button.length === 0) {
button = $(button_html).appendTo('body');
}
return button;
}
show() {
this.button.show();
}
render(): JQuery {
const result: JQuery = $('<div></div>').html(window_html);
const row_template = $('#april-fools__window-row', result).html();
const joke_container = $('.april-fools__jokes', result);
for (const joke of Object.values(this.joker.jokes)) {
const row = $(eval('`' + row_template + '`'));
$('input[type="checkbox"]', row).prop('checked', joke.settings.enabled)
.on('click', e => this.joker.toggleJoke(joke.id, (<any>e).currentTarget.checked));
joke_container.append(row);
}
$('button', result).on('click', async () => {
await this.joker.clearUserStates();
window.location.reload();
});
return $('.april-fools__content', result);
}
}
+7
View File
@@ -40,6 +40,10 @@ const config = {
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: "asset", type: "asset",
}, },
{
test: /\.html$/i,
loader: "html-loader",
},
// Add your rules for custom modules here // Add your rules for custom modules here
// Learn more about loaders from https://webpack.js.org/loaders/ // Learn more about loaders from https://webpack.js.org/loaders/
], ],
@@ -50,6 +54,9 @@ const config = {
externals: { externals: {
jquery: 'jQuery', jquery: 'jQuery',
}, },
watchOptions: {
aggregateTimeout: 20,
},
}; };
module.exports = () => { module.exports = () => {