Working Twitch Chat Integration

This commit is contained in:
Docker VM 2024-07-20 13:02:52 -04:00
parent 8049f10b65
commit 91c37fca0a
15 changed files with 965 additions and 53 deletions

View File

@ -63,6 +63,6 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
MIX_TWITCH_BOT_USERNAME=
MIX_TWITCH_BOT_OAUTH_TOKEN=
MIX_TWITCH_CHANNEL=
TWITCH_BOT_USERNAME=your_bot_name
TWITCH_BOT_OAUTH_TOKEN=oauth:your_oauth_token
TWITCH_CHANNEL=yourchannel

32
app/Helpers/TwitchHelper Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Helpers;
class TwitchHelper
{
public static function parseMessage($message)
{
$commandPattern = '/^!place\s([A-P])\s(\d{1,2})\s(\w+)$/i';
$shortCommandPattern = '/^!p\s([A-P])(\d{1,2})\s(\w+)$/i';
if (preg_match($commandPattern, $message, $matches)) {
return [
'command' => 'place',
'x' => $matches[1],
'y' => $matches[2],
'color' => $matches[3]
];
}
if (preg_match($shortCommandPattern, $message, $matches)) {
return [
'command' => 'place',
'x' => $matches[1],
'y' => $matches[2],
'color' => $matches[3]
];
}
return null;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Helpers\TwitchHelper;
class TwitchController extends Controller
{
public function parseChatMessage(Request $request)
{
$message = $request->input('message');
if (!$message) {
return response()->json(['error' => 'No message provided'], 400);
}
$parsedCommand = TwitchHelper::parseMessage($message);
if ($parsedCommand) {
return response()->json($parsedCommand);
}
return response()->json(['error' => 'Invalid command'], 400);
}
}

View File

@ -7,7 +7,9 @@
"require": {
"php": "^8.2",
"laravel/framework": "^11.9",
"laravel/tinker": "^2.9"
"laravel/tinker": "^2.9",
"ratchet/pawl": "^0.4.1",
"react/socket": "^1.15"
},
"require-dev": {
"fakerphp/faker": "^1.23",

611
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "58ef83953920e06181ea84f65f0b2635",
"content-hash": "87e4eebaf8ce7add2dd886e4161db1f1",
"packages": [
{
"name": "brick/math",
@ -506,6 +506,53 @@
],
"time": "2023-10-06T06:47:41+00:00"
},
{
"name": "evenement/evenement",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/igorw/evenement.git",
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^9 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Evenement\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
}
],
"description": "Événement is a very simple event dispatching library for PHP",
"keywords": [
"event-dispatcher",
"event-emitter"
],
"support": {
"issues": "https://github.com/igorw/evenement/issues",
"source": "https://github.com/igorw/evenement/tree/v3.0.2"
},
"time": "2023-08-08T05:53:35+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.3.0",
@ -3108,6 +3155,568 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "ratchet/pawl",
"version": "v0.4.1",
"source": {
"type": "git",
"url": "https://github.com/ratchetphp/Pawl.git",
"reference": "af70198bab77a582b31169d3cc3982bed25c161f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/af70198bab77a582b31169d3cc3982bed25c161f",
"reference": "af70198bab77a582b31169d3cc3982bed25c161f",
"shasum": ""
},
"require": {
"evenement/evenement": "^3.0 || ^2.0",
"guzzlehttp/psr7": "^2.0 || ^1.7",
"php": ">=5.4",
"ratchet/rfc6455": "^0.3.1",
"react/socket": "^1.9"
},
"require-dev": {
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8"
},
"suggest": {
"reactivex/rxphp": "~2.0"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"Ratchet\\Client\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Asynchronous WebSocket client",
"keywords": [
"Ratchet",
"async",
"client",
"websocket",
"websocket client"
],
"support": {
"issues": "https://github.com/ratchetphp/Pawl/issues",
"source": "https://github.com/ratchetphp/Pawl/tree/v0.4.1"
},
"time": "2021-12-10T14:32:34+00:00"
},
{
"name": "ratchet/rfc6455",
"version": "v0.3.1",
"source": {
"type": "git",
"url": "https://github.com/ratchetphp/RFC6455.git",
"reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb",
"reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb",
"shasum": ""
},
"require": {
"guzzlehttp/psr7": "^2 || ^1.7",
"php": ">=5.4.2"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
"react/socket": "^1.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Ratchet\\RFC6455\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"role": "Developer"
},
{
"name": "Matt Bonneau",
"role": "Developer"
}
],
"description": "RFC6455 WebSocket protocol handler",
"homepage": "http://socketo.me",
"keywords": [
"WebSockets",
"rfc6455",
"websocket"
],
"support": {
"chat": "https://gitter.im/reactphp/reactphp",
"issues": "https://github.com/ratchetphp/RFC6455/issues",
"source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1"
},
"time": "2021-12-09T23:20:49+00:00"
},
{
"name": "react/cache",
"version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/cache.git",
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"react/promise": "^3.0 || ^2.0 || ^1.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Async, Promise-based cache interface for ReactPHP",
"keywords": [
"cache",
"caching",
"promise",
"reactphp"
],
"support": {
"issues": "https://github.com/reactphp/cache/issues",
"source": "https://github.com/reactphp/cache/tree/v1.2.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2022-11-30T15:59:55+00:00"
},
{
"name": "react/dns",
"version": "v1.13.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/dns.git",
"reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5",
"reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"react/cache": "^1.0 || ^0.6 || ^0.5",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4.3 || ^3 || ^2",
"react/promise-timer": "^1.11"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Dns\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Async DNS resolver for ReactPHP",
"keywords": [
"async",
"dns",
"dns-resolver",
"reactphp"
],
"support": {
"issues": "https://github.com/reactphp/dns/issues",
"source": "https://github.com/reactphp/dns/tree/v1.13.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2024-06-13T14:18:03+00:00"
},
{
"name": "react/event-loop",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/event-loop.git",
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"suggest": {
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\EventLoop\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
"keywords": [
"asynchronous",
"event-loop"
],
"support": {
"issues": "https://github.com/reactphp/event-loop/issues",
"source": "https://github.com/reactphp/event-loop/tree/v1.5.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2023-11-13T13:48:05+00:00"
},
{
"name": "react/promise",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "8a164643313c71354582dc850b42b33fa12a4b63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63",
"reference": "8a164643313c71354582dc850b42b33fa12a4b63",
"shasum": ""
},
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.39 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v3.2.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2024-05-24T10:39:05+00:00"
},
{
"name": "react/socket",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/socket.git",
"reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038",
"reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038",
"shasum": ""
},
"require": {
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"php": ">=5.3.0",
"react/dns": "^1.11",
"react/event-loop": "^1.2",
"react/promise": "^3 || ^2.6 || ^1.2.1",
"react/stream": "^1.2"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4 || ^3 || ^2",
"react/promise-stream": "^1.4",
"react/promise-timer": "^1.10"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Socket\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
"keywords": [
"Connection",
"Socket",
"async",
"reactphp",
"stream"
],
"support": {
"issues": "https://github.com/reactphp/socket/issues",
"source": "https://github.com/reactphp/socket/tree/v1.15.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2023-12-15T11:02:10+00:00"
},
{
"name": "react/stream",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/stream.git",
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
"shasum": ""
},
"require": {
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"php": ">=5.3.8",
"react/event-loop": "^1.2"
},
"require-dev": {
"clue/stream-filter": "~1.2",
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"psr-4": {
"React\\Stream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
"keywords": [
"event-driven",
"io",
"non-blocking",
"pipe",
"reactphp",
"readable",
"stream",
"writable"
],
"support": {
"issues": "https://github.com/reactphp/stream/issues",
"source": "https://github.com/reactphp/stream/tree/v1.4.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2024-06-11T12:45:25+00:00"
},
{
"name": "symfony/clock",
"version": "v7.1.1",

46
package-lock.json generated
View File

@ -5,7 +5,8 @@
"packages": {
"": {
"dependencies": {
"tmi.js": "^1.8.5"
"tmi.js": "^1.8.5",
"ws": "^8.18.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.2",
@ -1000,6 +1001,21 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/bufferutil": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@ -1709,6 +1725,19 @@
}
}
},
"node_modules/node-gyp-build": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-releases": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
@ -2457,6 +2486,21 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -2,7 +2,7 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite build",
"build": "vite build"
},
"devDependencies": {
@ -16,6 +16,7 @@
"vite": "^5.0"
},
"dependencies": {
"tmi.js": "^1.8.5"
"tmi.js": "^1.8.5",
"ws": "^8.18.0"
}
}

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Grid</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="grid"></div>
<script src="scripts.js"></script>
</body>
</html>

View File

@ -1,22 +1,101 @@
import tmi from 'tmi.js';
const ws = new WebSocket('wss://irc-ws.chat.twitch.tv:443');
const client = new tmi.Client({
options: { debug: true },
identity: {
username: process.env.MIX_TWITCH_BOT_USERNAME,
password: process.env.MIX_TWITCH_BOT_OAUTH_TOKEN
},
channels: [ process.env.MIX_TWITCH_CHANNEL ]
});
ws.onopen = () => {
console.log('Connected to Twitch chat');
client.connect().catch(console.error);
const oauthToken = import.meta.env.VITE_TWITCH_BOT_OAUTH_TOKEN;
const username = import.meta.env.VITE_TWITCH_BOT_USERNAME;
const channel = import.meta.env.VITE_TWITCH_CHANNEL;
client.on('message', (channel, tags, message, self) => {
if(self) return;
console.log('OAuth Token:', oauthToken ? 'Loaded' : 'Missing');
console.log('Username:', username ? username : 'Missing');
console.log('Channel:', channel ? channel : 'Missing');
if(message.toLowerCase() === '!roll') {
const num = Math.floor(Math.random() * 6) + 1;
client.say(channel, `@${tags.username} rolled a ${num}`);
if (!oauthToken || !username || !channel) {
console.error('Missing required environment variables');
return;
}
});
ws.send(`PASS ${oauthToken}`);
ws.send(`NICK ${username}`);
ws.send(`JOIN #${channel}`);
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
const parsedMessage = parseTwitchMessage(event.data);
console.log('Parsed Message:', parsedMessage);
if (parsedMessage && parsedMessage.command === 'PRIVMSG') {
const chatMessage = parsedMessage.params.slice(1).join(' ').trim();
const username = parsedMessage.tags['display-name'] || parsedMessage.prefix.split('!')[0];
console.log('Chat Message:', chatMessage);
console.log('Username:', username);
window.dispatchEvent(new CustomEvent('chat-message', { detail: { user: username, message: chatMessage } }));
// Process !place command
fetch('/twitch/parse', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({ message: chatMessage })
})
.then(response => response.json())
.then(data => {
if (data && data.command === 'place') {
console.log('Parsed Command:', data);
// Call the existing API with parsed coordinates and color
// Example API call: /api/place/{x}/{y}/{color}
fetch(`/api/place/${data.x}/${data.y}/${data.color}`, { method: 'POST' })
.then(response => response.json())
.then(result => {
console.log('API Result:', result);
})
.catch(error => {
console.error('API Error:', error);
});
}
})
.catch(error => {
console.error('Parsing Error:', error);
});
}
};
ws.onerror = (error) => {
console.error('WebSocket Error:', error);
};
ws.onclose = () => {
console.log('WebSocket connection closed');
};
function parseTwitchMessage(message) {
console.log('Raw Message:', message);
const prefixEnd = message.indexOf(' ');
const prefix = message.substring(1, prefixEnd);
const commandEnd = message.indexOf(' ', prefixEnd + 1);
const command = message.substring(prefixEnd + 1, commandEnd);
const params = message.substring(commandEnd + 1).split(' :');
const tagsStart = message.indexOf('@');
const tagsEnd = message.indexOf(' ', tagsStart + 1);
const tagsRaw = message.substring(tagsStart + 1, tagsEnd);
const tags = tagsRaw.split(';').reduce((acc, tag) => {
const [key, value] = tag.split('=');
acc[key] = value;
return acc;
}, {});
console.log('Parsed Tags:', tags);
console.log('Parsed Command:', command);
console.log('Parsed Params:', params);
return { prefix, command, params, tags };
}

View File

@ -9,9 +9,35 @@
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
{{ __("You're logged in!") }}
<div id="tmi-status" class="mt-4"></div>
<div id="chat-messages" class="mt-4"></div>
<div id="dice-result" class="mt-4"></div>
</div>
</div>
</div>
</div>
</x-app-layout>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tmiStatus = document.getElementById('tmi-status');
const chatMessages = document.getElementById('chat-messages');
const diceResult = document.getElementById('dice-result');
window.addEventListener('tmi-connected', function() {
tmiStatus.innerText = 'Connected to Twitch chat!';
});
window.addEventListener('chat-message', function(event) {
console.log('Chat Message Event:', event.detail);
const message = document.createElement('div');
message.innerText = `${event.detail.user} : ${event.detail.message}`;
chatMessages.appendChild(message);
});
window.addEventListener('dice-rolled', function(event) {
console.log('Dice Rolled Event:', event.detail);
diceResult.innerText = 'Dice roll result: ' + event.detail;
});
});
</script>

View File

@ -12,7 +12,7 @@
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
@vite(['resources/css/app.css', 'resources/js/app.js', 'resources/js/client.js' ])
</head>
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100">

View File

@ -1,4 +1,49 @@
const gridSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--grid-size').trim());
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Grid</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
}
:root {
--grid-size: 16; /* Adjust this value for grid size (e.g., 3 for 3x3) */
--cell-size: 20px;
}
.grid {
display: grid;
grid-template-columns: repeat(calc(var(--grid-size) + 1), var(--cell-size));
grid-template-rows: repeat(calc(var(--grid-size) + 1), var(--cell-size));
gap: 0;
}
.label {
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
border: 1px solid #ddd;
}
.cell {
background-color: #fff;
border: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="grid"></div>
<script>
const gridSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--grid-size').trim());
const grid = document.querySelector('.grid');
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
@ -55,4 +100,7 @@ function colorCell(cellName, colorName) {
} else {
console.warn(`Cell ${cellName} not found.`);
}
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,44 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Testing') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
{{ __("You're logged in!") }}
<div id="tmi-status" class="mt-4"></div>
<div id="chat-messages" class="mt-4"></div>
<div id="dice-result" class="mt-4"></div>
</div>
</div>
</div>
</div>
</x-app-layout>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tmiStatus = document.getElementById('tmi-status');
const chatMessages = document.getElementById('chat-messages');
const diceResult = document.getElementById('dice-result');
window.addEventListener('tmi-connected', function() {
tmiStatus.innerText = 'Connected to Twitch chat!';
});
window.addEventListener('chat-message', function(event) {
console.log('Chat Message Event:', event.detail);
const message = document.createElement('div');
message.innerText = `${event.detail.user} : ${event.detail.message}`;
chatMessages.appendChild(message);
});
window.addEventListener('dice-rolled', function(event) {
console.log('Dice Rolled Event:', event.detail);
diceResult.innerText = 'Dice roll result: ' + event.detail;
});
});
</script>

View File

@ -2,15 +2,26 @@
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TwitchController;
Route::post('/twitch/parse', [TwitchController::class, 'parseChatMessage']);
Route::get('/', function () {
return view('welcome');
});
Route::get('/prototype', function () {
return view('prototype');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::get('/testing', function () {
return view('testing');
})->middleware(['auth', 'verified'])->name('testing');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');

View File

@ -1,14 +1,18 @@
import { defineConfig } from 'vite';
import { defineConfig, loadEnv } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
],
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js', 'resources/js/client.js'],
refresh: true,
}),
],
define: {
'process.env': env,
}
};
});