Utility and nsfw work
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
class PlaylistFullException : Exception
|
||||
{
|
||||
public PlaylistFullException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
public PlaylistFullException() : base("Queue is full.") { }
|
||||
}
|
||||
|
||||
class SongNotFoundException : Exception
|
||||
{
|
||||
public SongNotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
public SongNotFoundException() : base("Song is not found.") { }
|
||||
}
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
using Discord;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public static class MusicExtensions
|
||||
{
|
||||
public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) =>
|
||||
eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png");
|
||||
}
|
||||
}
|
@@ -1,295 +0,0 @@
|
||||
using Discord.Audio;
|
||||
using NadekoBot.Extensions;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public class SongInfo
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public MusicType ProviderType { get; set; }
|
||||
public string Query { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string AlbumArt { get; set; }
|
||||
}
|
||||
|
||||
public class Song
|
||||
{
|
||||
public SongInfo SongInfo { get; }
|
||||
public MusicPlayer MusicPlayer { get; set; }
|
||||
|
||||
private string _queuerName;
|
||||
public string QueuerName { get{
|
||||
return Discord.Format.Sanitize(_queuerName);
|
||||
} set { _queuerName = value; } }
|
||||
|
||||
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
|
||||
public TimeSpan CurrentTime => TimeSpan.FromSeconds(BytesSent / (float)_frameBytes / (1000 / (float)_milliseconds));
|
||||
|
||||
private const int _milliseconds = 20;
|
||||
private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
|
||||
private const int _frameBytes = 3840; //16-bit, 2 channels
|
||||
|
||||
private ulong BytesSent { get; set; }
|
||||
|
||||
//pwetty
|
||||
|
||||
public string PrettyProvider =>
|
||||
$"{(SongInfo.Provider ?? "???")}";
|
||||
|
||||
public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
|
||||
|
||||
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**";
|
||||
|
||||
public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}";
|
||||
|
||||
public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {QueuerName}`";
|
||||
|
||||
public string PrettyCurrentTime {
|
||||
get {
|
||||
var time = CurrentTime.ToString(@"mm\:ss");
|
||||
var hrs = (int)CurrentTime.TotalHours;
|
||||
|
||||
if (hrs > 0)
|
||||
return hrs + ":" + time;
|
||||
else
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
public string PrettyTotalTime {
|
||||
get
|
||||
{
|
||||
if (TotalTime == TimeSpan.Zero)
|
||||
return "(?)";
|
||||
if (TotalTime == TimeSpan.MaxValue)
|
||||
return "∞";
|
||||
var time = TotalTime.ToString(@"mm\:ss");
|
||||
var hrs = (int)TotalTime.TotalHours;
|
||||
|
||||
if (hrs > 0)
|
||||
return hrs + ":" + time;
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
public string Thumbnail {
|
||||
get {
|
||||
switch (SongInfo.ProviderType)
|
||||
{
|
||||
case MusicType.Radio:
|
||||
return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
|
||||
case MusicType.Normal:
|
||||
//todo have videoid in songinfo from the start
|
||||
var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+");
|
||||
return $"https://img.youtube.com/vi/{ videoId }/0.jpg";
|
||||
case MusicType.Local:
|
||||
return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
|
||||
case MusicType.Soundcloud:
|
||||
return SongInfo.AlbumArt;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SongUrl {
|
||||
get {
|
||||
switch (SongInfo.ProviderType)
|
||||
{
|
||||
case MusicType.Normal:
|
||||
return SongInfo.Query;
|
||||
case MusicType.Soundcloud:
|
||||
return SongInfo.Query;
|
||||
case MusicType.Local:
|
||||
return $"https://google.com/search?q={ WebUtility.UrlEncode(SongInfo.Title).Replace(' ', '+') }";
|
||||
case MusicType.Radio:
|
||||
return $"https://google.com/search?q={SongInfo.Title}";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int SkipTo { get; set; }
|
||||
|
||||
private readonly Logger _log;
|
||||
|
||||
public Song(SongInfo songInfo)
|
||||
{
|
||||
SongInfo = songInfo;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
public Song Clone()
|
||||
{
|
||||
var s = new Song(SongInfo)
|
||||
{
|
||||
MusicPlayer = MusicPlayer,
|
||||
QueuerName = QueuerName
|
||||
};
|
||||
return s;
|
||||
}
|
||||
|
||||
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
|
||||
{
|
||||
BytesSent = (ulong) SkipTo * 3840 * 50;
|
||||
var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
|
||||
|
||||
var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
|
||||
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var attempt = 0;
|
||||
|
||||
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy
|
||||
var finished = false;
|
||||
var count = 0;
|
||||
var sw = new Stopwatch();
|
||||
var slowconnection = false;
|
||||
sw.Start();
|
||||
while (!finished)
|
||||
{
|
||||
var t = await Task.WhenAny(prebufferingTask, Task.Delay(2000, cancelToken));
|
||||
if (t != prebufferingTask)
|
||||
{
|
||||
count++;
|
||||
if (count == 10)
|
||||
{
|
||||
slowconnection = true;
|
||||
prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 20.MiB());
|
||||
_log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inStream.BufferingCompleted && count == 1)
|
||||
{
|
||||
_log.Debug("Prebuffering canceled. Cannot get any data from the stream.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (prebufferingTask.IsCanceled)
|
||||
{
|
||||
_log.Debug("Prebuffering canceled. Cannot get any data from the stream.");
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
}
|
||||
sw.Stop();
|
||||
_log.Debug("Prebuffering successfully completed in " + sw.Elapsed);
|
||||
|
||||
var outStream = voiceClient.CreatePCMStream(AudioApplication.Music);
|
||||
|
||||
int nextTime = Environment.TickCount + _milliseconds;
|
||||
|
||||
byte[] buffer = new byte[_frameBytes];
|
||||
while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason
|
||||
!(MusicPlayer.MaxPlaytimeSeconds != 0 && CurrentTime.TotalSeconds >= MusicPlayer.MaxPlaytimeSeconds)) // or exceedded max playtime
|
||||
{
|
||||
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
|
||||
var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
//await inStream.CopyToAsync(voiceClient.OutputStream);
|
||||
if (read < _frameBytes)
|
||||
_log.Debug("read {0}", read);
|
||||
unchecked
|
||||
{
|
||||
BytesSent += (ulong)read;
|
||||
}
|
||||
if (read < _frameBytes)
|
||||
{
|
||||
if (read == 0)
|
||||
{
|
||||
if (inStream.BufferingCompleted)
|
||||
break;
|
||||
if (attempt++ == 20)
|
||||
{
|
||||
MusicPlayer.SongCancelSource.Cancel();
|
||||
break;
|
||||
}
|
||||
if (slowconnection)
|
||||
{
|
||||
_log.Warn("Slow connection has disrupted music, waiting a bit for buffer");
|
||||
|
||||
await Task.Delay(1000, cancelToken).ConfigureAwait(false);
|
||||
nextTime = Environment.TickCount + _milliseconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(100, cancelToken).ConfigureAwait(false);
|
||||
nextTime = Environment.TickCount + _milliseconds;
|
||||
}
|
||||
}
|
||||
else
|
||||
attempt = 0;
|
||||
}
|
||||
else
|
||||
attempt = 0;
|
||||
|
||||
while (MusicPlayer.Paused)
|
||||
{
|
||||
await Task.Delay(200, cancelToken).ConfigureAwait(false);
|
||||
nextTime = Environment.TickCount + _milliseconds;
|
||||
}
|
||||
|
||||
|
||||
buffer = AdjustVolume(buffer, MusicPlayer.Volume);
|
||||
if (read != _frameBytes) continue;
|
||||
nextTime = unchecked(nextTime + _milliseconds);
|
||||
int delayMillis = unchecked(nextTime - Environment.TickCount);
|
||||
if (delayMillis > 0)
|
||||
await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
|
||||
await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await bufferTask;
|
||||
inStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size)
|
||||
{
|
||||
while (!inStream.BufferingCompleted && inStream.Length < size)
|
||||
{
|
||||
await Task.Delay(100, cancelToken);
|
||||
}
|
||||
_log.Debug("Buffering successfull");
|
||||
}
|
||||
|
||||
//aidiakapi ftw
|
||||
public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume)
|
||||
{
|
||||
if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
|
||||
|
||||
// 16-bit precision for the multiplication
|
||||
var volumeFixed = (int)Math.Round(volume * 65536d);
|
||||
|
||||
var count = audioSamples.Length / 2;
|
||||
|
||||
fixed (byte* srcBytes = audioSamples)
|
||||
{
|
||||
var src = (short*)srcBytes;
|
||||
|
||||
for (var i = count; i != 0; i--, src++)
|
||||
*src = (short)(((*src) * volumeFixed) >> 16);
|
||||
}
|
||||
|
||||
return audioSamples;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,219 +0,0 @@
|
||||
using NadekoBot.Extensions;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.
|
||||
/// It also help for large music by deleting files that are already seen.
|
||||
/// </summary>
|
||||
class SongBuffer : Stream
|
||||
{
|
||||
public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize)
|
||||
{
|
||||
MusicPlayer = musicPlayer;
|
||||
Basename = basename;
|
||||
SongInfo = songInfo;
|
||||
SkipTo = skipTo;
|
||||
MaxFileSize = maxFileSize;
|
||||
CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
MusicPlayer MusicPlayer { get; }
|
||||
|
||||
private string Basename { get; }
|
||||
|
||||
private SongInfo SongInfo { get; }
|
||||
|
||||
private int SkipTo { get; }
|
||||
|
||||
private int MaxFileSize { get; } = 2.MiB();
|
||||
|
||||
private long FileNumber = -1;
|
||||
|
||||
private long NextFileToRead = 0;
|
||||
|
||||
public bool BufferingCompleted { get; private set; } = false;
|
||||
|
||||
private ulong CurrentBufferSize = 0;
|
||||
|
||||
private FileStream CurrentFileStream;
|
||||
private Logger _log;
|
||||
|
||||
public Task BufferSong(CancellationToken cancelToken) =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Process p = null;
|
||||
FileStream outStream = null;
|
||||
try
|
||||
{
|
||||
p = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "ffmpeg",
|
||||
Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = false,
|
||||
CreateNoWindow = true,
|
||||
});
|
||||
|
||||
byte[] buffer = new byte[81920];
|
||||
int currentFileSize = 0;
|
||||
ulong prebufferSize = 100ul.MiB();
|
||||
|
||||
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
while (!p.HasExited) //Also fix low bandwidth
|
||||
{
|
||||
int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false);
|
||||
if (currentFileSize >= MaxFileSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
outStream.Dispose();
|
||||
}
|
||||
catch { }
|
||||
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
currentFileSize = bytesRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFileSize += bytesRead;
|
||||
}
|
||||
CurrentBufferSize += Convert.ToUInt64(bytesRead);
|
||||
await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
|
||||
while (CurrentBufferSize > prebufferSize)
|
||||
await Task.Delay(100, cancelToken);
|
||||
}
|
||||
BufferingCompleted = true;
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception)
|
||||
{
|
||||
var oldclr = Console.ForegroundColor;
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(@"You have not properly installed or configured FFMPEG.
|
||||
Please install and configure FFMPEG to play music.
|
||||
Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
Windows Guide: https://goo.gl/OjKk8F
|
||||
Linux Guide: https://goo.gl/ShjCUo");
|
||||
Console.ForegroundColor = oldclr;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Buffering stopped: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (outStream != null)
|
||||
outStream.Dispose();
|
||||
Console.WriteLine($"Buffering done.");
|
||||
if (p != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
p.Kill();
|
||||
}
|
||||
catch { }
|
||||
p.Dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Return the next file to read, and delete the old one
|
||||
/// </summary>
|
||||
/// <returns>Name of the file to read</returns>
|
||||
private string GetNextFile()
|
||||
{
|
||||
string filename = Basename + "-" + NextFileToRead;
|
||||
|
||||
if (NextFileToRead != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length);
|
||||
File.Delete(Basename + "-" + (NextFileToRead - 1));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
NextFileToRead++;
|
||||
return filename;
|
||||
}
|
||||
|
||||
private bool IsNextFileReady()
|
||||
{
|
||||
return NextFileToRead <= FileNumber;
|
||||
}
|
||||
|
||||
private void CleanFiles()
|
||||
{
|
||||
for (long i = NextFileToRead - 1; i <= FileNumber; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Basename + "-" + i);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
//Stream part
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => (long)CurrentBufferSize;
|
||||
|
||||
public override long Position { get; set; } = 0;
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = CurrentFileStream.Read(buffer, offset, count);
|
||||
if (read < count)
|
||||
{
|
||||
if (!BufferingCompleted || IsNextFileReady())
|
||||
{
|
||||
CurrentFileStream.Dispose();
|
||||
CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
|
||||
read += CurrentFileStream.Read(buffer, read + offset, count - read);
|
||||
}
|
||||
if (read < count)
|
||||
Array.Clear(buffer, read, count - read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public new void Dispose()
|
||||
{
|
||||
CurrentFileStream.Dispose();
|
||||
MusicPlayer.SongCancelSource.Cancel();
|
||||
CleanFiles();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,215 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using VideoLibrary;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public static class SongHandler
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
if (musicType != MusicType.Local && IsRadioLink(query))
|
||||
{
|
||||
musicType = MusicType.Radio;
|
||||
query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (musicType)
|
||||
{
|
||||
case MusicType.Local:
|
||||
return new Song(new SongInfo
|
||||
{
|
||||
Uri = "\"" + Path.GetFullPath(query) + "\"",
|
||||
Title = Path.GetFileNameWithoutExtension(query),
|
||||
Provider = "Local File",
|
||||
ProviderType = musicType,
|
||||
Query = query,
|
||||
});
|
||||
case MusicType.Radio:
|
||||
return new Song(new SongInfo
|
||||
{
|
||||
Uri = query,
|
||||
Title = $"{query}",
|
||||
Provider = "Radio Stream",
|
||||
ProviderType = musicType,
|
||||
Query = query
|
||||
})
|
||||
{ TotalTime = TimeSpan.MaxValue };
|
||||
}
|
||||
if (SoundCloud.Default.IsSoundCloudLink(query))
|
||||
{
|
||||
var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false);
|
||||
return new Song(new SongInfo
|
||||
{
|
||||
Title = svideo.FullName,
|
||||
Provider = "SoundCloud",
|
||||
Uri = svideo.StreamLink,
|
||||
ProviderType = musicType,
|
||||
Query = svideo.TrackLink,
|
||||
AlbumArt = svideo.artwork_url,
|
||||
})
|
||||
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
|
||||
}
|
||||
|
||||
if (musicType == MusicType.Soundcloud)
|
||||
{
|
||||
var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false);
|
||||
return new Song(new SongInfo
|
||||
{
|
||||
Title = svideo.FullName,
|
||||
Provider = "SoundCloud",
|
||||
Uri = svideo.StreamLink,
|
||||
ProviderType = MusicType.Soundcloud,
|
||||
Query = svideo.TrackLink,
|
||||
AlbumArt = svideo.artwork_url,
|
||||
})
|
||||
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
|
||||
}
|
||||
|
||||
var link = (await NadekoBot.Google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(link))
|
||||
throw new OperationCanceledException("Not a valid youtube query.");
|
||||
var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
|
||||
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
|
||||
var video = videos
|
||||
.Where(v => v.AudioBitrate < 256)
|
||||
.OrderByDescending(v => v.AudioBitrate)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (video == null) // do something with this error
|
||||
throw new Exception("Could not load any video elements based on the query.");
|
||||
var m = Regex.Match(query, @"\?t=(?<t>\d*)");
|
||||
int gotoTime = 0;
|
||||
if (m.Captures.Count > 0)
|
||||
int.TryParse(m.Groups["t"].ToString(), out gotoTime);
|
||||
var song = new Song(new SongInfo
|
||||
{
|
||||
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
|
||||
Provider = "YouTube",
|
||||
Uri = await video.GetUriAsync().ConfigureAwait(false),
|
||||
Query = link,
|
||||
ProviderType = musicType,
|
||||
});
|
||||
song.SkipTo = gotoTime;
|
||||
return song;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn($"Failed resolving the link.{ex.Message}");
|
||||
_log.Warn(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> HandleStreamContainers(string query)
|
||||
{
|
||||
string file = null;
|
||||
try
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
file = await http.GetStringAsync(query).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return query;
|
||||
}
|
||||
if (query.Contains(".pls"))
|
||||
{
|
||||
//File1=http://armitunes.com:8000/
|
||||
//Regex.Match(query)
|
||||
try
|
||||
{
|
||||
var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
|
||||
var res = m.Groups["url"]?.ToString();
|
||||
return res?.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn($"Failed reading .pls:\n{file}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (query.Contains(".m3u"))
|
||||
{
|
||||
/*
|
||||
# This is a comment
|
||||
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
|
||||
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
|
||||
*/
|
||||
try
|
||||
{
|
||||
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
|
||||
var res = m.Groups["url"]?.ToString();
|
||||
return res?.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn($"Failed reading .m3u:\n{file}");
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
if (query.Contains(".asx"))
|
||||
{
|
||||
//<ref href="http://armitunes.com:8000"/>
|
||||
try
|
||||
{
|
||||
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
|
||||
var res = m.Groups["url"]?.ToString();
|
||||
return res?.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn($"Failed reading .asx:\n{file}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (query.Contains(".xspf"))
|
||||
{
|
||||
/*
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
||||
<trackList>
|
||||
<track><location>file:///mp3s/song_1.mp3</location></track>
|
||||
*/
|
||||
try
|
||||
{
|
||||
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
|
||||
var res = m.Groups["url"]?.ToString();
|
||||
return res?.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn($"Failed reading .xspf:\n{file}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static bool IsRadioLink(string query) =>
|
||||
(query.StartsWith("http") ||
|
||||
query.StartsWith("ww"))
|
||||
&&
|
||||
(query.Contains(".pls") ||
|
||||
query.Contains(".m3u") ||
|
||||
query.Contains(".asx") ||
|
||||
query.Contains(".xspf"));
|
||||
}
|
||||
}
|
@@ -1,142 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public class SoundCloud
|
||||
{
|
||||
private static readonly SoundCloud _instance = new SoundCloud();
|
||||
public static SoundCloud Default => _instance;
|
||||
|
||||
static SoundCloud() { }
|
||||
public SoundCloud() { }
|
||||
|
||||
public async Task<SoundCloudVideo> ResolveVideoAsync(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId))
|
||||
throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId));
|
||||
|
||||
string response = "";
|
||||
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject<SoundCloudVideo>(response);
|
||||
if (responseObj?.Kind != "track")
|
||||
throw new InvalidOperationException("Url is either not a track, or it doesn't exist.");
|
||||
|
||||
return responseObj;
|
||||
}
|
||||
|
||||
public bool IsSoundCloudLink(string url) =>
|
||||
System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)");
|
||||
|
||||
public async Task<SoundCloudVideo> GetVideoByQueryAsync(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId))
|
||||
throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId));
|
||||
|
||||
var response = "";
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo[]>(response).Where(s => s.Streamable).FirstOrDefault();
|
||||
if (responseObj?.Kind != "track")
|
||||
throw new InvalidOperationException("Query yielded no results.");
|
||||
|
||||
return responseObj;
|
||||
}
|
||||
}
|
||||
|
||||
public class SoundCloudVideo
|
||||
{
|
||||
public string Kind { get; set; } = "";
|
||||
public long Id { get; set; } = 0;
|
||||
public SoundCloudUser User { get; set; } = new SoundCloudUser();
|
||||
public string Title { get; set; } = "";
|
||||
[JsonIgnore]
|
||||
public string FullName => User.Name + " - " + Title;
|
||||
public bool Streamable { get; set; } = false;
|
||||
public int Duration { get; set; }
|
||||
[JsonProperty("permalink_url")]
|
||||
public string TrackLink { get; set; } = "";
|
||||
public string artwork_url { get; set; } = "";
|
||||
[JsonIgnore]
|
||||
public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Credentials.SoundCloudClientId}";
|
||||
}
|
||||
public class SoundCloudUser
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("username")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
/*
|
||||
{"kind":"track",
|
||||
"id":238888167,
|
||||
"created_at":"2015/12/24 01:04:52 +0000",
|
||||
"user_id":43141975,
|
||||
"duration":120852,
|
||||
"commentable":true,
|
||||
"state":"finished",
|
||||
"original_content_size":4834829,
|
||||
"last_modified":"2015/12/24 01:17:59 +0000",
|
||||
"sharing":"public",
|
||||
"tag_list":"Funky",
|
||||
"permalink":"18-fd",
|
||||
"streamable":true,
|
||||
"embeddable_by":"all",
|
||||
"downloadable":false,
|
||||
"purchase_url":null,
|
||||
"label_id":null,
|
||||
"purchase_title":null,
|
||||
"genre":"Disco",
|
||||
"title":"18 Ж",
|
||||
"description":"",
|
||||
"label_name":null,
|
||||
"release":null,
|
||||
"track_type":null,
|
||||
"key_signature":null,
|
||||
"isrc":null,
|
||||
"video_url":null,
|
||||
"bpm":null,
|
||||
"release_year":null,
|
||||
"release_month":null,
|
||||
"release_day":null,
|
||||
"original_format":"mp3",
|
||||
"license":"all-rights-reserved",
|
||||
"uri":"https://api.soundcloud.com/tracks/238888167",
|
||||
"user":{
|
||||
"id":43141975,
|
||||
"kind":"user",
|
||||
"permalink":"mrb00gi",
|
||||
"username":"Mrb00gi",
|
||||
"last_modified":"2015/12/01 16:06:57 +0000",
|
||||
"uri":"https://api.soundcloud.com/users/43141975",
|
||||
"permalink_url":"http://soundcloud.com/mrb00gi",
|
||||
"avatar_url":"https://a1.sndcdn.com/images/default_avatar_large.png"
|
||||
},
|
||||
"permalink_url":"http://soundcloud.com/mrb00gi/18-fd",
|
||||
"artwork_url":null,
|
||||
"waveform_url":"https://w1.sndcdn.com/gsdLfvEW1cUK_m.png",
|
||||
"stream_url":"https://api.soundcloud.com/tracks/238888167/stream",
|
||||
"playback_count":7,
|
||||
"download_count":0,
|
||||
"favoritings_count":1,
|
||||
"comment_count":0,
|
||||
"attachments_uri":"https://api.soundcloud.com/tracks/238888167/attachments"}
|
||||
|
||||
*/
|
||||
|
||||
}
|
@@ -23,8 +23,6 @@ namespace NadekoBot.Modules.Music
|
||||
[DontAutoLoad]
|
||||
public class Music : NadekoTopLevelModule
|
||||
{
|
||||
public const string MusicDataPath = "data/musicdata";
|
||||
|
||||
private static MusicService music;
|
||||
|
||||
static Music()
|
||||
|
@@ -11,15 +11,21 @@ using NadekoBot.Extensions;
|
||||
using System.Xml;
|
||||
using System.Threading;
|
||||
using System.Collections.Concurrent;
|
||||
using NadekoBot.Services.Searches;
|
||||
|
||||
namespace NadekoBot.Modules.NSFW
|
||||
{
|
||||
[NadekoModule("NSFW", "~")]
|
||||
[NadekoModule("NSFW")]
|
||||
public class NSFW : NadekoTopLevelModule
|
||||
{
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
|
||||
private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>();
|
||||
private readonly SearchesService _service;
|
||||
|
||||
public NSFW(SearchesService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
|
||||
{
|
||||
@@ -142,11 +148,11 @@ namespace NadekoBot.Modules.NSFW
|
||||
#endif
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task Yandere([Remainder] string tag = null)
|
||||
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Yandere);
|
||||
=> InternalDapiCommand(tag, DapiSearchType.Yandere);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task Konachan([Remainder] string tag = null)
|
||||
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Konachan);
|
||||
=> InternalDapiCommand(tag, DapiSearchType.Konachan);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task E621([Remainder] string tag = null)
|
||||
@@ -167,7 +173,7 @@ namespace NadekoBot.Modules.NSFW
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task Rule34([Remainder] string tag = null)
|
||||
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Rule34);
|
||||
=> InternalDapiCommand(tag, DapiSearchType.Rule34);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Danbooru([Remainder] string tag = null)
|
||||
@@ -210,7 +216,7 @@ namespace NadekoBot.Modules.NSFW
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public Task Gelbooru([Remainder] string tag = null)
|
||||
=> InternalDapiCommand(tag, Searches.Searches.DapiSearchType.Gelbooru);
|
||||
=> InternalDapiCommand(tag, DapiSearchType.Gelbooru);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Boobs()
|
||||
@@ -270,23 +276,23 @@ namespace NadekoBot.Modules.NSFW
|
||||
}
|
||||
});
|
||||
|
||||
public static Task<string> GetRule34ImageLink(string tag) =>
|
||||
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34);
|
||||
public Task<string> GetRule34ImageLink(string tag) =>
|
||||
_service.DapiSearch(tag, DapiSearchType.Rule34);
|
||||
|
||||
public static Task<string> GetYandereImageLink(string tag) =>
|
||||
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Yandere);
|
||||
public Task<string> GetYandereImageLink(string tag) =>
|
||||
_service.DapiSearch(tag, DapiSearchType.Yandere);
|
||||
|
||||
public static Task<string> GetKonachanImageLink(string tag) =>
|
||||
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Konachan);
|
||||
public Task<string> GetKonachanImageLink(string tag) =>
|
||||
_service.DapiSearch(tag, DapiSearchType.Konachan);
|
||||
|
||||
public static Task<string> GetGelbooruImageLink(string tag) =>
|
||||
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Gelbooru);
|
||||
public Task<string> GetGelbooruImageLink(string tag) =>
|
||||
_service.DapiSearch(tag, DapiSearchType.Gelbooru);
|
||||
|
||||
public async Task InternalDapiCommand(string tag, Searches.Searches.DapiSearchType type)
|
||||
public async Task InternalDapiCommand(string tag, DapiSearchType type)
|
||||
{
|
||||
tag = tag?.Trim() ?? "";
|
||||
|
||||
var url = await Searches.Searches.InternalDapiSearch(tag, type).ConfigureAwait(false);
|
||||
var url = await _service.DapiSearch(tag, type).ConfigureAwait(false);
|
||||
|
||||
if (url == null)
|
||||
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -16,20 +17,23 @@ namespace NadekoBot.Modules
|
||||
public readonly string ModuleTypeName;
|
||||
public readonly string LowerModuleTypeName;
|
||||
|
||||
|
||||
//todo :thinking:
|
||||
public NadekoStrings _strings { get; set; }
|
||||
public ILocalization _localization { get; set; }
|
||||
|
||||
protected NadekoTopLevelModule(bool isTopLevelModule = true)
|
||||
{
|
||||
//if it's top level module
|
||||
ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
|
||||
LowerModuleTypeName = ModuleTypeName.ToLowerInvariant();
|
||||
|
||||
if (!NadekoBot.ModulePrefixes.TryGetValue(ModuleTypeName, out Prefix))
|
||||
Prefix = "?err?";
|
||||
Prefix = NadekoBot.Prefix;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
protected override void BeforeExecute()
|
||||
{
|
||||
_cultureInfo = NadekoBot.Localization.GetCultureInfo(Context.Guild?.Id);
|
||||
_cultureInfo =_localization.GetCultureInfo(Context.Guild?.Id);
|
||||
|
||||
_log.Info("Culture info is {0}", _cultureInfo);
|
||||
}
|
||||
@@ -53,46 +57,12 @@ namespace NadekoBot.Modules
|
||||
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
|
||||
// return Context.Channel.SendErrorAsync(title, text, url, footer);
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Used as failsafe in case response key doesn't exist in the selected or default language.
|
||||
/// </summary>
|
||||
private static readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
|
||||
|
||||
public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
|
||||
{
|
||||
var text = NadekoBot.Strings.GetString(lowerModuleTypeName + "_" + key, cultureInfo);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
|
||||
text = NadekoBot.Strings.GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
|
||||
lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName,
|
||||
params object[] replacements)
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.Format(GetTextStatic(key, cultureInfo, lowerModuleTypeName), replacements);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return "I can't tell you if the command is executed, because there was an error printing out the response. Key '" +
|
||||
lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected string GetText(string key) =>
|
||||
GetTextStatic(key, _cultureInfo, LowerModuleTypeName);
|
||||
_strings.GetText(key, _cultureInfo, LowerModuleTypeName);
|
||||
|
||||
protected string GetText(string key, params object[] replacements) =>
|
||||
GetTextStatic(key, _cultureInfo, LowerModuleTypeName, replacements);
|
||||
_strings.GetText(key, _cultureInfo, LowerModuleTypeName, replacements);
|
||||
|
||||
public Task<IUserMessage> ErrorLocalized(string textKey, params object[] replacements)
|
||||
{
|
||||
|
@@ -1,27 +0,0 @@
|
||||
namespace NadekoBot.Modules.Permissions
|
||||
{
|
||||
public class PermissionAction
|
||||
{
|
||||
public static PermissionAction Enable => new PermissionAction(true);
|
||||
public static PermissionAction Disable => new PermissionAction(false);
|
||||
|
||||
public bool Value { get; }
|
||||
|
||||
public PermissionAction(bool value)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.Value == ((PermissionAction)obj).Value;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
}
|
||||
}
|
@@ -20,20 +20,6 @@ namespace NadekoBot.Modules.Permissions
|
||||
[NadekoModule("Permissions", ";")]
|
||||
public partial class Permissions : NadekoTopLevelModule
|
||||
{
|
||||
public class OldPermissionCache
|
||||
{
|
||||
public string PermRole { get; set; }
|
||||
public bool Verbose { get; set; } = true;
|
||||
public Permission RootPermission { get; set; }
|
||||
}
|
||||
|
||||
public class PermissionCache
|
||||
{
|
||||
public string PermRole { get; set; }
|
||||
public bool Verbose { get; set; } = true;
|
||||
public PermissionsCollection<Permissionv2> Permissions { get; set; }
|
||||
}
|
||||
|
||||
//guildid, root permission
|
||||
public static ConcurrentDictionary<ulong, PermissionCache> Cache { get; } =
|
||||
new ConcurrentDictionary<ulong, PermissionCache>();
|
||||
|
@@ -762,14 +762,6 @@ namespace NadekoBot.Modules.Searches
|
||||
// }
|
||||
//}
|
||||
|
||||
public enum DapiSearchType
|
||||
{
|
||||
Safebooru,
|
||||
Gelbooru,
|
||||
Konachan,
|
||||
Rule34,
|
||||
Yandere
|
||||
}
|
||||
|
||||
public async Task InternalDapiCommand(IUserMessage umsg, string tag, DapiSearchType type)
|
||||
{
|
||||
@@ -787,55 +779,6 @@ namespace NadekoBot.Modules.Searches
|
||||
.WithImageUrl(url)
|
||||
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<string> InternalDapiSearch(string tag, DapiSearchType type)
|
||||
{
|
||||
tag = tag?.Replace(" ", "_");
|
||||
var website = "";
|
||||
switch (type)
|
||||
{
|
||||
case DapiSearchType.Safebooru:
|
||||
website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Gelbooru:
|
||||
website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Rule34:
|
||||
website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Konachan:
|
||||
website = $"https://konachan.com/post.xml?s=post&q=index&limit=100&tags={tag}";
|
||||
break;
|
||||
case DapiSearchType.Yandere:
|
||||
website = $"https://yande.re/post.xml?limit=100&tags={tag}";
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
var toReturn = await Task.Run(async () =>
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.AddFakeHeaders();
|
||||
var data = await http.GetStreamAsync(website).ConfigureAwait(false);
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(data);
|
||||
|
||||
var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)];
|
||||
|
||||
var url = node.Attributes["file_url"].Value;
|
||||
if (!url.StartsWith("http"))
|
||||
url = "https:" + url;
|
||||
return url;
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
return toReturn;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public async Task<bool> ValidateQuery(IMessageChannel ch, string query)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query)) return true;
|
||||
|
@@ -1,48 +1,32 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
|
||||
{
|
||||
public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
|
||||
|
||||
public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
|
||||
}
|
||||
|
||||
[Group]
|
||||
public class CommandMapCommands : NadekoSubmodule
|
||||
{
|
||||
//guildId, (trigger, mapping)
|
||||
public static ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>();
|
||||
private readonly UtilityService _service;
|
||||
private readonly DbHandler _db;
|
||||
private readonly DiscordShardedClient _client;
|
||||
|
||||
static CommandMapCommands()
|
||||
public CommandMapCommands(UtilityService service, DbHandler db, DiscordShardedClient client)
|
||||
{
|
||||
var eq = new CommandAliasEqualityComparer();
|
||||
AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(
|
||||
NadekoBot.AllGuildConfigs.ToDictionary(
|
||||
x => x.GuildId,
|
||||
x => new ConcurrentDictionary<string, string>(x.CommandAliases
|
||||
.Distinct(eq)
|
||||
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
AliasMaps.Clear();
|
||||
_service = service;
|
||||
_db = db;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@@ -61,14 +45,14 @@ namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
ConcurrentDictionary<string, string> maps;
|
||||
string throwaway;
|
||||
if (!AliasMaps.TryGetValue(Context.Guild.Id, out maps) ||
|
||||
if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) ||
|
||||
!maps.TryRemove(trigger, out throwaway))
|
||||
{
|
||||
await ReplyErrorLocalized("alias_remove_fail", Format.Code(trigger)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias()
|
||||
@@ -83,9 +67,9 @@ namespace NadekoBot.Modules.Utility
|
||||
await ReplyConfirmLocalized("alias_removed", Format.Code(trigger)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
AliasMaps.AddOrUpdate(Context.Guild.Id, (_) =>
|
||||
_service.AliasMaps.AddOrUpdate(Context.Guild.Id, (_) =>
|
||||
{
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
config.CommandAliases.Add(new CommandAlias()
|
||||
@@ -100,7 +84,7 @@ namespace NadekoBot.Modules.Utility
|
||||
});
|
||||
}, (_, map) =>
|
||||
{
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias()
|
||||
@@ -131,7 +115,7 @@ namespace NadekoBot.Modules.Utility
|
||||
return;
|
||||
|
||||
ConcurrentDictionary<string, string> maps;
|
||||
if (!AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any())
|
||||
if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out maps) || !maps.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("aliases_none").ConfigureAwait(false);
|
||||
return;
|
||||
@@ -139,7 +123,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
var arr = maps.ToArray();
|
||||
|
||||
await Context.Channel.SendPaginatedConfirmAsync(page + 1, (curPage) =>
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page + 1, (curPage) =>
|
||||
{
|
||||
return new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("alias_list"))
|
||||
|
@@ -15,60 +15,13 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class CrossServerTextChannel : NadekoSubmodule
|
||||
{
|
||||
static CrossServerTextChannel()
|
||||
private readonly UtilityService _service;
|
||||
|
||||
public CrossServerTextChannel(UtilityService service)
|
||||
{
|
||||
NadekoBot.Client.MessageReceived += Client_MessageReceived;
|
||||
_service = service;
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
|
||||
}
|
||||
|
||||
private static async Task Client_MessageReceived(Discord.WebSocket.SocketMessage imsg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (imsg.Author.IsBot)
|
||||
return;
|
||||
var msg = imsg as IUserMessage;
|
||||
if (msg == null)
|
||||
return;
|
||||
var channel = imsg.Channel as ITextChannel;
|
||||
if (channel == null)
|
||||
return;
|
||||
if (msg.Author.Id == NadekoBot.Client.CurrentUser.Id) return;
|
||||
foreach (var subscriber in Subscribers)
|
||||
{
|
||||
var set = subscriber.Value;
|
||||
if (!set.Contains(channel))
|
||||
continue;
|
||||
foreach (var chan in set.Except(new[] { channel }))
|
||||
{
|
||||
try
|
||||
{
|
||||
await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author,
|
||||
msg)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
|
||||
$"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
|
||||
|
||||
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
|
||||
new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
@@ -76,7 +29,7 @@ namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
var token = new NadekoRandom().Next();
|
||||
var set = new ConcurrentHashSet<ITextChannel>();
|
||||
if (Subscribers.TryAdd(token, set))
|
||||
if (_service.Subscribers.TryAdd(token, set))
|
||||
{
|
||||
set.Add((ITextChannel) Context.Channel);
|
||||
await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString())
|
||||
@@ -90,7 +43,7 @@ namespace NadekoBot.Modules.Utility
|
||||
public async Task Jcsc(int token)
|
||||
{
|
||||
ConcurrentHashSet<ITextChannel> set;
|
||||
if (!Subscribers.TryGetValue(token, out set))
|
||||
if (!_service.Subscribers.TryGetValue(token, out set))
|
||||
return;
|
||||
set.Add((ITextChannel) Context.Channel);
|
||||
await ReplyConfirmLocalized("csc_join").ConfigureAwait(false);
|
||||
@@ -101,7 +54,7 @@ namespace NadekoBot.Modules.Utility
|
||||
[RequireUserPermission(GuildPermission.ManageGuild)]
|
||||
public async Task Lcsc()
|
||||
{
|
||||
foreach (var subscriber in Subscribers)
|
||||
foreach (var subscriber in _service.Subscribers)
|
||||
{
|
||||
subscriber.Value.TryRemove((ITextChannel) Context.Channel);
|
||||
}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -14,6 +16,17 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class InfoCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly DiscordShardedClient _client;
|
||||
private readonly IStatsService _stats;
|
||||
private readonly CommandHandler _ch;
|
||||
|
||||
public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch)
|
||||
{
|
||||
_client = client;
|
||||
_stats = stats;
|
||||
_ch = ch;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ServerInfo(string guildName = null)
|
||||
@@ -24,7 +37,7 @@ namespace NadekoBot.Modules.Utility
|
||||
if (string.IsNullOrWhiteSpace(guildName))
|
||||
guild = channel.Guild;
|
||||
else
|
||||
guild = NadekoBot.Client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
|
||||
guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
|
||||
if (guild == null)
|
||||
return;
|
||||
var ownername = await guild.GetUserAsync(guild.OwnerId);
|
||||
@@ -50,9 +63,9 @@ namespace NadekoBot.Modules.Utility
|
||||
.AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true))
|
||||
.WithImageUrl(guild.IconUrl)
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
if (guild.Emojis.Any())
|
||||
if (guild.Emotes.Any())
|
||||
{
|
||||
embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emojis.Count})").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
|
||||
embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emotes.Count})").WithValue(string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
|
||||
}
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
@@ -101,36 +114,36 @@ namespace NadekoBot.Modules.Utility
|
||||
embed.WithThumbnailUrl(user.RealAvatarUrl());
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Activity(int page = 1)
|
||||
{
|
||||
const int activityPerPage = 15;
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
int startCount = page * activityPerPage;
|
||||
|
||||
StringBuilder str = new StringBuilder();
|
||||
foreach (var kvp in NadekoBot.CommandHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page*activityPerPage).Take(activityPerPage))
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Activity(int page = 1)
|
||||
{
|
||||
str.AppendLine(GetText("activity_line",
|
||||
++startCount,
|
||||
Format.Bold(kvp.Key.ToString()),
|
||||
kvp.Value / NadekoBot.Stats.GetUptime().TotalSeconds, kvp.Value));
|
||||
}
|
||||
const int activityPerPage = 15;
|
||||
page -= 1;
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder()
|
||||
.WithTitle(GetText("activity_page", page + 1))
|
||||
.WithOkColor()
|
||||
.WithFooter(efb => efb.WithText(GetText("activity_users_total",
|
||||
NadekoBot.CommandHandler.UserMessagesSent.Count)))
|
||||
.WithDescription(str.ToString()));
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
int startCount = page * activityPerPage;
|
||||
|
||||
StringBuilder str = new StringBuilder();
|
||||
foreach (var kvp in _ch.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page * activityPerPage).Take(activityPerPage))
|
||||
{
|
||||
str.AppendLine(GetText("activity_line",
|
||||
++startCount,
|
||||
Format.Bold(kvp.Key.ToString()),
|
||||
kvp.Value / _stats.GetUptime().TotalSeconds, kvp.Value));
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder()
|
||||
.WithTitle(GetText("activity_page", page + 1))
|
||||
.WithOkColor()
|
||||
.WithFooter(efb => efb.WithText(GetText("activity_users_total",
|
||||
_ch.UserMessagesSent.Count)))
|
||||
.WithDescription(str.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,19 +1,17 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Modules.Utility.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
@@ -22,140 +20,15 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class RepeatCommands : NadekoSubmodule
|
||||
{
|
||||
//guildid/RepeatRunners
|
||||
public static ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
|
||||
private readonly UtilityService _service;
|
||||
private readonly DiscordShardedClient _client;
|
||||
private readonly DbHandler _db;
|
||||
|
||||
private static bool _ready;
|
||||
|
||||
public class RepeatRunner
|
||||
public RepeatCommands(UtilityService service, DiscordShardedClient client, DbHandler db)
|
||||
{
|
||||
private readonly Logger _log;
|
||||
|
||||
private CancellationTokenSource source { get; set; }
|
||||
private CancellationToken token { get; set; }
|
||||
public Repeater Repeater { get; }
|
||||
public SocketGuild Guild { get; }
|
||||
public ITextChannel Channel { get; private set; }
|
||||
private IUserMessage oldMsg = null;
|
||||
|
||||
public RepeatRunner(Repeater repeater, ITextChannel channel = null)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
Repeater = repeater;
|
||||
Channel = channel;
|
||||
|
||||
Guild = NadekoBot.Client.GetGuild(repeater.GuildId);
|
||||
if(Guild!=null)
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
source = new CancellationTokenSource();
|
||||
token = source.Token;
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
|
||||
|
||||
await Trigger().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Trigger()
|
||||
{
|
||||
var toSend = "🔄 " + Repeater.Message;
|
||||
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
|
||||
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
|
||||
// continue;
|
||||
|
||||
if (oldMsg != null)
|
||||
try
|
||||
{
|
||||
await oldMsg.DeleteAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
try
|
||||
{
|
||||
if (Channel == null)
|
||||
Channel = Guild.GetTextChannel(Repeater.ChannelId);
|
||||
|
||||
if (Channel != null)
|
||||
oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
_log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
_log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
source.Cancel();
|
||||
var _ = Task.Run(Run);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{Channel?.Mention ?? $"⚠<#{Repeater.ChannelId}>" } " +
|
||||
$"| {(int) Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
|
||||
$"| {Repeater.Message.TrimTo(33)}";
|
||||
}
|
||||
|
||||
static RepeatCommands()
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
#if !GLOBAL_NADEKO
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
#else
|
||||
await Task.Delay(30000).ConfigureAwait(false);
|
||||
#endif
|
||||
//todo this is pretty terrible
|
||||
Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(NadekoBot.AllGuildConfigs
|
||||
.ToDictionary(gc => gc.GuildId,
|
||||
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
|
||||
.Select(gr => new RepeatRunner(gr))
|
||||
.Where(x => x.Guild != null))));
|
||||
_ready = true;
|
||||
});
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
_ready = false;
|
||||
foreach (var kvp in Repeaters)
|
||||
{
|
||||
RepeatRunner r;
|
||||
while (kvp.Value.TryDequeue(out r))
|
||||
{
|
||||
r.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
Repeaters.Clear();
|
||||
_service = service;
|
||||
_client = client;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@@ -163,11 +36,10 @@ namespace NadekoBot.Modules.Utility
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task RepeatInvoke(int index)
|
||||
{
|
||||
if (!_ready)
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
index -= 1;
|
||||
ConcurrentQueue<RepeatRunner> rep;
|
||||
if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
|
||||
if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
|
||||
{
|
||||
await ReplyErrorLocalized("repeat_invoke_none").ConfigureAwait(false);
|
||||
return;
|
||||
@@ -193,14 +65,13 @@ namespace NadekoBot.Modules.Utility
|
||||
[Priority(0)]
|
||||
public async Task RepeatRemove(int index)
|
||||
{
|
||||
if (!_ready)
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
if (index < 1)
|
||||
return;
|
||||
index -= 1;
|
||||
|
||||
ConcurrentQueue<RepeatRunner> rep;
|
||||
if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
|
||||
|
||||
if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
|
||||
return;
|
||||
|
||||
var repeaterList = rep.ToList();
|
||||
@@ -215,7 +86,7 @@ namespace NadekoBot.Modules.Utility
|
||||
repeater.Stop();
|
||||
repeaterList.RemoveAt(index);
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var guildConfig = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.GuildRepeaters));
|
||||
|
||||
@@ -223,7 +94,7 @@ namespace NadekoBot.Modules.Utility
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
|
||||
if (_service.Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
|
||||
await Context.Channel.SendConfirmAsync(GetText("message_repeater"),
|
||||
GetText("repeater_stopped", index + 1) + $"\n\n{repeater}").ConfigureAwait(false);
|
||||
}
|
||||
@@ -234,7 +105,7 @@ namespace NadekoBot.Modules.Utility
|
||||
[Priority(1)]
|
||||
public async Task Repeat(int minutes, [Remainder] string message)
|
||||
{
|
||||
if (!_ready)
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
if (minutes < 1 || minutes > 10080)
|
||||
return;
|
||||
@@ -250,7 +121,7 @@ namespace NadekoBot.Modules.Utility
|
||||
Message = message
|
||||
};
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters));
|
||||
|
||||
@@ -261,9 +132,9 @@ namespace NadekoBot.Modules.Utility
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var rep = new RepeatRunner(toAdd, (ITextChannel) Context.Channel);
|
||||
var rep = new RepeatRunner(_client, toAdd, (ITextChannel) Context.Channel);
|
||||
|
||||
Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
|
||||
_service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
|
||||
{
|
||||
old.Enqueue(rep);
|
||||
return old;
|
||||
@@ -282,10 +153,9 @@ namespace NadekoBot.Modules.Utility
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task RepeatList()
|
||||
{
|
||||
if (!_ready)
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
ConcurrentQueue<RepeatRunner> repRunners;
|
||||
if (!Repeaters.TryGetValue(Context.Guild.Id, out repRunners))
|
||||
if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var repRunners))
|
||||
{
|
||||
await ReplyConfirmLocalized("repeaters_none").ConfigureAwait(false);
|
||||
return;
|
||||
|
@@ -22,16 +22,20 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class PatreonCommands : NadekoSubmodule
|
||||
{
|
||||
private static readonly PatreonThingy patreon;
|
||||
//todo rename patreon thingy and move it to be a service, or a part of utility service
|
||||
private readonly PatreonThingy patreon;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly BotConfig _config;
|
||||
private readonly DbHandler _db;
|
||||
private readonly CurrencyHandler _currency;
|
||||
|
||||
static PatreonCommands()
|
||||
public PatreonCommands(IBotCredentials creds, BotConfig config, DbHandler db, CurrencyHandler currency)
|
||||
{
|
||||
patreon = PatreonThingy.Instance;
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
patreon.Updater.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_creds = creds;
|
||||
_config = config;
|
||||
_db = db;
|
||||
_currency = currency;
|
||||
patreon = PatreonThingy.GetInstance(creds, db, currency);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@@ -46,7 +50,7 @@ namespace NadekoBot.Modules.Utility
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClaimPatreonRewards()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.PatreonAccessToken))
|
||||
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
|
||||
return;
|
||||
if (DateTime.UtcNow.Day < 5)
|
||||
{
|
||||
@@ -65,11 +69,11 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
await ReplyConfirmLocalized("clpa_success", amount + NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalized("clpa_success", amount + _config.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var rem = (patreon.Interval - (DateTime.UtcNow - patreon.LastUpdate));
|
||||
var helpcmd = Format.Code(NadekoBot.ModulePrefixes[typeof(Help.Help).Name] + "donate");
|
||||
var helpcmd = Format.Code(NadekoBot.Prefix + "donate");
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithDescription(GetText("clpa_fail"))
|
||||
.AddField(efb => efb.WithName(GetText("clpa_fail_already_title")).WithValue(GetText("clpa_fail_already")))
|
||||
@@ -83,8 +87,10 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
public class PatreonThingy
|
||||
{
|
||||
public static PatreonThingy _instance = new PatreonThingy();
|
||||
public static PatreonThingy Instance => _instance;
|
||||
//todo quickly hacked while rewriting, fix this
|
||||
private static PatreonThingy _instance = null;
|
||||
public static PatreonThingy GetInstance(IBotCredentials creds, DbHandler db, CurrencyHandler cur)
|
||||
=> _instance ?? (_instance = new PatreonThingy(creds, db, cur));
|
||||
|
||||
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
|
||||
|
||||
@@ -96,10 +102,17 @@ namespace NadekoBot.Modules.Utility
|
||||
private readonly Logger _log;
|
||||
|
||||
public readonly TimeSpan Interval = TimeSpan.FromHours(1);
|
||||
private IBotCredentials _creds;
|
||||
private readonly DbHandler _db;
|
||||
private readonly CurrencyHandler _currency;
|
||||
|
||||
private PatreonThingy()
|
||||
static PatreonThingy() { }
|
||||
private PatreonThingy(IBotCredentials creds, DbHandler db, CurrencyHandler currency)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.PatreonAccessToken))
|
||||
_creds = creds;
|
||||
_db = db;
|
||||
_currency = currency;
|
||||
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
|
||||
return;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval);
|
||||
@@ -116,7 +129,7 @@ namespace NadekoBot.Modules.Utility
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + NadekoBot.Credentials.PatreonAccessToken);
|
||||
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
|
||||
var data = new PatreonData()
|
||||
{
|
||||
Links = new PatreonDataLinks()
|
||||
@@ -170,7 +183,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
var amount = data.Reward.attributes.amount_cents;
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var users = uow._context.Set<RewardedUser>();
|
||||
var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id);
|
||||
@@ -185,7 +198,7 @@ namespace NadekoBot.Modules.Utility
|
||||
AmountRewardedThisMonth = amount,
|
||||
});
|
||||
|
||||
await CurrencyHandler.AddCurrencyAsync(userId, "Patreon reward - new", amount, uow).ConfigureAwait(false);
|
||||
await _currency.AddCurrencyAsync(userId, "Patreon reward - new", amount, uow).ConfigureAwait(false);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
return amount;
|
||||
@@ -197,7 +210,7 @@ namespace NadekoBot.Modules.Utility
|
||||
usr.AmountRewardedThisMonth = amount;
|
||||
usr.PatreonUserId = data.User.id;
|
||||
|
||||
await CurrencyHandler.AddCurrencyAsync(userId, "Patreon reward - recurring", amount, uow).ConfigureAwait(false);
|
||||
await _currency.AddCurrencyAsync(userId, "Patreon reward - recurring", amount, uow).ConfigureAwait(false);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
return amount;
|
||||
@@ -211,7 +224,7 @@ namespace NadekoBot.Modules.Utility
|
||||
usr.AmountRewardedThisMonth = amount;
|
||||
usr.PatreonUserId = data.User.id;
|
||||
|
||||
await CurrencyHandler.AddCurrencyAsync(usr.UserId, "Patreon reward - update", toAward, uow).ConfigureAwait(false);
|
||||
await _currency.AddCurrencyAsync(usr.UserId, "Patreon reward - update", toAward, uow).ConfigureAwait(false);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
return toAward;
|
||||
|
@@ -17,6 +17,13 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class QuoteCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly DbHandler _db;
|
||||
|
||||
public QuoteCommands(DbHandler db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ListQuotes(int page = 1)
|
||||
@@ -27,7 +34,7 @@ namespace NadekoBot.Modules.Utility
|
||||
return;
|
||||
|
||||
IEnumerable<Quote> quotes;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
quotes = uow.Quotes.GetGroup(Context.Guild.Id, page * 16, 16);
|
||||
}
|
||||
@@ -50,7 +57,7 @@ namespace NadekoBot.Modules.Utility
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
Quote quote;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
quote =
|
||||
await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false);
|
||||
@@ -87,7 +94,7 @@ namespace NadekoBot.Modules.Utility
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
Quote keywordquote;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
keywordquote =
|
||||
await uow.Quotes.SearchQuoteKeywordTextAsync(Context.Guild.Id, keyword, text)
|
||||
@@ -108,7 +115,7 @@ namespace NadekoBot.Modules.Utility
|
||||
if (id < 0)
|
||||
return;
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var qfromid = uow.Quotes.Get(id);
|
||||
CREmbed crembed;
|
||||
@@ -146,7 +153,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Quotes.Add(new Quote
|
||||
{
|
||||
@@ -169,7 +176,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
var success = false;
|
||||
string response;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var q = uow.Quotes.Get(id);
|
||||
|
||||
@@ -201,7 +208,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Quotes.RemoveAllByKeyword(Context.Guild.Id, keyword.ToUpperInvariant());
|
||||
|
||||
|
@@ -19,89 +19,13 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class RemindCommands : NadekoSubmodule
|
||||
{
|
||||
readonly Regex _regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
private readonly UtilityService _service;
|
||||
private readonly DbHandler _db;
|
||||
|
||||
private static string remindMessageFormat { get; }
|
||||
|
||||
private static readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
|
||||
public RemindCommands(UtilityService service, DbHandler db)
|
||||
{
|
||||
{ "%message%" , (r) => r.Message },
|
||||
{ "%user%", (r) => $"<@!{r.UserId}>" },
|
||||
{ "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
|
||||
};
|
||||
|
||||
private new static readonly Logger _log;
|
||||
private static readonly CancellationTokenSource cancelSource;
|
||||
private static readonly CancellationToken cancelAllToken;
|
||||
|
||||
static RemindCommands()
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
cancelSource = new CancellationTokenSource();
|
||||
cancelAllToken = cancelSource.Token;
|
||||
List<Reminder> reminders;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
reminders = uow.Reminders.GetAll().ToList();
|
||||
}
|
||||
remindMessageFormat = NadekoBot.BotConfig.RemindMessageFormat;
|
||||
|
||||
foreach (var r in reminders)
|
||||
{
|
||||
Task.Run(() => StartReminder(r, cancelAllToken));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
if (!cancelSource.IsCancellationRequested)
|
||||
cancelSource.Cancel();
|
||||
}
|
||||
|
||||
private static async Task StartReminder(Reminder r, CancellationToken t)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
var time = r.When - now;
|
||||
|
||||
if (time.TotalMilliseconds > int.MaxValue)
|
||||
return;
|
||||
|
||||
await Task.Delay(time, t).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
IMessageChannel ch;
|
||||
if (r.IsPrivate)
|
||||
{
|
||||
var user = NadekoBot.Client.GetGuild(r.ServerId).GetUser(r.ChannelId);
|
||||
if(user == null)
|
||||
return;
|
||||
ch = await user.CreateDMChannelAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ch = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
|
||||
}
|
||||
if (ch == null)
|
||||
return;
|
||||
|
||||
await ch.SendMessageAsync(
|
||||
_replacements.Aggregate(remindMessageFormat,
|
||||
(cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
|
||||
.SanitizeMentions()
|
||||
).ConfigureAwait(false); //it works trust me
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
finally
|
||||
{
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
uow.Reminders.Remove(r);
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
}
|
||||
_service = service;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public enum MeOrHere
|
||||
@@ -139,7 +63,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
public async Task RemindInternal(ulong targetId, bool isPrivate, string timeStr, [Remainder] string message)
|
||||
{
|
||||
var m = _regex.Match(timeStr);
|
||||
var m = _service.Remind.Regex.Match(timeStr);
|
||||
|
||||
if (m.Length == 0)
|
||||
{
|
||||
@@ -150,7 +74,7 @@ namespace NadekoBot.Modules.Utility
|
||||
string output = "";
|
||||
var namesAndValues = new Dictionary<string, int>();
|
||||
|
||||
foreach (var groupName in _regex.GetGroupNames())
|
||||
foreach (var groupName in _service.Remind.Regex.GetGroupNames())
|
||||
{
|
||||
if (groupName == "0") continue;
|
||||
int value;
|
||||
@@ -191,7 +115,7 @@ namespace NadekoBot.Modules.Utility
|
||||
ServerId = Context.Guild.Id
|
||||
};
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Reminders.Add(rem);
|
||||
await uow.CompleteAsync();
|
||||
@@ -210,7 +134,7 @@ namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
await StartReminder(rem, cancelAllToken);
|
||||
await _service.Remind.StartReminder(rem);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@@ -220,7 +144,7 @@ namespace NadekoBot.Modules.Utility
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim();
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
|
@@ -2,17 +2,8 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Utility.Commands.Models;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
@@ -22,108 +13,17 @@ namespace NadekoBot.Modules.Utility
|
||||
[Group]
|
||||
public class UnitConverterCommands : NadekoSubmodule
|
||||
{
|
||||
public static List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
|
||||
private new static readonly Logger _log;
|
||||
private static Timer _timer;
|
||||
private static readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
|
||||
private readonly UtilityService _service;
|
||||
|
||||
static UnitConverterCommands()
|
||||
public UnitConverterCommands(UtilityService service)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
try
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
|
||||
{
|
||||
Modifier = u.Modifier,
|
||||
UnitType = u.UnitType,
|
||||
InternalTrigger = string.Join("|", u.Triggers)
|
||||
}).ToArray();
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
if (uow.ConverterUnits.Empty())
|
||||
{
|
||||
uow.ConverterUnits.AddRange(data);
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
Units = data.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Could not load units: " + ex.Message);
|
||||
}
|
||||
|
||||
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
|
||||
_service = service;
|
||||
}
|
||||
|
||||
public static void Unload()
|
||||
{
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
public static async Task UpdateCurrency()
|
||||
{
|
||||
try
|
||||
{
|
||||
var currencyRates = await UpdateCurrencyRates();
|
||||
var unitTypeString = "currency";
|
||||
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
|
||||
{
|
||||
InternalTrigger = u.Key,
|
||||
Modifier = u.Value,
|
||||
UnitType = unitTypeString
|
||||
}).ToArray();
|
||||
var baseType = new ConvertUnit()
|
||||
{
|
||||
Triggers = new[] { currencyRates.Base },
|
||||
Modifier = decimal.One,
|
||||
UnitType = unitTypeString
|
||||
};
|
||||
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
|
||||
uow.ConverterUnits.Add(baseType);
|
||||
uow.ConverterUnits.AddRange(range);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
||||
Units.Add(baseType);
|
||||
Units.AddRange(range);
|
||||
_log.Info("Updated Currency");
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Failed updating currency. Ignore this.");
|
||||
}
|
||||
}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//[RequireContext(ContextType.Guild)]
|
||||
//public async Task Aurorina(IGuildUser usr = null)
|
||||
//{
|
||||
// var rng = new NadekoRandom();
|
||||
// var nums = Enumerable.Range(48, 10)
|
||||
// .Concat(Enumerable.Range(65, 26))
|
||||
// .Concat(Enumerable.Range(97, 26))
|
||||
// .Concat(new[] {45, 46, 95})
|
||||
// .ToArray();
|
||||
|
||||
// var token = String.Concat(new int[59]
|
||||
// .Select(x => (char) nums[rng.Next(0, nums.Length)]));
|
||||
// if (usr == null)
|
||||
// await Context.Channel.SendConfirmAsync(token).ConfigureAwait(false);
|
||||
// else
|
||||
// await Context.Channel.SendConfirmAsync($"Token of user {usr} is `{token}`").ConfigureAwait(false);
|
||||
//}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ConvertList()
|
||||
{
|
||||
var res = Units.GroupBy(x => x.UnitType)
|
||||
var res = _service.Converter.Units.GroupBy(x => x.UnitType)
|
||||
.Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
|
||||
.WithColor(NadekoBot.OkColor),
|
||||
(embed, g) => embed.AddField(efb =>
|
||||
@@ -135,8 +35,8 @@ namespace NadekoBot.Modules.Utility
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Convert(string origin, string target, decimal value)
|
||||
{
|
||||
var originUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
|
||||
var targetUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
|
||||
var originUnit = _service.Converter.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
|
||||
var targetUnit = _service.Converter.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
|
||||
if (originUnit == null || targetUnit == null)
|
||||
{
|
||||
await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false);
|
||||
@@ -189,14 +89,5 @@ namespace NadekoBot.Modules.Utility
|
||||
await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Rates> UpdateCurrencyRates()
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<Rates>(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Commands.Models
|
||||
{
|
||||
public class MeasurementUnit
|
||||
{
|
||||
public List<string> Triggers { get; set; }
|
||||
public string UnitType { get; set; }
|
||||
public decimal Modifier { get; set; }
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Commands.Models
|
||||
{
|
||||
public class Rates
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
[JsonProperty("rates")]
|
||||
public Dictionary<string, decimal> ConversionRates { get; set; }
|
||||
}
|
||||
}
|
114
src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs
Normal file
114
src/NadekoBot/Modules/Utility/Models/RepeatRunner.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Discord;
|
||||
using Discord.Net;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Models
|
||||
{
|
||||
public class RepeatRunner
|
||||
{
|
||||
private readonly Logger _log;
|
||||
|
||||
private CancellationTokenSource source { get; set; }
|
||||
private CancellationToken token { get; set; }
|
||||
public Repeater Repeater { get; }
|
||||
public SocketGuild Guild { get; }
|
||||
public ITextChannel Channel { get; private set; }
|
||||
private IUserMessage oldMsg = null;
|
||||
|
||||
public RepeatRunner(DiscordShardedClient client, Repeater repeater, ITextChannel channel = null)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
Repeater = repeater;
|
||||
Channel = channel;
|
||||
|
||||
//todo @.@ fix all of this
|
||||
Guild = client.GetGuild(repeater.GuildId);
|
||||
if (Guild != null)
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
source = new CancellationTokenSource();
|
||||
token = source.Token;
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(Repeater.Interval, token).ConfigureAwait(false);
|
||||
|
||||
await Trigger().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Trigger()
|
||||
{
|
||||
var toSend = "🔄 " + Repeater.Message;
|
||||
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
|
||||
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
|
||||
// continue;
|
||||
|
||||
if (oldMsg != null)
|
||||
try
|
||||
{
|
||||
await oldMsg.DeleteAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
try
|
||||
{
|
||||
if (Channel == null)
|
||||
Channel = Guild.GetTextChannel(Repeater.ChannelId);
|
||||
|
||||
if (Channel != null)
|
||||
oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
_log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
_log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
source.Cancel();
|
||||
var _ = Task.Run(Run);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{Channel?.Mention ?? $"⚠<#{Repeater.ChannelId}>" } " +
|
||||
$"| {(int)Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
|
||||
$"| {Repeater.Message.TrimTo(33)}";
|
||||
}
|
||||
}
|
@@ -20,15 +20,23 @@ using System.Diagnostics;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
[NadekoModule("Utility", ".")]
|
||||
[NadekoModule("Utility")]
|
||||
public partial class Utility : NadekoTopLevelModule
|
||||
{
|
||||
private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
|
||||
private readonly DiscordShardedClient _client;
|
||||
private readonly IStatsService _stats;
|
||||
private readonly UtilityService _service;
|
||||
//private readonly MusicService _music;
|
||||
private readonly IBotCredentials _creds;
|
||||
|
||||
public static void Unload()
|
||||
public Utility(UtilityService service, DiscordShardedClient client, IStatsService stats, IBotCredentials creds)
|
||||
{
|
||||
_rotatingRoleColors.ForEach(x => x.Value?.Change(Timeout.Infinite, Timeout.Infinite));
|
||||
_rotatingRoleColors.Clear();
|
||||
_client = client;
|
||||
_stats = stats;
|
||||
_service = service;
|
||||
//_music = music;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
@@ -355,11 +363,11 @@ namespace NadekoBot.Modules.Utility
|
||||
if (page < 1)
|
||||
return;
|
||||
|
||||
var status = string.Join(", ", NadekoBot.Client.Shards.GroupBy(x => x.ConnectionState)
|
||||
var status = string.Join(", ", _client.Shards.GroupBy(x => x.ConnectionState)
|
||||
.Select(x => $"{x.Count()} {x.Key}")
|
||||
.ToArray());
|
||||
|
||||
var allShardStrings = NadekoBot.Client.Shards
|
||||
var allShardStrings = _client.Shards
|
||||
.Select(x =>
|
||||
GetText("shard_stats_txt", x.ShardId.ToString(),
|
||||
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString())))
|
||||
@@ -367,7 +375,7 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
|
||||
|
||||
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) =>
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
|
||||
{
|
||||
|
||||
var str = string.Join("\n", allShardStrings.Skip(25 * (curPage - 1)).Take(25));
|
||||
@@ -386,7 +394,7 @@ namespace NadekoBot.Modules.Utility
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ShardId(IGuild guild)
|
||||
{
|
||||
var shardId = NadekoBot.Client.GetShardIdFor(guild);
|
||||
var shardId = _client.GetShardIdFor(guild);
|
||||
|
||||
await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false);
|
||||
}
|
||||
@@ -394,10 +402,8 @@ namespace NadekoBot.Modules.Utility
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Stats()
|
||||
{
|
||||
var stats = NadekoBot.Stats;
|
||||
|
||||
var shardId = Context.Guild != null
|
||||
? NadekoBot.Client.GetShardIdFor(Context.Guild)
|
||||
? _client.GetShardIdFor(Context.Guild)
|
||||
: 0;
|
||||
|
||||
await Context.Channel.EmbedAsync(
|
||||
@@ -405,21 +411,21 @@ namespace NadekoBot.Modules.Utility
|
||||
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
|
||||
.WithUrl("http://nadekobot.readthedocs.io/en/latest/")
|
||||
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
|
||||
.AddField(efb => efb.WithName(GetText("author")).WithValue(stats.Author).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("botid")).WithValue(NadekoBot.Client.CurrentUser.Id.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {NadekoBot.Client.Shards.Count}").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(stats.CommandsRan.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("messages")).WithValue($"{stats.MessageCounter} ({stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{stats.Heap} MB").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("owner_ids")).WithValue(string.Join("\n", NadekoBot.Credentials.OwnerIds)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {_client.Shards.Count}").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("owner_ids")).WithValue(string.Join("\n", _creds.OwnerIds)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
|
||||
GetText("presence_txt",
|
||||
NadekoBot.Client.Guilds.Count, stats.TextChannels, stats.VoiceChannels)).WithIsInline(true))
|
||||
_client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true))
|
||||
#if !GLOBAL_NADEKO
|
||||
.WithFooter(efb => efb.WithText(GetText("stats_songs",
|
||||
NadekoBot.MusicService.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
|
||||
NadekoBot.MusicService.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
|
||||
//.WithFooter(efb => efb.WithText(GetText("stats_songs",
|
||||
// _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
|
||||
// _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
|
||||
#endif
|
||||
);
|
||||
}
|
||||
@@ -427,7 +433,7 @@ namespace NadekoBot.Modules.Utility
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Showemojis([Remainder] string emojis)
|
||||
{
|
||||
var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emoji)t.Value);
|
||||
var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emote)t.Value);
|
||||
|
||||
var result = string.Join("\n", tags.Select(m => GetText("showemojis", m, m.Url)));
|
||||
|
||||
@@ -446,7 +452,7 @@ namespace NadekoBot.Modules.Utility
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
var guilds = await Task.Run(() => NadekoBot.Client.Guilds.OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15)).ConfigureAwait(false);
|
||||
var guilds = await Task.Run(() => _client.Guilds.OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15)).ConfigureAwait(false);
|
||||
|
||||
if (!guilds.Any())
|
||||
{
|
||||
|
319
src/NadekoBot/Modules/Utility/UtilityService.cs
Normal file
319
src/NadekoBot/Modules/Utility/UtilityService.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Utility.Models;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public class UtilityService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>();
|
||||
|
||||
//messagerepeater
|
||||
//guildid/RepeatRunners
|
||||
public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
|
||||
public bool RepeaterReady { get; private set; }
|
||||
|
||||
//remind
|
||||
public RemindService Remind { get; }
|
||||
|
||||
//unit conversion
|
||||
public ConverterService Converter { get; }
|
||||
|
||||
public UtilityService(IEnumerable<GuildConfig> guildConfigs, DiscordShardedClient client, BotConfig config, DbHandler db)
|
||||
{
|
||||
//commandmap
|
||||
AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(
|
||||
guildConfigs.ToDictionary(
|
||||
x => x.GuildId,
|
||||
x => new ConcurrentDictionary<string, string>(x.CommandAliases
|
||||
.Distinct(new CommandAliasEqualityComparer())
|
||||
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
|
||||
|
||||
//crossesrver
|
||||
_client = client;
|
||||
_client.MessageReceived += Client_MessageReceived;
|
||||
|
||||
//messagerepeater
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
#if !GLOBAL_NADEKO
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
#else
|
||||
await Task.Delay(30000).ConfigureAwait(false);
|
||||
#endif
|
||||
//todo this is pretty terrible :kms: no time
|
||||
Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(guildConfigs
|
||||
.ToDictionary(gc => gc.GuildId,
|
||||
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
|
||||
.Select(gr => new RepeatRunner(client, gr))
|
||||
.Where(x => x.Guild != null))));
|
||||
RepeaterReady = true;
|
||||
});
|
||||
|
||||
//reminder
|
||||
Remind = new RemindService(client, config, db);
|
||||
|
||||
//unit converter
|
||||
Converter = new ConverterService(db);
|
||||
}
|
||||
|
||||
private async Task Client_MessageReceived(Discord.WebSocket.SocketMessage imsg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (imsg.Author.IsBot)
|
||||
return;
|
||||
var msg = imsg as IUserMessage;
|
||||
if (msg == null)
|
||||
return;
|
||||
var channel = imsg.Channel as ITextChannel;
|
||||
if (channel == null)
|
||||
return;
|
||||
if (msg.Author.Id == _client.CurrentUser.Id) return;
|
||||
foreach (var subscriber in Subscribers)
|
||||
{
|
||||
var set = subscriber.Value;
|
||||
if (!set.Contains(channel))
|
||||
continue;
|
||||
foreach (var chan in set.Except(new[] { channel }))
|
||||
{
|
||||
try
|
||||
{
|
||||
await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author,
|
||||
msg)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
|
||||
$"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
|
||||
|
||||
public readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
|
||||
new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
|
||||
private DiscordShardedClient _client;
|
||||
}
|
||||
|
||||
public class ConverterService
|
||||
{
|
||||
public class MeasurementUnit
|
||||
{
|
||||
public List<string> Triggers { get; set; }
|
||||
public string UnitType { get; set; }
|
||||
public decimal Modifier { get; set; }
|
||||
}
|
||||
|
||||
public class Rates
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
[JsonProperty("rates")]
|
||||
public Dictionary<string, decimal> ConversionRates { get; set; }
|
||||
}
|
||||
|
||||
public List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
|
||||
private readonly Logger _log;
|
||||
private Timer _timer;
|
||||
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
|
||||
private readonly DbHandler _db;
|
||||
|
||||
public ConverterService(DbHandler db)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_db = db;
|
||||
try
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
|
||||
{
|
||||
Modifier = u.Modifier,
|
||||
UnitType = u.UnitType,
|
||||
InternalTrigger = string.Join("|", u.Triggers)
|
||||
}).ToArray();
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
if (uow.ConverterUnits.Empty())
|
||||
{
|
||||
uow.ConverterUnits.AddRange(data);
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
Units = data.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Could not load units: " + ex.Message);
|
||||
}
|
||||
|
||||
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
|
||||
}
|
||||
|
||||
public static async Task<Rates> UpdateCurrencyRates()
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<Rates>(res);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateCurrency()
|
||||
{
|
||||
try
|
||||
{
|
||||
var currencyRates = await UpdateCurrencyRates();
|
||||
var unitTypeString = "currency";
|
||||
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
|
||||
{
|
||||
InternalTrigger = u.Key,
|
||||
Modifier = u.Value,
|
||||
UnitType = unitTypeString
|
||||
}).ToArray();
|
||||
var baseType = new ConvertUnit()
|
||||
{
|
||||
Triggers = new[] { currencyRates.Base },
|
||||
Modifier = decimal.One,
|
||||
UnitType = unitTypeString
|
||||
};
|
||||
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
|
||||
uow.ConverterUnits.Add(baseType);
|
||||
uow.ConverterUnits.AddRange(range);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
||||
Units.Add(baseType);
|
||||
Units.AddRange(range);
|
||||
_log.Info("Updated Currency");
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Failed updating currency. Ignore this.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class RemindService
|
||||
{
|
||||
public readonly Regex Regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
public string RemindMessageFormat { get; }
|
||||
|
||||
public readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
|
||||
{
|
||||
{ "%message%" , (r) => r.Message },
|
||||
{ "%user%", (r) => $"<@!{r.UserId}>" },
|
||||
{ "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
|
||||
};
|
||||
|
||||
private readonly Logger _log;
|
||||
private readonly CancellationTokenSource cancelSource;
|
||||
private readonly CancellationToken cancelAllToken;
|
||||
private readonly BotConfig _config;
|
||||
private readonly DiscordShardedClient _client;
|
||||
private readonly DbHandler _db;
|
||||
|
||||
public RemindService(DiscordShardedClient client, BotConfig config, DbHandler db)
|
||||
{
|
||||
_config = config;
|
||||
_client = client;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_db = db;
|
||||
|
||||
cancelSource = new CancellationTokenSource();
|
||||
cancelAllToken = cancelSource.Token;
|
||||
List<Reminder> reminders;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
reminders = uow.Reminders.GetAll().ToList();
|
||||
}
|
||||
RemindMessageFormat = _config.RemindMessageFormat;
|
||||
|
||||
foreach (var r in reminders)
|
||||
{
|
||||
Task.Run(() => StartReminder(r));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartReminder(Reminder r)
|
||||
{
|
||||
var t = cancelAllToken;
|
||||
var now = DateTime.Now;
|
||||
|
||||
var time = r.When - now;
|
||||
|
||||
if (time.TotalMilliseconds > int.MaxValue)
|
||||
return;
|
||||
|
||||
await Task.Delay(time, t).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
IMessageChannel ch;
|
||||
if (r.IsPrivate)
|
||||
{
|
||||
var user = _client.GetGuild(r.ServerId).GetUser(r.ChannelId);
|
||||
if (user == null)
|
||||
return;
|
||||
ch = await user.CreateDMChannelAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
|
||||
}
|
||||
if (ch == null)
|
||||
return;
|
||||
|
||||
await ch.SendMessageAsync(
|
||||
_replacements.Aggregate(RemindMessageFormat,
|
||||
(cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
|
||||
.SanitizeMentions()
|
||||
).ConfigureAwait(false); //it works trust me
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
finally
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Reminders.Remove(r);
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
|
||||
{
|
||||
public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
|
||||
|
||||
public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user