5 use App\TwitchBot\TokenizedMessage;
6 use Illuminate\Database\Eloquent\Factories\HasFactory;
7 use Illuminate\Database\Eloquent\Model;
8 use Illuminate\Support\Arr;
9 use Illuminate\Support\Str;
10 use LanguageDetection\Language;
12 class ChatLog extends Model {
16 public function channel() {
17 return $this->belongsTo(Channel::class);
20 public function user() {
21 return $this->belongsTo(User::class);
24 public function tokenize() {
25 return TokenizedMessage::fromLog($this);
28 public function isReply() {
29 return !empty($this->tags['reply-parent-msg-body']);
32 public function getReplyParent() {
33 return str_replace('\\s', ' ', $this->tags['reply-parent-msg-body']);
36 public function getReplyParentUser() {
37 return $this->tags['reply-parent-display-name'];
40 public function getText() {
41 return $this->params[1];
44 public function getTextWithoutEmotes() {
45 $text = $this->params[1];
46 if (isset($this->tags['emotes']) && !empty($this->tags['emotes'])) {
47 $emotes = explode('/', $this->tags['emotes']);
48 foreach ($emotes as $emote) {
49 $set = explode(':', $emote);
50 $positions = explode(',', $set[1]);
51 foreach ($positions as $position) {
52 $coords = explode('-', $position);
53 $text = mb_substr($text, 0, $coords[0]).str_repeat(' ', $coords[1] - $coords[0] + 1).mb_substr($text, $coords[1] + 1);
57 return trim(preg_replace('/\s+/', ' ', $text));
60 public function getTextWithoutReply() {
61 if ($this->isReply()) {
62 return mb_substr($this->params[1], mb_strlen($this->getReplyParentUser()) + 2);
64 return $this->params[1];
67 public function evaluate() {
68 $this->evaluateUser();
69 $this->evaluateChannel();
71 if (is_null($this->nick)) {
72 $this->type = 'system';
75 if (in_array($this->nick, ['horstiebot', 'localhorsttv'])) {
79 if (!empty($this->params) && $this->params[0] == '#'.$this->nick) {
80 $this->type = 'owner';
84 if ($this->command == 'PRIVMSG' || $this->command == 'WHISPER') {
85 if (static::isKnownBot($this->nick)) {
87 } else if (substr($this->params[0], 0, 1) == '#') {
92 $this->text_content = $this->getTextWithoutReply();
93 $this->detectLanguage();
94 $tokenized = $this->tokenize();
95 if ($tokenized->isSpammy()) {
98 $this->emote_only = $tokenized->isEmoteOnly();
99 $this->classification = $tokenized->classify();
103 throw new \Exception('unidentified message');
106 public static function isKnownBot($nick) {
107 return in_array(strtolower($nick), [
109 'birrellthesquirrel',
114 'pokemoncommunitygame',
124 protected function evaluateUser() {
127 protected function evaluateChannel() {
128 if (empty($this->params)) {
131 $cname = $this->params[0];
132 if (substr($cname, 0, 1) != '#') {
135 $channel = Channel::firstWhere('twitch_chat', '=', $cname);
136 if (!is_null($channel)) {
137 $this->channel()->associate($channel);
138 if (empty($this->twitch_category) && now()->sub(15, 'minute')->isBefore($this->created_at)) {
139 $this->twitch_category = $channel->twitch_category;
144 protected function detectLanguage() {
145 $languages = ['de', 'en', 'es', 'fr'];
146 if (!is_null($this->channel)) {
147 $languages = array_values($this->channel->languages);
148 if (!in_array('en', $languages)) {
152 $detector = (new Language($languages))->detect($this->getTextWithoutEmotes());
153 $scores = $detector->close();
154 $lang = strval($detector);
155 //var_dump($scores, $lang, $this->text_content);
156 if (!empty($lang) && $scores[$lang] > 0.4) {
157 $this->detected_language = $lang;
162 'banned' => 'boolean',
165 'user_id' => 'string',
168 protected $fillable = [