Skip to content

Commit d9e71ed

Browse files
committedSep 14, 2021
Initial commit
0 parents  commit d9e71ed

28 files changed

+2579
-0
lines changed
 

‎.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

‎.gitignore

+357
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
## Ignore Visual Studio temporary files, build results, and
2+
## files generated by popular Visual Studio add-ons.
3+
##
4+
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5+
6+
# User-specific files
7+
*.rsuser
8+
*.suo
9+
*.user
10+
*.userosscache
11+
*.sln.docstates
12+
13+
# User-specific files (MonoDevelop/Xamarin Studio)
14+
*.userprefs
15+
16+
# Mono auto generated files
17+
mono_crash.*
18+
19+
# Build results
20+
[Dd]ebug/
21+
[Dd]ebugPublic/
22+
[Rr]elease/
23+
[Rr]eleases/
24+
x64/
25+
x86/
26+
[Aa][Rr][Mm]/
27+
[Aa][Rr][Mm]64/
28+
bld/
29+
[Bb]in/
30+
[Oo]bj/
31+
[Ll]og/
32+
[Ll]ogs/
33+
34+
# Visual Studio 2015/2017 cache/options directory
35+
.vs/
36+
# Uncomment if you have tasks that create the project's static files in wwwroot
37+
#wwwroot/
38+
39+
# Visual Studio 2017 auto generated files
40+
Generated\ Files/
41+
42+
# MSTest test Results
43+
[Tt]est[Rr]esult*/
44+
[Bb]uild[Ll]og.*
45+
46+
# NUnit
47+
*.VisualState.xml
48+
TestResult.xml
49+
nunit-*.xml
50+
51+
# Build Results of an ATL Project
52+
[Dd]ebugPS/
53+
[Rr]eleasePS/
54+
dlldata.c
55+
56+
# Benchmark Results
57+
BenchmarkDotNet.Artifacts/
58+
59+
# .NET Core
60+
project.lock.json
61+
project.fragment.lock.json
62+
artifacts/
63+
64+
# StyleCop
65+
StyleCopReport.xml
66+
67+
# Files built by Visual Studio
68+
*_i.c
69+
*_p.c
70+
*_h.h
71+
*.ilk
72+
*.meta
73+
*.obj
74+
*.iobj
75+
*.pch
76+
*.pdb
77+
*.ipdb
78+
*.pgc
79+
*.pgd
80+
*.rsp
81+
*.sbr
82+
*.tlb
83+
*.tli
84+
*.tlh
85+
*.tmp
86+
*.tmp_proj
87+
*_wpftmp.csproj
88+
*.log
89+
*.vspscc
90+
*.vssscc
91+
.builds
92+
*.pidb
93+
*.svclog
94+
*.scc
95+
96+
# Chutzpah Test files
97+
_Chutzpah*
98+
99+
# Visual C++ cache files
100+
ipch/
101+
*.aps
102+
*.ncb
103+
*.opendb
104+
*.opensdf
105+
*.sdf
106+
*.cachefile
107+
*.VC.db
108+
*.VC.VC.opendb
109+
110+
# Visual Studio profiler
111+
*.psess
112+
*.vsp
113+
*.vspx
114+
*.sap
115+
116+
# Visual Studio Trace Files
117+
*.e2e
118+
119+
# TFS 2012 Local Workspace
120+
$tf/
121+
122+
# Guidance Automation Toolkit
123+
*.gpState
124+
125+
# ReSharper is a .NET coding add-in
126+
_ReSharper*/
127+
*.[Rr]e[Ss]harper
128+
*.DotSettings.user
129+
130+
# JustCode is a .NET coding add-in
131+
.JustCode
132+
133+
# TeamCity is a build add-in
134+
_TeamCity*
135+
136+
# DotCover is a Code Coverage Tool
137+
*.dotCover
138+
139+
# AxoCover is a Code Coverage Tool
140+
.axoCover/*
141+
!.axoCover/settings.json
142+
143+
# Visual Studio code coverage results
144+
*.coverage
145+
*.coveragexml
146+
147+
# NCrunch
148+
_NCrunch_*
149+
.*crunch*.local.xml
150+
nCrunchTemp_*
151+
152+
# MightyMoose
153+
*.mm.*
154+
AutoTest.Net/
155+
156+
# Web workbench (sass)
157+
.sass-cache/
158+
159+
# Installshield output folder
160+
[Ee]xpress/
161+
162+
# DocProject is a documentation generator add-in
163+
DocProject/buildhelp/
164+
DocProject/Help/*.HxT
165+
DocProject/Help/*.HxC
166+
DocProject/Help/*.hhc
167+
DocProject/Help/*.hhk
168+
DocProject/Help/*.hhp
169+
DocProject/Help/Html2
170+
DocProject/Help/html
171+
172+
# Click-Once directory
173+
publish/
174+
175+
# Publish Web Output
176+
*.[Pp]ublish.xml
177+
*.azurePubxml
178+
# Note: Comment the next line if you want to checkin your web deploy settings,
179+
# but database connection strings (with potential passwords) will be unencrypted
180+
*.pubxml
181+
*.publishproj
182+
183+
# Microsoft Azure Web App publish settings. Comment the next line if you want to
184+
# checkin your Azure Web App publish settings, but sensitive information contained
185+
# in these scripts will be unencrypted
186+
PublishScripts/
187+
188+
# NuGet Packages
189+
*.nupkg
190+
# NuGet Symbol Packages
191+
*.snupkg
192+
# The packages folder can be ignored because of Package Restore
193+
**/[Pp]ackages/*
194+
# except build/, which is used as an MSBuild target.
195+
!**/[Pp]ackages/build/
196+
# Uncomment if necessary however generally it will be regenerated when needed
197+
#!**/[Pp]ackages/repositories.config
198+
# NuGet v3's project.json files produces more ignorable files
199+
*.nuget.props
200+
*.nuget.targets
201+
202+
# Microsoft Azure Build Output
203+
csx/
204+
*.build.csdef
205+
206+
# Microsoft Azure Emulator
207+
ecf/
208+
rcf/
209+
210+
# Windows Store app package directories and files
211+
AppPackages/
212+
BundleArtifacts/
213+
Package.StoreAssociation.xml
214+
_pkginfo.txt
215+
*.appx
216+
*.appxbundle
217+
*.appxupload
218+
219+
# Visual Studio cache files
220+
# files ending in .cache can be ignored
221+
*.[Cc]ache
222+
# but keep track of directories ending in .cache
223+
!?*.[Cc]ache/
224+
225+
# Others
226+
ClientBin/
227+
~$*
228+
*~
229+
*.dbmdl
230+
*.dbproj.schemaview
231+
*.jfm
232+
*.pfx
233+
*.publishsettings
234+
orleans.codegen.cs
235+
236+
# Including strong name files can present a security risk
237+
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
238+
#*.snk
239+
240+
# Since there are multiple workflows, uncomment next line to ignore bower_components
241+
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
242+
#bower_components/
243+
244+
# RIA/Silverlight projects
245+
Generated_Code/
246+
247+
# Backup & report files from converting an old project file
248+
# to a newer Visual Studio version. Backup files are not needed,
249+
# because we have git ;-)
250+
_UpgradeReport_Files/
251+
Backup*/
252+
UpgradeLog*.XML
253+
UpgradeLog*.htm
254+
ServiceFabricBackup/
255+
*.rptproj.bak
256+
257+
# SQL Server files
258+
*.mdf
259+
*.ldf
260+
*.ndf
261+
262+
# Business Intelligence projects
263+
*.rdl.data
264+
*.bim.layout
265+
*.bim_*.settings
266+
*.rptproj.rsuser
267+
*- [Bb]ackup.rdl
268+
*- [Bb]ackup ([0-9]).rdl
269+
*- [Bb]ackup ([0-9][0-9]).rdl
270+
271+
# Microsoft Fakes
272+
FakesAssemblies/
273+
274+
# GhostDoc plugin setting file
275+
*.GhostDoc.xml
276+
277+
# Node.js Tools for Visual Studio
278+
.ntvs_analysis.dat
279+
node_modules/
280+
281+
# Visual Studio 6 build log
282+
*.plg
283+
284+
# Visual Studio 6 workspace options file
285+
*.opt
286+
287+
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
288+
*.vbw
289+
290+
# Visual Studio LightSwitch build output
291+
**/*.HTMLClient/GeneratedArtifacts
292+
**/*.DesktopClient/GeneratedArtifacts
293+
**/*.DesktopClient/ModelManifest.xml
294+
**/*.Server/GeneratedArtifacts
295+
**/*.Server/ModelManifest.xml
296+
_Pvt_Extensions
297+
298+
# Paket dependency manager
299+
.paket/paket.exe
300+
paket-files/
301+
302+
# FAKE - F# Make
303+
.fake/
304+
305+
# CodeRush personal settings
306+
.cr/personal
307+
308+
# Python Tools for Visual Studio (PTVS)
309+
__pycache__/
310+
*.pyc
311+
312+
# Cake - Uncomment if you are using it
313+
# tools/**
314+
# !tools/packages.config
315+
316+
# Tabs Studio
317+
*.tss
318+
319+
# Telerik's JustMock configuration file
320+
*.jmconfig
321+
322+
# BizTalk build output
323+
*.btp.cs
324+
*.btm.cs
325+
*.odx.cs
326+
*.xsd.cs
327+
328+
# OpenCover UI analysis results
329+
OpenCover/
330+
331+
# Azure Stream Analytics local run output
332+
ASALocalRun/
333+
334+
# MSBuild Binary and Structured Log
335+
*.binlog
336+
337+
# NVidia Nsight GPU debugger configuration file
338+
*.nvuser
339+
340+
# MFractors (Xamarin productivity tool) working folder
341+
.mfractor/
342+
343+
# Local History for Visual Studio
344+
.localhistory/
345+
346+
# BeatPulse healthcheck temp database
347+
healthchecksdb
348+
349+
# Backup folder for Package Reference Convert tool in Visual Studio 2017
350+
MigrationBackup/
351+
352+
# Ionide (cross platform F# VS Code tools) working folder
353+
.ionide/
354+
355+
# Unused Files And Folders
356+
Properties/
357+
Setting.json

‎LICENSE

+674
Large diffs are not rendered by default.

‎README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Rosalind.Core
2+
C#으로 작성된 다용도 봇.

‎Rosalind.Core.sln

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31624.102
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rosalind.Core", "Rosalind.Core\Rosalind.Core.csproj", "{F5205DA0-493C-4204-B0B0-617DB3FF197A}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{F5205DA0-493C-4204-B0B0-617DB3FF197A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{F5205DA0-493C-4204-B0B0-617DB3FF197A}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{F5205DA0-493C-4204-B0B0-617DB3FF197A}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{F5205DA0-493C-4204-B0B0-617DB3FF197A}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {23F136B8-D409-4F35-B858-EDB014004348}
24+
EndGlobalSection
25+
EndGlobal

‎Rosalind.Core/Commands/Game/Bank.cs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Discord.Commands;
2+
using Rosalind.Core.Services;
3+
using System.Threading.Tasks;
4+
5+
namespace Rosalind.Core.Commands.Game
6+
{
7+
public class Bank : ModuleBase<SocketCommandContext>
8+
{
9+
private readonly SqlService _sql;
10+
11+
public Bank(SqlService sql)
12+
{
13+
_sql = sql;
14+
}
15+
16+
[Command("은행")]
17+
public async Task BankAsync()
18+
{
19+
var user = _sql.GetUser(Context.Guild.Id, Context.User.Id);
20+
await ReplyAsync($"💰 {Context.User.Username}님은 현재 `{string.Format("{0:#,0}", user.Coin)}` 코인을 소지하고 있습니다.");
21+
}
22+
}
23+
}

‎Rosalind.Core/Commands/Game/Kitty.cs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Newtonsoft.Json.Linq;
4+
using Rosalind.Core.Services;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Net;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
12+
namespace Rosalind.Core.Commands.Game
13+
{
14+
public class Kitty : ModuleBase<SocketCommandContext>
15+
{
16+
private readonly ReactService _react;
17+
18+
public Kitty(ReactService react)
19+
{
20+
_react = react;
21+
}
22+
23+
[Command("야옹이")]
24+
public async Task KittyAsync()
25+
{
26+
var client = new WebClient();
27+
var imageUrl = JObject.Parse(client.DownloadString("http://aws.random.cat/meow")).SelectToken("file").ToString();
28+
29+
var embed = new EmbedBuilder();
30+
embed.WithTitle("🐱 고양이");
31+
embed.WithColor(Color.LightOrange);
32+
embed.WithImageUrl(imageUrl);
33+
embed.WithFooter(new EmbedFooterBuilder
34+
{
35+
IconUrl = Context.User.GetAvatarUrl(ImageFormat.Png, 128),
36+
Text = $"{Context.User.Username}"
37+
});
38+
embed.WithTimestamp(DateTimeOffset.Now);
39+
40+
var message = await Context.Channel.SendMessageAsync(embed: embed.Build());
41+
42+
#region ReactMessage Delegate
43+
Action nextAction = async delegate
44+
{
45+
var client = new WebClient();
46+
var imageUrl = JObject.Parse(client.DownloadString("http://aws.random.cat/meow")).SelectToken("file").ToString();
47+
48+
var embed = new EmbedBuilder();
49+
embed.WithTitle("🐱 고양이");
50+
embed.WithColor(Color.LightOrange);
51+
embed.WithImageUrl(imageUrl);
52+
embed.WithFooter(new EmbedFooterBuilder
53+
{
54+
IconUrl = Context.User.GetAvatarUrl(ImageFormat.Png, 128),
55+
Text = $"{Context.User.Username}"
56+
});
57+
embed.WithTimestamp(DateTimeOffset.Now);
58+
59+
await message.ModifyAsync(msg => msg.Embed = embed.Build());
60+
};
61+
62+
Action closeAction = delegate
63+
{
64+
_react.RemoveReactionMessage(message.Id);
65+
};
66+
#endregion
67+
68+
var dictionary = new Dictionary<IEmote, Action>
69+
{
70+
{ new Emoji("▶️"), nextAction },
71+
{ new Emoji("🛑"), closeAction }
72+
};
73+
74+
_react.AddReactionMessage(message, Context.User.Id, Context.Guild.Id, dictionary, TimeSpan.FromSeconds(10));
75+
}
76+
}
77+
}

‎Rosalind.Core/Commands/Game/Pay.cs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Discord.Commands;
2+
using Rosalind.Core.Preconditions;
3+
using Rosalind.Core.Services;
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace Rosalind.Core.Commands.Game
8+
{
9+
public class Pay : ModuleBase<SocketCommandContext>
10+
{
11+
private readonly SqlService _sql;
12+
13+
public Pay(SqlService sql)
14+
{
15+
_sql = sql;
16+
}
17+
18+
[Command("용돈")]
19+
[Cooldown(60)]
20+
public async Task HelpAsync()
21+
{
22+
Random random = new Random();
23+
int value = random.Next(1, 1000);
24+
25+
_sql.AddUserCoin(Context.Guild.Id, Context.User.Id, Convert.ToUInt64(value));
26+
await ReplyAsync($"✅ 용돈으로 `{string.Format("{0:#,0}", value)}` 코인을 받았습니다!");
27+
}
28+
}
29+
}

‎Rosalind.Core/Commands/Game/Rank.cs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Rosalind.Core.Services;
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace Rosalind.Core.Commands.Game
8+
{
9+
public class Rank : ModuleBase<SocketCommandContext>
10+
{
11+
private readonly SqlService _sql;
12+
13+
public Rank(SqlService sql)
14+
{
15+
_sql = sql;
16+
}
17+
18+
[Command("랭킹")]
19+
public async Task RankAsync()
20+
{
21+
var message = await Context.Channel.SendMessageAsync("🧮 계산중...");
22+
var ranking = _sql.GetRanking(Context.Guild.Id, 20);
23+
var embed = new EmbedBuilder();
24+
25+
embed.WithTitle("💰 서버 코인 랭킹");
26+
embed.WithColor(Color.LightOrange);
27+
embed.WithFooter(new EmbedFooterBuilder
28+
{
29+
IconUrl = Context.User.GetAvatarUrl(ImageFormat.Png, 128),
30+
Text = Context.User.Username
31+
});
32+
embed.WithTimestamp(DateTimeOffset.Now);
33+
34+
if (ranking.Count == 0)
35+
{
36+
embed.WithDescription("❌ 데이터가 존재하지 않습니다.");
37+
}
38+
else
39+
{
40+
for (int i = 0; i < ranking.Count; i++)
41+
{
42+
var user = await Context.Client.Rest.GetUserAsync(ranking[i].UserId);
43+
44+
if (ranking[i].UserId == Context.User.Id)
45+
{
46+
embed.AddField($"{i + 1}등", $"**{user.Username}#{user.Discriminator}** - `{string.Format("{0:n0}", ranking[i].Coin)}` 코인");
47+
}
48+
else
49+
{
50+
embed.AddField($"{i + 1}등", $"{user.Username}#{user.Discriminator} - `{string.Format("{0:n0}", ranking[i].Coin)}` 코인");
51+
}
52+
}
53+
}
54+
55+
await message.ModifyAsync(msg => { msg.Content = string.Empty; msg.Embed = embed.Build(); });
56+
}
57+
}
58+
}
+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using System;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using System.Collections.Generic;
7+
using Rosalind.Core.Services;
8+
using Rosalind.Core.Preconditions;
9+
10+
namespace Rosalind.Core.Commands.Game
11+
{
12+
public class Slotmachine : ModuleBase<SocketCommandContext>
13+
{
14+
private readonly SqlService _sql;
15+
16+
public Slotmachine(SqlService sql)
17+
{
18+
_sql = sql;
19+
}
20+
21+
[Command("슬롯머신")]
22+
[Cooldown(10)]
23+
public async Task SlotmachineAsync([Remainder] string coin = null)
24+
{
25+
//게임 가능한지 조건 확인
26+
var user = _sql.GetUser(Context.Guild.Id, Context.User.Id);
27+
28+
if (string.IsNullOrWhiteSpace(coin))
29+
{
30+
await Context.Channel.SendMessageAsync("❌ 배팅할 코인을 입력하여 주세요.");
31+
return;
32+
}
33+
else if (!coin.All(char.IsDigit))
34+
{
35+
await Context.Channel.SendMessageAsync("❌ 배팅할 코인은 반드시 소수가 아닌 양수이여야 합니다.");
36+
return;
37+
}
38+
else if (Convert.ToUInt64(coin) < 0 || (Convert.ToDecimal(coin) % 1) > 0)
39+
{
40+
await Context.Channel.SendMessageAsync("❌ 배팅할 코인은 반드시 1 이상의 정수여야 합니다.");
41+
return;
42+
}
43+
else if (user.Coin < Convert.ToUInt64(coin))
44+
{
45+
await Context.Channel.SendMessageAsync("❌ 코인이 부족합니다.");
46+
return;
47+
}
48+
49+
//게임 로직 시작
50+
var items = new List<Item>();
51+
52+
for (int i = 0; i < 3; i++)
53+
{
54+
items.Add(RandomEnum());
55+
}
56+
57+
var embed = new EmbedBuilder();
58+
embed.WithTitle("🎲 슬롯머신");
59+
embed.WithDescription(EnumToEmoji(items[0]).ToString());
60+
embed.WithColor(Color.Orange);
61+
62+
var message = await Context.Channel.SendMessageAsync(embed: embed.Build());
63+
64+
for (int i = 1; i < 3; i++)
65+
{
66+
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
67+
embed.WithDescription(embed.Description + " " + EnumToEmoji(items[i]).ToString());
68+
await message.ModifyAsync(x => x.Embed = embed.Build());
69+
}
70+
71+
var multiply = Multiplier(items);
72+
var newCoin = Convert.ToUInt64(coin) * multiply;
73+
74+
if (multiply == 1)
75+
{
76+
embed.WithTitle($"💸 꽝..");
77+
embed.WithColor(Color.Red);
78+
embed.WithDescription($"슬롯머신에서 `{string.Format("{0:n0}", newCoin)}` 코인을 잃었습니다...");
79+
80+
_sql.SubUserCoin(Context.Guild.Id, Context.User.Id, newCoin);
81+
}
82+
else
83+
{
84+
embed.WithTitle($"{EnumToEmoji(items[0])}{EnumToEmoji(items[1])}{EnumToEmoji(items[2])} {multiply}배!");
85+
embed.WithColor(Color.Green);
86+
embed.WithDescription($"슬롯머신에서 잭팟이 나와 `{string.Format("{0:n0}", Convert.ToUInt64(newCoin))}` 코인을 얻었습니다!");
87+
88+
_sql.AddUserCoin(Context.Guild.Id, Context.User.Id, newCoin);
89+
}
90+
91+
await message.ModifyAsync(x => x.Embed = embed.Build());
92+
}
93+
94+
private enum Item
95+
{
96+
Melon,
97+
Cherry,
98+
Lemon,
99+
Star,
100+
Bell,
101+
Seven
102+
}
103+
104+
private Emoji EnumToEmoji(Item item)
105+
{
106+
var items = new Dictionary<Item, Emoji>
107+
{
108+
{ Item.Melon, new Emoji("\U0001f348") },
109+
{ Item.Cherry, new Emoji("\U0001f352") },
110+
{ Item.Lemon, new Emoji("\U0001f34b") },
111+
{ Item.Star, new Emoji("\u2B50") },
112+
{ Item.Bell, new Emoji("\U0001f514") },
113+
{ Item.Seven, new Emoji("7\u20E3") }
114+
};
115+
116+
return items[item];
117+
}
118+
119+
private Item RandomEnum()
120+
{
121+
Random rd = new Random();
122+
int value = rd.Next(1, 101);
123+
124+
if (value <= 30) //1 ~ 30
125+
{
126+
return Item.Melon;
127+
}
128+
else if (value > 30 && value <= 60) //31 ~ 60
129+
{
130+
return Item.Cherry;
131+
}
132+
else if (value > 60 && value <= 90) //61 ~ 90
133+
{
134+
return Item.Lemon;
135+
}
136+
else if (value > 90 && value <= 95) //91 ~ 95
137+
{
138+
return Item.Star;
139+
}
140+
else if (value > 95 && value <= 99) //96 ~ 99
141+
{
142+
return Item.Bell;
143+
}
144+
else //100
145+
{
146+
return Item.Seven;
147+
}
148+
}
149+
150+
private byte Multiplier(List<Item> emotes)
151+
{
152+
if ((emotes[0] == Item.Seven) && (emotes[1] == Item.Seven) && (emotes[2] == Item.Seven))
153+
{
154+
return 10;
155+
}
156+
else if ((emotes[0] == Item.Star) && (emotes[1] == Item.Star) && (emotes[2] == Item.Star))
157+
{
158+
return 7;
159+
}
160+
else if ((emotes[0] == Item.Bell) && (emotes[1] == Item.Bell) && (emotes[2] == Item.Bell))
161+
{
162+
return 5;
163+
}
164+
else if ((emotes[0] == emotes[1]) && (emotes[1] == emotes[2]))
165+
{
166+
return 3;
167+
}
168+
else if ((emotes[0] == emotes[2]))
169+
{
170+
return 2;
171+
}
172+
else
173+
{
174+
return 1;
175+
}
176+
}
177+
}
178+
}
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Rosalind.Core.Services;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
8+
namespace Rosalind.Core.Commands.General
9+
{
10+
public class Ping : ModuleBase<SocketCommandContext>
11+
{
12+
private readonly ReactService _react;
13+
14+
public Ping(ReactService react)
15+
{
16+
_react = react;
17+
}
18+
19+
[Command("핑")]
20+
public async Task PingAsync()
21+
{
22+
var message = await Context.Channel.SendMessageAsync($"Pinging...");
23+
var latency = message.Timestamp - Context.Message.Timestamp;
24+
var pingColor = new Color();
25+
26+
if (latency.TotalMilliseconds < 50)
27+
pingColor = Color.Green;
28+
else if (latency.TotalMilliseconds < 150)
29+
pingColor = Color.LightOrange;
30+
else if (latency.TotalMilliseconds < 300)
31+
pingColor = Color.Orange;
32+
else
33+
pingColor = Color.Red;
34+
35+
var embed = new EmbedBuilder();
36+
embed.WithTitle("🏓 Pong!");
37+
embed.WithColor(pingColor);
38+
embed.WithFields(new List<EmbedFieldBuilder>
39+
{
40+
new EmbedFieldBuilder{ Name = "Gateway Ping", Value = $"`{Context.Client.Latency}ms`" },
41+
new EmbedFieldBuilder{ Name = "Client Ping", Value = $"`{latency.TotalMilliseconds}ms`" }
42+
});
43+
embed.WithFooter(new EmbedFooterBuilder
44+
{
45+
IconUrl = Context.User.GetAvatarUrl(ImageFormat.Png, 128),
46+
Text = Context.User.Username
47+
});
48+
embed.WithTimestamp(DateTimeOffset.Now);
49+
50+
await message.ModifyAsync(msg => {
51+
msg.Content = null;
52+
msg.Embed = embed.Build();
53+
});
54+
}
55+
}
56+
}
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Rosalind.Core.Preconditions;
4+
using Rosalind.Core.Services;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Net.Http;
8+
using System.Threading.Tasks;
9+
10+
namespace Rosalind.Core.Commands.Hentai
11+
{
12+
public class Gelbooru : ModuleBase<SocketCommandContext>
13+
{
14+
private readonly ReactService _react;
15+
16+
public Gelbooru(ReactService react)
17+
{
18+
_react = react;
19+
}
20+
21+
[Nsfw]
22+
[Command("겔부루")]
23+
public async Task GelbooruAsync([Remainder] string tags = null)
24+
{
25+
//처음 실행시
26+
var result = new BooruSharp.Search.Post.SearchResult();
27+
var booru = new BooruSharp.Booru.Gelbooru();
28+
29+
try
30+
{
31+
result = await booru.GetRandomPostAsync(tags?.Split(null));
32+
}
33+
catch (HttpRequestException)
34+
{
35+
await Context.Channel.SendMessageAsync("❌ 해당 태그가 존재하지 않습니다.");
36+
return;
37+
}
38+
catch (BooruSharp.Search.TooManyTags)
39+
{
40+
await Context.Channel.SendMessageAsync("❌ 태그가 너무 많습니다!");
41+
return;
42+
}
43+
44+
var embed = new EmbedBuilder();
45+
embed.WithTitle("Gelbooru");
46+
embed.WithColor(Color.Red);
47+
embed.WithImageUrl(result.FileUrl.AbsoluteUri);
48+
embed.AddField("아이디", $"`{result.ID}`");
49+
embed.AddField("생성일", $"`{result.Creation}`");
50+
embed.AddField("소스", $"`{(string.IsNullOrWhiteSpace(result.Source) ? "알 수 없음" : result.Source)}`");
51+
embed.WithFooter(new EmbedFooterBuilder
52+
{
53+
IconUrl = Context.User.GetAvatarUrl(ImageFormat.Png, 128),
54+
Text = $"{Context.User.Username}"
55+
});
56+
embed.WithTimestamp(DateTimeOffset.Now);
57+
58+
var message = await Context.Channel.SendMessageAsync(embed: embed.Build()); //메시지 전송하고 객체 캡처
59+
var tagMessage = await Context.Channel.SendMessageAsync($"태그: `{string.Join(", ", result.Tags)}`"); //태그 메시지도 전송하고 캡처
60+
61+
#region ReactMessage Delegate
62+
Action nextAction = async delegate
63+
{
64+
var booru = new BooruSharp.Booru.Gelbooru();
65+
result = await booru.GetRandomPostAsync(tags?.Split(null));
66+
67+
var embed = new EmbedBuilder();
68+
embed.WithTitle("Gelbooru");
69+
embed.WithColor(Color.Red);
70+
embed.WithImageUrl(result.FileUrl.AbsoluteUri);
71+
embed.AddField("아이디", $"`{result.ID}`");
72+
embed.AddField("생성일", $"`{result.Creation}`");
73+
embed.AddField("소스", $"`{(string.IsNullOrWhiteSpace(result.Source) ? "알 수 없음" : result.Source)}`");
74+
embed.WithFooter(new EmbedFooterBuilder
75+
{
76+
IconUrl = Context.User.GetAvatarUrl(ImageFormat.Png, 128),
77+
Text = $"{Context.User.Username}"
78+
});
79+
embed.WithTimestamp(DateTimeOffset.Now);
80+
81+
await message.ModifyAsync(msg => msg.Embed = embed.Build());
82+
await tagMessage.ModifyAsync(msg => msg.Content = $"태그: `{string.Join(", ", result.Tags)}`");
83+
};
84+
85+
Action closeAction = delegate
86+
{
87+
_react.RemoveReactionMessage(message.Id);
88+
};
89+
#endregion
90+
91+
var dictionary = new Dictionary<IEmote, Action>
92+
{
93+
{ new Emoji("▶️"), nextAction },
94+
{ new Emoji("🛑"), closeAction }
95+
};
96+
97+
_react.AddReactionMessage(message, Context.User.Id, Context.Guild.Id, dictionary, TimeSpan.FromMinutes(5));
98+
}
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Rosalind.Core.Preconditions;
4+
using Rosalind.Core.Services;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Threading.Tasks;
8+
9+
namespace Rosalind.Core.Commands.Management
10+
{
11+
public class Restart : ModuleBase<SocketCommandContext>
12+
{
13+
private readonly ReactService _react;
14+
15+
public Restart(ReactService react)
16+
{
17+
_react = react;
18+
}
19+
20+
[Developer]
21+
[Command("재시작")]
22+
public async Task GelbooruAsync()
23+
{
24+
var message = await Context.Channel.SendMessageAsync("❓ 봇을 재시작할까요?");
25+
26+
#region ReactMessage Delegate
27+
Action okAction = delegate
28+
{
29+
var info = new System.Diagnostics.ProcessStartInfo(Environment.GetCommandLineArgs()[0]);
30+
System.Diagnostics.Process.Start(info);
31+
};
32+
33+
Action cancelAction = delegate
34+
{
35+
_react.RemoveReactionMessage(message.Id);
36+
};
37+
#endregion
38+
39+
var dictionary = new Dictionary<IEmote, Action>
40+
{
41+
{ new Emoji("✅"), okAction },
42+
{ new Emoji("❌"), cancelAction }
43+
};
44+
45+
_react.AddReactionMessage(message, Context.User.Id, Context.Guild.Id, dictionary, TimeSpan.FromSeconds(10));
46+
}
47+
}
48+
}

‎Rosalind.Core/Models/ReactMessage.cs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Discord;
2+
using Discord.Rest;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace Rosalind.Core.Models
7+
{
8+
public class ReactMessage
9+
{
10+
public RestUserMessage Message { get; }
11+
public ulong MessageUserId { get; }
12+
public ulong MessageGuildId { get; }
13+
public Dictionary<IEmote, Action> Dictionaries { get; }
14+
public bool RemoveMessageAfterTimeOut { get; }
15+
16+
public ReactMessage(RestUserMessage message, ulong messageUserId, ulong messageGuildId, Dictionary<IEmote, Action> dictionaries, bool removeMessageAfterTimeOut)
17+
{
18+
this.Message = message;
19+
this.MessageUserId = messageUserId;
20+
this.MessageGuildId = messageGuildId;
21+
this.Dictionaries = dictionaries;
22+
this.RemoveMessageAfterTimeOut = removeMessageAfterTimeOut;
23+
}
24+
}
25+
}

‎Rosalind.Core/Models/Setting.cs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Newtonsoft.Json;
2+
using System.IO;
3+
4+
namespace Rosalind.Core.Models
5+
{
6+
public class Setting
7+
{
8+
[JsonProperty("config")]
9+
public Config Config { get; set; }
10+
11+
[JsonProperty("commandGroup")]
12+
public CommandGroup[] CommandGroup { get; set; }
13+
14+
public void GetConfig(string filePath)
15+
{
16+
string jsonString = File.ReadAllText(filePath);
17+
var json = JsonConvert.DeserializeObject<Setting>(jsonString);
18+
19+
this.Config = json.Config;
20+
this.CommandGroup = json.CommandGroup;
21+
}
22+
}
23+
24+
public partial class Config
25+
{
26+
[JsonProperty("token")]
27+
public string Token { get; set; }
28+
29+
[JsonProperty("trnToken")]
30+
public string TrnToken { get; set; }
31+
32+
[JsonProperty("koreanbotsToken")]
33+
public string KoreanbotsToken { get; set; }
34+
35+
[JsonProperty("connectionString")]
36+
public string ConnectionString { get; set; }
37+
38+
[JsonProperty("developerId")]
39+
public ulong DeveloperId { get; set; }
40+
41+
[JsonProperty("koreanbotsId")]
42+
public string KoreanbotsId { get; set; }
43+
44+
[JsonProperty("errorLogChannelId")]
45+
public ulong ErrorLogChannelId { get; set; }
46+
47+
[JsonProperty("prefix")]
48+
public string Prefix { get; set; }
49+
}
50+
51+
public class CommandGroup
52+
{
53+
[JsonProperty("title")]
54+
public string Title { get; set; }
55+
56+
[JsonProperty("description")]
57+
public string Description { get; set; }
58+
59+
[JsonProperty("color")]
60+
public string Color { get; set; }
61+
62+
[JsonProperty("commands")]
63+
public Command[] Commands { get; set; }
64+
}
65+
66+
public partial class Command
67+
{
68+
[JsonProperty("name")]
69+
public string Name { get; set; }
70+
71+
[JsonProperty("description")]
72+
public string Description { get; set; }
73+
74+
[JsonProperty("args")]
75+
public string[] Args { get; set; }
76+
}
77+
}

‎Rosalind.Core/Models/User.cs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Rosalind.Core.Models
6+
{
7+
public class User
8+
{
9+
public ulong DatabaseId { get; }
10+
public ulong GuildId { get; }
11+
public ulong UserId { get; }
12+
public ulong Coin { get; }
13+
14+
public User(ulong id, ulong guildId, ulong userId, ulong coin)
15+
{
16+
this.DatabaseId = id;
17+
this.GuildId = guildId;
18+
this.UserId = userId;
19+
this.Coin = coin;
20+
}
21+
}
22+
}
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Discord.Commands;
2+
using Discord.WebSocket;
3+
using System;
4+
using System.Threading.Tasks;
5+
using System.Collections.Generic;
6+
7+
namespace Rosalind.Core.Preconditions
8+
{
9+
public class Cooldown : PreconditionAttribute
10+
{
11+
private readonly int _cooldownLength;
12+
private readonly string _errorMessage;
13+
14+
public Cooldown(int cooldownLength, string errorMessage = "❌ 본 명령어를 다시 사용하시려면 {TIME}초 더 기다리셔야 해요!")
15+
{
16+
_cooldownLength = cooldownLength;
17+
_errorMessage = errorMessage;
18+
}
19+
20+
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
21+
{
22+
var stackCooldownTimer = new List<DateTimeOffset>();
23+
var stackCooldownTarget = new List<SocketGuildUser>();
24+
25+
if (stackCooldownTarget.Contains(context.User as SocketGuildUser))
26+
{
27+
if (stackCooldownTimer[stackCooldownTarget.IndexOf(context.Message.Author as SocketGuildUser)].AddSeconds(_cooldownLength) >= DateTimeOffset.Now)
28+
{
29+
int secondsLeft = (int)(stackCooldownTimer[stackCooldownTarget.IndexOf(context.Message.Author as SocketGuildUser)].AddSeconds(_cooldownLength) - DateTimeOffset.Now).TotalSeconds;
30+
string errorMessage = _errorMessage.Replace("{TIME}", secondsLeft.ToString());
31+
32+
context.Channel.SendMessageAsync(errorMessage);
33+
return Task.FromResult(PreconditionResult.FromError(errorMessage));
34+
}
35+
else
36+
{
37+
return Task.FromResult(PreconditionResult.FromSuccess());
38+
}
39+
}
40+
else
41+
{
42+
stackCooldownTarget.Add(context.User as SocketGuildUser);
43+
stackCooldownTimer.Add(DateTimeOffset.Now);
44+
45+
return Task.FromResult(PreconditionResult.FromSuccess());
46+
}
47+
}
48+
}
49+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Discord.Commands;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Rosalind.Core.Models;
4+
using System;
5+
using System.Threading.Tasks;
6+
7+
namespace Rosalind.Core.Preconditions
8+
{
9+
public class Developer : PreconditionAttribute
10+
{
11+
private readonly string _errorMessage;
12+
13+
public Developer(string errorMessage = "❌ 본 명령어는 개발자만 사용할 수 있습니다.")
14+
{
15+
_errorMessage = errorMessage;
16+
}
17+
18+
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
19+
{
20+
var setting = services.GetRequiredService<Setting>();
21+
22+
if (context.User.Id == setting.Config.DeveloperId)
23+
{
24+
return Task.FromResult(PreconditionResult.FromSuccess());
25+
}
26+
else
27+
{
28+
context.Channel.SendMessageAsync(_errorMessage);
29+
return Task.FromResult(PreconditionResult.FromError(_errorMessage));
30+
}
31+
}
32+
}
33+
}

‎Rosalind.Core/Preconditions/Nsfw.cs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
namespace Rosalind.Core.Preconditions
7+
{
8+
public class Nsfw : PreconditionAttribute
9+
{
10+
private readonly string _errorMessage;
11+
12+
public Nsfw(string errorMessage = "❌ 본 명령어는 NSFW 채널에서만 사용할 수 있습니다.")
13+
{
14+
_errorMessage = errorMessage;
15+
}
16+
17+
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
18+
{
19+
var channel = context.Channel as ITextChannel;
20+
21+
if (channel != null && channel.IsNsfw)
22+
{
23+
return Task.FromResult(PreconditionResult.FromSuccess());
24+
}
25+
else
26+
{
27+
context.Channel.SendMessageAsync(_errorMessage);
28+
return Task.FromResult(PreconditionResult.FromError(_errorMessage));
29+
}
30+
}
31+
}
32+
}

‎Rosalind.Core/Program.cs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Rosalind.Core.Services;
2+
3+
namespace Rosalind.Core
4+
{
5+
class Program
6+
{
7+
static void Main(string[] args) => new DiscordService().MainAsync().GetAwaiter().GetResult();
8+
}
9+
}

‎Rosalind.Core/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


‎Rosalind.Core/Rosalind.Core.csproj

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="BooruSharp" Version="3.0.3" />
10+
<PackageReference Include="Discord.KillersLibrary.Labs" Version="1.0.8" />
11+
<PackageReference Include="Discord.Net.Labs" Version="3.0.3" />
12+
<PackageReference Include="Discord.Net.Labs.Commands" Version="3.0.0" />
13+
<PackageReference Include="Discord.Net.Labs.Core" Version="3.0.2" />
14+
<PackageReference Include="Discord.Net.Labs.Rest" Version="3.0.2" />
15+
<PackageReference Include="Discord.Net.Labs.Webhook" Version="3.0.0" />
16+
<PackageReference Include="Discord.Net.Labs.WebSocket" Version="3.0.3" />
17+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
18+
<PackageReference Include="MySql.Data" Version="8.0.26" />
19+
<PackageReference Include="System.Runtime.Caching" Version="5.0.0" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<Folder Include="Commands\Vote\" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<None Update="Setting.json">
28+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
29+
</None>
30+
</ItemGroup>
31+
32+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Discord.WebSocket;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Rosalind.Core.Models;
6+
using System;
7+
using System.Reflection;
8+
using System.Threading.Tasks;
9+
10+
namespace Rosalind.Core.Services
11+
{
12+
public class CommandHandlingService
13+
{
14+
private readonly Setting _setting;
15+
private readonly CommandService _command;
16+
private IServiceProvider _service;
17+
private DiscordSocketClient _client;
18+
19+
public CommandHandlingService(IServiceProvider service)
20+
{
21+
_service = service;
22+
_setting = service.GetRequiredService<Setting>();
23+
_command = service.GetRequiredService<CommandService>();
24+
_client = service.GetRequiredService<DiscordSocketClient>();
25+
26+
_client.MessageReceived += OnClientMessage;
27+
_command.Log += new LoggingService().OnLogReceived;
28+
_command.CommandExecuted += OnCommandExecuted;
29+
}
30+
31+
public async Task InitializeAsync()
32+
{
33+
await _command.AddModulesAsync(Assembly.GetEntryAssembly(), _service);
34+
}
35+
36+
private async Task OnCommandExecuted(Optional<CommandInfo> command, ICommandContext context, IResult result)
37+
{
38+
if (!command.IsSpecified || result.IsSuccess)
39+
return;
40+
41+
var embed = new EmbedBuilder();
42+
embed.WithTitle("❌ 오류");
43+
embed.WithColor(Color.Red);
44+
embed.WithDescription($"`{result}`");
45+
embed.WithFooter(new EmbedFooterBuilder
46+
{
47+
IconUrl = _client.CurrentUser.GetAvatarUrl(),
48+
Text = _client.CurrentUser.Username
49+
});
50+
embed.WithTimestamp(DateTimeOffset.Now);
51+
52+
await (context.Client.GetChannelAsync(_setting.Config.ErrorLogChannelId).Result as ISocketMessageChannel).SendMessageAsync(embed: embed.Build());
53+
}
54+
55+
private async Task OnClientMessage(SocketMessage socketMessage)
56+
{
57+
int argPos = 0;
58+
59+
if (!(socketMessage is SocketUserMessage message) || message.Source != MessageSource.User || socketMessage.Channel is IPrivateChannel || !message.HasStringPrefix(_setting.Config.Prefix, ref argPos))
60+
return;
61+
62+
SocketCommandContext context = new SocketCommandContext(_client, message);
63+
await _command.ExecuteAsync(context, argPos, _service);
64+
}
65+
}
66+
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Discord;
2+
using Discord.Commands;
3+
using Discord.WebSocket;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Rosalind.Core.Models;
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Rosalind.Core.Services
11+
{
12+
public class DiscordService
13+
{
14+
private Setting _setting;
15+
private ServiceProvider _service;
16+
private DiscordSocketClient _client;
17+
18+
public DiscordService()
19+
{
20+
_service = ConfigureServices();
21+
_setting = _service.GetRequiredService<Setting>();
22+
_client = _service.GetRequiredService<DiscordSocketClient>();
23+
}
24+
25+
public async Task MainAsync()
26+
{
27+
_setting.GetConfig($"{AppDomain.CurrentDomain.BaseDirectory}\\Setting.json");
28+
_client.Log += new LoggingService().OnLogReceived;
29+
_service.GetRequiredService<CommandService>().Log += new LoggingService().OnLogReceived;
30+
31+
await _client.LoginAsync(TokenType.Bot, _setting.Config.Token);
32+
await _client.StartAsync();
33+
await _client.SetGameAsync($"{_setting.Config.Prefix}도움말", null, ActivityType.Listening);
34+
await _service.GetRequiredService<CommandHandlingService>().InitializeAsync();
35+
await Task.Delay(Timeout.Infinite).ConfigureAwait(false);
36+
}
37+
38+
public ServiceProvider ConfigureServices()
39+
{
40+
return new ServiceCollection()
41+
.AddSingleton<CommandHandlingService>()
42+
.AddSingleton<DiscordSocketClient>(x => ActivatorUtilities.CreateInstance<DiscordSocketClient>(x, new DiscordSocketConfig { LogLevel = LogSeverity.Debug, AlwaysAcknowledgeInteractions = false }))
43+
.AddSingleton<CommandService>(x => ActivatorUtilities.CreateInstance<CommandService>(x, new CommandServiceConfig { DefaultRunMode = RunMode.Async, LogLevel = LogSeverity.Debug }))
44+
.AddSingleton<ReactService>()
45+
.AddSingleton<SqlService>()
46+
.AddSingleton<Setting>()
47+
.BuildServiceProvider();
48+
}
49+
}
50+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Discord;
2+
using System;
3+
using System.Threading.Tasks;
4+
5+
namespace Rosalind.Core.Services
6+
{
7+
public class LoggingService
8+
{
9+
public Task OnLogReceived(LogMessage log)
10+
{
11+
if (log.Severity == LogSeverity.Critical)
12+
Console.ForegroundColor = ConsoleColor.Red;
13+
else if (log.Severity == LogSeverity.Error)
14+
Console.ForegroundColor = ConsoleColor.DarkYellow;
15+
else if (log.Severity == LogSeverity.Warning)
16+
Console.ForegroundColor = ConsoleColor.Yellow;
17+
else if (log.Severity == LogSeverity.Info)
18+
Console.ForegroundColor = ConsoleColor.Gray;
19+
else if (log.Severity == LogSeverity.Verbose)
20+
Console.ForegroundColor = ConsoleColor.Gray;
21+
else if (log.Severity == LogSeverity.Debug)
22+
Console.ForegroundColor = ConsoleColor.DarkGray;
23+
24+
Console.WriteLine("{0} {1,-11} {2}", DateTime.Now.ToString("HH:mm:ss"), log.Source, log.Message ?? "Null");
25+
return Task.CompletedTask;
26+
}
27+
}
28+
}
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using Discord;
2+
using Discord.Rest;
3+
using Discord.WebSocket;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Rosalind.Core.Models;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Runtime.Caching;
9+
using System.Threading.Tasks;
10+
11+
namespace Rosalind.Core.Services
12+
{
13+
public class ReactService
14+
{
15+
private IServiceProvider _service;
16+
private DiscordSocketClient _client;
17+
18+
public ReactService(IServiceProvider service)
19+
{
20+
_service = service;
21+
_client = service.GetRequiredService<DiscordSocketClient>();
22+
_client.ReactionAdded += OnReactionAdded;
23+
_client.ReactionRemoved += OnReactionRemoved;
24+
_client.ReactionsCleared += OnReactionCleared;
25+
}
26+
27+
public void AddReactionMessage(RestUserMessage message, ulong userId, ulong guildId, Dictionary<IEmote, Action> dictionaries, TimeSpan timeout, bool removeMessageAfterTimeOut = false)
28+
{
29+
var cache = MemoryCache.Default;
30+
var policy = new CacheItemPolicy { SlidingExpiration = timeout, RemovedCallback = CacheRemovedCallback };
31+
var reactMessage = new ReactMessage(message, userId, guildId, dictionaries, removeMessageAfterTimeOut);
32+
33+
cache.Add(message.Id.ToString(), reactMessage, policy);
34+
35+
foreach (var item in dictionaries)
36+
{
37+
message.AddReactionAsync(item.Key);
38+
}
39+
40+
Console.WriteLine("{0} {1,-11} {2}", DateTime.Now.ToString("HH:mm:ss"), "React", $"Cached {message.Id}");
41+
}
42+
43+
public void RemoveReactionMessage(ulong messageId)
44+
{
45+
var cache = MemoryCache.Default;
46+
cache.Remove(messageId.ToString());
47+
}
48+
49+
private async void CacheRemovedCallback(CacheEntryRemovedArguments arguments)
50+
{
51+
var reactMessage = arguments.CacheItem.Value as ReactMessage;
52+
53+
if (reactMessage.RemoveMessageAfterTimeOut)
54+
{
55+
try
56+
{
57+
var message = await (_client.GetGuild(reactMessage.MessageGuildId).GetChannel(reactMessage.Message.Channel.Id) as ISocketMessageChannel).GetMessageAsync(reactMessage.Message.Id) as RestUserMessage;
58+
await message.DeleteAsync();
59+
}
60+
catch (Exception)
61+
{
62+
Console.WriteLine("{0} {1,-11} {2}", DateTime.Now.ToString("HH:mm:ss"), "React", $"Error deleting message! ({reactMessage.Message.Id})");
63+
}
64+
}
65+
}
66+
67+
private async Task OnReactionAdded(Cacheable<IUserMessage, ulong> cachedMessage, Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction)
68+
{
69+
var message = await cachedMessage.GetOrDownloadAsync();
70+
var cache = MemoryCache.Default;
71+
var reactMessage = cache.Get(message.Id.ToString()) as ReactMessage; //캐싱된 ReactMessage 객체
72+
73+
if (message != null && reactMessage != null && reaction.User.IsSpecified && reaction.UserId != _client.CurrentUser.Id && reactMessage.Dictionaries.ContainsKey(reaction.Emote) && reactMessage.MessageUserId == reaction.UserId)
74+
{
75+
Console.WriteLine("{0} {1,-11} {2}", DateTime.Now.ToString("HH:mm:ss"), "React", $"{reaction.User.Value} Added React At {message.Id}");
76+
77+
foreach (var item in reactMessage.Dictionaries)
78+
{
79+
if (item.Key.Equals(reaction.Emote))
80+
{
81+
item.Value(); //대리자 실행
82+
}
83+
}
84+
85+
await message.RemoveReactionAsync(reaction.Emote, reaction.UserId); //유저 반응 제거
86+
}
87+
}
88+
89+
private async Task OnReactionRemoved(Cacheable<IUserMessage, ulong> cachedMessage, Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction)
90+
{
91+
var message = await cachedMessage.GetOrDownloadAsync();
92+
93+
if (reaction.UserId == _client.CurrentUser.Id)
94+
{
95+
await message.AddReactionAsync(reaction.Emote);
96+
}
97+
}
98+
99+
private async Task OnReactionCleared(Cacheable<IUserMessage, ulong> cachedMessage, Cacheable<IMessageChannel, ulong> channel)
100+
{
101+
var message = await cachedMessage.GetOrDownloadAsync();
102+
var cache = MemoryCache.Default;
103+
var reactMessage = cache.Get(message.Id.ToString()) as ReactMessage;
104+
105+
if (reactMessage != null)
106+
{
107+
foreach (var item in reactMessage.Dictionaries)
108+
{
109+
await message.AddReactionAsync(item.Key);
110+
}
111+
}
112+
}
113+
}
114+
}

‎Rosalind.Core/Services/SqlService.cs

+327
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using MySql.Data.MySqlClient;
3+
using Rosalind.Core.Models;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Data;
7+
8+
namespace Rosalind.Core.Services
9+
{
10+
public class SqlService
11+
{
12+
private readonly Setting _setting;
13+
14+
public SqlService(IServiceProvider service)
15+
{
16+
_setting = service.GetRequiredService<Setting>();
17+
}
18+
19+
/// <summary>
20+
/// 길드 테이블에 새로운 유저 추가
21+
/// </summary>
22+
/// <param name="guildid">길드 아이디</param>
23+
/// <param name="userid">추가할 유저 아이디</param>
24+
public User AddNewUser(ulong guildId, ulong userId)
25+
{
26+
User user = null;
27+
28+
try
29+
{
30+
long insertedId = 0;
31+
32+
using (MySqlConnection _connection = new MySqlConnection(_setting.Config.ConnectionString))
33+
{
34+
_connection.Open();
35+
36+
using (MySqlCommand sqlCom = new MySqlCommand())
37+
{
38+
sqlCom.Connection = _connection;
39+
sqlCom.CommandText = $"INSERT INTO TABLE_{guildId} (USERID, MONEY) VALUES(@USERID, 0)";
40+
sqlCom.Parameters.AddWithValue("@USERID", userId);
41+
sqlCom.CommandType = CommandType.Text;
42+
sqlCom.ExecuteNonQuery();
43+
44+
insertedId = sqlCom.LastInsertedId;
45+
}
46+
47+
_connection.Close();
48+
}
49+
50+
user = new User(
51+
id: Convert.ToUInt64(insertedId),
52+
guildId: guildId,
53+
userId: userId,
54+
coin: 0
55+
);
56+
}
57+
catch (MySqlException ex)
58+
{
59+
switch ((MySqlErrorCode)ex.Number)
60+
{
61+
case MySqlErrorCode.NoSuchTable:
62+
AddNewGuild(guildId);
63+
user = AddNewUser(guildId, userId);
64+
break;
65+
66+
default:
67+
throw new Exception($"처리되지 못한 예외가 발생하였습니다. ({ex.Number}, {ex.Message})");
68+
}
69+
}
70+
71+
return user;
72+
}
73+
74+
/// <summary>
75+
/// 길드를 데이터베이스에 새로 추가합니다.
76+
/// </summary>
77+
/// <param name="guildid">길드 아이디</param>
78+
public void AddNewGuild(ulong guildid)
79+
{
80+
try
81+
{
82+
using (MySqlConnection _connection = new MySqlConnection(_setting.Config.ConnectionString))
83+
{
84+
_connection.Open();
85+
86+
using (MySqlCommand sqlCom = new MySqlCommand())
87+
{
88+
sqlCom.Connection = _connection;
89+
sqlCom.CommandText = $"CREATE TABLE TABLE_{guildid} (_ID INT PRIMARY KEY AUTO_INCREMENT, USERID BIGINT NOT NULL, MONEY BIGINT NOT NULL) ENGINE = INNODB;";
90+
sqlCom.CommandType = CommandType.Text;
91+
sqlCom.ExecuteNonQuery();
92+
}
93+
94+
_connection.Close();
95+
}
96+
}
97+
catch (MySqlException ex)
98+
{
99+
switch ((MySqlErrorCode)ex.Number)
100+
{
101+
case MySqlErrorCode.TableExists:
102+
throw new Exception("길드가 이미 존재합니다.");
103+
104+
default:
105+
throw new Exception($"처리되지 못한 예외가 발생하였습니다. ({ex.Number}, {ex.Message})");
106+
}
107+
}
108+
}
109+
110+
/// <summary>
111+
/// 랭킹을 데이터베이스로 부터 가져옵니다.
112+
/// </summary>
113+
/// <param name="guildid">길드 아이디</param>
114+
/// <param name="limit">랭킹 유저의 수</param>
115+
/// <returns></returns>
116+
public List<User> GetRanking(ulong guildId, int limit)
117+
{
118+
List<User> users = new List<User>();
119+
120+
try
121+
{
122+
using (MySqlConnection _connection = new MySqlConnection(_setting.Config.ConnectionString))
123+
{
124+
_connection.Open();
125+
126+
using (MySqlCommand sqlCom = new MySqlCommand())
127+
{
128+
sqlCom.Connection = _connection;
129+
sqlCom.CommandText = $"SELECT * FROM TABLE_{guildId} WHERE NOT MONEY=0 ORDER BY MONEY DESC LIMIT @LIMIT;";
130+
sqlCom.Parameters.AddWithValue("@LIMIT", limit);
131+
sqlCom.CommandType = CommandType.Text;
132+
133+
using (MySqlDataReader reader = sqlCom.ExecuteReader())
134+
{
135+
while (reader.Read())
136+
{
137+
users.Add(new User(
138+
id: Convert.ToUInt64(reader["_ID"]),
139+
guildId: guildId,
140+
userId: Convert.ToUInt64(reader["USERID"]),
141+
coin: Convert.ToUInt64(reader["MONEY"])
142+
));
143+
}
144+
}
145+
}
146+
147+
_connection.Close();
148+
}
149+
}
150+
catch (MySqlException ex)
151+
{
152+
switch ((MySqlErrorCode)ex.Number)
153+
{
154+
case MySqlErrorCode.NoSuchTable:
155+
AddNewGuild(guildId);
156+
users = GetRanking(guildId, limit);
157+
break;
158+
159+
default:
160+
throw new Exception($"처리되지 못한 예외가 발생하였습니다. ({ex.Number}, {ex.Message})");
161+
}
162+
}
163+
164+
return users;
165+
}
166+
167+
/// <summary>
168+
/// 유저를 데이터베이스에서 가져와 객체로 반환합니다.
169+
/// </summary>
170+
/// <param name="guildId">길드의 아이디</param>
171+
/// <param name="userId">유저의 아이디</param>
172+
/// <returns></returns>
173+
public User GetUser(ulong guildId, ulong userId)
174+
{
175+
User user = null;
176+
177+
try
178+
{
179+
using (MySqlConnection _connection = new MySqlConnection(_setting.Config.ConnectionString))
180+
{
181+
_connection.Open();
182+
183+
using (MySqlCommand sqlCom = new MySqlCommand())
184+
{
185+
sqlCom.Connection = _connection;
186+
sqlCom.CommandText = $"SELECT * FROM TABLE_{guildId} WHERE USERID=@ID LIMIT 1;";
187+
sqlCom.Parameters.AddWithValue("@ID", userId);
188+
sqlCom.CommandType = CommandType.Text;
189+
190+
using (MySqlDataReader reader = sqlCom.ExecuteReader())
191+
{
192+
if (reader.HasRows)
193+
{
194+
while (reader.Read())
195+
{
196+
user = new User(
197+
id: Convert.ToUInt64(reader["_ID"]),
198+
guildId: guildId,
199+
userId: Convert.ToUInt64(reader["USERID"]),
200+
coin: Convert.ToUInt64(reader["MONEY"])
201+
);
202+
}
203+
}
204+
else
205+
{
206+
user = AddNewUser(guildId, userId);
207+
}
208+
}
209+
}
210+
211+
_connection.Close();
212+
}
213+
}
214+
catch (MySqlException ex)
215+
{
216+
switch ((MySqlErrorCode)ex.Number)
217+
{
218+
case MySqlErrorCode.NoSuchTable:
219+
AddNewGuild(guildId);
220+
user = GetUser(guildId, userId);
221+
break;
222+
223+
default:
224+
throw new Exception($"처리되지 못한 예외가 발생하였습니다. ({ex.Number}, {ex.Message})");
225+
}
226+
}
227+
228+
return user;
229+
}
230+
231+
/// <summary>
232+
/// 유저에게 코인을 추가합니다.
233+
/// </summary>
234+
/// <param name="guildId">길드 아이디</param>
235+
/// <param name="userId">유저 아이디</param>
236+
/// <param name="coin">추가할 코인의 양</param>
237+
public void AddUserCoin(ulong guildId, ulong userId, ulong coin)
238+
{
239+
try
240+
{
241+
using (MySqlConnection _connection = new MySqlConnection(_setting.Config.ConnectionString))
242+
{
243+
_connection.Open();
244+
245+
using (MySqlCommand sqlCom = new MySqlCommand())
246+
{
247+
sqlCom.Connection = _connection;
248+
sqlCom.CommandText = $"UPDATE TABLE_{guildId} SET MONEY=MONEY+@MONEY WHERE USERID=@ID";
249+
sqlCom.Parameters.AddWithValue("@MONEY", coin);
250+
sqlCom.Parameters.AddWithValue("@ID", userId);
251+
sqlCom.CommandType = CommandType.Text;
252+
253+
int numberOfRecords = sqlCom.ExecuteNonQuery();
254+
255+
if (numberOfRecords == 0)
256+
{
257+
AddNewUser(guildId, userId);
258+
AddUserCoin(guildId, userId, coin);
259+
}
260+
}
261+
262+
_connection.Close();
263+
}
264+
}
265+
catch (MySqlException ex)
266+
{
267+
switch ((MySqlErrorCode)ex.Number)
268+
{
269+
case MySqlErrorCode.NoSuchTable: //ER_NO_SUCH_TABLE
270+
AddNewGuild(guildId);
271+
break;
272+
273+
default:
274+
throw new Exception($"처리되지 못한 예외가 발생하였습니다. ({ex.Number}, {ex.Message})");
275+
}
276+
}
277+
}
278+
279+
/// <summary>
280+
/// 유저에게서 코인을 가져갑니다.
281+
/// </summary>
282+
/// <param name="guildId">길드 아이디</param>
283+
/// <param name="userId">유저 아이디</param>
284+
/// <param name="coin">추가할 코인의 량</param>
285+
public void SubUserCoin(ulong guildId, ulong userId, ulong coin)
286+
{
287+
try
288+
{
289+
using (MySqlConnection _connection = new MySqlConnection(_setting.Config.ConnectionString))
290+
{
291+
_connection.Open();
292+
293+
using (MySqlCommand sqlCom = new MySqlCommand())
294+
{
295+
sqlCom.Connection = _connection;
296+
sqlCom.CommandText = $"UPDATE TABLE_{guildId} SET MONEY=MONEY-@MONEY WHERE USERID=@ID";
297+
sqlCom.Parameters.AddWithValue("@MONEY", coin);
298+
sqlCom.Parameters.AddWithValue("@ID", userId);
299+
sqlCom.CommandType = CommandType.Text;
300+
301+
int numberOfRecords = sqlCom.ExecuteNonQuery();
302+
303+
if (numberOfRecords == 0)
304+
{
305+
AddNewUser(guildId, userId);
306+
AddUserCoin(guildId, userId, coin);
307+
}
308+
}
309+
310+
_connection.Close();
311+
}
312+
}
313+
catch (MySqlException ex)
314+
{
315+
switch ((MySqlErrorCode)ex.Number)
316+
{
317+
case MySqlErrorCode.NoSuchTable:
318+
AddNewGuild(guildId);
319+
break;
320+
321+
default:
322+
throw new Exception($"처리되지 못한 예외가 발생하였습니다. ({ex.Number}, {ex.Message})");
323+
}
324+
}
325+
}
326+
}
327+
}

‎Rosalind.Core/Setting.example.json

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"config": {
3+
"token": "TOKEN_HERE",
4+
"trn_token": "TRN_TOKEN_HERE",
5+
"koreanbots_token": "KOREANBOTS_TOKEN_HERE",
6+
"connection_string": "DATABASE_CONNECTION_STRING_HERE",
7+
"developer_id": "DEVELOPER_ID_HERE",
8+
"koreanbots_id": "KOREANBOTS_ID_HERE",
9+
"error_log_channel_id": "ERROR_LOG_CHANNEL_ID_HERE",
10+
"prefix": "PREFIX_HERE"
11+
},
12+
"commandGroup": [
13+
{
14+
"title": "🎮 놀이 명령어",
15+
"description": "각종 재밌는 기능이 담겨있는 그룹입니다.",
16+
"color": "#ff0000",
17+
"commands": [
18+
{
19+
"name": "용돈",
20+
"description": "용돈을 지급합니다. 이 명령어는 1분에 한 번만 사용할 수 있습니다.",
21+
"args": []
22+
},
23+
{
24+
"name": "은행",
25+
"description": "자신의 코인을 확인합니다. 그 코인 아닙니다.",
26+
"args": []
27+
},
28+
{
29+
"name": "랭킹",
30+
"description": "해당 서버에서의 랭킹을 확인합니다.",
31+
"args": []
32+
},
33+
{
34+
"name": "슬롯머신",
35+
"description": "슬롯머신 게임을 시작합니다.",
36+
"args": [
37+
{
38+
"name": "코인의 수",
39+
"description": "슬롯머신 게임에 베팅할 코인의 수를 나타냅니다."
40+
}
41+
]
42+
},
43+
{
44+
"name": "고양이",
45+
"description": "귀여운 고양이 사진을 랜덤하게 볼 수 있습니다.",
46+
"args": []
47+
}
48+
]
49+
},
50+
{
51+
"title": "📄 기본 명령어",
52+
"description": "봇의 기본 명령어들이 들어있는 그룹입니다.",
53+
"color": "#FF7F00",
54+
"commands": [
55+
{
56+
"name": "",
57+
"description": "봇과 서버와의 연결 지연시간을 확인합니다.",
58+
"args": []
59+
},
60+
{
61+
"name": "도움말",
62+
"description": "도움말을 표시합니다.",
63+
"args": []
64+
}
65+
]
66+
},
67+
{
68+
"title": "🔞 NSFW 명령어",
69+
"description": "딥다크한 명령어들이 들어있는 그룹입니다.",
70+
"color": "#FFFF00",
71+
"commands": [
72+
{
73+
"name": "겔부루",
74+
"description": "겔부루에서 랜덤한 이미지를 가져옵니다.",
75+
"args": [
76+
{
77+
"name": "태그",
78+
"description": "검색에 이용할 태그를 의미합니다. 태그는 공백으로 구분합니다."
79+
}
80+
]
81+
}
82+
]
83+
}
84+
]
85+
}

0 commit comments

Comments
 (0)
Please sign in to comment.