size = $size; $converted = []; foreach ($this->categories as $category => $patterns) { $converted_patterns = []; foreach ($patterns as $pattern) { $converted_patterns[] = '/\b'.$pattern.'\b/u'; } $converted[strtoupper($category)] = $converted_patterns; } $this->categories = $converted; } public function addMessage(ChatLog $msg, ChatLog $previous = null) { if ($msg->isReply()) { $this->addText($msg->text_content, $msg->getReplyParent()); } else if (!is_null($previous)) { $this->addText($msg->text_content, $previous->text_content); } else { $this->addText($msg->text_content); } } public function addText($text, $context = '') { $tokens = $this->tokenize($text); for ($i = 0; $i < count($tokens) - $this->size; ++$i) { $this->addTransition(array_slice($tokens, $i, $this->size), $tokens[$i + $this->size]); } if (!empty($context)) { $tokens = $this->tokenizeWithContext($text, $context); $size = min($this->size - 1, count($tokens) - $this->size); for ($i = 0; $i < $size; ++$i) { $this->addTransition(array_slice($tokens, $i, $this->size), $tokens[$i + $this->size]); } } } public function compile() { foreach ($this->transitions as $key => $values) { $this->transitions[$key] = $this->index($values); } } public function generate($context = null) { if (!is_null($context)) { $tokens = $this->tokenizeWithContext('', $context); $generated = $this->loop($tokens); if (!empty($generated)) { return $generated; } } $tokens = $this->tokenize(''); return $this->loop($tokens); } 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 => $weight) { $lower = $sum; $sum += $weight; $result[] = [$key, $lower, $sum]; } return $result; } private function loop($tokens) { while (count($tokens) < 50) { $next = $this->randomNext($tokens); if ($next === ' ') break; $tokens[] = $next; } return $this->untokenize($tokens); } private function randomNext($tokens) { $key = $this->makeKey($tokens); if (!isset($this->transitions[$key])) return ' '; $pick = $this->pick($this->transitions[$key]); return $pick[0]; } private function pick($options) { if (empty($options)) return null; $max = end($options)[2] - 1; $num = random_int(0, $max); return static::search($options, $num); } public static function search($options, $num) { $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] - 1; 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($tokens, $next) { $key = $this->makeKey($tokens); if (!isset($this->transitions[$key])) { $this->transitions[$key] = []; } if (!isset($this->transitions[$key][$next])) { $this->transitions[$key][$next] = 1; } else { ++$this->transitions[$key][$next]; } } private function splitText($text) { if (trim($text) === '') return []; return preg_split('/\s+/u', $text); } private function makeKey($tokens) { $key = $this->joinText(array_slice($tokens, $this->size * -1)); $key = mb_strtolower($key); $key = str_replace(['.', ',', ':', ';', '!', '?', '^', '+', '-', '"', "'", '(', ')', '[', ']'], '', $key); $key = preg_replace('/\d+/u', '0', $key); foreach ($this->categories as $category => $patterns) { $key = preg_replace($patterns, $category, $key); } return $key; } private function joinText($tokens) { return implode(' ', $tokens); } private function untokenize($tokens) { return $this->joinText(array_slice($tokens, $this->size)); } private function tokenize($text) { $tokens = $this->splitText($text); $combined = array_merge(array_fill(0, $this->size, ' '), $tokens); if (!empty($tokens)) { $combined[] = ' '; } return $combined; } private function tokenizeWithContext($text, $context) { $combined = $this->tokenize($text); $context_tokens = array_slice($this->splitText($context), $this->size * -1 + 1); for ($i = 0; $i < count($context_tokens); ++$i) { $combined[$this->size - $i - 2] = $context_tokens[count($context_tokens) - $i - 1]; } return $combined; } private $size; private $transitions = []; 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', 'falcnwavehi', 'heyguys', 'holysm0heyguys', 'muftaahey', 'rei0wave', 'sayuri0wave', 'shindi0wave', 'svenkawave', 'wuschlwave', ], 'zb' => [ 'aga(hnim)?', 'armos( knights)?', 'arrghus', 'blind', 'ganon(dorf)?', 'helma', 'kholdstare', 'lanmo(las)?', 'moldorm', 'mothula', 'mott[ei]', 'trinexx', 'vit(reous|ty)', ], 'zd' => [ 'eastern', 'desert( palace)?', 'gt', 'hera', 'ice ?(palace)?', '(misery )?mire', 'pod', 'skull ?woods', 'swamp', 'thieve\'?s\'? ?town', 'tr', 'tt', ], 'zi' => [ '(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', ], 'zl' => [ '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)?', ], ]; }