diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm
index d1ae438525c..3074c797f6e 100644
--- a/code/__DEFINES/preferences.dm
+++ b/code/__DEFINES/preferences.dm
@@ -11,8 +11,9 @@
#define CHAT_PRAYER 256
#define CHAT_RADIO 512
#define MEMBER_PUBLIC 1024
+#define CHAT_PULLR 2048
-#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|MEMBER_PUBLIC)
+#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|MEMBER_PUBLIC|CHAT_PULLR)
#define BE_TRAITOR 1
#define BE_OPERATIVE 2
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index fac874e70d7..b277b48eda6 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -102,6 +102,9 @@
var/rename_cyborg = 0
var/ooc_during_round = 0
+ var/comms_key = "default_pwd" //Server API key
+ var/comms_allowed = 0 //By default, the server does not allow messages to be sent to it, unless the key is strong enough (this is to prevent misconfigured servers from becoming victims)
+
//Used for modifying movement speed for mobs.
//Unversal modifiers
var/run_speed = 0
@@ -266,6 +269,10 @@
Tickcomp = 1
if("automute_on")
automute_on = 1
+ if("comms_key")
+ comms_key = value
+ if(value != "default_pwd" && length(value) > 6) //It's the default value or less than 6 characters long, warn badmins
+ comms_allowed = 1
else
diary << "Unknown setting in configuration: '[name]'"
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index 1f8cbdc07b7..d87d0e987a4 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -130,6 +130,8 @@ var/next_external_rsc = 0
if(holder)
add_admin_verbs()
admin_memo_show()
+ if((config.comms_key == "default_pwd" || length(config.comms_key) <= 6) && config.comms_allowed) //It's the default value or less than 6 characters long, but it somehow didn't disable comms.
+ src << "The server's API key is either too short or is the default value! Consider changing it immediately!"
log_client_to_db()
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 0c41ac80b17..7dcd76da6e3 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -208,6 +208,7 @@ datum/preferences
dat += "Play lobby music: [(toggles & SOUND_LOBBY) ? "Yes" : "No"]
"
dat += "Ghost ears: [(toggles & CHAT_GHOSTEARS) ? "Nearest Creatures" : "All Speech"]
"
dat += "Ghost sight: [(toggles & CHAT_GHOSTSIGHT) ? "Nearest Creatures" : "All Emotes"]
"
+ dat += "Pull requests: [(toggles & CHAT_PULLR) ? "Yes" : "No"]
"
if(config.allow_Metadata)
dat += "OOC Notes: Edit
"
@@ -684,7 +685,8 @@ datum/preferences
if("ghost_sight")
toggles ^= CHAT_GHOSTSIGHT
-
+ if("pull_requests")
+ toggles ^= CHAT_PULLR
if("save")
save_preferences()
save_character()
diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm
index 31eb62cd73d..2fc6ef47c14 100644
--- a/code/modules/client/preferences_toggles.dm
+++ b/code/modules/client/preferences_toggles.dm
@@ -55,6 +55,15 @@
src << "You will [(prefs.toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat."
feedback_add_details("admin_verb","TP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/client/verb/togglePRs()
+ set name = "Show/Hide Pull Request Announcements"
+ set category = "Preferences"
+ set desc = "Toggles receiving a notification when new pull requests are created."
+ prefs.toggles ^= CHAT_PULLR
+ prefs.save_preferences()
+ src << "You will [(prefs.toggles & CHAT_PULLR) ? "now" : "no longer"] see new pull request announcements."
+ feedback_add_details("admin_verb","TPullR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
/client/verb/toggletitlemusic()
set name = "Hear/Silence LobbyMusic"
set category = "Preferences"
diff --git a/code/world.dm b/code/world.dm
index b620dfeff18..506b0cdd7b4 100644
--- a/code/world.dm
+++ b/code/world.dm
@@ -160,7 +160,17 @@
s["map_name"] = map_name ? map_name : "Unknown"
return list2params(s)
-
+ else if (copytext(T,1,9) == "announce")
+ var/input[] = params2list(T)
+ if(config.comms_allowed)
+ if(input["key"] != config.comms_key)
+ return "Bad Key"
+ else
+ #define CHAT_PULLR 2048
+ for(var/client/C in clients)
+ if(C.prefs && (C.prefs.toggles & CHAT_PULLR))
+ C << "PR: [input["announce"]]"
+ #undef CHAT_PULLR
/world/Reboot(var/reason)
#ifdef dellogging
diff --git a/config/config.txt b/config/config.txt
index 91dabaf17c1..8f71955fac1 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -144,3 +144,6 @@ TICKCOMP 0
## Comment this out to disable automuting
#AUTOMUTE_ON
+
+## Communication key for receiving data through world/Topic(), you don't want to give this out
+#COMMS_KEY default_pwd
\ No newline at end of file
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index a425073e7f1..69fe141385b 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -50,6 +50,7 @@ h1.alert, h2.alert {color: #000000;}
.userdanger {color: #ff0000; font-weight: bold;}
.danger {color: #ff0000;}
.warning {color: #ff0000; font-style: italic;}
+.announce {color: #228b22; font-weight: bold;}
.rose {color: #ff5050;}
.info {color: #0000CC;}
.notice {color: #000099;}
diff --git a/tools/PR_announcer_bot/Meebey.SmartIrc4net.dll b/tools/PR_announcer_bot/Meebey.SmartIrc4net.dll
new file mode 100644
index 00000000000..851c94700f7
Binary files /dev/null and b/tools/PR_announcer_bot/Meebey.SmartIrc4net.dll differ
diff --git a/tools/PR_announcer_bot/StarkSoftProxy.dll b/tools/PR_announcer_bot/StarkSoftProxy.dll
new file mode 100644
index 00000000000..a5210bed38a
Binary files /dev/null and b/tools/PR_announcer_bot/StarkSoftProxy.dll differ
diff --git a/tools/PR_announcer_bot/config.txt b/tools/PR_announcer_bot/config.txt
new file mode 100644
index 00000000000..dfaa1046567
--- /dev/null
+++ b/tools/PR_announcer_bot/config.txt
@@ -0,0 +1,7 @@
+serverip = 127.0.0.1 //SS13 server IP
+serverport = 7812
+commskey = this_is_a_test_key
+GitHub_bot_name = testbot_github //It'll get the data from this bot
+IRC_bot_name = testbot_BYOND //This'll be our name
+IRC_server = irc.rizon.net:6670 //format - server:port
+IRC_channel = #ircchannel
\ No newline at end of file
diff --git a/tools/PR_announcer_bot/output.txt b/tools/PR_announcer_bot/output.txt
new file mode 100644
index 00000000000..07db6152b6d
--- /dev/null
+++ b/tools/PR_announcer_bot/output.txt
@@ -0,0 +1 @@
+0131022900006397110110111117110991016191451161034511511697116105111110933286105115116978079876532111112101110101100321121171081083211410111311710111511632651001001153211511110910132981111141033210210197116117114101115329710811511132102105120101115321151111091013211511611710210246323551485248583210997115116101114464646106117115116951111101019510911111410195116101115116951001111101169510710510810895109101413210411611611211558474710310511610411798469911110947116103115116971161051111104745116103451151169711610511111047112117108108475148524832381071011216111610410511595105115959795116101115116951071011210
diff --git a/tools/PR_announcer_bot/sendkeys_ss13.exe b/tools/PR_announcer_bot/sendkeys_ss13.exe
new file mode 100644
index 00000000000..4f9332811d9
Binary files /dev/null and b/tools/PR_announcer_bot/sendkeys_ss13.exe differ
diff --git a/tools/PR_announcer_bot/sendkeys_ss13.sln b/tools/PR_announcer_bot/sendkeys_ss13.sln
new file mode 100644
index 00000000000..0786a395f6a
--- /dev/null
+++ b/tools/PR_announcer_bot/sendkeys_ss13.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sendkeys_ss13", "sendkeys_ss13\sendkeys_ss13.csproj", "{5D939FCB-F326-4B9B-8CEC-B2538126392E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5D939FCB-F326-4B9B-8CEC-B2538126392E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D939FCB-F326-4B9B-8CEC-B2538126392E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D939FCB-F326-4B9B-8CEC-B2538126392E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D939FCB-F326-4B9B-8CEC-B2538126392E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/tools/PR_announcer_bot/sendkeys_ss13/App.config b/tools/PR_announcer_bot/sendkeys_ss13/App.config
new file mode 100644
index 00000000000..72a71af99a7
--- /dev/null
+++ b/tools/PR_announcer_bot/sendkeys_ss13/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/tools/PR_announcer_bot/sendkeys_ss13/Meebey.SmartIrc4net.dll b/tools/PR_announcer_bot/sendkeys_ss13/Meebey.SmartIrc4net.dll
new file mode 100644
index 00000000000..851c94700f7
Binary files /dev/null and b/tools/PR_announcer_bot/sendkeys_ss13/Meebey.SmartIrc4net.dll differ
diff --git a/tools/PR_announcer_bot/sendkeys_ss13/Program.cs b/tools/PR_announcer_bot/sendkeys_ss13/Program.cs
new file mode 100644
index 00000000000..8d7d5856a98
--- /dev/null
+++ b/tools/PR_announcer_bot/sendkeys_ss13/Program.cs
@@ -0,0 +1,341 @@
+//////////////////////////////////////////////////////////////////
+/////GitHub pull request announcer bot for IRC////////////////////
+/////Made by VistaPOWA for the /tg/station 13 code project.///////
+/////This software is licensed under GPL3.////////////////////////
+/////Prequisites: Meebey's SmartIrc4Net library (incl, under GPL)/
+///// StarkSoft Proxy library (incl, under GPL)///////
+/////Usage: Edit the config.txt file to match the settings////////
+/////of your server's. Start the executable. Set up a Github hook/
+/////if you hadn't before to send IRC notifications to the////////
+/////channel of your choosing. Leave it running.//////////////////
+/////IT IS PREFERABLE IF YOU RAN THIS LOCALLY. YOUR SERVER'S//////
+/////API/COMMS KEY WILL BE SENT PLAINTEXT! ENSURE THAT THE API////
+/////KEY IS NOT PUBLICALLY AVAILABLE! IF LEAKED, CHANGE IT.///////
+/////Support available @ Rizon's #coderbus, ask for VistaPOWA.////
+//////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Meebey.SmartIrc4net;
+using System.Net;
+using System.Net.Sockets;
+using System.IO;
+
+namespace sendkeys_ss13
+{
+ public class Program
+ {
+ //default values
+ public static string serverIP = "127.0.0.1";
+ public static int serverPort = 11111;
+ public static string commskey = "this_is_a_test_key";
+ public static string Github_bot_name = "testbot_github";
+ public static string IRC_bot_name = "testbot";
+ public static string IRC_server = "test.net";
+ public static int IRC_port = 11111;
+ public static string IRC_channel = "#channel";
+
+ public static string[] merge_archive;
+ public static bool mergeflag = false;
+
+ public static IrcClient irc = new IrcClient();
+
+ public static int IRCReconnectAttempt = 0;
+ public static void Main(string[]args)
+ {
+ ReadConf();
+ irc.OnChannelMessage += new IrcEventHandler(OnChannelMessage);
+ irc.SupportNonRfc = true;
+ try
+ {
+ irc.Connect(IRC_server, IRC_port);
+ IRCReconnectAttempt = 0;
+ }
+ catch (Exception)
+ {
+ IRCReconnectAttempt++;
+ if (IRCReconnectAttempt <= 3)
+ {
+ Console.WriteLine("IRC server is unavaible at the moment. Reconnect attempt {0}...", IRCReconnectAttempt);
+ System.Threading.Thread.Sleep(3000); //Reconnecting after 5 seconds.
+ Main(args);
+ }
+ else
+ {
+ Console.WriteLine("IRC server unreachable. Please check your configuration file.");
+ Console.ReadLine();
+ return;
+ }
+ }
+ Console.WriteLine("Connected to IRC");
+ irc.Login(IRC_bot_name, IRC_bot_name);
+ Console.WriteLine("Logged in");
+ irc.RfcJoin(IRC_channel);
+ Console.WriteLine("Joining {0}", IRC_channel);
+ irc.Listen();
+ }
+
+ public static void ReadConf()
+ {
+ if (File.Exists("config.txt"))
+ {
+ StreamReader reader = new StreamReader("config.txt");
+ string[] line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ Match match1 = Regex.Match(line[2], @"(\d{1,3}.?){4}"); //rudimentary IP validation
+ if (match1.Success)
+ {
+ serverIP = line[2];
+ Console.WriteLine("Read IP: " + serverIP);
+ }
+ else
+ {
+ Console.WriteLine("IP cannot be validated.");
+ return;
+ }
+ }
+ line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ Match match2 = Regex.Match(line[2], @"\d{1,5}"); //rudimentary port validation
+ if (match2.Success && (Convert.ToInt32(line[2]) < 65535))
+ {
+ serverPort = Convert.ToInt32(line[2]);
+ Console.WriteLine("Read port: " + serverPort);
+ }
+ else
+ {
+ Console.WriteLine("Port cannot be validated.");
+ return;
+ }
+ }
+ line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ commskey = line[2];
+ Console.WriteLine("Commskey read.");
+ }
+ else
+ {
+ Console.WriteLine("No Commskey!");
+ return;
+ }
+ line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ Github_bot_name = line[2];
+ Console.WriteLine("Read Github bot name: " + Github_bot_name);
+ }
+ else
+ {
+ Console.WriteLine("No botname found.");
+ return;
+ }
+ line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ IRC_bot_name = line[2];
+ Console.WriteLine("Read IRC bot name: " + Github_bot_name);
+ }
+ else
+ {
+ Console.WriteLine("No botname found.");
+ return;
+ }
+ line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ Match match3 = Regex.Match(line[2], @"(.+)\:(\d{1,5})"); //rudimentary port validation
+ if (match3.Success)
+ {
+ IRC_server = Convert.ToString(match3.Groups[1]);
+ Int32.TryParse(Convert.ToString(match3.Groups[2]), out IRC_port);
+ Console.WriteLine("Read IRC server: " + IRC_server + ":" + Convert.ToString(IRC_port));
+ }
+ else
+ {
+ Console.WriteLine("Server:port invalid.");
+ return;
+ }
+ }
+ line = reader.ReadLine().Split(' ');
+ if (line[2] != null)
+ {
+ IRC_channel = line[2];
+ Console.WriteLine("Read channel: " + IRC_channel);
+ }
+ else
+ {
+ Console.WriteLine("No channel found.");
+ return;
+ }
+ reader.Close();
+ }
+ else
+ {
+ Console.WriteLine("Config file doesn't exist, using defaults");
+ return;
+ }
+ }
+
+ public static void OnChannelMessage(object sender, IrcEventArgs e)
+ {
+ if (e.Data.Nick == Github_bot_name)
+ {
+ string[] msg = e.Data.Message.Split(' ');
+ for (int i = 0; i < msg.Length; i++)
+ {
+ msg[i] = Regex.Replace(msg[i], @"[\x02\x1F\x0F\x16]|\x03(\d\d?(,\d\d?)?)?", String.Empty); //Sanitizing color codes
+ msg[i] = Regex.Replace(msg[i], @"[\\\&\=\;\<\>]", " "); //Filtering out some iffy characters
+ }
+ FormMessage(msg);
+ }
+ }
+
+ private static void FormMessage(string[] msg, bool ShortenedURL = false)
+ {
+ using (StreamWriter output = new StreamWriter("output.txt"))
+ {
+ if (msg[2] == "closed")
+ {
+ merge_archive = msg;
+ mergeflag = true;
+ output.Close();
+ }
+ else if ((msg[2] == "opened" || mergeflag) && msg[1] != "meant:") //Either we open a new PR or a PR was closed in the last message. Also protection from Whibyl's correction thingy!
+ {
+ if (msg[2] == "pushed")
+ {
+ msg = merge_archive; //We copy the "close" message and replace "close" with merge! The players won't know what him 'em!
+ msg[2] = "merged";
+ }
+ mergeflag = false;
+ merge_archive = null;
+ string URL = msg[msg.Length - 1];
+ if(!ShortenedURL)
+ URL = ShortenURL(URL);
+ msg[5] = "" + msg[5] + "";
+ msg[0] = ""; //Repo name
+ msg[msg.Length - 1] = ""; //The URL itself
+ msg[msg.Length - 2] = ""; //Branch info
+ byte[] PACKETS = CreatePacket(msg);
+ PACKETS[1] = 0x83;
+ int len = 0;
+ for (int i = 1; i < msg.Length - 1; i++)
+ {
+ len += msg[i].Length + 1; //The length of the word and the space following it.
+ Console.Write(msg[i] + " ");
+ }
+ len -= 1; //Compensating for the lack of space at the end.
+ len += 14 + commskey.Length + 6; //Argument names + Commskey length + 6 null bytes
+ PACKETS[3] = (byte)len;
+ StringBuilder test = new StringBuilder();
+ for (int i = 0; i < PACKETS.Length; i++)
+ {
+ test.Append(Convert.ToString(PACKETS[i]));
+ }
+ output.WriteLine(Convert.ToString(test));
+ SendPacket(output, PACKETS);
+ }
+ else
+ {
+ mergeflag = false;
+ merge_archive = null;
+ output.Close();
+ }
+ }
+ }
+
+ public static int ServerReconnectAttempt = 0;
+ private static void SendPacket(StreamWriter output, byte[] PACKETS)
+ {
+ Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ IPEndPoint ip = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);
+ try
+ {
+ server.Connect(ip);
+ server.Send(PACKETS);
+ Console.WriteLine("- sent ;)");
+ output.Close();
+ Console.WriteLine();
+ }
+ catch (Exception)
+ {
+ ServerReconnectAttempt++;
+ if(ServerReconnectAttempt <= 5)
+ {
+ Console.WriteLine("Server is not available at the moment. Reconnect attempt {0}...", ServerReconnectAttempt);
+ System.Threading.Thread.Sleep(5000); //Reconnecting after 5 seconds.
+ SendPacket(output, PACKETS);
+ }
+ else
+ {
+ output.Close();
+ Console.WriteLine("Server appears to be down for good. Press ENTER when you have restarted the server to continue.");
+ Console.ReadLine();
+ ServerReconnectAttempt = 0;
+ SendPacket(output, PACKETS);
+ }
+ }
+ }
+
+ public static int GitReconnectAttempt = 0;
+ private static string ShortenURL(string URL) //derived from GitIoSharp by dimapasko
+ {
+ WebRequest request = WebRequest.Create("http://git.io");
+ request.ContentType = "application/x-www-form-urlencoded";
+ request.Method = "POST";
+ byte[] packet = Encoding.ASCII.GetBytes("url=" + URL);
+ request.ContentLength = packet.Length;
+ try
+ {
+ using (Stream stream = request.GetRequestStream())
+ {
+ stream.Write(packet, 0, packet.Length);
+ GitReconnectAttempt = 0;
+ }
+ }
+ catch (Exception)
+ {
+ GitReconnectAttempt++;
+ if (GitReconnectAttempt <= 3)
+ {
+ Console.WriteLine("Git.IO is not available at the moment. Reconnect attempt {0}...", GitReconnectAttempt);
+ System.Threading.Thread.Sleep(3000); //Attempt to reconnect after 3 seconds.
+ ShortenURL(URL);
+ }
+ else
+ {
+ Console.WriteLine("Git.IO is down. Returning long URL.");
+ GitReconnectAttempt = 0; //Reset counter
+ return URL;
+ }
+ }
+
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ return Convert.ToString(new Uri(response.Headers[HttpResponseHeader.Location]));
+ }
+
+ private static byte[] CreatePacket(string[] msg)
+ {
+ StringBuilder packet = new StringBuilder();
+ packet.Append((char)'\x00', 8); //packet[1] is 0x83, packet[3] contain length
+ packet.Append("?announce=");
+ for (int i = 1; i < msg.Length - 1; i++)
+ {
+ if(i == msg.Length - 2)
+ packet.Append(msg[i]);
+ else
+ packet.Append(msg[i] + " ");
+ }
+ packet.Append("&key=");
+ packet.Append(commskey);
+ packet.Append((char)'\x00');
+ return Encoding.ASCII.GetBytes(packet.ToString());
+ }
+ }
+}
diff --git a/tools/PR_announcer_bot/sendkeys_ss13/Properties/AssemblyInfo.cs b/tools/PR_announcer_bot/sendkeys_ss13/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000000..669b610fc09
--- /dev/null
+++ b/tools/PR_announcer_bot/sendkeys_ss13/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("sendkeys_ss13")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("sendkeys_ss13")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("197641d3-a97a-4255-b833-d6481ca4ef54")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tools/PR_announcer_bot/sendkeys_ss13/StarkSoftProxy.dll b/tools/PR_announcer_bot/sendkeys_ss13/StarkSoftProxy.dll
new file mode 100644
index 00000000000..a5210bed38a
Binary files /dev/null and b/tools/PR_announcer_bot/sendkeys_ss13/StarkSoftProxy.dll differ
diff --git a/tools/PR_announcer_bot/sendkeys_ss13/sendkeys_ss13.csproj b/tools/PR_announcer_bot/sendkeys_ss13/sendkeys_ss13.csproj
new file mode 100644
index 00000000000..07e25db3b15
--- /dev/null
+++ b/tools/PR_announcer_bot/sendkeys_ss13/sendkeys_ss13.csproj
@@ -0,0 +1,69 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {5D939FCB-F326-4B9B-8CEC-B2538126392E}
+ Exe
+ Properties
+ sendkeys_ss13
+ sendkeys_ss13
+ v4.0
+ 512
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ .\Meebey.SmartIrc4net.dll
+
+
+ .\StarkSoftProxy.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file