Compare commits

..

23 Commits
release ... 1.4

Author SHA1 Message Date
ce783c3f75
Merge pull request #4 from mattburchett/revert-3-1.9
Revert "1.9 2017/11/30"
2017-11-30 19:16:55 -06:00
0589728f58
Revert "1.9 2017/11/30" 2017-11-30 19:16:48 -06:00
be9352bc03
Merge pull request #3 from mattburchett/1.9
1.9 2017/11/30
2017-11-30 19:15:58 -06:00
af26fcfa5f Merge pull request #2 from mattburchett/weather2
Weather2
2017-09-21 10:00:47 -07:00
Matt
7a73869a9f Changing commas to slashes 2017-09-21 10:23:10 -05:00
Matt
b1987be30c Fixing again. 2017-09-21 10:17:02 -05:00
Matt
cdd5928028 Adding a line break, maybe 2017-09-21 09:55:15 -05:00
c3a93b17dc Merge pull request #1 from mattburchett/weather2
Weather2
2017-09-20 19:27:57 -07:00
unknown
55c4691933 Fixing 2017-09-20 21:27:14 -05:00
unknown
cf2532af9a Typo 2017-09-20 21:27:03 -05:00
unknown
89a6adb389 Fixing typo. 2017-09-20 21:19:43 -05:00
unknown
b66ba51e0d Fixing? 2017-09-20 21:09:39 -05:00
unknown
f20ccd01f4 Fixing? 2017-09-20 21:06:44 -05:00
unknown
8842a86b30 Adding Weather Changes to include Fehrenheit as well as Celsius? 2017-09-20 20:32:25 -05:00
Master Kwoth
0234df0844 Merge pull request #1602 from shivaco/patch-2
Re-phrase, etc.
2017-09-16 21:10:09 +02:00
shivaco
68a92426c2 Re-phrased, added gif for using .permrole, etc. 2017-09-16 21:28:45 +06:00
shivaco
f5eaa4f335 Totally not noob at Markdowning 2017-09-16 21:23:56 +06:00
Master Kwoth
7923c32323 Merge pull request #1601 from shivaco/patch-2
Markdown edits and re-phrase
2017-09-16 10:36:03 +02:00
shivaco
cf5756e0a8 Some edits in Markdown and a bit of re-phrase 2017-09-16 14:12:53 +06:00
Master Kwoth
51f9ae9e3b Merge pull request #1593 from shivaco/patch-3
Fixed the mistype + added hyperlink
2017-09-15 19:38:19 +02:00
Master Kwoth
eef5ad0c36 Merge pull request #1598 from numbermaniac/1.4
FAQ.md: Grammar/spelling and sentence structure
2017-09-15 19:27:38 +02:00
numbermaniac
c3755055a1 FAQ.md: Grammar/spelling and sentence structure 2017-09-15 19:42:26 +10:00
shivaco
494d8405b8 Fixed the mistype + added hyperlink 2017-09-13 23:19:07 +06:00
596 changed files with 26433 additions and 49456 deletions

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Common.Attributes
{
public class NadekoOptions : Attribute
{
public Type OptionType { get; set; }
public NadekoOptions(Type t)
{
this.OptionType = t;
}
}
}

View File

@ -1,23 +0,0 @@
using System.Runtime.CompilerServices;
using Discord.Commands;
using NadekoBot.Core.Services.Impl;
using System.Linq;
using Discord;
namespace NadekoBot.Common.Attributes
{
public class Usage : RemarksAttribute
{
public Usage([CallerMemberName] string memberName="") : base(Usage.GetUsage(memberName))
{
}
public static string GetUsage(string memberName)
{
var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
return string.Join(" or ", usage
.Select(x => Format.Code(x)));
}
}
}

View File

@ -1,49 +0,0 @@
using System;
namespace NadekoBot.Core.Common.Caching
{
/// <summary>
/// A caching object which loads its value with a factory method when it expires.
/// </summary>
/// <typeparam name="T">Type of the value which is cached.</typeparam>
public class FactoryCache<T> : IFactoryCache
{
public DateTime LastUpdate { get; set; } = DateTime.MinValue;
private readonly object _locker = new object();
private TimeSpan _expireAfter;
private readonly Func<T> _factory;
private T Value;
/// <summary>
/// Creates a new factory cache object.
/// </summary>
/// <param name="factory">Method which loads the value when it expires or if it's not loaded the first time.</param>
/// <param name="expireAfter">Time after which the value will be reloaded.</param>
/// <param name="loadImmediately">Should the value be loaded right away. If set to false, value will load when it's first retrieved.</param>
public FactoryCache(Func<T> factory, TimeSpan expireAfter,
bool loadImmediately = false)
{
_expireAfter = expireAfter;
_factory = factory;
if (loadImmediately)
{
Value = _factory();
LastUpdate = DateTime.UtcNow;
}
}
public T GetValue()
{
lock (_locker)
{
if (DateTime.UtcNow - LastUpdate > _expireAfter)
{
LastUpdate = DateTime.UtcNow;
return Value = _factory();
}
return Value;
}
}
}
}

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Core.Common.Caching
{
public interface IFactoryCache
{
}
}

View File

@ -1,9 +0,0 @@
namespace NadekoBot.Common
{
public class CommandData
{
public string Cmd { get; set; }
public string Desc { get; set; }
public string[] Usage { get; set; }
}
}

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Core.Common
{
public interface INadekoCommandOptions
{
void NormalizeOptions();
}
}

View File

@ -1,20 +0,0 @@
using CommandLine;
namespace NadekoBot.Core.Common
{
public class OptionsParser
{
private static OptionsParser _instance = new OptionsParser();
public static OptionsParser Default => _instance;
static OptionsParser() { }
public T ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
{
var res = Parser.Default.ParseArguments<T>(args);
options = (T)res.MapResult(x => x, x => options);
options.NormalizeOptions();
return options;
}
}
}

View File

@ -1,8 +0,0 @@
namespace NadekoBot.Core.Common.Pokemon
{
public class PokemonNameId
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -1,10 +0,0 @@
namespace NadekoBot.Core.Common.Pokemon
{
public class SearchPokemonAbility
{
public string Desc { get; set; }
public string ShortDesc { get; set; }
public string Name { get; set; }
public float Rating { get; set; }
}
}

View File

@ -1,31 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NadekoBot.Core.Services;
namespace NadekoBot.Common.ShardCom
{
public class ShardComServer
{
private readonly IDataCache _cache;
public ShardComServer(IDataCache cache)
{
_cache = cache;
}
public void Start()
{
var sub = _cache.Redis.GetSubscriber();
sub.SubscribeAsync("shardcoord_send", (ch, data) =>
{
var _ = OnDataReceived(JsonConvert.DeserializeObject<ShardComMessage>(data));
}, StackExchange.Redis.CommandFlags.FireAndForget);
}
public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; };
}
}

View File

@ -1,19 +0,0 @@
using Discord.Commands;
using Discord.WebSocket;
namespace NadekoBot.Core.Common.TypeReaders
{
public abstract class NadekoTypeReader<T> : TypeReader where
T : class
{
private readonly DiscordSocketClient _client;
private readonly CommandService _cmds;
private NadekoTypeReader() { }
public NadekoTypeReader(DiscordSocketClient client, CommandService cmds)
{
_client = client;
_cmds = cmds;
}
}
}

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class totalxp : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "TotalXp",
table: "DiscordUser",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql(MigrationQueries.TotalXp);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TotalXp",
table: "DiscordUser");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class clubadmins : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsClubAdmin",
table: "DiscordUser",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsClubAdmin",
table: "DiscordUser");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class feeds : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "FeedSub",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
Url = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedSub", x => x.Id);
table.UniqueConstraint("AK_FeedSub_GuildConfigId_Url", x => new { x.GuildConfigId, x.Url });
table.ForeignKey(
name: "FK_FeedSub_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "FeedSub");
migrationBuilder.AlterColumn<DateTime>(
name: "LastLevelUp",
table: "UserXpStats",
nullable: false,
defaultValue: new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local),
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldDefaultValue: new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local));
migrationBuilder.AlterColumn<DateTime>(
name: "LastLevelUp",
table: "DiscordUser",
nullable: false,
defaultValue: new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local),
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldDefaultValue: new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class xprrfix : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable("XpRoleReward");
migrationBuilder.CreateTable(
name: "XpRoleReward",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Level = table.Column<int>(type: "INTEGER", nullable: false),
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
XpSettingsId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpRoleReward", x => x.Id);
table.ForeignKey(
name: "FK_XpRoleReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_XpRoleReward_XpSettingsId_Level",
table: "XpRoleReward",
columns: new[] { "XpSettingsId", "Level" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class clearandloadedpackage : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ClashCallers");
migrationBuilder.DropTable(
name: "ModulePrefixes");
migrationBuilder.DropTable(
name: "ClashOfClans");
migrationBuilder.CreateTable(
name: "LoadedPackages",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BotConfigId = table.Column<int>(type: "INTEGER", nullable: true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_LoadedPackages", x => x.Id);
table.ForeignKey(
name: "FK_LoadedPackages_BotConfig_BotConfigId",
column: x => x.BotConfigId,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_LoadedPackages_BotConfigId",
table: "LoadedPackages",
column: "BotConfigId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "LoadedPackages");
migrationBuilder.CreateTable(
name: "ClashOfClans",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<ulong>(nullable: false),
DateAdded = table.Column<DateTime>(nullable: true),
EnemyClan = table.Column<string>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
Size = table.Column<int>(nullable: false),
StartedAt = table.Column<DateTime>(nullable: false),
WarState = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClashOfClans", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ModulePrefixes",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BotConfigId = table.Column<int>(nullable: true),
DateAdded = table.Column<DateTime>(nullable: true),
ModuleName = table.Column<string>(nullable: true),
Prefix = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ModulePrefixes", x => x.Id);
table.ForeignKey(
name: "FK_ModulePrefixes_BotConfig_BotConfigId",
column: x => x.BotConfigId,
principalTable: "BotConfig",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ClashCallers",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BaseDestroyed = table.Column<bool>(nullable: false),
CallUser = table.Column<string>(nullable: true),
ClashWarId = table.Column<int>(nullable: false),
DateAdded = table.Column<DateTime>(nullable: true),
SequenceNumber = table.Column<int>(nullable: true),
Stars = table.Column<int>(nullable: false),
TimeAdded = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClashCallers", x => x.Id);
table.ForeignKey(
name: "FK_ClashCallers_ClashOfClans_ClashWarId",
column: x => x.ClashWarId,
principalTable: "ClashOfClans",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ClashCallers_ClashWarId",
table: "ClashCallers",
column: "ClashWarId");
migrationBuilder.CreateIndex(
name: "IX_ModulePrefixes_BotConfigId",
table: "ModulePrefixes",
column: "BotConfigId");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class removeconvertunits : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ConversionUnits");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ConversionUnits",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
InternalTrigger = table.Column<string>(nullable: true),
Modifier = table.Column<decimal>(nullable: false),
UnitType = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ConversionUnits", x => x.Id);
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class tesargrouping : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Group",
table: "SelfAssignableRoles",
type: "INTEGER",
nullable: false,
defaultValue: 0)
.Annotation("Sqlite:Autoincrement", true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Group",
table: "SelfAssignableRoles");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class vcautodc : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "AutoDcFromVc",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AutoDcFromVc",
table: "GuildConfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class timely : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "TimelyCurrency",
table: "BotConfig",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "TimelyCurrencyPeriod",
table: "BotConfig",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TimelyCurrency",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "TimelyCurrencyPeriod",
table: "BotConfig");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,100 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class pollrewrite : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Poll",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
Question = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Poll", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PollAnswer",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Index = table.Column<int>(type: "INTEGER", nullable: false),
PollId = table.Column<int>(type: "INTEGER", nullable: true),
Text = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PollAnswer", x => x.Id);
table.ForeignKey(
name: "FK_PollAnswer_Poll_PollId",
column: x => x.PollId,
principalTable: "Poll",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "PollVote",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
PollId = table.Column<int>(type: "INTEGER", nullable: true),
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
VoteIndex = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PollVote", x => x.Id);
table.ForeignKey(
name: "FK_PollVote_Poll_PollId",
column: x => x.PollId,
principalTable: "Poll",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Poll_GuildId",
table: "Poll",
column: "GuildId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PollAnswer_PollId",
table: "PollAnswer",
column: "PollId");
migrationBuilder.CreateIndex(
name: "IX_PollVote_PollId",
table: "PollVote",
column: "PollId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PollAnswer");
migrationBuilder.DropTable(
name: "PollVote");
migrationBuilder.DropTable(
name: "Poll");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class sarlevelreq : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "LevelRequirement",
table: "SelfAssignableRoles",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LevelRequirement",
table: "SelfAssignableRoles");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class currencylevelupreward : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "XpCurrencyReward",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Amount = table.Column<int>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Level = table.Column<int>(type: "INTEGER", nullable: false),
XpSettingsId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpCurrencyReward", x => x.Id);
table.ForeignKey(
name: "FK_XpCurrencyReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_XpCurrencyReward_XpSettingsId",
table: "XpCurrencyReward",
column: "XpSettingsId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "XpCurrencyReward");
}
}
}

View File

@ -1,46 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class persistsadandmusicchannel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "MusicSettings",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
MusicChannelId = table.Column<ulong>(type: "INTEGER", nullable: true),
SongAutoDelete = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MusicSettings", x => x.Id);
table.ForeignKey(
name: "FK_MusicSettings_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_MusicSettings_GuildConfigId",
table: "MusicSettings",
column: "GuildConfigId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "MusicSettings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +0,0 @@
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using System;
using System.Threading.Tasks;
using Discord;
#if !GLOBAL_NADEKO
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
[OwnerOnly]
public class DangerousCommands : NadekoSubmodule
{
private readonly DbService _db;
public DangerousCommands(DbService db)
{
_db = db;
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ExecSql([Remainder]string sql)
{
try
{
var msg = await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle(GetText("sql_confirm_exec"))
.WithDescription(Format.Code(sql))
.WithFooter("yes/no")).ConfigureAwait(false);
var input = await GetUserInputAsync(Context.User.Id, Context.Channel.Id);
input = input?.ToLowerInvariant().ToString();
if (input != "yes" && input != "y")
{
return;
}
var _ = msg.DeleteAsync();
int res;
using (var uow = _db.UnitOfWork)
{
res = uow._context.Database.ExecuteSqlCommand(sql);
}
await Context.Channel.SendConfirmAsync(res.ToString());
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync(ex.ToString());
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteWaifus() =>
ExecSql(@"DELETE FROM WaifuUpdates;
DELETE FROM WaifuItem;
DELETE FROM WaifuInfo;");
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteCurrency() =>
ExecSql("DELETE FROM Currency; DELETE FROM CurrencyTransactions;");
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeletePlaylists() =>
ExecSql("DELETE FROM MusicPlaylists;");
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteExp() =>
ExecSql(@"DELETE FROM UserXpStats;
UPDATE DiscordUser
SET ClubId=NULL,
IsClubAdmin=0,
TotalXp=0;
DELETE FROM ClubApplicants;
DELETE FROM ClubBans;
DELETE FROM Clubs;");
}
}
}
#endif

View File

@ -1,91 +0,0 @@
//using Discord.Commands;
//using NadekoBot.Common.Attributes;
//using NadekoBot.Modules.Administration.Services;
//using NadekoBot.Extensions;
//using System;
//using System.IO;
//using System.Reflection;
//using System.Text.RegularExpressions;
//using System.Threading.Tasks;
//using System.Linq;
//namespace NadekoBot.Modules.Administration
//{
// public partial class Administration
// {
// [Group]
// public class PackagesCommands : NadekoSubmodule<PackagesService>
// {
// private readonly NadekoBot _bot;
// public PackagesCommands(NadekoBot bot)
// {
// _bot = bot;
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// public async Task PackageList()
// {
// _service.ReloadAvailablePackages();
// await Context.Channel.SendConfirmAsync(
// string.Join(
// "\n",
// _service.Packages
// .Select(x => _bot.LoadedPackages.Contains(x)
// ? "【✘】" + x
// : "【 】" + x)));
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task PackageUnload(string name)
// {
// if (name.Contains(":") || name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
// return;
// name = name.ToTitleCase();
// var package = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory,
// "modules",
// $"NadekoBot.Modules.{name}",
// $"NadekoBot.Modules.{name}.dll"));
// await _bot.UnloadPackage(name).ConfigureAwait(false);
// await ReplyAsync(":ok:");
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task PackageLoad(string name)
// {
// if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
// return;
// name = name.ToTitleCase();
// if (await _bot.LoadPackage(name))
// await ReplyAsync(":ok:");
// else
// await ReplyAsync(":x:");
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task PackageReload(string name)
// {
// if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
// return;
// name = name.ToTitleCase();
// if (await _bot.UnloadPackage(name))
// {
// await _bot.LoadPackage(name);
// await ReplyAsync(":ok:");
// }
// else
// await ReplyAsync(":x:");
// }
// }
// }
//}

View File

@ -1,99 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Core.Services;
using NLog;
using Discord;
namespace NadekoBot.Modules.Administration.Services
{
public class AutoAssignRoleService : INService
{
private readonly Logger _log;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
//guildid/roleid
public ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
public BlockingCollection<(IGuildUser, ulong)> AutoAssignQueue { get; } = new BlockingCollection<(IGuildUser, ulong)>();
public AutoAssignRoleService(DiscordSocketClient client, NadekoBot bot, DbService db)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
_db = db;
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(
bot.AllGuildConfigs
.Where(x => x.AutoAssignRoleId != 0)
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
var _queueRunner = Task.Run(async () =>
{
while (true)
{
var (user, roleId) = AutoAssignQueue.Take();
try
{
var role = user.Guild.Roles.FirstOrDefault(r => r.Id == roleId);
if (role != null)
await user.AddRoleAsync(role).ConfigureAwait(false);
else
{
_log.Warn($"Disabled 'Auto assign role' feature on {0} server the role doesn't exist.",
roleId);
DisableAar(user.GuildId);
}
}
catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{
_log.Warn($"Disabled 'Auto assign role' feature on {0} server because I don't have role management permissions.",
roleId);
DisableAar(user.GuildId);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
});
_client.UserJoined += (user) =>
{
if (AutoAssignedRoles.TryGetValue(user.Guild.Id, out ulong roleId)
&& roleId != 0)
{
AutoAssignQueue.Add((user, roleId));
}
return Task.CompletedTask;
};
}
public void EnableAar(ulong guildId, ulong roleId)
{
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set);
gc.AutoAssignRoleId = roleId;
uow.Complete();
}
AutoAssignedRoles.AddOrUpdate(guildId,
roleId,
delegate { return roleId; });
}
public void DisableAar(ulong guildId)
{
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set);
gc.AutoAssignRoleId = 0;
uow.Complete();
}
AutoAssignedRoles.TryRemove(guildId, out _);
}
}
}

View File

@ -1,31 +0,0 @@
//using NadekoBot.Core.Services;
//using System;
//using System.Collections.Generic;
//using System.IO;
//using System.Linq;
//using System.Text.RegularExpressions;
//namespace NadekoBot.Modules.Administration.Services
//{
// public class PackagesService : INService
// {
// public IEnumerable<string> Packages { get; private set; }
// public PackagesService()
// {
// ReloadAvailablePackages();
// }
// public void ReloadAvailablePackages()
// {
// Packages = Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "modules\\"), "NadekoBot.Modules.*", SearchOption.AllDirectories)
// .SelectMany(x => Directory.GetFiles(x, "NadekoBot.Modules.*.dll"))
// .Select(x => Path.GetFileNameWithoutExtension(x))
// .Select(x =>
// {
// var m = Regex.Match(x, @"NadekoBot\.Modules\.(?<name>.*)");
// return m.Groups["name"].Value;
// });
// }
// }
//}

View File

@ -1,85 +0,0 @@
using System;
using System.Linq;
using System.Threading;
using Discord.WebSocket;
using NadekoBot.Common.Replacements;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NLog;
using NadekoBot.Modules.Music.Services;
namespace NadekoBot.Modules.Administration.Services
{
public class PlayingRotateService : INService
{
private readonly Timer _t;
private readonly DiscordSocketClient _client;
private readonly Logger _log;
private readonly IDataCache _cache;
private readonly Replacer _rep;
private readonly DbService _db;
private readonly IBotConfigProvider _bcp;
public BotConfig BotConfig => _bcp.BotConfig;
private class TimerState
{
public int Index { get; set; }
}
public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp,
DbService db, IDataCache cache, NadekoBot bot, MusicService music)
{
_client = client;
_bcp = bcp;
_db = db;
_log = LogManager.GetCurrentClassLogger();
_cache = cache;
if (client.ShardId == 0)
{
_rep = new ReplacementBuilder()
.WithClient(client)
.WithStats(client)
.WithMusic(music)
.Build();
_t = new Timer(async (objState) =>
{
try
{
bcp.Reload();
var state = (TimerState)objState;
if (!BotConfig.RotatingStatuses)
return;
if (state.Index >= BotConfig.RotatingStatusMessages.Count)
state.Index = 0;
if (!BotConfig.RotatingStatusMessages.Any())
return;
var status = BotConfig.RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status))
return;
status = _rep.Replace(status);
try
{
await bot.SetGameAsync(status).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
catch (Exception ex)
{
_log.Warn("Rotating playing status errored.\n" + ex);
}
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
}
}
}

View File

@ -1,17 +0,0 @@
using CommandLine;
using NadekoBot.Core.Common;
namespace NadekoBot.Core.Modules.Gambling.Common.AnimalRacing
{
public class RaceOptions : INadekoCommandOptions
{
[Option('s', "start-time", Default = 20, Required = false)]
public int StartTime { get; set; } = 20;
public void NormalizeOptions()
{
if (this.StartTime < 10 || this.StartTime > 120)
this.StartTime = 20;
}
}
}

View File

@ -1,12 +0,0 @@
using Discord;
using Discord.Commands;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
{
public abstract class CurrencyEvent
{
public abstract Task Stop();
public abstract Task Start(IUserMessage msg, ICommandContext channel);
}
}

View File

@ -1,144 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
{
public class ReactionEvent : CurrencyEvent
{
private readonly ConcurrentHashSet<ulong> _reactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly BotConfig _bc;
private readonly Logger _log;
private readonly DiscordSocketClient _client;
private readonly CurrencyService _cs;
private readonly SocketSelfUser _botUser;
private IUserMessage StartingMessage { get; set; }
private CancellationTokenSource Source { get; }
private CancellationToken CancelToken { get; }
private readonly ConcurrentQueue<ulong> _toGiveTo = new ConcurrentQueue<ulong>();
private readonly int _amount;
public ReactionEvent(BotConfig bc, DiscordSocketClient client, CurrencyService cs, int amount)
{
_bc = bc;
_log = LogManager.GetCurrentClassLogger();
_client = client;
_cs = cs;
_botUser = client.CurrentUser;
_amount = amount;
Source = new CancellationTokenSource();
CancelToken = Source.Token;
var _ = Task.Run(async () =>
{
var users = new List<ulong>();
while (!CancelToken.IsCancellationRequested)
{
await Task.Delay(1000).ConfigureAwait(false);
while (_toGiveTo.TryDequeue(out var usrId))
{
users.Add(usrId);
}
if (users.Count > 0)
{
await _cs.AddToManyAsync("Reaction Event", _amount, users.ToArray()).ConfigureAwait(false);
}
users.Clear();
}
}, CancelToken);
}
public override async Task Stop()
{
if (StartingMessage != null)
await StartingMessage.DeleteAsync().ConfigureAwait(false);
if (!Source.IsCancellationRequested)
Source.Cancel();
_client.MessageDeleted -= MessageDeletedEventHandler;
}
private Task MessageDeletedEventHandler(Cacheable<IMessage, ulong> msg, ISocketMessageChannel channel)
{
if (StartingMessage?.Id == msg.Id)
{
_log.Warn("Stopping flower reaction event because message is deleted.");
var __ = Task.Run(Stop);
}
return Task.CompletedTask;
}
public override async Task Start(IUserMessage umsg, ICommandContext context)
{
StartingMessage = umsg;
_client.MessageDeleted += MessageDeletedEventHandler;
IEmote iemote;
if (Emote.TryParse(_bc.CurrencySign, out var emote))
{
iemote = emote;
}
else
iemote = new Emoji(_bc.CurrencySign);
try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); }
catch
{
try { await StartingMessage.AddReactionAsync(iemote).ConfigureAwait(false); }
catch
{
try { await StartingMessage.DeleteAsync().ConfigureAwait(false); }
catch { return; }
}
}
using (StartingMessage.OnReaction(_client, (r) =>
{
try
{
if (r.UserId == _botUser.Id)
return;
if (r.Emote.Name == iemote.Name && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _reactionAwardedUsers.Add(r.User.Value.Id))
{
_toGiveTo.Enqueue(r.UserId);
}
}
catch
{
// ignored
}
}))
{
try
{
await Task.Delay(TimeSpan.FromHours(24), CancelToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
if (CancelToken.IsCancellationRequested)
return;
_log.Warn("Stopping flower reaction event because it expired.");
await Stop();
}
}
}
}

View File

@ -1,99 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Collections;
using NadekoBot.Core.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common.CurrencyEvents
{
public class SneakyEvent : CurrencyEvent
{
public event Action OnEnded;
public string Code { get; private set; } = string.Empty;
private readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
.Concat(Enumerable.Range(65, 26))
.Concat(Enumerable.Range(97, 26))
.Select(x => (char)x)
.ToArray();
private readonly CurrencyService _cs;
private readonly DiscordSocketClient _client;
private readonly IBotConfigProvider _bc;
private readonly int _length;
public SneakyEvent(CurrencyService cs, DiscordSocketClient client,
IBotConfigProvider bc, int len)
{
_cs = cs;
_client = client;
_bc = bc;
_length = len;
}
public override async Task Start(IUserMessage msg, ICommandContext channel)
{
GenerateCode();
//start the event
_client.MessageReceived += SneakyGameMessageReceivedEventHandler;
await _client.SetGameAsync($"type {Code} for " + _bc.BotConfig.CurrencyPluralName)
.ConfigureAwait(false);
await Task.Delay(_length * 1000).ConfigureAwait(false);
await Stop().ConfigureAwait(false);
}
private void GenerateCode()
{
var rng = new NadekoRandom();
for (var i = 0; i < 5; i++)
{
Code += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)];
}
}
public override async Task Stop()
{
try
{
_client.MessageReceived -= SneakyGameMessageReceivedEventHandler;
Code = string.Empty;
_sneakyGameAwardedUsers.Clear();
await _client.SetGameAsync(null).ConfigureAwait(false);
}
catch { }
finally
{
OnEnded?.Invoke();
}
}
private Task SneakyGameMessageReceivedEventHandler(SocketMessage arg)
{
if (arg.Content == Code &&
_sneakyGameAwardedUsers.Add(arg.Author.Id))
{
var _ = Task.Run(async () =>
{
await _cs.AddAsync(arg.Author, "Sneaky Game Event", 100, false)
.ConfigureAwait(false);
try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); }
catch
{
// ignored
}
});
}
return Task.CompletedTask;
}
}
}

View File

@ -1,85 +0,0 @@
using Discord;
using NadekoBot.Common;
using NLog;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Core.Modules.Gambling.Common
{
public class CurrencyRaffleGame
{
public enum Type {
Mixed,
Normal
}
public class User
{
public IUser DiscordUser { get; set; }
public int Amount { get; set; }
public override int GetHashCode()
{
return DiscordUser.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is User u
? u.DiscordUser == DiscordUser
: false;
}
}
private readonly HashSet<User> _users = new HashSet<User>();
public IEnumerable<User> Users => _users;
public Type GameType { get; }
private readonly Logger _log;
public CurrencyRaffleGame(Type type)
{
GameType = type;
_log = LogManager.GetCurrentClassLogger();
}
public bool AddUser(IUser usr, int amount)
{
// if game type is normal, and someone already joined the game
// (that's the user who created it)
if (GameType == Type.Normal && _users.Count > 0 &&
_users.First().Amount != amount)
return false;
if (!_users.Add(new User
{
DiscordUser = usr,
Amount = amount,
}))
{
return false;
}
return true;
}
public User GetWinner()
{
var rng = new NadekoRandom();
if (GameType == Type.Mixed)
{
var num = rng.Next(0, Users.Sum(x => x.Amount));
var sum = 0;
foreach (var u in Users)
{
sum += u.Amount;
if (sum > num)
return u;
}
_log.Error("Woah. Report this.\nRoll: {0}\nAmounts: {1}", num, string.Join(",", Users.Select(x => x.Amount)));
}
var usrs = _users.ToArray();
return usrs[rng.Next(0, usrs.Length)];
}
}
}

View File

@ -1,91 +0,0 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common.CurrencyEvents;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class CurrencyEventsCommands : NadekoSubmodule<CurrencyEventsService>
{
public enum CurrencyEvent
{
Reaction,
SneakyGameStatus
}
private readonly DiscordSocketClient _client;
private readonly IBotConfigProvider _bc;
private readonly CurrencyService _cs;
public CurrencyEventsCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs)
{
_client = client;
_bc = bc;
_cs = cs;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartEvent(CurrencyEvent e, int arg = -1)
{
switch (e)
{
case CurrencyEvent.Reaction:
await ReactionEvent(Context, arg).ConfigureAwait(false);
break;
case CurrencyEvent.SneakyGameStatus:
await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false);
break;
}
}
private async Task SneakyGameStatusEvent(ICommandContext context, int num)
{
if (num < 10 || num > 600)
num = 60;
var ev = new SneakyEvent(_cs, _client, _bc, num);
if (!await _service.StartSneakyEvent(ev, context.Message, context))
return;
try
{
var title = GetText("sneakygamestatus_title");
var desc = GetText("sneakygamestatus_desc",
Format.Bold(100.ToString()) + _bc.BotConfig.CurrencySign,
Format.Bold(num.ToString()));
await context.Channel.SendConfirmAsync(title, desc)
.ConfigureAwait(false);
}
catch
{
// ignored
}
}
public async Task ReactionEvent(ICommandContext context, int amount)
{
if (amount <= 0)
amount = 100;
var title = GetText("reaction_title");
var desc = GetText("reaction_desc", _bc.BotConfig.CurrencySign, Format.Bold(amount.ToString()) + _bc.BotConfig.CurrencySign);
var footer = GetText("reaction_footer", 24);
var re = new ReactionEvent(_bc.BotConfig, _client, _cs, amount);
var msg = await context.Channel.SendConfirmAsync(title,
desc, footer: footer)
.ConfigureAwait(false);
await re.Start(msg, context);
}
}
}
}

