Huge commit containing all the things that should have been commited seperately.

rr/dockerize
Will Hunt 2016-04-21 16:40:38 +01:00
parent a662b4d997
commit f26156001f
6 changed files with 583 additions and 12 deletions

156
MpdDj/MpdDj/Commands.cs Normal file
View File

@ -0,0 +1,156 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using MatrixSDK.Client;
namespace MpdDj
{
[AttributeUsage(AttributeTargets.Method)]
public class BotCmd : Attribute{
public readonly string CMD;
public readonly string[] BeginsWith;
public BotCmd(string cmd,params string[] beginswith){
CMD = cmd;
BeginsWith = beginswith;
}
}
public class Commands
{
[BotCmd("shuffle")]
public static void Shuffle(string cmd,string sender, MatrixRoom room){
Console.WriteLine("Shuffle");
}
[BotCmd("ping")]
public static void Ping(string cmd, string sender, MatrixRoom room){
room.SendMessage ("pong!");
}
[BotCmd("current")]
public static void GetSongName(string cmd,string sender,MatrixRoom room){
MPCCurrentSong song = Program.MPCClient.CurrentSong ();
if (song.file != null) {
FileInfo file = new FileInfo (song.file);
string name = file.Name.Replace (file.Extension, "");
name = new string(System.Text.Encoding.UTF8.GetChars (Convert.FromBase64String (name)));
room.SendMessage (name);
} else {
room.SendMessage ("Nothing is currently playing");
}
}
[BotCmd("next")]
public static void NextTrack(string cmd, string sender, MatrixRoom room){
Program.MPCClient.Next ();
}
[BotCmd("help")]
public static void Help(string cmd, string sender, MatrixRoom room){
}
[BotCmd("","http://","https://","youtube.com","youtu.be")]
public static void DownloadTrack(string cmd, string sender, MatrixRoom room)
{
try
{
List<string[]> videos = new List<string[]>();
if (Downloaders.YoutubeGetIDFromURL (cmd) != "") {
videos = DownloadYoutube (cmd, sender, room);
}
else if(Uri.IsWellFormedUriString (cmd, UriKind.Absolute)){
videos = new List<string[]>();
videos.Add(DownloadGeneric (cmd, sender, room));
}
else
{
room.SendMessage ("Sorry, that type of URL isn't supported right now :/");
return;
}
Console.WriteLine("Update starting");
Program.MPCClient.RequestLibraryUpdate();
//Program.MPCClient.Idle("update");//Wait for it to start
Program.MPCClient.Idle("update");//Wait for it to finish
foreach(string[] res in videos){
Program.MPCClient.AddFile(res[0]);
Console.WriteLine(res[0]);
}
int position = Program.MPCClient.Playlist().Length-videos.Count;
if(position == 0){
Program.MPCClient.Play();
room.SendMessage("Started playing " + videos[0][1] + " | " + Configuration.Config["mpc"]["streamurl"]);
}
else
{
room.SendMessage(videos[0][1] + " has been queued at position "+position+".");
}
}
catch(Exception e){
room.SendMessage ("There was an issue with that request, "+sender+": " + e.Message);
Console.WriteLine (e);
}
}
public static string[] DownloadGeneric(string cmd, string sender, MatrixRoom room){
Uri uri;
if (!Uri.TryCreate (cmd, UriKind.Absolute,out uri)) {
throw new Exception ("Not a url :(");
}
FileInfo info = new FileInfo (uri.Segments.Last ());
string filename = Convert.ToBase64String(Encoding.UTF8.GetBytes(info.Name))+info.Extension;
Downloaders.GenericDownload (cmd, filename);
return new string[2] {filename, uri.Segments.Last ()};
}
public static List<string[]> DownloadYoutube(string cmd, string sender, MatrixRoom room){
JObject[] videos = Downloaders.YoutubeGetData (cmd);
List<string[]> output = new List<string[]>(videos.Length);
foreach(JObject data in videos){
//Check Length
int seconds = data["duration"].ToObject<int>();
int max = int.Parse(Configuration.Config["youtube"]["maxlength"]);
if(seconds > max){
throw new Exception("Video exceeds duration limit of " + Math.Round(max / 60f,1) + " minutes");
}
string filename = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(data["fulltitle"].ToObject<string>()));
Downloaders.YoutubeDownload(data["id"].ToObject<string>(),filename);
output.Add(new string[2]{filename + ".ogg",data["title"].ToObject<string>()});
}
return output;
}
[BotCmd("","stream")]
public static void StreamUrl(string cmd, string sender, MatrixRoom room){
room.SendMessage(Configuration.Config["mpc"]["streamurl"]);
}
[BotCmd("playlist")]
public static void PlaylistDisplay(string cmd, string sender, MatrixRoom room){
string[] files = Program.MPCClient.Playlist ();
string output = "▶ ";
if (files.Length > 0) {
for (int i = 0; i < files.Length; i++) {
if (i > 4)
break;
string file = files [i].Substring (0, files [i].Length - 4) + '\n';//Remove the extension
file = new string(System.Text.Encoding.UTF8.GetChars (Convert.FromBase64String (file)));
output += file + "\n";
}
room.SendMessage (output);
} else {
room.SendMessage ("The playlist is empty");
}
}
}
}

