Улучшение оформления кода

This commit is contained in:
2023-03-30 13:41:34 +04:00
parent 8b7d6216a4
commit 352100e49c
27 changed files with 1997 additions and 1736 deletions
+14
View File
@@ -0,0 +1,14 @@
import $ from "jquery";
/**
* Получение всех элементов, являющимися TextNode.
* Используется некоторыми шутками для уменьшения влияния на существующее DOM-дерево.
*/
export function getTextNodes(
root: any
): JQuery<HTMLElement | Text | Comment | Document> {
return $(root)
.find("*")
.contents()
.filter((_, node) => node.nodeType === Node.TEXT_NODE);
}
+57
View File
@@ -0,0 +1,57 @@
import {
StupidTitleVariants,
BuildStupdiTitleVariant,
} from "../jokes/stupid_title";
export function StupidTitleDefaultVariants(vars: StupidTitleVariants) {
vars.adj = {
...BuildStupdiTitleVariant(
true,
"адекватный,$адский,активный,алчный,%ангельский,банальный,безнадёжный,$бесполезный,былой,верный,виртуальный,вкусный,воспитанный,выгодный,выходной,гигантский,$глупый,гордый,грамотный,громадный,грубый,далекий,двуличный,деятельный,доброжелательный,$жалкий,жёсткий,жестокий,$жирный,заинтересованный,запасной,застенчивый,знакомый,иностранный,истинный,конструктивный,креативный,ласковый,любимый,мелочный,^мирный,мстительный,небесный,невозможный,независимый,низкий,нужный,объективный,обыкновенный,опрятный,^оригинальный,особенный,острый,откровенный,открытый,пестрый,повседневный,подземный,пожилой,показательный,последний,посторонний,провинциальный,пышный,робкий,родной,самовлюблённый,северный,седой,семейный,серьёзный,сногсшибательный,^солнечный,спокойный,суровый,терпеливый,уверенный в себе,ударный,^удачливый,формальный,хладнокровный,целый,чистый,чрезвычайный,чужой,южный"
),
...BuildStupdiTitleVariant(
false,
"адекватная,$адская,активная,алчная,%ангельская,банальная,безнадёжная,$бесполезная,былая,верная,виртуальная,вкусная,воспитанная,выгодная,выходная,гигантская,$глупая,гордая,грамотная,громадная,грубая,далекая,двуличная,деятельная,доброжелательная,$жалкая,жёсткая,жестокая,$жирная,заинтересованная,запасная,застенчивая,знакомая,иностранная,истинная,конструктивная,креативная,ласковая,любимая,мелочная,^мирная,мстительная,небесная,невозможная,независимая,низкая,нужная,объективная,обыкновенная,опрятная,^оригинальная,особенная,острая,откровенная,открытая,пестрая,повседневная,подземная,пожилая,показательная,последняя,посторонняя,провинциальная,пышная,робкая,родная,самовлюблённая,северная,седая,семейная,серьезная,сногсшибательная,^солнечная,спокаяная,суровая,терпеливая,уверенная в себе,ударная,^удачливая,формальная,хладнокровная,целая,чистая,чрезвычайная,чужая,южная"
),
...vars.adj,
};
vars.noun = {
...BuildStupdiTitleVariant(
true,
"актёр,$балбес,бард,басурманин,борец,бродяга,бруталище,волк,дворф,%дед,%дедушка,джентльмен,диван,динозавр,дракон,ёж,%ёжик,жук,зайц,$злоумышленник,зомби,карасик,кентавр,кит,кот,кролик,кулак,лев,ледник,лесоруб,%мальчег,мальчик,манекен,массажист,мачо,монах,монстр,наследник,ниндзя,носорог,одиночка,организм,орёл,отец,принц,проказник,пузырь,$раб,рыцарь,спортсмен,сурикат,сын,%сынок,тигр,товарищ,хоббит,эльф"
),
...BuildStupdiTitleVariant(
false,
"актриса,акула,$бабка,бабочка,%бабушка,бактерия,белочка,бусинка,валькирия,$ведьма,волторна,волчица,горгулья,дворфиха,девочка,деревяшка,детка,%деффачка,%дочка,дочь,заноза,$злоумышленница,знаменитость,каруселька,килька,конфетка,косточка,кошка,красотка,крышка,леди,$личинка,львица,мать,машина,милашка,монашка,мышь,наследница,орлица,панда,пиранья,принцесса,простуда,$рабыня,русалочка,рыбка,рыбка,скамеечка,спортсменка,тигрица,травка,черепашка,чертовка,эльфийка"
),
...vars.noun,
};
vars.verb = {
...BuildStupdiTitleVariant(
null,
"бежит,берёт и делает,болеет,встаёт,говорит,думает,забывает,замирает,заходит,идёт,%имеет план и придерживается его,ловит,меняется,молчит,нарушает,оглядывается,орёт,освобождает,отвечает,отказаться куда-то идти,отказывается,отказывается вставать,отказывается что-то делать,оформляет,плывёт,поглядывает,поздравляет,поздравляет,получает,прибывает,приказывает,просыпается,прощает,работает,радуется,скрывается,снимает,снимает,снимается в сериале,снимается в фильме,собирается,соединяет,спрашивает,тянет,указывает,учит,читает,что-то скрывает,чувствует опасность,шепчет"
),
...BuildStupdiTitleVariant(
true,
"взял и сделал,додумался,женился,женится,заработался,не спал,не успел,обиделся,познал дзен,присел,проснулся,скрылся,смог,успел"
),
...BuildStupdiTitleVariant(
false,
"взяла и сделала,выходит замуж,вышла замуж,додумалась,заработалась,не спала,не успела,обиделась,познала дзен,присела,проснулась,скрылась,смогла,успела"
),
...vars.verb,
};
vars.place = {
...BuildStupdiTitleVariant(
null,
"в джунглях,в квартире,в космосе,в лесу,в очереди,в пустыне,в театре,%в тёплой и уютной квартире,в филармонии,где-то,на заснеженной вершине,на конференции,на пляже,на форуме,под водой,под дождём,%под проливным дождём"
),
...vars.place,
};
vars.adv = {
...BuildStupdiTitleVariant(
null,
"безрассудно,быстро,внезапно,вновь,$грубо,грустно,медленно,%молниеносно,немедленно,опять,осмысленно,позже,предсказуемо,расчётливо,регулярно,счастливо,уже,уныло"
),
...vars.adv,
};
}
+3 -3
View File
@@ -1,4 +1,4 @@
declare module "*.html" {
const content: string;
export default content;
}
const content: string;
export default content;
}
+1 -1
View File
@@ -1 +1 @@
<button class="april-fools__button">🎉</button>
<button class="april-fools__button">🎉</button>
+27 -27
View File
@@ -1,5 +1,5 @@
<style class="april-fools__styles">
.april-fools__button {
.april-fools__button {
position: fixed;
bottom: 0.2rem;
right: 0.2rem;
@@ -10,17 +10,17 @@
opacity: 0.3;
transition: 0.4s all;
cursor: pointer;
}
.april-fools__button:hover {
}
.april-fools__button:hover {
opacity: 1;
}
.april-fools__joke-row {
}
.april-fools__joke-row {
border-bottom: 1px solid black;
}
.april-fools__joke-row:first-of-type {
}
.april-fools__joke-row:first-of-type {
border-top: 1px solid black;
}
.april-fools__joke {
}
.april-fools__joke {
display: flex;
flex-flow: row wrap;
padding: 10px;
@@ -29,38 +29,38 @@
align-items: center;
gap: 1ch;
cursor: pointer;
}
.april-fools__joke:hover {
}
.april-fools__joke:hover {
text-shadow: 0 0 black;
}
.april-fools__joke:before {
}
.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 {
}
input.april-fools__joke-input:checked + .april-fools__joke {
opacity: 1;
}
input.april-fools__joke-input:checked + .april-fools__joke:before {
}
input.april-fools__joke-input:checked + .april-fools__joke:before {
opacity: 1;
}
.april-fools__joke .april-fools__joke-title {
}
.april-fools__joke .april-fools__joke-title {
flex: 1 auto;
font-weight: bold;
}
.april-fools__joke .april-fools__joke-description {
}
.april-fools__joke .april-fools__joke-description {
flex: 1 auto;
text-align: right;
}
.april-fools__joke .april-fools__joke-chance {
}
.april-fools__joke .april-fools__joke-chance {
flex: 0 0 4ch;
text-align: right;
}
.april-fools__content .april-fools__reset-button {
}
.april-fools__content .april-fools__reset-button {
display: block;
margin: 10px auto;
}
</style>
}
</style>
+19 -11
View File
@@ -1,15 +1,23 @@
<div class="april-fools__content">
<div class="april-fools__jokes"></div>
<div class="april-fools__jokes"></div>
<button class="april-fools__reset-button">Вернуться к настройкам форума (страница будет перезагружена!)</button>
<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>
<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>
+63 -65
View File
@@ -2,87 +2,85 @@
* Объект настроек шутки
*/
export interface JokeSettings {
/**
* Флаг блокировки срабатывания
*/
enabled: boolean;
/**
* Флаг блокировки срабатывания
*/
enabled: boolean;
/**
* Вероятность срабатывания в процентах
*/
chance: number;
/**
* Вероятность срабатывания в процентах
*/
chance: number;
}
/**
* Объект, описывающий шутку
*/
export abstract class Joke {
/**
* Идентификатор шутки
*/
abstract id: string;
/**
* Идентификатор шутки
*/
abstract id: string;
/**
* Заголовок шутки
*/
abstract title: string;
/**
* Заголовок шутки
*/
abstract title: string;
/**
* Описание шутки
*/
abstract description: string;
/**
* Описание шутки
*/
abstract description: string;
/**
* Настройки шутки
*/
settings: JokeSettings;
/**
* Настройки шутки
*/
settings: JokeSettings;
/**
* Проверка вероятности
*
* @param {number} chance Вероятность срабатывания
* @returns {boolean} Флаг срабатывания (true - сработало)
*/
check(chance: number): boolean {
return Math.random() < 0.01 * chance;
}
/**
* Разблокировать шутку
*/
enable() {
this.settings.enabled = true;
}
/**
* Заблокировать шутку
*/
disable() {
this.settings.enabled = false;
}
/**
* Проверка вероятности
*
* @param {number} chance Вероятность срабатывания
* @returns {boolean} Флаг срабатывания (true - сработало)
*/
check(chance: number): boolean {
return Math.random() < 0.01 * chance;
}
/**
* Переключить блокировку шутки
* @param value Новое значение
*/
toggle(value: boolean|null = null) {
this.settings.enabled = (value === null) ? !this.settings.enabled : value;
}
/**
* Разблокировать шутку
*/
enable() {
this.settings.enabled = true;
}
/**
* Запуск шутки
*/
abstract start(): void;
/**
* Заблокировать шутку
*/
disable() {
this.settings.enabled = false;
}
/**
* Переключить блокировку шутки
* @param value Новое значение
*/
toggle(value: boolean | null = null) {
this.settings.enabled = value === null ? !this.settings.enabled : value;
}
/**
* Запуск шутки
*/
abstract start(): void;
}
/**
* Объект, описывающий шутку, которая может быть остановлена
*/
export abstract class StopableJoke extends Joke
{
/**
* Остановка шутки, если возможно
*/
abstract stop(): void;
export abstract class StopableJoke extends Joke {
/**
* Остановка шутки, если возможно
*/
abstract stop(): void;
}
+241 -226
View File
@@ -4,279 +4,294 @@ import { JokerUI } from "./ui";
/**
* Блок обработки перво-апрельских шуток
*
*
* @author Kozhilya
*/
export class JokerClass {
ui: JokerUI = new JokerUI(this);
ui: JokerUI = new JokerUI(this);
/**
* Проверка вероятности
*
* @param {number} chance Вероятность срабатывания
* @returns {boolean} Флаг срабатывания (true - сработало)
*/
check(chance: number): boolean {
return Math.random() < 0.01 * chance;
/**
* Проверка вероятности
*
* @param {number} chance Вероятность срабатывания
* @returns {boolean} Флаг срабатывания (true - сработало)
*/
check(chance: number): boolean {
return Math.random() < 0.01 * chance;
}
/**
* Добавление шутки
*
* @param joke Объект шутки
*/
add(joke: Joke): void {
this.jokes[joke.id] = joke;
}
/**
* Регистрация новой шутки
*
* @param id Идентификатор шутки
* @param handler Метод, выполняющий шутку
* @param settings Объект настроек шутки
*/
addCustom(
id: string,
handler: Function,
settings: JokeSettings | null = null
): Joke {
const joke = new (class extends Joke {
id = id;
title = "Пользовательская шутка";
description = "Для шутки не определено описание";
settings: JokeSettings = { chance: 10, enabled: true };
start() {
handler.call(this, this.settings);
}
})();
Object.assign(joke.settings, settings ?? {});
this.add(joke);
return joke;
}
/**
* Получение полного списка настроек
*/
get settings(): SettingsMap {
const result: SettingsMap = {};
result["core"] = this.core_settings;
for (const entries of Object.entries(this.jokes)) {
const id = entries[0];
const joke = entries[1];
result[id] = joke.settings;
}
/**
* Добавление шутки
*
* @param joke Объект шутки
*/
add(joke: Joke): void {
this.jokes[joke.id] = joke;
return result;
}
/**
* Запись новых настроек
*
* @param settings
*/
set settings(settings: SettingsMap) {
for (const entry of Object.entries(settings)) {
const id = entry[0];
const jokeSettings = entry[1];
const targetSettings =
id === "core" ? this.core_settings : this.jokes[id].settings;
Object.assign(targetSettings, jokeSettings);
}
}
/**
* Разблокировать систему
*/
enable() {
this.core_settings.enabled = true;
}
/**
* Заблокировать систему
*/
disable() {
this.core_settings.enabled = false;
}
/**
* Разблокировать все шутки
*/
enableAll() {
for (const key of Object.keys(this.jokes)) {
this.jokes[key].enable();
}
}
/**
* Заблокировать все шутки
*/
disableAll() {
for (const key of Object.keys(this.jokes)) {
this.jokes[key].disable();
}
}
/**
* Переключение шутки
*/
toggleJoke(joke_id: string, state: boolean) {
const joke = this.jokes[joke_id];
joke.settings.enabled = state;
if (joke instanceof StopableJoke && !state) {
joke.stop();
}
/**
* Регистрация новой шутки
*
* @param id Идентификатор шутки
* @param handler Метод, выполняющий шутку
* @param settings Объект настроек шутки
*/
addCustom(id: string, handler: Function, settings: JokeSettings|null = null): Joke {
const joke = new class extends Joke {
id = id;
this.saveUserStates();
}
title = 'Пользовательская шутка';
/**
* Сохранение пользовательских настроек
*/
saveUserStates() {
const states: any = {};
description = 'Для шутки не определено описание';
settings: JokeSettings = { chance: 10, enabled: true };
start() {
handler.call(this, this.settings);
}
};
Object.assign(joke.settings, settings ?? {});
this.add(joke);
return joke;
for (const key of Object.keys(this.jokes)) {
states[key] = this.jokes[key].settings.enabled;
}
/**
* Получение полного списка настроек
*/
get settings(): SettingsMap {
const result: SettingsMap = {};
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,
})
);
}
result['core'] = this.core_settings;
/**
* Загрузка пользовательских настроек
*/
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();
for (const entries of Object.entries(this.jokes)) {
const id = entries[0];
const joke = entries[1];
result[id] = joke.settings;
}
return result;
if (json.error) {
return;
}
/**
* Запись новых настроек
*
* @param settings
*/
set settings(settings: SettingsMap) {
for (const entry of Object.entries(settings)) {
const id = entry[0];
const jokeSettings = entry[1];
const targetSettings = (id === 'core') ? this.core_settings : this.jokes[id].settings;
const data = JSON.parse(json.response.storage.data["april-fools"]);
Object.assign(targetSettings, jokeSettings);
}
for (const key of Object.keys(this.jokes)) {
this.jokes[key].settings.enabled = data[key];
}
/**
* Разблокировать систему
*/
enable() {
this.core_settings.enabled = true;
}
/**
* Заблокировать систему
*/
disable() {
this.core_settings.enabled = false;
}
/**
* Разблокировать все шутки
*/
enableAll() {
for (const key of Object.keys(this.jokes)) {
this.jokes[key].enable();
}
}
/**
* Заблокировать все шутки
*/
disableAll() {
for (const key of Object.keys(this.jokes)) {
this.jokes[key].disable();
}
}
async clearUserStates() {
await fetch(
"/api.php?" +
$.param({
method: "storage.delete",
user_id: (<any>window).UserID,
key: "april-fools",
token: (<any>window).ForumAPITicket,
})
);
return;
}
/**
* Запуск шутки
*
* @param id
* @param {boolean} forced
*/
start(id: string, forced: boolean = false) {
if (!(id in this.jokes)) return;
const joke = this.jokes[id];
if (
!forced &&
(!joke.settings.enabled || !this.check(joke.settings.chance))
) {
return;
}
/**
* Переключение шутки
*/
toggleJoke(joke_id: string, state: boolean) {
const joke = this.jokes[joke_id];
joke.settings.enabled = state;
joke.start();
}
if (joke instanceof StopableJoke && !state) {
joke.stop();
}
/**
* Запуск всех шуток
*/
startAll(forced: boolean = false): void {
for (const key of Object.keys(this.jokes)) {
this.start(key, forced);
}
}
this.saveUserStates();
regularCheck(): boolean {
if (!this.core_settings.enabled) {
return false;
}
/**
* Сохранение пользовательских настроек
*/
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
}));
if (this.core_settings.testers.indexOf((<any>window).UserID) >= 0) {
return true;
}
/**
* Загрузка пользовательских настроек
*/
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();
const today = new Date();
const month = today.getMonth() + 1;
const day = today.getDate();
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];
}
if (month !== 4 || day !== 1) {
return false;
}
async clearUserStates() {
await fetch('/api.php?' + $.param({
method: 'storage.delete',
user_id: (<any> window).UserID,
key: 'april-fools',
token: (<any> window).ForumAPITicket,
}));
return this.check(this.core_settings.chance);
}
return;
async regularStart() {
if (!this.regularCheck()) {
return;
}
await this.loadUserStates();
this.ui.show();
this.startAll();
}
/**
* Запуск шутки
*
* @param id
* @param {boolean} forced
*/
start(id: string, forced: boolean = false) {
if (!(id in this.jokes))
return;
/**
* Общие настройки системы
*/
core_settings = new CoreSettings();
const joke = this.jokes[id];
if (!forced && (!joke.settings.enabled || !this.check(joke.settings.chance))) {
return;
}
joke.start();
}
/**
* Запуск всех шуток
*/
startAll(forced: boolean = false): void {
for (const key of Object.keys(this.jokes)) {
this.start(key, forced);
}
}
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 month = today.getMonth() + 1;
const day = today.getDate();
if (month !== 4 || day !== 1) {
return false;
}
return this.check(this.core_settings.chance);
}
async regularStart() {
if (!this.regularCheck()) {
return;
}
await this.loadUserStates();
this.ui.show();
this.startAll();
}
/**
* Общие настройки системы
*/
core_settings = new CoreSettings;
/**
* Перечисление всех шуток
*/
jokes: JokeMap = {}
/**
* Перечисление всех шуток
*/
jokes: JokeMap = {};
}
/**
* Объект хранения шуток
*/
interface JokeMap {
[key: string]: Joke;
[key: string]: Joke;
}
/**
* Объект задания настроек
*/
interface SettingsMap {
[key: string]: JokeSettings;
[key: string]: JokeSettings;
}
class CoreSettings implements JokeSettings {
chance = 100;
enabled = true;
chance = 100;
enabled = true;
testers: number[] = [];
}
testers: number[] = [];
}
+47 -42
View File
@@ -4,65 +4,70 @@ import { JokerClass } from "../joker";
/**
* **Карнавал**
*
*
* Аватарки приобретают интересные цвета
*
*
* @author Kozhilya
*/
export class CarnivalJoke extends Joke {
id = 'carnival';
id = "carnival";
title = 'Карнавал';
title = "Карнавал";
description = 'Аватарки приобретают интересные цвета';
description = "Аватарки приобретают интересные цвета";
settings = new CarnivalJokeSettings;
start(): void {
$(this.settings.selector).each((_, elem) => {
const filters: string[] = [];
settings = new CarnivalJokeSettings();
for (const entry of Object.entries(this.settings.variants)) {
if (!this.check(this.settings.variant_chance)) {
continue;
}
start(): void {
$(this.settings.selector).each((_, elem) => {
const filters: string[] = [];
filters.push(entry[0].replace('{0}', Math.round(Math.random() * entry[1]).toString()));
}
for (const entry of Object.entries(this.settings.variants)) {
if (!this.check(this.settings.variant_chance)) {
continue;
}
if (filters.length > 0) {
$(elem).css({'filter': filters.join(' ')});
}
});
}
filters.push(
entry[0].replace(
"{0}",
Math.round(Math.random() * entry[1]).toString()
)
);
}
if (filters.length > 0) {
$(elem).css({ filter: filters.join(" ") });
}
});
}
}
export class CarnivalJokeSettings implements JokeSettings {
enabled: boolean = true;
enabled: boolean = true;
chance: number = 30;
chance: number = 30;
/**
* Селектор всех элементов, которые будут перекрашены
*/
selector: string = 'img[src*="/avatars/"]';
/**
* Селектор всех элементов, которые будут перекрашены
*/
selector: string = 'img[src*="/avatars/"]';
/**
* Вероятность применения некоторого фильтра
*/
variant_chance: number = 20;
/**
* Вероятность применения некоторого фильтра
*/
variant_chance: number = 20;
/**
* Список фильтров и диапозона случайных параметров
*/
variants: CarnivalJokeFilters = {
'sepia({0}%)': 100,
'saturate({0}%)': 100,
'hue-rotate({0}deg)': 360,
'invert({0}%)': 100,
}
/**
* Список фильтров и диапозона случайных параметров
*/
variants: CarnivalJokeFilters = {
"sepia({0}%)": 100,
"saturate({0}%)": 100,
"hue-rotate({0}deg)": 360,
"invert({0}%)": 100,
};
}
export interface CarnivalJokeFilters {
[key: string]: number;
}
[key: string]: number;
}
+23 -23
View File
@@ -3,41 +3,41 @@ import $ from "jquery";
/**
* **Мама, Я сОшЛа С уМа!**
*
*
* иМеНа ЗаБоРчИкОм
*
*
* @author Kozhilya
*/
export class CrazyLettersJoke extends Joke {
id = 'crazy_letters';
id = "crazy_letters";
title = 'Мама, Я сОшЛа С уМа!';
title = "Мама, Я сОшЛа С уМа!";
description = 'иМеНа ЗаБоРчИкОм';
description = "иМеНа ЗаБоРчИкОм";
settings = new CrazyLettersJokeSettings;
settings = new CrazyLettersJokeSettings();
start(): void {
$(this.settings.selector).each((_, elem) => {
let text = $(this).text();
let result = '';
start(): void {
$(this.settings.selector).each((_, elem) => {
let text = $(this).text();
let result = "";
for (let i = 0; i < text.length; i++) {
result += text[i][(i % 2) ? 'toLowerCase' : 'toUpperCase']();
}
for (let i = 0; i < text.length; i++) {
result += text[i][i % 2 ? "toLowerCase" : "toUpperCase"]();
}
$(this).text(result);
});
}
$(this).text(result);
});
}
}
export class CrazyLettersJokeSettings implements JokeSettings {
enabled: boolean = true;
enabled: boolean = true;
chance: number = 20;
chance: number = 20;
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = '.pa-author a';
}
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = ".pa-author a";
}
+258 -244
View File
@@ -6,319 +6,333 @@ const cursor_effects = require("cursor-effects");
/**
* **Красивый курсор**
*
*
* Эффекты, следующие за курсором
*
*
* @author Tim Holman
* @link https://tholman.com/cursor-effects/
*/
export class CursorEffectsJoke extends StopableJoke {
id = 'cursor_effects';
id = "cursor_effects";
title = 'Красивый курсор';
title = "Красивый курсор";
description = 'Эффекты, следующие за курсором';
description = "Эффекты, следующие за курсором";
settings = new CursorEffectsJokeSettings;
settings = new CursorEffectsJokeSettings();
active_cursor_effect: any;
/**
* Разблокировать все варианты курсоров
*/
enableAll() {
for (const key of Object.keys(this.settings.effects)) {
this.settings.effects[<CursorEffectsEnum> key].enabled = true;
}
}
/**
* Заблокировать все варианты курсоров
*/
disableAll() {
for (const key of Object.keys(this.settings.effects)) {
this.settings.effects[<CursorEffectsEnum> key].enabled = false;
}
active_cursor_effect: any;
/**
* Разблокировать все варианты курсоров
*/
enableAll() {
for (const key of Object.keys(this.settings.effects)) {
this.settings.effects[<CursorEffectsEnum>key].enabled = true;
}
}
/**
* Заблокировать все варианты курсоров
*/
disableAll() {
for (const key of Object.keys(this.settings.effects)) {
this.settings.effects[<CursorEffectsEnum>key].enabled = false;
}
}
private weight(effect: CursorEffectsEnum): number {
const s = this.settings.effects[effect];
return s.enabled ? s.weight : 0;
}
getRandomEffect(): CursorEffectsEnum {
const elements = Object.values(CursorEffectsEnum).map(
(e) => <CursorEffectsEnum>e
);
const totalWeight = elements.reduce(
(sum, element) => sum + this.weight(element),
0
);
const randomWeight = Math.random() * totalWeight;
let currentWeight = 0;
for (const element of elements) {
currentWeight += this.weight(element);
if (randomWeight <= currentWeight) {
return element;
}
}
private weight(effect: CursorEffectsEnum): number {
const s = this.settings.effects[effect];
return s.enabled ? s.weight : 0;
}
throw new Error("Тебя быть тут не должно...");
}
getRandomEffect(): CursorEffectsEnum {
const elements = Object.values(CursorEffectsEnum).map(e => <CursorEffectsEnum>e);
const totalWeight = elements.reduce((sum, element) => sum + this.weight(element), 0);
const randomWeight = Math.random() * totalWeight;
let currentWeight = 0;
for (const element of elements) {
currentWeight += this.weight(element);
if (randomWeight <= currentWeight) {
return element;
}
}
start(): void {
const effect = this.getRandomEffect();
const settings = this.settings.effects[effect];
throw new Error("Тебя быть тут не должно...");
}
start(): void {
const effect = this.getRandomEffect();
const settings = this.settings.effects[effect];
this.active_cursor_effect = resolveCursorEffectsEnum(effect, settings);
}
this.active_cursor_effect = resolveCursorEffectsEnum(effect, settings);
}
stop(): void {
this.active_cursor_effect.destroy();
}
stop(): void {
this.active_cursor_effect.destroy();
}
}
enum CursorEffectsEnum {
Rainbow = 'rainbow',
FairyDust = 'fairy_dust',
SpringyEmoji = 'springy_emoji',
Emoji = 'emoji',
Ghost = 'ghost',
Trailing = 'trailing',
TextFlag = 'text_flag',
FollowingDot = 'following_dot',
Bubbles = 'bubbles',
Snowflake = 'snowflake',
Clock = 'clock'
Rainbow = "rainbow",
FairyDust = "fairy_dust",
SpringyEmoji = "springy_emoji",
Emoji = "emoji",
Ghost = "ghost",
Trailing = "trailing",
TextFlag = "text_flag",
FollowingDot = "following_dot",
Bubbles = "bubbles",
Snowflake = "snowflake",
Clock = "clock",
}
function resolveCursorEffectsEnum(effect: CursorEffectsEnum|string, options: CursorEffectsSetting): any {
switch (effect) {
case CursorEffectsEnum.Rainbow:
return cursor_effects.rainbowCursor(options);
function resolveCursorEffectsEnum(
effect: CursorEffectsEnum | string,
options: CursorEffectsSetting
): any {
switch (effect) {
case CursorEffectsEnum.Rainbow:
return cursor_effects.rainbowCursor(options);
case CursorEffectsEnum.FairyDust:
return cursor_effects.fairyDustCursor(options);
case CursorEffectsEnum.FairyDust:
return cursor_effects.fairyDustCursor(options);
case CursorEffectsEnum.SpringyEmoji:
return cursor_effects.springyEmojiCursor(options);
case CursorEffectsEnum.SpringyEmoji:
return cursor_effects.springyEmojiCursor(options);
case CursorEffectsEnum.Emoji:
return cursor_effects.emojiCursor(options);
case CursorEffectsEnum.Emoji:
return cursor_effects.emojiCursor(options);
case CursorEffectsEnum.Ghost:
return cursor_effects.ghostCursor(options);
case CursorEffectsEnum.Ghost:
return cursor_effects.ghostCursor(options);
case CursorEffectsEnum.Trailing:
return cursor_effects.trailingCursor(options);
case CursorEffectsEnum.Trailing:
return cursor_effects.trailingCursor(options);
case CursorEffectsEnum.TextFlag:
return cursor_effects.textFlag(options);
case CursorEffectsEnum.TextFlag:
return cursor_effects.textFlag(options);
case CursorEffectsEnum.FollowingDot:
return cursor_effects.followingDotCursor(options);
case CursorEffectsEnum.FollowingDot:
return cursor_effects.followingDotCursor(options);
case CursorEffectsEnum.Bubbles:
return cursor_effects.bubbleCursor(options);
case CursorEffectsEnum.Bubbles:
return cursor_effects.bubbleCursor(options);
case CursorEffectsEnum.Snowflake:
return cursor_effects.snowflakeCursor(options);
case CursorEffectsEnum.Snowflake:
return cursor_effects.snowflakeCursor(options);
case CursorEffectsEnum.Clock:
return cursor_effects.clockCursor(options);
}
case CursorEffectsEnum.Clock:
return cursor_effects.clockCursor(options);
}
}
export class CursorEffectsJokeSettings implements JokeSettings {
enabled: boolean = true;
enabled: boolean = true;
chance: number = 100;
chance: number = 100;
effects: { [key in CursorEffectsEnum]?: CursorEffectsSetting } = {
effects: { [key in CursorEffectsEnum]?: CursorEffectsSetting } = {
/**
* Радужный след за курсором
*/
[CursorEffectsEnum.Rainbow]:
new (class extends DefaultCursorEffectsSetting {
/**
* Радужный след за курсором
* Длина полосы
*/
[CursorEffectsEnum.Rainbow]: new class extends DefaultCursorEffectsSetting {
/**
* Длина полосы
*/
length: number = 20;
/**
* Ширина полосы
*/
size: number = 3;
/**
* Цвета полос (сверху вниз)
*/
colors: string[] = [
"#9400D3",
"#4B0082",
"#0000FF",
"#00FF00",
"#FFFF00",
"#FF7F00",
"#FF0000"
];
},
length: number = 20;
/**
* Цепочка из эмодзи "на резинке" под курсором
* Ширина полосы
*/
[CursorEffectsEnum.SpringyEmoji]: new class extends DefaultCursorEffectsSetting {
/**
* Висящий эмодзи
*/
emoji: string = "🤪"
},
size: number = 3;
/**
* Падающие от курсора эмодзи
* Цвета полос (сверху вниз)
*/
[CursorEffectsEnum.Emoji]: new class extends DefaultCursorEffectsSetting {
/**
* Падающие эмодзи
*/
emoji: string[] = ["🔥", "🐬", "🦆"]
},
colors: string[] = [
"#9400D3",
"#4B0082",
"#0000FF",
"#00FF00",
"#FFFF00",
"#FF7F00",
"#FF0000",
];
})(),
/**
* Цепочка из эмодзи "на резинке" под курсором
*/
[CursorEffectsEnum.SpringyEmoji]:
new (class extends DefaultCursorEffectsSetting {
/**
* Висящий эмодзи
*/
emoji: string = "🤪";
})(),
/**
* Падающие от курсора эмодзи
*/
[CursorEffectsEnum.Emoji]: new (class extends DefaultCursorEffectsSetting {
/**
* Падающие эмодзи
*/
emoji: string[] = ["🔥", "🐬", "🦆"];
})(),
/**
* Падающие от курсора пылинки
*/
[CursorEffectsEnum.FairyDust]:
new (class extends DefaultCursorEffectsSetting {
/**
* Цвет пылинок
*/
colors: string[] = ["#ff0000", "#00ff00", "#0000ff"];
})(),
/**
* След "зависших" курсоров
*/
[CursorEffectsEnum.Ghost]: new DefaultCursorEffectsSetting(),
/**
* След курсоров "на резинке"
*/
[CursorEffectsEnum.Trailing]:
new (class extends DefaultCursorEffectsSetting {
/**
* Количество элементов
*/
particles: number = 15;
/**
* Падающие от курсора пылинки
* Скорость частиц
*/
[CursorEffectsEnum.FairyDust]: new class extends DefaultCursorEffectsSetting {
/**
* Цвет пылинок
*/
colors: string[] = ["#ff0000", "#00ff00", "#0000ff"];
},
rate: number = 0.4;
/**
* След "зависших" курсоров
* Ссылка на картинку с курсором
* Используйте `null`, что бы использовать картинку курсора по умолчанию
*/
[CursorEffectsEnum.Ghost]: new DefaultCursorEffectsSetting,
baseImageSrc: string | null = null;
})(),
/**
* Текст, развивающийся флагом от курсора
*/
[CursorEffectsEnum.TextFlag]:
new (class extends DefaultCursorEffectsSetting {
/**
* Отображаемый текст
*/
text: string = "С днём смеха!";
/**
* След курсоров "на резинке"
* Цвет текста
*/
[CursorEffectsEnum.Trailing]: new class extends DefaultCursorEffectsSetting {
/**
* Количество элементов
*/
particles: number = 15;
/**
* Скорость частиц
*/
rate: number = 0.4;
/**
* Ссылка на картинку с курсором
* Используйте `null`, что бы использовать картинку курсора по умолчанию
*/
baseImageSrc: string | null = null;
},
color: string = "#000000";
/**
* Текст, развивающийся флагом от курсора
* Шрифт текста
*/
[CursorEffectsEnum.TextFlag]: new class extends DefaultCursorEffectsSetting {
/**
* Отображаемый текст
*/
text: string = "С днём смеха!";
/**
* Цвет текста
*/
color: string = "#000000";
/**
* Шрифт текста
*/
font: string = "monospace";
/**
* Размер текста
*/
textSize: number = 12;
/**
* Растояние между бквами
*/
gap: number = 14;
/**
* Размер... чего-то.
*/
size: number = 3;
},
font: string = "monospace";
/**
* Точка, преследующая курсор
* Размер текста
*/
[CursorEffectsEnum.FollowingDot]: new class extends DefaultCursorEffectsSetting {
/**
* Цвет догоняющей точки
*/
color: string = "#323232a6";
},
/**
* Пузырьки от курсора
*/
[CursorEffectsEnum.Bubbles]: new DefaultCursorEffectsSetting,
textSize: number = 12;
/**
* Снежинки, летящие от курсора
*
* Отключено, потому что сейчас не зима :D
* Растояние между бквами
*/
[CursorEffectsEnum.Snowflake]: new class extends DefaultCursorEffectsSetting {
weight: number = 0;
},
gap: number = 14;
/**
* Циферблат вокруг курсора
* Размер... чего-то.
*/
[CursorEffectsEnum.Clock]: new class extends DefaultCursorEffectsSetting {
/**
* Цвет даты
*/
dateColor: string = "blue";
size: number = 3;
})(),
/**
* Цвет циферблата
*/
faceColor: string = "black";
/**
* Цвет секундной стрелки
*/
secondsColor: string = "red";
/**
* Цвет минутной стрелки
*/
minutesColor: string = "black";
/**
* Цвет часовой стрелки
*/
hoursColor: string = "black";
},
};
/**
* Точка, преследующая курсор
*/
[CursorEffectsEnum.FollowingDot]:
new (class extends DefaultCursorEffectsSetting {
/**
* Цвет догоняющей точки
*/
color: string = "#323232a6";
})(),
/**
* Пузырьки от курсора
*/
[CursorEffectsEnum.Bubbles]: new DefaultCursorEffectsSetting(),
/**
* Снежинки, летящие от курсора
*
* Отключено, потому что сейчас не зима :D
*/
[CursorEffectsEnum.Snowflake]:
new (class extends DefaultCursorEffectsSetting {
weight: number = 0;
})(),
/**
* Циферблат вокруг курсора
*/
[CursorEffectsEnum.Clock]: new (class extends DefaultCursorEffectsSetting {
/**
* Цвет даты
*/
dateColor: string = "blue";
/**
* Цвет циферблата
*/
faceColor: string = "black";
/**
* Цвет секундной стрелки
*/
secondsColor: string = "red";
/**
* Цвет минутной стрелки
*/
minutesColor: string = "black";
/**
* Цвет часовой стрелки
*/
hoursColor: string = "black";
})(),
};
}
interface CursorEffectsSetting {
[key: string]: any;
[key: string]: any;
}
class DefaultCursorEffectsSetting implements CursorEffectsSetting {
/**
* Флаг возможности срабатывания
*/
enabled: boolean = true;
/**
* Флаг возможности срабатывания
*/
enabled: boolean = true;
/**
* Весовой коэффициент вероятности курсора
*/
weight: number = 50;
/**
* Весовой коэффициент вероятности курсора
*/
weight: number = 50;
}
+28 -21
View File
@@ -1,38 +1,45 @@
import { Joke, JokeSettings, StopableJoke } from "../joke";
import { getTextNodes } from "../common";
import $ from "jquery";
/**
* **Ад гуманитария**
*
*
* Все "тся" заменяются на "ться", а все "ться" на "тся"
*
*
* @author Kozhilya
*/
export class HumanitarianHellJoke extends Joke {
id = 'humanitarian_hell';
id = "humanitarian_hell";
title = 'Ад гуманитария';
title = "Ад гуманитария";
description = 'Все "тся" заменяются на "ться", а все "ться" на "тся"';
settings = new HumanitarianHellJokeSettings;
description = 'Все "тся" заменяются на "ться", а все "ться" на "тся"';
start(): void {
$(this.settings.selector).each(function () {
$(this).html($(this).html().replace(/(ть?ся)/g, function (fnd) {
return (fnd.indexOf('ь') > 0) ? 'тся' : 'ться';
}));
});
}
settings = new HumanitarianHellJokeSettings();
processText(text: string): string {
return text.replace(/(ть?ся)/g, function (fnd) {
return fnd.indexOf("ь") > 0 ? "тся" : "ться";
});
}
start(): void {
const textNodes = getTextNodes(this.settings.selector);
textNodes.each((_, elem) => {
elem.textContent = this.processText(elem.textContent);
});
}
}
export class HumanitarianHellJokeSettings implements JokeSettings {
enabled: boolean = true;
enabled: boolean = true;
chance: number = 20;
chance: number = 20;
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = '.post-content';
}
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = ".post-content";
}
+53 -42
View File
@@ -1,68 +1,79 @@
import { Joke, JokeSettings, StopableJoke } from "../joke";
import { getTextNodes } from "../common";
import $ from "jquery";
import { JokerClass } from "../joker";
/**
* **Буквенный хаос**
*
* Все буквы в словах, кроме первой и последней, перемешены.
*
*
* Все буквы в словах, кроме первой и последней, перемешены
*
* @author Kozhilya
*/
export class LetterChaosJoke extends Joke {
id = 'letter_chaos';
id = "letter_chaos";
title = 'Буквенный хаос';
title = "Буквенный хаос";
description = 'Все буквы в словах, кроме первой и последней, перемешены.';
description = "Все буквы в словах, кроме первой и последней, перемешены";
settings = new LetterChaosJokeSettings;
settings = new LetterChaosJokeSettings();
punctuationSymbols: string[] | null = null;
punctuationSymbols: string[] | null = null;
private shuffleLetters(word: string): string {
// Check if the word has two or more letters
if ((word.length < 3) || !this.check(this.settings.shuffle_chance)) {
return word;
}
// Split the word into an array of characters
const characters = word.split("");
// Shuffle the characters between the first and last letters
const middleCharacters = characters.slice(1, -1);
for (let i = middleCharacters.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[middleCharacters[i], middleCharacters[j]] = [middleCharacters[j], middleCharacters[i]];
}
// Join the shuffled characters back into a word
return [characters[0], ...middleCharacters, characters[characters.length - 1]].join("");
private shuffleLetters(word: string): string {
// Check if the word has two or more letters
if (word.length < 3 || !this.check(this.settings.shuffle_chance)) {
return word;
}
private processNode(node: any) {
node.textContent = node.textContent?.replace(/([А-Яа-яA-Za-z]+)/gm, this.shuffleLetters);
// Split the word into an array of characters
const characters = word.split("");
// Shuffle the characters between the first and last letters
const middleCharacters = characters.slice(1, -1);
for (let i = middleCharacters.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[middleCharacters[i], middleCharacters[j]] = [
middleCharacters[j],
middleCharacters[i],
];
}
start(): void {
const items = $(this.settings.selector).find('*').contents().filter((_, node) => node.nodeType === Node.TEXT_NODE);
// Join the shuffled characters back into a word
return [
characters[0],
...middleCharacters,
characters[characters.length - 1],
].join("");
}
items.each((_, node) => this.processNode(node))
}
private processNode(node: any) {
node.textContent = node.textContent?.replace(
/([А-Яа-яA-Za-z]+)/gm,
this.shuffleLetters
);
}
start(): void {
const textNodes = getTextNodes(this.settings.selector);
textNodes.each((_, node) => this.processNode(node));
}
}
export class LetterChaosJokeSettings implements JokeSettings {
enabled: boolean = true;
enabled: boolean = true;
chance: number = 20;
chance: number = 20;
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = '.post-content';
/**
* Селектор всех элементов, которые будут затронуты
*/
selector: string = ".post-content";
/**
* Вероятность того, что буквы будут перемешаны
*/
shuffle_chance: number = 50;
/**
* Вероятность того, что буквы будут перемешаны
*/
shuffle_chance: number = 50;
}
+20 -16
View File
@@ -3,30 +3,34 @@ import $ from "jquery";
/**
* **Зеркало**
*
*
* Элементы форума отражаются (по крайней мере, пытаются)
*
*
* @author Kozhilya
*/
export class MirrorJoke extends Joke {
id = 'mirror';
id = "mirror";
title = 'Зеркало';
title = "Зеркало";
description = 'Элементы форума отражаются (по крайней мере, пытаются)';
settings = new MirrorJokeSettings;
description = "Элементы форума отражаются (по крайней мере, пытаются)";
start(): void {
$('.post, .post h3, .post-links, .post-author, .post-content')
.css('transform', 'scaleX(-1)').css('-moz-transform', 'scaleX(-1)').css('-moz-transform', 'scaleX(-1)').css('filter', 'FlipH').css('-ms-filter', '"FlipH"');
$('.post-links, .post-links ul, .post h3 span').css('margin-left', '0');
$('.post h3 span').css('margin-right', '25em');
}
settings = new MirrorJokeSettings();
start(): void {
$(".post, .post h3, .post-links, .post-author, .post-content")
.css("transform", "scaleX(-1)")
.css("-moz-transform", "scaleX(-1)")
.css("-moz-transform", "scaleX(-1)")
.css("filter", "FlipH")
.css("-ms-filter", '"FlipH"');
$(".post-links, .post-links ul, .post h3 span").css("margin-left", "0");
$(".post h3 span").css("margin-right", "25em");
}
}
export class MirrorJokeSettings implements JokeSettings {
enabled: boolean = false;
enabled: boolean = false;
chance: number = 30;
}
chance: number = 30;
}
+61 -61
View File
@@ -3,95 +3,95 @@ import $ from "jquery";
/**
* **Форумо-трясение**
*
*
* Страница форума начинает трястись
*
*
* @author Kozhilya
*/
export class ShakeJoke extends StopableJoke {
id = 'shake';
id = "shake";
title = 'Форумо-трясение';
title = "Форумо-трясение";
description = 'Страница форума начинает трястись';
description = "Страница форума начинает трястись";
settings = new ShakeJokeSettings;
settings = new ShakeJokeSettings();
private interval_id: NodeJS.Timer;
private interval_id: NodeJS.Timer;
private items: JQuery;
private items: JQuery;
private data_name: string = 'april-fools-shake';
private data_name: string = "april-fools-shake";
rand(a: number): number {
return a + this.settings.force * (Math.random() * 2 - 1);
}
rand(a: number): number {
return a + this.settings.force * (Math.random() * 2 - 1);
}
start(): void {
this.items = $(this.settings.selector);
start(): void {
this.items = $(this.settings.selector);
this.items.each((_, elem) => {
let data: ShakeJokeMargin = {};
this.items.each((_, elem) => {
let data: ShakeJokeMargin = {};
this.settings.directions.forEach(d => {
data[d] = parseInt($(elem).css('margin-' + d));
});
this.settings.directions.forEach((d) => {
data[d] = parseInt($(elem).css("margin-" + d));
});
$(elem).data(this.data_name, data);
});
$(elem).data(this.data_name, data);
});
this.interval_id = setInterval(() => {
this.items.each((_, elem) => {
const data: ShakeJokeMargin = $(elem).data(this.data_name);
this.interval_id = setInterval(() => {
this.items.each((_, elem) => {
const data: ShakeJokeMargin = $(elem).data(this.data_name);
for (const entry of Object.entries(data)) {
$(elem).css('margin-' + entry[0], this.rand(entry[1]))
}
});
}, this.settings.interval);
}
for (const entry of Object.entries(data)) {
$(elem).css("margin-" + entry[0], this.rand(entry[1]));
}
});
}, this.settings.interval);
}
stop(): void {
clearInterval(this.interval_id);
this.items.each((_, elem) => {
let data: ShakeJokeMargin = {};
stop(): void {
clearInterval(this.interval_id);
this.settings.directions.forEach(d => {
$(elem).css('margin-' + d, data[d]);
});
this.items.each((_, elem) => {
let data: ShakeJokeMargin = {};
$(elem).data(this.data_name, data);
});
}
this.settings.directions.forEach((d) => {
$(elem).css("margin-" + d, data[d]);
});
$(elem).data(this.data_name, data);
});
}
}
export class ShakeJokeSettings implements JokeSettings {
enabled: boolean = false;
enabled: boolean = false;
chance: number = 1;
chance: number = 1;
/**
* Направление тряски (тряска происходит путём изменения перечисленных направлений margin на значение ±force от стандартного значения)
*/
directions: string[] = ['top', 'left', 'bottom', 'left'];
/**
* Направление тряски (тряска происходит путём изменения перечисленных направлений margin на значение ±force от стандартного значения)
*/
directions: string[] = ["top", "left", "bottom", "left"];
/**
* Сила тряски - насколько отклоняется элемент от стандартного значения
*/
force: number = 0.5;
/**
* Сила тряски - насколько отклоняется элемент от стандартного значения
*/
force: number = 0.5;
/**
* Селектор всех элементов, которые будут трястись
*/
selector: string = '[id^=pun]';
/**
* Селектор всех элементов, которые будут трястись
*/
selector: string = "[id^=pun]";
/**
* Частота тряски
*/
interval: number = 100;
/**
* Частота тряски
*/
interval: number = 100;
}
export interface ShakeJokeMargin {
[key: string]: number;
}
[key: string]: number;
}
+256 -238
View File
@@ -1,88 +1,99 @@
import { Joke, JokeSettings, StopableJoke } from "../joke";
import $ from "jquery";
import { StupidTitleDefaultVariants } from "../data/stupid_title_variants";
/**
* **Глупые статусы**
*
* Каждому пользователю даётся новый глупый статус.
*
*
* Каждому пользователю даётся новый глупый статус
*
* @author Kozhilya
*/
export class StupidTitleJoke extends Joke {
id = 'stupid_title';
id = "stupid_title";
title = 'Глупые статусы';
title = "Глупые статусы";
description = 'Каждому пользователю даётся новый глупый статус.';
description = "Каждому пользователю даётся новый глупый статус";
settings = new StupidTitleJokeSettings;
settings = new StupidTitleJokeSettings();
private loadedGenders: any = null;
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;
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;
}, []);
json.response.users.forEach((element: any) => {
if (element.sex === '0') {
return;
}
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[element.username] = element.sex === '1';
});
this.loadedGenders = {};
if (json.error) {
return;
}
resolveGender(name: string): boolean {
if (this.settings.gender_resolver !== null) {
return !!this.settings.gender_resolver.call(this, name);
}
json.response.users.forEach((element: any) => {
if (element.sex === "0") {
return;
}
if (name in this.loadedGenders) {
return !!this.loadedGenders[name];
}
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";
}
private variantsGenerated = false;
async start(): Promise<void> {
if (!this.variantsGenerated) {
StupidTitleDefaultVariants(this.settings.variants);
this.variantsGenerated = true;
}
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();
return this.selectRandom(name, { 'm': 1, 'f': 1 }) === 'm';
}
cache[name] = this.generatePhrase(name);
}
async start(): Promise<void> {
await this.loadGenders();
$(".pa-title", root).text(cache[name]);
});
}
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 {
/*
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)
@@ -90,223 +101,230 @@ export class StupidTitleJoke extends Joke {
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;
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 = date.getUTCHours() * 60 + date.getUTCMinutes();
if (this.settings.change_frequency === 0) {
this.seed = time * 60 + date.getUTCSeconds();
return;
}
private seed = 0;
this.seed = Math.floor(time / this.settings.change_frequency);
}
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);
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);
console.log('Seed reset:', time, this.seed);
let currentWeight = 0;
for (const element of elements) {
currentWeight += variants[element];
if (randomWeight <= currentWeight) {
return element;
}
}
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("Тебя быть тут не должно...");
}
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;
}
private buildVariants(type: string, gender: boolean): StupidTitleWeightedVariants {
const result: StupidTitleWeightedVariants = {};
const variants: StupidTitleVariant = this.settings.variants[type];
return result;
}
for (const iterator of Object.entries(variants)) {
const word = iterator[0];
const wordSettings = iterator[1];
if (!wordSettings.enabled || (wordSettings.gender === !gender)) {
continue;
}
private capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
if (!this.settings.rude && wordSettings.rude) {
continue;
}
generatePhrase(name: string): string {
const gender = this.resolveGender(name);
const prhase = this.selectRandom(name, this.settings.schemas);
result[word] = wordSettings.weight;
}
return result;
}
return this.capitalizeFirst(
prhase.replace(/\{(.+?)\}/g, (_, type: string) => {
const variants = this.buildVariants(type, gender);
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);
}));
}
return this.selectRandom(name, variants);
})
);
}
}
export class StupidTitleJokeSettings implements JokeSettings {
enabled: boolean = true;
chance: number = 100;
enabled: boolean = true;
chance: number = 100;
/**
* Определение пола по имени.
* По умолчанию род устанавливается случайным образом
*
* Если установлено значение `null`, система попытается загрузить пол, установленный в настройках аккаунта. Если этот пол определить невозможно, то он будет выбран случайно. Эта настройка позволяет изменить это поведение, если ваш форум обладает ролевой системой, в которой пол определяется иным образом.
*
* @param name Имя персонажа
* @returns true, если мужской род; false, если женский род
*/
gender_resolver: Function | null = null;
/**
* Определение пола по имени.
* По умолчанию род устанавливается случайным образом
*
* Если установлено значение `null`, система попытается загрузить пол, установленный в настройках аккаунта. Если этот пол определить невозможно, то он будет выбран случайно. Эта настройка позволяет изменить это поведение, если ваш форум обладает ролевой системой, в которой пол определяется иным образом.
*
* @param name Имя персонажа
* @returns true, если мужской род; false, если женский род
*/
gender_resolver: Function | null = null;
/**
* Разрешить возможно оскорбительные варианты
*/
rude: boolean = false;
/**
* Разрешить возможно оскорбительные варианты
*/
rude: boolean = false;
/**
* Частота смены статусов (в минутах)
*/
change_frequency: number = 5;
/**
* Частота смены статусов (в минутах)
* Установите значение 0, что бы обновление происходило при каждом обновлении страницы
*/
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 }),
},
};
/**
* Разрешённые схемы формирования статусов
*
* {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: {},
noun: {},
verb: {},
place: {},
adv: {},
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
export interface StupidTitleVariants {
[key: string]: StupidTitleVariant;
}
interface StupidTitleVariant {
[key: string]: StupidTitleSetting
[key: string]: StupidTitleSetting;
}
interface StupidTitleWeightedVariants {
[key: string]: number
[key: string]: number;
}
class StupidTitleSetting {
weight: number = 10;
weight: number = 10;
enabled: boolean = true;
rude: boolean = false;
enabled: boolean = true;
gender: boolean|null = null;
rude: boolean = false;
constructor(options: any = new Object) {
for (const iterator of Object.entries(options)) {
if (iterator[0] in this) {
(<any> this)[iterator[0]] = iterator[1];
}
}
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, '');
export function BuildStupdiTitleVariant(
gender: boolean | null,
input: string
): StupidTitleVariant {
const result: StupidTitleVariant = {};
const words = input.split(",").map((word) => word.trim());
if (clean_word === '') {
return;
}
words.forEach((word) => {
const clean_word = word.replace(/[^А-ЯЁа-яёA-Za-z\- ]/g, "");
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)
});
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;
return result;
}
(window as any).BuildStupdiTitleVariant = buildVariant;
(window as any).BuildStupdiTitleVariant = BuildStupdiTitleVariant;
+15 -15
View File
@@ -10,24 +10,24 @@ import { CursorEffectsJoke } from "./jokes/cursor";
import { StupidTitleJoke } from "./jokes/stupid_title";
(window as any).AprilJokes = ((jokes: JokeClassList): JokerClass => {
const cl = new JokerClass();
const cl = new JokerClass();
jokes.forEach(joke => {
cl.add(new joke());
});
jokes.forEach((joke) => {
cl.add(new joke());
});
$(() => cl.regularStart());
$(() => cl.regularStart());
return cl;
return cl;
})([
ShakeJoke,
HumanitarianHellJoke,
MirrorJoke,
CarnivalJoke,
CrazyLettersJoke,
LetterChaosJoke,
CursorEffectsJoke,
StupidTitleJoke,
ShakeJoke,
HumanitarianHellJoke,
MirrorJoke,
CarnivalJoke,
CrazyLettersJoke,
LetterChaosJoke,
CursorEffectsJoke,
StupidTitleJoke,
]);
interface JokeClassList extends Array<new () => Joke> {}
interface JokeClassList extends Array<new () => Joke> {}
+45 -43
View File
@@ -5,58 +5,60 @@ 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;
joker: JokerClass;
constructor(joker: JokerClass) {
this.joker = joker;
constructor(joker: JokerClass) {
this.joker = joker;
$('link[rel="stylesheet"]').eq(0).before(styles_html);
$('link[rel="stylesheet"]').eq(0).before(styles_html);
const button = this.button;
const button = this.button;
button.hide();
(<any>button).mybbModal({
title: "Настройки первоапрельских шуток",
content: () => this.render(),
closer: true,
noscroll: true,
escClose: true,
});
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");
}
get button() {
let button = $('.april-fools__button');
return button;
}
if (button.length === 0) {
button = $(button_html).appendTo('body');
}
show() {
this.button.show();
}
return button;
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);
}
show() {
this.button.show();
}
$("button", result).on("click", async () => {
await this.joker.clearUserStates();
window.location.reload();
});
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);
}
}
return $(".april-fools__content", result);
}
}