View File

@ -1,62 +0,0 @@
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Modules.Gambling.Services;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Core.Services;
using NadekoBot.Extensions;
using System.Linq;
using Discord.Commands;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
public class CurrencyRaffleCommands : NadekoSubmodule<CurrencyRaffleService>
{
private readonly IBotConfigProvider _bc;
public CurrencyRaffleCommands(IBotConfigProvider bc)
{
_bc = bc;
}
public enum Mixed { Mixed }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task RaffleCur(Mixed _, int amount) =>
RaffleCur(amount, true);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RaffleCur(int amount, bool mixed = false)
{
if (amount < 1)
return;
async Task OnEnded(IUser arg, int won)
{
await Context.Channel.SendConfirmAsync(GetText("rafflecur_ended", _bc.BotConfig.CurrencyName, Format.Bold(arg.ToString()), won + _bc.BotConfig.CurrencySign));
}
var res = await _service.JoinOrCreateGame(Context.Channel.Id,
Context.User, amount, mixed, OnEnded)
.ConfigureAwait(false);
if (res.Item1 != null)
{
await Context.Channel.SendConfirmAsync(GetText("rafflecur", res.Item1.GameType.ToString()),
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({x.Amount})")),
footer: GetText("rafflecur_joined", Context.User.ToString())).ConfigureAwait(false);
}
else
{
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
await ReplyErrorLocalized("rafflecur_already_joined").ConfigureAwait(false);
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
await ReplyErrorLocalized("not_enough", _bc.BotConfig.CurrencySign).ConfigureAwait(false);
}
}
}
}
}

View File

@ -1,21 +0,0 @@
using System.Threading.Tasks;
using NadekoBot.Core.Services;
using System.Collections.Concurrent;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
namespace NadekoBot.Modules.Gambling.Services
{
public class AnimalRaceService : INService, IUnloadableService
{
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public Task Unload()
{
foreach (var kvp in AnimalRaces)
{
try { kvp.Value.Dispose(); } catch { }
}
return Task.CompletedTask;
}
}
}

View File

@ -1,66 +0,0 @@
using Discord;
using Discord.Commands;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.CurrencyEvents;
using NadekoBot.Core.Services;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Services
{
public class CurrencyEventsService : INService, IUnloadableService
{
public ConcurrentDictionary<ulong, List<ReactionEvent>> ReactionEvents { get; }
public SneakyEvent SneakyEvent { get; private set; } = null;
private SemaphoreSlim _sneakyLock = new SemaphoreSlim(1, 1);
public CurrencyEventsService()
{
ReactionEvents = new ConcurrentDictionary<ulong, List<ReactionEvent>>();
}
public async Task<bool> StartSneakyEvent(SneakyEvent ev, IUserMessage msg, ICommandContext ctx)
{
await _sneakyLock.WaitAsync().ConfigureAwait(false);
try
{
if (SneakyEvent != null)
return false;
SneakyEvent = ev;
ev.OnEnded += () => SneakyEvent = null;
var _ = SneakyEvent.Start(msg, ctx).ConfigureAwait(false);
}
finally
{
_sneakyLock.Release();
}
return true;
}
public async Task Unload()
{
foreach (var kvp in ReactionEvents)
{
foreach (var ev in kvp.Value)
{
try { await ev.Stop().ConfigureAwait(false); } catch { }
}
}
ReactionEvents.Clear();
await _sneakyLock.WaitAsync().ConfigureAwait(false);
try
{
await SneakyEvent.Stop().ConfigureAwait(false);
}
finally
{
_sneakyLock.Release();
}
}
}
}

View File

@ -1,97 +0,0 @@
using System.Threading.Tasks;
using NadekoBot.Core.Services;
using NadekoBot.Core.Modules.Gambling.Common;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using Discord;
using System;
namespace NadekoBot.Core.Modules.Gambling.Services
{
public class CurrencyRaffleService : INService
{
public enum JoinErrorType
{
NotEnoughCurrency,
AlreadyJoinedOrInvalidAmount
}
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly DbService _db;
private readonly CurrencyService _cs;
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new Dictionary<ulong, CurrencyRaffleGame>();
public CurrencyRaffleService(DbService db, CurrencyService cs)
{
_db = db;
_cs = cs;
}
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(ulong channelId, IUser user, int amount, bool mixed, Func<IUser, int, Task> onEnded)
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
var newGame = false;
if (!Games.TryGetValue(channelId, out var crg))
{
newGame = true;
crg = new CurrencyRaffleGame(mixed
? CurrencyRaffleGame.Type.Mixed
: CurrencyRaffleGame.Type.Normal);
Games.Add(channelId, crg);
}
using (var uow = _db.UnitOfWork)
{
//remove money, and stop the game if this
// user created it and doesn't have the money
if (!await _cs.RemoveAsync(user.Id, "Currency Raffle Join", amount, uow).ConfigureAwait(false))
{
if (newGame)
Games.Remove(channelId);
return (null, JoinErrorType.NotEnoughCurrency);
}
if (!crg.AddUser(user, amount))
{
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount, uow).ConfigureAwait(false);
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
}
uow.Complete();
}
if (newGame)
{
var _t = Task.Run(async () =>
{
await Task.Delay(60000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
var winner = crg.GetWinner();
var won = crg.Users.Sum(x => x.Amount);
using (var uow = _db.UnitOfWork)
{
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
won, uow);
uow.Complete();
}
Games.Remove(channelId, out _);
var oe = onEnded(winner.DiscordUser, won);
}
catch { }
finally { _locker.Release(); }
});
}
return (crg, null);
}
finally
{
_locker.Release();
}
}
}
}

View File

@ -1,49 +0,0 @@
using Discord;
using NadekoBot.Core.Services;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Services
{
public class WaifuService : INService
{
private readonly DbService _db;
private readonly CurrencyService _cs;
public WaifuService(DbService db, CurrencyService cs)
{
_db = db;
_cs = cs;
}
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
{
if (owner.Id == newOwner.Id || waifuId == newOwner.Id)
return false;
using (var uow = _db.UnitOfWork)
{
var waifu = uow.Waifus.ByWaifuUserId(waifuId);
var ownerUser = uow.DiscordUsers.GetOrCreate(owner);
// owner has to be the owner of the waifu
if (waifu == null || waifu.ClaimerId != ownerUser.Id)
return false;
if (!await _cs.RemoveAsync(owner.Id,
"Waifu Transfer",
waifu.Price / 10,
uow).ConfigureAwait(false))
{
return false;
}
//new claimerId is the id of the new owner
var newOwnerUser = uow.DiscordUsers.GetOrCreate(newOwner);
waifu.ClaimerId = newOwnerUser.Id;
await uow.CompleteAsync().ConfigureAwait(false);
}
return true;
}
}
}

View File

@ -1,72 +0,0 @@
using System.Threading.Tasks;
using Discord;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Core.Services;
using System;
using System.Threading;
namespace NadekoBot.Modules.Games.Common
{
public class PollRunner
{
public Poll Poll { get; }
private readonly DbService _db;
public event Func<IUserMessage, IGuildUser, Task> OnVoted;
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
public PollRunner(DbService db, Poll poll)
{
_db = db;
Poll = poll;
}
public async Task<bool> TryVote(IUserMessage msg)
{
PollVote voteObj;
await _locker.WaitAsync().ConfigureAwait(false);
try
{
// has to be a user message
// channel must be the same the poll started in
if (msg == null || msg.Author.IsBot || msg.Channel.Id != Poll.ChannelId)
return false;
// has to be an integer
if (!int.TryParse(msg.Content, out int vote))
return false;
--vote;
if (vote < 0 || vote >= Poll.Answers.Count)
return false;
var usr = msg.Author as IGuildUser;
if (usr == null)
return false;
voteObj = new PollVote()
{
UserId = msg.Author.Id,
VoteIndex = vote,
};
if (!Poll.Votes.Add(voteObj))
return false;
var _ = OnVoted?.Invoke(msg, usr);
}
finally { _locker.Release(); }
using (var uow = _db.UnitOfWork)
{
var trackedPoll = uow.Polls.Get(Poll.Id);
trackedPoll.Votes.Add(voteObj);
uow.Complete();
}
return true;
}
public void End()
{
OnVoted = null;
}
}
}

View File

