├── LICENSE ├── README.md ├── TwitchChatExample.cs └── TwitchIRC.cs /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Grahnz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![img](http://johangrahn.se/wp-content/uploads/2015/06/twitchChat.jpg) 2 | # TwitchIRC.cs 3 | Is a lightweight IRC client component for use with the Unity Engine. 4 | ## Prerequisites 5 | In order to connect to Twitch IRC, you must have three pieces of information: 6 | 7 | 1. The name of channel that you want to join. 8 | 2. A Twitch account. 9 | 3. Oauth token from API or from a site using it: www.twitchapps.com/tmi 10 | 11 | ## Usage 12 | Enter your twitch name, oauth token and the name of the channel you want to join in the inspector of TwitchIRC. 13 | Drop a component that utilizes TwitchIRC.cs or try the TwitchChatExample.cs chat component. 14 | 15 | ## API 16 | Sending: 17 | 18 | - TwitchIRC.SendMsg(string msg) 19 | - TwitchIRC.SendCommand(string cmd) 20 | 21 | Reading: 22 | Add a listener via: 23 | - TwitchIRC.messageRecievedEvent.AddListener(function(string)) 24 | 25 | For an example see TwitchChatExample.cs 26 | 27 | ## Known issues 28 | - Please tell me if you find any! 29 | -------------------------------------------------------------------------------- /TwitchChatExample.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | [RequireComponent(typeof(TwitchIRC))] 6 | public class TwitchChatExample : MonoBehaviour 7 | { 8 | public int maxMessages = 100; //we start deleting UI elements when the count is larger than this var. 9 | private LinkedList messages = 10 | new LinkedList(); 11 | public UnityEngine.UI.InputField inputField; 12 | public UnityEngine.UI.Button submitButton; 13 | public UnityEngine.RectTransform chatBox; 14 | public UnityEngine.UI.ScrollRect scrollRect; 15 | private TwitchIRC IRC; 16 | //when message is recieved from IRC-server or our own message. 17 | void OnChatMsgRecieved(string msg) 18 | { 19 | //parse from buffer. 20 | int msgIndex = msg.IndexOf("PRIVMSG #"); 21 | string msgString = msg.Substring(msgIndex + IRC.channelName.Length + 11); 22 | string user = msg.Substring(1, msg.IndexOf('!') - 1); 23 | 24 | //remove old messages for performance reasons. 25 | if (messages.Count > maxMessages) 26 | { 27 | Destroy(messages.First.Value); 28 | messages.RemoveFirst(); 29 | } 30 | 31 | //add new message. 32 | CreateUIMessage(user, msgString); 33 | } 34 | void CreateUIMessage(string userName, string msgString) 35 | { 36 | Color32 c = ColorFromUsername(userName); 37 | string nameColor = "#" + c.r.ToString("X2") + c.g.ToString("X2") + c.b.ToString("X2"); 38 | GameObject go = new GameObject("twitchMsg"); 39 | var text = go.AddComponent(); 40 | var layout = go.AddComponent(); 41 | go.transform.SetParent(chatBox); 42 | messages.AddLast(go); 43 | 44 | layout.minHeight = 20f; 45 | text.text = "" + userName + "" + ": " + msgString; 46 | text.color = Color.black; 47 | text.font = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font; 48 | scrollRect.velocity = new Vector2(0, 1000f); 49 | } 50 | //when Submit button is clicked or ENTER is pressed. 51 | public void OnSubmit() 52 | { 53 | if (inputField.text.Length > 0) 54 | { 55 | IRC.SendMsg(inputField.text); //send message. 56 | CreateUIMessage(IRC.nickName, inputField.text); //create ui element. 57 | inputField.text = ""; 58 | } 59 | } 60 | Color ColorFromUsername(string username) 61 | { 62 | Random.seed = username.Length + (int)username[0] + (int)username[username.Length - 1]; 63 | return new Color(Random.Range(0.25f, 0.55f), Random.Range(0.20f, 0.55f), Random.Range(0.25f, 0.55f)); 64 | } 65 | // Use this for initialization 66 | void Start() 67 | { 68 | IRC = this.GetComponent(); 69 | //IRC.SendCommand("CAP REQ :twitch.tv/tags"); //register for additional data such as emote-ids, name color etc. 70 | IRC.messageRecievedEvent.AddListener(OnChatMsgRecieved); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /TwitchIRC.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | public class TwitchIRC : MonoBehaviour 6 | { 7 | public string oauth; 8 | public string nickName; 9 | public string channelName; 10 | private string server = "irc.twitch.tv"; 11 | private int port = 6667; 12 | 13 | //event(buffer). 14 | public class MsgEvent : UnityEngine.Events.UnityEvent { } 15 | public MsgEvent messageRecievedEvent = new MsgEvent(); 16 | 17 | private string buffer = string.Empty; 18 | private bool stopThreads = false; 19 | private Queue commandQueue = new Queue(); 20 | private List recievedMsgs = new List(); 21 | private System.Threading.Thread inProc, outProc; 22 | private void StartIRC() 23 | { 24 | System.Net.Sockets.TcpClient sock = new System.Net.Sockets.TcpClient(); 25 | sock.Connect(server, port); 26 | if (!sock.Connected) 27 | { 28 | Debug.Log("Failed to connect!"); 29 | return; 30 | } 31 | var networkStream = sock.GetStream(); 32 | var input = new System.IO.StreamReader(networkStream); 33 | var output = new System.IO.StreamWriter(networkStream); 34 | 35 | //Send PASS & NICK. 36 | output.WriteLine("PASS " + oauth); 37 | output.WriteLine("NICK " + nickName.ToLower()); 38 | output.Flush(); 39 | 40 | //output proc 41 | outProc = new System.Threading.Thread(() => IRCOutputProcedure(output)); 42 | outProc.Start(); 43 | //input proc 44 | inProc = new System.Threading.Thread(() => IRCInputProcedure(input, networkStream)); 45 | inProc.Start(); 46 | } 47 | private void IRCInputProcedure(System.IO.TextReader input, System.Net.Sockets.NetworkStream networkStream) 48 | { 49 | while (!stopThreads) 50 | { 51 | if (!networkStream.DataAvailable) 52 | continue; 53 | 54 | buffer = input.ReadLine(); 55 | 56 | //was message? 57 | if (buffer.Contains("PRIVMSG #")) 58 | { 59 | lock (recievedMsgs) 60 | { 61 | recievedMsgs.Add(buffer); 62 | } 63 | } 64 | 65 | //Send pong reply to any ping messages 66 | if (buffer.StartsWith("PING ")) 67 | { 68 | SendCommand(buffer.Replace("PING", "PONG")); 69 | } 70 | 71 | //After server sends 001 command, we can join a channel 72 | if (buffer.Split(' ')[1] == "001") 73 | { 74 | SendCommand("JOIN #" + channelName); 75 | } 76 | } 77 | } 78 | private void IRCOutputProcedure(System.IO.TextWriter output) 79 | { 80 | System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch(); 81 | stopWatch.Start(); 82 | while (!stopThreads) 83 | { 84 | lock (commandQueue) 85 | { 86 | if (commandQueue.Count > 0) //do we have any commands to send? 87 | { 88 | // https://github.com/justintv/Twitch-API/blob/master/IRC.md#command--message-limit 89 | //have enough time passed since we last sent a message/command? 90 | if (stopWatch.ElapsedMilliseconds > 1750) 91 | { 92 | //send msg. 93 | output.WriteLine(commandQueue.Peek()); 94 | output.Flush(); 95 | //remove msg from queue. 96 | commandQueue.Dequeue(); 97 | //restart stopwatch. 98 | stopWatch.Reset(); 99 | stopWatch.Start(); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | public void SendCommand(string cmd) 107 | { 108 | lock (commandQueue) 109 | { 110 | commandQueue.Enqueue(cmd); 111 | } 112 | } 113 | public void SendMsg(string msg) 114 | { 115 | lock (commandQueue) 116 | { 117 | commandQueue.Enqueue("PRIVMSG #" + channelName + " :" + msg); 118 | } 119 | } 120 | 121 | //MonoBehaviour Events. 122 | void Start() 123 | { 124 | } 125 | void OnEnable() 126 | { 127 | stopThreads = false; 128 | StartIRC(); 129 | } 130 | void OnDisable() 131 | { 132 | stopThreads = true; 133 | //while (inProc.IsAlive || outProc.IsAlive) ; 134 | //print("inProc:" + inProc.IsAlive.ToString()); 135 | //print("outProc:" + outProc.IsAlive.ToString()); 136 | } 137 | void OnDestroy() 138 | { 139 | stopThreads = true; 140 | //while (inProc.IsAlive || outProc.IsAlive) ; 141 | //print("inProc:" + inProc.IsAlive.ToString()); 142 | //print("outProc:" + outProc.IsAlive.ToString()); 143 | } 144 | void Update() 145 | { 146 | lock (recievedMsgs) 147 | { 148 | if (recievedMsgs.Count > 0) 149 | { 150 | for (int i = 0; i < recievedMsgs.Count; i++) 151 | { 152 | messageRecievedEvent.Invoke(recievedMsgs[i]); 153 | } 154 | recievedMsgs.Clear(); 155 | } 156 | } 157 | } 158 | } 159 | --------------------------------------------------------------------------------