diff --git a/Install.md b/Install.md index 7395ef1..0424ab3 100644 --- a/Install.md +++ b/Install.md @@ -1,6 +1,7 @@ # Install -This Discord bot is made *very specifically* for the [Czech branch Discord chat](https://discord.gg/ZAdfEJ4); other then for testing purposes, there is probably no need for you to try to run this bot, or even read this guide. Why am I writing this. +This Discord bot is made *very specifically* for the [Czech branch Discord chat](https://discord.gg/ZAdfEJ4); other then for testing purposes, +there is probably no need for you to try to run this bot, or even read this guide. Why am I writing this. ## Setup @@ -20,45 +21,14 @@ $ dotnet build -c Release And you're all set! The output is in `thorn/bin/Release/net8.0/` ## Configuration -Now the fun part. This bot has multiple configuration files that need to be set up appropriately. +`config.json` is where most of the magic happens. Be sure to fill out the token field. -### `config.json` -This is self-explanatory +`feeds.json` is for configuring the RSS fetching. If you don't wish to fetch any RSS +feeds, just put `[]` (an ampty array) in the file. -```json -{ - "token": "YOUR-BOT-TOKEN", - "prefix": "-" -} -``` - -### `feeds.json` -This is a file that defines how and what RSS feeds are being read. Below is an example of such one. - -```json -[ - { - "Link": "http://scp-cs.wikidot.com/feed/site-changes.xml", - "ChannelIds": [800776102236324294], - "Filter": ["new page"], - "EmbedColor": 16711680, - "NewPageAnnouncement": true, - "RequireAuth": false - }, - { - "Link": "http://scp-cs.wikidot.com/feed/site-changes.xml", - "ChannelIds": [735012092329918297], - "EmbedColor": 16711680, - "NewPageAnnouncement": false, - "RequireAuth": false - } -] -``` -You don't have to have any feeds set up (in that case just put `[]` in the file). - -### `constants.json` and `daily.json` +There are examples in `config.example.json` and `feeds.example.json` respectively. -These are static configuration files and they are not meant to be changed, but you can do so if you wish. +`daily.json` is not really meant to be edited, but you surely get the idea. ## Done diff --git a/thorn/Config/FeedConfig.cs b/thorn/Config/FeedConfig.cs index 5f5cef4..a022675 100644 --- a/thorn/Config/FeedConfig.cs +++ b/thorn/Config/FeedConfig.cs @@ -8,7 +8,4 @@ public class FeedConfig public string CustomDescription { get; set; } public bool NewPageAnnouncement { get; set; } public uint EmbedColor { get; set; } - public bool RequireAuth { get; set; } - public string Username { get; set; } - public string Password { get; set; } } \ No newline at end of file diff --git a/thorn/Config/config.example.json b/thorn/Config/config.example.json index a848bc9..09c0d20 100644 --- a/thorn/Config/config.example.json +++ b/thorn/Config/config.example.json @@ -1,4 +1,30 @@ { "token": "YOUR-TOKEN", - "prefix": "." + "helpText": { + "translate": "[Návod pro Překladatele](http://scp-cs.wikidot.com/guide-for-translators)\n[Potřebné Překlady](http://scp-cs.wikidot.com/translation-requests)\n[Překladatelské Stránky](http://scp-cs.wikidot.com/translators-pages)", + "write": "[Jak napsat SCP](http://scp-cs.wikidot.com/how-to-write-an-scp)\n[Pravidla Kritizování](http://scp-cs.wikidot.com/criticism-policy)\n[Autorské Stránky](http://scp-cs.wikidot.com/authors-pages)", + "correction": "[Terminologie](http://scp-cs.wikidot.com/terminologie)\n[Korektorský hub](http://scp-cs.wikidot.com/korektorsky-hub)", + "join": "[Návod pro Nováčky](http://scp-cs.wikidot.com/guide-for-newbies)\n[Připoj se ke Stránce](http://scp-cs.wikidot.com/system:join)\n[Pravidla Stránky](http://scp-cs.wikidot.com/site-rules)\n" + }, + "miscText": { + "info": "**Verze:** v3.0.0\n**Source:** https://github.com/scp-cs/Thorn" + }, + "roles": { + "classC": 537322727225163776, + "INT": 537023765163409439, + "O5": 536984757410463764, + "O4": 938104159993995265 + }, + "emotes": { + "yes": "<:yes:778188278574612491>", + "no": "<:no:778188289797521419>", + "abstain": "<:abstain:778188303789850645>" + }, + "channels": { + "welcome": 666672988883779585, + "news": 537063810121334784, + "o5": 661982623085625364, + "general": 537064369725636611, + "console": 537380917157691392 + } } \ No newline at end of file diff --git a/thorn/Config/daily.json b/thorn/Config/daily.json index bb84404..7d56724 100644 --- a/thorn/Config/daily.json +++ b/thorn/Config/daily.json @@ -111,7 +111,7 @@ "18 04": "Svátek má **Valérie**\n\n**Události:**\n**1389** – Na Starém Městě pražském byl rozpoután židovský pogrom. Chudina vtrhla do ghetta, vydrancovala a vypálila obchody i domy a bylo vyvražděno na 3 000 mužů, žen i dětí.\n**1775 **– Paul Revere vyrazil varovat kolonisty, že Britové přicházejí z Bostonu zatknout Samuela Adamse a Johna Hancocka.\n**1880** – Při sérii tornád ve státě Missouri zahynulo 151 lidí a město Marshfield bylo smazáno z mapy.\n**1896** – Závěrečný ceremoniál, uzavírající první moderní letní olympijské hry v Athénách v Řecku.\n**1906** – San Francisco bylo zničeno zemětřesením.\n**1906** – Bertha von Suttnerová převzala v Kristianii, dnešním Oslu, Nobelovu cenu za mír. Je první osobou narozenou v Čechách, která získala Nobelovu cenu.\n**1912** – Parník Carpathia, patřící společnosti Cunard, v 20:30 připlul do New Yorku s 706 trosečníky Titanicu na palubě.\n**1920** – Proběhly první volby do parlamentu Československé republiky.\n**1945 **– Do prostoru Ašského výběžku vstoupili první američtí vojáci – průzkumná hlídka 3. praporu 358. pluku 90. divize americké armády pod vedením generála Pattona.\n**1946** – Společnost národů ukončila činnost.\n**1949** – Irsko se stalo nezávislou republikou a vystoupilo z Commonwealthu. Skončil 750 let trvající svazek s anglickou korunou.\n**1955** – Ve věku 76 let zemřel Albert Einstein, americký fyzik německého původu, nositel Nobelovy ceny za fyziku.\n**1966** – Československý film režisérů Jána Kadára a Elmara Klose Obchod na korze získal Oskara jako nejlepší neanglicky mluvený film roku 1965.\n**1980** – Zimbabwe získalo nezávislost a k moci se dostala černošská většina.\n**2011** – Maďarský parlament schválil novou ústavu (nový oficiální název státu zní Maďarsko).", "19 04": "Svátek má **Rostislav**\n\n**Události:**\n**65** – Propuštěnec Milichus prozradil Pisonovo spiknutí zabít císaře Nera. Všichni spiklenci byli zajati a uvězněni.\n**1770** – Anglický kapitán James Cook s lodí Endeavour přistál u východního pobřeží Austrálie v Botany Bay.\n**1775** – Proběhla bitva o Lexington a Concord, první vojenský střed americké války za nezávislost.\n**1809** – Bývalý prezident Thomas Jefferson prodal svého sluhu novému, právě zvolenému prezidentovi, Jamesi Madisonovi.\n**1839** – V Londýně byla podepsána Londýnská smlouva, která uznala Belgii za samostatné království.\n**1880** – Stremayrova jazyková nařízení zaručovala rovnoprávnost češtiny s němčinou u státních úřadů ve vnějším úřadování.\n**1882** – V 73 letech zemřel Charles Darwin, anglický biolog.\n**1892** – Charles Duryea jel prvním benzínem poháněným autem na světě.\n**1897** – První ročník Bostonského maratonu, který se běží každé třetí pondělí v dubnu na státní svátek Patriots’ Day („Den patriotů“).\n**1904** – Toronto a Ontario zachvátil obrovský požár.\n**1930** – V dnešní premiéře Hurvínkova jarní revue se objevují dvě nové loutky: holčička Mánička a pes Žeryk.\n**1943** – Švýcarský chemik Albert Hofmann, přezdívaný „otec LSD“, si vzal poprvé úmyslně LSD, aby na sobě vyzkoušel jeho účinky.\n**1943** – Zahájeno povstání ve varšavském ghettu – nejvýznamnější akce židovského odboje za 2. světové války.\n**1945** – Necelý měsíc před koncem války Nacisté vypálili partyzánskou osadu Ploština na území obce Drnovice (okres Zlín) ; zastřelili nebo zaživa upálili 28 lidí.\n**1961** – Končí Invaze v Zátoce sviní.\n**1971** – Byla vypuštěna první vesmírná stanice Saljut 1.\n**1999** – Evropský parlament vyzval Českou republiku k zrušení Benešových dekretů.\n**2010** – Pohřeb polského prezidenta Lecha Kaczyńskeho, který zahynul při leteckém neštěstí u Smolenska.", "20 04": "Svátek má **Marcela**\n\n**Události:**\n**295** – Osmý zaznamenaný průlet Halleyovy komety. <:elmofire:796489161472737331>\n**1519** – Španělský dobyvatel Hernando Cortés se vyloďuje v Mexiku s cílem dobýt aztéckou říši. Jeho hlavním cílem však bylo nalézt bájné město zlata El Dorado.\n**1534** – Francouzský mořeplavec a objevitel Kanady Jacques Cartier se vydal se dvěma loděmi a posádkou 61 mužů na svou první plavbu do Ameriky.\n**1777** – New York adoptuje státní ústavu jako nezávislý stát.\n**1792** – Revoluční Francie vyhlašuje válku Rakousku, Prusku a Sardínii. Začala první koaliční válka monarchistické Evropy proti Francii.\n**1841** – Edgar Allan Poe vydává Vraždy v ulici Morgue, což je považováno za první detektivku na světě.\n**1854** – Z Ringhofferovy dílny vyjel první český železniční vagon. Odebírá je takřka celý svět, v jejich salonní verzi se vozí většina evropských panovníků.\n**1862** – Louis Pasteur dokončil první pokusy s pasterizací.\n**1867** – Královna Viktorie položila základní kámen Royal Albert Hall v Londýně.\n**1902** – Pierre a Marie Curie extrahovali chlorid radia.\n**1926** – Western Electric a Warner Bros. představují Vitaphone, proces přidání zvuku do filmu.\n**1945** – Druhá světová válka: Fürherův bunkr: Adolf Hitler naposledy vychází na povrch ze svého bunkru, aby udělil Železný kříž vojákům z Hitlerjugend.\n**1998** – Rozpuštění Frakce Rudé armády.\n**1999** – Masakr na Columbine High School.\n**2010** – Exploze Deepwater Horizon. <:ef:796489161350578257>", - "21 04": "Svátek má **Alexandra**\n\n**Události:**\n**753 př. n. l.** – Podle římské tradice bylo založeno město Řím.\n**1757** – Sedmiletá válka: pruské a rakouské jednotky se střetly v bitvě u Liberce.\n**1783** – V Praze bylo slavnostně otevřeno Nosticovo divadlo Lessingovou tragédií Emilia Galotti.\n**1895** – Woodville Latham se svými syny představil Panopticon, první filmový promítací přístroj v USA.\n**1919** – Výrazné vylepšení zvukového filmu. Lee de Forest oznamuje vynález technologie *Phonofilmu*, kdy je film a zvuk na jednom celuloidovém pásu.\n**1926** – Narodila se Alžběta II., britská královna. Dnes je jí 95 let <:praisethesun:796393806693793827>\n**1938** – Vedení Wehrmachtu vypracovává pod krycím názvem Fall Grun (Zelený případ) plán na napadení Československa.\n**1945** – Americká vojska vstoupila na československé území a osvobodila Aš.\n**1960** – Hlavním městem Brazílie se stala Brasília místo dosavadního Rio de Janeira.\n**1973** – Sibiřský ropovod Družba začíná dodávat ropu do východní Evropy.\n**1990** – Papež Jan Pavel II. zahájil dvoudenní návštěvu Československa, historicky první oficiální návštěvu papeže v Československu.\n**2012** – Největší demonstrace od listopadu 1989. Odbory a dvě desítky iniciativ a sdružení přilákaly do Prahy prý až 120 tisíc protestujících. Odboráři požadovali demisi Nečasovy vlády a předčasné volby, odmítli vládní reformy.\n**2016** – Ve věku 57 let zemřel Prince, americký hudebník.\n**2019** – Teroristické útoky na Srí Lance.", + "21 04": "Svátek má **Alexandra**\n\n**Události:**\n**753 př. n. l.** – Podle římské tradice bylo založeno město Řím.\n**1757** – Sedmiletá válka: pruské a rakouské jednotky se střetly v bitvě u Liberce.\n**1783** – V Praze bylo slavnostně otevřeno Nosticovo divadlo Lessingovou tragédií Emilia Galotti.\n**1895** – Woodville Latham se svými syny představil Panopticon, první filmový promítací přístroj v USA.\n**1919** – Výrazné vylepšení zvukového filmu. Lee de Forest oznamuje vynález technologie *Phonofilmu*, kdy je film a zvuk na jednom celuloidovém pásu.\n**1926** – Narodila se Alžběta II., britská královna.\n**1938** – Vedení Wehrmachtu vypracovává pod krycím názvem Fall Grun (Zelený případ) plán na napadení Československa.\n**1945** – Americká vojska vstoupila na československé území a osvobodila Aš.\n**1960** – Hlavním městem Brazílie se stala Brasília místo dosavadního Rio de Janeira.\n**1973** – Sibiřský ropovod Družba začíná dodávat ropu do východní Evropy.\n**1990** – Papež Jan Pavel II. zahájil dvoudenní návštěvu Československa, historicky první oficiální návštěvu papeže v Československu.\n**2012** – Největší demonstrace od listopadu 1989. Odbory a dvě desítky iniciativ a sdružení přilákaly do Prahy prý až 120 tisíc protestujících. Odboráři požadovali demisi Nečasovy vlády a předčasné volby, odmítli vládní reformy.\n**2016** – Ve věku 57 let zemřel Prince, americký hudebník.\n**2019** – Teroristické útoky na Srí Lance.", "22 04": "Svátek má **Evžénie**\n\n**Události:**\n**687 př. n. l.** – Nejstarší čínský zápis o sprše meteorů v souhvězdí Lyry.\n**1370** – V Paříži byly položeny základy královské pevnosti Bastila.\n**1507** – Německý kartograf Martin Waldseemüller vydal knihu nazvanou Cosmographiae Introductio, kde se poprvé objevil název Amerika. Omylem se domníval, že cestovatel Amerigo Vespucci byl první Evropan, který objevil nový světadíl a na jeho počest ho nazvali Amerikou.\n**1870** – Narodil se Vladimir Iljič Lenin, ruský politik a komunistický revolucionář.\n**1897** – Badeniho jazyková nařízení o rovnoprávnosti češtiny s němčinou, která měla nadále platit nejen ve vnějším, nýbrž i ve vnitřním úřadování, byla vydána také pro Moravu.\n**1904** – Narodil se Robert Oppenheimer, americký teoretický fyzik, „otec atomové bomby“.\n**1945** – Rudá armáda osvobodila Opavu.\n**1947** – Cestovatelé Jiří Hanzelka a Miroslav Zikmund vyjeli od Aeroklubu v Praze na výpravu přes Afriku do Jižní Ameriky.\n**1957** – Poprvé se losovala Sportka. Jedna sázka stála 3 koruny a maximální výhra byla 40.000 Kčs.\n**1992** – Došlo k sérii výbuchů kanalizace ve čtvrti Reforma mexické Guadalajary. Čtvrť byla zničena, stovky lidí zemřely a tisíce byly těžce zraněny.\n**1994** – V 81 letech zemřel Richard Nixon, dřívější americký prezident.", "23 04": "Svátek má **Vojtěch**\n\n**Události:**\n**1348** – Král Eduard III. zakládá nejstarší rytířský řád Podvazkového pásu.\n**1500** – Pedro Álvares Cabral přistál v Brazílii a zabral ji pro portugalskou korunu.\n**1616** – V 52 letech zemřel William Shakespeare, anglický spisovatel.\n**1836** – Máchův Máj vyšel v Praze nákladem 600 výtisků.\n**1919** – K Olomouci bylo zákonem připojeno 13 sousedních obcí, vznikla Velká Olomouc.\n**1945** – Valašská obec Prlov byla vyhlazena německými nacisty.\n**1945** – Rudá armáda dorazila k Brnu a začala s jeho osvobozováním.\n**1996** – Na českém internetu se objevil *Neviditelný pes* (čistě internetový deník) s původním českým obsahem.\n**2005** – Na YouTube bylo publikováno zcela první video „Me at the zoo“ (Já v zoo), kde autor komentoval expozici slonů v zoologické zahradě v americkém San Diegu. Odkaz naleznete zde: https://youtu.be/jNQXAC9IVRw \n**2006** – Přestal se zveřejňovat monetární agregát M3 amerického dolaru.", "24 04": "Svátek má **Jiří**\n\n**Události:**\n\n**1547** – V bitvě u Mühlberku porazil císař Karel V. se svými spojenci vojska protestantských knížat.\n**1617** – Ludvík XIII. se násilným převratem chopil faktické moci ve Francii po té, co nechal odstranit maršála d'Ancré, jeho manželku Leonoru Galigai a svou matku Marii Medicejskou internoval.\n**1704** – Začaly vycházet The Boston News-Letter, první komerčně úspěšné noviny v USA.\n**1715** – Severní válka: Dánské loďstvo dostihlo a rozdrtilo švédské nájezdníky ve Fehmarnské úžině.\n**1877** – Vypukla rusko-turecká válka (1877-1878).\n**1915** – Zatčení okolo 250 arménských intelektuálů a vůdců v Istanbulu představuje počátek arménské genocidy.\n**1916** – Irští republikáni v Dublinu začali Velikonoční povstání.\n**1953** – Winston Churchill byl královnou Alžbětou II. pasován na rytíře.\n**1961** – Na hladinu byla vyzvednuta švédská válečná loď Vasa, která se potopila v roce 1628.\n**1967** – Kosmonaut Vladimir Komarov zemřel v přistávacím pouzdře rakety Sojuz 1, protože se mu neotevřel jeho padák.\n**1968** – Mauritius se stal členským státem OSN.\n**1975** – Německá krajně levicová teroristická skupina RAF zaútočila na Velvyslanectví Spolkové republiky Německo ve Stockholmu.\n**1990** – Vynesen na oběžnou dráhu vesmírný Hubbleův teleskop.\n**2003** – Microsoft vydal Windows Server 2003.\n**2009** – Došlo k uvedení do provozu italské tramvajové trati spojující města Bergamo a Albino v Lombardii.\n**2015** – Papež František přijal českého prezidenta Miloše Zemana ve Vatikánu.", diff --git a/thorn/Config/feeds.example.json b/thorn/Config/feeds.example.json new file mode 100644 index 0000000..4f2dcc6 --- /dev/null +++ b/thorn/Config/feeds.example.json @@ -0,0 +1,17 @@ +[ + { + "Link": "http://scp-cs.wikidot.com/feed/site-changes.xml", + "ChannelIds": [800776102236324294], + "Filter": ["new page"], + "EmbedColor": 16711680, + "NewPageAnnouncement": true, + "RequireAuth": false + }, + { + "Link": "http://scp-cs.wikidot.com/feed/site-changes.xml", + "ChannelIds": [735012092329918297], + "EmbedColor": 16711680, + "NewPageAnnouncement": false, + "RequireAuth": false + } +] diff --git a/thorn/Jobs/JobSchedule.cs b/thorn/Jobs/JobSchedule.cs deleted file mode 100644 index a004700..0000000 --- a/thorn/Jobs/JobSchedule.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace thorn.Jobs; - -public class JobSchedule -{ - public JobSchedule(Type jobType, string cronExpression) - { - JobType = jobType; - CronExpression = cronExpression; - } - - public Type JobType { get; } - public string CronExpression { get; } -} \ No newline at end of file diff --git a/thorn/Jobs/QuartzHostedService.cs b/thorn/Jobs/QuartzHostedService.cs deleted file mode 100644 index af968c2..0000000 --- a/thorn/Jobs/QuartzHostedService.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Quartz; -using Quartz.Spi; - -namespace thorn.Jobs; - -public class QuartzHostedService : IHostedService -{ - private readonly ISchedulerFactory _schedulerFactory; - private readonly IJobFactory _jobFactory; - private readonly IEnumerable _jobSchedules; - - public QuartzHostedService( - ISchedulerFactory schedulerFactory, - IJobFactory jobFactory, - IEnumerable jobSchedules) - { - _schedulerFactory = schedulerFactory; - _jobSchedules = jobSchedules; - _jobFactory = jobFactory; - } - - public IScheduler Scheduler { get; set; } - - public async Task StartAsync(CancellationToken cancellationToken) - { - Scheduler = await _schedulerFactory.GetScheduler(cancellationToken); - Scheduler.JobFactory = _jobFactory; - - foreach (var jobSchedule in _jobSchedules) - { - var job = CreateJob(jobSchedule); - var trigger = CreateTrigger(jobSchedule); - - await Scheduler.ScheduleJob(job, trigger, cancellationToken); - } - - await Scheduler.Start(cancellationToken); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - await Scheduler?.Shutdown(cancellationToken); - } - - private static IJobDetail CreateJob(JobSchedule schedule) - { - var jobType = schedule.JobType; - return JobBuilder - .Create(jobType) - .WithIdentity(jobType.FullName ?? string.Empty) - .WithDescription(jobType.Name) - .Build(); - } - - private static ITrigger CreateTrigger(JobSchedule schedule) - { - return TriggerBuilder - .Create() - .WithIdentity($"{schedule.JobType.FullName}.trigger") - .WithCronSchedule(schedule.CronExpression) - .WithDescription(schedule.CronExpression) - .Build(); - } -} \ No newline at end of file diff --git a/thorn/Jobs/ReminderJob.cs b/thorn/Jobs/ReminderJob.cs index b6dfa4e..f0ecd49 100644 --- a/thorn/Jobs/ReminderJob.cs +++ b/thorn/Jobs/ReminderJob.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using Discord; using Discord.WebSocket; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Quartz; -using thorn.Services; namespace thorn.Jobs; @@ -14,16 +14,15 @@ public class ReminderJob : IJob { private readonly ILogger _logger; private readonly SocketTextChannel _channel; - private readonly ConstantsService _constants; private readonly Dictionary _daily; - public ReminderJob(ILogger logger, DiscordSocketClient client, ConstantsService constants, IConfiguration config) + public ReminderJob(ILogger logger, DiscordSocketClient client, IConfiguration config) { _logger = logger; - _constants = constants; + var generalId = ulong.Parse(config["channels:general"] ?? throw new Exception("No general channel configured"), NumberStyles.Any); - _channel = client.GetChannel(_constants.Channels["general"]) as SocketTextChannel; + _channel = client.GetChannel(generalId) as SocketTextChannel; _daily = config.GetSection("daily").Get>(); } @@ -37,8 +36,6 @@ public async Task Execute(IJobExecutionContext context) { Title = "Krásné dobré ráno!", Description = description, - ThumbnailUrl = - "https://cdn.discordapp.com/attachments/537064369725636611/733080455217283131/calendar-flat.png", Color = Color.Green }.Build(); diff --git a/thorn/Jobs/RssJob.cs b/thorn/Jobs/RssJob.cs index 213bc41..6455831 100644 --- a/thorn/Jobs/RssJob.cs +++ b/thorn/Jobs/RssJob.cs @@ -3,8 +3,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; -using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -24,17 +22,13 @@ public class RssJob : IJob private readonly ILogger _logger; private readonly List _configs; private readonly Dictionary _channels; - private readonly HttpClient _webClient; - private readonly DiscordSocketClient _client; private Dictionary _lastUpdates; public RssJob(ILogger logger, DiscordSocketClient client) { _logger = logger; - _client = client; _channels = new Dictionary(); _lastUpdates = new Dictionary(); - _webClient = new HttpClient(); _configs = JsonConvert.DeserializeObject>(File.ReadAllText("Config/feeds.json")); @@ -70,24 +64,7 @@ public async Task Execute(IJobExecutionContext context) private async Task> GetNewItems(FeedConfig feedConfig) { - Feed feed; - - if (!feedConfig.RequireAuth) - { - feed = await FeedReader.ReadAsync(feedConfig.Link); - } - // TODO: I don't have time for a refactor now, but this whole section is broken anyway. Maintenance is in order here. - // else - // { - // // Basic HTTP auth - // _webClient.Credentials = new NetworkCredential(feedConfig.Username, feedConfig.Password); - // var response = _webClient.DownloadString(feedConfig.Link); - // feed = FeedReader.ReadFromString(response); - // } - else - return new List(); - // don't @ me - + var feed = await FeedReader.ReadAsync(feedConfig.Link); var lastUpdate = _lastUpdates[feedConfig]; if (lastUpdate is null) @@ -108,21 +85,12 @@ private async Task> GetNewItems(FeedConfig feedConfig) private Embed GetEmbed(FeedItem feedItem, FeedConfig feedConfig) { - // TODO: Get UserAccount and link to appropriate pages, these bugs relate to that: - // - Exits when null exception (no user with that username): - // - Just catch it and pass a placeholder "user unknown" or something - // - People with space in their name. Haven't tested this yet but I am pretty sure it won't work - var description = new Converter().Convert(feedItem.Description) .Replace("", "").Replace("", ""); if (feedConfig.NewPageAnnouncement) return GetAnnouncementEmbed(feedItem, feedConfig, description); - // There is a bug in the Html2Markdown library that inserts about 10 newlines instead of like 2 so this has to be in place - // (not needed cuz I got rid of the whole section lol) - // description = Regex.Replace(description, @"\n{4,}", "\n\n\n"); - // Remove two redundant lines var split = description.Split("\n").ToList(); split.RemoveRange(2, 2); @@ -151,24 +119,11 @@ private Embed GetAnnouncementEmbed(FeedItem feedItem, FeedConfig feedConfig, str var author = GetUsername(text); var description = new StringBuilder("Nový článek na wiki! Yay! \\o/\n"); - if (!(author is null)) - { - // TODO: fix this - /* - if (!(account is null)) - { - var user = _client.GetUser(account.Id) as SocketGuildUser; - description.Append($"[{title}]({feedItem.Link}) od uživatele {user?.Mention}"); - } - */ - // else + if (author is not null) description.Append($"[{title}]({feedItem.Link}) od uživatele `{author}`"); - } else - { description.Append($"[{title}]({feedItem.Link}) od kdoví koho :/"); - } - + return new EmbedBuilder { Title = title, diff --git a/thorn/Jobs/SingletonJobFactory.cs b/thorn/Jobs/SingletonJobFactory.cs deleted file mode 100644 index 4974ac6..0000000 --- a/thorn/Jobs/SingletonJobFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Quartz; -using Quartz.Spi; - -namespace thorn.Jobs; - -public class SingletonJobFactory : IJobFactory -{ - private readonly IServiceProvider _serviceProvider; - - public SingletonJobFactory(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) - { - return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob; - } - - public void ReturnJob(IJob job) - { - } -} \ No newline at end of file diff --git a/thorn/Modules/AdminInteractionModule.cs b/thorn/Modules/AdminInteractionModule.cs new file mode 100644 index 0000000..8df8db5 --- /dev/null +++ b/thorn/Modules/AdminInteractionModule.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Discord.Interactions; +using Discord.Rest; +using Discord.WebSocket; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace thorn.Modules; + +public class AdminInteractionModule(ILogger logger, IConfiguration config) : InteractionModuleBase +{ + [SlashCommand("stop", "Pošle stopku")] + [RequireRole("O5", Group = "Admins")] + [RequireRole("O4", Group = "Admins")] + public async Task Stop() + { + await RespondWithFileAsync("Media/stop.png"); + logger.LogInformation("{ContextUser} used STOP in #{ContextChannel}", Context.User, Context.Channel); + } + + [SlashCommand("řekni", "Něco řekne")] + [RequireRole("O5", Group = "Admins")] + [RequireRole("O4", Group = "Admins")] + public async Task Say([ChannelTypes(ChannelType.Text)] ISocketMessageChannel channel, string text) + { + await channel.SendMessageAsync(text); + await RespondAsync($"zpráva poslána do kanálu {channel}!", ephemeral: true); + } + + [SlashCommand("hlasování", "Zahájí hlasování")] + [RequireRole("O5", Group = "Admins")] + [RequireRole("O4", Group = "Admins")] + public async Task Vote([ChannelTypes(ChannelType.Text)] ISocketMessageChannel channel, string text) + { + var msg = await channel.SendMessageAsync(text); + await AddVoteEmotes(msg); + await RespondAsync($"hlasování zahájeno v kanálu {channel}!", ephemeral: true); + } + + private async Task AddVoteEmotes(RestUserMessage msg) + { + await msg.AddReactionAsync(Emote.Parse(config["emotes:yes"])); + await msg.AddReactionAsync(Emote.Parse(config["emotes:no"])); + await msg.AddReactionAsync(Emote.Parse(config["emotes:abstain"])); + + // I swear to god, since when are there 2 different eye emojis? + // await msg.AddReactionAsync(new Emoji("👁️")); This one doesn't work, I think + await msg.AddReactionAsync(new Emoji("👁")); + } +} \ No newline at end of file diff --git a/thorn/Modules/AdminModule.cs b/thorn/Modules/AdminModule.cs deleted file mode 100644 index 3ae8478..0000000 --- a/thorn/Modules/AdminModule.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.Threading.Tasks; -using Discord; -using Discord.Commands; -using Discord.WebSocket; -using Microsoft.Extensions.Logging; -using thorn.Services; - -namespace thorn.Modules; - -public class AdminModule : ModuleBase -{ - private readonly DiscordSocketClient _client; - private readonly ConstantsService _constants; - private readonly ILogger _logger; - - public AdminModule(ILogger logger, DiscordSocketClient client, ConstantsService constants) - { - _logger = logger; - _client = client; - _constants = constants; - } - - [Command("info")] - public async Task InfoCommand() - { - await ReplyAsync(embed: new EmbedBuilder - { - Title = "Thorn.aic", - Description = $"{_constants.Strings.info}\n\n**Ping:** {_client.Latency}ms", - ThumbnailUrl = _client.CurrentUser.GetAvatarUrl(), - Color = new Color(153, 204, 0) - }.Build()); - } - - [Command("stop")] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task StopCommand() - { - await Context.Channel.SendFileAsync("Media/stop.png"); - _logger.LogInformation("{ContextUser} used STOP in #{ContextChannel}", Context.User, Context.Channel); - } - - [Command("status")] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task StatusCommand(string mode, [Remainder] string status) - { - IActivity game = mode switch - { - "streaming" => new Game(status, ActivityType.Streaming), - "watching" => new Game(status, ActivityType.Watching), - "listening" => new Game(status, ActivityType.Listening), - _ => new Game(status) - }; - - await _client.SetActivityAsync(game); - - _logger.LogInformation("{ContextUser} changed {Game} status to: {Status}", Context.User, game.Type, status); - } - - [Command("say")] - [Priority(0)] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task SayCommand([Remainder] string text) - { - await Context.Message.DeleteAsync(); - await ReplyAsync(text); - _logger.LogInformation("{ContextUser} said \"{Text}\" in #{ContextChannel}", Context.User, text, - Context.Channel); - } - - [Command("say")] - [Priority(1)] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task SayElsewhereCommand(SocketTextChannel channel, [Remainder] string text) - { - await channel.SendMessageAsync(text); - // ReSharper disable once ComplexObjectDestructuringProblem - It freezes the gateway task, and that's not good lol - _logger.LogInformation("{ContextUser} said \"{Text}\" in #{Channel}", Context.User, text, channel); - } - - [Command("edit")] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task EditCommand(IUserMessage message, [Remainder] string text) - { - // BUG: If the message is not in cache, it will not get edited - // Not sure how to resolve this, I have not found a way to download a single message by ID - await message.ModifyAsync(x => x.Content = text); - _logger.LogInformation("{User} edited message '{Message}' to '{Text}'", Context.User, message.Content, text); - } - - - [Command("vote")] - [Priority(0)] - // This command is available only to OPs, because this *may* be recognised by users as an "official vote" - // I'll change this maybe someday? - [RequireUserPermission(GuildPermission.Administrator)] - public async Task VoteCommand([Remainder] string text) - { - var msg = await ReplyAsync(text); - await AddVoteEmotes(msg); - _logger.LogInformation("{ContextUser} made a vote in #{ContextChannel}: {Text}", Context.User, Context.Channel, - text); - } - - [Command("vote")] - [Priority(1)] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task VoteElsewhereCommand(SocketTextChannel channel, [Remainder] string text) - { - var msg = await channel.SendMessageAsync(text); - await AddVoteEmotes(msg); - // ReSharper disable once ComplexObjectDestructuringProblem - It freezes the gateway task, and that's not good lol - _logger.LogInformation("{ContextUser} made a vote in #{Channel}: {Text}", Context.User, channel, text); - } - - - // Boring stuff - - [Command("mping")] - [Alias("master-ping")] - [RequireUserPermission(GuildPermission.Administrator)] - // This exists solely for debugging purposes - public async Task MasterPingCommand() - { - await ReplyAsync("Pong!"); - _logger.LogInformation("{ContextUser} pinged!", Context.User); - } - - [Command("loads")] - [Alias("load-strings")] - [RequireUserPermission(GuildPermission.Administrator)] - public async Task ReloadStringsCommand() - { - _constants.ReloadConstants(); - _logger.LogInformation("{ContextUser} reloaded strings", Context.User); - await ReplyAsync("Reloaded!"); - } - - private async Task AddVoteEmotes(IMessage msg) - { - await msg.AddReactionAsync(Emote.Parse((string)_constants.Strings.emote.yes)); - await msg.AddReactionAsync(Emote.Parse((string)_constants.Strings.emote.no)); - await msg.AddReactionAsync(Emote.Parse((string)_constants.Strings.emote.abstain)); - - // I swear to god, since when are there 2 different eye emojis? - // await msg.AddReactionAsync(new Emoji("👁️")); This one doesn't work, I think - await msg.AddReactionAsync(new Emoji("👁")); - } -} \ No newline at end of file diff --git a/thorn/Modules/HelpModule.cs b/thorn/Modules/HelpModule.cs deleted file mode 100644 index 3e03b80..0000000 --- a/thorn/Modules/HelpModule.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord; -using Discord.Commands; -using thorn.Services; - -namespace thorn.Modules; - -[Group("help")] -[Alias("halp", "pomoc", "cože", "hlep", "aaa", "h", "fuckme", "hwelp")] -public class HelpModule : ModuleBase -{ - private readonly ConstantsService _constants; - - public HelpModule(ConstantsService constants) - { - _constants = constants; - } - - [Command] - public async Task HelpCommand(int page = 1) - { - var maxPages = _constants.Help.Count; - if (page > maxPages || page <= 0) page = 1; - var titleIndex = _constants.Help[page - 1].IndexOf(Environment.NewLine); - - var embed = new EmbedBuilder - { - Title = _constants.Help[page - 1][..titleIndex], - Description = _constants.Help[page - 1][++titleIndex..], - Color = Color.LightGrey, - Footer = new EmbedFooterBuilder().WithText($"Strana {page} z {maxPages}") - }.Build(); - - await ReplyAsync(embed: embed); - } - - [Command] - public async Task HelpCommand(string page) - { - string help; - - switch (page) - { - case "translation": - case "překlad": - case "překlady": - case "translations": - case "t": - case "translating": - help = _constants.Strings.specificHelp.translation; - break; - case "writing": - case "psaní": - case "w": - help = _constants.Strings.specificHelp.writing; - break; - case "correction": - case "korekce": - case "c": - case "correcting": - help = _constants.Strings.specificHelp.correction; - break; - case "join": - case "připoj-se": - case "připojit": - case "členství": - case "j": - case "joining": - help = _constants.Strings.specificHelp.join; - break; - default: - return; - } - - await ReplyAsync(embed: new EmbedBuilder - { - Description = help, - Color = Color.Blue - }.Build()); - } -} \ No newline at end of file diff --git a/thorn/Modules/MiscModule.cs b/thorn/Modules/MiscModule.cs deleted file mode 100644 index a98a05e..0000000 --- a/thorn/Modules/MiscModule.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Discord.Commands; - -namespace thorn.Modules; - -[Summary("Miscellaneous functions")] -public class MiscModule : ModuleBase -{ - // You cannot have a proper bot without these :) - - [Command("ping")] - public async Task PingCommand() - { - await ReplyAsync("Pong!"); - } - - [Command("pong")] - public async Task PongCommand() - { - await ReplyAsync("Ping!"); - } -} \ No newline at end of file diff --git a/thorn/Modules/UserInteractionModule.cs b/thorn/Modules/UserInteractionModule.cs new file mode 100644 index 0000000..a80c627 --- /dev/null +++ b/thorn/Modules/UserInteractionModule.cs @@ -0,0 +1,93 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Discord.Interactions; +using Microsoft.Extensions.Configuration; + +namespace thorn.Modules; + +public enum Help +{ + [ChoiceDisplay("Připojení na stránku")] Join, + [ChoiceDisplay("Překlad")] Translate, + [ChoiceDisplay("Psaní")] Write, + [ChoiceDisplay("Korekce")] Correction, +} + +public class UserInteractionModule(IConfiguration config) : InteractionModuleBase +{ + private readonly Random _random = new(); + + [SlashCommand("ping", "pong!")] + public async Task Ping() => await RespondAsync("pong!"); + + [SlashCommand("pomoc", "Pomoc s překládáním, psaním, korekcí a připojením na stránku.")] + public async Task Help([Summary("téma", "o čem zobrazit pomoc?")] Help choice) + { + switch (choice) + { + case Modules.Help.Join: + await RespondAsync(config["helpText:join"]); + break; + case Modules.Help.Translate: + await RespondAsync(config["helpText:translate"]); + break; + case Modules.Help.Write: + await RespondAsync(config["helpText:write"]); + break; + case Modules.Help.Correction: + await RespondAsync(config["helpText:correction"]); + break; + default: + await RespondAsync("wtf"); + break; + } + } + + [SlashCommand("info", "Informace o Thornovi")] + public async Task Info() + { + var embed = new EmbedBuilder + { + Title = "Thorn.aic", + Description = $"{config["miscText:info"]}\n\n**Ping:** {Context.Client.Latency}ms", + ThumbnailUrl = Context.Client.CurrentUser.GetAvatarUrl(), + Color = new Color(153, 204, 0) + }.Build(); + + await RespondAsync(embed: embed); + } + + [SlashCommand("penis", "pls penis \ud83d\ude33")] + public async Task Penis() + { + var len = _random.Next(15); + await RespondAsync($"{Context.User.Mention} tvůj pele: `8{new string('=', len)}D`"); + } + + [SlashCommand("waifu", "how waifu?")] + public async Task Waifu([Summary("uživatel", "jaký uživatel bude waifu?")] IUser user=null) + { + if (user == Context.Client.CurrentUser) + await RespondAsync("já jsem ta nejlepší waifu :3"); + + user ??= Context.User; + + var p = _random.Next(101); + var mention = $"{user.Mention} "; + + mention += p switch + { + 0 => "Jsi druhý příchod kristova vědomí (0% waifu) \\o/", + < 15 => $"Nic moc kamaráde, dnes jsi jen {p}% waifu :(", + < 50 => $"Ujde to příteli! Jsi z {p}% waifu.", + < 80 => $"Sluší ti to :) jsi {p} procentní waifu!", + < 100 => $"Neuvěřitelné! Jsi waifu ze skvělých {p}% :D", + _ => "Honto? 100%! Omae wa waifu no materiaru da yo!! Sugoi!!! 😳" + }; + + await RespondAsync(mention); + } + + +} \ No newline at end of file diff --git a/thorn/Program.cs b/thorn/Program.cs index 4ffaf88..3ec3995 100644 --- a/thorn/Program.cs +++ b/thorn/Program.cs @@ -5,9 +5,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Quartz; -using Quartz.Impl; -using Quartz.Spi; using Serilog; using Serilog.Events; using thorn.Jobs; @@ -26,51 +25,58 @@ private static async Task Main() .WriteTo.File("log/log.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); - var hostBuilder = Host.CreateDefaultBuilder() - .ConfigureAppConfiguration(x => - { - x.AddJsonFile("Config/config.json"); - x.AddJsonFile("Config/daily.json"); - x.Build(); - }) - .UseSerilog() - .ConfigureDiscordHost((context, config) => - { - config.SocketConfig = new DiscordSocketConfig - { - AlwaysDownloadUsers = true, - DefaultRetryMode = RetryMode.RetryRatelimit, - MessageCacheSize = 50, - LogLevel = LogSeverity.Info, - GatewayIntents = GatewayIntents.All, - }; + var builder = Host.CreateApplicationBuilder(); - config.Token = context.Configuration["token"]; - config.LogFormat = (message, _) => $"{message.Source}: {message.Message}"; - }) - .UseCommandService((_, config) => { config.LogLevel = LogSeverity.Info; }) - .ConfigureServices((_, collection) => + builder.Configuration.AddJsonFile("Config/config.json"); + builder.Configuration.AddJsonFile("Config/daily.json"); + + builder.Logging.ClearProviders(); + builder.Logging.AddSerilog(); + + builder.Services.AddDiscordHost((config, _) => + { + config.SocketConfig = new DiscordSocketConfig { - collection.AddHostedService(); - collection.AddHostedService(); - collection.AddHostedService(); + LogLevel = LogSeverity.Info, + MessageCacheSize = 50, + GatewayIntents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages | + GatewayIntents.GuildMessageReactions + }; + + config.Token = builder.Configuration["token"]!; + config.LogFormat = (message, _) => $"{message.Source}: {message.Message}"; + }); + + builder.Services.AddCommandService((config, _) => { config.LogLevel = LogSeverity.Info; }); + builder.Services.AddInteractionService((config, _) => + { + config.LogLevel = LogSeverity.Info; + config.UseCompiledLambda = true; + }); - collection.AddSingleton(); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); - collection.AddSingleton(); - collection.AddSingleton(); + builder.Services.AddQuartz(configure => + { + var twoMinSchedule = CronScheduleBuilder.CronSchedule("0 */2 * ? * *"); + var rssJob = new JobKey(nameof(RssJob)); + configure.AddJob(rssJob) + .AddTrigger(t => t.ForJob(rssJob).WithSchedule(twoMinSchedule)); - collection.AddSingleton(); - collection.AddSingleton(new JobSchedule( - typeof(ReminderJob), - "0 0 0 * * ?")); // Every day at midnight + var dailySchedule = CronScheduleBuilder.CronSchedule("0 0 0 * * ?"); + var reminderJob = new JobKey(nameof(ReminderJob)); + configure.AddJob(reminderJob) + .AddTrigger(t => t.ForJob(reminderJob).WithSchedule(dailySchedule)); + }); + builder.Services.AddQuartzHostedService(); - collection.AddSingleton(); - collection.AddSingleton(new JobSchedule( - typeof(RssJob), - "0 0/2 * * * ?")); // Every two minutes - }); + var host = builder.Build(); - await hostBuilder.RunConsoleAsync(); + await host.RunAsync(); } } \ No newline at end of file diff --git a/thorn/Services/ArticleData.cs b/thorn/Services/ArticleData.cs deleted file mode 100644 index 5968a57..0000000 --- a/thorn/Services/ArticleData.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; - -// Instantiated by the Json library -// ReSharper disable ClassNeverInstantiated.Global - -namespace thorn.Services; - -public class ResponseType -{ - public List SearchPages { get; set; } -} - -public class ArticleData -{ - public string Url { get; set; } - public WikidotInfo WikidotInfo { get; set; } - public List AlternateTitles { get; set; } - public List Attributions { get; set; } -} - -public class WikidotInfo -{ - public string Title { get; set; } - - public int Rating { get; set; } - // public string ThumbnailUrl { get; set; } -} - -public class AlternateTitle -{ - // public string Type { get; set; } - public string Title { get; set; } -} - -public class Attribution -{ - // public string Type { get; set; } - public User User { get; set; } -} - -public class User -{ - public string Name { get; set; } -} \ No newline at end of file diff --git a/thorn/Services/CommandHandler.cs b/thorn/Services/CommandHandler.cs deleted file mode 100644 index b37feec..0000000 --- a/thorn/Services/CommandHandler.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Discord; -using Discord.Addons.Hosting; -using Discord.Commands; -using Discord.WebSocket; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using thorn.UserAccounts; - -namespace thorn.Services; - -public class CommandHandler : DiscordClientService -{ - private readonly IServiceProvider _provider; - private readonly DiscordSocketClient _client; - private readonly CommandService _command; - private readonly IConfiguration _config; - - private readonly ConstantsService _constants; - private readonly Random _random; - - public CommandHandler(IServiceProvider provider, DiscordSocketClient client, CommandService commandService, - IConfiguration config, ConstantsService constants, ILogger logger) : - base(client, logger) - { - _provider = provider; - _client = client; - _command = commandService; - _config = config; - _constants = constants; - _random = new Random(); - } - - protected override async Task ExecuteAsync(CancellationToken cancellationToken) - { - _client.MessageReceived += HandleCommandAsync; - - _command.AddTypeReader(typeof(PointType), new PointTypeTypeReader()); - _command.AddTypeReader(typeof(AccountItem), new AccountItemTypeReader()); - await _command.AddModulesAsync(Assembly.GetEntryAssembly(), _provider); - } - - private async Task HandleCommandAsync(SocketMessage m) - { - if (m is not SocketUserMessage msg || m.Source == MessageSource.Bot) return; - - var argPos = 0; - - if (!msg.HasStringPrefix(_config["prefix"], ref argPos)) - { - if (await HahaFunni(m)) return; - } - else - { - var context = new SocketCommandContext(_client, msg); - await _command.ExecuteAsync(context, argPos, _provider); - } - } - - private async Task HahaFunni(SocketMessage m) - { - if (m.Content.Equals("good bot", StringComparison.InvariantCultureIgnoreCase)) - await m.AddReactionAsync(new Emoji("💕")); - - else if (m.Content.Equals("bad bot", StringComparison.InvariantCultureIgnoreCase)) - await m.AddReactionAsync(new Emoji("😔")); - - else if (Regex.IsMatch(m.Content, @"d[íi]ky(, |,| )thorne", RegexOptions.IgnoreCase)) - await m.AddReactionAsync(new Emoji("❤️")); - - else if (Regex.IsMatch(m.Content, @"nepozn[áa]v[áa]m t[ay] t[ěe]la ve vod[ěe]", RegexOptions.IgnoreCase)) - await m.Channel.SendMessageAsync(_random.Next(2) == 0 - ? (string) _constants.Strings.bodies.error - : (string) _constants.Strings.bodies.success); - - else if (Regex.IsMatch(m.Content, @"pozn[áa]v[áa]m t[ay] t[ěe]la ve vod[ěe]", RegexOptions.IgnoreCase)) - await m.Channel.SendMessageAsync((string) _constants.Strings.bodies.ohgodohfuck); - - else if (Regex.IsMatch(m.Content, @"pls penis")) - await m.Channel.SendMessageAsync(GeneratePenis(m.Author)); - - else if (Regex.IsMatch(m.Content, @":3")) - await m.Channel.SendMessageAsync(":33"); - - else if (Regex.IsMatch(m.Content, @"how waifu[\?]?", RegexOptions.IgnoreCase)){ - SocketUser usr; - try { usr = m.MentionedUsers.First(); } - catch { usr = m.Author; } - - await m.Channel.SendMessageAsync(HowWaifu(usr)); - } - - else return false; - return true; - } - - private string GeneratePenis(SocketUser usr) - { - var len = _random.Next(15); - return $"{usr.Mention} tvůj pele: `8{new string('=', len)}D`"; - } - - private string HowWaifu(SocketUser usr) - { - var p = _random.Next(101); - var mention = $"{usr.Mention} "; - - if (p == 0) - mention += $"Jsi druhý příchod kristova vědomí (0% waifu) \\o/"; - else if (p < 15) - mention += $"Nic moc kamaráde, dnes jsi jen {p}% waifu :("; - else if (p < 50) - mention += $"Ujde to příteli! Jsi z {p}% waifu."; - else if (p < 80) - mention += $"Sluší ti to :) jsi {p} procentní waifu!"; - else if (p < 100) - mention += $"Neuvěřitelné! Jsi waifu ze skvělých {p}% :D"; - else - mention += $"Honto? 100%! Omae wa waifu no materiaru da yo!! Sugoi!!! 😳"; - - return mention; - } -} diff --git a/thorn/Services/ConstantsService.cs b/thorn/Services/ConstantsService.cs deleted file mode 100644 index 280724a..0000000 --- a/thorn/Services/ConstantsService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Newtonsoft.Json; - -namespace thorn.Services; - -public class ConstantsService -{ - private const string ConstantsFile = "Config/constants.json"; - private const string ConstantsFolder = "Config"; - - public Dictionary Channels { get; private set; } - public Dictionary Roles { get; private set; } - public List Help { get; set; } - public dynamic Strings { get; private set; } - - public ConstantsService() - { - if (!File.Exists(ConstantsFile) || !Directory.Exists(ConstantsFolder)) - throw new Exception("Constants file does not exist!"); - ReloadConstants(); - } - - public void ReloadConstants() - { - var constants = JsonConvert.DeserializeObject(File.ReadAllText(ConstantsFile)); - - Channels = constants.Channels; - Roles = constants.Roles; - Help = constants.Help; - Strings = constants.Strings; - } - - private struct Constants - { - public Dictionary Channels { get; set; } - public Dictionary Roles { get; set; } - public List Help { get; set; } - public dynamic Strings { get; set; } - } -} \ No newline at end of file diff --git a/thorn/Services/InteractionHandler.cs b/thorn/Services/InteractionHandler.cs new file mode 100644 index 0000000..a7928af --- /dev/null +++ b/thorn/Services/InteractionHandler.cs @@ -0,0 +1,45 @@ +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Discord; +using Discord.Addons.Hosting; +using Discord.Addons.Hosting.Util; +using Discord.Interactions; +using Discord.WebSocket; +using Microsoft.Extensions.Logging; + +namespace thorn.Services; + +public class InteractionHandler( + DiscordSocketClient client, + ILogger logger, + InteractionService handler, + IServiceProvider provider) + : DiscordClientService(client, logger) +{ + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + await handler.AddModulesAsync(Assembly.GetEntryAssembly(), provider); + + Client.InteractionCreated += HandleInteraction; + handler.InteractionExecuted += HandleExecuted; + + await Client.WaitForReadyAsync(cancellationToken); + + await handler.RegisterCommandsGloballyAsync(); + } + + private async Task HandleExecuted(ICommandInfo command, IInteractionContext context, IResult result) + { + if (!result.IsSuccess && result.Error == InteractionCommandError.UnmetPrecondition) + await context.Interaction.RespondAsync("na tuto akci nemáš oprávnění :(", ephemeral: true); + } + + + private async Task HandleInteraction(SocketInteraction interaction) + { + var context = new SocketInteractionContext(Client, interaction); + await handler.ExecuteCommandAsync(context, provider); + } +} \ No newline at end of file diff --git a/thorn/Services/MessageHandler.cs b/thorn/Services/MessageHandler.cs new file mode 100644 index 0000000..feb3c34 --- /dev/null +++ b/thorn/Services/MessageHandler.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Discord; +using Discord.Addons.Hosting; +using Discord.Commands; +using Discord.WebSocket; +using Microsoft.Extensions.Logging; + +namespace thorn.Services; + +public class MessageHandler( + IServiceProvider provider, + DiscordSocketClient client, + CommandService commandService, + ILogger logger) + : DiscordClientService(client, logger) +{ + private readonly DiscordSocketClient _client = client; + private readonly Random _random = new(); + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + _client.MessageReceived += HandleMessageAsync; + await commandService.AddModulesAsync(Assembly.GetEntryAssembly(), provider); + } + + private async Task HandleMessageAsync(SocketMessage m) + { + if (m is not SocketUserMessage msg || m.Source == MessageSource.Bot) return; + + await HahaFunni(m); + } + + private async Task HahaFunni(SocketMessage m) + { + if (m.Content.Equals("good bot", StringComparison.InvariantCultureIgnoreCase)) + await m.AddReactionAsync(new Emoji("💕")); + + else if (m.Content.Equals("bad bot", StringComparison.InvariantCultureIgnoreCase)) + await m.AddReactionAsync(new Emoji("😔")); + + else if (Regex.IsMatch(m.Content, @"d[íi]ky(, |,| )thorne", RegexOptions.IgnoreCase)) + await m.AddReactionAsync(new Emoji("❤️")); + + else if (Regex.IsMatch(m.Content, @"nepozn[áa]v[áa]m t[ay] t[ěe]la ve vod[ěe]", RegexOptions.IgnoreCase)) + await m.Channel.SendMessageAsync(_random.Next(2) == 0 + ? """[✘] Zamítnuto. CRV uživatele není v přijatelných hodnotách. CRV uživatele je ovlivněno aktivními kognitohazardy. Prosím, zůstaňte na místě, člen lékařského tým[''///afe44/25\\\\23 s vámi bude za chvíli.""" + : """[✅] Přijato. CRV uživatele je v přijatelných hodnotách."""); + + else if (Regex.IsMatch(m.Content, @"pozn[áa]v[áa]m t[ay] t[ěe]la ve vod[ěe]", RegexOptions.IgnoreCase)) + await m.Channel.SendMessageAsync("<:monkaw:939084023190421504>"); + + else if (Regex.IsMatch(m.Content, @":3")) + await m.Channel.SendMessageAsync(":33"); + + else if (Regex.IsMatch(m.Content, @"amo[n]?g\s?us")) + await m.Channel.SendMessageAsync("ඞ"); + + else return false; + return true; + } +} diff --git a/thorn/Services/ReactionHandler.cs b/thorn/Services/ReactionHandler.cs index f14a08f..c73c48a 100644 --- a/thorn/Services/ReactionHandler.cs +++ b/thorn/Services/ReactionHandler.cs @@ -1,39 +1,27 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Discord; using Discord.Addons.Hosting; using Discord.WebSocket; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace thorn.Services; -public class ReactionHandler : DiscordClientService +public class ReactionHandler(DiscordSocketClient client, IConfiguration config, ILogger logger) + : DiscordClientService(client, logger) { - private readonly DiscordSocketClient _client; - private readonly ConstantsService _constants; - - // TODO: replace with SocketTextChannel - private readonly ulong _welcomeChannelId; - private readonly ulong _loggingChannelId; - private readonly ulong _classCRoleId; - private readonly ulong _intRoleId; - private readonly ulong _o5RoleId; - private readonly ulong _o4RoleId; - - public ReactionHandler(DiscordSocketClient client, ConstantsService constants, ILogger logger) : base(client, logger) - { - _client = client; - _constants = constants; - - _welcomeChannelId = _constants.Channels["welcome"]; - _loggingChannelId = _constants.Channels["o5"]; - _classCRoleId = _constants.Roles["classC"]; - _intRoleId = _constants.Roles["INT"]; - _o5RoleId = _constants.Roles["O5"]; - _o4RoleId = _constants.Roles["O4"]; - } + private readonly DiscordSocketClient _client = client; + + private readonly ulong _welcomeChannelId = ulong.Parse(config["channels:welcome"] ?? throw new Exception("Welcome channel is not configured"), NumberStyles.Any); + private readonly ulong _loggingChannelId = ulong.Parse(config["channels:o5"] ?? throw new Exception("O5 channel is not configured"), NumberStyles.Any); + private readonly ulong _classCRoleId = ulong.Parse(config["roles:classC"] ?? throw new Exception("Role Class C is not configured"), NumberStyles.Any); + private readonly ulong _intRoleId = ulong.Parse(config["roles:INT"] ?? throw new Exception("Role INT is not configured"), NumberStyles.Any); + private readonly ulong _o5RoleId = ulong.Parse(config["roles:O5"] ?? throw new Exception("Role O5 is not configured"), NumberStyles.Any); + private readonly ulong _o4RoleId = ulong.Parse(config["roles:O4"] ?? throw new Exception("Role O4 is not configured"), NumberStyles.Any); protected override Task ExecuteAsync(CancellationToken cancellationToken) { @@ -45,8 +33,7 @@ private async Task ClientOnReactionAdded(Cacheable messageC Cacheable channelCache, SocketReaction reaction) { if (reaction.UserId == _client.CurrentUser.Id) return; - - // Reaction handling in the #welcome channel + if (reaction.Channel.Id == _welcomeChannelId) await HandleWelcomeReactions(messageCache, reaction); } diff --git a/thorn/UserAccounts/AccountItem.cs b/thorn/UserAccounts/AccountItem.cs deleted file mode 100644 index aa4565b..0000000 --- a/thorn/UserAccounts/AccountItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace thorn.UserAccounts; - -public enum AccountItem -{ - WikidotUsername, - Description, - AuthorPage, - TranslatorPage, - PrivatePage, - Sandbox, - ProfileColor -} \ No newline at end of file diff --git a/thorn/UserAccounts/AccountItemTypeReader.cs b/thorn/UserAccounts/AccountItemTypeReader.cs deleted file mode 100644 index 8569b4e..0000000 --- a/thorn/UserAccounts/AccountItemTypeReader.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; - -namespace thorn.UserAccounts; - -public class AccountItemTypeReader : TypeReader -{ - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) - { - if (Enum.TryParse(input, out AccountItem result)) - return Task.FromResult(TypeReaderResult.FromSuccess(result)); - else - switch (input) - { - case "wikidot": - case "wikidot-username": - case "w": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.WikidotUsername)); - case "d": - case "description": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.Description)); - case "author": - case "author-page": - case "ap": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.AuthorPage)); - case "translator": - case "translator-page": - case "tp": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.TranslatorPage)); - case "private": - case "private-page": - case "pp": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.PrivatePage)); - case "s": - case "sandbox": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.Sandbox)); - case "color": - case "profile-color": - case "c": - return Task.FromResult(TypeReaderResult.FromSuccess(AccountItem.ProfileColor)); - default: - return Task.FromResult(TypeReaderResult.FromError( - CommandError.ParseFailed, "Input could not be parsed as a AccountItem.")); - } - } -} \ No newline at end of file diff --git a/thorn/UserAccounts/PointType.cs b/thorn/UserAccounts/PointType.cs deleted file mode 100644 index d62f347..0000000 --- a/thorn/UserAccounts/PointType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace thorn.UserAccounts; - -public enum PointType -{ - Translation, - Writing, - Correction -} \ No newline at end of file diff --git a/thorn/UserAccounts/PointTypeTypeReader.cs b/thorn/UserAccounts/PointTypeTypeReader.cs deleted file mode 100644 index a7262f6..0000000 --- a/thorn/UserAccounts/PointTypeTypeReader.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord.Commands; - -namespace thorn.UserAccounts; - -public class PointTypeTypeReader : TypeReader -{ - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) - { - if (Enum.TryParse(input, out PointType result)) - return Task.FromResult(TypeReaderResult.FromSuccess(result)); - else - switch (input) - { - case "t": - case "p": - case "překlad": - case "preklad": - return Task.FromResult(TypeReaderResult.FromSuccess(PointType.Translation)); - case "w": - case "s": - case "psaní": - case "psani": - case "spisovatel": - case "spisovatelské": - case "spisovatelske": - return Task.FromResult(TypeReaderResult.FromSuccess(PointType.Writing)); - case "c": - case "k": - case "korekce": - return Task.FromResult(TypeReaderResult.FromSuccess(PointType.Correction)); - default: - return Task.FromResult(TypeReaderResult.FromError( - CommandError.ParseFailed, "Input could not be parsed as a PointType.")); - } - } -} \ No newline at end of file diff --git a/thorn/UserAccounts/UserAccount.cs b/thorn/UserAccounts/UserAccount.cs deleted file mode 100644 index e5faa45..0000000 --- a/thorn/UserAccounts/UserAccount.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace thorn.UserAccounts; - -public class UserAccount -{ - // Internal - public ulong Id { get; set; } - public Dictionary Points { get; set; } - public Dictionary Ranks { get; set; } - - // User settable - // ReSharper disable once MemberCanBePrivate.Global - // This has to be public because of the JsonSerializer - public Dictionary Properties { get; set; } - - public UserAccount(ulong id) - { - Id = id; - Points = new Dictionary(); - Ranks = new Dictionary(); - Properties = new Dictionary(); - - foreach (var type in (PointType[])Enum.GetValues(typeof(PointType))) - { - Points.Add(type, 0); - Ranks.Add(type, 0); - } - - foreach (var type in (AccountItem[])Enum.GetValues(typeof(AccountItem))) - Properties.Add(type, null); - } - - public override string ToString() - { - var description = new StringBuilder(); - - if (this[AccountItem.Description] != null) - description.Append(this[AccountItem.Description] + "\n\n"); - - // This is for *our* based god, the one and only, ~Utylike~ - switch (Points[PointType.Translation] > 0) - { - case true when Id != 227114285074087938: - description.Append($"**Překladatelské body:** `{Points[PointType.Translation]}`\n" + - $"**Pořadí v žebříčku:** `#{Ranks[PointType.Translation]}`\n\n"); - break; - case true when Id == 227114285074087938: - description.Append("**Překladatelské body:** `ano`\n" + - "**Pořadí v žebříčku:** `#1`\n\n"); - break; - } - - if (Points[PointType.Writing] > 0) - description.Append($"**Spisovatelské body:** `{Points[PointType.Writing]}`\n" + - $"**Pořadí v žebříčku:** `#{Ranks[PointType.Writing]}`\n\n"); - - if (Points[PointType.Correction] > 0) - description.Append($"**Korektorské body:** `{Points[PointType.Correction]}`\n" + - $"**Pořadí v žebříčku:** `#{Ranks[PointType.Correction]}`\n\n"); - - if (this[AccountItem.TranslatorPage] != null && this[AccountItem.PrivatePage] == null) - description.Append($"[Překladatelská složka]({this[AccountItem.TranslatorPage]})\n"); - - if (this[AccountItem.AuthorPage] != null && this[AccountItem.PrivatePage] == null) - description.Append($"[Spisovatelská složka]({this[AccountItem.AuthorPage]})\n"); - - if (this[AccountItem.PrivatePage] != null) - description.Append($"[Osobní složka]({this[AccountItem.PrivatePage]})\n"); - - if (this[AccountItem.Sandbox] != null) - description.Append($"[Sandbox]({this[AccountItem.Sandbox]})\n"); - - if (this[AccountItem.WikidotUsername] != null) - description.Append("[Wikidot profil](https://wikidot.com/user:info/" + - $"{this[AccountItem.WikidotUsername].ToLower().Replace(" ", "-")})"); - - return description.ToString(); - } - - public string this[AccountItem item] - { - get => Properties[item]; - set => Properties[item] = value; - } -} \ No newline at end of file diff --git a/thorn/thorn.csproj b/thorn/thorn.csproj index 1600865..0d6dc64 100644 --- a/thorn/thorn.csproj +++ b/thorn/thorn.csproj @@ -7,19 +7,18 @@ - + - - - - - - - - - - - + + + + + + + + + +