@ -1,277 +0,0 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Core.Services.Impl;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Common
{
public class TicTacToe
{
enum Phase
{
Starting,
Started,
Ended
}
private readonly ITextChannel _channel;
private readonly IGuildUser[] _users;
private readonly int?[,] _state;
private Phase _phase;
private int _curUserIndex;
private readonly SemaphoreSlim _moveLock;
private IGuildUser _winner;
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
public Action<TicTacToe> OnEnded;
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
private readonly NadekoStrings _strings;
private readonly DiscordSocketClient _client;
public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_strings = strings;
_client = client;
_users = new[] { firstUser, null };
_state = new int?[,] {
{ null, null, null },
{ null, null, null },
{ null, null, null },
};
_phase = Phase.Starting;
_moveLock = new SemaphoreSlim(1, 1);
}
private string GetText(string key, params object[] replacements) =>
_strings.GetText(key,
_channel.GuildId,
typeof(Games).Name.ToLowerInvariant(),
replacements);
public string GetState()
{
var sb = new StringBuilder();
for (var i = 0; i < _state.GetLength(0); i++)
{
for (var j = 0; j < _state.GetLength(1); j++)
{
sb.Append(_state[i, j] == null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
if (j < _state.GetLength(1) - 1)
sb.Append("┃");
}
if (i < _state.GetLength(0) - 1)
sb.AppendLine("\n──────────");
}
return sb.ToString();
}
public EmbedBuilder GetEmbed(string title = null)
{
var embed = new EmbedBuilder()
.WithOkColor()
.WithDescription(Environment.NewLine + GetState())
.WithAuthor(eab => eab.WithName(GetText("vs", _users[0], _users[1])));
if (!string.IsNullOrWhiteSpace(title))
embed.WithTitle(title);
if (_winner == null)
{
if (_phase == Phase.Ended)
embed.WithFooter(efb => efb.WithText(GetText("ttt_no_moves")));
else
embed.WithFooter(efb => efb.WithText(GetText("ttt_users_move", _users[_curUserIndex])));
}
else
embed.WithFooter(efb => efb.WithText(GetText("ttt_has_won", _winner)));
return embed;
}
private static string GetIcon(int? val)
{
switch (val)
{
case 0:
return "❌";
case 1:
return "⭕";
case 2:
return "❎";
case 3:
return "🅾";
default:
return "⬛";
}
}
public async Task Start(IGuildUser user)
{
if (_phase == Phase.Started || _phase == Phase.Ended)
{
await _channel.SendErrorAsync(user.Mention + GetText("ttt_already_running")).ConfigureAwait(false);
return;
}
else if (_users[0] == user)
{
await _channel.SendErrorAsync(user.Mention + GetText("ttt_against_yourself")).ConfigureAwait(false);
return;
}
_users[1] = user;
_phase = Phase.Started;
_timeoutTimer = new Timer(async (_) =>
{
await _moveLock.WaitAsync();
try
{
if (_phase == Phase.Ended)
return;
_phase = Phase.Ended;
if (_users[1] != null)
{
_winner = _users[_curUserIndex ^= 1];
var del = _previousMessage?.DeleteAsync();
try
{
await _channel.EmbedAsync(GetEmbed(GetText("ttt_time_expired"))).ConfigureAwait(false);
if (del != null)
await del.ConfigureAwait(false);
}
catch { }
}
OnEnded?.Invoke(this);
}
catch { }
finally
{
_moveLock.Release();
}
}, null, 15000, Timeout.Infinite);
_client.MessageReceived += Client_MessageReceived;
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
}
private bool IsDraw()
{
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (_state[i, j] == null)
return false;
}
}
return true;
}
private Task Client_MessageReceived(SocketMessage msg)
{
var _ = Task.Run(async () =>
{
await _moveLock.WaitAsync().ConfigureAwait(false);
try
{
var curUser = _users[_curUserIndex];
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
return;
if (int.TryParse(msg.Content, out var index) &&
--index >= 0 &&
index <= 9 &&
_state[index / 3, index % 3] == null)
{
_state[index / 3, index % 3] = _curUserIndex;
// i'm lazy
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
{
_state[index / 3, 0] = _curUserIndex + 2;
_state[index / 3, 1] = _curUserIndex + 2;
_state[index / 3, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
{
_state[0, index % 3] = _curUserIndex + 2;
_state[1, index % 3] = _curUserIndex + 2;
_state[2, index % 3] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
{
_state[0, 0] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
{
_state[0, 2] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 0] = _curUserIndex + 2;
_phase = Phase.Ended;
}
var reason = "";
if (_phase == Phase.Ended) // if user won, stop receiving moves
{
reason = GetText("ttt_matched_three");
_winner = _users[_curUserIndex];
_client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
else if (IsDraw())
{
reason = GetText("ttt_a_draw");
_phase = Phase.Ended;
_client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
var sendstate = Task.Run(async () =>
{
var del1 = msg.DeleteAsync();
var del2 = _previousMessage?.DeleteAsync();
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
try { await del1; } catch { }
try { if (del2 != null) await del2; } catch { }
});
_curUserIndex ^= 1;
_timeoutTimer.Change(15000, Timeout.Infinite);
}
}
finally
{
_moveLock.Release();
}
});
return Task.CompletedTask;
}
}
}

View File

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using NadekoBot.Common;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
namespace NadekoBot.Modules.Games.Common.Trivia
{
public class TriviaQuestionPool
{
private readonly IDataCache _cache;
private readonly int maxPokemonId;
private readonly NadekoRandom _rng = new NadekoRandom();
private TriviaQuestion[] Pool => _cache.LocalData.TriviaQuestions;
private IReadOnlyDictionary<int, string> Map => _cache.LocalData.PokemonMap;
public TriviaQuestionPool(IDataCache cache)
{
_cache = cache;
maxPokemonId = 721; //xd
}
public TriviaQuestion GetRandomQuestion(HashSet<TriviaQuestion> exclude, bool isPokemon)
{
if (Pool.Length == 0)
return null;
if (isPokemon)
{
var num = _rng.Next(1, maxPokemonId + 1);
return new TriviaQuestion("Who's That Pokémon?",
Map[num].ToTitleCase(),
"Pokemon",
$@"http://nadekobot.me/images/pokemon/shadows/{num}.png",
$@"http://nadekobot.me/images/pokemon/real/{num}.png");
}
TriviaQuestion randomQuestion;
while (exclude.Contains(randomQuestion = Pool[_rng.Next(0, Pool.Length)])) ;
return randomQuestion;
}
}
}

View File

@ -1,113 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Core.Services.Database.Models;
using System.Text;
using System.Linq;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class PollCommands : NadekoSubmodule<PollService>
{
private readonly DiscordSocketClient _client;
public PollCommands(DiscordSocketClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public async Task Poll([Remainder] string arg)
{
if (string.IsNullOrWhiteSpace(arg))
return;
var poll = _service.CreatePoll(Context.Guild.Id,
Context.Channel.Id, arg);
if (_service.StartPoll(poll))
await Context.Channel
.EmbedAsync(new EmbedBuilder()
.WithTitle(GetText("poll_created", Context.User.ToString()))
.WithDescription(
Format.Bold(poll.Question) + "\n\n" +
string.Join("\n", poll.Answers
.Select(x => $"`{x.Index + 1}.` {Format.Bold(x.Text)}"))))
.ConfigureAwait(false);
else
await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public async Task PollStats()
{
if (!_service.ActivePolls.TryGetValue(Context.Guild.Id, out var pr))
return;
await Context.Channel.EmbedAsync(GetStats(pr.Poll, GetText("current_poll_results")));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public async Task Pollend()
{
var channel = (ITextChannel)Context.Channel;
Poll p;
if ((p = _service.StopPoll(Context.Guild.Id)) == null)
return;
var embed = GetStats(p, GetText("poll_closed"));
await Context.Channel.EmbedAsync(embed)
.ConfigureAwait(false);
}
public EmbedBuilder GetStats(Poll poll, string title)
{
var results = poll.Votes.GroupBy(kvp => kvp.VoteIndex)
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1))
.OrderByDescending(kvp => kvp.Value)
.ToArray();
var eb = new EmbedBuilder().WithTitle(title);
var sb = new StringBuilder()
.AppendLine(Format.Bold(poll.Question))
.AppendLine();
var totalVotesCast = 0;
if (results.Length == 0)
{
sb.AppendLine(GetText("no_votes_cast"));
}
else
{
for (int i = 0; i < results.Length; i++)
{
var result = results[i];
sb.AppendLine(GetText("poll_result",
result.Key + 1,
Format.Bold(poll.Answers[result.Key].Text),
Format.Bold(result.Value.ToString())));
totalVotesCast += result.Value;
}
}
return eb.WithDescription(sb.ToString())
.WithFooter(efb => efb.WithText(GetText("x_votes_cast", totalVotesCast)))
.WithOkColor();
}
}
}
}

View File

@ -1,128 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Modules.Games.Common;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Impl;
using NLog;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Core.Services.Database;
namespace NadekoBot.Modules.Games.Services
{
public class PollService : IEarlyBlockingExecutor, INService
{
public ConcurrentDictionary<ulong, PollRunner> ActivePolls { get; } = new ConcurrentDictionary<ulong, PollRunner>();
private readonly Logger _log;
private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings;
private readonly DbService _db;
private readonly NadekoStrings _strs;
public PollService(DiscordSocketClient client, NadekoStrings strings, DbService db,
NadekoStrings strs, IUnitOfWork uow)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
_strings = strings;
_db = db;
_strs = strs;
ActivePolls = uow.Polls.GetAllPolls()
.ToDictionary(x => x.GuildId, x =>
{
var pr = new PollRunner(db, x);
pr.OnVoted += Pr_OnVoted;
return pr;
})
.ToConcurrent();
}
public Poll CreatePoll(ulong guildId, ulong channelId, string input)
{
if (string.IsNullOrWhiteSpace(input) || !input.Contains(";"))
return null;
var data = input.Split(';');
if (data.Length < 3)
return null;
var col = new IndexedCollection<PollAnswer>(data.Skip(1)
.Select(x => new PollAnswer() { Text = x }));
return new Poll()
{
Answers = col,
Question = data[0],
ChannelId = channelId,
GuildId = guildId,
Votes = new System.Collections.Generic.HashSet<PollVote>()
};
}
public bool StartPoll(Poll p)
{
var pr = new PollRunner(_db, p);
if (ActivePolls.TryAdd(p.GuildId, pr))
{
using (var uow = _db.UnitOfWork)
{
uow.Polls.Add(p);
uow.Complete();
}
pr.OnVoted += Pr_OnVoted;
return true;
}
return false;
}
public Poll StopPoll(ulong guildId)
{
if (ActivePolls.TryRemove(guildId, out var pr))
{
pr.OnVoted -= Pr_OnVoted;
using (var uow = _db.UnitOfWork)
{
uow.Polls.RemovePoll(pr.Poll.Id);
uow.Complete();
}
return pr.Poll;
}
return null;
}
private async Task Pr_OnVoted(IUserMessage msg, IGuildUser usr)
{
var toDelete = await msg.Channel.SendConfirmAsync(_strs.GetText("poll_voted", usr.Guild.Id, "Games".ToLowerInvariant(), Format.Bold(usr.ToString())))
.ConfigureAwait(false);
toDelete.DeleteAfter(5);
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
}
public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{
if (guild == null)
return false;
if (!ActivePolls.TryGetValue(guild.Id, out var poll))
return false;
try
{
return await poll.TryVote(msg).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
return false;
}
}
}

View File

@ -1,63 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Services.Impl;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class TicTacToeCommands : NadekoSubmodule<GamesService>
{
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
private readonly DiscordSocketClient _client;
public TicTacToeCommands(DiscordSocketClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task TicTacToe()
{
var channel = (ITextChannel)Context.Channel;
await _sem.WaitAsync(1000);
try
{
if (_service.TicTacToeGames.TryGetValue(channel.Id, out TicTacToe game))
{
var _ = Task.Run(async () =>
{
await game.Start((IGuildUser)Context.User);
});
return;
}
game = new TicTacToe(base._strings, this._client, channel, (IGuildUser)Context.User);
_service.TicTacToeGames.Add(channel.Id, game);
await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
game.OnEnded += (g) =>
{
_service.TicTacToeGames.Remove(channel.Id);
_sem.Dispose();
};
}
finally
{
_sem.Release();
}
}
}
}
}

View File

@ -1,95 +0,0 @@
using NLog;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace NadekoBot.Modules.Music.Common
{
public class SongBuffer : IDisposable
{
const int readSize = 81920;
private Process p;
private Stream _outStream;
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
private readonly Logger _log;
public string SongUri { get; private set; }
public SongBuffer(string songUri, string skipTo, bool isLocal)
{
_log = LogManager.GetCurrentClassLogger();
this.SongUri = songUri;
this._isLocal = isLocal;
try
{
this.p = StartFFmpegProcess(SongUri, 0);
this._outStream = this.p.StandardOutput.BaseStream;
}
catch (System.ComponentModel.Win32Exception)
{
_log.Error(@"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");
}
catch (OperationCanceledException) { }
catch (InvalidOperationException) { } // when ffmpeg is disposed
catch (Exception ex)
{
_log.Info(ex);
}
}
private Process StartFFmpegProcess(string songUri, float skipTo = 0)
{
var args = $"-err_detect ignore_err -i {songUri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel error";
if (!_isLocal)
args = "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 " + args;
return Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
}
private readonly object locker = new object();
private readonly bool _isLocal;
public int Read(byte[] b, int offset, int toRead)
{
lock (locker)
return _outStream.Read(b, offset, toRead);
}
public void Dispose()
{
try
{
this.p.StandardOutput.Dispose();
}
catch (Exception ex)
{
_log.Error(ex);
}
try
{
if(!this.p.HasExited)
this.p.Kill();
}
catch
{
}
_outStream.Dispose();
this.p.Dispose();
}
}
}

View File

@ -1,5 +0,0 @@
using System;
namespace NadekoBot.Modules.NSFW.Exceptions
{
}

View File

@ -1,21 +0,0 @@
using Newtonsoft.Json;
namespace NadekoBot.Core.Modules.Searches.Common
{
public class CryptoData
{
public string Id { get; set; }
public string Name { get; set; }
public string Symbol { get; set; }
public int Rank { get; set; }
public string Price_Usd { get; set; }
public string Price_Btc { get; set; }
public decimal? Market_Cap_Usd { get; set; }
[JsonProperty("24h_volume_usd")]
public double? _24h_Volume_Usd { get; set; }
public string Percent_Change_1h { get; set; }
public string Percent_Change_24h { get; set; }
public string Percent_Change_7d { get; set; }
public string LastUpdated { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches.Common
{
public class NovelResult
{
public string Description { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public string ImageUrl { get; set; }
public string[] Authors { get; set; }
public string Status { get; set; }
public string[] Genres { get; set; }
public string Score { get; set; }
}
}

View File

@ -1,113 +0,0 @@
using Newtonsoft.Json;
namespace NadekoBot.Modules.Searches.Common
{
public interface IStreamResponse
{
int Viewers { get; }
string Title { get; }
bool Live { get; }
string Game { get; }
int Followers { get; }
string Url { get; }
string Icon { get; }
}
public class SmashcastResponse : IStreamResponse
{
public bool Success { get; set; } = true;
public int Followers { get; set; }
[JsonProperty("user_logo")]
public string UserLogo { get; set; }
[JsonProperty("is_live")]
public string IsLive { get; set; }
public int Viewers => 0;
public string Title => "";
public bool Live => IsLive == "1";
public string Game => "";
public string Icon => !string.IsNullOrWhiteSpace(UserLogo)
? "https://edge.sf.hitbox.tv" + UserLogo
: "";
public string Url { get; set; }
}
public class PicartoResponse : IStreamResponse
{
public string Name { get; set; }
public int Viewers { get; set; }
public string Title { get; set; }
[JsonProperty("online")]
public bool Live { get; set; }
[JsonProperty("category")]
public string Game { get; set; }
public int Followers { get; set; }
public string Url => "https://picarto.tv/" + Name;
[JsonProperty("thumbnail")]
public string Icon { get; set; }
}
public class TwitchResponse : IStreamResponse
{
public string Error { get; set; } = null;
public bool IsLive => Stream != null;
public StreamInfo Stream { get; set; }
public class StreamInfo
{
public int Viewers { get; set; }
public string Game { get; set; }
public ChannelInfo Channel { get; set; }
public class ChannelInfo
{
public string Status { get; set; }
public string Logo { get; set; }
public int Followers { get; set; }
}
}
public int Viewers => Stream?.Viewers ?? 0;
public string Title => Stream?.Channel?.Status;
public bool Live => IsLive;
public string Game => Stream?.Game;
public int Followers => Stream?.Channel?.Followers ?? 0;
public string Url { get; set; }
public string Icon => Stream?.Channel?.Logo;
}
public class MixerResponse : IStreamResponse
{
public class MixerType
{
public string Parent { get; set; }
public string Name { get; set; }
}
public class MixerThumbnail
{
public string Url { get; set; }
}
public string Url { get; set; }
public string Error { get; set; } = null;
[JsonProperty("online")]
public bool IsLive { get; set; }
public int ViewersCurrent { get; set; }
public string Name { get; set; }
public int NumFollowers { get; set; }
public MixerType Type { get; set; }
public MixerThumbnail Thumbnail { get; set; }
public int Viewers => ViewersCurrent;
public string Title => Name;
public bool Live => IsLive;
public string Game => Type?.Name ?? "";
public int Followers => NumFollowers;
public string Icon => Thumbnail?.Url;
}
}

View File

@ -1,110 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.SyndicationFeed.Rss;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
[Group]
public class FeedCommands : NadekoSubmodule<FeedsService>
{
private readonly DiscordSocketClient _client;
public FeedCommands(DiscordSocketClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task Feed(string url, [Remainder] ITextChannel channel = null)
{
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
if (success)
{
channel = channel ?? (ITextChannel)Context.Channel;
using (var xmlReader = XmlReader.Create(url, new XmlReaderSettings() { Async = true }))
{
var reader = new RssFeedReader(xmlReader);
try
{
await reader.Read();
}
catch (Exception ex)
{
Console.WriteLine(ex);
success = false;
}
}
if (success)
{
success = _service.AddFeed(Context.Guild.Id, channel.Id, url);
if (success)
{
await ReplyConfirmLocalized("feed_added").ConfigureAwait(false);
return;
}
}
}
await ReplyErrorLocalized("feed_not_valid").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task FeedRemove(int index)
{
if (_service.RemoveFeed(Context.Guild.Id, --index))
{
await ReplyConfirmLocalized("feed_removed").ConfigureAwait(false);
}
else
await ReplyErrorLocalized("feed_out_of_range").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task FeedList()
{
var feeds = _service.GetFeeds(Context.Guild.Id);
if (!feeds.Any())
{
await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithDescription(GetText("feed_no_feed")))
.ConfigureAwait(false);
return;
}
await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (cur) =>
{
var embed = new EmbedBuilder()
.WithOkColor();
var i = 0;
var fs = string.Join("\n", feeds.Skip(cur * 10)
.Take(10)
.Select(x => $"`{(cur * 10) + (++i)}.` <#{x.ChannelId}> {x.Url}"));
return embed.WithDescription(fs);
}, feeds.Count, 10);
}
}
}
}

View File

@ -1,154 +0,0 @@
using NadekoBot.Core.Services;
using Newtonsoft.Json;
using NLog;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using NadekoBot.Modules.Searches.Common;
using AngleSharp;
using AngleSharp.Dom.Html;
using System.Linq;
namespace NadekoBot.Modules.Searches.Services
{
public class AnimeSearchService : INService
{
private readonly Logger _log;
private readonly IDataCache _cache;
private readonly HttpClient _http;
public AnimeSearchService(IDataCache cache)
{
_log = LogManager.GetCurrentClassLogger();
_cache = cache;
_http = new HttpClient();
}
public async Task<AnimeResult> GetAnimeData(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
try
{
var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " "));
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
if (!ok)
{
data = await _http.GetStringAsync(link).ConfigureAwait(false);
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
}
return JsonConvert.DeserializeObject<AnimeResult>(data);
}
catch
{
return null;
}
}
public async Task<NovelResult> GetNovelData(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
query = query.Replace(" ", "-");
try
{
var link = "http://www.novelupdates.com/series/" + Uri.EscapeDataString(query.Replace("/", " "));
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetNovelDataAsync(link).ConfigureAwait(false);
if (!ok)
{
var config = Configuration.Default.WithDefaultLoader();
var document = await BrowsingContext.New(config).OpenAsync(link);
var imageElem = document.QuerySelector("div.seriesimg > img");
if (imageElem == null)
return null;
var imageUrl = ((IHtmlImageElement)imageElem).Source;
var descElem = document.QuerySelector("div#editdescription > p");
var desc = descElem.InnerHtml;
var genres = document.QuerySelector("div#seriesgenre").Children
.Select(x => x as IHtmlAnchorElement)
.Where(x => x != null)
.Select(x => $"[{x.InnerHtml}]({x.Href})")
.ToArray();
var authors = document
.QuerySelector("div#showauthors")
.Children
.Select(x => x as IHtmlAnchorElement)
.Where(x => x != null)
.Select(x => $"[{x.InnerHtml}]({x.Href})")
.ToArray();
var score = ((IHtmlSpanElement)document
.QuerySelector("h5.seriesother > span.uvotes"))
.InnerHtml;
var status = document
.QuerySelector("div#editstatus")
.InnerHtml;
var title = document
.QuerySelector("div.w-blog-content > div.seriestitlenu")
.InnerHtml;
var obj = new NovelResult()
{
Description = desc,
Authors = authors,
Genres = genres,
ImageUrl = imageUrl,
Link = link,
Score = score,
Status = status,
Title = title,
};
await _cache.SetNovelDataAsync(link,
JsonConvert.SerializeObject(obj)).ConfigureAwait(false);
return obj;
}
return JsonConvert.DeserializeObject<NovelResult>(data);
}
catch (Exception ex)
{
_log.Error(ex);
return null;
}
}
public async Task<MangaResult> GetMangaData(string query)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
try
{
var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " "));
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
if (!ok)
{
data = await _http.GetStringAsync(link).ConfigureAwait(false);
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
}
return JsonConvert.DeserializeObject<MangaResult>(data);
}
catch
{
return null;
}
}
}
}