164
MpdDj/MpdDj/Downloaders.cs Normal file
View File

@ -0,0 +1,164 @@
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace MpdDj
{
public class Downloaders
{
const string YT_BIN_WRAP = "/usr/bin/youtube-dl";
const string FFMPEG_BIN_WRAP = "/usr/bin/ffmpeg";
const string YT_FFMPEG = " -i \"{0}.{2}\" -vn -y -c:a libvorbis -b:a 192k \"{1}.ogg\"";
const string YT_YOUTUBEDL = " -f best -o '{0}.%(ext)s' {1}";
static readonly Regex YoutubeRegex = new Regex("youtu(?:\\.be|be\\.com)/(?:.*v(?:/|=)|(?:.*/)?)([a-zA-Z0-9-_]+)",RegexOptions.Compiled);
static readonly Regex YoutubePLRegex = new Regex ("^.*(youtu.be\\/|list=)([^#\\&\\?]*).*", RegexOptions.Compiled);
public static void GenericDownload(string url,string filename){
string[] allowedMimetypes = Configuration.Config ["file"] ["mimetypes"].Split (' ');
using (HttpClient client = new HttpClient ()) {
Task<HttpResponseMessage> msg = client.SendAsync (new HttpRequestMessage (HttpMethod.Head, url));
msg.Wait ();
IEnumerable<String> types;
IEnumerable<String> lengths;
if (msg.Result.StatusCode != System.Net.HttpStatusCode.OK) {
throw new Exception ("Server gave a " + msg.Result.StatusCode);
}
if (msg.Result.Content.Headers.TryGetValues ("Content-Type", out types)) {
if (!types.Any (x => allowedMimetypes.Contains (x))) {
throw new Exception ("Filetype not supported");
}
if (msg.Result.Content.Headers.TryGetValues ("Content-Length", out lengths)) {
float length = int.Parse (lengths.First()) / (float)Math.Pow(1024,2);
float maxlength = float.Parse (Configuration.Config ["file"] ["size_limit"]);
if (length > maxlength) {
throw new Exception ("File is over " + maxlength + "MBs in size");
}
} else {
throw new Exception ("Cannot gauge content size from headers. Bailing");
}
}
else
{
throw new Exception("Server does not state a Content-Type. Bailing.");
}
}
using (System.Net.WebClient client = new System.Net.WebClient ()) {
try {
string fname = System.IO.Path.Combine(Configuration.Config ["mpc"] ["music_dir"],filename);
System.IO.FileInfo finfo = new System.IO.FileInfo(fname);
if(!client.DownloadFileTaskAsync (url,finfo.FullName).Wait(TimeSpan.FromSeconds(15))){
throw new Exception("File took too long to download");
}
} catch (Exception e) {
Console.WriteLine ("Issue downloading file", e);
throw new Exception ("Couldn't download file");
}
}
}
public static string YoutubeGetIDFromURL(string url)
{
GroupCollection regg = YoutubeRegex.Match (url).Groups;
GroupCollection reggpl = YoutubePLRegex.Match (url).Groups;
if (regg.Count > 1 && regg[1].Value != "playlist") {
return regg [1].Value;
} else if (reggpl.Count > 2) {
return reggpl [2].Value;
} else {
return "";
}
}
private static void YoutubeConvert(string filename){
string[] extensions = new string[2]{"mp4","webm"};
//It doesn't tell us :/
string extension = null;
foreach (string ext in extensions) {
if (System.IO.File.Exists ("/tmp/" + filename + "." + ext)) {
extension = ext;
}
}
if(extension == null){
throw new Exception ("Couldn't find video file");
}
Process proc = new Process ();
proc.StartInfo = new ProcessStartInfo () {
FileName = FFMPEG_BIN_WRAP,
WorkingDirectory = "/tmp",
Arguments = String.Format(YT_FFMPEG,filename,Configuration.Config["mpc"]["music_dir"] + "/" + filename,extension),
UseShellExecute = false,
LoadUserProfile = true,
};
proc.Start ();
proc.WaitForExit ();
if(proc.ExitCode != 0){
throw new Exception("There was an error transcoding the video");
}
System.IO.File.Delete("/tmp/"+filename+"."+extension);
}
public static void YoutubeDownload(string url, string filename){
Process proc = new Process ();
proc.StartInfo = new ProcessStartInfo () {
FileName = YT_BIN_WRAP,
WorkingDirectory = "/tmp",
Arguments = String.Format(YT_YOUTUBEDL,filename,url),
UseShellExecute = false,
LoadUserProfile = true,
RedirectStandardOutput = true,
};
proc.Start ();
proc.WaitForExit ();
if(proc.ExitCode != 0){
throw new Exception("There was an error downloading the video");
}
YoutubeConvert (filename);
}
public static JObject[] YoutubeGetData(string url){
string id = YoutubeGetIDFromURL (url);
if (id == "") {
throw new Exception ("Bad url.");
}
Process proc = new Process ();
proc.StartInfo = new ProcessStartInfo () {
FileName = YT_BIN_WRAP,
Arguments = "-j " + id,
UseShellExecute = false,
LoadUserProfile = true,
RedirectStandardOutput = true,
};
proc.Start ();
string data = "";
while (!proc.HasExited) {
data += proc.StandardOutput.ReadToEnd ();
System.Threading.Thread.Sleep (50);
}
proc.Dispose ();
if (data == "") {
throw new Exception ("Bad url.");
}
List<JObject> videos = new List<JObject> ();
foreach(string line in data.Split('\n')){
if(string.IsNullOrWhiteSpace(line))
continue;
videos.Add(JObject.Parse(line));
}
return videos.ToArray ();
}
}
}

