class TwitchBot {
- public function __construct() {
+ public function __construct($nick) {
+ $this->nick = $nick;
$this->logger = new Logger('TwitchBot');
$this->logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
- $this->token = TwitchToken::firstWhere('nick', 'localhorsttv');
+ $this->token = TwitchToken::firstWhere('nick', $nick);
if (!$this->token) {
throw new \Exception('unable to find access token');
}
+ if ($this->token->hasExpired()) {
+ $this->token->refresh();
+ }
$this->connector = new Connector();
$this->connect();
- $this->listenCommands();
+ $this->startPinger();
}
public function getLogger() {
return Loop::get();
}
+ public function isReady() {
+ return $this->ready;
+ }
+
public function run() {
$this->shutting_down = false;
$this->getLoop()->run();
$this->ready = false;
$this->logger->info('websocket connection closed: '.$reason.' ['.$op.']');
if (!$this->shutting_down) {
- $this->logger->info('reconnecting in 10 seconds');
- Loop::addTimer(10, [$this, 'connect']);
+ $this->logger->info('reconnecting in 5 seconds');
+ Loop::addTimer(5, [$this, 'connect']);
}
}
public function handleIRCMessage(IRCMessage $msg) {
+ $this->last_contact = time();
+ if ($msg->isPing()) {
+ $this->sendIRCMessage($msg->makePong());
+ return;
+ }
+ if ($msg->isPong()) {
+ return;
+ }
+ $this->logMessage($msg);
if ($msg->isPrivMsg()) {
$this->handlePrivMsg($msg);
return;
}
- if ($msg->isPing()) {
- $this->sendIRCMessage($msg->makePong());
+ if ($msg->isWhisper()) {
+ $this->handleWhisper($msg);
return;
}
if ($msg->isNotice() && $msg->getText() == 'Login authentication failed') {
$this->ready = true;
return;
}
+ if ($msg->command == 'GLOBALUSERSTATE') {
+ // receive own user metadata
+ $this->handleUserState($msg);
+ return;
+ }
}
- public function handlePrivMsg(IRCMessage $msg) {
+ public function getMessageChannel(IRCMessage $msg) {
$target = $msg->getPrivMsgTarget();
- if ($target[0] != '#') return;
- $text = $msg->getText();
- if ($text[0] != '!') return;
- $channel = Channel::firstWhere('twitch_chat', '=', $target);
- if (!$channel) return;
- $this->handleChatCommand($channel, $msg);
- }
-
- public function handleChatCommand(Channel $channel, IRCMessage $msg) {
- $cmd = explode(' ', ltrim($msg->getText(), '!'), 2);
- if (!isset($channel->chat_commands[$cmd[0]])) return;
- $config = $channel->chat_commands[$cmd[0]];
- $this->logger->info('got command '.$cmd[0].' on channel '.$channel->title);
- try {
- $command = ChatCommand::create($this, $channel, $config);
- $command->execute($cmd[1] ?? '');
- } catch (\Exception $e) {
- $this->logger->warning('error executing command '.$cmd[0].' on channel '.$channel->title.': '.$e->getMessage());
+ if (substr($target, 0, 1) !== '#') {
+ $target = '#'.$target;
}
+ return Channel::firstWhere('twitch_chat', '=', $target);
+ }
+
+ public function logMessage(IRCMessage $msg) {
+ }
+
+ public function handlePrivMsg(IRCMessage $msg) {
+ }
+
+ public function handleUserState(IRCMessage $msg) {
+ if (isset($msg->tags['user-id'])) {
+ $this->user_id = $msg->tags['user-id'];
+ }
+ }
+
+ public function handleWhisper(IRCMessage $msg) {
}
public function login() {
$this->ws->send('PASS oauth:'.$this->token->access);
- $this->ws->send('NICK localhorsttv');
+ $this->ws->send('NICK '.$this->nick);
}
public function joinChannels() {
- $this->logger->info('joining channels');
- $channels = Channel::where('twitch_chat', '!=', '')->where('join', '=', true)->get();
- $names = [];
- foreach ($channels as $channel) {
- $names[] = $channel->twitch_chat;
- }
- $chunks = array_chunk($names, 10);
- foreach ($chunks as $chunk) {
- $this->sendIRCMessage(IRCMessage::join($chunk));
- }
}
- private function listenCommands() {
- $this->getLoop()->addPeriodicTimer(1, function () {
+ private function startPinger() {
+ $this->getLoop()->addPeriodicTimer(15, function () {
if (!$this->ready) return;
- $command = TwitchBotCommand::where('status', '=', 'pending')->oldest()->first();
- if ($command) {
- try {
- $command->execute($this);
- } catch (\Exception $e) {
- }
+ if (time() - $this->last_contact < 60) return;
+ try {
+ $this->sendIRCMessage(IRCMessage::ping($this->nick));
+ } catch (\Exception $e) {
}
});
-
}
public function sendIRCMessage(IRCMessage $msg) {
$irc_message = $msg->encode();
$this->logger->info('sending IRC message '.$irc_message);
$this->ws->send($irc_message);
+ $this->last_contact = time();
+ }
+
+ public function sendWhisper($to, $msg) {
+ $this->logger->info('sending whisper to '.$to.': '.$msg);
+ try {
+ $response = $this->token->request()->post('/whispers?from_user_id='.$this->user_id.'&to_user_id='.$to, [
+ 'message' => $msg,
+ ]);
+ if (!$response->successful()) {
+ $this->logger->error('sending whisper to '.$to.': '.$response->status());
+ }
+ } catch (\Exception $e) {
+ $this->logger->error('sending whisper to '.$to.': '.$e->getMessage());
+ }
+ }
+
+
+ protected function listenCommands() {
+ $this->getLoop()->addPeriodicTimer(1, function () {
+ if (!$this->isReady()) return;
+ $command = TwitchBotCommand::where('bot_nick', '=', $this->nick)->where('status', '=', 'pending')->oldest()->first();
+ if ($command) {
+ try {
+ $command->execute($this);
+ } catch (\Exception $e) {
+ }
+ }
+ });
}
private $logger;
+ private $nick;
private $token;
+ private $user_id = '';
private $connector;
private $ws;
private $ready = false;
private $shutting_down = false;
+ private $last_contact;
+
}
?>