View File

@ -1,214 +0,0 @@
using Discord;
using Microsoft.SyndicationFeed;
using Microsoft.SyndicationFeed.Rss;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Collections.Generic;
using NadekoBot.Core.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
using Discord.WebSocket;
namespace NadekoBot.Modules.Searches.Services
{
public class FeedsService : INService
{
private readonly DbService _db;
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
private readonly DiscordSocketClient _client;
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
new ConcurrentDictionary<string, DateTime>();
public FeedsService(NadekoBot bot, DbService db, DiscordSocketClient client)
{
_db = db;
_subs = bot
.AllGuildConfigs
.SelectMany(x => x.FeedSubs)
.GroupBy(x => x.Url)
.ToDictionary(x => x.Key, x => x.ToHashSet())
.ToConcurrent();
_client = client;
foreach (var kvp in _subs)
{
// to make sure rss feeds don't post right away, but
// only the updates from after the bot has started
_lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow);
}
var _ = Task.Run(TrackFeeds);
}
public async Task<EmbedBuilder> TrackFeeds()
{
while (true)
{
foreach (var kvp in _subs)
{
if (kvp.Value.Count == 0)
continue;
if (!_lastPosts.TryGetValue(kvp.Key, out DateTime lastTime))
lastTime = _lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow);
var rssUrl = kvp.Key;
try
{
using (var xmlReader = XmlReader.Create(rssUrl, new XmlReaderSettings() { Async = true }))
{
var feedReader = new RssFeedReader(xmlReader);
var embed = new EmbedBuilder()
.WithAuthor(kvp.Key)
.WithOkColor();
while (await feedReader.Read() && feedReader.ElementType != SyndicationElementType.Item)
{
switch (feedReader.ElementType)
{
case SyndicationElementType.Link:
var uri = await feedReader.ReadLink();
embed.WithAuthor(kvp.Key, url: uri.Uri.AbsoluteUri);
break;
case SyndicationElementType.Content:
var content = await feedReader.ReadContent();
break;
case SyndicationElementType.Category:
break;
case SyndicationElementType.Image:
ISyndicationImage image = await feedReader.ReadImage();
embed.WithThumbnailUrl(image.Url.AbsoluteUri);
break;
default:
break;
}
}
ISyndicationItem item = await feedReader.ReadItem();
if (item.Published.UtcDateTime <= lastTime)
continue;
var desc = item.Description.StripHTML();
lastTime = item.Published.UtcDateTime;
var title = string.IsNullOrWhiteSpace(item.Title) ? "-" : item.Title;
desc = Format.Code(item.Published.ToString()) + Environment.NewLine + desc;
var link = item.Links.FirstOrDefault();
if (link != null)
desc = $"[link]({link.Uri}) " + desc;
var img = item.Links.FirstOrDefault(x => x.RelationshipType == "enclosure")?.Uri.AbsoluteUri
?? Regex.Match(item.Description, @"src=""(?<src>.*?)""").Groups["src"].ToString();
if (!string.IsNullOrWhiteSpace(img) && Uri.IsWellFormedUriString(img, UriKind.Absolute))
embed.WithImageUrl(img);
embed.AddField(title, desc);
//send the created embed to all subscribed channels
var sendTasks = kvp.Value
.Where(x => x.GuildConfig != null)
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
?.GetTextChannel(x.ChannelId))
.Where(x => x != null)
.Select(x => x.EmbedAsync(embed));
_lastPosts.AddOrUpdate(kvp.Key, item.Published.UtcDateTime, (k, old) => item.Published.UtcDateTime);
await Task.WhenAll(sendTasks).ConfigureAwait(false);
}
}
catch { }
}
await Task.Delay(10000);
}
}
public List<FeedSub> GetFeeds(ulong guildId)
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs))
.FeedSubs
.OrderBy(x => x.Id)
.ToList();
}
}
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
{
rssFeed.ThrowIfNull(nameof(rssFeed));
var fs = new FeedSub()
{
ChannelId = channelId,
Url = rssFeed.Trim().ToLowerInvariant(),
};
using (var uow = _db.UnitOfWork)
{
var gc = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs));
if (gc.FeedSubs.Contains(fs))
{
return false;
}
else if (gc.FeedSubs.Count >= 10)
{
return false;
}
gc.FeedSubs.Add(fs);
//adding all, in case bot wasn't on this guild when it started
foreach (var f in gc.FeedSubs)
{
_subs.AddOrUpdate(f.Url, new HashSet<FeedSub>(), (k, old) =>
{
old.Add(f);
return old;
});
}
uow.Complete();
}
return true;
}
public bool RemoveFeed(ulong guildId, int index)
{
if (index < 0)
return false;
using (var uow = _db.UnitOfWork)
{
var items = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs))
.FeedSubs
.OrderBy(x => x.Id)
.ToList();
if (items.Count <= index)
return false;
var toRemove = items[index];
_subs.AddOrUpdate(toRemove.Url, new HashSet<FeedSub>(), (key, old) =>
{
old.Remove(toRemove);
return old;
});
uow._context.Remove(toRemove);
uow.Complete();
}
return true;
}
}
}

View File

@ -1,9 +0,0 @@
namespace NadekoBot.Modules.Utility.Common
{
public class ConvertUnit
{
public string[] Triggers { get; set; }
public string UnitType { get; set; }
public decimal Modifier { get; set; }
}
}

View File

@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Core.Services;
using Newtonsoft.Json;
using NLog;
using NadekoBot.Modules.Utility.Common;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Utility.Services
{
public class ConverterService : INService, IUnloadableService
{
public ConvertUnit[] Units =>
_cache.Redis.GetDatabase()
.StringGet("converter_units")
.ToString()
.MapJson<ConvertUnit[]>();
private readonly Logger _log;
private readonly Timer _currencyUpdater;
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
private readonly DbService _db;
private readonly IDataCache _cache;
private readonly HttpClient _http;
public ConverterService(DiscordSocketClient client, DbService db,
IDataCache cache)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
_cache = cache;
_http = new HttpClient();
if (client.ShardId == 0)
{
_currencyUpdater = new Timer(async (shouldLoad) => await UpdateCurrency((bool)shouldLoad),
client.ShardId == 0,
TimeSpan.Zero,
_updateInterval);
}
}
private async Task<Rates> GetCurrencyRates()
{
var res = await _http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
return JsonConvert.DeserializeObject<Rates>(res);
}
private async Task UpdateCurrency(bool shouldLoad)
{
try
{
var unitTypeString = "currency";
if (shouldLoad)
{
var currencyRates = await GetCurrencyRates();
var baseType = new ConvertUnit()
{
Triggers = new[] { currencyRates.Base },
Modifier = decimal.One,
UnitType = unitTypeString
};
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
{
Triggers = new[] { u.Key },
Modifier = u.Value,
UnitType = unitTypeString
}).ToArray();
var fileData = JsonConvert.DeserializeObject<ConvertUnit[]>(
File.ReadAllText("data/units.json"));
var data = JsonConvert.SerializeObject(range.Append(baseType).Concat(fileData).ToList());
_cache.Redis.GetDatabase()
.StringSet("converter_units", data);
}
}
catch (Exception ex)
{
_log.Warn("Ignore the message below");
_log.Warn(ex);
}
}
public Task Unload()
{
_currencyUpdater?.Change(Timeout.Infinite, Timeout.Infinite);
return Task.CompletedTask;
}
}
public class Rates
{
public string Base { get; set; }
public DateTime Date { get; set; }
[JsonProperty("rates")]
public Dictionary<string, decimal> ConversionRates { get; set; }
}
}

View File

@ -1,43 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.9" />
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
<PackageReference Include="Discord.Net" Version="2.0.0-beta-00854" />
<PackageReference Include="CoreCLR-NCalc" Version="2.1.3" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.30.0.138" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.30.0.1035" />
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.30.0.896" />
<PackageReference Include="ImageSharp" Version="1.0.0-alpha9-00194" />
<PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha9-00189" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.1-beta1" />
<PackageReference Include="NLog" Version="5.0.0-beta11" />
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' ">
<DefineConstants>$(DefineConstants);GLOBAL_NADEKO</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
</PropertyGroup>
</Project>

View File

@ -1,23 +0,0 @@
namespace NadekoBot.Core.Services.Database.Models
{
public class FeedSub : DbEntity
{
public int GuildConfigId { get; set; }
public GuildConfig GuildConfig { get; set; }
public ulong ChannelId { get; set; }
public string Url { get; set; }
public override int GetHashCode()
{
return Url.GetHashCode() ^ GuildConfigId.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is FeedSub s
? s.Url == Url && s.GuildConfigId == GuildConfigId
: false;
}
}
}

View File

@ -1,19 +0,0 @@
namespace NadekoBot.Core.Services.Database.Models
{
public class LoadedPackage : DbEntity
{
public string Name { get; set; }
public override bool Equals(object obj)
{
return obj is LoadedPackage p
? p.Name == Name
: false;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
}

View File

@ -1,11 +0,0 @@
namespace NadekoBot.Core.Services.Database.Models
{
public class MusicSettings : DbEntity
{
public int GuildConfigId { get; set; }
public GuildConfig GuildConfig { get; set; }
public bool SongAutoDelete { get; set; } = false;
public ulong? MusicChannelId { get; set; } = null;
}
}

View File

@ -1,20 +0,0 @@
using NadekoBot.Common.Collections;
using System.Collections.Generic;
namespace NadekoBot.Core.Services.Database.Models
{
public class Poll : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public string Question { get; set; }
public IndexedCollection<PollAnswer> Answers { get; set; }
public HashSet<PollVote> Votes { get; set; } = new HashSet<PollVote>();
}
public class PollAnswer : DbEntity, IIndexed
{
public int Index { get; set; }
public string Text { get; set; }
}
}

View File

@ -1,20 +0,0 @@
namespace NadekoBot.Core.Services.Database.Models
{
public class PollVote : DbEntity
{
public ulong UserId { get; set; }
public int VoteIndex { get; set; }
public override int GetHashCode()
{
return UserId.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is PollVote p
? p.UserId == UserId
: false;
}
}
}

View File

@ -1,11 +0,0 @@
namespace NadekoBot.Core.Services.Database.Models
{
public class SelfAssignedRole : DbEntity
{
public ulong GuildId { get; set; }
public ulong RoleId { get; set; }
public int Group { get; set; }
public int LevelRequirement { get; set; }
}
}

View File

@ -1,8 +0,0 @@
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Core.Services.Database.Repositories
{
public interface ICurrencyTransactionsRepository : IRepository<CurrencyTransaction>
{
}
}

View File

@ -1,9 +0,0 @@
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Core.Services.Database.Repositories
{
public interface ICustomReactionRepository : IRepository<CustomReaction>
{
}
}

View File

@ -1,12 +0,0 @@
using Discord;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Core.Services.Database.Repositories
{
public interface IDiscordUserRepository : IRepository<DiscordUser>
{
DiscordUser GetOrCreate(IUser original);
int GetUserGlobalRanking(ulong id);
DiscordUser[] GetUsersXpLeaderboardFor(int page);
}
}

View File

@ -1,15 +0,0 @@
using NadekoBot.Core.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Core.Services.Database.Repositories
{
public interface IPollsRepository : IRepository<Poll>
{
IEnumerable<Poll> GetAllPolls();
void RemovePoll(int id);
}
}

View File

@ -1,35 +0,0 @@
using NadekoBot.Core.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace NadekoBot.Core.Services.Database.Repositories.Impl
{
public class PollsRepository : Repository<Poll>, IPollsRepository
{
public PollsRepository(DbContext context) : base(context)
{
}
public IEnumerable<Poll> GetAllPolls()
{
return _set.Include(x => x.Answers)
.Include(x => x.Votes)
.ToArray();
}
public void RemovePoll(int id)
{
var p = _set
.Include(x => x.Answers)
.Include(x => x.Votes)
.FirstOrDefault(x => x.Id == id);
p.Votes.Clear();
p.Answers.Clear();
_set.Remove(p);
}
}
}

View File

@ -1,24 +0,0 @@
using StackExchange.Redis;
using System;
using System.Threading.Tasks;
namespace NadekoBot.Core.Services
{
public interface IDataCache
{
ConnectionMultiplexer Redis { get; }
IImageCache LocalImages { get; }
ILocalDataCache LocalData { get; }
Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key);
Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key);
Task<(bool Success, string Data)> TryGetNovelDataAsync(string key);
Task SetImageDataAsync(string key, byte[] data);
Task SetAnimeDataAsync(string link, string data);
Task SetNovelDataAsync(string link, string data);
TimeSpan? AddTimelyClaim(ulong id, int period);
void RemoveAllTimelyClaims();
bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time);
bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time);
}
}

View File

@ -1,27 +0,0 @@
using System.Collections.Immutable;
namespace NadekoBot.Core.Services
{
public interface IImageCache
{
byte[] Heads { get; }
byte[] Tails { get; }
byte[][] Currency { get; }
byte[][] Dice { get; }
byte[] SlotBackground { get; }
byte[][] SlotEmojis { get; }
byte[][] SlotNumbers { get; }
byte[] WifeMatrix { get; }
byte[] RategirlDot { get; }
byte[] XpCard { get; }
byte[] Rip { get; }
byte[] FlowerCircle { get; }
void Reload();
}
}

View File

@ -1,14 +0,0 @@
using NadekoBot.Core.Common.Pokemon;
using NadekoBot.Modules.Games.Common.Trivia;
using System.Collections.Generic;
namespace NadekoBot.Core.Services
{
public interface ILocalDataCache
{
IReadOnlyDictionary<string, SearchPokemon> Pokemons { get; }
IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities { get; }
TriviaQuestion[] TriviaQuestions { get; }
IReadOnlyDictionary<int, string> PokemonMap { get; }
}
}

View File

@ -1,20 +0,0 @@
using System.Threading.Tasks;
namespace NadekoBot.Core.Services
{
/// <summary>
/// All services must implement this interface in order to be auto-discovered by the DI system
/// </summary>
public interface INService
{
}
/// <summary>
/// All services which require cleanup after they are unloaded must implement this interface
/// </summary>
public interface IUnloadableService
{
Task Unload();
}
}

View File

@ -1,38 +0,0 @@
using SixLabors.Fonts;
using System.IO;
namespace NadekoBot.Core.Services.Impl
{
public class FontProvider : INService
{
private readonly FontCollection _fonts;
public FontProvider()
{
_fonts = new FontCollection();
if (Directory.Exists("data/fonts"))
foreach (var file in Directory.GetFiles("data/fonts"))
{
_fonts.Install(file);
}
UsernameFontFamily = _fonts.Find("Whitney-Bold");
ClubFontFamily = _fonts.Find("Whitney-Bold");
LevelFont = _fonts.Find("Whitney-Bold").CreateFont(45);
XpFont = _fonts.Find("Whitney-Bold").CreateFont(50);
AwardedFont = _fonts.Find("Whitney-Bold").CreateFont(25);
RankFont = _fonts.Find("Uni Sans Thin CAPS").CreateFont(30);
TimeFont = _fonts.Find("Whitney-Bold").CreateFont(20);
RipNameFont = _fonts.Find("Whitney-Bold").CreateFont(20);
}
public Font LevelFont { get; }
public Font XpFont { get; }
public Font AwardedFont { get; }
public Font RankFont { get; }
public Font TimeFont { get; }
public FontFamily UsernameFontFamily { get; }
public FontFamily ClubFontFamily { get; }
public Font RipNameFont { get; }
}
}

View File

@ -1,240 +0,0 @@
using NadekoBot.Extensions;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System;
using System.IO;
using System.Linq;
namespace NadekoBot.Core.Services.Impl
{
public class RedisImagesCache : IImageCache
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly Logger _log;
private IDatabase _db => _con.GetDatabase();
private const string _basePath = "data/images/";
private const string _headsPath = _basePath + "coins/heads.png";
private const string _tailsPath = _basePath + "coins/tails.png";
private const string _currencyImagesPath = _basePath + "currency";
private const string _diceImagesPath = _basePath + "dice";
private const string _slotBackgroundPath = _basePath + "slots/background2.png";
private const string _slotNumbersPath = _basePath + "slots/numbers/";
private const string _slotEmojisPath = _basePath + "slots/emojis/";
private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png";
private const string _rategirlDot = _basePath + "rategirl/dot.png";
private const string _xpCardPath = _basePath + "xp/xp.png";
private const string _ripPath = _basePath + "rip/rip.png";
private const string _ripFlowersPath = _basePath + "rip/rose_overlay.png";
public byte[] Heads
{
get
{
return Get<byte[]>("heads");
}
set
{
Set("heads", value);
}
}
public byte[] Tails
{
get
{
return Get<byte[]>("tails");
}
set
{
Set("tails", value);
}
}
public byte[][] Currency
{
get
{
return Get<byte[][]>("currency");
}
set
{
Set("currency", value);
}
}
public byte[][] Dice
{
get
{
return Get<byte[][]>("dice");
}
set
{
Set("dice", value);
}
}
public byte[] SlotBackground
{
get
{
return Get<byte[]>("slot_background");
}
set
{
Set("slot_background", value);
}
}
public byte[][] SlotNumbers
{
get
{
return Get<byte[][]>("slotnumbers");
}
set
{
Set("slotnumbers", value);
}
}
public byte[][] SlotEmojis
{
get
{
return Get<byte[][]>("slotemojis");
}
set
{
Set("slotemojis", value);
}
}
public byte[] WifeMatrix
{
get
{
return Get<byte[]>("wife_matrix");
}
set
{
Set("wife_matrix", value);
}
}
public byte[] RategirlDot
{
get
{
return Get<byte[]>("rategirl_dot");
}
set
{
Set("rategirl_dot", value);
}
}
public byte[] XpCard
{
get
{
return Get<byte[]>("xp_card");
}
set
{
Set("xp_card", value);
}
}
public byte[] Rip
{
get
{
return Get<byte[]>("rip");
}
set
{
Set("rip", value);
}
}
public byte[] FlowerCircle
{
get
{
return Get<byte[]>("flower_circle");
}
set
{
Set("flower_circle", value);
}
}
public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
{
_con = con;
_creds = creds;
_log = LogManager.GetCurrentClassLogger();
}
public void Reload()
{
try
{
Heads = File.ReadAllBytes(_headsPath);
Tails = File.ReadAllBytes(_tailsPath);
Currency = Directory.GetFiles(_currencyImagesPath)
.Select(x => File.ReadAllBytes(x))
.ToArray();
Dice = Directory.GetFiles(_diceImagesPath)
.OrderBy(x => int.Parse(Path.GetFileNameWithoutExtension(x)))
.Select(x => File.ReadAllBytes(x))
.ToArray();
SlotBackground = File.ReadAllBytes(_slotBackgroundPath);
SlotNumbers = Directory.GetFiles(_slotNumbersPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x))
.ToArray();
SlotEmojis = Directory.GetFiles(_slotEmojisPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x))
.ToArray();
WifeMatrix = File.ReadAllBytes(_wifeMatrixPath);
RategirlDot = File.ReadAllBytes(_rategirlDot);
XpCard = File.ReadAllBytes(_xpCardPath);
Rip = File.ReadAllBytes(_ripPath);
FlowerCircle = File.ReadAllBytes(_ripFlowersPath);
}
catch (Exception ex)
{
_log.Error(ex);
throw;
}
}
private T Get<T>(string key) where T : class
{
return JsonConvert.DeserializeObject<T>(_db.StringGet($"{_creds.RedisKey()}_localimg_{key}"));
}
private void Set(string key, object obj)
{
_db.StringSet($"{_creds.RedisKey()}_localimg_{key}", JsonConvert.SerializeObject(obj));
}
}
}