191
MpdDj/MpdDj/MPC.cs Normal file
View File

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Linq;
using System.Threading;
namespace MpdDj
{
public struct MPCCurrentSong
{
public string file;
public DateTime date;
public string Artist;
public string Title;
public string Album;
public string Track;
public string Date;
public string Genre;
public string Disc;
public int Time;
public int Pos;
public int Id;
}
public struct MPCStatus
{
public int volume;
public bool repeat;
public bool random;
public bool single;
public bool consume;
public int playlist;
public int playlistlength;
public float mixrampdb;
public string state;
public int song;
public int songid;
public string time;
public float elapsed;
public int bitrate;
public string audio;
public int nextsong;
public int nextsongid;
}
public class MPC
{
TcpClient client;
NetworkStream stream;
public MPCStatus lastStatus { get; private set; }
int port;
string host;
Mutex client_mutex;
public MPC (string host, string port)
{
this.host = host;
this.port = int.Parse (port);
client_mutex = new Mutex ();
OpenConnection ();
}
private T FillStruct<T>(string data){
string[] lines = data.Split ('\n');
object o = Activator.CreateInstance<T> ();
foreach (string line in lines) {
string[] keyval = line.Split (new string[1]{": "}, 2,StringSplitOptions.RemoveEmptyEntries);
if (keyval.Length == 2) {
System.Reflection.FieldInfo f = typeof(T).GetField (keyval [0]);
if (f != null) {
object value;
if (f.FieldType == typeof(float)) {
value = float.Parse (keyval [1]);
} else if (f.FieldType == typeof(int)) {
value = int.Parse (keyval [1]);
} else if (f.FieldType == typeof(bool)) {
value = int.Parse (keyval [1]) == 1;
} else if (f.FieldType == typeof(DateTime)) {
value = DateTime.Parse (keyval [1]);
} else {
value = keyval [1];
}
f.SetValue(o,value);
}
}
}
return (T)o;
}
private void OpenConnection(){
client = new TcpClient (host,port);
stream = client.GetStream ();
byte[] buff = new byte[6];
stream.Read (buff, 0, buff.Length);
if (Encoding.UTF8.GetString (buff) != "OK MPD") {
throw new Exception ("Connection is not a MPD stream");
}
//Eat the rest
while (stream.DataAvailable) {
stream.ReadByte ();
}
}
private string Send(string data, bool wait = false){
if (!client_mutex.WaitOne (30000)) {
throw new Exception ("Timed out waiting for the mutex to become avaliable for the mpc client.");
}
OpenConnection ();
byte[] bdata = Encoding.UTF8.GetBytes (data+"\n");
stream.Write (bdata,0,bdata.Length);
List<byte> buffer = new List<byte>();
while (!stream.DataAvailable && wait) {
System.Threading.Thread.Sleep (100);
}
while (stream.DataAvailable) {
buffer.Add ((byte)stream.ReadByte ());
}
string sbuffer = Encoding.UTF8.GetString (buffer.ToArray());
client.Close ();
stream.Dispose ();
client_mutex.ReleaseMutex ();
return sbuffer;
}
public void Play(){
Send ("play");
}
public void Next(){
Send ("next");
}
public void Previous(){
Send ("prev");
}
public void AddFile(string file){
Send ("add " + file);
}
public void Idle(string subsystem){
Send ("idle " + subsystem,true);
}
public string[] Playlist(){
string playlist = Send ("playlist");
List<string> newsongs = new List<string> ();
foreach (string song in playlist.Split ('\n')) {
int indexof = song.IndexOf (' ');
if (indexof != -1) {
newsongs.Add(song.Substring (indexof + 1));
}
}
return newsongs.ToArray();
}
public void Status(){
string sstatus = Send ("status");
MPCStatus status = FillStruct<MPCStatus> (sstatus);
lastStatus = status;
}
public MPCCurrentSong CurrentSong(){
string result = Send("currentsong");
MPCCurrentSong song = FillStruct<MPCCurrentSong>(result);
return song;
}
public void RequestLibraryUpdate(){
Send ("update");
}
}
public static class TcpExtensions{
public static TcpState GetState(this TcpClient tcpClient)
{
var foo = IPGlobalProperties.GetIPGlobalProperties()
.GetActiveTcpConnections()
.SingleOrDefault(x => x.LocalEndPoint.Equals(tcpClient.Client.LocalEndPoint));
return foo != null ? foo.State : TcpState.Unknown;
}
}
}

