From 05120f04280344277f7f4ff4766017938e7507fc Mon Sep 17 00:00:00 2001
From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>
Date: Mon, 1 Aug 2022 14:19:34 +0300
Subject: [PATCH 01/24] Add AutoServiceScopes to IF docs
---
docs/guides/int_framework/intro.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md
index 54e9086a1..23be5b544 100644
--- a/docs/guides/int_framework/intro.md
+++ b/docs/guides/int_framework/intro.md
@@ -279,8 +279,8 @@ Meaning, the constructor parameters and public settable properties of a module w
For more information on dependency injection, read the [DependencyInjection] guides.
> [!NOTE]
-> On every command execution, module dependencies are resolved using a new service scope which allows you to utilize scoped service instances, just like in Asp.Net.
-> Including the precondition checks, every module method is executed using the same service scope and service scopes are disposed right after the `AfterExecute` method returns.
+> On every command execution, if the 'AutoServiceScopes' option is enabled in the config , module dependencies are resolved using a new service scope which allows you to utilize scoped service instances, just like in Asp.Net.
+> Including the precondition checks, every module method is executed using the same service scope and service scopes are disposed right after the `AfterExecute` method returns. This doesn't apply to methods other than `ExecuteAsync()`.
## Module Groups
From e0d68d47d48b4022c362e7e8bd293fbe0e0d6fd6 Mon Sep 17 00:00:00 2001
From: Wojciech Berdowski <10144015+wberdowski@users.noreply.github.com>
Date: Mon, 1 Aug 2022 13:20:48 +0200
Subject: [PATCH 02/24] Add note about voice binaries on linux
Makes voice section about precompiled binaries more visible.
---
docs/guides/voice/sending-voice.md | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/docs/guides/voice/sending-voice.md b/docs/guides/voice/sending-voice.md
index 555adbca2..36184e3a3 100644
--- a/docs/guides/voice/sending-voice.md
+++ b/docs/guides/voice/sending-voice.md
@@ -17,11 +17,9 @@ bot. (When developing on .NET Framework, this would be `bin/debug`,
when developing on .NET Core, this is where you execute `dotnet run`
from; typically the same directory as your csproj).
-For Windows Users, precompiled binaries are available for your
-convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives).
+**For Windows users, precompiled binaries are available for your convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives).**
-For Linux Users, you will need to compile [Sodium] and [Opus] from
-source, or install them from your package manager.
+**For Linux users, you will need to compile [Sodium] and [Opus] from source, or install them from your package manager.**
[Sodium]: https://download.libsodium.org/libsodium/releases/
[Opus]: http://downloads.xiph.org/releases/opus/
From ee6e0adf7cd5873c2ca886ec85556d3e8d5656fc Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Mon, 1 Aug 2022 14:23:43 +0300
Subject: [PATCH 03/24] Add RequiredInput to example modal (#2348) - Misha-133
---
docs/guides/int_framework/samples/intro/modal.cs | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/docs/guides/int_framework/samples/intro/modal.cs b/docs/guides/int_framework/samples/intro/modal.cs
index 65cc81abf..8a6ba9d8a 100644
--- a/docs/guides/int_framework/samples/intro/modal.cs
+++ b/docs/guides/int_framework/samples/intro/modal.cs
@@ -12,7 +12,9 @@ public class FoodModal : IModal
[ModalTextInput("food_name", placeholder: "Pizza", maxLength: 20)]
public string Food { get; set; }
- // Additional paremeters can be specified to further customize the input.
+ // Additional paremeters can be specified to further customize the input.
+ // Parameters can be optional
+ [RequiredInput(false)]
[InputLabel("Why??")]
[ModalTextInput("food_reason", TextInputStyle.Paragraph, "Kuz it's tasty", maxLength: 500)]
public string Reason { get; set; }
@@ -22,10 +24,15 @@ public class FoodModal : IModal
[ModalInteraction("food_menu")]
public async Task ModalResponse(FoodModal modal)
{
+ // Check if "Why??" field is populated
+ string reason = string.IsNullOrWhiteSpace(modal.Reason)
+ ? "."
+ : $" because {modal.Reason}";
+
// Build the message to send.
string message = "hey @everyone, I just learned " +
$"{Context.User.Mention}'s favorite food is " +
- $"{modal.Food} because {modal.Reason}.";
+ $"{modal.Food}{reason}";
// Specify the AllowedMentions so we don't actually ping everyone.
AllowedMentions mentions = new();
From 06ed99512256125c0d32666906feedc2a323a6da Mon Sep 17 00:00:00 2001
From: misticos <21005901+IvMisticos@users.noreply.github.com>
Date: Mon, 1 Aug 2022 13:37:41 +0200
Subject: [PATCH 04/24] docs: Add ServerStarter.Host to deployment.md (#2385)
---
docs/guides/deployment/deployment.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/docs/guides/deployment/deployment.md b/docs/guides/deployment/deployment.md
index 0491e841d..4313e85b4 100644
--- a/docs/guides/deployment/deployment.md
+++ b/docs/guides/deployment/deployment.md
@@ -47,6 +47,12 @@ enough. Here is a list of recommended VPS provider.
* Location(s):
* Europe: Lithuania
* Based in: Europe
+* [ServerStarter.Host](https://serverstarter.host/clients/store/discord-bots)
+ * Description: Bot hosting with a panel for quick deployment and
+ no Linux knowledge required.
+ * Location(s):
+ * America: United States
+ * Based in: United States
## .NET Core Deployment
@@ -100,4 +106,4 @@ Windows 10 x64 based machine:
* `dotnet publish -c Release -r win10-x64`
[.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/
-[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
\ No newline at end of file
+[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
From cf25acdbc10941003046ef097e996b82acd00e4b Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Mon, 1 Aug 2022 14:39:11 +0300
Subject: [PATCH 05/24] docs: Add IgnoreGroupNames clarification to IF docs
(#2374)
---
docs/guides/int_framework/intro.md | 5 +++++
docs/guides/int_framework/samples/intro/groupmodule.cs | 7 ++++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md
index 23be5b544..5d3253a6f 100644
--- a/docs/guides/int_framework/intro.md
+++ b/docs/guides/int_framework/intro.md
@@ -291,6 +291,11 @@ By nesting commands inside a module that is tagged with [GroupAttribute] you can
> Although creating nested module stuctures are allowed,
> you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy.
+> [!NOTE]
+> To not use the command group's name as a prefix for component or modal interaction's custom id set `ignoreGroupNames` parameter to `true` in classes with [GroupAttribute]
+>
+> However, you have to be careful to prevent overlapping ids of buttons and modals
+
[!code-csharp[Command Group Example](samples/intro/groupmodule.cs)]
## Executing Commands
diff --git a/docs/guides/int_framework/samples/intro/groupmodule.cs b/docs/guides/int_framework/samples/intro/groupmodule.cs
index f0d992aff..a07b2e4d8 100644
--- a/docs/guides/int_framework/samples/intro/groupmodule.cs
+++ b/docs/guides/int_framework/samples/intro/groupmodule.cs
@@ -16,6 +16,11 @@ public class CommandGroupModule : InteractionModuleBase
kyOd-+Eq=1;SMQV8Xy?rGHZVv0t?DCgm+LpE%+1eIMk=?=g1U(q`b?Lt zlRa$adfP!Ma`)-<$4dR(80d`-70xI%ef7%cGoR#R$`5~G6Byr3!S%Xj{%wqlo9tOf zb##oCd913otbPMt2wxACTWbqqAMRH#LG(3(_Y>gr1>~j0^C#kTB&+SO8v#T5zqFk5 z5G^x|b#Yq0bctbleVQt`5#Svb7KYz6U+%*NnNO(p>|+9kaaA?e9PRDCze`o_ty8d$J$z>G&EGKjwK z#qOvY=$zi`>r{H*o2*q@+Nl)`ec0;>2rBnPy1|!Ix+ju*b@ZN-R2+J2C-DXaFHyGj zjj7Iv$xg^awW^rp6(*brBaMx)Zyp?vl*676nuvP}HIh`hr#h8Po)GwC*VWK1xEr=Z z3w5u7EV>fD$Z BQ|IB6e^$$=_-qB(!%g4^>xA=xY;Mj8Mi>EtJQ1`S~9u3OD#XvG;# z>Yubcoh~91h*ZN90pICJKYZx1*V(VoNRIihA%cD11WHchebZId 5h44ARO0MH~F~Z4wNE9;P`fLfG`10(*sD-$&aTU z-`=QewD&7d1>Wkho+SSWKmzP+yjqjBO^cy&^@|+FU5-zU8;|GRnS%`vCSEXQ8iDF} zkHYn=Us!rwgP#{|cM6a873@iibj;op#j!7NU7gj=u*dAk#Ru3 Kcpb ztNWB3?~ky1HNS-AEvg@%e-a9ec0^~hX{}^`rwu7Q`y+iLpn&-r7yqtV%t~&BDt7~( zWad@s(@QVbxa fgVRcLdY&e2JCgRVC8q%(H`F+Tknj{j>g``y_K5D znw*g^IgnEXjaXnH6D5k9u}6Z8kvMT}{(0cZRYB=eJ{NN}Dwi)L#88ybW>b@L72Vhd z>fm4wl57+PdNyOwHFE}L-z~&P)MC^@yDxUGt*E1kJDP%O_1_n3qO~d&ytbyoLqwe4 zo9r1Y1Z&-_ )tT$ =!_i}0Lb29ez` zbbxfJ%gd 8lz z @<8|2+s=O|EP}?jyUg}le&pHE7R1%*$&*}# zas=Un`ux66s=rz7Cpe>J&+cnBO`Oqt7j&Gw!W{8V3PV!zUS3K**iQ8FFgpbXa1=ba z62^R#R7&1=F@0_NFcH_T5}Gsz1Kmb50lLTu?cJ`-w}B=yvsI->h-}imG lo2H-IA9VyJ^>c+Odm!yhp}oKU~yMIUaV&pY?EiRA^u&ONQR{t!>I2nHUQ21iSd zOU0KDJ|oP`?V2~B=W##QCI$VQobL7~z@mW}x>@ENVngt=ZouoVhTM#Oq$&OByi(tt zpB0O)cYKxYik-jHfrg<1j({|MpAm$7bnhIz*RrOKw-Q _i zEfeE0k E?8eEu}$@zf2x!;ey+IUR96z#)dv~L7G_PBb`Jl)G+ >1b@=By^6P_HBy|9mejGNF|t5W5D<++fK|3rd9O?S;e-6r_2tPsmzK@4UQ zG4f>X6OfxyK>uVgoP{kJX5hS &DE?h*?~D6RJyub1kwpX`c>6p?gV z0X2-N6zLhePQxhCn-r9(=H`*gK@^-<)$6+$nUG6wawzNbB`DpzRRFIB#V8RRtpY3E zG* J}7Jb=3TCD5SFqCa72>1XyuxY;+YScOVEi`O`1 zvEg(M%kkVBU~2;owF}@7n*gQ89G}>23BZormhbEk_Ri5j6&5Ez{Y2Ux?jW1E#ZT(m z51Ue2o3!w!b-sfhBZBLxhsP`xzb(9=o>AfYVO4r4i;wnh{Mm;%+M{D8+d#HG@%6KQ zGo+3@07CTJ(ZhxT4kzM|hL(z$)bryvb}W@kO={N?y{rs?1!O7w*|*>HJ4zSu7H1rV zO m`^f%5(jy{eW`u;rV?n*%Cb95yH|<8F zt62BD)Vyn?RiOgU_l#G+(G?Z?sv%i{Slp?RRMSm;F}?VD2D4k&Xt^#gQje3zSi#_- z7F(aJOrlY{w{)ph-FkmDN!NwhE~*2N*1yB?=03LF NSOqZ2yt-?_*cK z!h$%rncuCmsl7n$3b}l91Cl0$>#R6<%*`b(ax&JsN!SGr;lCty`s!NF8w`Svu~bce^g7Y$e~A$HKk zpZ=xUOn)+ ndWKzr_Lsq3TmpU8Vn+piKa>e?ifyA z9{7uRjf#~esD9{-`!{KvVlihl!f-1Kp6+zY4S~FrPnl@$|MZf)hf{?A&OZfD`C1?< z+GqO0|3p{Ii&wb;`ojO_p1+>v2R9yZ{}SaduKVldgV D^}kf{%n@BXCU z21%zR^prUN#*I(s-#A70ZO2;!J =Z1sCPnB_y#cQ! 7)e`$_?=Yic{HMiZD z&i)-8UYY;k(~k%ni0uM;JEcDzFVN~A0w&z*B`=}Q?abeqBfS3~eERdTw;5qLwCeVs zF5Fb{4*}C!@iqSVRo&kiYaqtvAADMy3@<&lQ<}Q`r>5qH{}3=1hNEiWl2?C6M-yJ+ zSGfE)pEkGR`q5sAT$Ch0Eij AH z^^f9&-s@NYoP365;?jSTmFqHgdVOwyjos^nTEbg6gYG?kdYv;JkOl`mgq)_B0U4B1 zvNdd7&f0-bA$b`lZ14TXc9^#YrP)sNKr+*~?3i~vZa4<>I;7I?xQ4Uc_)53jCE`vA zvhScW&`~sW?F&k#>&q@$ptNBK TZI#BQM@Y=Lcffb zrg|#pm36n<`of=Cu~Jnm(nGmypqFBQTu5&|?_db27JJNXO9) KSJuu{BPh@k_by*4khNuqI8kFo!+_dx@){v^B=J1~)MFRZVO}4jqF=lmZGzXU6 zfE98l2N*?KS?I4fc5d8L*0fNEUg?s`+&p!*vU8N9be(yPB;SUK08v@}Xz#S~OAICg zz4h(#%8I~8CLL_G!T1 GRoWk#?<5Z7gB+P3iU}KuME4J7 zvFXPodB-WRmPg{m_DcFfEh+;;Hn #F($VZ2*`erU7M1#xJT$k5R z%!oMN>2>vf_(20!epHRG)pxcxnejo~$pD+=6jy8E)s&1p`bZ5NBzVCzHM+@EA&JKn zZqO=Q;#=SGEd#2MKzIXWVJ)yimzs?KA);2f>Za%D43t+*t`XQWkYpb!t@N~XF1a%1 zDQN3*YGdoIm8d75GB67cvdW4VVZw`-rUPbZ&PYtID4HwiU!Qp3VfiC>W$&4*cJR>L z58C4ZBswPkd&Zi^t^5E6XF>JSd2BpNn>@s^G2YzMyX#7yUi_)+<-!YQn!@$P7jlx> zX(^KJPqS*o)D^-{3~hjIJnULvHNo!f?U3F$t0(zYmpV7LzrJ;3HqIPEdN}1eC(3Bn z#2)W&WsGusGY%=0yretU3 E4%Ls4Ce4ct=30(JK2(qh-&hyeFcFF*P z0fo^8Bba@<^iB2I1I+VMMt(Zi;*D(-C{=$U*Q* 9D>7;PK<5N40Z|gK4^|#Y4&HmP% C-VVR9$7Gq__x`EzZ$*t)_gAf6$u_TOeTb6u*RRWttwHD-&>fW$> zwE;n;9$Eik2#ezc-zyaeUoqXTGt>NvbtUkZpH_y}Mk8O|@_E`?fNIV58SRf`s6O(a z^|QLz`hr-CQ;Kz65@`FJc5X49(zmr^=3yDytl?0J7qUTa@y1ji$S>jRuo;%uY20E} z(7Z>hyeL8(SMIug_c|SK!~&oGDQf*T>EVtw>cw!F0}vY%(oy^^Y`e%Skrh*8*Nv;t zTq3;0mDZ=dL>s@9FOO&8VT!cO0zHVDY!q`T<)Wn>91u}DYEMBx0&u~qKO@f7DW=~< zo7oM~m{IMia6KMgy{_VHR?4py>>UqI6J2-TSzfIzUjW_fjs`<}L{?ZO=Pi1U-r$J1 zYw-btwiJ^4SPUC2TIcIht6x5itZ>6Ix4lJvyuDyS_tk@FkyVHt0MF2B#OrBSl#@_s zh3da1%L*0c%snEw4 _iowr{$XEd&04OTMZYXE z71y@^6bOs7^785e`7=M60%VjDSg63WyugzP-!5@oxo?;TFeTz?{!lKb2;Z9Q1-)Ax zFxq#_CmSd3$+J5`BHRkZx#Un4#Rk>vJ^EuR4!Bzgv$5Gma;xd8KqgG&8CZU%?R!nU zQ)KVRQ|=@_c0Ze%O6G-|?SqRigdhr%%U&-7)Yxq7Uco>J# zGt1siV03VXz-rNg_DX!j(=TD=R(*N()Xk~$`Ho`*Vc!5l%+|-5hx$z1CU>EI4&}I@ zM(Lzv*9V9tu*0kFeaBW<6g4p#p>bTH;Jco74U{XOwLEbDl82>Ik+b)Xb`p7lgm Kg|j% KG9n6*yHoQZRz7_p;xfxp{Sb z7U6J(hU2&ndW6Ox$zyQw*v-*?Fou*W1!>|SRssTA<##43@mpcYrIV8#S@1Zic|B+r zaoCHUW-vVjfQfSuK+>f4kF02yT`jg $&Ri6W%bp(BaV+p#QdA59W(ZH==M$neuEWI|g#d2x c8(e{u u&FY}g@v zvupz)AU;Mg&IVk>PtkYMf||BbAeir^O&Nhd3^W_?{;b+yPM^G<2vaQ!wn+PVU ^9HEDI(A+*_OlLX~=M0k6>;l#C!Bd zetO7k*3Xh~E7$PyK^mb0sYs2FV+rFi)z6Jo*41TI!g{*|`w}C4tRA%G?E7@i`?Pw6 zBRq>!WZLhOeLHBxq9t{s;*ps06zlR*lEHyv>-}=1Dx3!&01#xsp+L&$HlIDvm-g vTuqv9#~>XNd{$gBgF; z_%DXV1UJR?QM1B=)~A5Pg =r1nEKwigBsjxZ_CQM-fUU&$qn0=AUUEgY=R z%_i^>p#jg;JVzF~ta_a_axy MJPt zKDZ8@EH{`@zSLbswD$j!ww{($*R)XrHk6%`sl@z*ciqf{IXeQ;mCi(un)E%IVJL7m zk?>?Z)wqc`rQCwW6WtG}k-p8OL6Z{!;r$w8?pzvP yO&3H4jW`_QEu+=*Fm>p&AK8a-$w|sGr&lSc5fP6QDtS%Ja>qL`R#2< z&Rr`)XRE=hmKChblzGnOg1xK?!-|jhN1 tgNn!J(w4q z+L$o8)r~0ag*hDFOj+fTLnGSe>KJ1OHgC@e0n?xgMkiH(geXbi;Y7O^YqN>fkPptm zYz;#F7}2O2qKLZ`1UT5+sAF}i%_<+bu!Qm>$k-aM&O70kx9r{PIAf2e`gI}D{dMIO z$1sJDLxq?YQ`DQjxUJGi4Mwpng}vPe==OtWX*=h`V(t_Mm`oh`t@rmx$(;wxr@wQT zka7m^=p;+Ne=St7Y;20uM&x(*C!Xy_eevm0MbwE@2QKOx53ze8rzI78#qW?5C-+gK zrg&p~4Gfe5i3hCs%v`#~%Z_e&G-rhEmd3vU@kU3+l_WhuU8%m4nYvExaE->yV>_ey z1$BSz0cnB3FH8IVJCT5++Xuu-I`Rp#*^YBg%0y54xkk6UkyZDv>@rO?wmqVd6gCyt z`M~+ig}bR+?&BvxCa#C(d*YSHN?gqfu&hV>t9kK>!bZb3`<{tndOVGq*Q(u)-vEbJ z74HjE35W@RSl?@Wgry!+TVjNsRIns_@gy1+n(6oZVM}vM+wOqx+BN^=x8RqwNN+=s z#zfNBL*TK9)dPj6sbf!0(7_et!HzremOzwIfRU9Rj(#*}23ZUpgj$~zqC1E`FO128 zyF&txwWrd}0%jn+h2eq8WKFUxjoUgV^Ypavytk7N;xjY5c8P=@6Kj`kml-MS$wZL< zu&{6M16EwL%Bw2G7tSWy@jqWJowgwjCfq>RTN(`8Ag(#y*}Lj*J^jpcL`FcQP%wQO zl-YO8cVx5tf?#XUP9350J}k*mLE|BTu8FezJqW$~cq4vtqYVWM!Lai`wdD2`Y_;lk zc9%=_W}QAjx3z}KMMs`#z2MX?QKlEm38f+tA^WNLh^Luu&hT}Ul4~?>1&?P`KAhAb z`5S*Wu*Zrj*it+xmP9==Qcj?&?In0L$hDq7oXd6$VsO13$K=C%+iP3Nj_iFI@6V6u z5S)L+E_Sp#j>MjC`6ywNWF)O>oJNJ$RF~H}doc34Wg^VQ$o^P1y7-$3cer+~%Yh9B z0y&um?MV ^HJJFGoHLSN8DXPvTkm4e$2IN=tJ-(?L zXqdcM59PSaX9=D<*ekz6l-wJ+PDbePg-D l*OvbFfF*|WQ(j%KSEQ9d)1Mm>36GCe6|Ctqb zd?J4^y|8!}QB;cfWYe$bMFjc3wc7kF^mh?&?(l}RUhHR!IcWvJap{IDbEa=IW^ZYu z-k81irzx?sVEVxU7)@FaiFDe}!(wVONhNR`3_V>TvU=vnmXc~z#P8xVQ_Au&F7`El z*MiK_1ta~&ua@jXmNr0W(n`EYJsq5=S3%we(p+3mQ~e-&f$K7O*HX)R z{t?)2H!Bj*6N4k|U&TW?AcGiJ6x*H8q9PA2fx|#5ODp>P?1WT6?)nvx^ZSVI`uXm6 z?F{chZzkh$B`r1QC1dWgQ(DD1VI9L)s6Z@eVaws@*UpKss)j+Fd{Y#8`N=|RLIp>h zOpJ`JNjo^Eq;m5*;ydJrHvuX{ytgvOSP3>+LpqDMHoK-7dvhdjKM7bH=10FtgvGQl z`#vd7j|p}JI(4%prZduPUkZ}5;D$0y!cU6?yQSA?OOIP?rfV{Y8S!R!*)I7#ob$S> zWa*{_Q> p=HjsR;)Pd+*g5~BW{I`$0}oR*YYOtm1JIj>N5aG~;gj198i$5Q zp3@4dzIT*cbm`VS$5(1CLrGP7TugA5$X|lSRWt+-FkTSKav?Hjcze(IVwGb*Bf0Br zwO_S;wsxGrd3t>HOx7C!ohhL zZR;T{%xCtAnBmcUx6MjoeL7m*x4QX$`noroDB(Ro-~@-Fn1i(JbcVFhvfd>EIr^wV ztHRo$+Ml^Y1kqGkklgJ MXzUg1tiJQBicm`Zc&8B6w2J z?9fUw(kErVd;QYVN?X~^_r;HkO5LU?By2nY`IDi7Gd9F&LrfMetW;jJVl=Ia+4Vo7 z -G1dLN5N3pVdmPCDmI z+QN+ruI(JhJR4-LaEq)@OHmLm7hcti#%ib9f+HLzGvZ3$ICYFo>{mN>daZtM4Kfh4 zHZM?akzyxX2aNpu#+>m|ey-Tyu<843B+8les&;Kt3J)HV-|3N1fB(*+Y%0?``?GY0 zNoP5B9d 1Rhw}@ 2G~#z!y7|Pk`VUDMpK}%m>_xt zc3_d(Xh87cMphU^Xqmps+wo-!T}>BOeJ7Vs4>XB{(O#X;_Dk@D5v>Axf`s>Z%SA{= zfCtcTHj}1_V4a0LLykRH9n4LfX>}1;KVT1cbBedjY(%Jn^*DlS3Z~J%)^S9XEBrP- zi^@~*>Tzn7C^8n1R0?qLPbz2FidxVR$q<47jVpRvSg7(v%U*E(NHqfunSh-AI3BkK z9(SjoC&fEnq4j&UzjPNH$t=RI!|g$t#PMN0_Y0-2*oCfh^UR8$^r#!e(|^G$%8*xy z<<7*^pqs6Y%2M2O)@--0(Pfo!4%d5Uf?GH`;t5luzV;UzH)I0oN6L4{H@vMhD+ZTh zkK;t(pPW`@Wb&<> YiN7Plf`jr0Qdm~utdrqRwL-TSvE z@kdUtdPe7`3HjcI$JlJwY(0)O%e&TMOp+!mTBG>v`YtZ-qv^|}hxVjOvfj41>-4u) z0kQn&lM2F19~UN*9a+qP#m0NL1kszA%Dknp{fMoqm9G6gB20cV Y+Ad zRPZTN*mb=_v{MO^7e+fD?s`vCGRTRVa8CsGIN1cpu-)obn|RJ09Gp`9x<*|Z_Yf|$ z61v~*9$jQ^xhDO`SS;`w*-G+ByJ+{-c}d^1AK!ehie6+*YuJep(rEP(zwfBKm6L&L zb-epuKzWMD4e{vrE TA5mZu^B~qx{ zX^mii?9~WWH?~bp#918ldI?B=TWB<3KtC%^^%$K%@4rb60bGo-`W2)bGvLgVtSa4e z9SHJhAF0bG^H-LYd5`hUfLe{$cZ-5retp)cH$fxk-*Q~=ylu;Kp%criD{n(B4L1n_ z_D?#hNmYqpqc$cf9Cxm-1dx~}h5I?$Lie&FH6Dbj)ED< g*qBvC1w}bcHjx AjUkV(L#qoVpCPMe(Xi^J2^`;^6Cv^VGP@!$FG|nP z7~FPX3aMPTHMCH&_?Ed}tHE*nv*v({BdD=K!Cq?OmGwni^r-lKVV+P#2~5K=y>}~p zV*#cSHr1Iy=Q!ky =no}O$~HzxREH%l^)81S e$I;z8C3t1Cf6?P&F WL zSKZu>YFi&qKG@n>C^$V>?q*K)b%EcnZSAeKaLCS7HkzBfL5ah5d`mc>(x%qX`Hvpf z#Vd4JVNA+>hea {lZKg=Kmk)wiG7{E1i)DK~CyvcoOxIvllUBV4!py3;0J9TFe*?njPI zi1PIMS=P97{pk59fo}<+BPS;;g$)&Jm!aLLAD?Tv1C^Wa%aD&$wdFHrdc$gw^FX!O zn?ic%4UfJ?@AdgRlb7*}?>qU|n>IsPX3pqIb+z;TYomoHL<~>+U1AU q(f{J4I9%7H}Jx!tR>eZ62?1ic-%J@0*4 z^1=xJ=T<68@!z<9(iy|e)Pe{0Q1<966qZ9jkGxGXWrY~Wu5))i)^7f@gW&W=ZkOfW z25kcI&IaS@O(Z<^CA>BB@cUoin^sxwsXVR|DHo2}JJ&pM-@!==&t3d*PX7EeLebin z);NQ|x)F*K&k)HvKHee$#LS2I*~vlY9SIapHmCP~D%?L<)j&Ws)xoxvrabJ=-iWt> zmgby@S~D_JI3eg#UFtyNbQ(f&nl|)UDp~iF<)-)I6mzVoxVFdF-p9|@Bjo?quK2WF z^ek5(I_|HAf?qFxHevj6 K&*>-P^A)))SF@xNgG f|A~3i3H23IlHY=+>-W?CdkQrbP300r^Pv9&$NfzP literal 0 HcmV?d00001 diff --git a/docs/guides/dependency_injection/injection.md b/docs/guides/dependency_injection/injection.md new file mode 100644 index 000000000..c7d40c479 --- /dev/null +++ b/docs/guides/dependency_injection/injection.md @@ -0,0 +1,44 @@ +--- +uid: Guides.DI.Injection +title: Injection +--- + +# Injecting instances within the provider + +You can inject registered services into any class that is registered to the `IServiceProvider`. +This can be done through property or constructor. + +> [!NOTE] +> As mentioned above, the dependency *and* the target class have to be registered in order for the serviceprovider to resolve it. + +## Injecting through a constructor + +Services can be injected from the constructor of the class. +This is the preferred approach, because it automatically locks the readonly field in place with the provided service and isn't accessible outside of the class. + +[!code-csharp[Property Injection(samples/property-injecting.cs)]] + +## Injecting through properties + +Injecting through properties is also allowed as follows. + +[!code-csharp[Property Injection](samples/property-injecting.cs)] + +> [!WARNING] +> Dependency Injection will not resolve missing services in property injection, and it will not pick a constructor instead. +> If a publically accessible property is attempted to be injected and its service is missing, the application will throw an error. + +## Using the provider itself + +You can also access the provider reference itself from injecting it into a class. There are multiple use cases for this: + +- Allowing libraries (Like Discord.Net) to access your provider internally. +- Injecting optional dependencies. +- Calling methods on the provider itself if necessary, this is often done for creating scopes. + +[!code-csharp[Provider Injection](samples/provider.cs)] + +> [!NOTE] +> It is important to keep in mind that the provider will pick the 'biggest' available constructor. +> If you choose to introduce multiple constructors, +> keep in mind that services missing from one constructor may have the provider pick another one that *is* available instead of throwing an exception. diff --git a/docs/guides/dependency_injection/samples/access-activator.cs b/docs/guides/dependency_injection/samples/access-activator.cs new file mode 100644 index 000000000..29e71e894 --- /dev/null +++ b/docs/guides/dependency_injection/samples/access-activator.cs @@ -0,0 +1,9 @@ +async Task RunAsync() +{ + //... + + await _serviceProvider.GetRequiredService () + .ActivateAsync(); + + //... +} diff --git a/docs/guides/dependency_injection/samples/collection.cs b/docs/guides/dependency_injection/samples/collection.cs new file mode 100644 index 000000000..4d0457dc9 --- /dev/null +++ b/docs/guides/dependency_injection/samples/collection.cs @@ -0,0 +1,13 @@ +static IServiceProvider CreateServices() +{ + var config = new DiscordSocketConfig() + { + //... + }; + + var collection = new ServiceCollection() + .AddSingleton(config) + .AddSingleton (); + + return collection.BuildServiceProvider(); +} diff --git a/docs/guides/dependency_injection/samples/ctor-injecting.cs b/docs/guides/dependency_injection/samples/ctor-injecting.cs new file mode 100644 index 000000000..c412bd29c --- /dev/null +++ b/docs/guides/dependency_injection/samples/ctor-injecting.cs @@ -0,0 +1,14 @@ +public class ClientHandler +{ + private readonly DiscordSocketClient _client; + + public ClientHandler(DiscordSocketClient client) + { + _client = client; + } + + public async Task ConfigureAsync() + { + //... + } +} diff --git a/docs/guides/dependency_injection/samples/enumeration.cs b/docs/guides/dependency_injection/samples/enumeration.cs new file mode 100644 index 000000000..cc8c617f3 --- /dev/null +++ b/docs/guides/dependency_injection/samples/enumeration.cs @@ -0,0 +1,18 @@ +public class ServiceActivator +{ + // This contains *all* registered services of serviceType IService + private readonly IEnumerable _services; + + public ServiceActivator(IEnumerable services) + { + _services = services; + } + + public async Task ActivateAsync() + { + foreach(var service in _services) + { + await service.StartAsync(); + } + } +} diff --git a/docs/guides/dependency_injection/samples/implicit-registration.cs b/docs/guides/dependency_injection/samples/implicit-registration.cs new file mode 100644 index 000000000..52f84228b --- /dev/null +++ b/docs/guides/dependency_injection/samples/implicit-registration.cs @@ -0,0 +1,12 @@ +public static ServiceCollection RegisterImplicitServices(this ServiceCollection collection, Type interfaceType, Type activatorType) +{ + // Get all types in the executing assembly. There are many ways to do this, but this is fastest. + foreach (var type in typeof(Program).Assembly.GetTypes()) + { + if (interfaceType.IsAssignableFrom(type) && !type.IsAbstract) + collection.AddSingleton(interfaceType, type); + } + + // Register the activator so you can activate the instances. + collection.AddSingleton(activatorType); +} diff --git a/docs/guides/dependency_injection/samples/modules.cs b/docs/guides/dependency_injection/samples/modules.cs new file mode 100644 index 000000000..2fadc13d4 --- /dev/null +++ b/docs/guides/dependency_injection/samples/modules.cs @@ -0,0 +1,16 @@ +public class MyModule : InteractionModuleBase +{ + private readonly MyService _service; + + public MyModule(MyService service) + { + _service = service; + } + + [SlashCommand("things", "Shows things")] + public async Task ThingsAsync() + { + var str = string.Join("\n", _service.Things) + await RespondAsync(str); + } +} diff --git a/docs/guides/dependency_injection/samples/program.cs b/docs/guides/dependency_injection/samples/program.cs new file mode 100644 index 000000000..6d985319a --- /dev/null +++ b/docs/guides/dependency_injection/samples/program.cs @@ -0,0 +1,24 @@ +public class Program +{ + private readonly IServiceProvider _serviceProvider; + + public Program() + { + _serviceProvider = CreateProvider(); + } + + static void Main(string[] args) + => new Program().RunAsync(args).GetAwaiter().GetResult(); + + static IServiceProvider CreateProvider() + { + var collection = new ServiceCollection(); + //... + return collection.BuildServiceProvider(); + } + + async Task RunAsync(string[] args) + { + //... + } +} diff --git a/docs/guides/dependency_injection/samples/property-injecting.cs b/docs/guides/dependency_injection/samples/property-injecting.cs new file mode 100644 index 000000000..c0c50e150 --- /dev/null +++ b/docs/guides/dependency_injection/samples/property-injecting.cs @@ -0,0 +1,9 @@ +public class ClientHandler +{ + public DiscordSocketClient Client { get; set; } + + public async Task ConfigureAsync() + { + //... + } +} diff --git a/docs/guides/dependency_injection/samples/provider.cs b/docs/guides/dependency_injection/samples/provider.cs new file mode 100644 index 000000000..26b600b9d --- /dev/null +++ b/docs/guides/dependency_injection/samples/provider.cs @@ -0,0 +1,26 @@ +public class UtilizingProvider +{ + private readonly IServiceProvider _provider; + private readonly AnyService _service; + + // This service is allowed to be null because it is only populated if the service is actually available in the provider. + private readonly AnyOtherService? _otherService; + + // This constructor injects only the service provider, + // and uses it to populate the other dependencies. + public UtilizingProvider(IServiceProvider provider) + { + _provider = provider; + _service = provider.GetRequiredService (); + _otherService = provider.GetService (); + } + + // This constructor injects the service provider, and AnyService, + // making sure that AnyService is not null without having to call GetRequiredService + public UtilizingProvider(IServiceProvider provider, AnyService service) + { + _provider = provider; + _service = service; + _otherService = provider.GetService (); + } +} diff --git a/docs/guides/dependency_injection/samples/runasync.cs b/docs/guides/dependency_injection/samples/runasync.cs new file mode 100644 index 000000000..d24efc83e --- /dev/null +++ b/docs/guides/dependency_injection/samples/runasync.cs @@ -0,0 +1,17 @@ +async Task RunAsync(string[] args) +{ + // Request the instance from the client. + // Because we're requesting it here first, its targetted constructor will be called and we will receive an active instance. + var client = _services.GetRequiredService (); + + client.Log += async (msg) => + { + await Task.CompletedTask; + Console.WriteLine(msg); + } + + await client.LoginAsync(TokenType.Bot, ""); + await client.StartAsync(); + + await Task.Delay(Timeout.Infinite); +} diff --git a/docs/guides/dependency_injection/samples/scoped.cs b/docs/guides/dependency_injection/samples/scoped.cs new file mode 100644 index 000000000..9942f8d8e --- /dev/null +++ b/docs/guides/dependency_injection/samples/scoped.cs @@ -0,0 +1,6 @@ + +// With serviceType: +collection.AddScoped (); + +// Without serviceType: +collection.AddScoped (); diff --git a/docs/guides/dependency_injection/samples/service-registration.cs b/docs/guides/dependency_injection/samples/service-registration.cs new file mode 100644 index 000000000..f6e4d22dd --- /dev/null +++ b/docs/guides/dependency_injection/samples/service-registration.cs @@ -0,0 +1,21 @@ +static IServiceProvider CreateServices() +{ + var config = new DiscordSocketConfig() + { + //... + }; + + // X represents either Interaction or Command, as it functions the exact same for both types. + var servConfig = new XServiceConfig() + { + //... + } + + var collection = new ServiceCollection() + .AddSingleton(config) + .AddSingleton () + .AddSingleton(servConfig) + .AddSingleton (); + + return collection.BuildServiceProvider(); +} diff --git a/docs/guides/dependency_injection/samples/services.cs b/docs/guides/dependency_injection/samples/services.cs new file mode 100644 index 000000000..2e5235b69 --- /dev/null +++ b/docs/guides/dependency_injection/samples/services.cs @@ -0,0 +1,9 @@ +public class MyService +{ + public List Things { get; } + + public MyService() + { + Things = new(); + } +} diff --git a/docs/guides/dependency_injection/samples/singleton.cs b/docs/guides/dependency_injection/samples/singleton.cs new file mode 100644 index 000000000..f395d743e --- /dev/null +++ b/docs/guides/dependency_injection/samples/singleton.cs @@ -0,0 +1,6 @@ + +// With serviceType: +collection.AddSingleton (); + +// Without serviceType: +collection.AddSingleton (); diff --git a/docs/guides/dependency_injection/samples/transient.cs b/docs/guides/dependency_injection/samples/transient.cs new file mode 100644 index 000000000..ae1e1a5d8 --- /dev/null +++ b/docs/guides/dependency_injection/samples/transient.cs @@ -0,0 +1,6 @@ + +// With serviceType: +collection.AddTransient (); + +// Without serviceType: +collection.AddTransient (); diff --git a/docs/guides/dependency_injection/scaling.md b/docs/guides/dependency_injection/scaling.md new file mode 100644 index 000000000..356fb7c72 --- /dev/null +++ b/docs/guides/dependency_injection/scaling.md @@ -0,0 +1,39 @@ +--- +uid: Guides.DI.Scaling +title: Scaling your DI +--- + +# Scaling your DI + +Dependency injection has a lot of use cases, and is very suitable for scaled applications. +There are a few ways to make registering & using services easier in large amounts. + +## Using a range of services. + +If you have a lot of services that all have the same use such as handling an event or serving a module, +you can register and inject them all at once by some requirements: + +- All classes need to inherit a single interface or abstract type. +- While not required, it is preferred if the interface and types share a method to call on request. +- You need to register a class that all the types can be injected into. + +### Registering implicitly + +Registering all the types is done through getting all types in the assembly and checking if they inherit the target interface. + +[!code-csharp[Registering](samples/implicit-registration.cs)] + +> [!NOTE] +> As seen above, the interfaceType and activatorType are undefined. For our usecase below, these are `IService` and `ServiceActivator` in order. + +### Using implicit dependencies + +In order to use the implicit dependencies, you have to get access to the activator you registered earlier. + +[!code-csharp[Accessing the activator](samples/access-activator.cs)] + +When the activator is accessed and the `ActivateAsync()` method is called, the following code will be executed: + +[!code-csharp[Executing the activator](samples/enumeration.cs)] + +As a result of this, all the services that were registered with `IService` as its implementation type will execute their starting code, and start up. diff --git a/docs/guides/dependency_injection/services.md b/docs/guides/dependency_injection/services.md new file mode 100644 index 000000000..e021a88be --- /dev/null +++ b/docs/guides/dependency_injection/services.md @@ -0,0 +1,48 @@ +--- +uid: Guides.DI.Services +title: Using DI in Interaction & Command Frameworks +--- + +# DI in the Interaction- & Command Service + +For both the Interaction- and Command Service modules, DI is quite straight-forward to use. + +You can inject any service into modules without the modules having to be registered to the provider. +Discord.Net resolves your dependencies internally. + +> [!WARNING] +> The way DI is used in the Interaction- & Command Service are nearly identical, except for one detail: +> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies) + +## Registering the Service + +Thanks to earlier described behavior of allowing already registered members as parameters of the available ctors, +The socket client & configuration will automatically be acknowledged and the XService(client, config) overload will be used. + +[!code-csharp[Service Registration](samples/service-registration.cs)] + +## Usage in modules + +In the constructor of your module, any parameters will be filled in by +the @System.IServiceProvider that you've passed. + +Any publicly settable properties will also be filled in the same +manner. + +[!code-csharp[Module Injection](samples/modules.cs)] + +If you accept `Command/InteractionService` or `IServiceProvider` as a parameter in your constructor or as an injectable property, +these entries will be filled by the `Command/InteractionService` that the module is loaded from and the `IServiceProvider` that is passed into it respectively. + +> [!NOTE] +> Annotating a property with a [DontInjectAttribute] attribute will +> prevent the property from being injected. + +## Services + +Because modules are transient of nature and will reinstantiate on every request, +it is suggested to create a singleton service behind it to hold values across multiple command executions. + +[!code-csharp[Services](samples/services.cs)] + + diff --git a/docs/guides/dependency_injection/types.md b/docs/guides/dependency_injection/types.md new file mode 100644 index 000000000..e539d0695 --- /dev/null +++ b/docs/guides/dependency_injection/types.md @@ -0,0 +1,52 @@ +--- +uid: Guides.DI.Dependencies +title: Types of Dependencies +--- + +# Dependency Types + +There are 3 types of dependencies to learn to use. Several different usecases apply for each. + +> [!WARNING] +> When registering types with a serviceType & implementationType, +> only the serviceType will be available for injection, and the implementationType will be used for the underlying instance. + +## Singleton + +A singleton service creates a single instance when first requested, and maintains that instance across the lifetime of the application. +Any values that are changed within a singleton will be changed across all instances that depend on it, as they all have the same reference to it. + +### Registration: + +[!code-csharp[Singleton Example](samples/singleton.cs)] + +> [!NOTE] +> Types like the Discord client and Interaction/Command services are intended to be singleton, +> as they should last across the entire app and share their state with all references to the object. + +## Scoped + +A scoped service creates a new instance every time a new service is requested, but is kept across the 'scope'. +As long as the service is in view for the created scope, the same instance is used for all references to the type. +This means that you can reuse the same instance during execution, and keep the services' state for as long as the request is active. + +### Registration: + +[!code-csharp[Scoped Example](samples/scoped.cs)] + +> [!NOTE] +> Without using HTTP or libraries like EFCORE, scopes are often unused in Discord bots. +> They are most commonly used for handling HTTP and database requests. + +## Transient + +A transient service is created every time it is requested, and does not share its state between references within the target service. +It is intended for lightweight types that require little state, to be disposed quickly after execution. + +### Registration: + +[!code-csharp[Transient Example](samples/transient.cs)] + +> [!NOTE] +> Discord.Net modules behave exactly as transient types, and are intended to only last as long as the command execution takes. +> This is why it is suggested for apps to use singleton services to keep track of cross-execution data. diff --git a/docs/guides/int_framework/dependency-injection.md b/docs/guides/int_framework/dependency-injection.md deleted file mode 100644 index 31d001f4b..000000000 --- a/docs/guides/int_framework/dependency-injection.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -uid: Guides.IntFw.DI -title: Dependency Injection ---- - -# Dependency Injection - -Dependency injection in the Interaction Service is mostly based on that of the Text-based command service, -for which further information is found [here](xref:Guides.TextCommands.DI). - -> [!NOTE] -> The 2 are nearly identical, except for one detail: -> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies) diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md index b51aa8088..5cf38bff1 100644 --- a/docs/guides/int_framework/intro.md +++ b/docs/guides/int_framework/intro.md @@ -374,8 +374,7 @@ delegate can be used to create HTTP responses from a deserialized json object st - Use the interaction endpoints of the module base instead of the interaction object (ie. `RespondAsync()`, `FollowupAsync()`...). [AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion -[DependencyInjection]: xref:Guides.TextCommands.DI -[Post Execution Docuemntation]: xref:Guides.IntFw.PostExecution +[DependencyInjection]: xref:Guides.DI.Intro [GroupAttribute]: xref:Discord.Interactions.GroupAttribute [InteractionService]: xref:Discord.Interactions.InteractionService diff --git a/docs/guides/text_commands/dependency-injection.md b/docs/guides/text_commands/dependency-injection.md deleted file mode 100644 index 3253643ef..000000000 --- a/docs/guides/text_commands/dependency-injection.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -uid: Guides.TextCommands.DI -title: Dependency Injection ---- - -# Dependency Injection - -The Text Command Service is bundled with a very barebone Dependency -Injection service for your convenience. It is recommended that you use -DI when writing your modules. - -> [!WARNING] -> If you were brought here from the Interaction Service guides, -> make sure to replace all namespaces that imply `Discord.Commands` with `Discord.Interactions` - -## Setup - -1. Create a @Microsoft.Extensions.DependencyInjection.ServiceCollection. -2. Add the dependencies to the service collection that you wish - to use in the modules. -3. Build the service collection into a service provider. -4. Pass the service collection into @Discord.Commands.CommandService.AddModulesAsync* / @Discord.Commands.CommandService.AddModuleAsync* , @Discord.Commands.CommandService.ExecuteAsync* . - -### Example - Setting up Injection - -[!code-csharp[IServiceProvider Setup](samples/dependency-injection/dependency_map_setup.cs)] - -## Usage in Modules - -In the constructor of your module, any parameters will be filled in by -the @System.IServiceProvider that you've passed. - -Any publicly settable properties will also be filled in the same -manner. - -> [!NOTE] -> Annotating a property with a [DontInjectAttribute] attribute will -> prevent the property from being injected. - -> [!NOTE] -> If you accept `CommandService` or `IServiceProvider` as a parameter -> in your constructor or as an injectable property, these entries will -> be filled by the `CommandService` that the module is loaded from and -> the `IServiceProvider` that is passed into it respectively. - -### Example - Injection in Modules - -[!code-csharp[Injection Modules](samples/dependency-injection/dependency_module.cs)] -[!code-csharp[Disallow Dependency Injection](samples/dependency-injection/dependency_module_noinject.cs)] - -[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute diff --git a/docs/guides/text_commands/intro.md b/docs/guides/text_commands/intro.md index 6632c127a..1113b0821 100644 --- a/docs/guides/text_commands/intro.md +++ b/docs/guides/text_commands/intro.md @@ -187,7 +187,7 @@ service provider. ### Module Constructors -Modules are constructed using [Dependency Injection](xref:Guides.TextCommands.DI). Any parameters +Modules are constructed using [Dependency Injection](xref:Guides.DI.Intro). Any parameters that are placed in the Module's constructor must be injected into an @System.IServiceProvider first. diff --git a/docs/guides/text_commands/samples/dependency-injection/dependency_map_setup.cs b/docs/guides/text_commands/samples/dependency-injection/dependency_map_setup.cs deleted file mode 100644 index 16ca479db..000000000 --- a/docs/guides/text_commands/samples/dependency-injection/dependency_map_setup.cs +++ /dev/null @@ -1,65 +0,0 @@ -public class Initialize -{ - private readonly CommandService _commands; - private readonly DiscordSocketClient _client; - - // Ask if there are existing CommandService and DiscordSocketClient - // instance. If there are, we retrieve them and add them to the - // DI container; if not, we create our own. - public Initialize(CommandService commands = null, DiscordSocketClient client = null) - { - _commands = commands ?? new CommandService(); - _client = client ?? new DiscordSocketClient(); - } - - public IServiceProvider BuildServiceProvider() => new ServiceCollection() - .AddSingleton(_client) - .AddSingleton(_commands) - // You can pass in an instance of the desired type - .AddSingleton(new NotificationService()) - // ...or by using the generic method. - // - // The benefit of using the generic method is that - // ASP.NET DI will attempt to inject the required - // dependencies that are specified under the constructor - // for us. - .AddSingleton () - .AddSingleton () - .BuildServiceProvider(); -} -public class CommandHandler -{ - private readonly DiscordSocketClient _client; - private readonly CommandService _commands; - private readonly IServiceProvider _services; - - public CommandHandler(IServiceProvider services, CommandService commands, DiscordSocketClient client) - { - _commands = commands; - _services = services; - _client = client; - } - - public async Task InitializeAsync() - { - // Pass the service provider to the second parameter of - // AddModulesAsync to inject dependencies to all modules - // that may require them. - await _commands.AddModulesAsync( - assembly: Assembly.GetEntryAssembly(), - services: _services); - _client.MessageReceived += HandleCommandAsync; - } - - public async Task HandleCommandAsync(SocketMessage msg) - { - // ... - // Pass the service provider to the ExecuteAsync method for - // precondition checks. - await _commands.ExecuteAsync( - context: context, - argPos: argPos, - services: _services); - // ... - } -} diff --git a/docs/guides/text_commands/samples/dependency-injection/dependency_module.cs b/docs/guides/text_commands/samples/dependency-injection/dependency_module.cs deleted file mode 100644 index 3e42074ca..000000000 --- a/docs/guides/text_commands/samples/dependency-injection/dependency_module.cs +++ /dev/null @@ -1,37 +0,0 @@ -// After setting up dependency injection, modules will need to request -// the dependencies to let the library know to pass -// them along during execution. - -// Dependency can be injected in two ways with Discord.Net. -// You may inject any required dependencies via... -// the module constructor -// -or- -// public settable properties - -// Injection via constructor -public class DatabaseModule : ModuleBase -{ - private readonly DatabaseService _database; - public DatabaseModule(DatabaseService database) - { - _database = database; - } - - [Command("read")] - public async Task ReadFromDbAsync() - { - await ReplyAsync(_database.GetData()); - } -} - -// Injection via public settable properties -public class DatabaseModule : ModuleBase -{ - public DatabaseService DbService { get; set; } - - [Command("read")] - public async Task ReadFromDbAsync() - { - await ReplyAsync(DbService.GetData()); - } -} diff --git a/docs/guides/text_commands/samples/dependency-injection/dependency_module_noinject.cs b/docs/guides/text_commands/samples/dependency-injection/dependency_module_noinject.cs deleted file mode 100644 index 48cd52308..000000000 --- a/docs/guides/text_commands/samples/dependency-injection/dependency_module_noinject.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Sometimes injecting dependencies automatically with the provided -// methods in the prior example may not be desired. - -// You may explicitly tell Discord.Net to **not** inject the properties -// by either... -// restricting the access modifier -// -or- -// applying DontInjectAttribute to the property - -// Restricting the access modifier of the property -public class ImageModule : ModuleBase -{ - public ImageService ImageService { get; } - public ImageModule() - { - ImageService = new ImageService(); - } -} - -// Applying DontInjectAttribute -public class ImageModule : ModuleBase -{ - [DontInject] - public ImageService ImageService { get; set; } - public ImageModule() - { - ImageService = new ImageService(); - } -} From f17866085e308bfdb340fef607fcb406d3e10ada Mon Sep 17 00:00:00 2001 From: Pusheon <59923820+Pusheon@users.noreply.github.com> Date: Tue, 2 Aug 2022 05:24:37 -0400 Subject: [PATCH 15/24] fix: Add DeleteMessagesAsync to IVoiceChannel (#2367) Also adds remaining rate-limit information to client log. --- .../Entities/Channels/IVoiceChannel.cs | 39 +++++++++++++++++++ src/Discord.Net.Rest/BaseDiscordClient.cs | 4 +- .../MockedEntities/MockedVoiceChannel.cs | 3 ++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index d921a2474..d75a4e29c 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Discord @@ -25,6 +26,44 @@ namespace Discord /// int? UserLimit { get; } + /// + /// Bulk-deletes multiple messages. + /// + ///+ /// + ///The following example gets 250 messages from the channel and deletes them. + ///+ /// var messages = await voiceChannel.GetMessagesAsync(250).FlattenAsync(); + /// await voiceChannel.DeleteMessagesAsync(messages); + ///
+ ///+ /// This method attempts to remove the messages specified in bulk. + /// + /// The messages to be bulk-deleted. + /// The options to be used when sending the request. + ///+ /// Due to the limitation set by Discord, this method can only remove messages that are posted within 14 days! + /// + ///+ /// A task that represents the asynchronous bulk-removal operation. + /// + Task DeleteMessagesAsync(IEnumerablemessages, RequestOptions options = null); + /// + /// Bulk-deletes multiple messages. + /// + ///+ /// This method attempts to remove the messages specified in bulk. + /// + /// The snowflake identifier of the messages to be bulk-deleted. + /// The options to be used when sending the request. + ///+ /// Due to the limitation set by Discord, this method can only remove messages that are posted within 14 days! + /// + ///+ /// A task that represents the asynchronous bulk-removal operation. + /// + Task DeleteMessagesAsync(IEnumerablemessageIds, RequestOptions options = null); + /// /// Modifies this voice channel. /// diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 75f477c7c..af43e9f4e 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -57,7 +57,7 @@ namespace Discord.Rest if (info == null) await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); else - await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); + await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} Remaining: {info.Value.RetryAfter}s {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); }; ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); } @@ -257,6 +257,6 @@ namespace Discord.Rest ///Task IDiscordClient.StopAsync() => Task.Delay(0); - #endregion + #endregion } } diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs index fdbdeda5e..2ffc75a24 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs @@ -12,6 +12,9 @@ namespace Discord public int Bitrate => throw new NotImplementedException(); public int? UserLimit => throw new NotImplementedException(); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => throw new NotImplementedException(); + + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => throw new NotImplementedException(); public ulong? CategoryId => throw new NotImplementedException(); From ba024164216e8abdec31600e4103e7f3ff89f714 Mon Sep 17 00:00:00 2001 From: Alex Thomson Date: Tue, 2 Aug 2022 21:26:34 +1200 Subject: [PATCH 16/24] fix: DisconnectAsync not disconnecting users (#2346) --- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 2 +- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 974ea69ad..3e0ad1840 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -1404,7 +1404,7 @@ namespace Discord.Rest /// /// The user to disconnect. /// A task that represents the asynchronous operation for disconnecting a user. - async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional()); + async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = null); /// async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index cf01857e3..78fb33206 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1407,7 +1407,7 @@ namespace Discord.WebSocket /// /// The user to disconnect. /// A task that represents the asynchronous operation for disconnecting a user. - async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional()); + async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = null); #endregion #region Stickers From b0b8167efb1b0153913f1251228897c89b178a1f Mon Sep 17 00:00:00 2001 From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> Date: Wed, 3 Aug 2022 10:54:30 +0200 Subject: [PATCH 17/24] fix: Remove group check from RequireContextAttribute (#2409) --- .../Attributes/Preconditions/RequireContextAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs index 9d1cee8d9..057055ffc 100644 --- a/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs @@ -58,7 +58,7 @@ namespace Discord.Interactions if ((Contexts & ContextType.Guild) != 0) isValid = !context.Interaction.IsDMInteraction; - if ((Contexts & ContextType.DM) != 0 && (Contexts & ContextType.Group) != 0) + if ((Contexts & ContextType.DM) != 0) isValid = context.Interaction.IsDMInteraction; if (isValid) From c49d4830af625e57ca3f764f4c962cfad25871b0 Mon Sep 17 00:00:00 2001 From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> Date: Wed, 3 Aug 2022 11:11:26 +0200 Subject: [PATCH 18/24] docs: Fix missing entries in TOC (#2415) --- docs/guides/toc.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml index 45f3983af..c892eb8c4 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -26,6 +26,18 @@ topicUid: Guides.Entities.Casting - name: Glossary & Flowcharts topicUid: Guides.Entities.Glossary +- name: Dependency Injection + items: + - name: Introduction + topicUid: Guides.DI.Intro + - name: Injection + topicUid: Guides.DI.Injection + - name: Command- & Interaction Services + topicUid: Guides.DI.Services + - name: Service Types + topicUid: Guides.DI.Dependencies + - name: Scaling your Application + topicUid: Guides.DI.Scaling - name: Working with Text-based Commands items: - name: Introduction @@ -36,8 +48,6 @@ topicUid: Guides.TextCommands.NamedArguments - name: Preconditions topicUid: Guides.TextCommands.Preconditions - - name: Dependency Injection - topicUid: Guides.TextCommands.DI - name: Post-execution Handling topicUid: Guides.TextCommands.PostExecution - name: Working with the Interaction Framework @@ -50,8 +60,6 @@ topicUid: Guides.IntFw.TypeConverters - name: Preconditions topicUid: Guides.IntFw.Preconditions - - name: Dependency Injection - topicUid: Guides.IntFw.DI - name: Post-execution Handling topicUid: Guides.IntFw.PostExecution - name: Permissions From 1eb42c6128c295c5dcea076d149a0f9ff13797ec Mon Sep 17 00:00:00 2001 From: d4n Date: Wed, 3 Aug 2022 04:17:43 -0500 Subject: [PATCH 19/24] fix: Issues related to absence of bot scope (#2352) --- .../DiscordSocketClient.cs | 14 ++----- .../Channels/SocketCategoryChannel.cs | 2 +- .../Entities/Channels/SocketForumChannel.cs | 2 +- .../Entities/Channels/SocketNewsChannel.cs | 2 +- .../Entities/Channels/SocketStageChannel.cs | 2 +- .../Entities/Channels/SocketTextChannel.cs | 4 +- .../Entities/Channels/SocketVoiceChannel.cs | 6 +-- .../MessageCommands/SocketMessageCommand.cs | 4 +- .../UserCommands/SocketUserCommand.cs | 4 +- .../SocketMessageComponent.cs | 4 +- .../SlashCommands/SocketSlashCommand.cs | 4 +- .../SocketApplicationCommand.cs | 2 +- .../SocketBaseCommand/SocketCommandBase.cs | 4 +- .../SocketBaseCommand/SocketResolvableData.cs | 38 ++++++++++++++----- .../Entities/Messages/SocketUserMessage.cs | 2 +- .../Entities/Roles/SocketRole.cs | 2 +- 16 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 3c5621304..f0b50aa8f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -2318,7 +2318,7 @@ namespace Discord.WebSocket case "INTERACTION_CREATE": { await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); - + var data = (payload as JToken).ToObject (_serializer); var guild = data.GuildId.IsSpecified ? GetGuild(data.GuildId.Value) : null; @@ -2326,7 +2326,6 @@ namespace Discord.WebSocket if (guild != null && !guild.IsSynced) { await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; } SocketUser user = data.User.IsSpecified @@ -2346,15 +2345,8 @@ namespace Discord.WebSocket { channel = CreateDMChannel(data.ChannelId.Value, user, State); } - else - { - if (guild != null) // The guild id is set, but the guild cannot be found as the bot scope is not set. - { - await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); - return; - } - // The channel isnt required when responding to an interaction, so we can leave the channel null. - } + + // The channel isnt required when responding to an interaction, so we can leave the channel null. } } else if (data.User.IsSpecified) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index 43f23de1a..42f0c76d4 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -39,7 +39,7 @@ namespace Discord.WebSocket } internal new static SocketCategoryChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketCategoryChannel(guild.Discord, model.Id, guild); + var entity = new SocketCategoryChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs index bc6e28442..ea58ecdb5 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs @@ -34,7 +34,7 @@ namespace Discord.WebSocket internal new static SocketForumChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketForumChannel(guild.Discord, model.Id, guild); + var entity = new SocketForumChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs index eed8f9374..81e152530 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs @@ -23,7 +23,7 @@ namespace Discord.WebSocket } internal new static SocketNewsChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketNewsChannel(guild.Discord, model.Id, guild); + var entity = new SocketNewsChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs index 56cd92185..4983bc466 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs @@ -49,7 +49,7 @@ namespace Discord.WebSocket internal new static SocketStageChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketStageChannel(guild.Discord, model.Id, guild); + var entity = new SocketStageChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 6aece7d78..2d8aeeae7 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -61,12 +61,12 @@ namespace Discord.WebSocket internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { - if (Discord.MessageCacheSize > 0) + if (Discord?.MessageCacheSize > 0) _messages = new MessageCache(Discord); } internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketTextChannel(guild.Discord, model.Id, guild); + var entity = new SocketTextChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 7bf65d638..9036659fe 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -50,7 +50,7 @@ namespace Discord.WebSocket } internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model) { - var entity = new SocketVoiceChannel(guild.Discord, model.Id, guild); + var entity = new SocketVoiceChannel(guild?.Discord, model.Id, guild); entity.Update(state, model); return entity; } @@ -58,8 +58,8 @@ namespace Discord.WebSocket internal override void Update(ClientState state, Model model) { base.Update(state, model); - Bitrate = model.Bitrate.Value; - UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; + Bitrate = model.Bitrate.GetValueOrDefault(64000); + UserLimit = model.UserLimit.GetValueOrDefault() != 0 ? model.UserLimit.Value : (int?)null; RTCRegion = model.RTCRegion.GetValueOrDefault(null); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs index ad5575caa..0c473bcdd 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs @@ -20,9 +20,7 @@ namespace Discord.WebSocket ? (DataModel)model.Data.Value : null; - ulong? guildId = null; - if (Channel is SocketGuildChannel guildChannel) - guildId = guildChannel.Guild.Id; + ulong? guildId = model.GuildId.ToNullable(); Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs index c33c06f83..70e06f273 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs @@ -20,9 +20,7 @@ namespace Discord.WebSocket ? (DataModel)model.Data.Value : null; - ulong? guildId = null; - if (Channel is SocketGuildChannel guildChannel) - guildId = guildChannel.Guild.Id; + ulong? guildId = model.GuildId.ToNullable(); Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs index 4f9a769c2..2a1a67d04 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs @@ -61,7 +61,9 @@ namespace Discord.WebSocket author = channel.Guild.GetUser(model.Message.Value.Author.Value.Id); } else if (model.Message.Value.Author.IsSpecified) - author = (Channel as SocketChannel).GetUser(model.Message.Value.Author.Value.Id); + author = (Channel as SocketChannel)?.GetUser(model.Message.Value.Author.Value.Id); + + author ??= Discord.State.GetOrAddUser(model.Message.Value.Author.Value.Id, _ => SocketGlobalUser.Create(Discord, Discord.State, model.Message.Value.Author.Value)); Message = SocketUserMessage.Create(Discord, Discord.State, author, Channel, model.Message.Value); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs index b3aa4a826..69f733e85 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs @@ -20,9 +20,7 @@ namespace Discord.WebSocket ? (DataModel)model.Data.Value : null; - ulong? guildId = null; - if (Channel is SocketGuildChannel guildChannel) - guildId = guildChannel.Guild.Id; + ulong? guildId = model.GuildId.ToNullable(); Data = SocketSlashCommandData.Create(client, dataModel, guildId); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs index 8f27b65f4..f6b3f9699 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -19,7 +19,7 @@ namespace Discord.WebSocket /// Gets whether or not this command is a global application command. /// public bool IsGlobalCommand - => Guild == null; + => GuildId is null; /// public ulong ApplicationId { get; private set; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 273f27c9c..bdab128f4 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -43,9 +43,7 @@ namespace Discord.WebSocket ? (DataModel)model.Data.Value : null; - ulong? guildId = null; - if (Channel is SocketGuildChannel guildChannel) - guildId = guildChannel.Guild.Id; + ulong? guildId = model.GuildId.ToNullable(); Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId); } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs index 98a7daefc..2167a69a1 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs @@ -1,3 +1,4 @@ +using Discord.Net; using System.Collections.Generic; namespace Discord.WebSocket @@ -45,13 +46,24 @@ namespace Discord.WebSocket if (socketChannel == null) { - var channelModel = guild != null - ? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() - : discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); - - socketChannel = guild != null - ? SocketGuildChannel.Create(guild, discord.State, channelModel) - : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel); + try + { + var channelModel = guild != null + ? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id) + .ConfigureAwait(false).GetAwaiter().GetResult() + : discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false) + .GetAwaiter().GetResult(); + + socketChannel = guild != null + ? SocketGuildChannel.Create(guild, discord.State, channelModel) + : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel); + } + catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.MissingPermissions) + { + socketChannel = guildId != null + ? SocketGuildChannel.Create(guild, discord.State, channel.Value) + : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channel.Value); + } } discord.State.AddChannel(socketChannel); @@ -73,7 +85,10 @@ namespace Discord.WebSocket { foreach (var role in resolved.Roles.Value) { - var socketRole = guild.AddOrUpdateRole(role.Value); + var socketRole = guild is null + ? SocketRole.Create(null, discord.State, role.Value) + : guild.AddOrUpdateRole(role.Value); + Roles.Add(ulong.Parse(role.Key), socketRole); } } @@ -93,16 +108,19 @@ namespace Discord.WebSocket author = guild.GetUser(msg.Value.Author.Value.Id); } else - author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); + author = (channel as SocketChannel)?.GetUser(msg.Value.Author.Value.Id); if (channel == null) { - if (!msg.Value.GuildId.IsSpecified) // assume it is a DM + if (guildId is null) // assume it is a DM { channel = discord.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, discord.State); + author = ((SocketDMChannel)channel).Recipient; } } + author ??= discord.State.GetOrAddUser(msg.Value.Author.Value.Id, _ => SocketGlobalUser.Create(discord, discord.State, msg.Value.Author.Value)); + var message = SocketMessage.Create(discord, discord.State, author, channel, msg.Value); Messages.Add(message.Id, message); } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index e5776a089..f5abb2c49 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -127,7 +127,7 @@ namespace Discord.WebSocket refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id); } else - refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id); + refMsgAuthor = (Channel as SocketChannel)?.GetUser(refMsg.Author.Value.Id); if (refMsgAuthor == null) refMsgAuthor = SocketUnknownUser.Create(Discord, state, refMsg.Author.Value); } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 1e90b8f5c..b6a61cfb0 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -63,7 +63,7 @@ namespace Discord.WebSocket => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); internal SocketRole(SocketGuild guild, ulong id) - : base(guild.Discord, id) + : base(guild?.Discord, id) { Guild = guild; } From e551431d72838be8a514c417ea98458e6602bdfe Mon Sep 17 00:00:00 2001 From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:44:30 +0300 Subject: [PATCH 20/24] Max/Min length fields for ApplicationCommandOption (#2379) * implement max/min length fields for ApplicationCommandOption * fix badly formed xml comments --- .../Interactions/ApplicationCommandOption.cs | 10 ++++ .../Interactions/IApplicationCommandOption.cs | 10 ++++ .../SlashCommands/SlashCommandBuilder.cs | 51 +++++++++++++++++-- .../Attributes/MaxLengthAttribute.cs | 25 +++++++++ .../Attributes/MinLengthAttribute.cs | 25 +++++++++ .../Builders/ModuleClassBuilder.cs | 6 +++ .../SlashCommandParameterBuilder.cs | 36 +++++++++++++ .../Parameters/SlashCommandParameterInfo.cs | 12 +++++ .../Utilities/ApplicationCommandRestUtil.cs | 12 ++++- .../API/Common/ApplicationCommandOption.cs | 10 ++++ .../RestApplicationCommandOption.cs | 9 ++++ .../SocketApplicationCommandOption.cs | 9 ++++ 12 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs create mode 100644 src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs index 5857bac81..5e4f6a81d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs @@ -81,6 +81,16 @@ namespace Discord /// public double? MaxValue { get; set; } + /// + /// Gets or sets the minimum allowed length for a string input. + /// + public int? MinLength { get; set; } + + ///+ /// Gets or sets the maximum allowed length for a string input. + /// + public int? MaxLength { get; set; } + ////// Gets or sets the choices for string and int types for the user to pick from. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs index 72554fc98..c0a752fdc 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs @@ -47,6 +47,16 @@ namespace Discord /// double? MaxValue { get; } + ///+ /// Gets the minimum allowed length for a string input. + /// + int? MinLength { get; } + + ///+ /// Gets the maximum allowed length for a string input. + /// + int? MaxLength { get; } + ////// Gets the choices for string and int types for the user to pick from. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs index d7d086762..bf22d4e3a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs @@ -196,7 +196,7 @@ namespace Discord ///The current builder. public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type, string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, - Listoptions = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) + int? minLength = null, int? maxLength = null, List options = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) { Preconditions.Options(name, description); @@ -222,6 +222,8 @@ namespace Discord ChannelTypes = channelTypes, MinValue = minValue, MaxValue = maxValue, + MinLength = minLength, + MaxLength = maxLength, }; return AddOption(option); @@ -354,6 +356,16 @@ namespace Discord /// public double? MaxValue { get; set; } + /// + /// Gets or sets the minimum allowed length for a string input. + /// + public int? MinLength { get; set; } + + ///+ /// Gets or sets the maximum allowed length for a string input. + /// + public int? MaxLength { get; set; } + ////// Gets or sets the choices for string and int types for the user to pick from. /// @@ -377,6 +389,7 @@ namespace Discord { bool isSubType = Type == ApplicationCommandOptionType.SubCommandGroup; bool isIntType = Type == ApplicationCommandOptionType.Integer; + bool isStrType = Type == ApplicationCommandOptionType.String; if (isSubType && (Options == null || !Options.Any())) throw new InvalidOperationException("SubCommands/SubCommandGroups must have at least one option"); @@ -390,6 +403,12 @@ namespace Discord if (isIntType && MaxValue != null && MaxValue % 1 != 0) throw new InvalidOperationException("MaxValue cannot have decimals on Integer command options."); + if(isStrType && MinLength is not null && MinLength < 0) + throw new InvalidOperationException("MinLength cannot be smaller than 0."); + + if (isStrType && MaxLength is not null && MaxLength < 1) + throw new InvalidOperationException("MaxLength cannot be smaller than 1."); + return new ApplicationCommandOptionProperties { Name = Name, @@ -404,7 +423,9 @@ namespace Discord IsAutocomplete = IsAutocomplete, ChannelTypes = ChannelTypes, MinValue = MinValue, - MaxValue = MaxValue + MaxValue = MaxValue, + MinLength = MinLength, + MaxLength = MaxLength, }; } @@ -425,7 +446,7 @@ namespace Discord ///The current builder. public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type, string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, - Listoptions = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) + int? minLength = null, int? maxLength = null, List options = null, List channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) { Preconditions.Options(name, description); @@ -447,6 +468,8 @@ namespace Discord IsAutocomplete = isAutocomplete, MinValue = minValue, MaxValue = maxValue, + MinLength = minLength, + MaxLength = maxLength, Options = options, Type = type, Choices = (choices ?? Array.Empty ()).ToList(), @@ -669,6 +692,28 @@ namespace Discord return this; } + /// + /// Sets the current builders min length field. + /// + /// The value to set. + ///The current builder. + public SlashCommandOptionBuilder WithMinLength(int length) + { + MinLength = length; + return this; + } + + ///+ /// Sets the current builders max length field. + /// + /// The value to set. + ///The current builder. + public SlashCommandOptionBuilder WithMaxLength(int lenght) + { + MaxLength = lenght; + return this; + } + ////// Sets the current type of this builder. /// diff --git a/src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs b/src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs new file mode 100644 index 000000000..1099e7d92 --- /dev/null +++ b/src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Discord.Interactions +{ + ///+ /// Sets the maximum length allowed for a string type parameter. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class MaxLengthAttribute : Attribute + { + ///+ /// Gets the maximum length allowed for a string type parameter. + /// + public int Length { get; } + + ///+ /// Sets the maximum length allowed for a string type parameter. + /// + /// Maximum string length allowed. + public MaxLengthAttribute(int lenght) + { + Length = lenght; + } + } +} diff --git a/src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs b/src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs new file mode 100644 index 000000000..7d0b0fd63 --- /dev/null +++ b/src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Discord.Interactions +{ + ///+ /// Sets the minimum length allowed for a string type parameter. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class MinLengthAttribute : Attribute + { + ///+ /// Gets the minimum length allowed for a string type parameter. + /// + public int Length { get; } + + ///+ /// Sets the minimum length allowed for a string type parameter. + /// + /// Minimum string length allowed. + public MinLengthAttribute(int lenght) + { + Length = lenght; + } + } +} diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index 1bbdfcc4a..35126a674 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -463,6 +463,12 @@ namespace Discord.Interactions.Builders case MinValueAttribute minValue: builder.MinValue = minValue.Value; break; + case MinLengthAttribute minLength: + builder.MinLength = minLength.Length; + break; + case MaxLengthAttribute maxLength: + builder.MaxLength = maxLength.Length; + break; case ComplexParameterAttribute complexParameter: { builder.IsComplexParameter = true; diff --git a/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs index d600c9cc7..6f8038cef 100644 --- a/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs @@ -28,6 +28,16 @@ namespace Discord.Interactions.Builders /// public double? MinValue { get; set; } + ///+ /// Gets or sets the minimum length allowed for a string type parameter. + /// + public int? MinLength { get; set; } + + ///+ /// Gets or sets the maximum length allowed for a string type parameter. + /// + public int? MaxLength { get; set; } + ////// Gets a collection of the choices of this command. /// @@ -125,6 +135,32 @@ namespace Discord.Interactions.Builders return this; } + ///+ /// Sets + /// New value of the. + /// . + /// + /// The builder instance. + /// + public SlashCommandParameterBuilder WithMinLength(int length) + { + MinLength = length; + return this; + } + + ///+ /// Sets + /// New value of the. + /// . + /// + /// The builder instance. + /// + public SlashCommandParameterBuilder WithMaxLength(int length) + { + MaxLength = length; + return this; + } + ////// Adds parameter choices to diff --git a/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs b/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs index 8702d69f7..0bce42186 100644 --- a/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs +++ b/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs @@ -38,6 +38,16 @@ namespace Discord.Interactions /// public double? MaxValue { get; } + ///. /// + /// Gets the minimum length allowed for a string type parameter. + /// + public int? MinLength { get; } + + ///+ /// Gets the maximum length allowed for a string type parameter. + /// + public int? MaxLength { get; } + ////// Gets the that will be used to convert the incoming into /// . @@ -86,6 +96,8 @@ namespace Discord.Interactions Description = builder.Description; MaxValue = builder.MaxValue; MinValue = builder.MinValue; + MinLength = builder.MinLength; + MaxLength = builder.MaxLength; IsComplexParameter = builder.IsComplexParameter; IsAutocomplete = builder.Autocomplete; Choices = builder.Choices.ToImmutableArray(); diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs index e4b6f893c..409c0e796 100644 --- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs +++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs @@ -23,7 +23,9 @@ namespace Discord.Interactions ChannelTypes = parameterInfo.ChannelTypes?.ToList(), IsAutocomplete = parameterInfo.IsAutocomplete, MaxValue = parameterInfo.MaxValue, - MinValue = parameterInfo.MinValue + MinValue = parameterInfo.MinValue, + MinLength = parameterInfo.MinLength, + MaxLength = parameterInfo.MaxLength, }; parameterInfo.TypeConverter.Write(props, parameterInfo); @@ -209,7 +211,13 @@ namespace Discord.Interactions Name = x.Name, Value = x.Value }).ToList(), - Options = commandOption.Options?.Select(x => x.ToApplicationCommandOptionProps()).ToList() + Options = commandOption.Options?.Select(x => x.ToApplicationCommandOptionProps()).ToList(), + MaxLength = commandOption.MaxLength, + MinLength = commandOption.MinLength, + MaxValue = commandOption.MaxValue, + MinValue = commandOption.MinValue, + IsAutocomplete = commandOption.IsAutocomplete.GetValueOrDefault(), + ChannelTypes = commandOption.ChannelTypes.ToList(), }; public static Modal ToModal(this ModalInfo modalInfo, string customId, Action modifyModal = null) diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs index d703bd46b..fff5730f4 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs @@ -38,6 +38,12 @@ namespace Discord.API [JsonProperty("channel_types")] public Optional ChannelTypes { get; set; } + [JsonProperty("min_length")] + public Optional MinLength { get; set; } + + [JsonProperty("max_length")] + public Optional MaxLength { get; set; } + public ApplicationCommandOption() { } public ApplicationCommandOption(IApplicationCommandOption cmd) @@ -56,6 +62,8 @@ namespace Discord.API Default = cmd.IsDefault ?? Optional .Unspecified; MinValue = cmd.MinValue ?? Optional .Unspecified; MaxValue = cmd.MaxValue ?? Optional .Unspecified; + MinLength = cmd.MinLength ?? Optional .Unspecified; + MaxLength = cmd.MaxLength ?? Optional .Unspecified; Autocomplete = cmd.IsAutocomplete ?? Optional .Unspecified; Name = cmd.Name; @@ -77,6 +85,8 @@ namespace Discord.API Default = option.IsDefault ?? Optional .Unspecified; MinValue = option.MinValue ?? Optional .Unspecified; MaxValue = option.MaxValue ?? Optional .Unspecified; + MinLength = option.MinLength ?? Optional .Unspecified; + MaxLength = option.MaxLength ?? Optional .Unspecified; ChannelTypes = option.ChannelTypes?.ToArray() ?? Optional .Unspecified; diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs index 86c6019ed..c47080be7 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs @@ -35,6 +35,12 @@ namespace Discord.Rest /// public double? MaxValue { get; private set; } + /// + public int? MinLength { get; private set; } + + /// + public int? MaxLength { get; private set; } + /// /// Gets a collection of @@ -78,6 +84,9 @@ namespace Discord.Rest if (model.Autocomplete.IsSpecified) IsAutocomplete = model.Autocomplete.Value; + MinLength = model.MinLength.ToNullable(); + MaxLength = model.MaxLength.ToNullable(); + Options = model.Options.IsSpecified ? model.Options.Value.Select(Create).ToImmutableArray() : ImmutableArray.Creates for this command. /// (); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs index 27777749a..478c7cb54 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs @@ -33,6 +33,12 @@ namespace Discord.WebSocket /// public double? MaxValue { get; private set; } + /// + public int? MinLength { get; private set; } + + /// + public int? MaxLength { get; private set; } + /// /// Gets a collection of choices for the user to pick from. /// @@ -72,6 +78,9 @@ namespace Discord.WebSocket IsAutocomplete = model.Autocomplete.ToNullable(); + MinLength = model.MinLength.ToNullable(); + MaxLength = model.MaxLength.ToNullable(); + Choices = model.Choices.IsSpecified ? model.Choices.Value.Select(SocketApplicationCommandChoice.Create).ToImmutableArray() : ImmutableArray.Create(); From 02bc3b797745784b41e00789b6da209f3960635f Mon Sep 17 00:00:00 2001 From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> Date: Thu, 4 Aug 2022 11:05:22 +0200 Subject: [PATCH 21/24] Fix NRE on commandbase data assignment (#2414) --- .../Entities/Interactions/CommandBase/RestCommandBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs index 22e56a733..102ede7b7 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs @@ -49,6 +49,9 @@ namespace Discord.Rest internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall) { await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false); + + if (model.Data.IsSpecified && model.Data.Value is RestCommandBaseData data) + Data = data; } /// From 500e7b44caaf9bd47bbbed58a19fbe23dbd8ab64 Mon Sep 17 00:00:00 2001 From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:37:40 +0300 Subject: [PATCH 22/24] Using RespondWithModalAsync () without prior IModal declaration (#2369) * add RespondWithModalAsync method for initializing missing ModalInfos on runtime * update method name and add inline docs --- .../IDiscordInteractionExtensions.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs index 8f0987661..d970b9930 100644 --- a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs +++ b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs @@ -19,9 +19,36 @@ namespace Discord.Interactions if (!ModalUtils.TryGet (out var modalInfo)) throw new ArgumentException($"{typeof(T).FullName} isn't referenced by any registered Modal Interaction Command and doesn't have a cached {typeof(ModalInfo)}"); + await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal); + } + + /// + /// Respond to an interaction with a + ///. + /// + /// This method overload uses the + ///parameter to create a new + /// if there isn't a built one already in cache. + /// Type of the + /// The interaction to respond to. + /// Interaction service instance that should be used to buildimplementation. s. + /// The request options for this request. + /// Delegate that can be used to modify the modal. + /// + public static async Task RespondWithModalAsync (this IDiscordInteraction interaction, string customId, InteractionService interactionService, + RequestOptions options = null, Action modifyModal = null) + where T : class, IModal + { + var modalInfo = ModalUtils.GetOrAdd (interactionService); + + await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal); + } + + private static async Task SendModalResponseAsync(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, RequestOptions options = null, Action modifyModal = null) + { var builder = new ModalBuilder(modalInfo.Title, customId); - foreach(var input in modalInfo.Components) + foreach (var input in modalInfo.Components) switch (input) { case TextInputComponentInfo textComponent: From 8dfe19f32892e60ae259a507e4596b76b1b5003f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gutyina=20Gerg=C5=91?= Date: Mon, 15 Aug 2022 19:33:23 +0200 Subject: [PATCH 23/24] Fix placeholder length being hardcoded (#2421) * Fix placeholder length being hardcoded * Add docs for TextInputBuilder.MaxPlaceholderLength --- .../Interactions/MessageComponents/ComponentBuilder.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs index 37342b039..fd8798ed3 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs @@ -1198,6 +1198,10 @@ namespace Discord public class TextInputBuilder { + /// + /// The max length of a + public const int MaxPlaceholderLength = 100; public const int LargestMaxLength = 4000; ///. + /// @@ -1229,13 +1233,13 @@ namespace Discord /// /// Gets or sets the placeholder of the current text input. /// - ///+ /// is longer than 100 characters public string Placeholder { get => _placeholder; - set => _placeholder = (value?.Length ?? 0) <= 100 + set => _placeholder = (value?.Length ?? 0) <= MaxPlaceholderLength ? value - : throw new ArgumentException("Placeholder cannot have more than 100 characters."); + : throw new ArgumentException($"Placeholder cannot have more than {MaxPlaceholderLength} characters."); } /// is longer than characters From 65b98f8b1251f1cfa64a63c54809a71e4111ad3d Mon Sep 17 00:00:00 2001 From: Bob ///Date: Tue, 16 Aug 2022 03:34:17 +1000 Subject: [PATCH 24/24] Update xmldocs to reflect the ConnectedUsers split (#2418) --- .../Entities/Channels/SocketGuildChannel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 16ed7b32d..808982785 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -36,8 +36,8 @@ namespace Discord.WebSocket /// Gets a collection of users that are able to view the channel. /// - /// If this channel is a voice channel, a collection of users who are currently connected to this channel - /// is returned. + /// If this channel is a voice channel, use ///to retrieve a + /// collection of users who are currently connected to this channel. /// /// A read-only collection of users that can access the channel (i.e. the users seen in the user list).