View File

@ -1,115 +0,0 @@
using NadekoBot.Extensions;
using StackExchange.Redis;
using System;
using System.Threading.Tasks;
namespace NadekoBot.Core.Services.Impl
{
public class RedisCache : IDataCache
{
public ConnectionMultiplexer Redis { get; }
public IImageCache LocalImages { get; }
public ILocalDataCache LocalData { get; }
private readonly IDatabase _db;
private readonly string _redisKey;
public RedisCache(IBotCredentials creds)
{
Redis = ConnectionMultiplexer.Connect("127.0.0.1");
Redis.PreserveAsyncOrder = false;
LocalImages = new RedisImagesCache(Redis, creds);
LocalData = new RedisLocalDataCache(Redis, creds);
_db = Redis.GetDatabase();
_redisKey = creds.RedisKey();
}
// things here so far don't need the bot id
// because it's a good thing if different bots
// which are hosted on the same PC
// can re-use the same image/anime data
public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key)
{
byte[] x = await _db.StringGetAsync("image_" + key);
return (x != null, x);
}
public Task SetImageDataAsync(string key, byte[] data)
{
return _db.StringSetAsync("image_" + key, data);
}
public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key)
{
string x = await _db.StringGetAsync("anime_" + key);
return (x != null, x);
}
public Task SetAnimeDataAsync(string key, string data)
{
return _db.StringSetAsync("anime_" + key, data);
}
public async Task<(bool Success, string Data)> TryGetNovelDataAsync(string key)
{
string x = await _db.StringGetAsync("novel_" + key);
return (x != null, x);
}
public Task SetNovelDataAsync(string key, string data)
{
return _db.StringSetAsync("novel_" + key, data);
}
private readonly object timelyLock = new object();
public TimeSpan? AddTimelyClaim(ulong id, int period)
{
if (period == 0)
return null;
lock (timelyLock)
{
var time = TimeSpan.FromHours(period);
if ((bool?)_db.StringGet($"{_redisKey}_timelyclaim_{id}") == null)
{
_db.StringSet($"{_redisKey}_timelyclaim_{id}", true, time);
return null;
}
return _db.KeyTimeToLive($"{_redisKey}_timelyclaim_{id}");
}
}
public void RemoveAllTimelyClaims()
{
var server = Redis.GetServer("127.0.0.1", 6379);
foreach (var k in server.Keys(pattern: $"{_redisKey}_timelyclaim_*"))
{
_db.KeyDelete(k, CommandFlags.FireAndForget);
}
}
public bool TryAddAffinityCooldown(ulong userId, out TimeSpan? time)
{
time = _db.KeyTimeToLive($"{_redisKey}_affinity_{userId}");
if (time == null)
{
time = TimeSpan.FromMinutes(30);
_db.StringSet($"{_redisKey}_affinity_{userId}", true, time);
return true;
}
return false;
}
public bool TryAddDivorceCooldown(ulong userId, out TimeSpan? time)
{
time = _db.KeyTimeToLive($"{_redisKey}_divorce_{userId}");
if (time == null)
{
time = TimeSpan.FromHours(6);
_db.StringSet($"{_redisKey}_divorce_{userId}", true, time);
return true;
}
return false;
}
}
}

View File

@ -1,122 +0,0 @@
using NadekoBot.Core.Common.Pokemon;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Common.Trivia;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace NadekoBot.Core.Services.Impl
{
public class RedisLocalDataCache : ILocalDataCache
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly Logger _log;
private IDatabase _db => _con.GetDatabase();
private const string pokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
private const string pokemonListFile = "data/pokemon/pokemon_list7.json";
private const string pokemonMapPath = "data/pokemon/name-id_map4.json";
private const string questionsFile = "data/trivia_questions.json";
public IReadOnlyDictionary<string, SearchPokemon> Pokemons
{
get
{
return Get<Dictionary<string, SearchPokemon>>("pokemon_list");
}
private set
{
Set("pokemon_list", value);
}
}
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities
{
get
{
return Get<Dictionary<string, SearchPokemonAbility>>("pokemon_abilities");
}
private set
{
Set("pokemon_abilities", value);
}
}
public TriviaQuestion[] TriviaQuestions
{
get
{
return Get<TriviaQuestion[]>("trivia_questions");
}
private set
{
Set("trivia_questions", value);
}
}
public IReadOnlyDictionary<int, string> PokemonMap
{
get
{
return Get<Dictionary<int, string>>("pokemon_map");
}
private set
{
Set("pokemon_map", value);
}
}
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds)
{
_con = con;
_creds = creds;
_log = LogManager.GetCurrentClassLogger();
if (!File.Exists(pokemonListFile))
{
_log.Warn(pokemonListFile + " is missing. Pokemon abilities not loaded.");
}
else
{
Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(pokemonListFile));
}
if (!File.Exists(pokemonAbilitiesFile))
{
_log.Warn(pokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
}
else
{
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(pokemonAbilitiesFile));
}
try
{
TriviaQuestions = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(questionsFile));
PokemonMap = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(pokemonMapPath))
.ToDictionary(x => x.Id, x => x.Name);
}
catch (Exception ex)
{
_log.Error(ex);
throw;
}
}
private T Get<T>(string key) where T : class
{
return JsonConvert.DeserializeObject<T>(_db.StringGet($"{_creds.RedisKey()}_localdata_{key}"));
}
private void Set(string key, object obj)
{
_db.StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj));
}
}
}

View File

@ -1,498 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Impl;
using NLog;
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Collections.Immutable;
using System.Diagnostics;
using NadekoBot.Core.Services.Database.Models;
using System.Threading;
using System.IO;
using NadekoBot.Extensions;
using System.Collections.Generic;
using NadekoBot.Common;
using NadekoBot.Common.ShardCom;
using NadekoBot.Core.Services.Database;
using StackExchange.Redis;
using Newtonsoft.Json;
namespace NadekoBot
{
public class NadekoBot
{
private Logger _log;
public BotCredentials Credentials { get; }
public DiscordSocketClient Client { get; }
public CommandService CommandService { get; }
private readonly DbService _db;
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
/* I don't know how to make this not be static
* and keep the convenience of .WithOkColor
* and .WithErrorColor extensions methods.
* I don't want to pass botconfig every time I
* want to send a confirm or error message, so
* I'll keep this for now */
public static Color OkColor { get; private set; }
public static Color ErrorColor { get; private set; }
public TaskCompletionSource<bool> Ready { get; private set; } = new TaskCompletionSource<bool>();
public INServiceProvider Services { get; private set; }
private readonly BotConfig _botConfig;
public IDataCache Cache { get; private set; }
public int GuildCount =>
Cache.Redis.GetDatabase()
.ListRange(Credentials.RedisKey() + "_shardstats")
.Select(x => JsonConvert.DeserializeObject<ShardComMessage>(x))
.Sum(x => x.Guilds);
public NadekoBot(int shardId, int parentProcessId)
{
if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId));
LogSetup.SetupLogger(shardId);
_log = LogManager.GetCurrentClassLogger();
TerribleElevatedPermissionCheck();
Credentials = new BotCredentials();
Cache = new RedisCache(Credentials);
_db = new DbService(Credentials);
Client = new DiscordSocketClient(new DiscordSocketConfig
{
MessageCacheSize = 10,
LogLevel = LogSeverity.Warning,
ConnectionTimeout = int.MaxValue,
TotalShards = Credentials.TotalShards,
ShardId = shardId,
AlwaysDownloadUsers = false,
});
CommandService = new CommandService(new CommandServiceConfig()
{
CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Sync,
});
using (var uow = _db.UnitOfWork)
{
_botConfig = uow.BotConfig.GetOrCreate();
OkColor = new Color(Convert.ToUInt32(_botConfig.OkColor, 16));
ErrorColor = new Color(Convert.ToUInt32(_botConfig.ErrorColor, 16));
}
SetupShard(parentProcessId);
#if GLOBAL_NADEKO
Client.Log += Client_Log;
#endif
}
private void StartSendingData()
{
Task.Run(async () =>
{
while (true)
{
var data = new ShardComMessage()
{
ConnectionState = Client.ConnectionState,
Guilds = Client.ConnectionState == ConnectionState.Connected ? Client.Guilds.Count : 0,
ShardId = Client.ShardId,
Time = DateTime.UtcNow,
};
var sub = Cache.Redis.GetSubscriber();
var msg = JsonConvert.SerializeObject(data);
await sub.PublishAsync(Credentials.RedisKey() + "_shardcoord_send", msg).ConfigureAwait(false);
await Task.Delay(7500);
}
});
}
private void AddServices()
{
var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList();
//this unit of work will be used for initialization of all modules too, to prevent multiple queries from running
using (var uow = _db.UnitOfWork)
{
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
IBotConfigProvider botConfigProvider = new BotConfigProvider(_db, _botConfig, Cache);
//initialize Services
Services = new NServiceProvider()
.AddManual<IBotCredentials>(Credentials)
.AddManual(_db)
.AddManual(Client)
.AddManual(CommandService)
.AddManual(botConfigProvider)
.AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow)
.AddManual<IDataCache>(Cache);
Services.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler)));
var commandHandler = Services.GetService<CommandHandler>();
commandHandler.AddServices(Services);
LoadTypeReaders(typeof(NadekoBot).Assembly);
}
Services.Unload(typeof(IUnitOfWork)); // unload it after the startup
}
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
{
Type[] allTypes;
try
{
allTypes = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
Console.WriteLine(ex.LoaderExceptions[0]);
return Enumerable.Empty<object>();
}
var filteredTypes = allTypes
.Where(x => x.IsSubclassOf(typeof(TypeReader))
&& x.BaseType.GetGenericArguments().Length > 0
&& !x.IsAbstract);
var toReturn = new List<object>();
foreach (var ft in filteredTypes)
{
var x = (TypeReader)Activator.CreateInstance(ft, Client, CommandService);
var baseType = ft.BaseType;
var typeArgs = baseType.GetGenericArguments();
try
{
CommandService.AddTypeReader(typeArgs[0], x);
}
catch (Exception ex)
{
_log.Error(ex);
throw;
}
toReturn.Add(x);
}
return toReturn;
}
private async Task LoginAsync(string token)
{
var clientReady = new TaskCompletionSource<bool>();
Task SetClientReady()
{
var _ = Task.Run(async () =>
{
clientReady.TrySetResult(true);
try
{
foreach (var chan in (await Client.GetDMChannelsAsync()))
{
await chan.CloseAsync().ConfigureAwait(false);
}
}
catch
{
// ignored
}
finally
{
}
});
return Task.CompletedTask;
}
//connect
_log.Info("Shard {0} logging in ...", Client.ShardId);
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
await Client.StartAsync().ConfigureAwait(false);
Client.Ready += SetClientReady;
await clientReady.Task.ConfigureAwait(false);
Client.Ready -= SetClientReady;
Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild;
_log.Info("Shard {0} logged in.", Client.ShardId);
}
private Task Client_LeftGuild(SocketGuild arg)
{
_log.Info("Left server: {0} [{1}]", arg?.Name, arg?.Id);
return Task.CompletedTask;
}
private Task Client_JoinedGuild(SocketGuild arg)
{
_log.Info("Joined server: {0} [{1}]", arg?.Name, arg?.Id);
return Task.CompletedTask;
}
public async Task RunAsync(params string[] args)
{
var sw = Stopwatch.StartNew();
await LoginAsync(Credentials.Token).ConfigureAwait(false);
_log.Info($"Shard {Client.ShardId} loading services...");
try
{
AddServices();
}
catch (Exception ex)
{
_log.Error(ex);
throw;
}
sw.Stop();
_log.Info($"Shard {Client.ShardId} connected in {sw.Elapsed.TotalSeconds:F2}s");
var stats = Services.GetService<IStatsService>();
stats.Initialize();
var commandHandler = Services.GetService<CommandHandler>();
var CommandService = Services.GetService<CommandService>();
// start handling messages received in commandhandler
await commandHandler.StartHandling().ConfigureAwait(false);
var _ = await CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly);
bool isPublicNadeko = false;
#if GLOBAL_NADEKO
isPublicNadeko = true;
#endif
//unload modules which are not available on the public bot
if (isPublicNadeko)
CommandService
.Modules
.ToArray()
.Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot)))
.ForEach(x => CommandService.RemoveModuleAsync(x));
Ready.TrySetResult(true);
HandleStatusChanges();
StartSendingData();
_log.Info($"Shard {Client.ShardId} ready.");
}
private Task Client_Log(LogMessage arg)
{
_log.Warn(arg.Source + " | " + arg.Message);
if (arg.Exception != null)
_log.Warn(arg.Exception);
return Task.CompletedTask;
}
public async Task RunAndBlockAsync(params string[] args)
{
await RunAsync(args).ConfigureAwait(false);
await Task.Delay(-1).ConfigureAwait(false);
}
private void TerribleElevatedPermissionCheck()
{
try
{
File.WriteAllText("test", "test");
File.Delete("test");
}
catch
{
_log.Error("You must run the application as an ADMINISTRATOR.");
Console.ReadKey();
Environment.Exit(2);
}
}
private void SetupShard(int parentProcessId)
{
new Thread(new ThreadStart(() =>
{
try
{
var p = Process.GetProcessById(parentProcessId);
if (p == null)
return;
p.WaitForExit();
}
finally
{
Environment.Exit(10);
}
})).Start();
}
private void HandleStatusChanges()
{
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
sub.Subscribe(Client.CurrentUser.Id + "_status.game_set", async (ch, game) =>
{
try
{
var obj = new { Name = default(string) };
obj = JsonConvert.DeserializeAnonymousType(game, obj);
await Client.SetGameAsync(obj.Name).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}, CommandFlags.FireAndForget);
sub.Subscribe(Client.CurrentUser.Id + "_status.stream_set", async (ch, streamData) =>
{
try
{
var obj = new { Name = "", Url = "" };
obj = JsonConvert.DeserializeAnonymousType(streamData, obj);
await Client.SetGameAsync(obj.Name, obj.Url, StreamType.Twitch).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}, CommandFlags.FireAndForget);
}
public Task SetGameAsync(string game)
{
var obj = new { Name = game };
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
return sub.PublishAsync(Client.CurrentUser.Id + "_status.game_set", JsonConvert.SerializeObject(obj));
}
public Task SetStreamAsync(string name, string url)
{
var obj = new { Name = name, Url = url };
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
return sub.PublishAsync(Client.CurrentUser.Id + "_status.game_set", JsonConvert.SerializeObject(obj));
}
//private readonly Dictionary<string, (IEnumerable<ModuleInfo> Modules, IEnumerable<Type> Types)> _loadedPackages = new Dictionary<string, (IEnumerable<ModuleInfo>, IEnumerable<Type>)>();
//private readonly SemaphoreSlim _packageLocker = new SemaphoreSlim(1, 1);
//public IEnumerable<string> LoadedPackages => _loadedPackages.Keys;
///// <summary>
///// Unloads a package
///// </summary>
///// <param name="name">Package name. Case sensitive.</param>
///// <returns>Whether the unload is successful.</returns>
//public async Task<bool> UnloadPackage(string name)
//{
// await _packageLocker.WaitAsync().ConfigureAwait(false);
// try
// {
// if (!_loadedPackages.Remove(name, out var data))
// return false;
// var modules = data.Modules;
// var types = data.Types;
// var i = 0;
// foreach (var m in modules)
// {
// await CommandService.RemoveModuleAsync(m).ConfigureAwait(false);
// i++;
// }
// _log.Info("Unloaded {0} modules.", i);
// if (types != null && types.Any())
// {
// i = 0;
// foreach (var t in types)
// {
// var obj = Services.Unload(t);
// if (obj is IUnloadableService s)
// await s.Unload().ConfigureAwait(false);
// i++;
// }
// _log.Info("Unloaded {0} types.", i);
// }
// using (var uow = _db.UnitOfWork)
// {
// uow.BotConfig.GetOrCreate().LoadedPackages.Remove(new LoadedPackage
// {
// Name = name,
// });
// }
// return true;
// }
// finally
// {
// _packageLocker.Release();
// }
//}
///// <summary>
///// Loads a package
///// </summary>
///// <param name="name">Name of the package to load. Case sensitive.</param>
///// <returns>Whether the load is successful.</returns>
//public async Task<bool> LoadPackage(string name)
//{
// await _packageLocker.WaitAsync().ConfigureAwait(false);
// try
// {
// if (_loadedPackages.ContainsKey(name))
// return false;
// var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList();
// using (var uow = _db.UnitOfWork)
// {
// AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
// }
// var domain = new Context();
// var package = domain.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory,
// "modules",
// $"NadekoBot.Modules.{name}",
// $"NadekoBot.Modules.{name}.dll"));
// //var package = Assembly.LoadFile(Path.Combine(AppContext.BaseDirectory,
// // "modules",
// // $"NadekoBot.Modules.{name}",
// // $"NadekoBot.Modules.{name}.dll"));
// var types = Services.LoadFrom(package);
// var added = await CommandService.AddModulesAsync(package).ConfigureAwait(false);
// var trs = LoadTypeReaders(package);
// /* i don't have to unload typereaders
// * (and there's no api for it)
// * because they get overwritten anyway, and since
// * the only time I'd unload typereaders, is when unloading a module
// * which means they won't have a chance to be used
// * */
// _log.Info("Loaded {0} modules and {1} types.", added.Count(), types.Count());
// _loadedPackages.Add(name, (added, types));
// using (var uow = _db.UnitOfWork)
// {
// uow.BotConfig.GetOrCreate().LoadedPackages.Add(new LoadedPackage
// {
// Name = name,
// });
// }
// return true;
// }
// finally
// {
// _packageLocker.Release();
// }
//}
}
}

View File

