protected function noticeOrPrivmsg($origin, $msg, $mapping)
    {
        // :nick1!ident@host NOTICE <nick2/#chan> :Message
        // :nick1!ident@host PRIVMSG <nick2/#chan> :Message
        $target = $msg[0];
        $msg    = $msg[1];
        $isChan = (int) $this->connection->isChannel($target);
        $len = strlen($msg);
        if ($len > 1 && $msg[$len-1] == "\x01" && $msg[0] == "\x01") {
            // Remove the markers.
            $msg    = (string) substr($msg, 1, -1);
            // Unquote the message.
            $msg    = self::ctcpUnquote($msg);
            // Extract the tag from the rest of the message.
            $pos    = strcspn($msg, " ");
            $ctcp   = substr($msg, 0, $pos);
            $msg    = (string) substr($msg, $pos + 1);

            if ($ctcp == "ACTION") {
                if ($isChan) {
                    return $this->connection->dispatch(
                        $this->makeEvent($mapping['action'][$isChan], $target, $origin, $msg)
                    );
                }

                return $this->connection->dispatch(
                    $this->makeEvent($mapping['action'][$isChan], $origin, $msg)
                );
            }

            if ($isChan) {
                return $this->connection->dispatch(
                    $this->makeEvent($mapping['ctcp'][$isChan], $target, $origin, $ctcp, $msg)
                );
            }

            return $this->connection->dispatch(
                $this->makeEvent($mapping['ctcp'][$isChan], $origin, $ctcp, $msg)
            );
        }

        if ($isChan) {
            return $this->connection->dispatch(
                $this->makeEvent(
                    $mapping['normal'][$isChan],
                    $target,
                    $origin,
                    $msg
                )
            );
        }

        return $this->connection->dispatch(
            $this->makeEvent($mapping['normal'][$isChan], $origin, $msg)
        );
    }