addText($msg->text_content); } public function addText($text) { $tokens = $this->tokenize($text); if (empty($tokens)) return; $tokens[] = ''; foreach ($tokens as $num => $token) { if ($num === 0) { $this->addTransition([], $token); } else { $start = max(0, $num - $this->size - 1); $end = $num; for ($i = $start; $i < $end; ++$i) { $this->addTransition(array_slice($tokens, $i, $end - $i), $token); if ($end - $i < 5) break; } } } } public function compile() { foreach ($this->transitions as $key => $value) { $this->transitions[$key] = $this->index($this->transitions[$key]); if (empty($this->transitions[$key])) { unset($this->transitions[$key]); } } } public function generate($limit = 100) { $tokens = ['']; $generated = ''; while (strlen($generated) < $limit) { $next = $this->randomNext($tokens); if ($next === '') break; $tokens[] = $next; $generated .= $next; } return $generated; } public function saveAs($name) { $data = [ 'size' => $this->size, 'transitions' => $this->transitions, ]; Storage::disk('chatlib')->put($name.'.json', json_encode($data)); } public function loadFrom($name) { $data = json_decode(Storage::disk('chatlib')->get($name.'.json'), true); $this->size = $data['size']; $this->transitions = $data['transitions']; } private function index($arr) { $result = []; $sum = 0; foreach ($arr as $key => $entry) { $weight = $entry['count']; if ($weight == 1) continue; $lower = $sum; $sum += $weight; $examples = []; if (is_array(end($entry['examples']))) { // already processed $examples = $entry['examples']; } else if ($key === ' ') { $examples = [[' ', 0, 1]]; } else { $subsum = 0; foreach ($entry['examples'] as $example => $subweight) { $sublower = $subsum; $subsum += $subweight; $examples[] = [$example, $sublower, $subsum]; } } $result[] = [$key, $lower, $sum, $examples]; } return $result; } private function randomNext($tokens) { $cnt = count($tokens); for ($size = min($this->size, $cnt); $size > 0; --$size) { $cmb = $this->generalize(array_slice($tokens, -$size)); if (isset($this->transitions[$cmb])) { $pick = $this->pick($this->transitions[$cmb]); if (!is_null($pick)) { return $this->exampleOf($pick); } } } return ''; } private function pick($options) { if (empty($options)) return null; $max = end($options)[2]; $num = random_int(0, $max); $min_index = 0; $max_index = count($options) - 1; while ($min_index < $max_index) { $cur_index = intval(($min_index + $max_index) / 2); $cur_low = $options[$cur_index][1]; $cur_high = $options[$cur_index][2]; if ($cur_low > $num) { $max_index = $cur_index; } else if ($cur_high < $num) { $min_index = $cur_index + 1; } else { $min_index = $cur_index; break; } } return $options[$min_index]; } private function addTransition($state, $next) { $cmb = $this->generalize($state); if (!isset($this->transitions[$cmb])) { $this->transitions[$cmb] = []; } $this->increment($this->transitions[$cmb], $next); } private function increment(&$which, $token) { $generalized = $this->generalize([$token]); if (!isset($which[$generalized])) { $which[$generalized] = [ 'count' => 1, 'examples' => [], ]; $which[$generalized]['examples'][$token] = 1; } else { ++$which[$generalized]['count']; if (!isset($which[$generalized]['examples'][$token])) { $which[$generalized]['examples'][$token] = 1; } else { ++$which[$generalized]['examples'][$token]; } } } private function tokenize($str) { return array_values(array_filter(preg_split('/\b/u', $str), function($token) { if ($token === '') return false; if (preg_match('/cheer\d+/u', strtolower($token))) return false; return true; })); } private function generalize($tokens) { $str = ''; foreach ($tokens as $token) { $replaced = preg_replace('/\d+/u', '0', $token); $replaced = preg_replace('/\s+/u', ' ', $token); $replaced = preg_replace('/(.)\1{2,}/u', '$1$1', $token); $replaced = strtolower($replaced); foreach ($this->aliases as $canonical => $variants) { if (in_array($replaced, $variants)) { $replaced = $canonical; break; } if ($replaced === $canonical) { break; } } $str .= $replaced; } foreach ($this->categories as $category => $patterns) { foreach ($patterns as $pattern) { $str = preg_replace('/\b'.$pattern.'\b/u', '%'.strtoupper($category).'%', $str); } } return $str; } private function exampleOf($pick) { $example = $this->pick($pick[3]); return $example[0]; } private $size = 7; private $transitions = []; private $aliases = [ 'chest' => ['kiste'], 'einen' => ['n', 'nen'], 'musik' => ['mukke'], 'schade' => ['schad'], ]; private $categories = [ 'fail' => [ 'failfish', 'holysm0notlikethis', 'notlikethis', 'tetobridge0', 'vinter0clown', ], 'hype' => [ 'dergoaparty', 'dinodance', 'elemen0party', 'muftaahype', 'luckwuhype', 'olliwahype', 'osora0umbrihype', 'partyhat', 'peepocheer', 'rei0hype', 'sakayahype', 'tetotroete', 'ticknaboargeil0', 'ticknahype0', ], 'kappa' => [ 'kappa(claus|hd)?', ], 'jam' => [ '(cat|dog|rat)jam', 'kanash0jam', 'rei0jamers', 'samusdance', ], 'lol' => [ ':d', 'boothi0lul', 'kekw', 'lol', 'lul', 'rei0lul', 'samusgrin', 'ticknaauslachen', 'xd', ], 'love' => [ '<3', 'duden0love', 'exec0love', 'krawal0heart', 'lodanzhug', 'luckwulove', 'luvsign', 'muftaal', 'osora0love', 'peepoexcitedhug', 'spirit0love', 'svenkalove', 'ticknaherz', ], 'name' => [ 'baba', 'baka', 'bobe?r', 'brog(i|or)', 'cfate', 'danny', 'danzi+', 'daruck', 'dennsen', 'dimez', 'divi', 'dud(en|i+)', 'ele', 'eri(ror)?', '(name)?faker', 'fetti+', 'gamma(chuu)?', 'goat(buster|ie?|y)?', 'hitsu(yan)?', 'holy', 'jem', 'kala(marino)?', 'kromb', 'koval', 'kum(i|o|p)', 'lanux', 'len(esha|chen)', 'leya+', 'magno', 'malmo', 'markam', 'micha', 'mimsy', 'muf(fy|taay)', 'murd(elizer|i+)', 'nami', 'nula', 'onio', 'paulinche', 'phaaze', 'ralen', 'ramond', 'ray(vis)?', 'schulzer', 'skunk(ner)?', 'skipsy', 'soli+', 'sven(ka+)?', 'tantalus', 'teto', 'thalanee?', 'tick(i+|naldo|y+)', 'tofu', 'tr[i0]x+', 'vin(nie?|ny|ter)', 'xall', 'yasi', ], 'pog' => [ 'bumble0Pog', 'komodohype', 'pog', 'pogchamp', 'poggers', 'satono0pog', ], 'run' => [ 'dennsenboots', 'lodanzrun', 'ticknaldosprint', 'vinter0run', ], 'wave' => [ 'dennsenhi', 'dergoawave', 'heyguys', 'holysm0heyguys', 'muftaahey', 'rei0wave', 'sayuri0wave', 'shindi0wave', 'svenkawave', 'wuschlwave', ], 'zelda_boss' => [ 'aga(hnim)?', 'armos( knights)?', 'arrghus', 'blind', 'ganon(dorf)?', 'helma', 'kholdstare', 'lanmo(las)?', 'moldorm', 'mothula', 'mott[ei]', 'trinexx', 'vit(reous|ty)', ], 'zelda_dungeon' => [ 'eastern', 'desert( palace)?', 'gt', 'hera', 'ice ?(palace)?', '(misery )?mire', 'pod', 'skull ?woods', 'swamp', 'thieve\'?s\'? ?town', 'tr', 'tt', ], 'zelda_item' => [ '(big|small|retro|generic) ?keys?', 'b[oö]gen', 'bombos', 'boots', 'bottle', 'bows?', 'bugnet', 'byrna', 'cape', 'ether', 'flasche', 'flippers', 'fl[uö]te', 'frod', '(gloves?|mitts|handschuhe?)', '(half|quarter) ?magic', 'hammer', 'hookshot', '(ice|fire) ?rod', 'lampe?', 'laser ?bridge', 'mearl', 'mirror', 'moon ?pearl', 'mushroom', 'ocarina', 'pilz', 'powder', 'puder', 'quake', '(red|blue) ?cane', '(red|green|blue) ?(goo|potion)', '(red|green|blue|baby) ?mail', '(red|blue|bu|boo|good|bad|both)merang', 'schaufel', '(gro(ss|ß)er? |kleiner? )?schlüssel', 'schwert', 'shovel', 'silvers', 'somaria', 'spiegel', 'sword', ], 'zelda_location' => [ 'big chest', 'bumper( cave)?( ledge)?', '(hyrule)? ?castle ?(tower)?', 'catfish', 'cave 0?', 'chest ?game', 'cutscene ?chest', 'damm', 'desert( ledge)?', 'dig(ging)? ?game', '((back|front) of )?escape', 'gyl', 'hobo', 'hook ?(shot) cave', 'lava ?chest', '(light|dark) ?world', 'lss', 'magic bat', '(dark )?(death )?mountain', 'ped(estal)?', 'pyramid( fairy)?( ledge)?', 'red bomb', 'sahasrahla', 'sasha', 'sick kid', 'stumpy', 'tile ?room', 'torch', 'zora( ledge)?', ], ]; }