@ -1,331 +0,0 @@
using NadekoBot.Core.Services.Impl;
using NLog;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using NadekoBot.Common.ShardCom;
using StackExchange.Redis;
using Newtonsoft.Json;
using NadekoBot.Extensions;
using NadekoBot.Common.Collections;
using System.Linq;
using System.Collections.Generic;
namespace NadekoBot.Core.Services
{
public class ShardsCoordinator
{
private class ShardsCoordinatorQueue
{
private readonly object _locker = new object();
private readonly HashSet<int> _set = new HashSet<int>();
private readonly Queue<int> _queue = new Queue<int>();
public int Count => _queue.Count;
public void Enqueue(int i)
{
lock (_locker)
{
if (_set.Add(i))
_queue.Enqueue(i);
}
}
public bool TryPeek(out int id)
{
lock (_locker)
{
return _queue.TryPeek(out id);
}
}
public bool TryDequeue(out int id)
{
lock (_locker)
{
if (_queue.TryDequeue(out id))
{
_set.Remove(id);
return true;
}
}
return false;
}
}
private readonly BotCredentials _creds;
private readonly string _key;
private readonly Process[] _shardProcesses;
private readonly Logger _log;
private readonly int _curProcessId;
private readonly ConnectionMultiplexer _redis;
private ShardComMessage _defaultShardState;
private ShardsCoordinatorQueue _shardStartQueue =
new ShardsCoordinatorQueue();
private ConcurrentHashSet<int> _shardRestartWaitingList =
new ConcurrentHashSet<int>();
public ShardsCoordinator()
{
//load main stuff
LogSetup.SetupLogger(-1);
_log = LogManager.GetCurrentClassLogger();
_creds = new BotCredentials();
_log.Info("Starting NadekoBot v" + StatsService.BotVersion);
_key = _creds.RedisKey();
_redis = ConnectionMultiplexer.Connect("127.0.0.1");
new RedisImagesCache(_redis, _creds).Reload(); //reload images into redis
//setup initial shard statuses
_defaultShardState = new ShardComMessage()
{
ConnectionState = Discord.ConnectionState.Disconnected,
Guilds = 0,
Time = DateTime.UtcNow
};
var db = _redis.GetDatabase();
//clear previous statuses
db.KeyDelete(_key + "_shardstats");
_shardProcesses = new Process[_creds.TotalShards];
for (int i = 0; i < _creds.TotalShards; i++)
{
//add it to the list of shards which should be started
#if DEBUG
if (i > 0)
_shardStartQueue.Enqueue(i);
else
_shardProcesses[i] = Process.GetCurrentProcess();
#else
_shardStartQueue.Enqueue(i);
#endif
//set the shard's initial state in redis cache
var msg = _defaultShardState.Clone();
msg.ShardId = i;
//this is to avoid the shard coordinator thinking that
//the shard is unresponsive while starting up
var delay = 45;
#if GLOBAL_NADEKO
delay = 180;
#endif
msg.Time = DateTime.UtcNow + TimeSpan.FromSeconds(delay * (i + 1));
db.ListRightPush(_key + "_shardstats",
JsonConvert.SerializeObject(msg),
flags: CommandFlags.FireAndForget);
}
_curProcessId = Process.GetCurrentProcess().Id;
//subscribe to shardcoord events
var sub = _redis.GetSubscriber();
//send is called when shard status is updated. Every 7.5 seconds atm
sub.Subscribe(_key + "_shardcoord_send",
OnDataReceived,
CommandFlags.FireAndForget);
//called to stop the shard, although the shard will start again when it finds out it's dead
sub.Subscribe(_key + "_shardcoord_stop",
OnStop,
CommandFlags.FireAndForget);
//called kill the bot
sub.Subscribe(_key + "_die",
(ch, x) => Environment.Exit(0),
CommandFlags.FireAndForget);
}
private void OnStop(RedisChannel ch, RedisValue data)
{
var shardId = JsonConvert.DeserializeObject<int>(data);
OnStop(shardId);
}
private void OnStop(int shardId)
{
var db = _redis.GetDatabase();
var msg = _defaultShardState.Clone();
msg.ShardId = shardId;
db.ListSetByIndex(_key + "_shardstats",
shardId,
JsonConvert.SerializeObject(msg),
CommandFlags.FireAndForget);
var p = _shardProcesses[shardId];
_shardProcesses[shardId] = null;
try { p?.Kill(); } catch { }
try { p?.Dispose(); } catch { }
}
private void OnDataReceived(RedisChannel ch, RedisValue data)
{
var msg = JsonConvert.DeserializeObject<ShardComMessage>(data);
if (msg == null)
return;
var db = _redis.GetDatabase();
//sets the shard state
db.ListSetByIndex(_key + "_shardstats",
msg.ShardId,
data,
CommandFlags.FireAndForget);
if (msg.ConnectionState == Discord.ConnectionState.Disconnected
|| msg.ConnectionState == Discord.ConnectionState.Disconnecting)
{
_log.Error("!!! SHARD {0} IS IN {1} STATE !!!", msg.ShardId, msg.ConnectionState.ToString());
OnShardUnavailable(msg.ShardId);
}
else
{
// remove the shard from the waiting list if it's on it,
// because it's connected/connecting now
_shardRestartWaitingList.TryRemove(msg.ShardId);
}
return;
}
private void OnShardUnavailable(int shardId)
{
//if the shard is dc'd, add it to the restart waiting list
if (!_shardRestartWaitingList.Add(shardId))
{
//if it's already on the waiting list
//stop the shard
OnStop(shardId);
//add it to the start queue (start the shard)
_shardStartQueue.Enqueue(shardId);
//remove it from the waiting list
_shardRestartWaitingList.TryRemove(shardId);
}
}
public async Task RunAsync()
{
//this task will complete when the initial start of the shards
//is complete, but will keep running in order to restart shards
//which are disconnected for too long
TaskCompletionSource<bool> tsc = new TaskCompletionSource<bool>();
var _ = Task.Run(async () =>
{
do
{
//start a shard which is scheduled for start every 6 seconds
while (_shardStartQueue.TryPeek(out var id))
{
// if the shard is on the waiting list again
// remove it since it's starting up now
_shardRestartWaitingList.TryRemove(id);
//if the task is already completed,
//it means the initial shard starting is done,
//and this is an auto-restart
if (tsc.Task.IsCompleted)
{
_log.Warn("Auto-restarting shard {0}", id);
}
var p = StartShard(id);
var toRemove = _shardProcesses[id];
try { toRemove?.Kill(); } catch { }
try { toRemove?.Dispose(); } catch { }
_shardProcesses[id] = p;
_shardStartQueue.TryDequeue(out var __);
await Task.Delay(6000).ConfigureAwait(false);
}
tsc.TrySetResult(true);
await Task.Delay(6000).ConfigureAwait(false);
}
while (true);
// ^ keep checking for shards which need to be restarted
});
//restart unresponsive shards
_ = Task.Run(async () =>
{
//after all shards have started initially
await tsc.Task.ConfigureAwait(false);
while (true)
{
await Task.Delay(15000).ConfigureAwait(false);
try
{
var db = _redis.GetDatabase();
//get all shards which didn't communicate their status in the last 30 seconds
var all = db.ListRange(_creds.RedisKey() + "_shardstats")
.Select(x => JsonConvert.DeserializeObject<ShardComMessage>(x));
var statuses = all
.Where(x => x.Time < DateTime.UtcNow - TimeSpan.FromSeconds(30));
if (!statuses.Any())
{
#if DEBUG
for (var i = 0; i < _shardProcesses.Length; i++)
{
var p = _shardProcesses[i];
if (p == null || p.HasExited)
{
_log.Warn("Scheduling shard {0} for restart because it's process is stopped.", i);
_shardStartQueue.Enqueue(i);
}
}
#endif
}
else
{
foreach (var s in statuses)
{
OnStop(s.ShardId);
_shardStartQueue.Enqueue(s.ShardId);
//to prevent shards which are already scheduled for restart to be scheduled again
s.Time = DateTime.UtcNow + TimeSpan.FromSeconds(30 * _shardStartQueue.Count);
db.ListSetByIndex(_key + "_shardstats", s.ShardId,
JsonConvert.SerializeObject(s), CommandFlags.FireAndForget);
_log.Warn("Shard {0} is scheduled for a restart because it's unresponsive.", s.ShardId);
}
}
}
catch (Exception ex) { _log.Error(ex); throw; }
}
});
await tsc.Task.ConfigureAwait(false);
return;
}
private Process StartShard(int shardId)
{
return Process.Start(new ProcessStartInfo()
{
FileName = _creds.ShardRunCommand,
Arguments = string.Format(_creds.ShardRunArguments, shardId, _curProcessId, "")
});
// last "" in format is for backwards compatibility
// because current startup commands have {2} in them probably
}
public async Task RunAndBlockAsync()
{
try
{
await RunAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Error(ex);
foreach (var p in _shardProcesses)
{
try { p.Kill(); } catch { }
try { p.Dispose(); } catch { }
}
return;
}
await Task.Delay(-1);
}
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Core.Services
{
public class StandardConversions
{
public static double CelsiusToFahrenheit(double cel)
{
return cel * 1.8f + 32;
}
}
}

View File

@ -13,22 +13,21 @@ Compression=lzma2
SolidCompression=yes
OutputDir=userdocs:projekti/NadekoInstallerOutput
OutputBaseFilename=NadekoBot-setup-{#version}
AppReadmeFile=http://nadekobot.readthedocs.io/en/latest/Commands%20List/
AppReadmeFile=http://nadekobot.readthedocs.io/en/1.4/Commands%20List/
ArchitecturesInstallIn64BitMode=x64
UsePreviousSetupType=no
DisableWelcomePage=no
[Files]
;install
Source: "src\NadekoBot\bin\Release\netcoreapp2.0\{#target}\publish\*"; DestDir: "{app}\{#sysfolder}"; Permissions: users-full; Flags: recursesubdirs onlyifdoesntexist ignoreversion createallsubdirs; Excludes: "*.pdb, *.db"
Source: "src\NadekoBot\bin\Release\netcoreapp2.0\{#target}\publish\data\command_strings.json"; DestDir: "{app}\{#sysfolder}\data"; DestName: "command_strings.json"; Permissions: users-full; Flags: skipifsourcedoesntexist ignoreversion createallsubdirs recursesubdirs;
Source: "src\NadekoBot\bin\Release\netcoreapp1.1\{#target}\publish\*"; DestDir: "{app}\{#sysfolder}"; Permissions: users-full; Flags: recursesubdirs onlyifdoesntexist ignoreversion createallsubdirs; Excludes: "*.pdb, *.db"
;rename credentials example to credentials, but don't overwrite if it exists
;Source: "src\NadekoBot\bin\Release\netcoreapp2.0\{#target}\publish\credentials_example.json"; DestName: "credentials.json"; DestDir: "{app}\{#sysfolder}"; Permissions: users-full; Flags: skipifsourcedoesntexist onlyifdoesntexist;
;Source: "src\NadekoBot\bin\Release\netcoreapp1.1\{#target}\publish\credentials_example.json"; DestName: "credentials.json"; DestDir: "{app}\{#sysfolder}"; Permissions: users-full; Flags: skipifsourcedoesntexist onlyifdoesntexist;
;reinstall - i want to copy all files, but i don't want to overwrite any data files because users will lose their customization if they don't have a backup,
; and i don't want them to have to backup and then copy-merge into data folder themselves, or lose their currency images due to overwrite.
Source: "src\NadekoBot\bin\Release\netcoreapp2.0\{#target}\publish\*"; DestDir: "{app}\{#sysfolder}"; Permissions: users-full; Flags: recursesubdirs ignoreversion onlyifdestfileexists createallsubdirs; Excludes: "*.pdb, *.db, data\*, credentials.json";
Source: "src\NadekoBot\bin\Release\netcoreapp2.0\{#target}\publish\data\*"; DestDir: "{app}\{#sysfolder}\data"; Permissions: users-full; Flags: recursesubdirs onlyifdoesntexist createallsubdirs;
Source: "src\NadekoBot\bin\Release\netcoreapp1.1\{#target}\publish\*"; DestDir: "{app}\{#sysfolder}"; Permissions: users-full; Flags: recursesubdirs ignoreversion onlyifdestfileexists createallsubdirs; Excludes: "*.pdb, *.db, data\*, credentials.json";
Source: "src\NadekoBot\bin\Release\netcoreapp1.1\{#target}\publish\data\*"; DestDir: "{app}\{#sysfolder}\data"; Permissions: users-full; Flags: recursesubdirs onlyifdoesntexist createallsubdirs;
;readme
;Source: "readme"; DestDir: "{app}"; Flags: isreadme

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2005
VisualStudioVersion = 15.0.26430.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}"
EndProject
@ -9,13 +9,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
global.json = global.json
NadekoBot.iss = NadekoBot.iss
Performance1.psess = Performance1.psess
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot", "src\NadekoBot\NadekoBot.csproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Core", "NadekoBot.Core\NadekoBot.Core.csproj", "{A6CCEFBD-DCF2-482C-9643-47664683548F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -29,21 +26,11 @@ Global
{45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.Build.0 = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.Build.0 = Release|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.GlobalNadeko|Any CPU.ActiveCfg = Release|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.GlobalNadeko|Any CPU.Build.0 = Release|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{A6CCEFBD-DCF2-482C-9643-47664683548F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
EndGlobalSection
EndGlobal

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="Imagesharp" value="https://www.myget.org/F/imagesharp/api/v3/index.json" />
<add key="Discord.Net" value="https://www.myget.org/F/discord-net/api/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Kwoth-myget" value="https://www.myget.org/F/kwoth/api/v3/index.json" />
<add key="ImageSharp" value="https://www.myget.org/F/imagesharp/api/v3/index.json" />
<add key="Kwoth" value="https://www.myget.org/F/kwoth/api/v3/index.json" />
</packageSources>
</configuration>

4
build.ps1 Normal file
View File

@ -0,0 +1,4 @@
appveyor-retry dotnet restore NadekoBot.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
dotnet build NadekoBot.sln -c "Release" /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }

View File

@ -1,6 +1,6 @@
You can support the project on patreon: <https://patreon.com/nadekobot> or paypal: <https://paypal.me/Kwoth>
##Table Of Contents
##Table of contents
- [Help](#help)
- [Administration](#administration)
- [CustomReactions](#customreactions)
@ -16,7 +16,7 @@ You can support the project on patreon: <https://patreon.com/nadekobot> or paypa
### Administration
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.delmsgoncmd` | Toggles the automatic deletion of the user's successful command message to prevent chat flood. **Requires Administrator server permission.** | `.delmsgoncmd`
`.setrole` `.sr` | Sets a role for a given user. **Requires ManageRoles server permission.** | `.sr @User Guest`
@ -36,21 +36,17 @@ Command and aliases | Description | Usage
`.setchanlname` `.schn` | Changes the name of the current channel. **Requires ManageChannels server permission.** | `.schn NewName`
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. **Requires MentionEveryone server permission.** | `.menro RoleName`
`.donators` | List of the lovely people who donated to keep this project alive. | `.donators`
`.donadd` | Add a donator to the database. **Bot Owner Only** | `.donadd Donate Amount`
`.autoassignrole` `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Requires ManageRoles server permission.** | `.aar to disable` or `.aar Role Name to enable`
`.execsql` | Executes an sql command and returns the number of affected rows. Dangerous. **Bot Owner Only** | `.execsql UPDATE Currency SET Amount=Amount+1234`
`.deletewaifus` | Deletes everything from WaifuUpdates and WaifuInfo tables. **Bot Owner Only** | `.deletewaifus`
`.deletecurrency` | Deletes everything from Currency and CurrencyTransactions. **Bot Owner Only** | `.deletecurrency`
`.deleteplaylists` | Deletes everything from MusicPlaylists. **Bot Owner Only** | `.deleteplaylists`
`.deleteexp` | deleteexp **Bot Owner Only** | `deleteexp`
`.donadd` | Add a donator to the database. **Bot owner only** | `.donadd Donate Amount`
`.autoassignrole` `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Requires ManageRoles server permission.** | `.aar` to disable, `.aar Role Name` to enable
`.gvc` | Toggles game voice channel feature in the voice channel you're currently in. Users who join the game voice channel will get automatically redirected to the voice channel with the name of their current game, if it exists. Can't move users to channels that the bot has no connect permission for. One per server. **Requires Administrator server permission.** | `.gvc`
`.languageset` `.langset` | Sets this server's response language. If bot's response strings have been translated to that language, bot will use that language in this server. Reset by using `default` as the locale name. Provide no arguments to see currently set language. | `.langset de-DE ` or `.langset default`
`.langsetdefault` `.langsetd` | Sets the bot's default response language. All servers which use a default locale will use this one. Setting to `default` will use the host's current culture. Provide no arguments to see currently set language. | `.langsetd en-US` or `.langsetd default`
`.languageslist` `.langli` | List of languages for which translation (or part of it) exist atm. | `.langli`
`.logserver` | Enables or Disables ALL log events. If enabled, all log events will log to this channel. **Requires Administrator server permission.** **Bot Owner Only** | `.logserver enable` or `.logserver disable`
`.logignore` | Toggles whether the `.logserver` command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot Owner Only** | `.logignore`
`.logevents` | Shows a list of all events you can subscribe to with `.log` **Requires Administrator server permission.** **Bot Owner Only** | `.logevents`
`.log` | Toggles logging event. Disables it if it is active anywhere on the server. Enables if it isn't active. Use `.logevents` to see a list of all events you can subscribe to. **Requires Administrator server permission.** **Bot Owner Only** | `.log userpresence` or `.log userbanned`
`.logserver` | Enables or Disables ALL log events. If enabled, all log events will log to this channel. **Requires Administrator server permission.** **Bot owner only** | `.logserver enable` or `.logserver disable`
`.logignore` | Toggles whether the `.logserver` command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot owner only** | `.logignore`
`.logevents` | Shows a list of all events you can subscribe to with `.log` **Requires Administrator server permission.** **Bot owner only** | `.logevents`
`.log` | Toggles logging event. Disables it if it is active anywhere on the server. Enables if it isn't active. Use `.logevents` to see a list of all events you can subscribe to. **Requires Administrator server permission.** **Bot owner only** | `.log userpresence` or `.log userbanned`
`.migratedata` | Migrate data from old bot configuration **Bot owner only** | `.migratedata`
`.setmuterole` | Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. **Requires ManageRoles server permission.** | `.setmuterole Silenced`
`.mute` | Mutes a mentioned user both from speaking and chatting. You can also specify time in minutes (up to 1440) for how long the user should be muted. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone` or `.mute 30 @Someone`
`.unmute` | Unmutes a mentioned user previously muted with `.mute` command. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.unmute @Someone`
@ -58,51 +54,48 @@ Command and aliases | Description | Usage
`.chatunmute` | Removes a mute role previously set on a mentioned user with `.chatmute` which prevented him from chatting in text channels. **Requires ManageRoles server permission.** | `.chatunmute @Someone`
`.voicemute` | Prevents a mentioned user from speaking in voice channels. **Requires MuteMembers server permission.** | `.voicemute @Someone`
`.voiceunmute` | Gives a previously voice-muted user a permission to speak. **Requires MuteMembers server permission.** | `.voiceunmute @Someguy`
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot Owner Only** | `.ropl`
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: `%servers%`, `%users%`, `%playing%`, `%queued%`, `%time%`, `%shardid%`, `%shardcount%`, `%shardguilds%`. **Bot Owner Only** | `.adpl`
`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner Only** | `.lipl`
`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. **Bot Owner Only** | `.rmpl`
`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot owner only** | `.ropl`
`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: `%servers%`, `%users%`, `%playing%`, `%queued%`, `%time%`, `%shardid%`, `%shardcount%`, `%shardguilds%`. **Bot owner only** | `.adpl`
`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. **Bot owner only** | `.lipl`
`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. **Bot owner only** | `.rmpl`
`.prefix` | Sets this server's prefix for all bot commands. Provide no arguments to see the current server prefix. | `.prefix +`
`.defprefix` | Sets bot's default prefix for all bot commands. Provide no arguments to see the current default prefix. This will not change this server's current prefix. **Bot Owner Only** | `.defprefix +`
`.defprefix` | Sets bot's default prefix for all bot commands. Provide no arguments to see the current default prefix. This will not change this server's current prefix. **Bot owner only** | `.defprefix +`
`.antiraid` | Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute) **Requires Administrator server permission.** | `.antiraid 5 20 Kick`
`.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. If you're using mute, you can add a number of seconds at the end to use a timed mute. Max message count is 10. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.antispamignore` | Toggles whether antispam ignores current channel. Antispam must be enabled. **Requires Administrator server permission.** | `.antispamignore`
`.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.antispamignore` | Toggles whether antispam ignores current channel. Antispam must be enabled. | `.antispamignore`
`.antilist` `.antilst` | Shows currently enabled protection features. | `.antilist`
`.prune` `.clear` | `.prune` removes all Nadeko's messages in the last 100 messages. `.prune X` removes last `X` number of messages from the channel (up to 100). `.prune @Someone` removes all Someone's messages in the last 100 messages. `.prune @Someone X` removes last `X` number of 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode`
`.slowmodewl` | Ignores a role or a user from the slowmode feature. **Requires ManageMessages server permission.** | `.slowmodewl SomeRole` or `.slowmodewl AdminDude`
`.adsarm` | Toggles the automatic deletion of confirmations for `.iam` and `.iamn` commands. **Requires ManageMessages server permission.** | `.adsarm`
`.asar` | Adds a role to the list of self-assignable roles. You can also specify a group. If 'Exclusive self-assignable roles' feature is enabled, users will be able to pick one role per group. **Requires ManageRoles server permission.** | `.asar Gamer` or `.asar 1 Alliance` or `.asar 1 Horde`
`.asar` | Adds a role to the list of self-assignable roles. **Requires ManageRoles server permission.** | `.asar Gamer`
`.rsar` | Removes a specified role from the list of self-assignable roles. **Requires ManageRoles server permission.** | `.rsar`
`.lsar` | Lists all self-assignable roles. | `.lsar`
`.togglexclsar` `.tesar` | Toggles whether the self-assigned roles are exclusive. While enabled, users can only have one self-assignable role per group. **Requires ManageRoles server permission.** | `.tesar`
`.togglexclsar` `.tesar` | Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles) **Requires ManageRoles server permission.** | `.tesar`
`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer`
`.iamnot` `.iamn` | Removes a specified role from you. Role must be on a list of self-assignable roles. | `.iamn Gamer`
`.scadd` | Adds a command to the list of commands which will be executed automatically in the current channel, in the order they were added in, by the bot when it startups up. **Bot Owner Only** | `.scadd .stats`
`.sclist` | Lists all startup commands in the order they will be executed in. **Bot Owner Only** | `.sclist`
`.wait` | Used only as a startup command. Waits a certain number of miliseconds before continuing the execution of the following startup commands. **Bot Owner Only** | `.wait 3000`
`.scrm` | Removes a startup command with the provided command text. **Bot Owner Only** | `.scrm .stats`
`.scclr` | Removes all startup commands. **Bot Owner Only** | `.scclr`
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot Owner Only** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file **Bot Owner Only** | `.fwtoall`
`.shardstats` | Stats for shards. Paginated with 25 shards per page. | `.shardstats` or `.shardstats 2`
`.restartshard` | Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors. **Bot Owner Only** | `.restartshard 2`
`.leave` | Makes Nadeko leave the server. Either server name or server ID is required. **Bot Owner Only** | `.leave 123123123331`
`.die` | Shuts the bot down. **Bot Owner Only** | `.die`
`.restart` | Restarts the bot. Might not work. **Bot Owner Only** | `.restart`
`.setname` `.newnm` | Gives the bot a new name. **Bot Owner Only** | `.newnm BotName`
`.scadd` | Adds a command to the list of commands which will be executed automatically in the current channel, in the order they were added in, by the bot when it startups up. **Bot owner only** | `.scadd .stats`
`.sclist` | Lists all startup commands in the order they will be executed in. **Bot owner only** | `.sclist`
`.wait` | Used only as a startup command. Waits a certain number of miliseconds before continuing the execution of the following startup commands. **Bot owner only** | `.wait 3000`
`.scrm` | Removes a startup command with the provided command text. **Bot owner only** | `.scrm .stats`
`.scclr` | Removes all startup commands. **Bot owner only** | `.scclr`
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot owner only** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file **Bot owner only** | `.fwtoall`
`.leave` | Makes Nadeko leave the server. Either server name or server ID is required. **Bot owner only** | `.leave 123123123331`
`.die` | Shuts the bot down. **Bot owner only** | `.die`
`.setname` `.newnm` | Gives the bot a new name. **Bot owner only** | `.newnm BotName`
`.setnick` | Changes the nickname of the bot on this server. You can also target other users to change their nickname. **Requires ManageNicknames server permission.** | `.setnick BotNickname` or `.setnick @SomeUser New Nickname`
`.setstatus` | Sets the bot's status. (Online/Idle/Dnd/Invisible) **Bot Owner Only** | `.setstatus Idle`
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only** | `.setav http://i.imgur.com/xTG3a1I.jpg`
`.setgame` | Sets the bots game. **Bot Owner Only** | `.setgame with snakes`
`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot Owner Only** | `.setstream TWITCHLINK Hello`
`.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prefix the channel id with `c:` and the user id with `u:`. **Bot Owner Only** | `.send serverid|c:channelid message` or `.send serverid|u:userid message`
`.reloadimages` | Reloads images bot is using. Safe to use even when bot is being used heavily. **Bot Owner Only** | `.reloadimages`
`.setstatus` | Sets the bot's status. (Online/Idle/Dnd/Invisible) **Bot owner only** | `.setstatus Idle`
`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot owner only** | `.setav http://i.imgur.com/xTG3a1I.jpg`
`.setgame` | Sets the bots game. **Bot owner only** | `.setgame with snakes`
`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot owner only** | `.setstream TWITCHLINK Hello`
`.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prefix the channel id with `c:` and the user id with `u:`. **Bot owner only** | `.send serverid|c:channelid message` or `.send serverid|u:userid message`
`.reloadimages` | Reloads images bot is using. Safe to use even when bot is being used heavily. **Bot owner only** | `.reloadimages`
`.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30`
`.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet`
`.greetmsg` | Sets a new join announcement message which will be shown in the server's channel. Type `%user%` if you want to mention the new member. Using it with no message will show the current greet message. You can use embed json from <http://nadekobot.me/embedbuilder/> instead of a regular text, if you want the message to be embedded. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.`
`.greetdm` | Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled). **Requires ManageServer server permission.** | `.greetdm`
`.greetdmmsg` | Sets a new join announcement message which will be sent to the user who joined. Type `%user%` if you want to mention the new member. Using it with no message will show the current DM greet message. You can use embed json from <http://nadekobot.me/embedbuilder/> instead of a regular text, if you want the message to be embedded. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`
`.greetdmmsg` | Sets a new join announcement message which will be sent to the user who joined. Type `%user%` if you want to mention the new member. Using it with no message will show the current DM greet message. You can use embed json from <http://nadekobot.me/embedbuilder/> instead of a regular text, if you want the message to be embedded. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`.
`.bye` | Toggles anouncements on the current channel when someone leaves the server. **Requires ManageServer server permission.** | `.bye`
`.byemsg` | Sets a new leave announcement message. Type `%user%` if you want to show the name the user who left. Type `%id%` to show id. Using this command with no message will show the current bye message. You can use embed json from <http://nadekobot.me/embedbuilder/> instead of a regular text, if you want the message to be embedded. **Requires ManageServer server permission.** | `.byemsg %user% has left.`
`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set it to `0` to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30`
@ -126,10 +119,9 @@ Command and aliases | Description | Usage
###### [Back to ToC](#table-of-contents)
### CustomReactions
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.addcustreact` `.acr` | Add a custom reaction with a trigger and a response. Running this command in server requires the Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: <http://nadekobot.readthedocs.io/en/latest/Custom%20Reactions/> | `.acr "hello" Hi there %user%`
`.editcustreact` `.ecr` | Edits the custom reaction's response given its ID. | `.ecr 123 I'm a magical girl`
`.listcustreact` `.lcr` | Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions. | `.lcr 1` or `.lcr all`
`.listcustreactg` `.lcrg` | Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcrg 1`
`.showcustreact` `.scr` | Shows a custom reaction's response on a given ID. | `.scr 1`
@ -137,31 +129,27 @@ Command and aliases | Description | Usage
`.crca` | Toggles whether the custom reaction will trigger if the triggering message contains the keyword (instead of only starting with it). | `.crca 44`
`.crdm` | Toggles whether the response message of the custom reaction will be sent as a direct message. | `.crdm 44`
`.crad` | Toggles whether the message triggering the custom reaction will be automatically deleted. | `.crad 59`
`.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. **Bot Owner Only** | `.crstatsclear` or `.crstatsclear rng`
`.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. **Bot owner only** | `.crstatsclear` or `.crstatsclear rng`
`.crstats` | Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `.crstatsclear` to reset the counters. | `.crstats` or `.crstats 3`
###### [Back to ToC](#table-of-contents)
### Gambling
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.timely` | Use to claim your 'timely' currency. Bot owner has to specify the amount and the period on how often you can claim your currency. | `.timely`
`.timelyreset` | Resets all user timeouts on `.timely` command. **Bot Owner Only** | `.timelyreset`
`.timelyset` | Sets the 'timely' currency allowance amount for users. Second argument is period in hours, default is 24 hours. **Bot Owner Only** | `.timelyset 100` or `.timelyset 50 12`
`.raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `.raffle` or `.raffle RoleName`
`.$` `.currency` `.$$` `.$$$` `.cash` `.cur` | Check how much currency a person has. (Defaults to yourself) | `.$` or `.$ @SomeGuy`
`.give` | Give someone a certain amount of currency. | `.give 1 @SomeGuy`
`.award` | Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role. **Bot Owner Only** | `.award 100 @person` or `.award 5 Role Of Gamblers`
`.take` | Takes a certain amount of currency from someone. **Bot Owner Only** | `.take 1 @SomeGuy`
`.award` | Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role. **Bot owner only** | `.award 100 @person` or `.award 5 Role Of Gamblers`
`.take` | Takes a certain amount of currency from someone. **Bot owner only** | `.take 1 @SomeGuy`
`.betroll` `.br` | Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x4 and 100 x10. | `.br 5`
`.leaderboard` `.lb` | Displays the bot's currency leaderboard. | `.lb`
`.race` | Starts a new animal race. | `.race`
`.joinrace` `.jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `.jr` or `.jr 5`
`.startevent` | Starts one of the events seen on public nadeko. `reaction` and `sneakygamestatus` are the only 2 available now. **Bot Owner Only** | `.startevent reaction`
`.rafflecur` | Starts or joins a currency raffle with a specified amount. Users who join the raffle will lose the amount of currency specified and add it to the pot. After 30 seconds, random winner will be selected who will receive the whole pot. There is also a `mixed` mode in which the users will be able to join the game with any amount of currency, and have their chances be proportional to the amount they've bet. | `.rafflecur 20` or `.rafflecur mixed 15`
`.startevent` | Starts one of the events seen on public nadeko. **Bot owner only** | `.startevent flowerreaction`
`.roll` | Rolls 0-100. If you supply a number `X` it rolls up to 30 normal dice. If you split 2 numbers with letter `d` (`xdy`) it will roll `X` dice from 1 to `y`. `Y` can be a letter 'F' if you want to roll fate dice instead of dnd. | `.roll` or `.roll 7` or `.roll 3d5` or `.roll 5dF`
`.rolluo` | Rolls `X` normal dice (up to 30) unordered. If you split 2 numbers with letter `d` (`xdy`) it will roll `X` dice from 1 to `y`. | `.rolluo` or `.rolluo 7` or `.rolluo 3d5`
`.nroll` | Rolls in a given range. If you specify just one number instead of the range, it will role from 0 to that number. | `.nroll 5` or `.nroll 5-15`
`.nroll` | Rolls in a given range. | `.nroll 5` (rolls 0-5) or `.nroll 5-15`
`.draw` | Draws a card from this server's deck. You can draw up to 10 cards by supplying a number of cards to draw. | `.draw` or `.draw 5`
`.drawnew` | Draws a card from the NEW deck of cards. You can draw up to 10 cards by supplying a number of cards to draw. | `.drawnew` or `.drawnew 5`
`.deckshuffle` `.dsh` | Reshuffles all cards back into the deck. | `.dsh`
@ -172,11 +160,10 @@ Command and aliases | Description | Usage
`.shopadd` | Adds an item to the shop by specifying type price and name. Available types are role and list. **Requires Administrator server permission.** | `.shopadd role 1000 Rich`
`.shoplistadd` | Adds an item to the list of items for sale in the shop entry given the index. You usually want to run this command in the secret channel, so that the unique items are not leaked. **Requires Administrator server permission.** | `.shoplistadd 1 Uni-que-Steam-Key`
`.shoprem` `.shoprm` | Removes an item from the shop by its ID. **Requires Administrator server permission.** | `.shoprm 1`
`.slotstats` | Shows the total stats of the slot command for this bot's session. **Bot Owner Only** | `.slotstats`
`.slottest` | Tests to see how much slots payout for X number of plays. **Bot Owner Only** | `.slottest 1000`
`.slotstats` | Shows the total stats of the slot command for this bot's session. **Bot owner only** | `.slotstats`
`.slottest` | Tests to see how much slots payout for X number of plays. **Bot owner only** | `.slottest 1000`
`.slot` | Play Nadeko slots. Max bet is 9999. 1.5 second cooldown per user. | `.slot 5`
`.claimwaifu` `.claim` | Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `.affinity` towards you. | `.claim 50 @Himesama`
`.waifutransfer` | Transfer the ownership of one of your waifus to another user. You must pay 10% of your waifu's value. | `.waifutransfer @ExWaifu @NewOwner`
`.divorce` | Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. | `.divorce @CheatingSloot`
`.affinity` | Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `.claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown. | `.affinity @MyHusband` or `.affinity`
`.waifus` `.waifulb` | Shows top 9 waifus. You can specify another page to show other waifus. | `.waifus` or `.waifulb 3`
@ -187,10 +174,10 @@ Command and aliases | Description | Usage
###### [Back to ToC](#table-of-contents)
### Games
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.choose` | Chooses a thing from a list of things | `.choose Get up;Sleep;Sleep more`
`.8ball` | Ask the 8ball a yes/no question. | `.8ball Is b1nzy a nice guy?`
`.8ball` | Ask the 8ball a yes/no question. | `.8ball should I do something`
`.rps` | Play a game of Rocket-Paperclip-Scissors with Nadeko. | `.rps scissors`
`.rategirl` | Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart. | `.rategirl @SomeGurl`
`.linux` | Prints a customizable Linux interjection | `.linux Spyware Windows`
@ -210,10 +197,10 @@ Command and aliases | Description | Usage
`.pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `.pollend`
`.typestart` | Starts a typing contest. | `.typestart`
`.typestop` | Stops a typing contest on the current channel. | `.typestop`
`.typeadd` | Adds a new article to the typing contest. **Bot Owner Only** | `.typeadd wordswords`
`.typeadd` | Adds a new article to the typing contest. **Bot owner only** | `.typeadd wordswords`
`.typelist` | Lists added typing articles with their IDs. 15 per page. | `.typelist` or `.typelist 3`
`.typedel` | Deletes a typing article given the ID. **Bot Owner Only** | `.typedel 3`
`.tictactoe` `.ttt` | Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move. | `.ttt`
`.typedel` | Deletes a typing article given the ID. **Bot owner only** | `.typedel 3`
`.tictactoe` `.ttt` | Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move. | .ttt
`.trivia` `.t` | Starts a game of trivia. You can add `nohint` to prevent hints. First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `.t` or `.t 5 nohint`
`.tl` | Shows a current trivia leaderboard. | `.tl`
`.tq` | Quits current trivia after current question. | `.tq`
@ -221,19 +208,19 @@ Command and aliases | Description | Usage
###### [Back to ToC](#table-of-contents)
### Help
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.modules` `.mdls` | Lists all bot modules. | `.modules`
`.commands` `.cmds` | List all of the bot's commands from a certain module. You can either specify the full name or only the first few letters of the module name. | `.commands Administration` or `.cmds Admin`
`.help` `.h` | Either shows a help for a single command, or DMs you help link if no arguments are specified. | `.h .cmds` or `.h`
`.hgit` | Generates the commandlist.md file. **Bot Owner Only** | `.hgit`
`.hgit` | Generates the commandlist.md file. **Bot owner only** | `.hgit`
`.readme` `.guide` | Sends a readme and a guide links to the channel. | `.readme` or `.guide`
`.donate` | Instructions for helping the project financially. | `.donate`
###### [Back to ToC](#table-of-contents)
### Music
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.play` `.start` | If no arguments are specified, acts as `.next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `.q` command | `.play` or `.play 5` or `.play Dream Of Venice`
`.queue` `.q` `.yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `.q Dream Of Venice`
@ -242,14 +229,13 @@ Command and aliases | Description | Usage
`.listqueue` `.lq` | Lists 10 currently queued songs per page. Default page is 1. | `.lq` or `.lq 2`
`.next` `.n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if .rcs or .rpl is enabled. | `.n` or `.n 5`
`.stop` `.s` | Stops the music and preserves the current song index. Stays in the channel. | `.s`
`.autodisconnect` `.autodc` | Toggles whether the bot should disconnect from the voice channel once it's done playing all of the songs. | `.autodc`
`.destroy` `.d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `.d`
`.pause` `.p` | Pauses or Unpauses the song. | `.p`
`.volume` `.vol` | Sets the music playback volume (0-100%) | `.vol 50`
`.defvol` `.dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `.dv 80`
`.songremove` `.srm` | Remove a song by its # in the queue, or 'all' to remove all songs from the queue and reset the song index. | `.srm 5`
`.playlists` `.pls` | Lists all playlists. Paginated, 20 per page. Default page is 0. | `.pls 1`
`.deleteplaylist` `.delpls` | Deletes a saved playlist using its id. Works only if you made it or if you are the bot owner. | `.delpls 5`
`.deleteplaylist` `.delpls` | Deletes a saved playlist. Works only if you made it or if you are the bot owner. | `.delpls animu-5`
`.save` | Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes. | `.save classical1`
`.load` | Loads a saved playlist using its ID. Use `.pls` to list all saved playlists and `.save` to save new ones. | `.load 5`
`.fairplay` `.fp` | Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `.fp`
@ -258,10 +244,10 @@ Command and aliases | Description | Usage
`.soundcloudpl` `.scpl` | Queue a Soundcloud playlist using a link. | `.scpl soundcloudseturl`
`.nowplaying` `.np` | Shows the song that the bot is currently playing. | `.np`
`.shuffle` `.sh` `.plsh` | Shuffles the current playlist. | `.plsh`
`.playlist` `.pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `.pl <youtube_playlist_link>`
`.playlist` `.pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `.pl playlist link or name`
`.radio` `.ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) | `.ra radio link here`
`.local` `.lo` | Queues a local file by specifying a full path. **Bot Owner Only** | `.lo C:/music/mysong.mp3`
`.localplaylst` `.lopl` | Queues all songs from a directory. **Bot Owner Only** | `.lopl C:/music/classical`
`.local` `.lo` | Queues a local file by specifying a full path. **Bot owner only** | `.lo C:/music/mysong.mp3`
`.localplaylst` `.lopl` | Queues all songs from a directory. **Bot owner only** | `.lopl C:/music/classical`
`.move` `.mv` | Moves the bot to your voice channel. (works only if music is already playing) | `.mv`
`.movesong` `.ms` | Moves a song from one position to another. | `.ms 5>3`
`.setmaxqueue` `.smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `.smq 50` or `.smq`
@ -274,12 +260,10 @@ Command and aliases | Description | Usage
###### [Back to ToC](#table-of-contents)
### NSFW
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `.autohentai 30 yuri|tail|long_hair` or `.autohentai`
`.autoboobs` | Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `.autoboobs 30` or `.autoboobs`
`.autobutts` | Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `.autobutts 30` or `.autobutts`
`.hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `.hentai yuri`
`.autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `.autohentai 30 yuri|tail|long_hair` or `.autohentai`
`.hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `.hentaibomb yuri`
`.yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `.yandere tag1+tag2`
`.konachan` | Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. | `.konachan yuri`
@ -290,12 +274,12 @@ Command and aliases | Description | Usage
`.boobs` | Real adult content. | `.boobs`
`.butts` `.ass` `.butt` | Real adult content. | `.butts` or `.ass`
`.nsfwtagbl` `.nsfwtbl` | Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags. | `.nsfwtbl poop`
`.nsfwcc` | Clears nsfw cache. **Bot Owner Only** | `.nsfwcc`
`.nsfwcc` | Clears nsfw cache. **Bot owner only** | `.nsfwcc`
###### [Back to ToC](#table-of-contents)
### Permissions
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.verbose` `.v` | Sets whether to show when a command/module is blocked. | `.verbose true`
`.permrole` `.pr` | Sets a role which can change permissions. Supply no parameters to see the current one. Default is 'Nadeko'. | `.pr role`
@ -314,9 +298,9 @@ Command and aliases | Description | Usage
`.allrolemdls` `.arm` | Enable or disable all modules for a specific role. | `.arm [enable/disable] MyRole`
`.allusrmdls` `.aum` | Enable or disable all modules for a specific user. | `.aum enable @someone`
`.allsrvrmdls` `.asm` | Enable or disable all modules for your server. | `.asm [enable/disable]`
`.ubl` | Either [add]s or [rem]oves a user specified by a Mention or an ID from a blacklist. **Bot Owner Only** | `.ubl add @SomeUser` or `.ubl rem 12312312313`
`.cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner Only** | `.cbl rem 12312312312`
`.sbl` | Either [add]s or [rem]oves a server specified by a Name or an ID from a blacklist. **Bot Owner Only** | `.sbl add 12312321312` or `.sbl rem SomeTrashServer`
`.ubl` | Either [add]s or [rem]oves a user specified by a Mention or an ID from a blacklist. **Bot owner only** | `.ubl add @SomeUser` or `.ubl rem 12312312313`
`.cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot owner only** | `.cbl rem 12312312312`
`.sbl` | Either [add]s or [rem]oves a server specified by a Name or an ID from a blacklist. **Bot owner only** | `.sbl add 12312321312` or `.sbl rem SomeTrashServer`
`.cmdcooldown` `.cmdcd` | Sets a cooldown per user for a command. Set it to 0 to remove the cooldown. | `.cmdcd "some cmd" 5`
`.allcmdcooldowns` `.acmdcds` | Shows a list of all commands and their respective cooldowns. | `.acmdcds`
`.srvrfilterinv` `.sfi` | Toggles automatic deletion of invites posted in the server. Does not affect the Bot Owner. | `.sfi`
@ -325,31 +309,29 @@ Command and aliases | Description | Usage
`.chnlfilterwords` `.cfw` | Toggles automatic deletion of messages containing filtered words on the channel. Does not negate the `.srvrfilterwords` enabled setting. Does not affect the Bot Owner. | `.cfw`
`.fw` | Adds or removes (if it exists) a word from the list of filtered words. Use`.sfw` or `.cfw` to toggle filtering. | `.fw poop`
`.lstfilterwords` `.lfw` | Shows a list of filtered words. | `.lfw`
`.listglobalperms` `.lgp` | Lists global permissions set by the bot owner. **Bot Owner Only** | `.lgp`
`.globalmodule` `.gmod` | Toggles whether a module can be used on any server. **Bot Owner Only** | `.gmod nsfw`
`.globalcommand` `.gcmd` | Toggles whether a command can be used on any server. **Bot Owner Only** | `.gcmd .stats`
`.listglobalperms` `.lgp` | Lists global permissions set by the bot owner. **Bot owner only** | `.lgp`
`.globalmodule` `.gmod` | Toggles whether a module can be used on any server. **Bot owner only** | `.gmod nsfw`
`.globalcommand` `.gcmd` | Toggles whether a command can be used on any server. **Bot owner only** | `.gcmd .stats`
`.resetperms` | Resets the bot's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms`
`.resetglobalperms` | Resets global permissions set by bot owner. **Bot Owner Only** | `.resetglobalperms`
`.resetglobalperms` | Resets global permissions set by bot owner. **Bot owner only** | `.resetglobalperms`
###### [Back to ToC](#table-of-contents)
### Pokemon
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.attack` | Attacks a target with the given move. Use `.movelist` to see a list of moves your type can use. | `.attack "vine whip" @someguy`
`.movelist` `.ml` | Lists the moves you are able to use | `.ml`
`.heal` | Heals someone. Revives those who fainted. Costs one Currency. | `.heal @someone`
`.heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower. | `.heal @someone`
`.type` | Get the poketype of the target. | `.type @someone`
`.settype` | Set your poketype. Costs one Currency. Provide no arguments to see a list of available types. | `.settype fire` or `.settype`
`.settype` | Set your poketype. Costs a NadekoFlower. Provide no arguments to see a list of available types. | `.settype fire` or `.settype`
###### [Back to ToC](#table-of-contents)
### Searches
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.lolban` | Shows top banned champions ordered by ban rate. | `.lolban`
`.rip` | rip | `rip`
`.say` | Bot will send the message you typed in this channel. Supports embeds. **Requires ManageMessages server permission.** | `.say hi`
`.weather` `.we` | Shows weather data for a specified city. You can also specify a country after a comma. | `.we Moscow, RU`
`.time` | Shows the current time and timezone in the specified location. | `.time London, UK`
`.youtube` `.yt` | Searches youtubes and shows the first result | `.yt query`
@ -376,13 +358,9 @@ Command and aliases | Description | Usage
`.videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `.videocall "@the First" "@Xyz"`
`.avatar` `.av` | Shows a mentioned person's avatar. | `.av @SomeGuy`
`.wikia` | Gives you back a wikia link | `.wikia mtg Vigilance` or `.wikia mlp Dashy`
`.novel` | Searches for a novel on `http://novelupdates.com/`. You have to provide an exact name. | `.novel the nine cauldrons`
`.mal` | Shows basic info from a MyAnimeList profile. | `.mal straysocks`
`.anime` `.ani` `.aq` | Queries anilist for an anime and shows the first result. | `.ani aquarion evol`
`.manga` `.mang` `.mq` | Queries anilist for a manga and shows the first result. | `.mq Shingeki no kyojin`
`.feed` `.feedadd` | Subscribes to a feed. Bot will post an update up to once every 10 seconds. You can have up to 10 feeds on one server. All feeds must have unique URLs. **Requires ManageMessages server permission.** | `.feed https://www.rt.com/rss/`
`.feedremove` `.feedrm` `.feeddel` | Stops tracking a feed on the given index. Use `.feeds` command to see a list of feeds and their indexes. **Requires ManageMessages server permission.** | `.feedremove 3`
`.feeds` `.feedlist` | Shows the list of feeds you've subscribed to on this server. **Requires ManageMessages server permission.** | `.feeds`
`.yomama` `.ym` | Shows a random joke from <http://api.yomomma.info/> | `.ym`
`.randjoke` `.rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random> | `.rj`
`.chucknorris` `.cn` | Shows a random Chuck Norris joke from <http://api.icndb.com/jokes/random/> | `.cn`
@ -405,7 +383,7 @@ Command and aliases | Description | Usage
`.removestream` `.rms` | Removes notifications of a certain streamer from a certain platform on this channel. **Requires ManageMessages server permission.** | `.rms Twitch SomeGuy` or `.rms mixer SomeOtherGuy`
`.checkstream` `.cs` | Checks if a user is online on a certain streaming platform. | `.cs twitch MyFavStreamer`
`.translate` `.trans` | Translates from>to text. From the given language to the destination language. | `.trans en>fr Hello`
`.autotrans` `.at` | Starts automatic translation of all messages by users who set their `.atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot Owner Only** | `.at` or `.at del`
`.autotrans` `.at` | Starts automatic translation of all messages by users who set their `.atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot owner only** | `.at` or `.at del`
`.autotranslang` `.atl` | Sets your source and target language to be used with `.at`. Specify no arguments to remove previously set value. | `.atl en>fr`
`.translangs` | Lists the valid languages for translation. | `.translangs`
`.xkcd` | Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. | `.xkcd` or `.xkcd 1400` or `.xkcd latest`
@ -413,7 +391,7 @@ Command and aliases | Description | Usage
###### [Back to ToC](#table-of-contents)
### Utility
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.togethertube` `.totube` | Creates a new room on <https://togethertube.com> and shows the link in the chat. | `.totube`
`.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch`
@ -422,15 +400,16 @@ Command and aliases | Description | Usage
`.userid` `.uid` | Shows user ID. | `.uid` or `.uid @SomeGuy`
`.channelid` `.cid` | Shows current channel ID. | `.cid`
`.serverid` `.sid` | Shows current server ID. | `.sid`
`.roles` | List roles on this server or roles of a user if specified. Paginated, 20 roles per page. | `.roles 2` or `.roles @Someone`
`.roles` | List roles on this server or a roles of a specific user if specified. Paginated, 20 roles per page. | `.roles 2` or `.roles @Someone`
`.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct`
`.createinvite` `.crinv` | Creates a new invite which has infinite max uses and never expires. **Requires CreateInstantInvite channel permission.** | `.crinv`
`.shardstats` | Stats for shards. Paginated with 25 shards per page. | `.shardstats` or `.shardstats 2`
`.stats` | Shows some basic stats for Nadeko. | `.stats`
`.showemojis` `.se` | Shows a name and a link to every SPECIAL emoji in the message. | `.se A message full of SPECIAL emojis`
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot Owner Only** | `.listservers 3`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.savechat 150`
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot owner only** | `.listservers 3`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot owner only** | `.savechat 150`
`.ping` | Ping the bot to see if there are latency issues. | `.ping`
`.botconfigedit` `.bce` | Sets one of available bot config settings to a specified value. Use the command without any parameters to get a list of available settings. **Bot Owner Only** | `.bce CurrencyName b1nzy` or `.bce`
`.botconfigedit` `.bce` | Sets one of available bot config settings to a specified value. Use the command without any parameters to get a list of available settings. **Bot owner only** | `.bce CurrencyName b1nzy` or `.bce`
`.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1`
`.calcops` | Shows all available operations in the `.calc` command | `.calcops`
`.alias` `.cmdmap` | Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one. **Requires Administrator server permission.** | `.alias allin $bf 100 h` or `.alias "linux thingy" >loonix Spyware Windows`
@ -438,8 +417,8 @@ Command and aliases | Description | Usage
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no server is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo` `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.activity` | Checks for spammers. **Bot Owner Only** | `.activity`
`.parewrel` | Forces the update of the list of patrons who are eligible for the reward. | `.parewrel`
`.activity` | Checks for spammers. **Bot owner only** | `.activity`
`.parewrel` | Forces the update of the list of patrons who are eligible for the reward. **Bot owner only** | `.parewrel`
`.clparew` | Claim patreon rewards. If you're subscribed to bot owner's patreon you can use this command to claim your rewards - assuming bot owner did setup has their patreon key. | `.clparew`
`.listquotes` `.liqu` | Lists all quotes on the server ordered alphabetically. 15 Per page. | `.liqu` or `.liqu 3`
`...` | Shows a random quote with a specified name. | `... abc`
@ -449,7 +428,7 @@ Command and aliases | Description | Usage
`.quotedel` `.qdel` | Deletes a quote with the specified ID. You have to be either server Administrator or the creator of the quote to delete it. | `.qdel 123456`
`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek`
`.remind` | Sends a message to you or a channel after certain amount of time. First argument is `me`/`here`/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword) message. | `.remind me 1d5h Do something` or `.remind #general 1m Start now!`
`.remindtemplate` | Sets message for when the remind is triggered. Available placeholders are `%user%` - user who ran the command, `%message%` - Message specified in the remind, `%target%` - target channel of the remind. **Bot Owner Only** | `.remindtemplate %user%, do %message%!`
`.remindtemplate` | Sets message for when the remind is triggered. Available placeholders are `%user%` - user who ran the command, `%message%` - Message specified in the remind, `%target%` - target channel of the remind. **Bot owner only** | `.remindtemplate %user%, do %message%!`
`.repeatinvoke` `.repinv` | Immediately shows the repeat message on a certain index and restarts its timer. **Requires ManageMessages server permission.** | `.repinv 1`
`.repeatremove` `.reprm` | Removes a repeating message on a specified index. Use `.repeatlist` to see indexes. **Requires ManageMessages server permission.** | `.reprm 2`
`.repeat` | Repeat a message every `X` minutes in the current channel. You can instead specify time of day for the message to be repeated at daily (make sure you've set your server's timezone). You can have up to 5 repeating messages on the server in total. **Requires ManageMessages server permission.** | `.repeat 5 Hello there` or `.repeat 17:30 tea time`
@ -465,19 +444,18 @@ Command and aliases | Description | Usage
###### [Back to ToC](#table-of-contents)
### Xp
Command and aliases | Description | Usage
Commands and aliases | Description | Usage
----------------|--------------|-------
`.experience` `.xp` | Shows your xp stats. Specify the user to show that user's stats instead. | `.xp`
`.xprolerewards` `.xprrs` | Shows currently set role rewards. | `.xprrs`
`.xprolereward` `.xprr` | Sets a role reward on a specified level. Provide no role name in order to remove the role reward. **Requires ManageRoles server permission.** | `.xprr 3 Social`
`.xpnotify` `.xpn` | Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable. | `.xpn global dm` or `.xpn server channel`
`.xpexclude` `.xpex` | Exclude a channel, role or current server from the xp system. **Requires Administrator server permission.** | `.xpex Role Excluded-Role` or `.xpex Server`
`.xpnotify` `.xpn` | Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable. | `.xpn global dm` `.xpn server channel`
`.xpexclude` `.xpex` | Exclude a user or a role from the xp system, or whole current server. **Requires Administrator server permission.** | `.xpex Role Excluded-Role` `.xpex Server`
`.xpexclusionlist` `.xpexl` | Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded. | `.xpexl`
`.xpleaderboard` `.xplb` | Shows current server's xp leaderboard. | `.xplb`
`.xpgleaderboard` `.xpglb` | Shows the global xp leaderboard. | `.xpglb`
`.xpadd` | Adds xp to a user on the server. This does not affect their global ranking. You can use negative values. **Requires Administrator server permission.** | `.xpadd 100 @b1nzy`
`.clubadmin` | Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications. | `.clubadmin`
`.clubcreate` | Creates a club. You must be at least level 5 and not be in the club already. | `.clubcreate b1nzy's friends`
`.clubcreate` | Creates a club. You must be atleast level 5 and not be in the club already. | `.clubcreate b1nzy's friends`
`.clubicon` | Sets the club icon. | `.clubicon https://i.imgur.com/htfDMfU.png`
`.clubinfo` | Shows information about the club. | `.clubinfo b1nzy's friends#123`
`.clubbans` | Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command. | `.clubbans 2`

View File

@ -1,6 +1,6 @@
### How to contribute
1. Make Pull Requests to the [**1.9 BRANCH**](https://github.com/Kwoth/NadekoBot/tree/1.9).
1. Make Pull Requests to the [**dev BRANCH**](https://github.com/Kwoth/NadekoBot/tree/dev).
2. Keep 1 Pull Request to a single feature.
3. Explain what you did in the PR message.

View File

@ -19,9 +19,9 @@
###Question 5: I have an issue/bug/suggestion, where do I put it so it gets noticed?
-----------
**Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the [suggestions](https://feathub.com/Kwoth/NadekoBot).
**Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the `#suggestions` channel in the Nadeko [help server](https://discord.gg/nadekobot).
If your problem or suggestion is not there, feel free to request/notify us about it either in the Issues section of GitHub for issues or on feathub for suggestions.
If your problem or suggestion is not there, feel free to request/notify us about it either in the Issues section of GitHub for issues or in the `#suggestions` channel on the Nadeko help server for suggestions.
###Question 6: How do I use this command?
--------
@ -56,13 +56,13 @@ Follow this Detailed [Guide](http://discord.kongslien.net/guide.html).
###Question 12: I'm building NadekoBot from source, but I get hundreds of (namespace) errors without changing anything!?
-----
**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects
**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects.
###Question 13: My bot has all permissions but it's still saying, "Failed to add roles. Bot has insufficient permissions". How do I fix this?
----------
**Answer:** Discord has added few new features and the roles now follows the role hierarchy which means you need to place your bot's role above every-other role your server has to fix the role hierarchy issue. [Here's](https://support.discordapp.com/hc/en-us/articles/214836687-Role-Management-101) a link to Discords role management 101.
**Answer:** Discord has added a few new features and the roles now follow the role hierarchy, which means you need to place your bot's role above every other role on your server to fix the issue. [Here's](https://support.discordapp.com/hc/en-us/articles/214836687-Role-Management-101) a link to Discord's role management 101.
**Please Note:** *The bot can only set/add all roles below its own highest role. It can not assign it's "highest role" to anyone else.*
**Please Note:** *The bot can only set/add all roles below its own highest role. It cannot assign its "highest role" to anyone else.*
###Question 14: I've broken permissions and am stuck, can I reset permissions?
----------

View File

@ -5,6 +5,7 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl
```json
{
"ClientId": 179372110000358912,
"BotId": 179372110000358912,
"Token": "MTc5MzcyXXX2MDI1ODY3MjY0.ChKs4g.I8J_R9XX0t-QY-0PzXXXiN0-7vo",
"OwnerIds": [
105635123466156544,
@ -16,11 +17,10 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl
"MashapeKey": "4UrKpcWXc2mshS8RKi00000y8Kf5p1Q8kI6jsn32bmd8oVWiY7",
"OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6",
"CleverbotApiKey": "",
"Db": null,
"TotalShards": 1,
"PatreonAccessToken": "",
"PatreonCampaignId": "334038",
"RestartCommand": null,
"Db": null,
"TotalShards": 1,
"ShardRunCommand": "",
"ShardRunArguments": "",
"ShardRunPort": null
@ -71,13 +71,16 @@ It should look like:
```json
"Token": "MTc5MzcyXXX2MDI1ODY3MjY0.ChKs4g.I8J_R9XX0t-QY-0PzXXXiN0-7vo",
```
##### Getting Client ID:
##### Getting Client and Bot ID:
- Copy the `Client ID` on the page and replace the `12312123` part of the **`"ClientId"`** line with it.
- **Important: Bot ID and Client ID** will be the same in **newer bot accounts** due to recent changes by Discord.
- If that's the case, **copy the same client ID** to **`"BotId"`**
```
It should look like:
```
```json
"ClientId": 179372110000358912,
"BotId": 179372110000358912,
```
-----
##### Getting Owner ID*(s)*:
@ -159,38 +162,15 @@ It should look like:
- For Patreon creators only.
- **PatreonCampaignId**
- For Patreon creators only. Id of your campaign.
##### Additional Settings
- **TotalShards**
- Required if the bot will be connected to more than 1500 servers.
- Most likely unnecessary to change until your bot is added to more than 1500 servers.
- **RestartCommand**
- Required if you want to be able to use `.restart` command
- It requires command, and arguments to the command which to execute right before bot stops
- If you're using linux, it's easier, and more reliable to use auto restart option, and just use `.die`
For linux, or from the source, this is usually
```json
"RestartCommand": {
"Cmd": "dotnet",
"Args": "run -c Release"
},
```
For windows (regular installation, or from the updater), this is usually
```json
"RestartCommand": {
"Cmd": "NadekoBot.exe"
},
```
-----
## DB files
Nadeko saves all the settings and infomations in `NadekoBot.db` file here:
`NadekoBot/src/NadekoBot/bin/Release/netcoreapp2.0/data/NadekoBot.db` (macOS and Linux)
`NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data/NadekoBot.db` (macOS and Linux)
`NadekoBot\system\data` (Windows)
in order to open the database file you will need [DB Browser for SQLite](http://sqlitebrowser.org/).
@ -213,8 +193,6 @@ in order to open the database file you will need [DB Browser for SQLite](http://
and that will save all the changes.
-----
## Sharding your bot
- **ShardRunCommand**
@ -232,6 +210,8 @@ and that will save all the changes.
- **ShardRunPort**
- Bot uses a random UDP port in [5000, 6000) range for communication between shards
[Google Console]: https://console.developers.google.com
[DiscordApp]: https://discordapp.com/developers/applications/me
[Invite Guide]: http://discord.kongslien.net/guide.html

Some files were not shown because too many files have changed in this diff Show More