View File

@ -34,11 +34,18 @@
<Reference Include="INIFileParser">
<HintPath>..\packages\ini-parser.2.2.4\lib\net20\INIFileParser.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Configuration.cs" />
<Compile Include="Commands.cs" />
<Compile Include="MPC.cs" />
<Compile Include="Downloaders.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

View File

@ -1,32 +1,65 @@
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Threading.Tasks;
using MatrixSDK.Client;
using MatrixSDK.Structures;
namespace MpdDj
{
class MainClass
class Program
{
public static MatrixClient client;
public static MatrixClient Client;
public static MPC MPCClient;
public static Dictionary<BotCmd,MethodInfo> Cmds = new Dictionary<BotCmd, MethodInfo>();
public static void Main (string[] args)
{
Console.WriteLine ("Reading INI File");
string cfgpath;
if (args.Length > 1) {
cfgpath = System.IO.Path.GetFullPath (args [1]);
cfgpath = args [1];
} else {
cfgpath = "~/.config/mpddj.ini";
cfgpath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/.config/mpddj.ini";
}
cfgpath = System.IO.Path.GetFullPath (cfgpath);
Console.WriteLine("Trying to read from " + cfgpath);
Configuration.ReadConfig (cfgpath);
Console.WriteLine ("Connecting to MPD");
MPCClient = new MPC (Configuration.Config ["mpc"] ["host"], Configuration.Config ["mpc"] ["port"]);
MPCClient.Status ();
Console.WriteLine ("Connecting to Matrix");
client = new MatrixClient (Configuration.Config ["mpc"] ["host"]);
Client = new MatrixClient (Configuration.Config ["matrix"] ["host"]);
Console.WriteLine("Connected. Logging in");
client.LoginWithPassword (Configuration.Config ["mpc"] ["user"], Configuration.Config ["mpc"] ["pass"]);
Client.LoginWithPassword (Configuration.Config ["matrix"] ["user"], Configuration.Config ["matrix"] ["pass"]);
Console.WriteLine("Logged in OK");
Console.WriteLine("Joining Rooms:");
foreach (string roomid in Configuration.Config ["mpc"] ["rooms"].Split(',')) {
MatrixRoom room = client.JoinRoom (roomid);
room.OnMessage += Room_OnMessage;
Console.WriteLine("\tJoined " + roomid);
foreach (string roomid in Configuration.Config ["matrix"] ["rooms"].Split(',')) {
MatrixRoom room = Client.GetRoomByAlias (roomid);
if (room == null) {
room = Client.JoinRoom (roomid);
if (room != null) {
Console.WriteLine ("\tJoined " + roomid);
room.OnMessage += Room_OnMessage;
} else {
Console.WriteLine ("\tCouldn't find " + roomid);
}
} else {
room.OnMessage += Room_OnMessage;
}
}
Console.WriteLine ("Done!");
//Find commands
foreach(MethodInfo method in typeof(Commands).GetMethods(BindingFlags.Static|BindingFlags.Public)){
BotCmd cmd = method.GetCustomAttribute<BotCmd> ();
if (cmd != null) {
Cmds.Add (cmd, method);
}
}
}
@ -36,13 +69,32 @@ namespace MpdDj
if (evt.age > 3000) {
return; // Too old
}
string msg = ((MatrixMRoomMessage)evt.content).body;
if (msg.StartsWith ("!mpddj")) {
msg = msg.Substring (7);
Console.WriteLine ("Got message okay");
}
string[] parts = msg.Split (' ');
string cmd = parts [0].ToLower ();
try
{
MethodInfo method = Cmds.First(x => {
return (x.Key.CMD == cmd) || ( x.Key.BeginsWith.Any( y => cmd.StartsWith(y) ));
}).Value;
Task task = new Task (() => {
method.Invoke (null, new object[3]{ msg, evt.sender, room });
});
task.Start ();
}
catch(InvalidOperationException){
//Command not found
}
catch(Exception e){
Console.Error.WriteLine ("Problem with one of the commands");
Console.Error.WriteLine (e);
}
}
}
}
}

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ini-parser" version="2.2.4" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
</packages>