Polls persist restarts now.
This commit is contained in:
parent
2fbb80a2a2
commit
29f97f3732
@ -10,6 +10,10 @@ namespace NadekoBot.Common.Collections
|
|||||||
public List<T> Source { get; }
|
public List<T> Source { get; }
|
||||||
private readonly object _locker = new object();
|
private readonly object _locker = new object();
|
||||||
|
|
||||||
|
public IndexedCollection()
|
||||||
|
{
|
||||||
|
Source = new List<T>();
|
||||||
|
}
|
||||||
public IndexedCollection(IEnumerable<T> source)
|
public IndexedCollection(IEnumerable<T> source)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
|
1992
NadekoBot.Core/Migrations/20171027155001_poll-rewrite.Designer.cs
generated
Normal file
1992
NadekoBot.Core/Migrations/20171027155001_poll-rewrite.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
100
NadekoBot.Core/Migrations/20171027155001_poll-rewrite.cs
Normal file
100
NadekoBot.Core/Migrations/20171027155001_poll-rewrite.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -983,6 +983,67 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("PlaylistSong");
|
b.ToTable("PlaylistSong");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.Poll", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId");
|
||||||
|
|
||||||
|
b.Property<string>("Question");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Poll");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.PollAnswer", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded");
|
||||||
|
|
||||||
|
b.Property<int>("Index");
|
||||||
|
|
||||||
|
b.Property<int?>("PollId");
|
||||||
|
|
||||||
|
b.Property<string>("Text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PollId");
|
||||||
|
|
||||||
|
b.ToTable("PollAnswer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.PollVote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded");
|
||||||
|
|
||||||
|
b.Property<int?>("PollId");
|
||||||
|
|
||||||
|
b.Property<ulong>("UserId");
|
||||||
|
|
||||||
|
b.Property<int>("VoteIndex");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PollId");
|
||||||
|
|
||||||
|
b.ToTable("PollVote");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.Quote", b =>
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.Quote", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -1771,6 +1832,20 @@ namespace NadekoBot.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.PollAnswer", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NadekoBot.Core.Services.Database.Models.Poll")
|
||||||
|
.WithMany("Answers")
|
||||||
|
.HasForeignKey("PollId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.PollVote", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NadekoBot.Core.Services.Database.Models.Poll")
|
||||||
|
.WithMany("Votes")
|
||||||
|
.HasForeignKey("PollId");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.RaceAnimal", b =>
|
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.RaceAnimal", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NadekoBot.Core.Services.Database.Models.BotConfig")
|
b.HasOne("NadekoBot.Core.Services.Database.Models.BotConfig")
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using NadekoBot.Extensions;
|
|
||||||
using NadekoBot.Core.Services.Impl;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common
|
|
||||||
{
|
|
||||||
public class Poll
|
|
||||||
{
|
|
||||||
private readonly IUserMessage _originalMessage;
|
|
||||||
private readonly IGuild _guild;
|
|
||||||
private readonly string[] answers;
|
|
||||||
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
|
|
||||||
private readonly string _question;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
private readonly NadekoStrings _strings;
|
|
||||||
private bool running = false;
|
|
||||||
|
|
||||||
public event Action<ulong> OnEnded = delegate { };
|
|
||||||
|
|
||||||
public Poll(DiscordSocketClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable<string> enumerable)
|
|
||||||
{
|
|
||||||
_client = client;
|
|
||||||
_strings = strings;
|
|
||||||
|
|
||||||
_originalMessage = umsg;
|
|
||||||
_guild = ((ITextChannel)umsg.Channel).Guild;
|
|
||||||
_question = question;
|
|
||||||
answers = enumerable as string[] ?? enumerable.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public EmbedBuilder GetStats(string title)
|
|
||||||
{
|
|
||||||
var results = _participants.GroupBy(kvp => kvp.Value)
|
|
||||||
.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(_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,
|
|
||||||
Format.Bold(answers[result.Key - 1]),
|
|
||||||
Format.Bold(result.Value.ToString())));
|
|
||||||
totalVotesCast += result.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
eb.WithDescription(sb.ToString())
|
|
||||||
.WithFooter(efb => efb.WithText(GetText("x_votes_cast", totalVotesCast)));
|
|
||||||
|
|
||||||
return eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StartPoll()
|
|
||||||
{
|
|
||||||
var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n";
|
|
||||||
var num = 1;
|
|
||||||
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
|
|
||||||
msgToSend += "\n" + Format.Bold(GetText("poll_vote_public"));
|
|
||||||
|
|
||||||
await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false);
|
|
||||||
running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StopPoll()
|
|
||||||
{
|
|
||||||
running = false;
|
|
||||||
OnEnded(_guild.Id);
|
|
||||||
await _originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> TryVote(IUserMessage msg)
|
|
||||||
{
|
|
||||||
// has to be a user message
|
|
||||||
if (msg == null || msg.Author.IsBot || !running)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// has to be an integer
|
|
||||||
if (!int.TryParse(msg.Content, out int vote))
|
|
||||||
return false;
|
|
||||||
if (vote < 1 || vote > answers.Length)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
IMessageChannel ch;
|
|
||||||
//if public, channel must be the same the poll started in
|
|
||||||
if (_originalMessage.Channel.Id != msg.Channel.Id)
|
|
||||||
return false;
|
|
||||||
ch = msg.Channel;
|
|
||||||
|
|
||||||
//user can vote only once
|
|
||||||
if (_participants.TryAdd(msg.Author.Id, vote))
|
|
||||||
{
|
|
||||||
var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false);
|
|
||||||
toDelete.DeleteAfter(5);
|
|
||||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetText(string key, params object[] replacements)
|
|
||||||
=> _strings.GetText(key,
|
|
||||||
_guild.Id,
|
|
||||||
"Games".ToLowerInvariant(),
|
|
||||||
replacements);
|
|
||||||
}
|
|
||||||
}
|
|
72
NadekoBot.Core/Modules/Games/Common/PollRunner.cs
Normal file
72
NadekoBot.Core/Modules/Games/Common/PollRunner.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,9 @@ using NadekoBot.Extensions;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using NadekoBot.Modules.Games.Services;
|
using NadekoBot.Modules.Games.Services;
|
||||||
|
using NadekoBot.Core.Services.Database.Models;
|
||||||
|
using System.Text;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games
|
namespace NadekoBot.Modules.Games
|
||||||
{
|
{
|
||||||
@ -23,24 +26,33 @@ namespace NadekoBot.Modules.Games
|
|||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public Task Poll([Remainder] string arg = null)
|
public async Task Poll([Remainder] string arg)
|
||||||
=> InternalStartPoll(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(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]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task PollStats()
|
public async Task PollStats()
|
||||||
{
|
{
|
||||||
if (!_service.ActivePolls.TryGetValue(Context.Guild.Id, out var poll))
|
if (!_service.ActivePolls.TryGetValue(Context.Guild.Id, out var pr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Context.Channel.EmbedAsync(poll.GetStats(GetText("current_poll_results")));
|
await Context.Channel.EmbedAsync(GetStats(pr.Poll, GetText("current_poll_results")));
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InternalStartPoll(string arg)
|
|
||||||
{
|
|
||||||
if(await _service.StartPoll(Context.Guild.Id, Context.Message, arg) == false)
|
|
||||||
await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
@ -50,8 +62,49 @@ namespace NadekoBot.Modules.Games
|
|||||||
{
|
{
|
||||||
var channel = (ITextChannel)Context.Channel;
|
var channel = (ITextChannel)Context.Channel;
|
||||||
|
|
||||||
if(_service.ActivePolls.TryRemove(channel.Guild.Id, out var poll))
|
Poll p;
|
||||||
await poll.StopPoll().ConfigureAwait(false);
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,45 +9,102 @@ using NadekoBot.Modules.Games.Common;
|
|||||||
using NadekoBot.Core.Services;
|
using NadekoBot.Core.Services;
|
||||||
using NadekoBot.Core.Services.Impl;
|
using NadekoBot.Core.Services.Impl;
|
||||||
using NLog;
|
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
|
namespace NadekoBot.Modules.Games.Services
|
||||||
{
|
{
|
||||||
public class PollService : IEarlyBlockingExecutor, INService
|
public class PollService : IEarlyBlockingExecutor, INService
|
||||||
{
|
{
|
||||||
public ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
|
public ConcurrentDictionary<ulong, PollRunner> ActivePolls { get; } = new ConcurrentDictionary<ulong, PollRunner>();
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly NadekoStrings _strings;
|
private readonly NadekoStrings _strings;
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly NadekoStrings _strs;
|
||||||
|
|
||||||
public PollService(DiscordSocketClient client, NadekoStrings strings)
|
public PollService(DiscordSocketClient client, NadekoStrings strings, DbService db,
|
||||||
|
NadekoStrings strs, IUnitOfWork uow)
|
||||||
{
|
{
|
||||||
_log = LogManager.GetCurrentClassLogger();
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
_client = client;
|
_client = client;
|
||||||
_strings = strings;
|
_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 async Task<bool?> StartPoll(ulong guildId, IUserMessage msg, string arg)
|
public Poll CreatePoll(ulong guildId, ulong channelId, string input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";"))
|
if (string.IsNullOrWhiteSpace(input) || !input.Contains(";"))
|
||||||
return null;
|
return null;
|
||||||
var data = arg.Split(';');
|
var data = input.Split(';');
|
||||||
if (data.Length < 3)
|
if (data.Length < 3)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var poll = new Poll(_client, _strings, msg, data[0], data.Skip(1));
|
var col = new IndexedCollection<PollAnswer>(data.Skip(1)
|
||||||
if (ActivePolls.TryAdd(guildId, poll))
|
.Select(x => new PollAnswer() { Text = x }));
|
||||||
{
|
|
||||||
poll.OnEnded += (gid) =>
|
|
||||||
{
|
|
||||||
ActivePolls.TryRemove(gid, out _);
|
|
||||||
};
|
|
||||||
|
|
||||||
await poll.StartPoll().ConfigureAwait(false);
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
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)
|
public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg)
|
||||||
{
|
{
|
||||||
if (guild == null)
|
if (guild == null)
|
||||||
|
@ -24,6 +24,7 @@ namespace NadekoBot.Core.Services.Database
|
|||||||
IWarningsRepository Warnings { get; }
|
IWarningsRepository Warnings { get; }
|
||||||
IXpRepository Xp { get; }
|
IXpRepository Xp { get; }
|
||||||
IClubRepository Clubs { get; }
|
IClubRepository Clubs { get; }
|
||||||
|
IPollsRepository Polls { get; }
|
||||||
|
|
||||||
int Complete();
|
int Complete();
|
||||||
Task<int> CompleteAsync();
|
Task<int> CompleteAsync();
|
||||||
|
@ -3,15 +3,16 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace NadekoBot.Core.Services.Database.Models
|
namespace NadekoBot.Core.Services.Database.Models
|
||||||
{
|
{
|
||||||
public class Poll
|
public class Poll : DbEntity
|
||||||
{
|
{
|
||||||
public ulong GuildId { get; set; }
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
public string Question { get; set; }
|
public string Question { get; set; }
|
||||||
public IndexedCollection<PollAnswer> Answers { get; set; }
|
public IndexedCollection<PollAnswer> Answers { get; set; }
|
||||||
public HashSet<PollVote> Votes { get; set; }
|
public HashSet<PollVote> Votes { get; set; } = new HashSet<PollVote>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PollAnswer : IIndexed
|
public class PollAnswer : DbEntity, IIndexed
|
||||||
{
|
{
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
using System;
|
namespace NadekoBot.Core.Services.Database.Models
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NadekoBot.Core.Services.Database.Models
|
|
||||||
{
|
{
|
||||||
public class PollVote
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,6 +341,12 @@ namespace NadekoBot.Core.Services.Database
|
|||||||
.WithMany(x => x.Bans);
|
.WithMany(x => x.Bans);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Polls
|
||||||
|
modelBuilder.Entity<Poll>()
|
||||||
|
.HasIndex(x => x.GuildId)
|
||||||
|
.IsUnique();
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,9 @@ namespace NadekoBot.Core.Services.Database
|
|||||||
private IClubRepository _clubs;
|
private IClubRepository _clubs;
|
||||||
public IClubRepository Clubs => _clubs ?? (_clubs = new ClubRepository(_context));
|
public IClubRepository Clubs => _clubs ?? (_clubs = new ClubRepository(_context));
|
||||||
|
|
||||||
|
private IPollsRepository _polls;
|
||||||
|
public IPollsRepository Polls => _polls ?? (_polls = new PollsRepository(_context));
|
||||||
|
|
||||||
public UnitOfWork(NadekoContext context)
|
public UnitOfWork(NadekoContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
@ -713,7 +713,8 @@
|
|||||||
"games_current_poll_results": "Current poll results",
|
"games_current_poll_results": "Current poll results",
|
||||||
"games_no_votes_cast": "No votes cast.",
|
"games_no_votes_cast": "No votes cast.",
|
||||||
"games_poll_already_running": "Poll is already running on this server.",
|
"games_poll_already_running": "Poll is already running on this server.",
|
||||||
"games_poll_created": "📃 {0} has created a poll which requires your attention:",
|
"games_poll_created": "📃 {0} has created a poll",
|
||||||
|
"games_poll_closed": "Poll Closed!",
|
||||||
"games_poll_result": "`{0}.` {1} with {2} votes.",
|
"games_poll_result": "`{0}.` {1} with {2} votes.",
|
||||||
"games_poll_voted": "{0} voted.",
|
"games_poll_voted": "{0} voted.",
|
||||||
"games_poll_vote_private": "Private Message me with the corresponding number of the answer.",
|
"games_poll_vote_private": "Private Message me with the corresponding number of the answer.",
|
||||||
|
Loading…
Reference in New Issue
Block a user